Skip to content

Commit efdd178

Browse files
committed
feat(admin): added certification prerequisite form to certification registry manager certification update page
1 parent faf392c commit efdd178

File tree

5 files changed

+231
-35
lines changed

5 files changed

+231
-35
lines changed

packages/reva-admin-react/cypress/e2e/responsable-certifications/certifications/update-certification-prerequisites-page/fixtures/certification-bp-boucher.json

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,44 +14,17 @@
1414
"label": "Niveau 4 : Baccalauréat"
1515
},
1616
"domains": [],
17-
"competenceBlocs": [
17+
"competenceBlocs": [],
18+
"prerequisites": [
1819
{
19-
"id": "008a6fab-55ad-4412-ab17-56bc4b8e2fd0",
20-
"code": "B1",
21-
"label": "Préparation, présentation, décoration et vente en boucherie",
22-
"competences": [
23-
{
24-
"id": "b8786018-6d24-4538-9622-f4ac62eb1742",
25-
"label": "Réaliser les opérations de préparations des viandes"
26-
},
27-
{
28-
"id": "2cbb6688-7b80-416f-940a-e348246484f8",
29-
"label": "Mettre en valeur les produits notamment l’intégralité de la carcasse dans une démarche de développement durable"
30-
},
31-
{
32-
"id": "2c829da9-ed10-48a3-ae1b-2f7db433c6d8",
33-
"label": "Vendre les produits au client en argumentant et en proposant des conseils culinaires"
34-
},
35-
{
36-
"id": "5aad9206-27a0-4afa-ac28-06d30bab6504",
37-
"label": "Communiquer sur l’étiquetage, la conservation, la traçabilité, les signes officiels de qualité et l’origine des viandes"
38-
}
39-
]
20+
"id": "b1b1b1b1-b1b1-b1b1-b1b1-b1b1b1b1b1b1",
21+
"label": "Prérequis 1",
22+
"order": 0
4023
},
4124
{
42-
"id": "cc8f1e74-fcd8-4d8b-b03f-97b3012b015d",
43-
"code": "B2",
44-
"label": "Application des règles relatives à l'alimentation et à l'hygiène, aux locaux et équipements du laboratoire et de l'unité de vente en boucherie",
45-
"competences": [
46-
{
47-
"id": "40300ce1-e877-44e1-af0b-ec3db3ba7eb8",
48-
"label": "Analyser des situations professionnelles nécessitant la connaissance des animaux de boucherie et leurs produits, l’environnement professionnel du boucher et les techniques professionnelles de la boucherie"
49-
},
50-
{
51-
"id": "a8e39ba6-9463-48b2-aa99-f8ce1b83904c",
52-
"label": "Appliquer les règles relatives à l’alimentation, à l’hygiène, aux locaux et équipements dans l’environnement professionnel du boucher et de la boucherie"
53-
}
54-
]
25+
"id": "b2b2b2b2-b2b2-b2b2-b2b2-b2b2b2b2b2b2",
26+
"label": "Prérequis 2",
27+
"order": 2
5528
}
5629
]
5730
}

packages/reva-admin-react/cypress/e2e/responsable-certifications/certifications/update-certification-prerequisites-page/update-certification-prerequisites-page.cy.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,48 @@ context("when i access the update certification page ", () => {
3939
.children("h1")
4040
.should("have.text", "Prérequis obligatoires");
4141
});
42+
43+
it("dont let me submit the form if no edit has been made", function () {
44+
interceptCertification();
45+
cy.admin(
46+
"http://localhost:3003/admin2/responsable-certifications/certifications/bf78b4d6-f6ac-4c8f-9e6b-d6c6ae9e891b/prerequisites",
47+
);
48+
cy.wait("@activeFeaturesForConnectedUser");
49+
cy.wait("@getMaisonMereCGUQuery");
50+
cy.wait("@getCertificationForUpdateCertificationPrerequisitesPage");
51+
52+
cy.get("button").contains("Enregistrer").should("be.disabled");
53+
});
54+
55+
it("let me add a new prerequisite to the certification", function () {
56+
interceptCertification();
57+
cy.admin(
58+
"http://localhost:3003/admin2/responsable-certifications/certifications/bf78b4d6-f6ac-4c8f-9e6b-d6c6ae9e891b/prerequisites",
59+
);
60+
cy.wait("@activeFeaturesForConnectedUser");
61+
cy.wait("@getMaisonMereCGUQuery");
62+
cy.wait("@getCertificationForUpdateCertificationPrerequisitesPage");
63+
64+
cy.get('[data-test="prerequisite-list"] input').should("have.length", 2);
65+
66+
cy.get('[data-test="add-prerequisite-button"]').click();
67+
68+
cy.get('[data-test="prerequisite-list"] input').should("have.length", 3);
69+
});
70+
71+
it("let me delete a prerequisite from the certification", function () {
72+
interceptCertification();
73+
cy.admin(
74+
"http://localhost:3003/admin2/responsable-certifications/certifications/bf78b4d6-f6ac-4c8f-9e6b-d6c6ae9e891b/prerequisites",
75+
);
76+
cy.wait("@activeFeaturesForConnectedUser");
77+
cy.wait("@getMaisonMereCGUQuery");
78+
cy.wait("@getCertificationForUpdateCertificationPrerequisitesPage");
79+
80+
cy.get('[data-test="prerequisite-list"] input').should("have.length", 2);
81+
82+
cy.get('[data-test="delete-prerequisite-button"]').eq(1).click();
83+
84+
cy.get('[data-test="prerequisite-list"] input').should("have.length", 1);
85+
});
4286
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { FormButtons } from "@/components/form/form-footer/FormButtons";
2+
import { SortableList } from "@/components/sortable-list";
3+
import { Button } from "@codegouvfr/react-dsfr/Button";
4+
import Checkbox from "@codegouvfr/react-dsfr/Checkbox";
5+
import { Input } from "@codegouvfr/react-dsfr/Input";
6+
import { zodResolver } from "@hookform/resolvers/zod";
7+
import { useFieldArray, useForm } from "react-hook-form";
8+
import { z } from "zod";
9+
10+
export const prerequisitesFormSchema = z.object({
11+
noPrerequisites: z.boolean().default(false),
12+
prerequisites: z
13+
.object({
14+
id: z.string().optional(),
15+
label: z.string().min(1, "Merci de remplir ce champ").default(""),
16+
index: z.number(),
17+
})
18+
.array(),
19+
});
20+
21+
export type PrerequisitesFormData = z.infer<typeof prerequisitesFormSchema>;
22+
23+
export const PrerequisitesForm = ({
24+
onSubmit,
25+
defaultValues,
26+
backUrl,
27+
className = "",
28+
onDeletePrerequisitesButtonClick,
29+
}: {
30+
onSubmit(data: PrerequisitesFormData): Promise<void>;
31+
defaultValues?: Partial<PrerequisitesFormData>;
32+
backUrl: string;
33+
className?: string;
34+
onDeletePrerequisitesButtonClick?: () => void;
35+
}) => {
36+
const methods = useForm<PrerequisitesFormData>({
37+
resolver: zodResolver(prerequisitesFormSchema),
38+
defaultValues,
39+
});
40+
41+
const {
42+
register,
43+
control,
44+
reset,
45+
handleSubmit,
46+
formState: { isDirty, isSubmitting, errors },
47+
} = methods;
48+
49+
const {
50+
fields: prerequisitesFields,
51+
append: appendPrerequisite,
52+
remove: removePrerequisite,
53+
move: movePrerequisites,
54+
} = useFieldArray({
55+
control,
56+
name: "prerequisites",
57+
});
58+
59+
const handleFormSubmit = handleSubmit(
60+
(d: PrerequisitesFormData) => {
61+
//Re index the prerequisites since they could have been moved in the form
62+
const reIndexPrerequisites = d.prerequisites.map((c, index) => ({
63+
...c,
64+
index,
65+
}));
66+
onSubmit({ ...d, prerequisites: reIndexPrerequisites });
67+
},
68+
(e) => console.log(e),
69+
);
70+
return (
71+
<form
72+
onSubmit={handleFormSubmit}
73+
className={`flex flex-col ${className}`}
74+
onReset={(e) => {
75+
e.preventDefault();
76+
reset();
77+
}}
78+
>
79+
<Checkbox
80+
options={[
81+
{
82+
label:
83+
"Il n’y a aucun prérequis obligatoire pour cette certification.",
84+
nativeInputProps: { ...register("noPrerequisites") },
85+
},
86+
]}
87+
/>
88+
<div
89+
className="flex flex-col gap-2 mb-2 pl-4"
90+
data-test="prerequisite-list"
91+
>
92+
<SortableList
93+
items={prerequisitesFields}
94+
onItemMoved={movePrerequisites}
95+
renderItem={(c, cIndex) => (
96+
<SortableList.Item
97+
className="flex items-center gap-2"
98+
id={c.id}
99+
key={c.id}
100+
>
101+
<div className="flex content-betwee gap-2 w-full mt-2" key={c.id}>
102+
<SortableList.DragHandle />
103+
<Input
104+
className="mb-0 w-full"
105+
label=""
106+
state={
107+
errors.prerequisites?.[cIndex]?.label ? "error" : "default"
108+
}
109+
stateRelatedMessage={errors.prerequisites?.[
110+
cIndex
111+
]?.label?.message?.toString()}
112+
nativeInputProps={{
113+
...register(`prerequisites.${cIndex}.label`),
114+
}}
115+
/>
116+
<Button
117+
data-test="delete-prerequisite-button"
118+
type="button"
119+
priority="tertiary no outline"
120+
iconId="fr-icon-delete-line"
121+
iconPosition="left"
122+
onClick={() => removePrerequisite(cIndex)}
123+
>
124+
Supprimer
125+
</Button>
126+
</div>
127+
</SortableList.Item>
128+
)}
129+
/>
130+
</div>
131+
<Button
132+
data-test="add-prerequisite-button"
133+
type="button"
134+
priority="tertiary no outline"
135+
iconId="fr-icon-add-line"
136+
iconPosition="left"
137+
onClick={() =>
138+
appendPrerequisite({ label: "", index: prerequisitesFields.length })
139+
}
140+
>
141+
Ajouter un prérequis
142+
</Button>
143+
<hr className="mt-6 mb-1" />
144+
{onDeletePrerequisitesButtonClick && (
145+
<Button
146+
data-test="delete-prerequisite-bloc-button"
147+
type="button"
148+
priority="tertiary no outline"
149+
iconId="fr-icon-delete-line"
150+
iconPosition="left"
151+
onClick={onDeletePrerequisitesButtonClick}
152+
>
153+
Supprimer ce bloc
154+
</Button>
155+
)}
156+
<FormButtons
157+
backUrl={backUrl}
158+
formState={{
159+
isDirty,
160+
isSubmitting,
161+
}}
162+
/>
163+
</form>
164+
);
165+
};

packages/reva-admin-react/src/app/responsable-certifications/certifications/[certificationId]/prerequisites/page.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useParams } from "next/navigation";
33
import { FormOptionalFieldsDisclaimer } from "@/components/form-optional-fields-disclaimer/FormOptionalFieldsDisclaimer";
44
import { Breadcrumb } from "@codegouvfr/react-dsfr/Breadcrumb";
55
import { useUpdatePrerequisitesPage } from "./updatePrerequisites.hook";
6+
import { PrerequisitesForm } from "./(components)/prerequisites-form/PrerequisitesForm";
67

78
type CertificationForPage = Exclude<
89
ReturnType<typeof useUpdatePrerequisitesPage>["certification"],
@@ -53,5 +54,13 @@ const PageContent = ({
5354
depuis France compétences et, si nécessaire, procédez à des corrections
5455
(ordre des prérequis, fautes de frappe...).
5556
</p>
57+
<PrerequisitesForm
58+
onSubmit={async (d) => console.log({ d })}
59+
defaultValues={{
60+
noPrerequisites: !certification.prerequisites.length,
61+
prerequisites: certification.prerequisites,
62+
}}
63+
backUrl={`/responsable-certifications/certifications/${certification.id}`}
64+
/>
5665
</div>
5766
);

packages/reva-admin-react/src/app/responsable-certifications/certifications/[certificationId]/prerequisites/updatePrerequisites.hook.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ const getCertificationQuery = graphql(`
1010
id
1111
codeRncp
1212
label
13+
prerequisites {
14+
id
15+
label
16+
index
17+
}
1318
}
1419
}
1520
`);

0 commit comments

Comments
 (0)