Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/components/shared/GreyAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Alert } from '@mui/material'
import React from 'react'

interface GreyAlertProps {
children: React.ReactNode
}

export const GreyAlert: React.FC<GreyAlertProps> = ({ children }) => {
return (
<Alert icon={false} sx={{ p: 2, borderLeftColor: 'grey.700', backgroundColor: 'grey.50' }}>
{children}
</Alert>
)
}
10 changes: 10 additions & 0 deletions src/pages/ConsumerPurposeEditPage/ConsumerPurposeEdit.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -60,6 +61,15 @@ const ConsumerPurposeEditPage: React.FC = () => {
to: 'SUBSCRIBE_PURPOSE_LIST',
}}
>
<Typography
sx={{
fontSize: 16,
fontWeight: 700,
color: 'text.secondary',
}}
>
{t('create.requiredLabel')}
</Typography>
{!isReceive && <Stepper steps={steps} activeIndex={activeStep} />}
<Step {...stepProps} />
</PageContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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,
Expand Down Expand Up @@ -75,6 +77,18 @@ const PurposeEditStepGeneralForm: React.FC<PurposeEditStepGeneralFormProps> = ({

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 (
<FormProvider {...formMethods}>
<Box component="form" noValidate onSubmit={formMethods.handleSubmit(onSubmit)}>
Expand All @@ -86,6 +100,7 @@ const PurposeEditStepGeneralForm: React.FC<PurposeEditStepGeneralFormProps> = ({
focusOnMount
inputProps={{ maxLength: 60 }}
rules={{ required: true, minLength: 5 }}
required
/>

<RHFTextField
Expand All @@ -95,6 +110,7 @@ const PurposeEditStepGeneralForm: React.FC<PurposeEditStepGeneralFormProps> = ({
multiline
inputProps={{ maxLength: 250 }}
rules={{ required: true, minLength: 10 }}
required
/>

<RHFRadioGroup
Expand All @@ -116,15 +132,57 @@ const PurposeEditStepGeneralForm: React.FC<PurposeEditStepGeneralFormProps> = ({
rules={{ required: true, minLength: 10 }}
/>
)}

</SectionContainer>
<SectionContainer
title={t('edit.loadEstimationSection.title')}
description={t('edit.loadEstimationSection.description')}
>
<RHFTextField
name="dailyCalls"
label={t('edit.stepGeneral.dailyCallsField.label')}
label={t('edit.loadEstimationSection.dailyCalls.label')}
infoLabel={t('edit.loadEstimationSection.dailyCalls.infoLabel')}
type="number"
inputProps={{ min: '1' }}
sx={{ mb: 0 }}
rules={{ required: true, min: 1 }}
/>
{alertProps && <Alert {...alertProps} sx={{ mt: 1, mb: 3 }} />}
<GreyAlert>
<AlertTitle sx={{ textTransform: 'uppercase', fontWeight: 700 }}>
{t('edit.loadEstimationSection.providerThresholdsInfo.label')}
</AlertTitle>
<Stack direction="row" spacing={6} sx={{ mt: 0.5, mb: 1 }}>
<Stack direction="row" spacing={2} alignItems="center">
<Typography>
{t(
'edit.loadEstimationSection.providerThresholdsInfo.dailyCallsPerConsumer.label'
)}
</Typography>
<Typography fontWeight={600}>
{t(
'edit.loadEstimationSection.providerThresholdsInfo.dailyCallsPerConsumer.value',
{
min: '#' /* @TODO - add residual threshold */,
max: dailyCallsPerConsumer,
}
)}
</Typography>
</Stack>
<Stack direction="row" spacing={2} alignItems="center">
<Typography>
{t('edit.loadEstimationSection.providerThresholdsInfo.dailyCallsTotal.label')}
</Typography>
<Typography fontWeight={600}>
{t('edit.loadEstimationSection.providerThresholdsInfo.dailyCallsTotal.value', {
min: '#' /* @TODO - add residual threshold */,
max: dailyCallsTotal,
})}
</Typography>
</Stack>
</Stack>
<Typography variant="caption" color="text.secondary">
{t('edit.loadEstimationSection.providerThresholdsInfo.description')}
</Typography>
</GreyAlert>
</SectionContainer>
<StepActions
back={{ to: 'SUBSCRIBE_PURPOSE_LIST', label: t('backToListBtn'), type: 'link' }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { renderHook } from '@testing-library/react'
import { useGetConsumerPurposeEditPageInfoAlertProps } from '../useGetConsumerPurposeEditPageInfoAlertProps'

describe('useGetConsumerPurposeInfoAlertProps', () => {
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',
})
})
})
Original file line number Diff line number Diff line change
@@ -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<AlertProps | undefined>()
.with({ isDailyCallsTotalExceed: true }, () => ({
severity: 'info',
children: t('infoDailyCallsTotalExceed'),
}))
.with({ isDailyCallsPerConsumerExceed: true }, () => ({
severity: 'info',
children: t('infoDailyCallsPerConsumerExceed'),
}))
.otherwise(() => undefined)
/* @TODO - Add residual threshold cases */
}
44 changes: 34 additions & 10 deletions src/static/locales/en/purpose.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"create": {
"emptyTitle": "Create purpose",
"requiredLabel": "*Required fields.",
"preliminaryInformationSectionTitle": "Purpose informations",
"preliminaryInformationSectionTitle": "General informations",
"isTemplateField": {
"label": "Clone from template"
},
Expand Down Expand Up @@ -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": {
Expand Down Expand Up @@ -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 <strong>{{days}} days</strong>. 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",
Expand Down
2 changes: 1 addition & 1 deletion src/static/locales/en/purposeTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"freeOfChargeReason": "I'm a Public Administration"
},
"stepper": {
"step1Label": "General",
"step1Label": "General informations",
"step2Label": "Suggested e-services",
"step3Label": "Risk analysis"
},
Expand Down
46 changes: 35 additions & 11 deletions src/static/locales/it/purpose.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"create": {
"emptyTitle": "Crea finalità",
"requiredLabel": "*Campi obbligatori.",
"preliminaryInformationSectionTitle": "Informazioni sulla finalità",
"preliminaryInformationSectionTitle": "Informazioni generali",
"isTemplateField": {
"label": "Usa un template precompilato"
},
Expand Down Expand Up @@ -65,7 +65,7 @@
"edit": {
"emptyTitle": "Modifica finalità",
"stepper": {
"stepGeneralLabel": "Generale",
"stepGeneralLabel": "Informazioni generali",
"stepRiskAnalysisLabel": "Analisi del rischio"
},
"backWithoutSaveBtn": "Indietro",
Expand All @@ -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": "",
"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": {
Expand Down Expand Up @@ -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 <strong>{{days}} giorni</strong>. 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",
Expand Down
2 changes: 1 addition & 1 deletion src/static/locales/it/purposeTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"freeOfChargeReason": "Sono una Pubblica Amministrazione"
},
"stepper": {
"step1Label": "Generale",
"step1Label": "Informazioni generali",
"step2Label": "E-service suggeriti",
"step3Label": "Analisi del rischio"
},
Expand Down
Loading