Skip to content

Commit

Permalink
Feature: PIN-5747 - Attributi in-add (#1021)
Browse files Browse the repository at this point in the history
* Added in-add attributes drawer

* Update AttributeAutocomplete.tsx

* fix type error

* Design fixes
  • Loading branch information
Carminepo2 authored Dec 9, 2024
1 parent c3407d4 commit cf5cd13
Show file tree
Hide file tree
Showing 21 changed files with 378 additions and 100 deletions.
27 changes: 27 additions & 0 deletions src/api/eservice/eservice.mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
import type { EServiceRiskAnalysisSeed, UpdateEServiceDescriptorSeed } from '../api.generatedTypes'
import { EServiceServices } from './eservice.services'
import { EServiceQueries } from './eservice.queries'
import type { AttributeKey } from '@/types/attribute.types'

function useCreateDraft() {
const { t } = useTranslation('mutations-feedback', { keyPrefix: 'eservice.createDraft' })
Expand Down Expand Up @@ -306,6 +307,31 @@ function useUpdateEServiceDescription() {
})
}

function useUpdateDescriptorAttributes() {
const { t } = useTranslation('mutations-feedback', {
keyPrefix: 'eservice.updateDescriptorAttributes',
})
const { t: tAttribute } = useTranslation('attribute', { keyPrefix: 'type' })
return useMutation({
mutationFn: EServiceServices.updateDescriptorAttributes,
meta: {
successToastLabel: (_: unknown, variables: unknown) =>
t('outcome.success', {
attributeKind: tAttribute(
`${(variables as { attributeKey: AttributeKey }).attributeKey}_other`
),
}),
errorToastLabel: (_: unknown, variables: unknown) =>
t('outcome.error', {
attributeKind: tAttribute(
`${(variables as { attributeKey: AttributeKey }).attributeKey}_other`
),
}),
loadingLabel: t('loading'),
},
})
}

export const EServiceMutations = {
useCreateDraft,
useUpdateDraft,
Expand All @@ -326,4 +352,5 @@ export const EServiceMutations = {
useUpdateEServiceDescription,
useUpdateVersionDraftDocumentDescription,
useImportVersion,
useUpdateDescriptorAttributes,
}
19 changes: 19 additions & 0 deletions src/api/eservice/eservice.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
CreatedEServiceDescriptor,
CreatedResource,
CreateEServiceDocumentPayload,
DescriptorAttributesSeed,
EServiceDescriptionSeed,
EServiceDoc,
EServiceRiskAnalysis,
Expand All @@ -26,6 +27,7 @@ import type {
UpdateEServiceDescriptorSeed,
UpdateEServiceSeed,
} from '../api.generatedTypes'
import type { AttributeKey } from '@/types/attribute.types'

async function getCatalogList(params: GetEServicesCatalogParams) {
const response = await axiosInstance.get<CatalogEServices>(
Expand Down Expand Up @@ -389,6 +391,22 @@ async function importVersion({ eserviceFile }: { eserviceFile: File }) {
})
}

async function updateDescriptorAttributes({
eserviceId,
descriptorId,
attributeKey: _attributeKey,
...payload
}: {
eserviceId: string
descriptorId: string
attributeKey: AttributeKey
} & DescriptorAttributesSeed) {
return axiosInstance.post<void>(
`${BACKEND_FOR_FRONTEND_URL}/eservices/${eserviceId}/descriptors/${descriptorId}/attributes/update`,
payload
)
}

export const EServiceServices = {
getCatalogList,
getProviderList,
Expand Down Expand Up @@ -420,4 +438,5 @@ export const EServiceServices = {
updateEServiceDescription,
exportVersion,
importVersion,
updateDescriptorAttributes,
}
3 changes: 2 additions & 1 deletion src/components/layout/containers/AttributeContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const AttributeContainer = <TAttribute extends { id: string; name: string
}

return (
<Stack direction="row" alignItems="center" spacing={2}>
<Stack direction="row" alignItems="center">
<Stack direction="row" alignItems="center" spacing={2}>
{checked && <CheckCircleIcon sx={{ color: 'success.main' }} />}
{onRemove && (
Expand Down Expand Up @@ -130,6 +130,7 @@ const AttributeDetails: React.FC<{ attributeId: string }> = ({ attributeId }) =>
<Stack sx={{ mt: 1 }} spacing={2}>
<Typography variant="body2">{attribute.description}</Typography>
<InformationContainer
direction="column"
content={attributeId}
copyToClipboard={{
value: attributeId,
Expand Down
14 changes: 11 additions & 3 deletions src/components/layout/containers/AttributeGroupContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React from 'react'
import ClearIcon from '@mui/icons-material/Clear'
import type { CardProps, SxProps } from '@mui/material'
import { Card, CardContent, CardHeader, IconButton, alpha } from '@mui/material'
import { theme } from '@pagopa/interop-fe-commons'
import { useTranslation } from 'react-i18next'

interface AttributeGroupContainerProps {
type AttributeGroupContainerProps = CardProps & {
title: string
onRemove?: () => void
subheader?: React.ReactNode
children?: React.ReactNode
color?: 'primary' | 'success' | 'error' | 'warning' | 'gray'
cardContentSx?: SxProps
}

const containerColors = {
Expand Down Expand Up @@ -50,13 +52,18 @@ export const AttributeGroupContainer: React.FC<AttributeGroupContainerProps> = (
onRemove,
children,
subheader,
cardContentSx,
color = 'primary',
...cardProps
}) => {
const { t } = useTranslation('shared-components', { keyPrefix: 'attributeGroupContainer' })
const { headerColor, borderColor, bodyColor, textColor } = containerColors[color]

return (
<Card sx={{ border: '1px solid', borderColor, bgcolor: bodyColor }}>
<Card
{...cardProps}
sx={{ border: '1px solid', borderColor, bgcolor: bodyColor, ...cardProps.sx }}
>
<CardHeader
titleTypographyProps={{ variant: 'body1', fontWeight: 600, color: textColor }}
title={title}
Expand All @@ -75,8 +82,9 @@ export const AttributeGroupContainer: React.FC<AttributeGroupContainerProps> = (
sx={{
p: 2,
'&:last-child': {
paddingBottom: 2,
pb: 2,
},
...cardContentSx,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,30 @@ import { AttributeQueries } from '@/api/attribute'
import { RHFAutocompleteSingle } from '@/components/shared/react-hook-form-inputs'
import type { AttributeKey } from '@/types/attribute.types'
import { Button, Stack } from '@mui/material'
import { FormProvider, useForm, useFormContext } from 'react-hook-form'
import { FormProvider, useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import type { AttributeKind, DescriptorAttribute } from '@/api/api.generatedTypes'
import { useAutocompleteTextInput } from '@pagopa/interop-fe-commons'
import type { EServiceCreateStepAttributesFormValues } from '..'
import { keepPreviousData, useQuery } from '@tanstack/react-query'

export type AttributeAutocompleteProps = {
groupIndex: number
attributeKey: AttributeKey
handleHideAutocomplete: VoidFunction
onAddAttribute: (attribute: DescriptorAttribute) => void
alreadySelectedAttributeIds: string[]
direction?: 'column' | 'row'
}

type AttributeAutocompleteFormValues = { attribute: null | DescriptorAttribute }

export const AttributeAutocomplete: React.FC<AttributeAutocompleteProps> = ({
groupIndex,
attributeKey,
handleHideAutocomplete,
onAddAttribute,
alreadySelectedAttributeIds,
direction = 'row',
}) => {
const { t } = useTranslation('attribute', { keyPrefix: 'group' })
const [attributeSearchParam, setAttributeSearchParam] = useAutocompleteTextInput()

const { watch, setValue } = useFormContext<EServiceCreateStepAttributesFormValues>()
const attributeGroups = watch(`attributes.${attributeKey}`)

const attributeAutocompleteFormMethods = useForm<AttributeAutocompleteFormValues>({
defaultValues: { attribute: null },
})
Expand Down Expand Up @@ -62,29 +60,26 @@ export const AttributeAutocomplete: React.FC<AttributeAutocompleteProps> = ({

const handleAddAttributeToGroup = handleSubmit(({ attribute }) => {
if (!attribute) return
const newAttributeGroups = [...attributeGroups]
newAttributeGroups[groupIndex].push(attribute)
setValue(`attributes.${attributeKey}`, newAttributeGroups)
handleHideAutocomplete()
onAddAttribute(attribute)
})

const options = React.useMemo(() => {
const attributes = data?.results ?? []
const attributesAlreadyInGroups = attributeGroups.reduce(
(acc, group) => [...acc, ...group.map(({ id }) => id)],
[] as Array<string>
)
return attributes
.filter((att) => !attributesAlreadyInGroups.includes(att.id))
.filter((att) => !alreadySelectedAttributeIds.includes(att.id))
.map((att) => ({
label: att.name,
value: att,
}))
}, [data?.results, attributeGroups])
}, [data?.results, alreadySelectedAttributeIds])

return (
<FormProvider {...attributeAutocompleteFormMethods}>
<Stack direction="row" alignItems="center" spacing={1}>
<Stack
direction={direction}
alignItems={direction === 'column' ? 'start' : 'center'}
spacing={1}
>
<RHFAutocompleteSingle
label={t('autocompleteInput.label')}
placeholder={t('autocompleteInput.placeholder')}
Expand Down
87 changes: 42 additions & 45 deletions src/components/shared/ReadOnlyDescriptorAttributes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@/components/layout/containers'
import type { DescriptorAttribute, DescriptorAttributes } from '@/api/api.generatedTypes'
import { useCurrentRoute } from '@/router'
import type { ProviderOrConsumer } from '@/types/common.types'
import type { ActionItemButton, ProviderOrConsumer } from '@/types/common.types'
import { attributesHelpLink } from '@/config/constants'

type ReadOnlyDescriptorAttributesProps = {
Expand All @@ -19,76 +19,73 @@ type ReadOnlyDescriptorAttributesProps = {
export const ReadOnlyDescriptorAttributes: React.FC<ReadOnlyDescriptorAttributesProps> = ({
descriptorAttributes,
}) => {
const { t: tAttribute } = useTranslation('attribute')
const { mode } = useCurrentRoute()

const providerOrConsumer = mode as ProviderOrConsumer

const getSubtitle = (attributeKey: AttributeKey) => {
return (
<Trans
components={{ 1: <Link underline="hover" href={attributesHelpLink} target="_blank" /> }}
>
{tAttribute(`${attributeKey}.description`)}
</Trans>
)
}

return (
<>
<AttributeGroupsListSection
title={tAttribute('certified.label')}
subtitle={getSubtitle('certified')}
attributeGroups={descriptorAttributes.certified}
emptyLabel={tAttribute(`noAttributesRequiredAlert.${providerOrConsumer}`, {
attributeKey: tAttribute(`type.certified_other`),
})}
descriptorAttributes={descriptorAttributes}
attributeKey="certified"
/>
<Divider sx={{ my: 3 }} />
<AttributeGroupsListSection
title={tAttribute('verified.label')}
subtitle={getSubtitle('verified')}
attributeGroups={descriptorAttributes.verified}
emptyLabel={tAttribute(`noAttributesRequiredAlert.${providerOrConsumer}`, {
attributeKey: tAttribute(`type.verified_other`),
})}
descriptorAttributes={descriptorAttributes}
attributeKey="verified"
/>
<Divider sx={{ my: 3 }} />
<AttributeGroupsListSection
title={tAttribute('declared.label')}
subtitle={getSubtitle('declared')}
attributeGroups={descriptorAttributes.declared}
emptyLabel={tAttribute(`noAttributesRequiredAlert.${providerOrConsumer}`, {
attributeKey: tAttribute(`type.declared_other`),
})}
descriptorAttributes={descriptorAttributes}
attributeKey="declared"
/>
</>
)
}

type AttributeGroupsListSectionProps = {
attributeGroups: Array<Array<DescriptorAttribute>>
title: string
subtitle: React.ReactNode
emptyLabel: string
descriptorAttributes: DescriptorAttributes
attributeKey: AttributeKey
topSideActions?: Array<ActionItemButton>
}

const AttributeGroupsListSection: React.FC<AttributeGroupsListSectionProps> = ({
attributeGroups,
title,
subtitle,
emptyLabel,
export const AttributeGroupsListSection: React.FC<AttributeGroupsListSectionProps> = ({
descriptorAttributes,
attributeKey,
topSideActions,
}) => {
const { t: tAttribute } = useTranslation('attribute')

const { mode } = useCurrentRoute()

const providerOrConsumer = mode as ProviderOrConsumer

const attributeGroups = descriptorAttributes[attributeKey]

return (
<SectionContainer innerSection title={title} description={subtitle}>
<SectionContainer
innerSection
title={tAttribute(`${attributeKey}.label`)}
description={
<Trans
components={{ 1: <Link underline="hover" href={attributesHelpLink} target="_blank" /> }}
>
{tAttribute(`${attributeKey}.description`)}
</Trans>
}
topSideActions={topSideActions}
>
{attributeGroups.length > 0 && (
<Stack spacing={3}>
{attributeGroups.map((attributeGroup, index) => (
<AttributeGroup key={index} attributes={attributeGroup} index={index} />
))}
</Stack>
)}
{attributeGroups.length === 0 && <AttributeGroupContainer title={emptyLabel} color="gray" />}
{attributeGroups.length === 0 && (
<AttributeGroupContainer
title={tAttribute(`noAttributesRequiredAlert.${providerOrConsumer}`, {
attributeKey: tAttribute(`type.${attributeKey}_other`),
})}
color="gray"
/>
)}
</SectionContainer>
)
}
Expand Down
Loading

0 comments on commit cf5cd13

Please sign in to comment.