From 7082b65045f25ac67cfe8a8f61099326ea0ac4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Dr=C3=A9au?= Date: Mon, 2 Oct 2023 15:57:02 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20wip=20simplification=20=C3=A9crans=20me?= =?UTF-8?q?s=20effectifs=20+=20param=C3=A8tres?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: configuration ERP v2 feat: wip configuration v3 feat: enregistrement ERP non supporté feat: sauvegarde l'auteur de la configuration ERP feat: redirection apiv3 ok --- .../actions/organismes/organismes.actions.ts | 54 +- server/src/common/model/@types/Organisme.ts | 12 +- server/src/common/model/organismes.model.ts | 11 +- .../validation/configurationERPSchema.ts | 6 +- server/src/http/server.ts | 8 + .../integration/http/organisme.routes.test.ts | 2 - shared/constants/erps.ts | 56 ++ shared/constants/index.ts | 1 + ui/common/constants/erps.ts | 71 --- ui/common/internal/Organisme.ts | 12 +- ui/components/ErpTutorial/ErpTutorial.tsx | 3 +- ui/components/Links/Link.tsx | 2 +- .../Page/components/NavigationMenu.tsx | 12 +- ui/components/buttons/Button.tsx | 45 ++ ui/components/buttons/DownloadButton.tsx | 42 +- ui/hooks/organismes.ts | 2 + ui/modules/admin/OrganismeDetail.tsx | 3 - ui/modules/dashboard/DashboardOrganisme.tsx | 50 +- ui/modules/effectifs/EffectifsPage.tsx | 244 ++++++++ ui/modules/indicateurs/NewTable.tsx | 116 ++-- ui/modules/mon-espace/SIFA/SIFAPage.tsx | 54 +- ui/modules/mon-espace/effectifs/ChoixERP.tsx | 2 +- .../mon-espace/effectifs/ConfigurationAPI.tsx | 2 +- .../mon-espace/effectifs/EffectifsBanner.tsx | 2 +- .../effectifs/engine/EffectifsTable.tsx | 2 +- .../DemandeBranchementErpForm.tsx | 2 +- .../DemandeBranchementErpFormBlock.tsx | 3 +- .../useSubmitDemandeBranchementErp.ts | 2 +- ui/pages/connexion-api.tsx | 4 +- ui/pages/effectifs.tsx | 21 +- .../organismes/[organismeId]/effectifs.tsx | 22 +- ui/pages/parametres.tsx | 538 ++++++++++++++++++ .../images/parametres-choix-transmission.svg | 1 + ui/theme/components/link.js | 5 + 34 files changed, 1124 insertions(+), 288 deletions(-) create mode 100644 shared/constants/erps.ts delete mode 100644 ui/common/constants/erps.ts create mode 100644 ui/components/buttons/Button.tsx create mode 100644 ui/modules/effectifs/EffectifsPage.tsx create mode 100644 ui/pages/parametres.tsx create mode 100644 ui/public/images/parametres-choix-transmission.svg diff --git a/server/src/common/actions/organismes/organismes.actions.ts b/server/src/common/actions/organismes/organismes.actions.ts index 82ac075a98..df23cd2101 100644 --- a/server/src/common/actions/organismes/organismes.actions.ts +++ b/server/src/common/actions/organismes/organismes.actions.ts @@ -650,15 +650,46 @@ export async function configureOrganismeERP( if (!(await canConfigureOrganismeERP(ctx, organismeId))) { throw Boom.forbidden("Permissions invalides"); } - if (conf.mode_de_transmission === null) { - await organismesDb().updateOne( - { _id: new ObjectId(organismeId) }, - { - $unset: { mode_de_transmission: "" }, - } - ); + await organismesDb().updateOne( + { _id: new ObjectId(organismeId) }, + { + $set: { + mode_de_transmission: conf.mode_de_transmission, + erps: conf.erps ?? [], + ...(conf.erp_unsupported ? { erp_unsupported: conf.erp_unsupported } : {}), + mode_de_transmission_configuration_date: new Date(), + mode_de_transmission_configuration_author_fullname: `${ctx.prenom} ${ctx.nom}`, + }, + $unset: conf.erp_unsupported + ? {} + : { + erp_unsupported: 1, + }, + } + ); +} + +export async function resetConfigurationERP(ctx: AuthContext, organismeId: ObjectId): Promise { + if (!(await canConfigureOrganismeERP(ctx, organismeId))) { + throw Boom.forbidden("Permissions invalides"); } - await organismesDb().updateOne({ _id: new ObjectId(organismeId) }, { $set: stripEmptyFields(conf) as any }); + await organismesDb().updateOne( + { _id: new ObjectId(organismeId) }, + { + $unset: { + mode_de_transmission: 1, + mode_de_transmission_configuration_date: 1, + erp_unsupported: 1, + api_configuration_date: 1, + api_siret: 1, + api_uai: 1, + // pas besoin de réinitialiser api_key + }, + $set: { + erps: [], + }, + } + ); } export async function verifyOrganismeAPIKeyToUser( @@ -813,10 +844,12 @@ export function getOrganismeProjection( organismesFormateurs: 1, fiabilisation_statut: 1, erps: permissionsOrganisme.infoTransmissionEffectifs, + erp_unsupported: permissionsOrganisme.infoTransmissionEffectifs, first_transmission_date: permissionsOrganisme.infoTransmissionEffectifs, last_transmission_date: permissionsOrganisme.infoTransmissionEffectifs, mode_de_transmission: permissionsOrganisme.infoTransmissionEffectifs, - setup_step_courante: permissionsOrganisme.infoTransmissionEffectifs, + mode_de_transmission_configuration_date: permissionsOrganisme.infoTransmissionEffectifs, + mode_de_transmission_configuration_author_fullname: permissionsOrganisme.infoTransmissionEffectifs, // configuration API api_key: permissionsOrganisme.manageEffectifs, @@ -852,6 +885,9 @@ export function getOrganismeListProjection( erps: { $cond: [infoTransmissionEffectifsCondition, "$erps", undefined], }, + erp_unsupported: { + $cond: [infoTransmissionEffectifsCondition, "$erp_unsupported", undefined], + }, first_transmission_date: { $cond: [infoTransmissionEffectifsCondition, "$first_transmission_date", undefined], }, diff --git a/server/src/common/model/@types/Organisme.ts b/server/src/common/model/@types/Organisme.ts index 6fc05b9a35..90a7d9548c 100644 --- a/server/src/common/model/@types/Organisme.ts +++ b/server/src/common/model/@types/Organisme.ts @@ -40,6 +40,10 @@ export interface Organisme { * ERPs rattachés au CFA, s'ils existent */ erps?: string[]; + /** + * ERP renseigné par l'utilisateur à la configuration quand il n'est pas supporté + */ + erp_unsupported?: string; /** * Compteur sur le nombre d'effectifs de l'organisme */ @@ -1009,9 +1013,13 @@ export interface Organisme { */ mode_de_transmission?: "API" | "MANUEL"; /** - * Etape d'installation courante + * Date à laquelle le mode de transmission a été configuré + */ + mode_de_transmission_configuration_date?: Date; + /** + * Auteur de la configuration (prénom nom) */ - setup_step_courante?: "STEP1" | "STEP2" | "STEP3" | "COMPLETE"; + mode_de_transmission_configuration_author_fullname?: string; /** * Flag pour identifier que c'est un organisme créé à partir d'un lieu */ diff --git a/server/src/common/model/organismes.model.ts b/server/src/common/model/organismes.model.ts index 283ce32ad6..2322ae7e2b 100644 --- a/server/src/common/model/organismes.model.ts +++ b/server/src/common/model/organismes.model.ts @@ -91,6 +91,9 @@ const schema = object( description: "ERPs rattachés au CFA, s'ils existent", } ), + erp_unsupported: string({ + description: "ERP renseigné par l'utilisateur à la configuration quand il n'est pas supporté", + }), effectifs_count: integer({ description: "Compteur sur le nombre d'effectifs de l'organisme" }), effectifs_current_year_count: integer({ description: "Compteur sur le nombre d'effectifs de l'organisme sur l'année courante", @@ -181,9 +184,11 @@ const schema = object( description: "Mode de transmission des effectifs", enum: ["API", "MANUEL"], }), - setup_step_courante: string({ - description: "Etape d'installation courante", - enum: ["STEP1", "STEP2", "STEP3", "COMPLETE"], + mode_de_transmission_configuration_date: date({ + description: "Date à laquelle le mode de transmission a été configuré", + }), + mode_de_transmission_configuration_author_fullname: string({ + description: "Auteur de la configuration (prénom nom)", }), creation_statut: string({ description: "Flag pour identifier que c'est un organisme créé à partir d'un lieu", diff --git a/server/src/common/validation/configurationERPSchema.ts b/server/src/common/validation/configurationERPSchema.ts index 6a9835dc98..41c8ea6770 100644 --- a/server/src/common/validation/configurationERPSchema.ts +++ b/server/src/common/validation/configurationERPSchema.ts @@ -1,9 +1,9 @@ import { z } from "zod"; export const configurationERPSchema = { - erps: z.string().array().optional(), - mode_de_transmission: z.enum(["API", "MANUEL"]).nullable().optional(), - setup_step_courante: z.enum(["STEP1", "STEP2", "STEP3", "COMPLETE"]).optional(), + erps: z.string().toLowerCase().array().optional(), + mode_de_transmission: z.enum(["API", "MANUEL"]).optional(), + erp_unsupported: z.string().optional(), }; export type ConfigurationERP = z.infer>; diff --git a/server/src/http/server.ts b/server/src/http/server.ts index 2e96f57b70..4956cfbd8c 100644 --- a/server/src/http/server.ts +++ b/server/src/http/server.ts @@ -68,6 +68,7 @@ import { getOrganismeByUAIAndSIRET, getInvalidSiretsFromDossierApprenant, getInvalidUaisFromDossierApprenant, + resetConfigurationERP, } from "@/common/actions/organismes/organismes.actions"; import { searchOrganismesFormations } from "@/common/actions/organismes/organismes.formations.actions"; import { createSession } from "@/common/actions/sessions.actions"; @@ -459,6 +460,13 @@ function setupRoutes(app: Application) { await configureOrganismeERP(req.user, res.locals.organismeId, conf); }) ) + .delete( + "/configure-erp", + requireOrganismePermission("manageEffectifs"), + returnResult(async (req, res) => { + await resetConfigurationERP(req.user, res.locals.organismeId); + }) + ) .post( "/verify-user", requireOrganismePermission("manageEffectifs"), diff --git a/server/tests/integration/http/organisme.routes.test.ts b/server/tests/integration/http/organisme.routes.test.ts index bf45996bfd..ab4afc2dd4 100644 --- a/server/tests/integration/http/organisme.routes.test.ts +++ b/server/tests/integration/http/organisme.routes.test.ts @@ -627,7 +627,6 @@ describe("Routes /organismes/:id", () => { it("Erreur si non authentifié", async () => { const response = await httpClient.put(`/api/v1/organismes/${id(1)}/configure-erp`, { mode_de_transmission: "MANUEL", - setup_step_courante: "COMPLETE", }); expectUnauthorizedError(response); @@ -665,7 +664,6 @@ describe("Routes /organismes/:id", () => { `/api/v1/organismes/${id(1)}/configure-erp`, { mode_de_transmission: "MANUEL", - setup_step_courante: "COMPLETE", } ); diff --git a/shared/constants/erps.ts b/shared/constants/erps.ts new file mode 100644 index 0000000000..c297ee2138 --- /dev/null +++ b/shared/constants/erps.ts @@ -0,0 +1,56 @@ +import { sortAlphabeticallyBy } from "../utils"; + +interface ERP { + id: string; + name: string; + helpFilePath?: string; + helpFileSize?: string; + apiV3?: boolean; +} + +export const ERPS = sortAlphabeticallyBy("name", [ + { + id: "gesti", + name: "Gesti", + helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/gesti.pdf", + helpFileSize: "352 ko", + }, + { + id: "ymag", + name: "Ypareo", + helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/ypareo.pdf", + helpFileSize: "1.7 Mo", + }, + { + id: "scform", + name: "SC Form", + // helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/scform.pdf", + // helpFileSize: "734 ko", + apiV3: true, + }, + { + id: "formasup", + name: "Formasup", + }, + { + id: "fcamanager", + name: "FCA Manager", + helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/fcamanager.pdf", + helpFileSize: "288 ko", + }, +] satisfies ERP[]); + +export const ERPS_BY_ID = ERPS.reduce( + (acc, erp) => { + acc[erp.id] = erp; + return acc; + }, + {} as Record +); + +// obsolète, utilisé par les anciens composants uniquement +export const ERPS_FORM: any[] = [ + ...ERPS, + { id: "AUTRE", name: "Autre ERP", state: "otherErp" }, + { id: "NON", name: "Je n'ai pas d'ERP", state: "noErp" }, +]; diff --git a/shared/constants/index.ts b/shared/constants/index.ts index 511cef8eca..51e61da419 100644 --- a/shared/constants/index.ts +++ b/shared/constants/index.ts @@ -1,3 +1,4 @@ +export * from "./erps"; export * from "./plausible-goals"; export * from "./sifa"; export * from "./territoires"; diff --git a/ui/common/constants/erps.ts b/ui/common/constants/erps.ts deleted file mode 100644 index cc0a4342b6..0000000000 --- a/ui/common/constants/erps.ts +++ /dev/null @@ -1,71 +0,0 @@ -export const ERP_STATE = { - ready: "ready", - ongoing: "ongoing", - coming: "coming", - otherErp: "otherErp", - noErp: "noErp", -}; - -export const ERP_STATE_COLOR = { - [ERP_STATE.ready]: "#417DC4", - [ERP_STATE.ongoing]: "#C3992A", - [ERP_STATE.coming]: "#BD987A", -}; - -export const ERPS = [ - { - id: "GESTI", - name: "Gesti", - state: ERP_STATE.ready, - helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/gesti.pdf", - helpFileSize: "352kb", - }, - { - id: "YMAG", - name: "Ypareo", - state: ERP_STATE.ready, - helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/ypareo.pdf", - helpFileSize: "1.7Mb", - }, - { - id: "SCFORM", - name: "SC Form", - state: ERP_STATE.ready, - helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/scform.pdf", - helpFileSize: "734Kb", - }, - { - id: "FORMASUP", - name: "Formasup", - state: ERP_STATE.ready, - }, - { - id: "FCAMANAGER", - name: "FCA Manager", - state: ERP_STATE.ready, - helpFilePath: "https://files.tableau-de-bord.apprentissage.beta.gouv.fr/pas-a-pas/fcamanager.pdf", - helpFileSize: "288Kb", - }, - { - id: "AURIGA", - name: "Auriga", - state: ERP_STATE.coming, - helpFilePath: "https://wiki.auriga.fr/index.php?title=Connecteur_Tableau_de_bord_de_l%27apprentissage", - }, - { id: "CNAM", name: "CNAM (Gessic@)", state: ERP_STATE.coming }, - { id: "ALCUINSOFTWARE", name: "Alcuin Software", state: ERP_STATE.coming }, - { id: "HYPERPLANNING", name: "Hyperplanning", state: ERP_STATE.coming }, - { id: "VALSOFTWARE", name: "Valsoftware", state: ERP_STATE.coming }, - { id: "AGATE", name: "Agate", state: ERP_STATE.coming }, -]; - -export const ERPS_FORM_CASES = [ - ...ERPS, - { id: "AUTRE", name: "Autre ERP", state: ERP_STATE.otherErp }, - { id: "NON", name: "Je n'ai pas d'ERP", state: ERP_STATE.noErp }, -]; - -export const ERPS_BY_ID = ERPS_FORM_CASES.reduce((acc, item) => { - acc[item.id] = item; - return acc; -}, {}); diff --git a/ui/common/internal/Organisme.ts b/ui/common/internal/Organisme.ts index f32ceb79ea..dedf9cb616 100644 --- a/ui/common/internal/Organisme.ts +++ b/ui/common/internal/Organisme.ts @@ -34,6 +34,10 @@ export interface Organisme { * ERPs rattachés au CFA, s'ils existent */ erps?: string[]; + /** + * ERP renseigné par l'utilisateur à la configuration quand il n'est pas supporté + */ + erp_unsupported?: string; /** * Compteur sur le nombre d'effectifs de l'organisme */ @@ -973,9 +977,13 @@ export interface Organisme { */ mode_de_transmission?: "API" | "MANUEL"; /** - * Etape d'installation courante + * Date à laquelle le mode de transmission a été configuré + */ + mode_de_transmission_configuration_date?: string; + /** + * Auteur de la configuration (prénom nom) */ - setup_step_courante?: "STEP1" | "STEP2" | "STEP3" | "COMPLETE"; + mode_de_transmission_configuration_author_fullname?: string; /** * Date de mise à jour en base de données */ diff --git a/ui/components/ErpTutorial/ErpTutorial.tsx b/ui/components/ErpTutorial/ErpTutorial.tsx index d6498e8641..5d2fff1f26 100644 --- a/ui/components/ErpTutorial/ErpTutorial.tsx +++ b/ui/components/ErpTutorial/ErpTutorial.tsx @@ -1,8 +1,7 @@ import { ArrowForwardIcon } from "@chakra-ui/icons"; import { Box, Button, Heading, Stack, Text } from "@chakra-ui/react"; import React from "react"; - -import { ERPS_BY_ID } from "@/common/constants/erps"; +import { ERPS_BY_ID } from "shared"; const ErpTutorial = ({ erp, ...rest }) => { return ( diff --git a/ui/components/Links/Link.tsx b/ui/components/Links/Link.tsx index 828f1b47b5..1922e954f7 100644 --- a/ui/components/Links/Link.tsx +++ b/ui/components/Links/Link.tsx @@ -15,13 +15,13 @@ const Link = ({ children, href, shallow, plausibleGoal, ...rest }: LinkProps) => const { trackPlausibleEvent } = usePlausibleTracking(); return ( { plausibleGoal && trackPlausibleEvent(plausibleGoal); }} + {...rest} > {children} diff --git a/ui/components/Page/components/NavigationMenu.tsx b/ui/components/Page/components/NavigationMenu.tsx index 1215ca0502..b2ace9049a 100644 --- a/ui/components/Page/components/NavigationMenu.tsx +++ b/ui/components/Page/components/NavigationMenu.tsx @@ -1,4 +1,4 @@ -import { ChevronDownIcon } from "@chakra-ui/icons"; +import { ChevronDownIcon, SettingsIcon } from "@chakra-ui/icons"; import { Box, Container, Flex, Menu, MenuButton, MenuItem, MenuList, Text, Tooltip } from "@chakra-ui/react"; import { useRouter } from "next/router"; import { ReactElement, useState } from "react"; @@ -100,6 +100,7 @@ const NavBarPublic = () => { Opérateurs publics Organismes de formation + ); }; @@ -114,6 +115,7 @@ function NavBarTransverse(): ReactElement { {getMesOrganismesLabelFromOrganisationType(organisationType)} Mes indicateurs + ); } @@ -129,7 +131,6 @@ function NavBarOrganismeFormation(): ReactElement { {organisme?.organismesFormateurs && organisme.organismesFormateurs.length > 0 && ( Mes organismes )} - {/* */} Mes indicateurs Mes effectifs {organisme && ( @@ -143,6 +144,12 @@ function NavBarOrganismeFormation(): ReactElement { Mon enquête SIFA )} + + + + + Paramètres + ); } @@ -293,7 +300,6 @@ const NavigationMenu = () => { textStyle="sm" > {getNavBarComponent(auth)} - diff --git a/ui/components/buttons/Button.tsx b/ui/components/buttons/Button.tsx new file mode 100644 index 0000000000..04f43fb5ef --- /dev/null +++ b/ui/components/buttons/Button.tsx @@ -0,0 +1,45 @@ +import { Button, ButtonProps } from "@chakra-ui/react"; +import { useState } from "react"; + +import { sleep } from "@/common/utils/misc"; +import useToaster from "@/hooks/useToaster"; + +export type AppButtonProps = { + action: (() => Promise) | (() => any); + children: React.ReactNode; +} & ButtonProps; + +function AppButton({ children, action, ...props }: AppButtonProps) { + const { toastError } = useToaster(); + const [isLoading, setIsLoading] = useState(false); + + async function onClick() { + try { + setIsLoading(true); + await action(); + } catch (err) { + toastError(err.message); + } finally { + await sleep(300); // évite un changement instantané + setIsLoading(false); + } + } + + return ( + + ); +} + +export default AppButton; diff --git a/ui/components/buttons/DownloadButton.tsx b/ui/components/buttons/DownloadButton.tsx index 66dbaf5e47..a530163e8f 100644 --- a/ui/components/buttons/DownloadButton.tsx +++ b/ui/components/buttons/DownloadButton.tsx @@ -1,46 +1,12 @@ -import { Button, ButtonProps } from "@chakra-ui/react"; -import { useState } from "react"; - -import { sleep } from "@/common/utils/misc"; -import useToaster from "@/hooks/useToaster"; import { DownloadLine } from "@/theme/components/icons"; -type Props = { - action: (() => Promise) | (() => any); - children: React.ReactNode; -} & ButtonProps; - -function DownloadButton({ children, action, ...props }: Props) { - const { toastError } = useToaster(); - const [isLoading, setIsLoading] = useState(false); - - async function onClick() { - try { - setIsLoading(true); - await action(); - } catch (err) { - toastError(err.message); - } finally { - await sleep(300); // évite un changement instantané - setIsLoading(false); - } - } +import AppButton, { AppButtonProps } from "./Button"; +function DownloadButton({ children, ...props }: AppButtonProps) { return ( - + ); } diff --git a/ui/hooks/organismes.ts b/ui/hooks/organismes.ts index fe15d51004..2b9325bb5c 100644 --- a/ui/hooks/organismes.ts +++ b/ui/hooks/organismes.ts @@ -54,6 +54,7 @@ export function useOrganisationOrganisme(enabled?: boolean) { const { data: organisme, isLoading, + refetch, error, } = useQuery(["organisation/organisme"], () => _get("/api/v1/organisation/organisme"), { enabled: enabled ?? true, @@ -62,6 +63,7 @@ export function useOrganisationOrganisme(enabled?: boolean) { return { organisme, isLoading, + refetch, error, }; } diff --git a/ui/modules/admin/OrganismeDetail.tsx b/ui/modules/admin/OrganismeDetail.tsx index 2483e0d5e6..60a9167e97 100644 --- a/ui/modules/admin/OrganismeDetail.tsx +++ b/ui/modules/admin/OrganismeDetail.tsx @@ -147,9 +147,6 @@ const OrganismeDetail = ({ data }) => { mode_de_transmission: { header: () => "Mode de transmission", }, - setup_step_courante: { - header: () => "Setup step courante", - }, fiabilisation_statut: { header: () => "Fiabilisation", cell: ({ value }) => ( diff --git a/ui/modules/dashboard/DashboardOrganisme.tsx b/ui/modules/dashboard/DashboardOrganisme.tsx index 2d5751999e..76deb11156 100644 --- a/ui/modules/dashboard/DashboardOrganisme.tsx +++ b/ui/modules/dashboard/DashboardOrganisme.tsx @@ -450,15 +450,16 @@ const DashboardOrganisme = ({ organisme, modePublique }: Props) => { {modePublique ? ( "Cet établissement ne transmet pas encore ses effectifs au tableau de bord." - ) : ( + ) : !organisme.mode_de_transmission ? ( <> - Les indicateurs sont nuls car votre établissement ne transmet pas encore ses effectifs. Veuillez - cliquer dans l’onglet{" "} - - Mes effectifs + Les indicateurs sont nuls car votre établissement ne transmet pas encore ses effectifs. Veuillez{" "} + + paramétrer {" "} - pour démarrer l’interfaçage ERP ou transmettre manuellement vos effectifs. + votre moyen de transmission. + ) : ( + <>Les indicateurs sont nuls car votre établissement ne transmet pas encore ses effectifs. )} @@ -469,31 +470,28 @@ const DashboardOrganisme = ({ organisme, modePublique }: Props) => { )} {aucunEffectifTransmis ? ( - !modePublique && ( - - ) + !modePublique && + (!organisme.mode_de_transmission ? ( + + Paramétrer un moyen de transmission + + ) : ( + organisme.mode_de_transmission === "MANUEL" && ( + + Ajouter via fichier Excel + + ) + )) ) : ( - + )} {organisme.organismesFormateurs && organisme.organismesFormateurs.length > 0 && ( diff --git a/ui/modules/effectifs/EffectifsPage.tsx b/ui/modules/effectifs/EffectifsPage.tsx new file mode 100644 index 0000000000..4a69607872 --- /dev/null +++ b/ui/modules/effectifs/EffectifsPage.tsx @@ -0,0 +1,244 @@ +import { AddIcon } from "@chakra-ui/icons"; +import { + Box, + Button, + Center, + Circle, + Container, + HStack, + Heading, + Spinner, + Switch, + Text, + VStack, +} from "@chakra-ui/react"; +import { useQuery } from "@tanstack/react-query"; +import groupBy from "lodash.groupby"; +import { useRouter } from "next/router"; +import { useMemo, useState } from "react"; +import { ERPS_BY_ID } from "shared"; + +import { _get } from "@/common/httpClient"; +import { Organisme } from "@/common/internal/Organisme"; +import Link from "@/components/Links/Link"; +import SimplePage from "@/components/Page/SimplePage"; +import Ribbons from "@/components/Ribbons/Ribbons"; +import { DoubleChevrons } from "@/theme/components/icons/DoubleChevrons"; + +import EffectifsTable from "../mon-espace/effectifs/engine/EffectifsTable"; +import { Input } from "../mon-espace/effectifs/engine/formEngine/components/Input/Input"; + +interface EffectifsPageProps { + organisme: Organisme; + modePublique: boolean; +} +function EffectifsPage(props: EffectifsPageProps) { + const router = useRouter(); + + const [searchValue, setSearchValue] = useState(""); + const [showOnlyErrors, setShowOnlyErrors] = useState(false); + const [anneScolaire, setAnneScolaire] = useState("all"); + + const { data: organismesEffectifs, isLoading } = useQuery(["organismes", props.organisme._id, "effectifs"], () => + _get(`/api/v1/organismes/${props.organisme._id}/effectifs`) + ); + + const { data: duplicates } = useQuery(["organismes", props.organisme._id, "duplicates"], () => + _get(`/api/v1/organismes/${props.organisme?._id}/duplicates`) + ); + + const effectifsByAnneeScolaire = useMemo(() => groupBy(organismesEffectifs, "annee_scolaire"), [organismesEffectifs]); + + const title = `${props.modePublique ? "Ses" : "Mes"} effectifs`; + return ( + + + + + {props.modePublique ? "Ses" : "Mes"} effectifs + + + + + Ajouter via fichier Excel + + + + {organismesEffectifs && + organismesEffectifs.length === 0 && + props.organisme.erps && + props.organisme.erps.length > 0 && ( + + + + {ERPS_BY_ID[props.organisme.erps[0]]?.name} est votre moyen de transmission. + + + L’importation de vos effectifs est en cours. Le tableau de bord recevra vos effectifs entre 24-48 + heures. Revenez plus tard pour consulter le tableau de vos effectifs. + + + + )} + + {duplicates && duplicates?.length > 0 && ( + + + + Nous avons détecté {duplicates?.length} duplicat{duplicates?.length > 1 ? "s" : ""} pour l’année + scolaire en cours. + + + + Vérifier + + + + )} + + {isLoading && ( +
+ +
+ )} + + {organismesEffectifs && organismesEffectifs.length > 0 && ( + <> + + { + setSearchValue(value.trim()); + }, + }} + w="35%" + /> + + + + + Filtrer : + + + { + setShowOnlyErrors(e.target.checked); + }} + /> + Afficher uniquement les données en erreur + + + + Par année scolaire + setAnneScolaire("all")} active={anneScolaire === "all"}> + Toutes + + {Object.keys(effectifsByAnneeScolaire).map((anneeScolaire) => { + return ( + setAnneScolaire(anneeScolaire)} + key={anneeScolaire} + active={anneScolaire === anneeScolaire} + > + {anneeScolaire} + + ); + })} + + + + )} + + + {Object.entries(effectifsByAnneeScolaire).map(([anneeScolaire, effectifs]) => { + if (anneScolaire !== "all" && anneScolaire !== anneeScolaire) { + return null; + } + const orgaEffectifs = showOnlyErrors + ? effectifs.filter((effectif) => effectif.validation_errors.length) + : effectifs; + const effectifsByCfd: { [cfd: string]: any[] } = groupBy(orgaEffectifs, "formation.cfd"); + return ( + + + {anneeScolaire} {!searchValue ? `- ${orgaEffectifs.length} apprenant(es) total` : ""} + + + {Object.entries(effectifsByCfd).map(([cfd, effectifs], i) => { + const { formation } = effectifs[0]; + return ( + + ); + })} + + + ); + })} + +
+
+ ); +} + +export default EffectifsPage; + +const BadgeButton = ({ onClick, active = false, children, ...props }) => { + return ( + + ); +}; + +const EffectifsTableContainer = ({ effectifs, formation, canEdit, searchValue, ...props }) => { + const [count, setCount] = useState(effectifs.length); + return ( + + {count !== 0 && ( + + + + {formation.libelle_long} + + + [Code diplôme {formation.cfd}] - [Code RNCP {formation.rncp}] + + + )} + setCount(count)} + /> + + ); +}; diff --git a/ui/modules/indicateurs/NewTable.tsx b/ui/modules/indicateurs/NewTable.tsx index 8ac7efba7f..d52173134e 100644 --- a/ui/modules/indicateurs/NewTable.tsx +++ b/ui/modules/indicateurs/NewTable.tsx @@ -26,6 +26,7 @@ interface NewTableProps extends SystemProps { sortingState?: SortingState; paginationState?: PaginationState; variant?: string; + showPagination?: boolean; onSortingChange?: (state: SortingState) => any; onPaginationChange?: (state: PaginationState) => any; renderSubComponent?: (row: Row) => React.ReactElement; @@ -93,6 +94,7 @@ function NewTable(props: NewTableProps) { } : undefined } + w={header.getSize()} > {header.isPlaceholder ? null : ( <> @@ -153,68 +155,70 @@ function NewTable(props: NewTableProps) { - - - - - - {table.getState().pagination.pageIndex - 1 > 0 && ( - - )} - {table.getState().pagination.pageIndex > 0 && ( - - )} - - {table.getCanNextPage() && ( - - )} - {table.getState().pagination.pageIndex + 2 < table.getPageCount() && ( - + )} + {table.getState().pagination.pageIndex > 0 && ( + + )} + - )} + {table.getCanNextPage() && ( + + )} + {table.getState().pagination.pageIndex + 2 < table.getPageCount() && ( + + )} - - - + + + - - + + + - + )} ); } diff --git a/ui/modules/mon-espace/SIFA/SIFAPage.tsx b/ui/modules/mon-espace/SIFA/SIFAPage.tsx index 152df2dacf..329641d01b 100644 --- a/ui/modules/mon-espace/SIFA/SIFAPage.tsx +++ b/ui/modules/mon-espace/SIFA/SIFAPage.tsx @@ -88,7 +88,7 @@ const SIFAPage = (props: SIFAPageProps) => { if (isLoading) { return ( -
+
); @@ -100,33 +100,31 @@ const SIFAPage = (props: SIFAPageProps) => { {props.modePublique ? "Son" : "Mon"} Enquête SIFA - - { - trackPlausibleEvent("telechargement_sifa"); - downloadObject( - await _getBlob(`/api/v1/organismes/${organisme._id}/sifa-export`), - `tdb-données-sifa-${ - organisme.enseigne ?? organisme.raison_sociale ?? "Organisme inconnu" - }-${new Date().toLocaleDateString()}.csv`, - "text/plain" - ); - const nbEffectifsInvalides = organismesEffectifs.filter( - (effectif) => effectif.requiredSifa.length > 0 - ).length; - toastWarning( - `Parmi les ${organismesEffectifs.length} effectifs que vous avez déclarés, ${nbEffectifsInvalides} d'entre eux ne comportent pas l'ensemble des informations requises pour l'enquête SIFA. Si vous ne les corrigez/complétez pas, votre fichier risque d'être rejeté. Vous pouvez soit les éditer directement sur la plateforme soit modifier votre fichier sur votre ordinateur.`, - { - isClosable: true, - duration: 20000, - } - ); - }} - > - Télécharger le fichier SIFA - - + { + trackPlausibleEvent("telechargement_sifa"); + downloadObject( + await _getBlob(`/api/v1/organismes/${organisme._id}/sifa-export`), + `tdb-données-sifa-${ + organisme.enseigne ?? organisme.raison_sociale ?? "Organisme inconnu" + }-${new Date().toLocaleDateString()}.csv`, + "text/plain" + ); + const nbEffectifsInvalides = organismesEffectifs.filter( + (effectif) => effectif.requiredSifa.length > 0 + ).length; + toastWarning( + `Parmi les ${organismesEffectifs.length} effectifs que vous avez déclarés, ${nbEffectifsInvalides} d'entre eux ne comportent pas l'ensemble des informations requises pour l'enquête SIFA. Si vous ne les corrigez/complétez pas, votre fichier risque d'être rejeté. Vous pouvez soit les éditer directement sur la plateforme soit modifier votre fichier sur votre ordinateur.`, + { + isClosable: true, + duration: 20000, + } + ); + }} + > + Télécharger le fichier SIFA + diff --git a/ui/modules/mon-espace/effectifs/ChoixERP.tsx b/ui/modules/mon-espace/effectifs/ChoixERP.tsx index c4fa136bbe..73b84679d7 100644 --- a/ui/modules/mon-espace/effectifs/ChoixERP.tsx +++ b/ui/modules/mon-espace/effectifs/ChoixERP.tsx @@ -2,10 +2,10 @@ import { Box, Button, RadioGroup, Radio, Text, VStack, HStack } from "@chakra-ui import { useFormik } from "formik"; import { useRouter } from "next/router"; import React from "react"; +import { ERPS } from "shared"; import * as Yup from "yup"; import { configureOrganismeERP } from "@/common/api/tableauDeBord"; -import { ERPS } from "@/common/constants/erps"; import Ribbons from "@/components/Ribbons/Ribbons"; import { InfoCircle } from "@/theme/components/icons/index"; diff --git a/ui/modules/mon-espace/effectifs/ConfigurationAPI.tsx b/ui/modules/mon-espace/effectifs/ConfigurationAPI.tsx index eef3336047..a61d596d00 100644 --- a/ui/modules/mon-espace/effectifs/ConfigurationAPI.tsx +++ b/ui/modules/mon-espace/effectifs/ConfigurationAPI.tsx @@ -2,9 +2,9 @@ import { Box, Button, Flex, RadioGroup, Radio, Text, VStack, Stack, HStack } fro import { useFormik } from "formik"; import { useRouter } from "next/router"; import React from "react"; +import { ERPS } from "shared"; import { configureOrganismeERP } from "@/common/api/tableauDeBord"; -import { ERPS } from "@/common/constants/erps"; import { DownloadLine } from "@/theme/components/icons/index"; type ConfigurationAPIProps = { diff --git a/ui/modules/mon-espace/effectifs/EffectifsBanner.tsx b/ui/modules/mon-espace/effectifs/EffectifsBanner.tsx index aabd311b4e..3f5ff2f5f8 100644 --- a/ui/modules/mon-espace/effectifs/EffectifsBanner.tsx +++ b/ui/modules/mon-espace/effectifs/EffectifsBanner.tsx @@ -1,7 +1,7 @@ import { Image, VStack, Heading, Text, HStack } from "@chakra-ui/react"; import React from "react"; +import { ERPS } from "shared"; -import { ERPS } from "@/common/constants/erps"; import { Organisme } from "@/common/internal/Organisme"; import { prettyPrintDate } from "@/common/utils/dateUtils"; import Section from "@/components/Section/Section"; diff --git a/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx b/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx index 6f14d9634c..fe76d7b9dd 100644 --- a/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx +++ b/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx @@ -3,8 +3,8 @@ import { useQueryClient } from "@tanstack/react-query"; import { DateTime } from "luxon"; import React, { useEffect, useRef, useState } from "react"; import { useRecoilValue, useSetRecoilState } from "recoil"; +import { ERPS } from "shared"; -import { ERPS } from "@/common/constants/erps"; import Table from "@/components/Table/Table"; import { AddFill, Alert, InfoLine, SubtractLine, ValidateIcon } from "@/theme/components/icons"; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpForm.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpForm.tsx index 24f415a954..8b2c720ee8 100644 --- a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpForm.tsx +++ b/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpForm.tsx @@ -1,9 +1,9 @@ import { FormControl, FormErrorMessage, FormLabel, Select, Stack } from "@chakra-ui/react"; import { Field, Form, Formik } from "formik"; import React from "react"; +import { ERP_STATE, ERPS_FORM_CASES } from "shared"; import * as Yup from "yup"; -import { ERP_STATE, ERPS_FORM_CASES } from "@/common/constants/erps"; import { UAI_REGEX } from "@/common/domain/uai"; import { diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock.tsx index 09d028f214..1de00f31be 100644 --- a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock.tsx +++ b/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock.tsx @@ -1,8 +1,7 @@ import { Box, Flex, HStack, Text } from "@chakra-ui/react"; import NavLink from "next/link"; import React from "react"; - -import { ERP_STATE } from "@/common/constants/erps"; +import { ERP_STATE } from "shared"; import DemandeBranchementErpForm from "./DemandeBranchementErpForm"; import useSubmitDemandeBranchementErp, { SUBMIT_STATE } from "./useSubmitDemandeBranchementErp"; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/useSubmitDemandeBranchementErp.ts b/ui/modules/organisme-formation/DemandeBranchementErp/useSubmitDemandeBranchementErp.ts index b41f07cc06..f698c07a65 100644 --- a/ui/modules/organisme-formation/DemandeBranchementErp/useSubmitDemandeBranchementErp.ts +++ b/ui/modules/organisme-formation/DemandeBranchementErp/useSubmitDemandeBranchementErp.ts @@ -1,6 +1,6 @@ import { useState } from "react"; +import { ERPS_FORM_CASES } from "shared"; -import { ERPS_FORM_CASES } from "@/common/constants/erps"; import { _post } from "@/common/httpClient"; export const SUBMIT_STATE = { diff --git a/ui/pages/connexion-api.tsx b/ui/pages/connexion-api.tsx index 9f13a34687..b34fa8b5b7 100644 --- a/ui/pages/connexion-api.tsx +++ b/ui/pages/connexion-api.tsx @@ -34,7 +34,7 @@ const ConnexionAPI = () => { useEffect(() => { if (!router.query.api_key && !currentOrganisme?.api_key) { - router.push(`/mon-compte/erp?erp=${router.query.erp}`); + router.push("/parametres"); // FIXME REVIEW pas sûr du workflow ici } }, [currentOrganisme]); @@ -57,7 +57,7 @@ const ConnexionAPIVerifyUser = ({ organisme }) => { await _post("/api/v1/auth/logout"); router.push("/"); } else if (data?.message === "success") { - window.location.href = "/mon-compte/erp"; + window.location.href = `/parametres?erpV3=${router.query.erp}`; } } run(); diff --git a/ui/pages/effectifs.tsx b/ui/pages/effectifs.tsx index 9156778f43..3ae6bc8fc7 100644 --- a/ui/pages/effectifs.tsx +++ b/ui/pages/effectifs.tsx @@ -1,27 +1,18 @@ -import Head from "next/head"; -import React from "react"; - import { getAuthServerSideProps } from "@/common/SSR/getAuthServerSideProps"; -import Page from "@/components/Page/Page"; import withAuth from "@/components/withAuth"; -import EffectifsPage from "@/modules/mon-espace/effectifs/EffectifsPage"; +import EffectifsPage from "@/modules/effectifs/EffectifsPage"; import { useEffectifsOrganismeOrganisation } from "@/modules/mon-espace/effectifs/useEffectifsOrganisme"; export const getServerSideProps = async (context) => ({ props: { ...(await getAuthServerSideProps(context)) } }); const PageEffectifsDeMonOrganisme = () => { - const title = "Mes effectifs"; - const { organisme } = useEffectifsOrganismeOrganisation(); - return ( - - - {title} - - - - ); + if (!organisme) { + return <>; + } + + return ; }; export default withAuth(PageEffectifsDeMonOrganisme); diff --git a/ui/pages/organismes/[organismeId]/effectifs.tsx b/ui/pages/organismes/[organismeId]/effectifs.tsx index 31db60edca..19e2292703 100644 --- a/ui/pages/organismes/[organismeId]/effectifs.tsx +++ b/ui/pages/organismes/[organismeId]/effectifs.tsx @@ -1,27 +1,21 @@ -import Head from "next/head"; import { useRouter } from "next/router"; -import React from "react"; import { getAuthServerSideProps } from "@/common/SSR/getAuthServerSideProps"; -import Page from "@/components/Page/Page"; import withAuth from "@/components/withAuth"; -import EffectifsPage from "@/modules/mon-espace/effectifs/EffectifsPage"; -import { useEffectifsOrganisme } from "@/modules/mon-espace/effectifs/useEffectifsOrganisme"; +import { useOrganisme } from "@/hooks/organismes"; +import EffectifsPage from "@/modules/effectifs/EffectifsPage"; export const getServerSideProps = async (context) => ({ props: { ...(await getAuthServerSideProps(context)) } }); const PageEffectifsDeSonOrganisme = () => { const router = useRouter(); - const { organisme } = useEffectifsOrganisme(router.query.organismeId as string); + const { organisme } = useOrganisme(router.query.organismeId as string); - return ( - - - Ses effectifs - - - - ); + if (!organisme) { + return <>; + } + + return ; }; export default withAuth(PageEffectifsDeSonOrganisme); diff --git a/ui/pages/parametres.tsx b/ui/pages/parametres.tsx new file mode 100644 index 0000000000..7438d40fde --- /dev/null +++ b/ui/pages/parametres.tsx @@ -0,0 +1,538 @@ +import { ArrowForwardIcon } from "@chakra-ui/icons"; +import { + Box, + Button, + Checkbox, + Container, + FormControl, + FormLabel, + HStack, + Heading, + Image, + Input, + List, + ListIcon, + ListItem, + Select, + Stack, + Text, + VStack, +} from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import { ReactNode, useEffect, useState } from "react"; +import { CopyToClipboard } from "react-copy-to-clipboard"; +import { ERPS, ERPS_BY_ID } from "shared"; + +import { CONTACT_ADDRESS } from "@/common/constants/product"; +import { _delete, _post, _put } from "@/common/httpClient"; +import { Organisme } from "@/common/internal/Organisme"; +import { getAuthServerSideProps } from "@/common/SSR/getAuthServerSideProps"; +import { formatDateDayMonthYear, formatDateNumericDayMonthYear } from "@/common/utils/dateUtils"; +import AppButton from "@/components/buttons/Button"; +import Link from "@/components/Links/Link"; +import SimplePage from "@/components/Page/SimplePage"; +import Ribbons from "@/components/Ribbons/Ribbons"; +import withAuth from "@/components/withAuth"; +import { useOrganisationOrganisme } from "@/hooks/organismes"; +import useToaster from "@/hooks/useToaster"; +import NewTable from "@/modules/indicateurs/NewTable"; +import { Check, DownloadLine, Checkbox as IconCheckbox } from "@/theme/components/icons"; + +export const getServerSideProps = async (context) => ({ props: { ...(await getAuthServerSideProps(context)) } }); + +/** + * Composant à plusieurs états selon stepConfigurationERP. + */ +const ParametresPage = () => { + const router = useRouter(); + const { toastSuccess } = useToaster(); + const [stepConfigurationERP, setStepConfigurationERP] = useState< + "none" | "choix_erp" | "unsupported_erp" | "v2" | "v3" + >("none"); + const [selectedERPId, setSelectedERPId] = useState(""); + const [unsupportedERPName, setUnsupportedERPName] = useState(""); + + const erp = ERPS_BY_ID[selectedERPId]; + + const { organisme, refetch: refetchOrganisme } = useOrganisationOrganisme(); + + const erpV3 = (router.query.erpV3 as string | undefined)?.toLowerCase(); + + // redirige vers la finalisation API v3 si le paramètre est présent (= on vient de connexion-api) + useEffect(() => { + if (!erpV3) { + return; + } + setSelectedERPId(erpV3); + setStepConfigurationERP("v3"); + router.push(router.pathname); // supprime le paramètre en query + }, []); + + if (!organisme) { + return <>; + } + + const title = "Paramétrage de votre moyen de transmission"; + return ( + + + + {title} + + + {stepConfigurationERP === "none" && + (organisme.mode_de_transmission ? ( + <> + + + {organisme.mode_de_transmission === "API" ? ( + <> + + Votre moyen de transmission est paramétré avec{" "} + {organisme.erps?.map((erpId) => ERPS_BY_ID[erpId]?.name).join(", ")}. + + {organisme.mode_de_transmission_configuration_date && ( + + (configuré le{" "} + {formatDateNumericDayMonthYear(organisme.mode_de_transmission_configuration_date)} par{" "} + {organisme.mode_de_transmission_configuration_author_fullname}) + + )} + + Mes effectifs + + + ) : organisme.erp_unsupported ? ( + <> + + Votre moyen de transmission est {organisme.erp_unsupported}. + + + Actuellement, cet ERP n’est pas encore interfaçé avec le tableau de bord. Nous vous tiendrons + informé dès que ce sera le cas. + + En attendant, veuillez téléverser manuellement vos effectifs. + + Téléverser les effectifs + + + ) : ( + <> + + Votre établissement n’utilise pas d’ERP. + + Cliquez ci-dessous pour transmettre manuellement vos effectifs. + + Téléverser les effectifs + + + )} + + + + + + Vous avez besoin de modifier ou rajouter un moyen de transmission ? + + + + + ) : ( + <> + + + Si vous utilisez deux outils de gestion, vous aurez la possibilité de combiner deux moyens + d’importation. Cependant, vous devez paramétrer un premier (le principal). + + + + + + + Vous avez un ERP ? + + Liez votre ou vos ERP au tableau de bord + + + + + + Vous n’avez pas d’ERP ? + + Importez vos effectifs avec un fichier Excel + + + + + ))} + + {stepConfigurationERP === "choix_erp" && ( + <> + setSelectedERPId(e.target.value)}> + Sélectionnez votre ERP ou outil de gestion utilisé + + + + + + )} + + {stepConfigurationERP === "unsupported_erp" && ( + + + Votre établissement utilise un autre ERP. + + + setUnsupportedERPName(e.target.value)}> + Veuillez indiquer le nom de votre ERP : + + + + { + await _put(`/api/v1/organismes/${organisme._id}/configure-erp`, { + mode_de_transmission: "MANUEL", + erp_unsupported: unsupportedERPName, + }); + await refetchOrganisme(); + setStepConfigurationERP("none"); + setSelectedERPId(""); + setUnsupportedERPName(""); + }} + > + Valider + + + )} + {stepConfigurationERP === "v2" && ( + { + await _put(`/api/v1/organismes/${organisme._id}/configure-erp`, { + mode_de_transmission: "API", + erps: [selectedERPId], + }); + await refetchOrganisme(); + setStepConfigurationERP("none"); + setSelectedERPId(""); + }} + /> + )} + + {stepConfigurationERP === "v3" && ( + { + await _post(`/api/v1/organismes/${organisme._id}/api-key`); + toastSuccess("Votre clé d’échange a été correctement générée."); + await refetchOrganisme(); + }} + onConfigurationMismatch={async () => { + setStepConfigurationERP("none"); + setSelectedERPId(""); + }} + onSubmit={async () => { + await _put(`/api/v1/organismes/${organisme._id}/configure-erp`, { + mode_de_transmission: "API", + erps: [selectedERPId], + }); + await refetchOrganisme(); + setStepConfigurationERP("none"); + setSelectedERPId(""); + }} + /> + )} + + + ); +}; + +export default withAuth(ParametresPage); + +interface ConfigurationERPV2Props { + erpId: string; + onSubmit: () => any; +} +function ConfigurationERPV2(props: ConfigurationERPV2Props) { + const [configurationFinished, setConfigurationFinished] = useState(false); + const erp = ERPS_BY_ID[props.erpId]; + + return ( + + + Votre établissement utilise {erp.name}. + + + + Démarrer l’interfaçage avec {erp.name}. + + + {erp?.helpFilePath && ( + + + Télécharger le pas-à-pas {erp.name} + + + {erp.helpFileSize && ( + + PDF – {erp.helpFileSize} + + )} + + )} + + + Temps estimé : 5 minutes + + + Pré-requis : +

La configuration doit être effectuée par un administrateur sur {erp.name}.

+

Votre logiciel doit être à jour.

+

Vous devez avoir renseigné votre UAI et votre SIRET dans {erp.name}.

+
+ + } + onChange={(e) => { + setConfigurationFinished(e.target.checked); + }} + > + J’ai bien paramétré mon ERP avec le tableau de bord. + + + +
+ ); +} + +interface ConfigurationERPV3Props { + erpId: string; + organisme: Organisme; + onGenerateKey: () => any; + onConfigurationMismatch: () => any; + onSubmit: () => any; +} +function ConfigurationERPV3(props: ConfigurationERPV3Props) { + const { toastSuccess } = useToaster(); + const [copied, setCopied] = useState(false); + + const erp = ERPS_BY_ID[props.erpId]; + const verified = !!props.organisme.api_siret && !!props.organisme.api_uai; + + return ( + + + Votre établissement utilise {erp.name}. + + + {verified ? ( + <> + + Finalisez l’opération de paramétrage pour transmettre vos effectifs. + + + + + Vous avez correctement installé la nouvelle clé d’échange sur votre ERP + + + + + Confirmez que l’UAI et le SIRET indiqués ci-dessous correspondent à votre établissement : + + "Organisme de formation", + accessorKey: "_id", + enableSorting: false, + size: 450, + cell: () => props.organisme.enseigne ?? props.organisme.raison_sociale ?? "Organisme inconnu", + }, + { + header: () => "SIRET", + accessorKey: "api_siret", + size: 150, + enableSorting: false, + }, + { + header: () => "UAI", + accessorKey: "api_uai", + size: 100, + enableSorting: false, + }, + { + header: () => "Interfaçage", + accessorKey: "api_configuration_date", + size: 150, + enableSorting: false, + cell: ({ getValue }) => formatDateDayMonthYear(getValue()), + }, + ]} + /> + + + + Je ne confirme pas + + + Je confirme + + + + ) : ( + <> + + Démarrer l’interfaçage avec {erp.name}. + + + + + Comment générer votre clé d’échange + + + + 1. Générer la clé en cliquant sur le bouton ci-dessous + + 2. Copier la clé + + 3. Retourner dans votre compte ERP pour la coller + + + 4. Finaliser en confirmant l’UAI et SIRET de votre établissement + + + + + {props.organisme.api_key ? ( + <> + + { + setCopied(true); + toastSuccess("Copié !"); + }} + > + + + + ) : ( + + Générer la clé d’échange + + )} + + )} + + ); +} + +interface StepItemProps { + children: ReactNode; + active?: boolean; +} + +function StepItem({ children, active = false }: StepItemProps) { + return ( + + {active && } + {children} + + ); +} diff --git a/ui/public/images/parametres-choix-transmission.svg b/ui/public/images/parametres-choix-transmission.svg new file mode 100644 index 0000000000..3755b19972 --- /dev/null +++ b/ui/public/images/parametres-choix-transmission.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/theme/components/link.js b/ui/theme/components/link.js index 4f09597c2d..2ac3b9bcc1 100644 --- a/ui/theme/components/link.js +++ b/ui/theme/components/link.js @@ -6,6 +6,11 @@ const Link = { alignItems: "baseline", }, variants: { + link: { + color: "bluefrance", + borderBottom: "1px solid", + _hover: { textDecoration: "none", bg: "grey.200" }, + }, ghost: { _hover: { bg: "#eceae3", textDecoration: "none" }, },