diff --git a/server/src/common/actions/organismes/organismes.actions.ts b/server/src/common/actions/organismes/organismes.actions.ts index 82ac075a9..cd2867d77 100644 --- a/server/src/common/actions/organismes/organismes.actions.ts +++ b/server/src/common/actions/organismes/organismes.actions.ts @@ -650,15 +650,47 @@ 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, + mode_de_transmission_configuration_author_fullname: 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 +845,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 +886,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 271a0d52d..2754da9de 100644 --- a/server/src/common/model/@types/Organisme.ts +++ b/server/src/common/model/@types/Organisme.ts @@ -41,6 +41,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 */ @@ -1010,9 +1014,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 aad7a979e..2890cf19c 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 6a9835dc9..41c8ea677 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/db/migrations/20231012000000-reinit-organisme-api-sans-erps.ts b/server/src/db/migrations/20231012000000-reinit-organisme-api-sans-erps.ts new file mode 100644 index 000000000..97335789c --- /dev/null +++ b/server/src/db/migrations/20231012000000-reinit-organisme-api-sans-erps.ts @@ -0,0 +1,20 @@ +import { Db } from "mongodb"; + +export const up = async (db: Db) => { + // Certains organismes ont un mode de transmission défini à API sans ERPs. + // On réinitialise cette configuration incomplète pour les forcer à reparamétrer leur organisme. + await db.collection("organismes").updateMany( + { + mode_de_transmission: "API", + erps: [], + }, + { + $unset: { + mode_de_transmission: 1, + }, + }, + { + bypassDocumentValidation: true, + } + ); +}; diff --git a/server/src/http/server.ts b/server/src/http/server.ts index 9877137a3..e833a812d 100644 --- a/server/src/http/server.ts +++ b/server/src/http/server.ts @@ -69,6 +69,7 @@ import { getOrganismeByUAIAndSIRET, getInvalidSiretsFromDossierApprenant, getInvalidUaisFromDossierApprenant, + resetConfigurationERP, } from "@/common/actions/organismes/organismes.actions"; import { searchOrganismesFormations } from "@/common/actions/organismes/organismes.formations.actions"; import { getFicheRNCP } from "@/common/actions/rncp.actions"; @@ -474,6 +475,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 13bff286e..5e6710862 100644 --- a/server/tests/integration/http/organisme.routes.test.ts +++ b/server/tests/integration/http/organisme.routes.test.ts @@ -724,7 +724,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); @@ -762,7 +761,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 000000000..1f9f00ef5 --- /dev/null +++ b/shared/constants/erps.ts @@ -0,0 +1,59 @@ +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", + 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", + }, + { + id: "auriga", + name: "Auriga", + apiV3: true, + }, +] 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 6afe6934d..3e9284943 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 cc0a4342b..000000000 --- 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 f32ceb79e..dedf9cb61 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 deleted file mode 100644 index d6498e864..000000000 --- a/ui/components/ErpTutorial/ErpTutorial.tsx +++ /dev/null @@ -1,50 +0,0 @@ -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"; - -const ErpTutorial = ({ erp, ...rest }) => { - return ( - - - Tutoriel pour {ERPS_BY_ID[erp]?.name} - - - Une fois votre clé générée et copiée,
- veuillez la coller dans votre compte ERP. -
- Ci-dessous, voyez comment procéder. -
- - {ERPS_BY_ID[erp]?.helpFilePath && ( - - - {ERPS_BY_ID[erp]?.helpFileSize && ( - - PDF – {ERPS_BY_ID[erp]?.helpFileSize} - - )} - - )} -
- ); -}; - -export default ErpTutorial; diff --git a/ui/components/Links/Link.tsx b/ui/components/Links/Link.tsx index 828f1b47b..1922e954f 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 1215ca050..b2ace9049 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 000000000..04f43fb5e --- /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 66dbaf5e4..a530163e8 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 fe15d5100..2b9325bb5 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/hooks/userLocalStorage.ts b/ui/hooks/userLocalStorage.ts deleted file mode 100644 index 3a9fc3590..000000000 --- a/ui/hooks/userLocalStorage.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useState } from "react"; - -const useLocalStorage = (key, initialValue = "") => { - const [state, setState] = useState(() => { - // Initialize the state - try { - const value = window.localStorage.getItem(key); - // Check if the local storage already has any values, - // otherwise initialize it with the passed initialValue - return value ? JSON.parse(value) : initialValue; - } catch (error) { - console.log(error); - } - }); - - const setValue = (value) => { - try { - // If the passed value is a callback function, - // then call it with the existing state. - const valueToStore = value instanceof Function ? value(state) : value; - window.localStorage.setItem(key, JSON.stringify(valueToStore)); - setState(value); - } catch (error) { - console.log(error); - } - }; - - return [state, setValue]; -}; - -export default useLocalStorage; diff --git a/ui/modules/admin/OrganismeDetail.tsx b/ui/modules/admin/OrganismeDetail.tsx index 2483e0d5e..60a9167e9 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/auth/connexion/Connexion.tsx b/ui/modules/auth/connexion/Connexion.tsx index 09f19d023..2d7f9be6d 100644 --- a/ui/modules/auth/connexion/Connexion.tsx +++ b/ui/modules/auth/connexion/Connexion.tsx @@ -17,15 +17,15 @@ import { Field, Form, Formik } from "formik"; import NavLink from "next/link"; import { useRouter } from "next/router"; import React from "react"; +import { useLocalStorage } from "usehooks-ts"; import * as Yup from "yup"; import { _post } from "@/common/httpClient"; import useAuth from "@/hooks/useAuth"; -import useLocalStorage from "@/hooks/userLocalStorage"; import { AlertRounded, ShowPassword } from "@/theme/components/icons"; const Login = (props) => { - const [originConnexionUrl, setOriginConnexionUrl] = useLocalStorage("originConnexionUrl"); + const [originConnexionUrl, setOriginConnexionUrl] = useLocalStorage("originConnexionUrl", ""); const { refreshSession } = useAuth(); const router = useRouter(); diff --git a/ui/modules/dashboard/DashboardOrganisme.tsx b/ui/modules/dashboard/DashboardOrganisme.tsx index 1ace6a0e0..1c6455b90 100644 --- a/ui/modules/dashboard/DashboardOrganisme.tsx +++ b/ui/modules/dashboard/DashboardOrganisme.tsx @@ -38,6 +38,7 @@ import { DashboardWelcome } from "@/theme/components/icons/DashboardWelcome"; import { ExternalLinks } from "../admin/OrganismeDetail"; import { NewOrganisation } from "../auth/inscription/common"; import { IndicateursEffectifs, IndicateursEffectifsAvecFormation, IndicateursOrganismes } from "../models/indicateurs"; +import BandeauTransmission from "../organismes/BandeauTransmission"; import IndicateursEffectifsParFormationTable from "../organismes/IndicateursEffectifsParFormationTable"; import InfoTransmissionDonnees from "../organismes/InfoTransmissionDonnees"; @@ -460,22 +461,7 @@ const DashboardOrganisme = ({ organisme, modePublique }: Props) => { )} {aucunEffectifTransmis && ( - - - {modePublique ? ( - "Cet établissement ne transmet pas encore ses effectifs au tableau de bord." - ) : ( - <> - Les indicateurs sont nuls car votre établissement ne transmet pas encore ses effectifs. Veuillez - cliquer dans l’onglet{" "} - - Mes effectifs - {" "} - pour démarrer l’interfaçage ERP ou transmettre manuellement vos effectifs. - - )} - - + )} {indicateursEffectifs && ( @@ -483,31 +469,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 000000000..6324fc835 --- /dev/null +++ b/ui/modules/effectifs/EffectifsPage.tsx @@ -0,0 +1,242 @@ +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 { useSetRecoilState } from "recoil"; + +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 { effectifsStateAtom } from "../mon-espace/effectifs/engine/atoms"; +import EffectifsTable from "../mon-espace/effectifs/engine/EffectifsTable"; +import { Input } from "../mon-espace/effectifs/engine/formEngine/components/Input/Input"; +import BandeauTransmission from "../organismes/BandeauTransmission"; + +interface EffectifsPageProps { + organisme: Organisme; + modePublique: boolean; +} +function EffectifsPage(props: EffectifsPageProps) { + const router = useRouter(); + const setCurrentEffectifsState = useSetRecoilState(effectifsStateAtom); + + const [searchValue, setSearchValue] = useState(""); + const [showOnlyErrors, setShowOnlyErrors] = useState(false); + const [anneeScolaire, setAnneeScolaire] = useState("all"); + + const { data: organismesEffectifs, isLoading } = useQuery( + ["organismes", props.organisme._id, "effectifs"], + async () => { + const organismesEffectifs = await _get(`/api/v1/organismes/${props.organisme._id}/effectifs`); + // met à jour l'état de validation de chaque effectif (nécessaire pour le formulaire) + setCurrentEffectifsState( + organismesEffectifs.reduce((acc, { id, validation_errors }) => { + acc.set(id, { validation_errors, requiredSifa: [] }); + return acc; + }, new Map()) + ); + return organismesEffectifs; + } + ); + + 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 ( + + + + + {title} + + + + + Ajouter via fichier Excel + + + + {organismesEffectifs && organismesEffectifs.length === 0 && ( + + )} + + {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 + setAnneeScolaire("all")} active={anneeScolaire === "all"}> + Toutes + + {Object.keys(effectifsByAnneeScolaire).map((anneeScolaire) => { + return ( + setAnneeScolaire(anneeScolaire)} + key={anneeScolaire} + active={anneeScolaire === anneeScolaire} + > + {anneeScolaire} + + ); + })} + + + + )} + + + {Object.entries(effectifsByAnneeScolaire).map(([anneeScolaire, effectifs]) => { + if (anneeScolaire !== "all" && anneeScolaire !== 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 8ac7efba7..d52173134 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-compte/ConfigurationERP.tsx b/ui/modules/mon-compte/ConfigurationERP.tsx deleted file mode 100644 index 6a8559b3b..000000000 --- a/ui/modules/mon-compte/ConfigurationERP.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { ArrowForwardIcon } from "@chakra-ui/icons"; -import { Heading, Button, Text, List, ListItem, VStack, Input, Box, Flex, ListIcon, HStack } from "@chakra-ui/react"; -import React, { useState } from "react"; -import { CopyToClipboard } from "react-copy-to-clipboard"; - -import { _put } from "@/common/httpClient"; -import { Organisme } from "@/common/internal/Organisme"; -import { formatDateDayMonthYear } from "@/common/utils/dateUtils"; -import ErpTutorial from "@/components/ErpTutorial/ErpTutorial"; -import Ribbons from "@/components/Ribbons/Ribbons"; -import Table from "@/components/Table/Table"; -import useToaster from "@/hooks/useToaster"; -import { Checkbox } from "@/theme/components/icons"; - -type ConfigurationERPProps = { - organisme: Organisme; - erp: string | undefined | string[]; - isGenerating: boolean; - onGenerate: () => Promise; -}; - -const DynamicList = ({ apiKey, copied, verified }) => { - const Item = ({ children, active = false }) => { - return ( - - {active && } - {children} - - ); - }; - return ( - - 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 - - - ); -}; - -const ConfigurationERP = ({ erp, onGenerate, isGenerating, organisme }: ConfigurationERPProps) => { - const { toastSuccess, toastError } = useToaster(); - const [copied, setCopied] = useState(false); - const [verified] = useState(!!organisme.api_siret && !!organisme.api_uai); - - return ( - - - Paramétrage de votre ERP - - - Au 15 juin 2023, les données récoltées sur le parcours de l’apprenant ont évolué afin de
- mieux restituer une photographie en temps réel de l’apprentissage en France.
- {!verified && ( - <> - Ainsi, la clé d’échange (API key) actuellement installée sur votre ERP doit être changée.
- - L’opération prend quelques minutes (un tutoriel est disponible ci-dessous) si vous êtes -
habilité à modifier les paramètres de votre ERP. -
- - )} -
- {!verified && ( - - - - Comment mettre à jour votre clé d’échange - - - - - - {organisme.api_key ? ( - <> - - { - setCopied(true); - toastSuccess("Copié!"); - }} - > - - - - ) : ( - <> - Cliquez sur le bouton ci-dessous pour générer votre nouvelle clé d’échange. - - - )} - - {erp && } - - )} - {verified && ( - - - - - Vous avez correctement installé la nouvelle clé d’échange sur votre ERP - - - - - Information récues depuis votre ERP : - - "Siret", - }, - api_uai: { - size: 200, - header: () => "UAI", - }, - api_configuration_date: { - size: 200, - header: () => "Date d'interfaçage", - cell: ({ getValue }) => {formatDateDayMonthYear(getValue())}, - }, - }} - /> - - - - Félicitations ! L’opération est terminée. - - - Votre établissement {organisme.enseigne ?? organisme.raison_sociale} transmet bien les - données au -
- tableau de bord via la nouvelle API installée sur votre ERP. -
- Vous n’avez plus rien à faire. -
-
- - )} - - ); -}; - -export default ConfigurationERP; diff --git a/ui/modules/mon-compte/NavigationCompte.tsx b/ui/modules/mon-compte/NavigationCompte.tsx index d989fe117..c1dbd3e4a 100644 --- a/ui/modules/mon-compte/NavigationCompte.tsx +++ b/ui/modules/mon-compte/NavigationCompte.tsx @@ -13,14 +13,6 @@ const NavigationCompte = () => { name: "Mes informations", href: "/mon-compte", }, - ...(["recette", "local"].includes(process.env.NEXT_PUBLIC_ENV as string) - ? [ - { - name: "Paramétrage ERP", - href: "/mon-compte/erp", - }, - ] - : []), ]; return ( diff --git a/ui/modules/mon-espace/SIFA/SIFAPage.tsx b/ui/modules/mon-espace/SIFA/SIFAPage.tsx index 152df2dac..329641d01 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 deleted file mode 100644 index c4fa136bb..000000000 --- a/ui/modules/mon-espace/effectifs/ChoixERP.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Box, Button, RadioGroup, Radio, Text, VStack, HStack } from "@chakra-ui/react"; -import { useFormik } from "formik"; -import { useRouter } from "next/router"; -import React from "react"; -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"; - -const ChoixERP = ({ organisme, isMine }) => { - const router = useRouter(); - - const { values, handleSubmit, handleChange, isSubmitting } = useFormik({ - initialValues: { - erp: organisme?.erps?.[0] || "", - }, - validationSchema: Yup.object().shape({ - erp: Yup.string().required("Requis"), - }), - onSubmit: async (submittedValues) => { - if (submittedValues.erp === "SCFORM") { - await configureOrganismeERP(organisme._id, { - erps: ["SCFORM"], - setup_step_courante: "COMPLETE", - mode_de_transmission: "API", - }); - router.push("/mon-compte/erp"); - } else { - router.push( - isMine - ? `/effectifs/aide-configuration-erp?erp=${submittedValues.erp}` - : `/organismes/${organisme._id}/effectifs/aide-configuration-erp?erp=${submittedValues.erp}` - ); - } - }, - }); - return ( -
- - - Importer vos effectifs - - - - - Quel ERP ou outil de gestion utilise votre organisme de formation ? - - - - {ERPS.filter((o) => o.state === "ready").map(({ id, name }) => ( - - - {name} - - - ))} - - - - Un outil différent de ceux-là - - (Excel, Salesforce, Hubspot, Sheets ...) - - - - - - {values.erp === "other" ? ( - <> - - - Transmettez facilement vos données grâce à notre service dédié. - - - - - ) : ( - - )} - - - - - - Un ERP (Enterprise Ressource Planning ou PGI pour Progiciel de Gestion Intégré) est une solution - logicielle qui permet d’unifier le système d’information d’une entreprise autour d’une base de données - unique.Pour un organisme de formation, la plupart des ERP proposent des fonctionnalités telles que la - gestion et le suivi des apprenants, la gestion des plannings de formation, la gestion des formateurs, - parfois la facturation, etc. - - - Vous aurez la possibilité de combiner deux moyens d’importation mais vous devez les programmer un à la - fois. - - - - - - ); -}; - -export default ChoixERP; diff --git a/ui/modules/mon-espace/effectifs/ChoixTransmission.tsx b/ui/modules/mon-espace/effectifs/ChoixTransmission.tsx deleted file mode 100644 index e72f39914..000000000 --- a/ui/modules/mon-espace/effectifs/ChoixTransmission.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { Box, Button, Center, Flex, Heading, HStack, Text } from "@chakra-ui/react"; -import { useRouter } from "next/router"; -import React from "react"; - -import { configureOrganismeERP } from "@/common/api/tableauDeBord"; -import { ArrowDropRightLine } from "@/theme/components/icons"; - -type ChoixTransmissionProps = { - organismeId: string; - isMine: boolean; -}; - -const ChoixTransmission = ({ organismeId, isMine = false }: ChoixTransmissionProps) => { - const router = useRouter(); - - return ( - <> - - - Comment aimeriez-vous importer vos effectifs ? - - - - - - - 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 - - - -
- -
-
-
-
- - ); -}; - -export default ChoixTransmission; diff --git a/ui/modules/mon-espace/effectifs/ConfigurationAPI.tsx b/ui/modules/mon-espace/effectifs/ConfigurationAPI.tsx deleted file mode 100644 index eef333604..000000000 --- a/ui/modules/mon-espace/effectifs/ConfigurationAPI.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { Box, Button, Flex, RadioGroup, Radio, Text, VStack, Stack, HStack } from "@chakra-ui/react"; -import { useFormik } from "formik"; -import { useRouter } from "next/router"; -import React from "react"; - -import { configureOrganismeERP } from "@/common/api/tableauDeBord"; -import { ERPS } from "@/common/constants/erps"; -import { DownloadLine } from "@/theme/components/icons/index"; - -type ConfigurationAPIProps = { - organismeId: string; - isMine: boolean; - erpIdSelected: string | null | undefined; -}; - -const ConfigurationAPI = ({ organismeId, isMine, erpIdSelected }: ConfigurationAPIProps) => { - const router = useRouter(); - - const erpSelected = ERPS.find((e) => e.id === erpIdSelected); - const erpName = erpSelected?.name; - - const { values, handleChange } = useFormik({ - initialValues: { doYouUseAnotherService: "" }, - onSubmit: async () => { - // DO NOTHING - }, - }); - - return ( -
- - - Démarrer l’interfaçage avec [{erpName}]. - - - {erpSelected?.helpFilePath && ( - - - {erpSelected.helpFileSize && ( - - PDF – {erpSelected.helpFileSize} - - )} - - )} - - - Temps estimé : 5 minutes - - - Pré-requis : -

- La configuration doit être effectuée par un administrateur sur {erpName}. Votre logiciel {erpName} doit être - à jour. Vous devez avoir renseigné votre UAI dans {erpName}. -

-
- - - Utilisez-vous un autre outil de gestion de vos effectifs au quotidien ? - - - - - - - Oui - - - {values.doYouUseAnotherService === "yes" && ( - - - - Il est possible de transmettre une partie de vos effectifs via votre ERP et d’importer l’autre - partie via l’onglet “Mes effectifs”. Si vous utilisez deux ERP connectés au tableau de bord - (exemple : Yparéo et SC Form), veuillez nous contacter. - - - )} - - - - - Non, {erpName} est mon unique outil de gestion - - - {values.doYouUseAnotherService === "no" && ( - - - Vous pouvez télécharger le pas-à-pas disponible ci-dessus. - - )} - - - - - -
- - ); -}; - -export default ConfigurationAPI; diff --git a/ui/modules/mon-espace/effectifs/EffectifsBanner.tsx b/ui/modules/mon-espace/effectifs/EffectifsBanner.tsx deleted file mode 100644 index aabd311b4..000000000 --- a/ui/modules/mon-espace/effectifs/EffectifsBanner.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Image, VStack, Heading, Text, HStack } from "@chakra-ui/react"; -import React from "react"; - -import { ERPS } from "@/common/constants/erps"; -import { Organisme } from "@/common/internal/Organisme"; -import { prettyPrintDate } from "@/common/utils/dateUtils"; -import Section from "@/components/Section/Section"; - -type EffectifsBannerProps = { - organisme: Partial; - isMine: boolean; -}; - -const EffectifsBanner = ({ organisme, isMine }: EffectifsBannerProps) => { - const erpId = organisme.erps?.[0]; - const mode_de_transmission = organisme.mode_de_transmission; - const erpName = ERPS.find((erp) => erp.id === erpId?.toUpperCase())?.name; - const prefixOrganismeText = isMine ? "Votre" : "Cet"; - // Legacy: pas de mode de transmission connu ni d'ERP configuré, mais une date de transmission. - const legacy = !mode_de_transmission && !erpName && organisme.last_transmission_date; - - return ( -
- - - - {legacy && `${prefixOrganismeText} organisme transmet ses effectifs`} - {erpName && `${prefixOrganismeText} organisme transmet ses effectifs via ${erpName}`} - {mode_de_transmission === "MANUEL" && - `${prefixOrganismeText} organisme transmet ses effectifs manuellement`} - - - {erpName && - `Dernière transmission de données par ${erpName} ${ - organisme.last_transmission_date - ? `le ${prettyPrintDate(organisme.last_transmission_date)}` - : "[en cours d'importation]" - }.`} - {legacy && `Dernière transmission de données le ${prettyPrintDate(organisme.last_transmission_date)}.`} - - - - -
- ); -}; - -export default EffectifsBanner; diff --git a/ui/modules/mon-espace/effectifs/EffectifsBannerERPNotConfigured.tsx b/ui/modules/mon-espace/effectifs/EffectifsBannerERPNotConfigured.tsx deleted file mode 100644 index 22ee36547..000000000 --- a/ui/modules/mon-espace/effectifs/EffectifsBannerERPNotConfigured.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { HStack, Heading, ListItem, Text, UnorderedList, VStack } from "@chakra-ui/react"; - -import Ribbons from "@/components/Ribbons/Ribbons"; -import Section from "@/components/Section/Section"; -import { InfoCircle } from "@/theme/components/icons/InfoCircle"; - -type EffectifsBannerERPNotConfiguredProps = { - isMine: boolean; -}; - -const EffectifsBannerERPNotConfigured = ({ isMine }: EffectifsBannerERPNotConfiguredProps) => { - return ( - -
- - {isMine ? "Aperçu de vos effectifs transmis" : "Aperçu des effectifs transmis"} - - - {isMine - ? "Vous n’avez pas encore transmis vos effectifs" - : "Cet organisme n'a pas encore transmis ses effectifs"} - - {isMine && ( - - - Vous devez effectuer vos mises à jours entre le 1er et le 5 de chaque mois. - - )} -
- -
- - - - Les données exposées en temps réel donnent : - - - Une vision dynamique de l’organisation de l’apprentissage sur les territoires, - Des indications sur les profils de jeunes à accompagner. - - - - Vous n’avez pas encore transmis de données - - -
-
- ); -}; - -export default EffectifsBannerERPNotConfigured; diff --git a/ui/modules/mon-espace/effectifs/EffectifsPage.tsx b/ui/modules/mon-espace/effectifs/EffectifsPage.tsx deleted file mode 100644 index 8c374b8c9..000000000 --- a/ui/modules/mon-espace/effectifs/EffectifsPage.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { Box, Center, Container, Spinner } from "@chakra-ui/react"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import React, { useEffect, useRef } from "react"; -import { useSetRecoilState } from "recoil"; - -import { _get } from "@/common/httpClient"; -import { Organisme } from "@/common/internal/Organisme"; - -import ChoixERP from "./ChoixERP"; -import ChoixTransmission from "./ChoixTransmission"; -import EffectifsBanner from "./EffectifsBanner"; -import EffectifsBannerERPNotConfigured from "./EffectifsBannerERPNotConfigured"; -import { effectifsStateAtom } from "./engine/atoms"; -import Effectifs from "./engine/Effectifs"; - -function useOrganismesEffectifs(organismeId: string | null | undefined) { - const setCurrentEffectifsState = useSetRecoilState(effectifsStateAtom); - - const queryClient = useQueryClient(); - const prevOrganismeId = useRef(null); - - useEffect(() => { - if (prevOrganismeId.current !== organismeId) { - prevOrganismeId.current = organismeId; - // queryClient.resetQueries("organismesEffectifs", { exact: true }); - } - }, [queryClient, organismeId]); - - const { data, isLoading, isFetching } = useQuery( - ["organismesEffectifs", organismeId], - async () => { - const organismesEffectifs = await _get(`/api/v1/organismes/${organismeId}/effectifs`); - - // eslint-disable-next-line no-undef - const newEffectifsState = new Map(); - for (const { id, validation_errors } of organismesEffectifs as any) { - newEffectifsState.set(id, { validation_errors, requiredSifa: [] }); - } - setCurrentEffectifsState(newEffectifsState); - - return organismesEffectifs; - }, - { - enabled: !!organismeId, - } - ); - - return { isLoading: isFetching || isLoading, organismesEffectifs: data || [] }; -} - -type EffectifsPageProps = { - isMine: boolean; - organisme: Organisme | null | undefined; -}; - -const EffectifsPage = ({ isMine, organisme }: EffectifsPageProps) => { - const { isLoading, organismesEffectifs } = useOrganismesEffectifs(organisme?._id); - const { data: duplicates } = useQuery( - [`duplicates-effectifs`, organisme?._id], - () => _get(`/api/v1/organismes/${organisme?._id}/duplicates`), - { - enabled: !!organisme?._id, - } - ); - - if (!organisme) { - return null; - } - - if (isLoading) { - return ( -
- -
- ); - } - - let MainComponent; - if (organisme.last_transmission_date) { - MainComponent = ( - - ); - } else if (!organisme.mode_de_transmission) { - MainComponent = ; - } else if (organisme.mode_de_transmission === "API" && !organisme.erps?.length) { - MainComponent = ; - } else { - MainComponent = ( - - ); - } - - return ( - <> - {organisme.last_transmission_date ? ( - - ) : ( - - )} - - - - {MainComponent} - - - - ); -}; - -export default EffectifsPage; diff --git a/ui/modules/mon-espace/effectifs/engine/AjoutApprenantModal.tsx b/ui/modules/mon-espace/effectifs/engine/AjoutApprenantModal.tsx deleted file mode 100644 index b3df35202..000000000 --- a/ui/modules/mon-espace/effectifs/engine/AjoutApprenantModal.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Text } from "@chakra-ui/react"; -import React, { useCallback } from "react"; -import { useRecoilValue } from "recoil"; - -import { _post } from "@/common/httpClient"; -import PromptModal from "@/components/Modals/PromptModal"; -import { organismeAtom } from "@/hooks/organismeAtoms"; - -// TODO https://github.com/mission-apprentissage/flux-retour-cfas/issues/2387 -// ce composant a été partiellement développé, et n'est pas encore utilisé -const AjoutApprenantModal = (modal) => { - const organisme = useRecoilValue(organismeAtom); - const onCreateEffectifClicked = useCallback(async () => { - try { - await _post("/api/v1/effectif", { - organisme_id: organisme._id, - annee_scolaire: "2020-2021", - source: "televersement", - apprenant: { nom: "Hanry", prenom: "Pablo" }, - formation: { cfd: "26033206" }, - }); - window.location.reload(); // TODO tmp - } catch (e) { - console.error(e); - } - }, [organisme?._id]); - - return ( - <> - { - onCreateEffectifClicked(); - modal.onClose(); - }} - onKo={() => { - modal.onClose(); - }} - bgOverlay="rgba(0, 0, 0, 0.28)" - okText={"Ajouter"} - koText={"Annuler"} - > - FORMULAIRE - + formulaire STATUT courant - - - ); -}; -export default AjoutApprenantModal; diff --git a/ui/modules/mon-espace/effectifs/engine/Effectifs.tsx b/ui/modules/mon-espace/effectifs/engine/Effectifs.tsx deleted file mode 100644 index abbfd4dea..000000000 --- a/ui/modules/mon-espace/effectifs/engine/Effectifs.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import { Box, Flex, Text, HStack, Button, useDisclosure, Heading, VStack, Circle, Switch } from "@chakra-ui/react"; -import groupBy from "lodash.groupby"; -import { useRouter } from "next/router"; -import React, { useState, useMemo } from "react"; -import { useRecoilValue } from "recoil"; - -import { _getBlob } from "@/common/httpClient"; -import Ribbons from "@/components/Ribbons/Ribbons"; -import { organismeAtom } from "@/hooks/organismeAtoms"; -import { DoubleChevrons } from "@/theme/components/icons/DoubleChevrons"; - -import AjoutApprenantModal from "./AjoutApprenantModal"; -import EffectifsTable from "./EffectifsTable"; -import { Input } from "./formEngine/components/Input/Input"; - -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)} - /> - - ); -}; - -const Effectifs = ({ organismesEffectifs, nbDuplicates, isMine }) => { - const router = useRouter(); - const organisme = useRecoilValue(organismeAtom); - const ajoutModal = useDisclosure(); - const [searchValue, setSearchValue] = useState(""); - - const organismesEffectifsGroupedBySco = useMemo( - () => groupBy(organismesEffectifs, "annee_scolaire"), - [organismesEffectifs] - ); - const [anneScolaire, setAnneScolaire] = useState("all"); - const [showOnlyErrors, setShowOnlyErrors] = useState(false); - - if (!organisme) { - return <>; - } - return ( - - - - {isMine ? "Mes effectifs" : "Ses effectifs"} - - - - - {organisme.mode_de_transmission !== "API" && ( - <> - {/* TODO TMP */} - - - )} - - - - - - Service d’import de vos effectifs en version bêta. - - - Nous listons actuellement toutes les informations qui peuvent empêcher l{"'"}import de fichier afin de - permettre par la suite une meilleure prise en charge de tout type de fichier. - - - - {nbDuplicates > 0 && ( - - - - Nous avons détécté {nbDuplicates} duplicat{nbDuplicates > 1 ? "s" : ""} pour l{"'"}année scolaire en - cours. - - - - - - )} - - {organisme.mode_de_transmission === "MANUEL" && organismesEffectifs.length === 0 && ( - - - {isMine - ? `Vous n'avez pas encore ajouté d'effectifs` - : `Aucun effectif n'a été transmis pour cet organisme.`} - - - Vous pouvez ajouter des effectifs à l’aide du bouton "Ajouter" ci-dessus. -
-
-
- )} - {organisme.mode_de_transmission === "API" && organismesEffectifs.length === 0 && ( - - - Aucun effectif n’a été transmis depuis votre ERP. - - - Merci de revenir ultérieurement. Si vous venez de configurer votre ERP, la transmission de vos effectifs - sera active demain matin. -
-
-
- )} - {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(organismesEffectifsGroupedBySco).map((anneSco) => { - return ( - setAnneScolaire(anneSco)} key={anneSco} active={anneScolaire === anneSco}> - {anneSco} - - ); - })} - - - - )} - - - {Object.entries(organismesEffectifsGroupedBySco).map(([anneSco, orgaE]) => { - if (anneScolaire !== "all" && anneScolaire !== anneSco) return null; - const orgaEffectifs = showOnlyErrors ? orgaE.filter((ef) => ef.validation_errors.length) : orgaE; - const effectifsByCfd: { [cfd: string]: any[] } = groupBy(orgaEffectifs, "formation.cfd"); - const borderStyle = { borderColor: "dgalt", borderWidth: 1 }; //anneScolaire === "all" ? { borderColor: "bluefrance", borderWidth: 1 } : {}; - return ( - - - {anneSco} {!searchValue ? `- ${orgaEffectifs.length} apprenant(es) total` : ""} - - - {Object.entries(effectifsByCfd).map(([cfd, effectifs], i) => { - const { formation } = effectifs[0]; - return ( - - ); - })} - - - ); - })} - -
- ); -}; - -export default Effectifs; diff --git a/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx b/ui/modules/mon-espace/effectifs/engine/EffectifsTable.tsx index 6f14d9634..fe76d7b9d 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/CheckCfaTransmission/CheckCfaTransmissionContent.tsx b/ui/modules/organisme-formation/CheckCfaTransmission/CheckCfaTransmissionContent.tsx deleted file mode 100644 index 9000845e1..000000000 --- a/ui/modules/organisme-formation/CheckCfaTransmission/CheckCfaTransmissionContent.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Box, Link } from "@chakra-ui/react"; -import React, { useState } from "react"; - -import { CfaTransmissionFound, CfaTransmissionNotFound, CfaTransmissionSection } from "./FormSections"; - -export const ASKURL_FORM_STATE = { - askOrganisme: "askOrganisme", - organismeFound: "organismeFound", - organismeNotFound: "organismeNotFound", -}; - -const CheckCfaTransmissionContent = () => { - const [formState, setFormState] = useState(ASKURL_FORM_STATE.askOrganisme); - return ( - - {formState === ASKURL_FORM_STATE.askOrganisme && ( - setFormState(ASKURL_FORM_STATE.organismeFound)} - setOrganismeNotFound={() => setFormState(ASKURL_FORM_STATE.organismeNotFound)} - /> - )} - {formState !== ASKURL_FORM_STATE.askOrganisme && ( - setFormState(ASKURL_FORM_STATE.askOrganisme)}> - - Retour - - )} - {formState === ASKURL_FORM_STATE.organismeFound && } - {formState === ASKURL_FORM_STATE.organismeNotFound && } - - ); -}; - -export default CheckCfaTransmissionContent; diff --git a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionFound.tsx b/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionFound.tsx deleted file mode 100644 index 961a2ae9d..000000000 --- a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionFound.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Box, Flex, HStack, Text } from "@chakra-ui/react"; - -import Link from "@/components/Links/Link"; - -const CfaTransmissionFound = () => { - return ( - - - - - Votre organisme transmet bien des données
au tableau de bord de l’apprentissage. -
- - Utiliser votre URL unique disponible dans votre ERP pour consulter votre page - - - En savoir plus - - - -
- ); -}; - -export default CfaTransmissionFound; diff --git a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionNotFound.tsx b/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionNotFound.tsx deleted file mode 100644 index 7d05c7e09..000000000 --- a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionNotFound.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Box, Button, HStack, Text } from "@chakra-ui/react"; -import NavLink from "next/link"; - -const CfaTransmissionNotFound = () => { - return ( - - - - - Votre organisme ne transmet pas de données
- au tableau de bord de l’apprentissage. -
- -
-
- ); -}; - -export default CfaTransmissionNotFound; diff --git a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionSection.tsx b/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionSection.tsx deleted file mode 100644 index d90305f77..000000000 --- a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/CfaTransmissionSection.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Box, Button, HStack, Input, Text } from "@chakra-ui/react"; -import { useState } from "react"; - -import { validateSiret } from "@/common/domain/siret"; -import { validateUai } from "@/common/domain/uai"; -import { _post } from "@/common/httpClient"; -import { queryClient } from "@/common/queryClient"; - -const CfaTransmissionSection = ({ - setOrganismeFound, - setOrganismeNotFound, -}: { - setOrganismeFound: () => void; - setOrganismeNotFound: () => void; -}) => { - const [searchTerm, setSearchTerm] = useState(""); - const [isInputValid, setIsInputValid] = useState(); - - const submit = async () => { - // check that input is a valid SIRET or valid UAI - const shouldFetch = validateUai(searchTerm) || validateSiret(searchTerm); - - setIsInputValid(shouldFetch); - if (!shouldFetch) { - return; - } - - const data = await queryClient.fetchQuery("search-cfas" as any, () => - _post("/api/v1/organismes/search", { searchTerm }) - ); - - if (data?.[0]) { - setOrganismeFound(); - } else { - setOrganismeNotFound(); - } - }; - - return ( -
- Rechercher l'organisme par UAI ou par SIRET : - - Format valide d’un UAI : 7 chiffres et 1 lettre, et d’un SIRET : 14 chiffres - - setSearchTerm(e.target.value)} - /> -
- - {isInputValid === false && ( - - - Le format du SIRET ou de l'UAI n'est pas valide - - )} -
- ); -}; - -export default CfaTransmissionSection; diff --git a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/index.ts b/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/index.ts deleted file mode 100644 index 508395840..000000000 --- a/ui/modules/organisme-formation/CheckCfaTransmission/FormSections/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as CfaTransmissionSection } from "./CfaTransmissionSection"; -export { default as CfaTransmissionFound } from "./CfaTransmissionFound"; -export { default as CfaTransmissionNotFound } from "./CfaTransmissionNotFound"; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpForm.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpForm.tsx deleted file mode 100644 index 24f415a95..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpForm.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { FormControl, FormErrorMessage, FormLabel, Select, Stack } from "@chakra-ui/react"; -import { Field, Form, Formik } from "formik"; -import React from "react"; -import * as Yup from "yup"; - -import { ERP_STATE, ERPS_FORM_CASES } from "@/common/constants/erps"; -import { UAI_REGEX } from "@/common/domain/uai"; - -import { - DemandeBranchementErpFormErpComingSection, - DemandeBranchementErpFormErpOnGoingSection, - DemandeBranchementErpFormErpReadySection, - DemandeBranchementErpFormNoErpSection, - DemandeBranchementErpFormOtherErpSection, -} from "./FormSections"; - -const formInitialValues = { - erpIndex: 0, - nom_organisme: "", - uai_organisme: "", - nb_apprentis: "", - email_demandeur: "", - is_ready_co_construction: false, - autre_erp_nom: "", -}; - -const ErpSelectionList: any = [{ name: "Sélectionnez une option", state: null }].concat(ERPS_FORM_CASES as any); - -const DemandeBranchementErpForm = ({ onSubmit }: { onSubmit: (values: any) => void }) => { - return ( - - {({ isSubmitting, values }) => ( -
- - - {({ field, meta }) => ( - - ERP ou logiciel de gestion utilisé - - {meta.error} - - )} - - - - {/* Cas ERP Ready */} - {ErpSelectionList[values.erpIndex].state === ERP_STATE.ready && ( - - )} - - {/* Cas ERP OnGoing */} - {ErpSelectionList[values.erpIndex].state === ERP_STATE.ongoing && ( - - )} - - {/* Cas ERP Coming */} - {ErpSelectionList[values.erpIndex].state === ERP_STATE.coming && ( - - )} - - {/* Cas Other ERP */} - {ErpSelectionList[values.erpIndex].state === ERP_STATE.otherErp && ( - - )} - - {/* Cas No ERP */} - {ErpSelectionList[values.erpIndex].state === ERP_STATE.noErp && ( - - )} - - )} -
- ); -}; - -export default DemandeBranchementErpForm; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock.tsx deleted file mode 100644 index 09d028f21..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock.tsx +++ /dev/null @@ -1,82 +0,0 @@ -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 DemandeBranchementErpForm from "./DemandeBranchementErpForm"; -import useSubmitDemandeBranchementErp, { SUBMIT_STATE } from "./useSubmitDemandeBranchementErp"; - -const Message = ({ iconClassName, title, message }: { iconClassName: string; title: string; message: string }) => { - return ( - <> - - - {title} - - - {message} - - - - Retourner à la page d'accueil - - - ); -}; - -const DemandeBranchementErpFormBlock = () => { - const { submitState, erpState, submitDemandeBranchementErp } = useSubmitDemandeBranchementErp(); - if (submitState === SUBMIT_STATE.success) { - if (erpState === ERP_STATE.ongoing) { - return ( - - ); - } - if (erpState === ERP_STATE.coming || erpState === ERP_STATE.otherErp) { - return ( - - ); - } - if (erpState === ERP_STATE.noErp) { - return ( - - ); - } - } - - if (submitState === SUBMIT_STATE.fail) { - if (erpState === ERP_STATE.ongoing) { - return ( - - ); - } - return ( - - ); - } - - return ; -}; - -export default DemandeBranchementErpFormBlock; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpComingSection.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpComingSection.tsx deleted file mode 100644 index 74bf910ac..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpComingSection.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Box, Button, FormControl, FormErrorMessage, FormLabel, Input, Stack, Text } from "@chakra-ui/react"; -import { Field } from "formik"; - -import { PRODUCT_NAME } from "@/common/constants/product"; - -const DemandeBranchementErpFormErpComingSection = ({ isSubmitting }: { isSubmitting: boolean }) => { - return ( - <> - - - - L'interfaçage du {PRODUCT_NAME} avec cet ERP n’a pas encore démarré. -  Nous vous invitons à lui faire part de votre besoin de transmettre vos données au {PRODUCT_NAME}. - - - - - Merci de nous communiquer les informations sur votre organisme : - - - - {({ field, meta }) => ( - - Nom de votre organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - UAI formateur de l'organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Nombre d’apprentis sur la dernière année : - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Email de la personne faisant la demande - - {meta.error} - - )} - - - - - ); -}; - -export default DemandeBranchementErpFormErpComingSection; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpOnGoingSection.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpOnGoingSection.tsx deleted file mode 100644 index db89e6420..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpOnGoingSection.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Box, Button, FormControl, FormErrorMessage, FormLabel, Input, Stack, Text } from "@chakra-ui/react"; -import { Field } from "formik"; - -import { PRODUCT_NAME } from "@/common/constants/product"; - -const DemandeBranchementErpFormErpOnGoingSection = ({ isSubmitting }: { isSubmitting: boolean }) => { - return ( - <> - - - - - L'interfaçage du {PRODUCT_NAME} avec cet ERP a démarré mais les travaux ne sont pas achevés. - -  Nous vous invitons à lui faire part de votre besoin de transmettre vos données au {PRODUCT_NAME} afin - d'accélérer leur livraison. - - - - - Renseignez vos coordonnées pour être informé des évolutions : - - - - {({ field, meta }) => ( - - Nom de votre organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - UAI formateur de l'organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Email de la personne faisant la demande - - {meta.error} - - )} - - - - - ); -}; - -export default DemandeBranchementErpFormErpOnGoingSection; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpReadySection.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpReadySection.tsx deleted file mode 100644 index 4dcb034c9..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormErpReadySection.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Box, Button, HStack, Stack, Text } from "@chakra-ui/react"; -import NavLink from "next/link"; - -import { PRODUCT_NAME } from "@/common/constants/product"; - -const DemandeBranchementErpFormErpReadySection = ({ helpFilePath }: { helpFilePath: string }) => { - return ( - <> - - - - Le {PRODUCT_NAME} est interfacé avec cet ERP. Vous pouvez l'autoriser à transmettre - vos données en 2 clics via une fonctionnalité disponible dans l'interface de votre logiciel de gestion. - - - - - - - - - Retourner à la page d'accueil - - - ); -}; - -export default DemandeBranchementErpFormErpReadySection; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormNoErpSection.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormNoErpSection.tsx deleted file mode 100644 index 2b30f7e1d..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormNoErpSection.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Box, Button, FormControl, FormErrorMessage, FormLabel, HStack, Input, Stack, Text } from "@chakra-ui/react"; -import { Field } from "formik"; - -const DemandeBranchementErpFormNoErpSection = ({ isSubmitting }: { isSubmitting: boolean }) => { - return ( - <> - - - - - Nous travaillons actuellement à une solution pour vous permettre de transmettre vos effectifs dès la - rentrée prochaine. - - - - - - Pour être informé de l'ouverture de ce nouveau service, veuillez remplir le formulaire ci-dessous : - - - - {({ field, meta }) => ( - - Nom de votre organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - UAI formateur de l'organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Nombre d’apprentis sur la dernière année : - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Email de la personne faisant la demande - - {meta.error} - - )} - - - - - Je souhaite participer à la construction de ce nouveau service ! - - - - - - ); -}; -export default DemandeBranchementErpFormNoErpSection; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormOtherErpSection.tsx b/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormOtherErpSection.tsx deleted file mode 100644 index c59a4e062..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/DemandeBranchementErpFormOtherErpSection.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { Box, Button, FormControl, FormErrorMessage, FormLabel, Input, Stack, Text } from "@chakra-ui/react"; -import { Field } from "formik"; - -import { PRODUCT_NAME } from "@/common/constants/product"; - -const DemandeBranchementErpFormOtherErpSection = ({ isSubmitting }: { isSubmitting: boolean }) => { - return ( - <> - - - - L'interfaçage du {PRODUCT_NAME} avec l'ensemble des ERP est en cours. -  Nous sommes en contact avec l'ensemble des ERP afin de permettre à tous les organismes de - formation de transmettre leur données et d'accéder au {PRODUCT_NAME}. Pour encourager la mise en oeuvre - de ces travaux nous vous invitons à faire part de votre besoin à votre éditeur de logiciel ERP. - - - - - Merci de nous communiquer les informations sur votre organisme pour nous aider à prioriser nos travaux : - - - - {({ field, meta }) => ( - - Nom de votre organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Quel est votre ERP ? - - {meta.error} - - )} - - - {({ field, meta }) => ( - - UAI formateur de l'organisme - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Nombre d’apprentis sur la dernière année : - - {meta.error} - - )} - - - {({ field, meta }) => ( - - Email de la personne faisant la demande - - {meta.error} - - )} - - - - - ); -}; - -export default DemandeBranchementErpFormOtherErpSection; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/index.ts b/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/index.ts deleted file mode 100644 index df94f8434..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/FormSections/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as DemandeBranchementErpFormErpComingSection } from "./DemandeBranchementErpFormErpComingSection"; -export { default as DemandeBranchementErpFormErpReadySection } from "./DemandeBranchementErpFormErpReadySection"; -export { default as DemandeBranchementErpFormErpOnGoingSection } from "./DemandeBranchementErpFormErpOnGoingSection"; -export { default as DemandeBranchementErpFormOtherErpSection } from "./DemandeBranchementErpFormOtherErpSection"; -export { default as DemandeBranchementErpFormNoErpSection } from "./DemandeBranchementErpFormNoErpSection"; diff --git a/ui/modules/organisme-formation/DemandeBranchementErp/useSubmitDemandeBranchementErp.ts b/ui/modules/organisme-formation/DemandeBranchementErp/useSubmitDemandeBranchementErp.ts deleted file mode 100644 index b41f07cc0..000000000 --- a/ui/modules/organisme-formation/DemandeBranchementErp/useSubmitDemandeBranchementErp.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useState } from "react"; - -import { ERPS_FORM_CASES } from "@/common/constants/erps"; -import { _post } from "@/common/httpClient"; - -export const SUBMIT_STATE = { - waiting: "waiting", - success: "success", - fail: "fail", -}; - -const useSubmitDemandeBranchementErp = () => { - const [submitState, setSubmitState] = useState(SUBMIT_STATE.waiting); - const [erpState, setErpState] = useState(ERPS_FORM_CASES[0].state); - - const submitDemandeBranchementErp = async (formData) => { - try { - await _post("/api/demande-branchement-erp", { - erp: formData.autre_erp_nom !== "" ? formData.autre_erp_nom : ERPS_FORM_CASES[formData.erpIndex - 1].name, - nom_organisme: formData.nom_organisme, - uai_organisme: formData.uai_organisme, - email_demandeur: formData.email_demandeur, - nb_apprentis: `${formData.nb_apprentis}`, - is_ready_co_construction: formData.is_ready_co_construction ?? false, - }); - setErpState(ERPS_FORM_CASES[formData.erpIndex - 1].state); - setSubmitState(SUBMIT_STATE.success); - } catch (err) { - setSubmitState(SUBMIT_STATE.fail); - } - }; - - return { submitState, erpState, submitDemandeBranchementErp }; -}; - -export default useSubmitDemandeBranchementErp; diff --git a/ui/modules/organisme-formation/OrganismeFormationPagesMenu.tsx b/ui/modules/organisme-formation/OrganismeFormationPagesMenu.tsx deleted file mode 100644 index 0140dc2bb..000000000 --- a/ui/modules/organisme-formation/OrganismeFormationPagesMenu.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Flex, Link, Text } from "@chakra-ui/react"; -import { useRouter } from "next/router"; -import React, { ReactNode } from "react"; - -const OrganismeFormationPagesMenu = (props) => { - return ( - - - - ); -}; - -const NavItem = ({ children, to = "/", ...rest }: { children: ReactNode; to: string }) => { - const router = useRouter(); - const isActive = router.pathname === to; - - return ( - - - {children} - - - ); -}; - -const NavLinks = () => { - return ( - - Comment transmettre les données de votre organisme ? -
- - Comment consulter et vérifier les données que vous transmettez ? - -
- Une question ? Besoin d’aide ? Consulter la page d’aide -
- ); -}; - -const NavBarContainer = ({ children }: { children: ReactNode }) => { - return ( - - {children} - - ); -}; - -export default OrganismeFormationPagesMenu; diff --git a/ui/modules/organisme-formation/aide/Question.tsx b/ui/modules/organisme-formation/aide/Question.tsx deleted file mode 100644 index aff119011..000000000 --- a/ui/modules/organisme-formation/aide/Question.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Box, Flex, Text } from "@chakra-ui/react"; -import React, { useState } from "react"; - -const Question = ({ question, answer }: { question: string; answer: any }) => { - const [showAnswer, setShowAnswer] = useState(false); - - return ( -
- setShowAnswer(!showAnswer)}> - - - {question} - - - {showAnswer && ( - - {answer} - - )} -
- ); -}; - -export default Question; diff --git a/ui/modules/organisme-formation/aide/questions.tsx b/ui/modules/organisme-formation/aide/questions.tsx deleted file mode 100644 index 64a41b4d7..000000000 --- a/ui/modules/organisme-formation/aide/questions.tsx +++ /dev/null @@ -1,244 +0,0 @@ -import { Box, Img, Stack, Text } from "@chakra-ui/react"; - -import Link from "@/components/Links/Link"; - -export const questions = [ - { - question: "Est-ce que les données de votre organisme s'affichent sur le tableau de bord ?", - answer: ( - - Les données de votre organisme s'affichent si vous avez autorisé votre ERP (ou logiciel de gestion) à - transmettre vos données au tableau de bord de l'apprentissage. Ceci concerne les clients de SC Form, Ymag, - Gestibase ou FCA Manager. - - ), - }, - { - question: "Comment accéder votre page organisme de formation sur le tableau de bord ?", - answer: ( - - - Si vous transmettez des données au tableau de bord de l'apprentissage, vous pouvez les consulter et les - vérifier directement sur le tableau de bord de l'apprentissage. Le lien vous permettant d'accéder à - votre page organisme de formation, est disponible directement dans votre ERP. - - - Ce lien ne doit pas être partagé en dehors des personnes habilitées de votre organisme de formation, car vous - pourrez y consulter des données personnelles. - - Si vous utilisez Ymag : - - Si vous utilisez Gesti : - - En attente des visuels pour FCA Manager et SC Form. - - ), - }, - { - question: "Qui peut consulter les données de votre organisme ?", - answer: ( - - - Les personnes autorisées à consulter les données de votre organisme dépendent du service public de - l'emploi (Conseil régional, Académie et DREETS), les organisations professionnelles (OPCO) et le Carif - Oref de votre région. Si vous êtes membre d'un réseau, la tête de votre réseau peut également consulter - votre page. - - L'analyse de l'impact de la protection des données (AIPD) est disponible à la demande. - - ), - }, - { - question: "Pourquoi transmettre les données de votre organisme au tableau de bord ?", - answer: ( - - - Différentes institutions (Conseil régional, DREETS, Opco, Carif Oref, Académie, DGEFP) consultent le tableau - de bord de l'apprentissage quotidiennement pour suivre l'évolution des effectifs.  - - - Ces données les éclairent notamment pour attribuer des subventions, pour mettre en place des plans - d'actions d'accompagnement des jeunes sans contrat ou pour définir les politiques publiques - d'aide à l'apprentissage. - - - ), - }, - { - question: "Comment consulter ces données sur le tableau de bord ?", - answer: ( - - - Vous pouvez y accéder via une URL unique pour votre organisme de formation.  - - Cette URL est privée, ne la partagez qu'avec les personnes gestionnaires de votre organisme de - formation. - - - - - Vous la trouverez directement dans votre ERP (ou logiciel de gestion). - - Si toutefois, l'URL n'est pas encore intégrée dans votre interface de gestion, vous pouvez en faire - la demande en  - - contactant l'équipe du tableau de bord - - . - - - ), - }, - { - question: "Comment transmettre les données de mon organisme au tableau de bord de l'apprentissage ?", - answer: ( - - - Pour transmettre les données de votre ERP vers le tableau de bod, vous devez en effectuer le paramétrage. Ce - paramétrage à faire une seule fois est estimé à 10 minutes. - - - Pour ce faire,  - - sélectionnez l'ERP que vous utilisez - - , téléchargez le pas à pas correspondant et suivez les étapes une à une. - - - ), - }, - { - question: - "Qu'est-ce qu'un UAI ? Comment retrouver l'UAI de votre organisme ? Vous avez plusieurs UAI, lequel devez-vous renseigner ?", - answer: ( - - - L'UAI (unité administrative immatriculée) est le code d'immatriculation qu'utilise - l'Éducation Nationale pour enregistrer votre organisme dans le répertoire national des établissement - (RNE). Elle est composée de sept chiffres et une lettre. - - - Si vous êtes une Unité de Formation en Apprentissage (UFA) ou un organisme formateur, il se peut que vous ayez - deux UAI, celle de votre organisme gestionnaire (ou responsable) et la votre. Pour que les effectifs soient - affichés au niveau formateur, renseignez votre UAI plutôt que celle de votre organisme gestionnaire (ou - responsable). - - - ), - }, - { - question: "Qu'est-ce qu'un ERP (ou logiciel de gestion) ?", - answer: ( - - - ERP signifie Enterprise Ressource Planning, en français on utilise le terme Progiciel de Gestion Intégré. Il - s'agit d'un logiciel de gestion permettant de suivre au quotidien les aspects administratifs et les - informations d'une entreprise. - - - La plupart des organismes de formation utilisent un ERP pour suivre leur apprenants et les contrats des - apprentis. - - - ), - }, - { - question: "Je n'ai pas d'ERP (ou logiciel de gestion), comment puis-je transmettre ?", - answer: ( - - - A ce jour, si vous utilisez Ypaéro, Gesti, SC Form ou FCA Manager, vous pouvez transmettre vos données au - tableau de bord (cf pas à pas) Si vous utilisez un autre ERP, n'hésitez pas à les contacter pour les - inviter à faire les développements nécessaires. - - - Si vous n'utilisez pas d'ERP, vous ne pouvez pas transmettre vos données au tableau de bord. - Cependant, l'équipe projet travaille à une évolution qui vous permettra de communiquer vos données via - des feuilles de calcul. Si vous souhaitez être informé lorsque cette fonctionnalité sera possible,  - - - transmettez-nous vos coordonnées - - - . - - - ), - }, - { - question: "Quelles sont les données affichées ?", - answer: ( - - - Les données affichées sont celles transmises par votre établissement via votre ERP. Ces données ne sont pas - transformées, elles sont juste agrégées afin d'identifier le nombre d'apprentis par niveau et code - diplôme, les inscrits sans contrats, les rupturants et les abandons. - - Pour connaître la définition des différents statuts affichés, dépliez la question ci-dessous. - - ), - }, - { - question: "Comment le tableau de bord définit un “inscrit sans contrat” ? et un apprenant en situation d'abandon ?", - answer: ( - - Les apprentis sont les apprenants avec un contrat d'apprentissage, les inscrits sans contrat sont les - apprenants qui n'ont pas encore de contrat d'apprentissage, les rupturants sont les apprentis qui - n'ont plus de contrat d'apprentissage, les abandons correspondent à tous les apprenants ayant mis fin - à leur formation avant la date de fin prévue. - - ), - }, - { - question: "Certaines données affichées ne sont pas bonnes. Comment puis-je les corriger ?", - answer: ( - - - Cela provient d'un mauvais paramétrage dans votre ERP, vous pouvez  - - consulter les pas à pas - -  pour le modifier. - - Avez vous bien paramétré votre ERP ? Avez-vous renseigné toutes les informations dans votre ERP ? - Si cela ne résoud pas le problème d'affichage, contactez-nous. - - ), - }, - { - question: "La transmission des données au tableau de bord est-elle obligatoire ?", - answer: ( - - - En tant qu'opérateur d'une mission de service public, c'est une obligation légale au sens du - premier article de la loi pour une République numérique. - - - Le tableau de bord de l'apprentissage va devenir l'outil de référence des pouvoirs publics. À ce - titre, certaines régions utilisent déjà cet outil pour attribuer les aides aux centres de formation. Il est - porté par la DGEFP comme le futur outil de pilotage des politiques publiques de l'apprentissage. En ne - transmettant pas vos données, vous ne donnerez donc aucune visibilité sur votre réalité et sur vos besoins en - tant qu'organisme de formation. - - - ), - }, - { - question: "La transmission des données au tableau de bord remplace-t'elle l'enquête SIFA ?", - answer: ( - - - À ce jour, transmettre vos données au tableau de bord ne vous dispense pas de remplir l'enquête SIFA. - - - Une fois les objectifs d'acquisition et de qualité des données seront atteints, de nouveaux usages des - données collectées pourront être étudiés et si rien n'est engagé, la DEPP qui administre SIFA, - s'intéresse à nos travaux. - - - ), - }, -]; diff --git a/ui/modules/organismes/BandeauTransmission.tsx b/ui/modules/organismes/BandeauTransmission.tsx new file mode 100644 index 000000000..da6fcb51e --- /dev/null +++ b/ui/modules/organismes/BandeauTransmission.tsx @@ -0,0 +1,95 @@ +import { SystemProps, Text } from "@chakra-ui/react"; +import { differenceInDays } from "date-fns"; +import { ERPS_BY_ID } from "shared"; + +import { CONTACT_ADDRESS } from "@/common/constants/product"; +import { _get } from "@/common/httpClient"; +import { Organisme } from "@/common/internal/Organisme"; +import { formatDateDayMonthYear } from "@/common/utils/dateUtils"; +import Link from "@/components/Links/Link"; +import Ribbons from "@/components/Ribbons/Ribbons"; + +interface BandeauTransmissionProps { + organisme: Organisme; + modePublique?: boolean; + modeIndicateurs?: boolean; +} + +/** + * Affiche un bandeau quand les effectifs ne sont pas encore transmis selon l'état de configuration ERP. + */ +function BandeauTransmission({ + organisme, + modePublique, + modeIndicateurs, + ...props +}: BandeauTransmissionProps & SystemProps) { + return ( + + {getContenuBandeauTransmission({ organisme, modePublique, modeIndicateurs })} + + ); +} + +export default BandeauTransmission; + +function getContenuBandeauTransmission({ + organisme, + modePublique, + modeIndicateurs, +}: BandeauTransmissionProps): JSX.Element { + const erpName = organisme.erps?.map((erpId) => ERPS_BY_ID[erpId]?.name).join(", "); // généralement 1 seul ERP + + if (modePublique) { + return <>Cet établissement ne transmet pas encore ses effectifs au tableau de bord.; + } + + if (!organisme.mode_de_transmission) { + return ( + <> + Les {modeIndicateurs ? "indicateurs sont nuls" : "effectifs sont vides"} car votre établissement ne transmet pas + encore ses effectifs. Veuillez{" "} + + paramétrer + {" "} + votre moyen de transmission. + + ); + } + + if (organisme.mode_de_transmission === "API") { + if (differenceInDays(new Date(organisme.mode_de_transmission_configuration_date as string), new Date()) < 7) { + return ( + <> + Votre outil de gestion est {erpName}. Les {modeIndicateurs ? "indicateurs sont nuls" : "effectifs sont vides"}{" "} + car l’importation de vos effectifs est en cours. Le tableau de bord recevra vos effectifs entre 24 et 48 + heures. + + ); + } + + return ( + <> + Votre outil de gestion est {erpName}. Le tableau de bord ne reçoit pas vos effectifs. Veuillez vérifier à + nouveau le paramétrage de votre ERP fait le{" "} + {formatDateDayMonthYear(organisme.mode_de_transmission_configuration_date as string)}. En cas de difficultés,{" "} + + contactez-nous + {" "} + pour obtenir de l’aide. + + ); + } + + return ( + <> + Les {modeIndicateurs ? "indicateurs sont nuls" : "effectifs sont vides"} car votre établissement ne transmet pas + encore ses effectifs. + + ); +} diff --git a/ui/package.json b/ui/package.json index 47890e22c..d4eab9072 100644 --- a/ui/package.json +++ b/ui/package.json @@ -56,6 +56,7 @@ "recoil": "0.7.7", "remixicon": "2.5.0", "shared": "workspace:*", + "usehooks-ts": "^2.9.1", "xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz", "yup": "0.32.11", "yup-password": "0.2.2", diff --git a/ui/pages/connexion-api.tsx b/ui/pages/connexion-api.tsx index 9f13a3468..673ca2dfd 100644 --- a/ui/pages/connexion-api.tsx +++ b/ui/pages/connexion-api.tsx @@ -4,6 +4,7 @@ import { useQuery } from "@tanstack/react-query"; import Head from "next/head"; import { useRouter } from "next/router"; import React, { useEffect } from "react"; +import { useLocalStorage } from "usehooks-ts"; import { z } from "zod"; import { _post } from "@/common/httpClient"; @@ -13,7 +14,6 @@ import Page from "@/components/Page/Page"; import Ribbons from "@/components/Ribbons/Ribbons"; import { useOrganisme } from "@/hooks/organismes"; import useAuth from "@/hooks/useAuth"; -import useLocalStorage from "@/hooks/userLocalStorage"; import { useEffectifsOrganismeOrganisation } from "@/modules/mon-espace/effectifs/useEffectifsOrganisme"; export const getServerSideProps = async (context) => ({ props: { ...(await getAuthServerSideProps(context)) } }); @@ -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?erpV3=${router.query.erp}`); } }, [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(); @@ -99,7 +99,7 @@ const ConnexionAPIVerifyUser = ({ organisme }) => { const ConnexionAPIUserNotConnected = () => { const { auth } = useAuth(); const router = useRouter(); - const [originConnexionUrl, setOriginConnexionUrl] = useLocalStorage("originConnexionUrl"); + const [originConnexionUrl, setOriginConnexionUrl] = useLocalStorage("originConnexionUrl", ""); useEffect(() => { if (originConnexionUrl !== window.location.href) { diff --git a/ui/pages/effectifs.tsx b/ui/pages/effectifs.tsx index 9156778f4..3ae6bc8fc 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/effectifs/aide-configuration-erp.tsx b/ui/pages/effectifs/aide-configuration-erp.tsx deleted file mode 100644 index 95060f5b7..000000000 --- a/ui/pages/effectifs/aide-configuration-erp.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Box, Container } from "@chakra-ui/react"; -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 ConfigurationAPI from "@/modules/mon-espace/effectifs/ConfigurationAPI"; -import { useEffectifsOrganismeOrganisation } from "@/modules/mon-espace/effectifs/useEffectifsOrganisme"; - -export const getServerSideProps = async (context) => ({ props: { ...(await getAuthServerSideProps(context)) } }); - -const PageAideConfigurationErpMonOrganisme = () => { - const router = useRouter(); - const erpIdSelected = router.query.erp as string; - - const { organisme } = useEffectifsOrganismeOrganisation(); - return ( - - - Configuration ERP - - - - {organisme && } - - - - ); -}; - -export default withAuth(PageAideConfigurationErpMonOrganisme); diff --git a/ui/pages/mon-compte/erp.tsx b/ui/pages/mon-compte/erp.tsx deleted file mode 100644 index abccce93d..000000000 --- a/ui/pages/mon-compte/erp.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Box, Flex } from "@chakra-ui/react"; -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 { useOrganisme } from "@/hooks/organismes"; -import ConfigurationERP from "@/modules/mon-compte/ConfigurationERP"; -import NavigationCompte from "@/modules/mon-compte/NavigationCompte"; -import { useEffectifsOrganismeOrganisation } from "@/modules/mon-espace/effectifs/useEffectifsOrganisme"; - -export const getServerSideProps = async (context) => ({ props: { ...(await getAuthServerSideProps(context)) } }); - -const ConfigurationErpPage = () => { - const router = useRouter(); - const { organisme } = useEffectifsOrganismeOrganisation(); - const { organisme: currentOrganisme, generateApiKey, isGeneratingApiKey } = useOrganisme(organisme?._id); - - return ( - - - Paramétrage ERP - - - - - {currentOrganisme && ( - generateApiKey()} - /> - )} - - - - ); -}; - -export default withAuth(ConfigurationErpPage); diff --git a/ui/pages/organisme-formation/aide.tsx b/ui/pages/organisme-formation/aide.tsx deleted file mode 100644 index dee39d3a0..000000000 --- a/ui/pages/organisme-formation/aide.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Box, Button, Container, Divider, Heading, HStack, Stack, Text } from "@chakra-ui/react"; -import Head from "next/head"; -import React from "react"; - -import { CONTACT_ADDRESS } from "@/common/constants/product"; -import Page from "@/components/Page/Page"; -import Section from "@/components/Section/Section"; -import Question from "@/modules/organisme-formation/aide/Question"; -import { questions } from "@/modules/organisme-formation/aide/questions"; -import OrganismeFormationPagesMenu from "@/modules/organisme-formation/OrganismeFormationPagesMenu"; - -export default function Aide() { - const title = "Page d'aide"; - return ( - - - {title} - -
- - {title} - - - - - - - - - - Page d'aide - - - {questions.map(({ question, answer }, index) => { - return ; - })} - - - Vous ne trouvez pas la réponse à votre question ou vous avez besoin de contacter notre équipe ? - - - - - - - -
-
- ); -} diff --git a/ui/pages/organisme-formation/consulter.tsx b/ui/pages/organisme-formation/consulter.tsx deleted file mode 100644 index 161200f22..000000000 --- a/ui/pages/organisme-formation/consulter.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Box, Divider, Heading, HStack, Text } from "@chakra-ui/react"; -import Head from "next/head"; -import React from "react"; - -import Link from "@/components/Links/Link"; -import Page from "@/components/Page/Page"; -import Section from "@/components/Section/Section"; -import CheckCfaTransmissionContent from "@/modules/organisme-formation/CheckCfaTransmission/CheckCfaTransmissionContent"; -import OrganismeFormationPagesMenu from "@/modules/organisme-formation/OrganismeFormationPagesMenu"; - -export default function CommentConsulterEtVerifierLesDonnees() { - const title = "Comment vérifier les données que vous transmettez ?"; - return ( - - - {title} - -
- - - - - - - - - - Comment consulter et vérifier les données que vous transmettez ? - - - Vous pouvez accéder sur le tableau de bord de l’apprentissage, à - - {" "} - une page dédiée à votre organisme de formation via une URL unique disponible dans votre ERP (ou - logiciel de gestion) - - . Cette URL est privée, ne la partagez qu’avec les personnes gestionnaires de votre organisme de - formation. - - {" "} - Pour plus d’informations, consultez la rubrique d’aide. - - - - Comment consulter et vérifier les données que vous transmettez ? - - Renseigner les informations suivantes pour vérifier la transmission : - - - - - - - -
-
- ); -} diff --git a/ui/pages/organisme-formation/transmettre.tsx b/ui/pages/organisme-formation/transmettre.tsx deleted file mode 100644 index 37d3ac0f5..000000000 --- a/ui/pages/organisme-formation/transmettre.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Box, Divider, Heading, HStack, Text } from "@chakra-ui/react"; -import Head from "next/head"; -import React from "react"; - -import Page from "@/components/Page/Page"; -import Section from "@/components/Section/Section"; -import DemandeBranchementErpFormBlock from "@/modules/organisme-formation/DemandeBranchementErp/DemandeBranchementErpFormBlock"; -import OrganismeFormationPagesMenu from "@/modules/organisme-formation/OrganismeFormationPagesMenu"; - -const CommentTransmettreVosDonneesPage = () => { - const title = "Comment transmettre les données de votre organisme ?"; - return ( - - - {title} - -
- - - - - - - - - - Comment transmettre les données de votre organisme ? - - - Afin de mieux vous guider, merci de renseigner le formulaire ci dessous : - - - - - - - - -
-
- ); -}; - -export default CommentTransmettreVosDonneesPage; diff --git a/ui/pages/organismes/[organismeId]/effectifs.tsx b/ui/pages/organismes/[organismeId]/effectifs.tsx index 31db60edc..19e229270 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/organismes/[organismeId]/effectifs/aide-configuration-erp.tsx b/ui/pages/organismes/[organismeId]/effectifs/aide-configuration-erp.tsx deleted file mode 100644 index 12b4f9f75..000000000 --- a/ui/pages/organismes/[organismeId]/effectifs/aide-configuration-erp.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Box, Container } from "@chakra-ui/react"; -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 ConfigurationAPI from "@/modules/mon-espace/effectifs/ConfigurationAPI"; -import { useEffectifsOrganisme } from "@/modules/mon-espace/effectifs/useEffectifsOrganisme"; - -export const getServerSideProps = async (context) => ({ props: { ...(await getAuthServerSideProps(context)) } }); - -const PageAideConfigurationErpSonOrganisme = () => { - const router = useRouter(); - const { organisme } = useEffectifsOrganisme(router.query.organismeId as string); - - const erpIdSelected = router.query.erp as string; - - return ( - - - Configuration ERP - - - - {organisme && } - - - - ); -}; - -export default withAuth(PageAideConfigurationErpSonOrganisme); diff --git a/ui/pages/parametres.tsx b/ui/pages/parametres.tsx new file mode 100644 index 000000000..9c3d0b1a2 --- /dev/null +++ b/ui/pages/parametres.tsx @@ -0,0 +1,626 @@ +import { ArrowForwardIcon, InfoIcon } 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}. + + {organisme.mode_de_transmission_configuration_date && ( + + (configuré le{" "} + {formatDateNumericDayMonthYear(organisme.mode_de_transmission_configuration_date)} par{" "} + {organisme.mode_de_transmission_configuration_author_fullname}) + + )} + + 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. + + {organisme.mode_de_transmission_configuration_date && ( + + (configuré le{" "} + {formatDateNumericDayMonthYear(organisme.mode_de_transmission_configuration_date)} par{" "} + {organisme.mode_de_transmission_configuration_author_fullname}) + + )} + Cliquez ci-dessous pour transmettre manuellement vos effectifs. + + Téléverser les effectifs + + + )} + + + + + + + + ) : ( + <> + + + + 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 + + + + + + + Un outil de gestion / ERP (Enterprise Ressource Planning ou PGI pour Progiciel de Gestion Intégré) est + une solution logicielle permettant d’unifier le système d’information d’une entreprise autour d’une + base de données unique. + + + + ))} + + {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" && ( + { + setStepConfigurationERP("choix_erp"); + setSelectedERPId(""); + }} + onSubmit={async () => { + 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(""); + }} + onBack={() => { + setStepConfigurationERP("choix_erp"); + 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; + onBack: () => any; + 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; + onBack: () => 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; + + if (!erp) { + return ( + <> + + + L’ERP {props.erpId} n’est pas pris en charge. Veuillez{" "} + + contacter le support + + . + + + + ); + } + + 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/pages/test-erp.tsx b/ui/pages/test-erp.tsx new file mode 100644 index 000000000..7c8e87823 --- /dev/null +++ b/ui/pages/test-erp.tsx @@ -0,0 +1,132 @@ +import { ExternalLinkIcon } from "@chakra-ui/icons"; +import { Heading, Container, Text, FormControl, FormLabel, Input, Box, Button, VStack } from "@chakra-ui/react"; +import { Form, Formik, useFormik } from "formik"; +import { useEffect } from "react"; +import { useLocalStorage } from "usehooks-ts"; + +import { _post, _put, _delete } from "@/common/httpClient"; +import SimplePage from "@/components/Page/SimplePage"; + +// pas de SSR ici car localStorage + +function TestERP() { + const [configTestERP, setConfigTestERP] = useLocalStorage("tdb-config-test-erp", { + siret: "", + uai: "", + erp: "", + api_key: "", + }); + + const { values, handleChange } = useFormik({ + initialValues: configTestERP, + onSubmit: () => {}, + }); + + // sauvegarde l'état du form dans le local storage + useEffect(() => { + setConfigTestERP(values); + }, [JSON.stringify(values)]); + + const title = "Test d’ERP"; + return ( + + + + {title} + + + + Cette page reproduit l’intégration et le paramétrage réalisés côté ERP pour l’authentification à l’API v3 du + TDB. + + + Cette page est publique et vous n’avez pas besoin d’être authentifié pour y accéder. Si vous l’êtes, il faudra + appartenir à un OFA pour que le process fonctionne correctement. + + + + {}}> +
+ + + 1. Configuration de l’organisme + + + + SIRET + + + + + UAI + + + + + ERP + + + + + + + 2. Configuration de la clé d’API + + + + Clé d’API + + + + + + +
+
+
+
+ ); +} +export default TestERP; diff --git a/ui/public/images/parametres-choix-transmission.svg b/ui/public/images/parametres-choix-transmission.svg new file mode 100644 index 000000000..3755b1997 --- /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 4f09597c2..2ac3b9bcc 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" }, }, diff --git a/yarn.lock b/yarn.lock index d86913785..c8e33470e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20231,6 +20231,7 @@ request-debug@latest: remixicon: 2.5.0 shared: "workspace:*" typescript: 5.0.4 + usehooks-ts: ^2.9.1 xlsx: "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz" yup: 0.32.11 yup-password: 0.2.2 @@ -20542,6 +20543,16 @@ request-debug@latest: languageName: node linkType: hard +"usehooks-ts@npm:^2.9.1": + version: 2.9.1 + resolution: "usehooks-ts@npm:2.9.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 36f1e4142ce23bc019b81d2e93aefd7f2c350abcf255598c21627114a69a2f2f116b35dc3a353375f09c6e4c9b704a04f104e3d10e98280545c097feca66c30a + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2"