diff --git a/package-lock.json b/package-lock.json index 53c56f4c4..2151686c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "interop-dashboard-frontend", - "version": "1.6.2", + "version": "1.6.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "interop-dashboard-frontend", - "version": "1.6.2", + "version": "1.6.3", "dependencies": { "@date-io/date-fns": "^2.17.0", "@emotion/react": "^11.14.0", diff --git a/src/components/layout/containers/AttributeContainer.tsx b/src/components/layout/containers/AttributeContainer.tsx index 8153329c5..0625b3540 100644 --- a/src/components/layout/containers/AttributeContainer.tsx +++ b/src/components/layout/containers/AttributeContainer.tsx @@ -11,6 +11,7 @@ import { Skeleton, Stack, Typography, + Button, } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { ButtonNaked } from '@pagopa/mui-italia' @@ -21,7 +22,9 @@ import { InformationContainer } from '@pagopa/interop-fe-commons' import { useTranslation } from 'react-i18next' import { useQuery, useQueryClient } from '@tanstack/react-query' -type AttributeContainerProps = { +type AttributeContainerProps< + TAttribute extends { id: string; name: string; dailyCallsPerConsumer?: number }, +> = { attribute: TAttribute actions?: Array<{ label: React.ReactNode @@ -31,14 +34,18 @@ type AttributeContainerProps = chipLabel?: string checked?: boolean onRemove?: (id: string, name: string) => void + onCustomizeThreshold?: VoidFunction } -export const AttributeContainer = ({ +export const AttributeContainer = < + TAttribute extends { id: string; name: string; dailyCallsPerConsumer?: number }, +>({ attribute, actions, chipLabel, checked, onRemove, + onCustomizeThreshold, }: AttributeContainerProps) => { const { t } = useTranslation('shared-components', { keyPrefix: 'attributeContainer' }) const panelContentId = React.useId() @@ -83,7 +90,45 @@ export const AttributeContainer = - {attribute.name} + + {attribute.name} + {onCustomizeThreshold && ( + + {attribute.dailyCallsPerConsumer && ( + + + {t('thresholdLabel')} + + + {attribute.dailyCallsPerConsumer} + + + )} + + + )} + {hasExpandedOnce && } diff --git a/src/components/shared/AddAttributesToForm/AddAttributesToForm.tsx b/src/components/shared/AddAttributesToForm/AddAttributesToForm.tsx index 45a8ef821..9e5972f4b 100644 --- a/src/components/shared/AddAttributesToForm/AddAttributesToForm.tsx +++ b/src/components/shared/AddAttributesToForm/AddAttributesToForm.tsx @@ -6,23 +6,25 @@ import { SectionContainer } from '@/components/layout/containers' import { Box, Button, Link, Stack } from '@mui/material' import { attributesHelpLink } from '@/config/constants' import { AttributeGroup } from './AttributeGroup' -import type { CreateStepAttributesFormValues } from '@/pages/ProviderEServiceCreatePage/components/EServiceCreateStepAttributes' +import { type CreateStepThresholdsFormValues } from '@/pages/ProviderEServiceCreatePage/components/EServiceCreateStepThresholds' export type AddAttributesToFormProps = { attributeKey: AttributeKey readOnly: boolean + withThreshold?: boolean openCreateAttributeDrawer?: VoidFunction } export const AddAttributesToForm: React.FC = ({ attributeKey, readOnly, + withThreshold, openCreateAttributeDrawer, }) => { const { t } = useTranslation('eservice', { keyPrefix: `create.step3` }) const { t: tAttribute } = useTranslation('attribute') - const { watch, setValue } = useFormContext() + const { watch, setValue } = useFormContext() const attributeGroups = watch(`attributes.${attributeKey}`) @@ -68,6 +70,7 @@ export const AddAttributesToForm: React.FC = ({ group={group} attributeKey={attributeKey} readOnly={readOnly} + withThreshold={withThreshold} onRemoveAttributesGroup={handleRemoveAttributesGroup} onRemoveAttributeFromGroup={handleRemoveAttributeFromGroup} /> diff --git a/src/components/shared/AddAttributesToForm/AttributeGroup.tsx b/src/components/shared/AddAttributesToForm/AttributeGroup.tsx index a328d9c8d..a136a7d5a 100644 --- a/src/components/shared/AddAttributesToForm/AttributeGroup.tsx +++ b/src/components/shared/AddAttributesToForm/AttributeGroup.tsx @@ -9,12 +9,14 @@ import { AttributeAutocomplete } from '../AttributeAutocomplete' import type { DescriptorAttribute } from '@/api/api.generatedTypes' import { useFormContext } from 'react-hook-form' import type { CreateStepAttributesFormValues } from '@/pages/ProviderEServiceCreatePage/components/EServiceCreateStepAttributes' +import { useCustomizeThresholdDrawer } from '../CustomizeThresholdDrawer' export type AttributeGroupProps = { group: Array groupIndex: number attributeKey: AttributeKey readOnly: boolean + withThreshold?: boolean onRemoveAttributesGroup: (groupIndex: number) => void onRemoveAttributeFromGroup: (attributeId: string, groupIndex: number) => void } @@ -24,11 +26,13 @@ export const AttributeGroup: React.FC = ({ groupIndex, attributeKey, readOnly, + withThreshold, onRemoveAttributesGroup, onRemoveAttributeFromGroup, }) => { const { t } = useTranslation('attribute', { keyPrefix: 'group' }) const [isAttributeAutocompleteShown, setIsAttributeAutocompleteShown] = React.useState(false) + const { open } = useCustomizeThresholdDrawer() const handleDeleteAttributesGroup = () => { onRemoveAttributesGroup(groupIndex) @@ -63,6 +67,7 @@ export const AttributeGroup: React.FC = ({ onRemove={ !readOnly ? handleDeleteAttributeFromGroup.bind(null, attribute.id) : undefined } + onCustomizeThreshold={withThreshold ? () => open(attribute, groupIndex) : undefined} /> ))} diff --git a/src/components/shared/CustomizeThresholdDrawer.tsx b/src/components/shared/CustomizeThresholdDrawer.tsx new file mode 100644 index 000000000..4409ae57b --- /dev/null +++ b/src/components/shared/CustomizeThresholdDrawer.tsx @@ -0,0 +1,193 @@ +import React, { useEffect } from 'react' +import { FormProvider, type SubmitHandler, useForm } from 'react-hook-form' +import { Drawer } from '@/components/shared/Drawer' +import { Alert, Stack, Button, Typography } from '@mui/material' +import { RHFTextField } from './react-hook-form-inputs' +import { useTranslation } from 'react-i18next' +import { type DescriptorAttribute } from '@/api/api.generatedTypes' +import { WarningAmber } from '@mui/icons-material' +import { create } from 'zustand' +import { isEmpty } from 'lodash' + +export type CustomizeThresholdDrawerProps = { + dailyCallsPerConsumer?: number + dailyCallsTotal?: number + onSubmit: (threshold: number) => void +} + +type CustomizeThresholdDrawerStore = { + isOpen: boolean + open: (attribute: DescriptorAttribute, attributeGroupIndex: number) => void + close: VoidFunction + attribute?: DescriptorAttribute + attributeGroupIndex?: number +} + +export const useCustomizeThresholdDrawer = create((set) => ({ + isOpen: false, + open: (attribute, attributeGroupIndex) => set({ attribute, attributeGroupIndex, isOpen: true }), + close: () => set({ isOpen: false }), +})) + +type CustomizeThresholdFormValues = { + threshold: number +} + +export const CustomizeThresholdDrawer: React.FC = ({ + onSubmit, + dailyCallsPerConsumer, + dailyCallsTotal, +}) => { + const { isOpen, close, attribute } = useCustomizeThresholdDrawer() + const { t } = useTranslation('eservice', { + keyPrefix: 'create.step2.attributes.customizeThresholdDrawer', + }) + const formMethods = useForm({ + defaultValues: { + threshold: attribute?.dailyCallsPerConsumer, + }, + }) + + const handleFormSubmit: SubmitHandler = (values) => { + onSubmit(values.threshold) + } + + useEffect(() => { + if (isOpen) { + formMethods.reset({ + threshold: attribute?.dailyCallsPerConsumer, + }) + } + }, [isOpen, formMethods, attribute]) + + return ( + + + + + + + + + {t('limitAlert.title')} + + + + {t('limitAlert.totalLimit')} + + {dailyCallsTotal ? ( + + {t('limitAlert.label', { threshold: dailyCallsTotal })} + + ) : ( + + + + {t('limitAlert.toInsert')} + + + )} + + + + {t('limitAlert.consumerLimit')} + + {dailyCallsPerConsumer ? ( + + {t('limitAlert.label', { threshold: dailyCallsPerConsumer })} + + ) : ( + + + + {t('limitAlert.toInsert')} + + + )} + + + + + + {t('alert')} + + + + + + ) +} diff --git a/src/pages/ProviderEServiceCreatePage/ProviderEServiceCreate.page.tsx b/src/pages/ProviderEServiceCreatePage/ProviderEServiceCreate.page.tsx index 076566f19..eab84f089 100644 --- a/src/pages/ProviderEServiceCreatePage/ProviderEServiceCreate.page.tsx +++ b/src/pages/ProviderEServiceCreatePage/ProviderEServiceCreate.page.tsx @@ -33,6 +33,10 @@ import type { EServiceMode } from '@/api/api.generatedTypes' import { useQuery } from '@tanstack/react-query' import { EServiceCreateFromTemplateStepPurpose } from './components/EServiceCreateStepPurpose/EServiceCreateFromTemplateStepPurpose' import { EServiceTemplateQueries } from '@/api/eserviceTemplate' +import { + EServiceCreateStepThresholds, + EServiceCreateStepThresholdsSkeleton, +} from './components/EServiceCreateStepThresholds' const ProviderEServiceCreatePage: React.FC = () => { const { t } = useTranslation('eservice') @@ -76,7 +80,7 @@ const ProviderEServiceCreatePage: React.FC = () => { eserviceMode === 'DELIVER' ? [ { label: t('create.stepper.step1Label'), component: EServiceCreateStepGeneral }, - { label: t('create.stepper.step2Label'), component: EServiceCreateStepVersion }, + { label: t('create.stepper.step2Label'), component: EServiceCreateStepThresholds }, { label: t('create.stepper.step3Label'), component: EServiceCreateStepAttributes }, { label: t('create.stepper.step4Label'), component: CreateStepDocuments }, ] @@ -86,7 +90,7 @@ const ProviderEServiceCreatePage: React.FC = () => { label: t('create.stepper.step2ReceiveLabel'), component: CreateStepPurpose, }, - { label: t('create.stepper.step2Label'), component: EServiceCreateStepVersion }, + { label: t('create.stepper.step2Label'), component: EServiceCreateStepThresholds }, { label: t('create.stepper.step3Label'), component: EServiceCreateStepAttributes }, { label: t('create.stepper.step4Label'), component: CreateStepDocuments }, ] @@ -117,14 +121,14 @@ const ProviderEServiceCreatePage: React.FC = () => { eserviceMode === 'DELIVER' ? [ , - , + , , , ] : [ , , - , + , , , ] diff --git a/src/pages/ProviderEServiceCreatePage/__test__/ProviderEServiceCreate.test.tsx b/src/pages/ProviderEServiceCreatePage/__test__/ProviderEServiceCreate.test.tsx new file mode 100644 index 000000000..9cd9bd170 --- /dev/null +++ b/src/pages/ProviderEServiceCreatePage/__test__/ProviderEServiceCreate.test.tsx @@ -0,0 +1,58 @@ +import { mockUseJwt, renderWithApplicationContext } from '@/utils/testing.utils' +import ProviderEServiceCreatePage from '../ProviderEServiceCreate.page' +import { screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' + +mockUseJwt() + +describe('Provider E-service create page', () => { + it('Should be visible section', () => { + renderWithApplicationContext(, { + withRouterContext: true, + withReactQueryContext: true, + }) + + expect(screen.getByText('create.step1.detailsTitle')) + expect(screen.getByText('create.step1.delegationSection.title')) + expect(screen.getByText('create.step1.isSignalHubEnabled.title')) + }) + + it('Should navigate steps', () => { + const user = userEvent.setup() + renderWithApplicationContext(, { + withRouterContext: true, + withReactQueryContext: true, + }) + + const nameInput = screen.getByLabelText(/eserviceNameField/i) + user.type(nameInput, 'My test eservice') + + const descriptionInput = screen.getByLabelText(/eserviceTechnologyField/i) + user.type(descriptionInput, 'This is a test description for the eservice') + + // const noPersonalDataRadio = screen.getByLabelText(/eservicePersonalDataField.*.option.false/i) + // user.click(noPersonalDataRadio) + // + // expect(noPersonalDataRadio).toBeChecked() + + const nextButton = screen.getByText(/forwardWithSaveBtn/i) + + user.click(nextButton) + + waitFor(() => { + expect(screen.getByText(/step2.thresholdSection.title/i)).toBeInTheDocument() + + const dailyCallsPerConsumerInput = screen.getByLabelText(/dailyCallsPerConsumerField/i) + user.type(dailyCallsPerConsumerInput, '20') + + const dailyCallsTotalInput = screen.getByLabelText(/dailyCallsTotalField/i) + user.type(dailyCallsTotalInput, '20') + + user.click(nextButton) + + waitFor(() => { + expect(screen.getByText(/step3/i)).toBeInTheDocument() + }) + }) + }) +}) diff --git a/src/pages/ProviderEServiceCreatePage/components/EServiceCreateStepThresholds/EServiceCreateStepThresholds.tsx b/src/pages/ProviderEServiceCreatePage/components/EServiceCreateStepThresholds/EServiceCreateStepThresholds.tsx new file mode 100644 index 000000000..4c7e728fc --- /dev/null +++ b/src/pages/ProviderEServiceCreatePage/components/EServiceCreateStepThresholds/EServiceCreateStepThresholds.tsx @@ -0,0 +1,178 @@ +import { type ActiveStepProps } from '@/hooks/useActiveStep' +import { useTranslation } from 'react-i18next' +import { useEServiceCreateContext } from '../EServiceCreateContext' +import { EServiceMutations } from '@/api/eservice' +import { type SubmitHandler, useForm } from 'react-hook-form' +import { FormProvider } from 'react-hook-form' +import React from 'react' +import { type AttributeKey } from '@/types/attribute.types' +import { + type ProducerEServiceDescriptor, + type DescriptorAttribute, + type DescriptorAttributes, + type UpdateEServiceDescriptorSeed, +} from '@/api/api.generatedTypes' +import { Box } from '@mui/material' +import { StepActions } from '@/components/shared/StepActions' +import ArrowBackIcon from '@mui/icons-material/ArrowBack' +import SaveIcon from '@mui/icons-material/Save' +import { CreateAttributeDrawer } from '@/components/shared/CreateAttributeDrawer' +import { compareObjects } from '@/utils/common.utils' +import { remapDescriptorAttributesToDescriptorAttributesSeed } from '@/utils/attribute.utils' +import { + CustomizeThresholdDrawer, + useCustomizeThresholdDrawer, +} from '@/components/shared/CustomizeThresholdDrawer' +import { ThresholdSection } from '../sections/ThresholdSection' +import { AttributesSection } from '../sections/AttributesSection' +import { SectionContainerSkeleton } from '@/components/layout/containers' + +export type CreateStepThresholdsFormValues = { + dailyCallsPerConsumer?: number + dailyCallsTotal?: number + attributes: DescriptorAttributes +} + +export const EServiceCreateStepThresholds: React.FC = () => { + const { t } = useTranslation('eservice', { keyPrefix: 'create' }) + const { descriptor, forward, back } = useEServiceCreateContext() + + const { mutate: updateVersionDraft } = EServiceMutations.useUpdateVersionDraft({ + suppressSuccessToast: true, + }) + + const formMethods = useForm({ + defaultValues: { + dailyCallsPerConsumer: descriptor?.dailyCallsPerConsumer, + dailyCallsTotal: descriptor?.dailyCallsTotal, + attributes: descriptor?.attributes ?? { certified: [], verified: [], declared: [] }, + }, + }) + + const { + attribute, + attributeGroupIndex, + close: closeCustomizeThresholdDrawer, + } = useCustomizeThresholdDrawer() + + const isEServiceCreatedFromTemplate = Boolean(descriptor?.templateRef?.templateVersionId) + + const [createAttributeCreateDrawerState, setCreateAttributeCreateDrawerState] = React.useState<{ + attributeKey: Exclude + isOpen: boolean + }>({ + attributeKey: 'verified', + isOpen: false, + }) + + const handleCloseAttributeCreateDrawer = () => { + setCreateAttributeCreateDrawerState((prevState) => ({ ...prevState, isOpen: false })) + } + + const handleOpenAttributeCreateDrawerFactory = + (attributeKey: Exclude) => () => { + setCreateAttributeCreateDrawerState({ attributeKey, isOpen: true }) + } + + const handleSubmitCustomizeThresholdDrawer = (threshold: number) => { + if (!attribute || attributeGroupIndex === undefined) return + + const attributes = formMethods.getValues('attributes') + const groups = attributes['certified'] + const group = groups[attributeGroupIndex] + + groups[attributeGroupIndex] = group.map((att) => + att.id === attribute.id ? { ...att, dailyCallsPerConsumer: threshold } : att + ) + + formMethods.setValue(`attributes.certified`, groups, { + shouldValidate: false, + }) + closeCustomizeThresholdDrawer() + } + + const dailyCallsPerConsumer = formMethods.watch('dailyCallsPerConsumer') + const dailyCallsTotal = formMethods.watch('dailyCallsTotal') + + const onSubmit: SubmitHandler = (values) => { + if (!descriptor) return + + const removeEmptyAttributeGroups = (attributes: Array>) => { + return attributes.filter((group) => group.length > 0) + } + + const attributes = { + certified: removeEmptyAttributeGroups(values.attributes.certified), + verified: removeEmptyAttributeGroups(values.attributes.verified), + declared: removeEmptyAttributeGroups(values.attributes.declared), + } + + const newDescriptorData: ProducerEServiceDescriptor = { + ...descriptor, + dailyCallsPerConsumer: values.dailyCallsPerConsumer ?? 1, + dailyCallsTotal: values.dailyCallsTotal ?? 1, + attributes, + } + + const areDescriptorsEquals = compareObjects(newDescriptorData, descriptor) + if (areDescriptorsEquals) { + forward() + return + } + + const payload: UpdateEServiceDescriptorSeed & { + eserviceId: string + descriptorId: string + } = { + audience: [], + voucherLifespan: 0, + dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer, + dailyCallsTotal: descriptor.dailyCallsTotal, + agreementApprovalPolicy: descriptor.agreementApprovalPolicy, + description: descriptor.description, + attributes: remapDescriptorAttributesToDescriptorAttributesSeed(attributes), + eserviceId: descriptor.eservice.id, + descriptorId: descriptor.id, + } + + updateVersionDraft(payload, { onSuccess: forward }) + } + + return ( + <> + + + + + , + }} + forward={{ label: t('forwardWithSaveBtn'), type: 'submit', startIcon: }} + /> + + + + + + + ) +} + +export const EServiceCreateStepThresholdsSkeleton: React.FC = () => { + return +} diff --git a/src/pages/ProviderEServiceCreatePage/components/EServiceCreateStepThresholds/index.ts b/src/pages/ProviderEServiceCreatePage/components/EServiceCreateStepThresholds/index.ts new file mode 100644 index 000000000..96f521a20 --- /dev/null +++ b/src/pages/ProviderEServiceCreatePage/components/EServiceCreateStepThresholds/index.ts @@ -0,0 +1 @@ +export * from './EServiceCreateStepThresholds' diff --git a/src/pages/ProviderEServiceCreatePage/components/sections/AttributesSection.tsx b/src/pages/ProviderEServiceCreatePage/components/sections/AttributesSection.tsx new file mode 100644 index 000000000..bd82e929c --- /dev/null +++ b/src/pages/ProviderEServiceCreatePage/components/sections/AttributesSection.tsx @@ -0,0 +1,62 @@ +import { SectionContainer } from '@/components/layout/containers' +import { AddAttributesToForm } from '@/components/shared/AddAttributesToForm' +import { useActiveTab } from '@/hooks/useActiveTab' +import { type AttributeKey } from '@/types/attribute.types' +import { TabContext, TabList, TabPanel } from '@mui/lab' +import { Tab } from '@mui/material' +import { useTranslation } from 'react-i18next' + +type AttributesSectionProps = { + version?: string + isEServiceCreatedFromTemplate: boolean + handleOpenAttributeCreateDrawerFactory: ( + attributeKey: Exclude + ) => () => void +} + +export const AttributesSection: React.FC = ({ + version, + isEServiceCreatedFromTemplate, + handleOpenAttributeCreateDrawerFactory, +}) => { + const { t } = useTranslation('eservice', { keyPrefix: 'create' }) + + const { activeTab, updateActiveTab } = useActiveTab('certified') + + return ( + + + + + + + + + + + + + + + + + + + ) +} diff --git a/src/pages/ProviderEServiceCreatePage/components/sections/ThresholdSection.tsx b/src/pages/ProviderEServiceCreatePage/components/sections/ThresholdSection.tsx new file mode 100644 index 000000000..028f81998 --- /dev/null +++ b/src/pages/ProviderEServiceCreatePage/components/sections/ThresholdSection.tsx @@ -0,0 +1,43 @@ +import { SectionContainer } from '@/components/layout/containers' +import { RHFTextField } from '@/components/shared/react-hook-form-inputs' +import { Stack } from '@mui/material' +import { useFormContext } from 'react-hook-form' +import { useTranslation } from 'react-i18next' + +export const ThresholdSection: React.FC = () => { + const { t } = useTranslation('eservice', { keyPrefix: 'create' }) + const { watch } = useFormContext() + + const dailyCallsPerConsumer = watch('dailyCallsPerConsumer') + + return ( + + + + + + + ) +} diff --git a/src/static/locales/en/eservice.json b/src/static/locales/en/eservice.json index 2a014c5a7..cbfe36c6b 100644 --- a/src/static/locales/en/eservice.json +++ b/src/static/locales/en/eservice.json @@ -149,6 +149,32 @@ "agreementApprovalPolicySection": { "title": "Do you want to manually activate all the fruition requests received?", "label": "Yes, I will activate them manually" + }, + "attributes": { + "tabs": { + "ariaLabel": "Three different tabs to manage certified, verified and declared attributes", + "certified": "Certified attributes", + "verified": "Verified attributes", + "declared": "Declared attributes" + }, + "customizeThresholdDrawer": { + "title": "Customize threshold", + "subtitle": "Set a custom threshold for the certified attribute {{name}}.", + "submitBtnLabel": "Customize threshold", + "field": { + "placeholder": "API calls/day per consumer", + "error": "The threshold per consumer cannot be higher than the total threshold.", + "info": "Please enter a number greater than or equal to 1." + }, + "limitAlert": { + "title": "For this e-service you have already defined", + "totalLimit": "Total threshold", + "consumerLimit": "Threshold per consumer", + "toInsert": "To insert", + "label": "{{threshold}} API calls/day" + }, + "alert": "If the consumer satisfies multiple attributes with different thresholds, the highest threshold is automatically applied." + } } }, "step3": { diff --git a/src/static/locales/en/shared-components.json b/src/static/locales/en/shared-components.json index 9412df3a0..d86cb4f13 100644 --- a/src/static/locales/en/shared-components.json +++ b/src/static/locales/en/shared-components.json @@ -341,7 +341,10 @@ "attributeContainer": { "attributeIdLabel": "Attribute ID (attributeId)", "idCopytooltipLabel": "ID copied successfully", - "removeAttributeAriaLabel": "Remove {{attributeName}} attribute" + "removeAttributeAriaLabel": "Remove {{attributeName}} attribute", + "thresholdLabel": "API calls/day per consumer", + "customizeBtn": "Customize threshold", + "changeBtn": "Change threshold" }, "attributeGroupContainer": { "removeGroupAriaLabel": "Remove attribute group" diff --git a/src/static/locales/it/eservice.json b/src/static/locales/it/eservice.json index b0a68b903..6da745e7a 100644 --- a/src/static/locales/it/eservice.json +++ b/src/static/locales/it/eservice.json @@ -149,6 +149,32 @@ "agreementApprovalPolicySection": { "title": "Desideri attivare manualmente tutte le richieste di fruizione pervenute?", "label": "Sì, le attiverò io manualmente" + }, + "attributes": { + "tabs": { + "ariaLabel": "tre tabs differenti per la gestione di attributi certificati, verificati e dichiarati", + "certified": "Attributi certificati", + "verified": "Attributi verificati", + "declared": "Attributi dichiarati" + }, + "customizeThresholdDrawer": { + "title": "Personalizza soglia", + "subtitle": "Imposta una soglia personalizzata per l’attributo certificato {{name}}.", + "submitBtnLabel": "Personalizza soglia", + "field": { + "placeholder": "Chiamate API/giorno per fruitore", + "error": "La soglia per fruitore non può essere superiore alla soglia totale.", + "info": "Inserisci un numero superiore o uguale a 1" + }, + "limitAlert": { + "title": "Per questo e-service hai già definito", + "totalLimit": "Soglia totale", + "consumerLimit": "Soglia per fruitore", + "toInsert": "Da inserire", + "label": "{{threshold}} chiamate API/giorno" + }, + "alert": "Se il fruitore soddisfa più attributi con soglie diverse, si applica automaticamente la soglia più alta." + } } }, "step3": { diff --git a/src/static/locales/it/shared-components.json b/src/static/locales/it/shared-components.json index e99d09c1e..376e63a90 100644 --- a/src/static/locales/it/shared-components.json +++ b/src/static/locales/it/shared-components.json @@ -341,7 +341,10 @@ "attributeContainer": { "attributeIdLabel": "ID dell'attributo (attributeId)", "idCopytooltipLabel": "ID copiato correttamente", - "removeAttributeAriaLabel": "Rimuovi attributo {{attributeName}}" + "removeAttributeAriaLabel": "Rimuovi attributo {{attributeName}}", + "thresholdLabel": "Chiamate API/giorno per fruitore", + "customizeBtn": "Personalizza soglia", + "changeBtn": "Modifica soglia" }, "attributeGroupContainer": { "removeGroupAriaLabel": "Rimuovi gruppo di attributi"