diff --git a/src/components/shared/GreyAlert.tsx b/src/components/shared/GreyAlert.tsx new file mode 100644 index 000000000..00abe7345 --- /dev/null +++ b/src/components/shared/GreyAlert.tsx @@ -0,0 +1,14 @@ +import { Alert } from '@mui/material' +import React from 'react' + +interface GreyAlertProps { + children: React.ReactNode +} + +export const GreyAlert: React.FC = ({ children }) => { + return ( + + {children} + + ) +} diff --git a/src/pages/ConsumerPurposeEditPage/ConsumerPurposeEdit.page.tsx b/src/pages/ConsumerPurposeEditPage/ConsumerPurposeEdit.page.tsx index 429202934..e271a4f7f 100644 --- a/src/pages/ConsumerPurposeEditPage/ConsumerPurposeEdit.page.tsx +++ b/src/pages/ConsumerPurposeEditPage/ConsumerPurposeEdit.page.tsx @@ -9,6 +9,7 @@ import { PurposeEditStepRiskAnalysis } from './components/PurposeEditStepRiskAna import { useParams, useNavigate } from '@/router' import { PurposeQueries } from '@/api/purpose' import { useQuery } from '@tanstack/react-query' +import { Typography } from '@mui/material' const ConsumerPurposeEditPage: React.FC = () => { const { t } = useTranslation('purpose') @@ -60,6 +61,15 @@ const ConsumerPurposeEditPage: React.FC = () => { to: 'SUBSCRIBE_PURPOSE_LIST', }} > + + {t('create.requiredLabel')} + {!isReceive && } diff --git a/src/pages/ConsumerPurposeEditPage/components/PurposeEditStepGeneral/PurposeEditStepGeneralForm.tsx b/src/pages/ConsumerPurposeEditPage/components/PurposeEditStepGeneral/PurposeEditStepGeneralForm.tsx index f8f61affb..c32ddc23c 100644 --- a/src/pages/ConsumerPurposeEditPage/components/PurposeEditStepGeneral/PurposeEditStepGeneralForm.tsx +++ b/src/pages/ConsumerPurposeEditPage/components/PurposeEditStepGeneral/PurposeEditStepGeneralForm.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Box } from '@mui/material' +import { Alert, AlertTitle, Box, Stack, Typography } from '@mui/material' import { FormProvider, useForm } from 'react-hook-form' import { RHFRadioGroup, RHFTextField } from '@/components/shared/react-hook-form-inputs' import { useTranslation } from 'react-i18next' @@ -10,6 +10,8 @@ import type { ActiveStepProps } from '@/hooks/useActiveStep' import type { Purpose, PurposeUpdateContent } from '@/api/api.generatedTypes' import SaveIcon from '@mui/icons-material/Save' import { useNavigate } from '@/router' +import { useGetConsumerPurposeEditPageInfoAlertProps } from '../../hooks/useGetConsumerPurposeEditPageInfoAlertProps' +import { GreyAlert } from '@/components/shared/GreyAlert' export type PurposeEditStepGeneralFormValues = Omit< PurposeUpdateContent, @@ -75,6 +77,18 @@ const PurposeEditStepGeneralForm: React.FC = ({ const isFreeOfCharge = formMethods.watch('isFreeOfCharge') + const dailyCallsFormValue = formMethods.watch('dailyCalls') + + const dailyCallsPerConsumer = purpose.dailyCallsPerConsumer + + const dailyCallsTotal = purpose.dailyCallsTotal + + const alertProps = useGetConsumerPurposeEditPageInfoAlertProps( + dailyCallsFormValue, + dailyCallsPerConsumer, + dailyCallsTotal + ) + return ( @@ -86,6 +100,7 @@ const PurposeEditStepGeneralForm: React.FC = ({ focusOnMount inputProps={{ maxLength: 60 }} rules={{ required: true, minLength: 5 }} + required /> = ({ multiline inputProps={{ maxLength: 250 }} rules={{ required: true, minLength: 10 }} + required /> = ({ rules={{ required: true, minLength: 10 }} /> )} - + + + {alertProps && } + + + {t('edit.loadEstimationSection.providerThresholdsInfo.label')} + + + + + {t( + 'edit.loadEstimationSection.providerThresholdsInfo.dailyCallsPerConsumer.label' + )} + + + {t( + 'edit.loadEstimationSection.providerThresholdsInfo.dailyCallsPerConsumer.value', + { + min: '#' /* @TODO - add residual threshold */, + max: dailyCallsPerConsumer, + } + )} + + + + + {t('edit.loadEstimationSection.providerThresholdsInfo.dailyCallsTotal.label')} + + + {t('edit.loadEstimationSection.providerThresholdsInfo.dailyCallsTotal.value', { + min: '#' /* @TODO - add residual threshold */, + max: dailyCallsTotal, + })} + + + + + {t('edit.loadEstimationSection.providerThresholdsInfo.description')} + + { + it('should return undefined if there is no exceed', () => { + const { result } = renderHook(() => useGetConsumerPurposeEditPageInfoAlertProps(1, 10, 100)) + expect(result.current).toStrictEqual(undefined) + }) + it('should return infoDailyCallsPerConsumerExceed if dailyCalls > dailyCallsPerConsumer', () => { + const { result } = renderHook(() => useGetConsumerPurposeEditPageInfoAlertProps(11, 10, 100)) + expect(result.current).toStrictEqual({ + children: 'infoDailyCallsPerConsumerExceed', + severity: 'info', + }) + }) + it('should return infoDailyCallsTotalExceed if dailyCalls > dailyCallsTotal', () => { + const { result } = renderHook(() => useGetConsumerPurposeEditPageInfoAlertProps(111, 10, 100)) + expect(result.current).toStrictEqual({ + children: 'infoDailyCallsTotalExceed', + severity: 'info', + }) + }) +}) diff --git a/src/pages/ConsumerPurposeEditPage/hooks/useGetConsumerPurposeEditPageInfoAlertProps.tsx b/src/pages/ConsumerPurposeEditPage/hooks/useGetConsumerPurposeEditPageInfoAlertProps.tsx new file mode 100644 index 000000000..97a4fbc6f --- /dev/null +++ b/src/pages/ConsumerPurposeEditPage/hooks/useGetConsumerPurposeEditPageInfoAlertProps.tsx @@ -0,0 +1,27 @@ +import type { AlertProps } from '@mui/material' +import { useTranslation } from 'react-i18next' +import { match } from 'ts-pattern' + +export function useGetConsumerPurposeEditPageInfoAlertProps( + dailyCalls: number, + dailyCallsPerConsumer: number, + dailyCallsTotal: number +): AlertProps | undefined { + const { t } = useTranslation('purpose', { keyPrefix: 'edit.loadEstimationSection.alerts' }) + + return match({ + isDailyCallsPerConsumerExceed: dailyCalls > dailyCallsPerConsumer, + isDailyCallsTotalExceed: dailyCalls > dailyCallsTotal, + }) + .returnType() + .with({ isDailyCallsTotalExceed: true }, () => ({ + severity: 'info', + children: t('infoDailyCallsTotalExceed'), + })) + .with({ isDailyCallsPerConsumerExceed: true }, () => ({ + severity: 'info', + children: t('infoDailyCallsPerConsumerExceed'), + })) + .otherwise(() => undefined) + /* @TODO - Add residual threshold cases */ +} diff --git a/src/static/locales/en/purpose.json b/src/static/locales/en/purpose.json index 68c5b4805..3f79ca028 100644 --- a/src/static/locales/en/purpose.json +++ b/src/static/locales/en/purpose.json @@ -3,7 +3,7 @@ "create": { "emptyTitle": "Create purpose", "requiredLabel": "*Required fields.", - "preliminaryInformationSectionTitle": "Purpose informations", + "preliminaryInformationSectionTitle": "General informations", "isTemplateField": { "label": "Clone from template" }, @@ -75,26 +75,47 @@ "stepGeneral": { "title": "General information", "nameField": { - "label": "Purpose name (required)", + "label": "Purpose name", "infoLabel": "It will help you tell them apart. Min 5 characters, max 60 characters" }, "descriptionField": { - "label": "Purpose description (required)", + "label": "Purpose description", "infoLabel": "Min 10 characters, max 250 characters" }, "isFreeOfChargeField": { - "label": "Indicate whether access to the data made available with the use of this e-service is free of charge (required)", + "label": "How will you provide access to data from this e-service?*", "options": { - "YES": "Yes", - "NO": "No" + "YES": "Free of charge", + "NO": "For a fee" } }, "freeOfChargeReasonField": { "label": "Free of charge reason (required)", - "infoLabel": "It is requested to specify whether the provision of data is free of charge by virtue of an exemption/exclusion provided for by law or other. Min 10 characters, max 250 characters" + "infoLabel": "Explain why you will make this data available free of charge. Indicate whether you are doing so under a statutory exemption or exclusion, or for another reason. Minimum 10 characters, maximum 250 characters." + } + }, + "loadEstimationSection": { + "title": "Estimate API calls", + "description": "Indicates the number of calls expected each day for this purpose.", + "dailyCalls": { + "label": "Estimated number of API calls/day", + "infoLabel": "Please enter a number greater than or equal to 1." + }, + "providerThresholdsInfo": { + "label": "Thresholds defined by the provider", + "description": "Residual calls may vary if the provider receives other purposes before your publication.", + "dailyCallsPerConsumer": { + "label": "Threshold per user", + "value": "{{min}} of {{max}} API calls/day" + }, + "dailyCallsTotal": { + "label": "Total threshold", + "value": "{{min}} of {{max}} API calls/day" + } }, - "dailyCallsField": { - "label": "How many API calls do you expect to make per day?" + "alerts": { + "infoDailyCallsPerConsumerExceed": "The number you entered exceeds the user limit. The purpose must be approved by the provider.", + "infoDailyCallsTotalExceed": "The number you entered exceeds the total threshold. The purpose must be approved by the provider." } }, "stepRiskAnalysis": { @@ -170,7 +191,10 @@ "label": "This risk analysis uses an obsolete version that you cannot publish.", "action": "Create new purpose" }, - "infoRulesetExpiration": "This risk analysis uses a version that will become obsolete in {{days}} days. Publish it by {{date}} to avoid having to recompile it." + "infoRulesetExpiration": "This risk analysis uses a version that will become obsolete in {{days}} days. Publish it by {{date}} to avoid having to recompile it.", + "infoApprovalMayBeRequired": "If the producer receives other purposes before your publication, approval may be required.", + "infoDailyCallsPerConsumerExceed": "The estimated number of calls you indicated exceeds the per-user threshold. The purpose must be approved by the producer.", + "infoDailyCallsTotalExceed": "The estimated number of calls you indicated exceeds the total threshold. The purpose will need to be approved by the producer." }, "generalInformationSection": { "title": "General information", diff --git a/src/static/locales/en/purposeTemplate.json b/src/static/locales/en/purposeTemplate.json index 31941193e..0a6418f0c 100644 --- a/src/static/locales/en/purposeTemplate.json +++ b/src/static/locales/en/purposeTemplate.json @@ -43,7 +43,7 @@ "freeOfChargeReason": "I'm a Public Administration" }, "stepper": { - "step1Label": "General", + "step1Label": "General informations", "step2Label": "Suggested e-services", "step3Label": "Risk analysis" }, diff --git a/src/static/locales/it/purpose.json b/src/static/locales/it/purpose.json index 57f300e51..0efcf8c2d 100644 --- a/src/static/locales/it/purpose.json +++ b/src/static/locales/it/purpose.json @@ -3,7 +3,7 @@ "create": { "emptyTitle": "Crea finalità", "requiredLabel": "*Campi obbligatori.", - "preliminaryInformationSectionTitle": "Informazioni sulla finalità", + "preliminaryInformationSectionTitle": "Informazioni generali", "isTemplateField": { "label": "Usa un template precompilato" }, @@ -65,7 +65,7 @@ "edit": { "emptyTitle": "Modifica finalità", "stepper": { - "stepGeneralLabel": "Generale", + "stepGeneralLabel": "Informazioni generali", "stepRiskAnalysisLabel": "Analisi del rischio" }, "backWithoutSaveBtn": "Indietro", @@ -75,26 +75,47 @@ "stepGeneral": { "title": "Informazioni generali", "nameField": { - "label": "Nome della finalità (richiesto)", + "label": "Nome della finalità", "infoLabel": "Ti aiuterà a distinguerla dalle altre. Min 5 caratteri, max 60 caratteri" }, "descriptionField": { - "label": "Descrizione della finalità (richiesto)", + "label": "Descrizione finalità", "infoLabel": "Min 10 caratteri, max 250 caratteri" }, "isFreeOfChargeField": { - "label": "Indicare se l’accesso ai dati messi a disposizione con la fruizione del presente E-service è a titolo gratuito (richiesto)", + "label": "Come metterai a disposizione l'accesso ai dati di questo e-service?*", "options": { - "YES": "Sì", - "NO": "No" + "YES": "A titolo gratuito", + "NO": "A pagamento" } }, "freeOfChargeReasonField": { "label": "Motivazione titolo gratuito (richiesto)", - "infoLabel": "Si richiede di specificare se la messa a disposizione dei dati è a titolo gratuito in forza di una esenzione/esclusione prevista dalla legge o altro. Min 10 caratteri, max 250 caratteri" + "infoLabel": "Spiega perché metterai questi dati a disposizione gratuitamente. Indica se lo fai in base a un’esenzione o a un’esclusione prevista dalla legge, oppure per un altro motivo. Min 10, max 250 caratteri" + } + }, + "loadEstimationSection": { + "title": "Stima chiamate API", + "description": "Indica il numero di chiamate previste ogni giorno per questa finalità.", + "dailyCalls": { + "label": "Stima numero chiamate API/giorno", + "infoLabel": "Inserisci un numero superiore o uguale a 1" + }, + "providerThresholdsInfo": { + "label": "Soglie definite dall’erogatore", + "description": "Le chiamate residue possono variare se l'erogatore riceve altre finalità prima della tua pubblicazione.", + "dailyCallsPerConsumer": { + "label": "Soglia per fruitore", + "value": "{{min}} di {{max}} chiamate API/giorno" + }, + "dailyCallsTotal": { + "label": "Soglia totale", + "value": "{{min}} di {{max}} chiamate API/giorno" + } }, - "dailyCallsField": { - "label": "Quante chiamate API/giorno stimi di effettuare?" + "alerts": { + "infoDailyCallsPerConsumerExceed": "Il numero che hai inserito supera la soglia prevista per fruitore. La finalità dovrà essere approvata dall’erogatore.", + "infoDailyCallsTotalExceed": "Il numero che hai inserito supera la soglia totale. La finalità dovrà essere approvata dall’erogatore." } }, "stepRiskAnalysis": { @@ -170,7 +191,10 @@ "label": "Questa analisi del rischio utilizza una versione obsoleta che non puoi pubblicare.", "action": "Crea nuova finalità" }, - "infoRulesetExpiration": "Questa analisi del rischio utilizza una versione che sarà obsoleta tra {{days}} giorni. Pubblicala entro {{date}} per non doverla ricompilare." + "infoRulesetExpiration": "Questa analisi del rischio utilizza una versione che sarà obsoleta tra {{days}} giorni. Pubblicala entro {{date}} per non doverla ricompilare.", + "infoApprovalMayBeRequired": "Se l’erogatore riceve altre finalità prima della tua pubblicazione, potrebbe essere necessaria l’approvazione.", + "infoDailyCallsPerConsumerExceed": "La stima di chiamate che hai indicato supera la soglia per fruitore. La finalità dovrà essere approvata dall’erogatore.", + "infoDailyCallsTotalExceed": "La stima di chiamate che hai indicato supera la soglia totale. La finalità dovrà essere approvata dall’erogatore." }, "generalInformationSection": { "title": "Informazioni generali", diff --git a/src/static/locales/it/purposeTemplate.json b/src/static/locales/it/purposeTemplate.json index 9dac32a01..342eebc5f 100644 --- a/src/static/locales/it/purposeTemplate.json +++ b/src/static/locales/it/purposeTemplate.json @@ -43,7 +43,7 @@ "freeOfChargeReason": "Sono una Pubblica Amministrazione" }, "stepper": { - "step1Label": "Generale", + "step1Label": "Informazioni generali", "step2Label": "E-service suggeriti", "step3Label": "Analisi del rischio" },