From ff98cd0302b973268d3e712153f0bc0470e9038c Mon Sep 17 00:00:00 2001 From: Nicolas KREMER Date: Fri, 6 Dec 2024 10:47:11 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20mise=20=C3=A0=20jour=20interface=20et?= =?UTF-8?q?=20champs=20obligatoires=20SIFA=20(#3916)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Paul G. --- ui/components/Table/Table.tsx | 13 +- ui/modules/effectifs/EffectifsPage.tsx | 4 - ui/modules/mon-espace/SIFA/SIFAPage.tsx | 162 +++++++++++------ .../engine/EffectifTableContainer.tsx | 8 +- .../effectifs/engine/EffectifsTable.tsx | 172 ++++++++++-------- .../engine/effectifForm/EffectifForm.tsx | 13 +- .../components/Input/InputController.tsx | 3 +- .../apprenantCodePostalDeNaissance.control.ts | 2 +- .../engine/formEngine/initFields.tsx | 7 +- .../organismes/InfoTeleversementSIFA.tsx | 46 +++++ 10 files changed, 275 insertions(+), 155 deletions(-) create mode 100644 ui/modules/organismes/InfoTeleversementSIFA.tsx diff --git a/ui/components/Table/Table.tsx b/ui/components/Table/Table.tsx index 821269da5..cd84d60d7 100644 --- a/ui/components/Table/Table.tsx +++ b/ui/components/Table/Table.tsx @@ -188,10 +188,19 @@ export default function Table({ onRowClick?.(row.id)} + onClick={() => { + onRowClick?.(row.id); + if (!props.isRowExpanded) { + row.toggleExpanded(); + } + }} + _hover={{ + backgroundColor: "grey.200", + cursor: "pointer", + }} > {/* first row is a normal row */} {row.getVisibleCells().map((cell) => { diff --git a/ui/modules/effectifs/EffectifsPage.tsx b/ui/modules/effectifs/EffectifsPage.tsx index e83119d0e..75045dc4e 100644 --- a/ui/modules/effectifs/EffectifsPage.tsx +++ b/ui/modules/effectifs/EffectifsPage.tsx @@ -49,8 +49,6 @@ function EffectifsPage(props: EffectifsPageProps) { const [showOnlyErrors, setShowOnlyErrors] = useState(false); const [filtreAnneeScolaire, setFiltreAnneeScolaire] = useState("all"); - const [triggerExpand, setTriggerExpand] = useState({} as { tableId: string; rowId: string }); - const { data: organismesEffectifs, isFetching, @@ -246,8 +244,6 @@ function EffectifsPage(props: EffectifsPageProps) { effectifs={effectifs} formation={formation} searchValue={searchValue} - triggerExpand={triggerExpand} - onTriggerExpand={setTriggerExpand} refetch={refetch} /> ); diff --git a/ui/modules/mon-espace/SIFA/SIFAPage.tsx b/ui/modules/mon-espace/SIFA/SIFAPage.tsx index 3bb3c94f3..d1d716d33 100644 --- a/ui/modules/mon-espace/SIFA/SIFAPage.tsx +++ b/ui/modules/mon-espace/SIFA/SIFAPage.tsx @@ -1,3 +1,4 @@ +import { ChevronDownIcon, ChevronUpIcon } from "@chakra-ui/icons"; import { Center, Heading, @@ -14,6 +15,7 @@ import { ListItem, Grid, Image, + Collapse, } from "@chakra-ui/react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import groupBy from "lodash.groupby"; @@ -25,6 +27,7 @@ import { DuplicateEffectifGroupPagination, getSIFADate, SIFA_GROUP } from "share import { _get, _getBlob } from "@/common/httpClient"; import { Organisme } from "@/common/internal/Organisme"; import { downloadObject } from "@/common/utils/browser"; +import ButtonTeleversement from "@/components/buttons/ButtonTeleversement"; import DownloadButton from "@/components/buttons/DownloadButton"; import Link from "@/components/Links/Link"; import SupportLink from "@/components/Links/SupportLink"; @@ -36,7 +39,9 @@ import useToaster from "@/hooks/useToaster"; import { effectifsStateAtom, effectifFromDecaAtom } from "@/modules/mon-espace/effectifs/engine/atoms"; import EffectifTableContainer from "@/modules/mon-espace/effectifs/engine/EffectifTableContainer"; import { Input } from "@/modules/mon-espace/effectifs/engine/formEngine/components/Input/Input"; +import InfoTeleversementSIFA from "@/modules/organismes/InfoTeleversementSIFA"; import { DownloadLine, ExternalLinkLine } from "@/theme/components/icons"; +import Eye from "@/theme/components/icons/Eye"; function useOrganismesEffectifs(organismeId: string) { const setCurrentEffectifsState = useSetRecoilState(effectifsStateAtom); @@ -82,9 +87,12 @@ const SIFAPage = (props: SIFAPageProps) => { const { toastWarning, toastSuccess } = useToaster(); const organisme = useRecoilValue(organismeAtom); const { isLoading, organismesEffectifs, refetch } = useOrganismesEffectifs(organisme._id); + const [show, setShow] = useState(false); + const handleToggle = () => { + setShow(!show); + }; const [searchValue, setSearchValue] = useState(""); - const [triggerExpand, setTriggerExpand] = useState({} as { tableId: string; rowId: string }); const organismesEffectifsGroupedBySco: any = useMemo( () => groupBy(organismesEffectifs, "annee_scolaire"), @@ -143,7 +151,7 @@ const SIFAPage = (props: SIFAPageProps) => { { trackPlausibleEvent("telechargement_sifa"); downloadObject( @@ -196,17 +204,36 @@ const SIFAPage = (props: SIFAPageProps) => { - - Fichier d’instruction SIFA (2023) - - + + + Fichier d’instruction SIFA (2023) + + + ( + { + e.preventDefault(); + trackPlausibleEvent("televersement_clic_modale_donnees_obligatoires"); + onOpen(); + }} + > + + Les données obligatoires + + )} + title="SIFA : les données obligatoires à renseigner" + size="4xl" + > + + + PDF – 1.5 Mo @@ -215,52 +242,69 @@ const SIFAPage = (props: SIFAPageProps) => { Quelques conseils sur le fichier SIFA et sa manipulation : - - - - Vérifiez que tous vos apprentis soient bien présents dans le fichier. Si non, téléchargez le fichier - et complétez à la main avec vos effectifs manquants. - - - Avant de téléverser votre fichier SIFA sur le portail de la DEPP, veuillez en{" "} - supprimer la première ligne d‘en-tête de colonnes. - - - Attention ! Si vous ouvrez le fichier avec Excel, veuillez le sauvegarder (Fichier - > Enregistrer sous) au format{" "} - - CSV Delimiter - {" "} - après suppression de la première ligne pour assurer la compatibilité avec l‘enquête SIFA. - - - L’enquête SIFA sera terminée dès lors que le fichier est accepté par la plateforme SIFA. - - - En cas de difficultés ou questions, veuillez lire la{" "} - - FAQ dédiée - - . - - + + {" "} + {!show ? : } Voir les détails + + + + + Vérifiez que tous vos apprentis soient bien présents dans le fichier. Si non, téléchargez le fichier + et complétez à la main avec vos effectifs manquants. + + + Avant de téléverser votre fichier SIFA sur le portail de la DEPP, veuillez{" "} + supprimer la première ligne d‘en-tête de colonnes. + + + Attention ! Si vous ouvrez le fichier avec Excel, veuillez le sauvegarder (Fichier + > Enregistrer sous) au format{" "} + + CSV Delimiter + {" "} + après suppression de la première ligne pour assurer la compatibilité avec l‘enquête SIFA. + + + L’enquête SIFA sera terminée dès lors que le fichier est accepté par la plateforme SIFA. + + + En cas de difficultés ou questions, veuillez lire la{" "} + + FAQ dédiée + + . + + + + @@ -344,8 +388,6 @@ const SIFAPage = (props: SIFAPageProps) => { formation={formation} searchValue={searchValue} modeSifa={true} - triggerExpand={triggerExpand} - onTriggerExpand={setTriggerExpand} refetch={refetch} /> ); diff --git a/ui/modules/mon-espace/effectifs/engine/EffectifTableContainer.tsx b/ui/modules/mon-espace/effectifs/engine/EffectifTableContainer.tsx index 4720e74d5..cfd137518 100644 --- a/ui/modules/mon-espace/effectifs/engine/EffectifTableContainer.tsx +++ b/ui/modules/mon-espace/effectifs/engine/EffectifTableContainer.tsx @@ -1,6 +1,6 @@ import { Box, HStack, Text } from "@chakra-ui/react"; import { UseQueryResult } from "@tanstack/react-query"; -import { Dispatch, SetStateAction, useState } from "react"; +import { useState } from "react"; import { DoubleChevrons } from "@/theme/components/icons/DoubleChevrons"; @@ -11,8 +11,6 @@ interface EffectifsTableContainerProps { modeSifa?: boolean; canEdit?: boolean; searchValue?: string; - triggerExpand: object; - onTriggerExpand: Dispatch>; tableId: string; formation: any; refetch: (options: { throwOnError: boolean; cancelRefetch: boolean }) => Promise; @@ -22,8 +20,6 @@ const EffectifsTableContainer = ({ formation, canEdit, searchValue, - triggerExpand, - onTriggerExpand, tableId, modeSifa, refetch, @@ -49,8 +45,6 @@ const EffectifsTableContainer = ({ organismesEffectifs={effectifs} searchValue={searchValue} onCountItemsChange={(count) => setCount(count)} - triggerExpand={triggerExpand} - onTriggerExpand={onTriggerExpand} modeSifa={modeSifa} refetch={refetch} /> diff --git a/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx b/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx index 60163778e..cf656ab32 100644 --- a/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx +++ b/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx @@ -1,4 +1,4 @@ -import { Box, Text, HStack, Button, UnorderedList, ListItem } from "@chakra-ui/react"; +import { Box, Text, HStack, Button, UnorderedList, ListItem, Flex } from "@chakra-ui/react"; import { UseQueryResult } from "@tanstack/react-query"; import { DateTime } from "luxon"; import React, { useState } from "react"; @@ -36,8 +36,6 @@ interface EffectifsTableProps { searchValue?: string; RenderErrorImport?: (data: any) => any; onCountItemsChange?: (count: number) => any; - triggerExpand: any; - onTriggerExpand: any; tableId: string; refetch: (options: { throwOnError: boolean; cancelRefetch: boolean }) => Promise; } @@ -51,8 +49,6 @@ const EffectifsTable = ({ searchValue, RenderErrorImport = () => {}, onCountItemsChange = () => {}, - triggerExpand, - onTriggerExpand, tableId, refetch, }: EffectifsTableProps) => { @@ -70,32 +66,7 @@ const EffectifsTable = ({ setCount(count); onCountItemsChange(count); }} - triggerExpand={triggerExpand} columns={{ - ...(columns.includes("expander") - ? { - expander: { - size: 25, - header: () => " ", - cell: ({ row }) => { - return row.getCanExpand() ? ( - - ) : null; - }, - }, - } - : {}), ...(columns.includes("annee_scolaire") ? { annee_scolaire: { @@ -170,40 +141,6 @@ const EffectifsTable = ({ }, } : {}), - ...(columns.includes("statut_courant") - ? { - statut_courant: { - size: 170, - header: () => "Statut courant apprenant(e)", - cell: ({ row }) => { - const statut = organismesEffectifs[row.id]?.statut; - - if (!statut || !statut.parcours.length) { - return ( - - Aucun statut - - ); - } - const historiqueSorted = statut.parcours.sort((a, b) => { - return new Date(a.date_statut).getTime() - new Date(b.date_statut).getTime(); - }); - const current = [...historiqueSorted].pop(); - - return ( - - - {getStatut(current.valeur)} - - - (depuis {DateTime.fromISO(current.date).setLocale("fr-FR").toFormat("dd/MM/yyyy")}) - - - ); - }, - }, - } - : {}), ...(columns.includes("nom") ? { nom: { @@ -257,6 +194,40 @@ const EffectifsTable = ({ }, } : {}), + ...(columns.includes("statut_courant") + ? { + statut_courant: { + size: 170, + header: () => "Statut courant apprenant(e)", + cell: ({ row }) => { + const statut = organismesEffectifs[row.id]?.statut; + + if (!statut || !statut.parcours.length) { + return ( + + Aucun statut + + ); + } + const historiqueSorted = statut.parcours.sort((a, b) => { + return new Date(a.date_statut).getTime() - new Date(b.date_statut).getTime(); + }); + const current = [...historiqueSorted].pop(); + + return ( + + + {getStatut(current.valeur)} + + + (depuis {DateTime.fromISO(current.date).setLocale("fr-FR").toFormat("dd/MM/yyyy")}) + + + ); + }, + }, + } + : {}), ...(columns.includes("separator") ? { separator: { @@ -347,12 +318,31 @@ const EffectifsTable = ({ État de la donnée État de la donnée} contentComponent={() => ( - - {modeSifa - ? "Ce champ indique si les données renseignées sont complètes pour l'enquête SIFA ou non." - : "Ce champ indique si les données affichées contiennent des erreurs ou non."} - + + {modeSifa ? ( + <> + + Cette colonne indique si les données obligatoires pour l’enquête SIFA sont complètes + ou manquantes. + + + Si manquante(s), veuillez les compléter à la source (sur votre ERP si votre organisme + transmet via API). Les modifications apportées seront visibles dès le lendemain sur + cette page. + + + Note : vous pouvez aussi télécharger le fichier en l’état, mais il + faudra compléter les données manquantes sur ce dernier. + + + ) : ( + + Ce champ indique si les données affichées contiennent des erreurs ou non. + + )} + )} /> @@ -365,14 +355,14 @@ const EffectifsTable = ({ const MissingSIFA = ({ requiredSifa }) => { if (!requiredSifa?.length) return ( - + Complète pour SIFA ); return ( - + {" "} {requiredSifa.length} manquante(s) pour SIFA {fieldName} ))} + + Veuillez le(s) corriger/compléter sur : + + + + + votre outil de gestion (ex : Gestibase, Ypareo) si vous transmettez par API. La + donnée apparaîtra sur le Tableau de bord dans les prochaines 24 heures. + + + + + le Tableau de bord de l’apprentissage si vous avez transmis vos effectifs via + fichier Excel. + + + )} /> @@ -396,7 +403,7 @@ const EffectifsTable = ({ if (!validation_errors?.length) return null; return ( - + {" "} {validation_errors.length} erreur(s) de transmission @@ -426,6 +433,27 @@ const EffectifsTable = ({ }, } : {}), + ...(columns.includes("expander") + ? { + expander: { + size: 25, + header: () => " ", + cell: ({ row }) => { + return row.getCanExpand() ? ( + + + + ) : null; + }, + }, + } + : {}), }} getRowCanExpand={() => true} renderSubComponent={({ row }) => { diff --git a/ui/modules/mon-espace/effectifs/engine/effectifForm/EffectifForm.tsx b/ui/modules/mon-espace/effectifs/engine/effectifForm/EffectifForm.tsx index c4909b08d..aed25f3f7 100644 --- a/ui/modules/mon-espace/effectifs/engine/effectifForm/EffectifForm.tsx +++ b/ui/modules/mon-espace/effectifs/engine/effectifForm/EffectifForm.tsx @@ -12,6 +12,7 @@ import { Select, FormControl, Input, + Flex, } from "@chakra-ui/react"; import { UseQueryResult } from "@tanstack/react-query"; import React, { memo, useEffect, useRef, useState } from "react"; @@ -69,11 +70,17 @@ const SuppressionEffectifComponent = ({ nom, prenom, id, refetch }) => { }; return ( - + } action={() => {}}> + } + action={() => {}} + > Supprimer l'apprenant } @@ -157,7 +164,7 @@ const SuppressionEffectifComponent = ({ nom, prenom, id, refetch }) => { )} {" "} - + ); }; diff --git a/ui/modules/mon-espace/effectifs/engine/formEngine/components/Input/InputController.tsx b/ui/modules/mon-espace/effectifs/engine/formEngine/components/Input/InputController.tsx index 825f049d3..3d9c2c99d 100644 --- a/ui/modules/mon-espace/effectifs/engine/formEngine/components/Input/InputController.tsx +++ b/ui/modules/mon-espace/effectifs/engine/formEngine/components/Input/InputController.tsx @@ -28,8 +28,7 @@ export const InputController = memo(({ name, fieldType, mt, mb, ml, mr, w, onApp fieldType={fieldType ?? "text"} name={name} {...field} - // locked={field.locked && Boolean(field.value)} - locked={true} + locked={field.locked} value={field.value ?? ""} onChange={handle} isRequired={field.required} diff --git a/ui/modules/mon-espace/effectifs/engine/formEngine/controls/apprenantCodePostalDeNaissance.control.ts b/ui/modules/mon-espace/effectifs/engine/formEngine/controls/apprenantCodePostalDeNaissance.control.ts index e37a122c1..fff987914 100644 --- a/ui/modules/mon-espace/effectifs/engine/formEngine/controls/apprenantCodePostalDeNaissance.control.ts +++ b/ui/modules/mon-espace/effectifs/engine/formEngine/controls/apprenantCodePostalDeNaissance.control.ts @@ -20,7 +20,7 @@ export const apprenantCodePostalDeNaissanceControl = [ signal, }); - if (response.messages.error) { + if (response?.messages?.error) { return { error: response.messages.error }; } }, diff --git a/ui/modules/mon-espace/effectifs/engine/formEngine/initFields.tsx b/ui/modules/mon-espace/effectifs/engine/formEngine/initFields.tsx index 622769587..06e97091e 100644 --- a/ui/modules/mon-espace/effectifs/engine/formEngine/initFields.tsx +++ b/ui/modules/mon-espace/effectifs/engine/formEngine/initFields.tsx @@ -256,8 +256,8 @@ const createFieldFactory = } if (modeSifa && requiredFieldsSifa.includes(name)) { - fieldSchema.required = true; // TODO - if (!value) fieldSchema.warning = "Requis pour l'enquête"; // TODO + fieldSchema.required = true; + if (!value) fieldSchema.warning = "Donnée requise par SIFA. Veuillez la compléter."; } return { @@ -268,8 +268,7 @@ const createFieldFactory = pattern: data.pattern, enum: data.enum, ...fieldSchema, - // locked: !draft ? true : fieldSchema.locked ?? data.locked, - locked: fieldSchema.locked ?? data.locked, + locked: modeSifa ? false : true, success: !isEmptyValue(data?.value), description: fieldSchema.showInfo ? data.description : fieldSchema.description, example: data.example, diff --git a/ui/modules/organismes/InfoTeleversementSIFA.tsx b/ui/modules/organismes/InfoTeleversementSIFA.tsx new file mode 100644 index 000000000..8dabb9b32 --- /dev/null +++ b/ui/modules/organismes/InfoTeleversementSIFA.tsx @@ -0,0 +1,46 @@ +import { Box, Grid, HStack, ListItem, Text, UnorderedList } from "@chakra-ui/react"; + +export default function InfoTeleversementSIFA() { + return ( + <> + + 18 variables sont obligatoires pour chaque effectif présent au 31/12 de l’année N : + + + + + + le numéro UAI (unité administrative immatriculée) de l’établissement + le type de CFA + le numéro UAI du site de formation + l’UAI de l’EPLE + la nature de la structure juridique + le statut du jeune + le diplôme préparé + La durée théorique de la formation + La durée réelle de la formation + + + + + le nom du jeune (nom1) + le prénom du jeune (prénom1) + l’adresse + La date de naissance + le lieu de naissance + le sexe + la situation ou classe fréquentée l’année dernière (N-1) + le numéro UAI de l’établissement fréquenté l’année dernière (N-1) + + + + + + + L'application SIFA permettra d'importer des fichiers qui contiennent soit des codes diplômes soit + des codes RNCP. Dans un fichier, il ne peut y avoir qu’un seul type de codes. + + + + ); +}