From 7423c129698520f4963212b25558c8d1185a3149 Mon Sep 17 00:00:00 2001 From: Pierre-Eric Garcia Date: Wed, 2 Aug 2023 16:27:30 +0200 Subject: [PATCH] Migrate enrollment reducer to typescript --- .../components/molecules/StickyActions.tsx | 25 ++++---- .../InstructorEnrollmentListFilters/index.tsx | 4 +- .../DataProviderList/ApiDataProviderList.tsx | 1 - .../templates/Form/SubmissionPanel/index.tsx | 2 +- ...ucer.test.js => enrollmentReducer.test.ts} | 28 +++++++-- ...ollmentReducer.js => enrollmentReducer.ts} | 57 +++++++++++++++---- .../templates/InstructorEnrollmentList.tsx | 41 +++++-------- .../components/templates/InstructorHome.tsx | 4 +- frontend/src/components/templates/Stats.tsx | 4 +- .../UserEnrollmentList/Enrollment.tsx | 14 ++--- 10 files changed, 109 insertions(+), 71 deletions(-) rename frontend/src/components/templates/Form/{enrollmentReducer.test.js => enrollmentReducer.test.ts} (92%) rename frontend/src/components/templates/Form/{enrollmentReducer.js => enrollmentReducer.ts} (61%) diff --git a/frontend/src/components/molecules/StickyActions.tsx b/frontend/src/components/molecules/StickyActions.tsx index 63007e0c67..484def0192 100644 --- a/frontend/src/components/molecules/StickyActions.tsx +++ b/frontend/src/components/molecules/StickyActions.tsx @@ -287,17 +287,20 @@ export const StickyActions: FunctionComponent = ({ /> )} - {user && user.roles.length > 1 && authorizedEvents.length > 1 && ( -
- handleActionChange(EnrollmentEvent.instruct)} - label="Instruction" - icon="edit" - quaternary={currentAction !== EnrollmentEvent.instruct} - iconFill - /> -
- )} + {user && + user.roles && + user.roles.length > 1 && + authorizedEvents.length > 1 && ( +
+ handleActionChange(EnrollmentEvent.instruct)} + label="Instruction" + icon="edit" + quaternary={currentAction !== EnrollmentEvent.instruct} + iconFill + /> +
+ )} {authorizedEvents.includes(EnrollmentEvent.notify) && (
role.endsWith(':reporter')) + ?.filter((role) => role.endsWith(':reporter')) .map((role) => { const targetApiKey = role.split(':')[0]; @@ -75,7 +75,7 @@ const InstructorEnrollmentListFilters = ({ id="status" options={Object.entries(STATUS_LABELS) .filter(([key]) => - user?.roles.includes('administrator') ? key : key !== 'archived' + user?.roles?.includes('administrator') ? key : key !== 'archived' ) .map(([key, value]) => ({ key, diff --git a/frontend/src/components/templates/DataProviderList/ApiDataProviderList.tsx b/frontend/src/components/templates/DataProviderList/ApiDataProviderList.tsx index 0fc498a8ba..76988a8a1f 100644 --- a/frontend/src/components/templates/DataProviderList/ApiDataProviderList.tsx +++ b/frontend/src/components/templates/DataProviderList/ApiDataProviderList.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import ListHeader from '../../molecules/ListHeader'; import { DataProviderCard } from './DataProviderCard'; import Button from '../../atoms/hyperTexts/Button'; diff --git a/frontend/src/components/templates/Form/SubmissionPanel/index.tsx b/frontend/src/components/templates/Form/SubmissionPanel/index.tsx index 9ae4570ac5..c7b3b9dabf 100644 --- a/frontend/src/components/templates/Form/SubmissionPanel/index.tsx +++ b/frontend/src/components/templates/Form/SubmissionPanel/index.tsx @@ -37,7 +37,7 @@ const SubmissionPanel: FunctionComponent = ({ updateEnrollment, processEvent ); - if (user && user?.roles?.length > 1) { + if (user && user?.roles && user?.roles?.length > 1) { return null; } diff --git a/frontend/src/components/templates/Form/enrollmentReducer.test.js b/frontend/src/components/templates/Form/enrollmentReducer.test.ts similarity index 92% rename from frontend/src/components/templates/Form/enrollmentReducer.test.js rename to frontend/src/components/templates/Form/enrollmentReducer.test.ts index e44ecf9ccd..93e667d27e 100644 --- a/frontend/src/components/templates/Form/enrollmentReducer.test.js +++ b/frontend/src/components/templates/Form/enrollmentReducer.test.ts @@ -1,29 +1,38 @@ +import { EnrollmentEvent } from '../../../config/event-configuration'; +import { EnrollmentStatus } from '../../../config/status-parameters'; +import { TeamMemberType } from '../InstructorEnrollmentList'; import enrollmentReducerFactory from './enrollmentReducer'; describe('enrollmentReducerFactory', () => { const previousEnrollment = { + id: 1, acl: { - update: true, - submit: true, + [EnrollmentEvent.update]: true, + [EnrollmentEvent.submit]: true, }, - status: 'draft', + status: EnrollmentStatus.draft, target_api: 'api_particulier', scopes: { dgfip_declarant1_nom: false, cnaf_quotient_familial: false, }, + updated_at: 'now', + created_at: 'now', team_members: [ { - type: 'demandeur', + id: 1, + type: TeamMemberType.demandeur, tmp_id: 'tmp_31', email: 'datapass@yopmail.com', }, { - type: 'responsable_technique', + id: 2, + type: TeamMemberType.responsable_technique, tmp_id: 'tmp_34', }, ], }; + describe('enrollmentReducer with no demarches', () => { const enrollmentReducer = enrollmentReducerFactory(null); @@ -65,7 +74,8 @@ describe('enrollmentReducerFactory', () => { }; expect( - enrollmentReducer(previousEnrollment, event).scopes.dgfip_declarant1_nom + enrollmentReducer(previousEnrollment, event)?.scopes + ?.dgfip_declarant1_nom ).toEqual(true); }); @@ -235,6 +245,7 @@ describe('enrollmentReducerFactory', () => { ).toEqual([ { type: 'demandeur', + id: 1, email: 'datapass@yopmail.com', tmp_id: 'tmp_31', }, @@ -242,6 +253,7 @@ describe('enrollmentReducerFactory', () => { type: 'responsable_technique', email: 'technique@editeur.fr', tmp_id: 'tmp_34', + id: 2, }, ]); }); @@ -261,6 +273,7 @@ describe('enrollmentReducerFactory', () => { ).toEqual([ { type: 'demandeur', + id: 1, email: 'datapass@yopmail.com', tmp_id: 'tmp_31', }, @@ -268,6 +281,7 @@ describe('enrollmentReducerFactory', () => { type: 'responsable_technique', email: '', tmp_id: 'tmp_34', + id: 2, }, ]); }); @@ -317,11 +331,13 @@ describe('enrollmentReducerFactory', () => { type: 'demandeur', email: 'datapass@yopmail.com', tmp_id: 'tmp_31', + id: 1, }, { type: 'responsable_technique', email: '', tmp_id: 'tmp_34', + id: 2, }, ]); }); diff --git a/frontend/src/components/templates/Form/enrollmentReducer.js b/frontend/src/components/templates/Form/enrollmentReducer.ts similarity index 61% rename from frontend/src/components/templates/Form/enrollmentReducer.js rename to frontend/src/components/templates/Form/enrollmentReducer.ts index 4a1842c8cb..7fe1ffb64e 100644 --- a/frontend/src/components/templates/Form/enrollmentReducer.js +++ b/frontend/src/components/templates/Form/enrollmentReducer.ts @@ -1,6 +1,20 @@ import { cloneDeep, get, isEmpty, isObject, merge, omitBy, set } from 'lodash'; +import { Enrollment } from '../InstructorEnrollmentList'; -export const globalUpdate = ({ previousEnrollment, futureEnrollment }) => +type Demarche = { + label: string; + about: string; + state: any; + team_members: any; +}; + +export const globalUpdate = ({ + previousEnrollment, + futureEnrollment, +}: { + previousEnrollment: Enrollment; + futureEnrollment: Enrollment; +}) => merge( {}, previousEnrollment, @@ -8,12 +22,15 @@ export const globalUpdate = ({ previousEnrollment, futureEnrollment }) => ); export const eventUpdateFactory = - (demarches = null) => + (demarches: Demarche[] | null) => ({ previousEnrollment, event: { target: { type = null, checked = null, value: inputValue, name }, }, + }: { + previousEnrollment: Enrollment; + event: any; }) => { const value = type === 'checkbox' ? checked : inputValue; @@ -21,28 +38,34 @@ export const eventUpdateFactory = set(futureEnrollment, name, value); if (demarches && name === 'demarche') { - const defaultDemarche = get(demarches, 'default', {}); - const selectedDemarche = get(demarches, value, {}); + const defaultDemarche = get(demarches, 'default', {}) as Demarche; + const selectedDemarche = get(demarches, value, {}) as Demarche; let futureTeamMembers = futureEnrollment.team_members; if ( !isEmpty(futureEnrollment.team_members) && !isEmpty(defaultDemarche.team_members) ) { - futureTeamMembers = futureEnrollment.team_members.map( + futureTeamMembers = futureEnrollment?.team_members?.map( (futureTeamMember) => { - if (!defaultDemarche.team_members[futureTeamMember.type]) { + if ( + !defaultDemarche.team_members[futureTeamMember.type as string] + ) { return futureTeamMember; } if ( !selectedDemarche.team_members || - !selectedDemarche.team_members[futureTeamMember.type] + !selectedDemarche.team_members[futureTeamMember.type as string] ) { - return defaultDemarche.team_members[futureTeamMember.type]; + return defaultDemarche.team_members[ + futureTeamMember.type as string + ]; } - return selectedDemarche.team_members[futureTeamMember.type]; + return selectedDemarche.team_members[ + futureTeamMember.type as string + ]; } ); } @@ -59,16 +82,28 @@ export const eventUpdateFactory = return futureEnrollment; }; +type Event = { + target: { type: string; checked: boolean; value: any; name: string }; +}; + +// Fonction de garde de type +function isEvent(obj: Enrollment | Event): obj is Event { + return (obj as Event).target !== undefined; +} + export const enrollmentReducerFactory = (demarches = null) => - (previousEnrollment, eventOrFutureEnrollment) => { + ( + previousEnrollment: Enrollment, + eventOrFutureEnrollment: Enrollment | Event | string + ) => { if (!isObject(eventOrFutureEnrollment)) { return previousEnrollment; } // if no eventOrFutureEnrollment.target, this is a direct state update (network for instance) // a direct state update DOES NOT trigger a pre-filled demarche update - if (!eventOrFutureEnrollment.target) { + if (!isEvent(eventOrFutureEnrollment)) { const futureEnrollment = eventOrFutureEnrollment; return globalUpdate({ previousEnrollment, futureEnrollment }); diff --git a/frontend/src/components/templates/InstructorEnrollmentList.tsx b/frontend/src/components/templates/InstructorEnrollmentList.tsx index bd7aa4fe28..cbaeb33be8 100644 --- a/frontend/src/components/templates/InstructorEnrollmentList.tsx +++ b/frontend/src/components/templates/InstructorEnrollmentList.tsx @@ -78,14 +78,15 @@ export enum TeamMemberType { } export type TeamMember = { - email: string; - family_name: string; - given_name: string; + email?: string; + family_name?: string; + given_name?: string; id: number; - job: string; - phone_number: string; + job?: string; + phone_number?: string; type?: TeamMemberType; uid?: string; + tmp_id?: string; }; export type Contact = { @@ -110,8 +111,8 @@ export type Enrollment = { notify_events_from_demandeurs_count?: number; unprocessed_notify_events_from_demandeurs_count?: number; id: number; - intitule: string; - siret: string; + intitule?: string; + siret?: string; consulted_by_instructor?: boolean; requested_changes_have_been_done?: boolean; nom_raison_sociale?: string | null; @@ -120,29 +121,13 @@ export type Enrollment = { status: EnrollmentStatus; linked_franceconnect_enrollment_id?: number | null; events?: Event[]; - acl?: { - archive: boolean; - copy: boolean; - create: boolean; - destroy: boolean; - get_email_templates: boolean; - index: boolean; - mark_event_as_processed: boolean; - notify: boolean; - refuse: boolean; - request_changes: boolean; - revoke: boolean; - show: boolean; - submit: boolean; - update: boolean; - validate: boolean; - }; + acl?: Partial>; additional_content?: Record; - cgu_approved: boolean; + cgu_approved?: boolean; copied_from_enrollment_id?: number; - data_recipients: any; - data_retention_comment: any; - data_retention_period: number; + data_recipients?: any; + data_retention_comment?: any; + data_retention_period?: number; date_mise_en_production?: string; demarche?: any; description?: string; diff --git a/frontend/src/components/templates/InstructorHome.tsx b/frontend/src/components/templates/InstructorHome.tsx index 93a4e43622..3c5c9a9dbb 100644 --- a/frontend/src/components/templates/InstructorHome.tsx +++ b/frontend/src/components/templates/InstructorHome.tsx @@ -57,7 +57,7 @@ const InstructorHome: React.FC = () => { const instructorTargetApis: TargetAPI[] = user?.roles - .filter((role) => role.endsWith(':reporter')) + ?.filter((role) => role.endsWith(':reporter')) .map((role) => role.split(':')[0] as TargetAPI) || []; return ( @@ -73,7 +73,7 @@ const InstructorHome: React.FC = () => { defaultOverviewLabel="Toutes les habilitations" options={instructorTargetApis.map((targetApiKey) => ({ key: targetApiKey, - label: dataProviderConfigurations?.[targetApiKey].label, + label: dataProviderConfigurations?.[targetApiKey]?.label, }))} values={targetApis.length === 0 ? [] : targetApis} onChange={(values = []) => setTargetApis(values)} diff --git a/frontend/src/components/templates/Stats.tsx b/frontend/src/components/templates/Stats.tsx index 2ed6717e62..deee857331 100644 --- a/frontend/src/components/templates/Stats.tsx +++ b/frontend/src/components/templates/Stats.tsx @@ -74,7 +74,7 @@ const USER_STATUS_COLORS = { revoked: '#FF4747', }; -type Stats = { +type StatsData = { enrollment_count: number; validated_enrollment_count: number; average_processing_time_in_days: number; @@ -91,7 +91,7 @@ type Stats = { }; export const Stats = () => { - const [stats, setStats] = useState(null); + const [stats, setStats] = useState(null); const { targetApi } = useParams(); const { dataProviderConfigurations } = useDataProviderConfigurations(); diff --git a/frontend/src/components/templates/UserEnrollmentList/Enrollment.tsx b/frontend/src/components/templates/UserEnrollmentList/Enrollment.tsx index ad32308d59..56195e9ed1 100644 --- a/frontend/src/components/templates/UserEnrollmentList/Enrollment.tsx +++ b/frontend/src/components/templates/UserEnrollmentList/Enrollment.tsx @@ -7,15 +7,15 @@ import ActivityFeedWrapper from './ActivityFeedWrapper'; import Button from '../../atoms/hyperTexts/Button'; import { StatusBadge } from '../../molecules/StatusBadge'; import NextStepButton from '../../molecules/NextStepButton'; -import { Enrollment } from '../InstructorEnrollmentList'; +import { Enrollment as EnrollmentType } from '../InstructorEnrollmentList'; type EnrollmentProps = { - id: Enrollment['id']; - events: Enrollment['events']; - target_api: Enrollment['target_api']; - intitule: Enrollment['intitule']; - description: Enrollment['description']; - status: Enrollment['status']; + id: EnrollmentType['id']; + events: EnrollmentType['events']; + target_api: EnrollmentType['target_api']; + intitule: EnrollmentType['intitule']; + description: EnrollmentType['description']; + status: EnrollmentType['status']; onSelect: ( target_api: string, id: number,