Contexte
Implémente l'étape 2 du funnel repeq : écarts cadres dirigeants avec deux cas (calculable / non calculable). Point RGAA critique N°37 : les pourcentages calculés doivent être annoncés au lecteur d'écran via aria-live, les champs doivent être focusables, et les libellés reformulés pour lever l'ambiguïté. Issue parent #3287. Dépend de #TICKET_ROUTER, #TICKET_INFRA_FUNNEL.
Fichiers impactés
packages/app/src/modules/representation-equilibree/steps/Step2ExecutiveGap.tsx (création)
packages/app/src/modules/representation-equilibree/shared/GapCalculator.tsx (création — composant réutilisable par Step2 + Step3)
packages/app/src/modules/representation-equilibree/shared/__tests__/GapCalculator.test.tsx (création)
packages/app/src/modules/representation-equilibree/steps/__tests__/Step2ExecutiveGap.test.tsx (création)
packages/app/src/modules/representation-equilibree/StepPageClient.tsx (modification)
packages/app/src/modules/representation-equilibree/index.ts (modification — exports)
Changement attendu
- Composant
GapCalculator (réutilisé par Step2 & Step3) :
- Props :
{ kind: "executive" | "member", initialData, onSubmit }.
<fieldset> + <legend> "Pouvez-vous calculer les écarts de représentation parmi les cadres dirigeants / les membres des instances dirigeantes ?".
- Radio "Oui" / "Non", vertical (cohérence avec rémunération V2 et meilleur a11y mobile, cf. décision design 2).
- Cas
computable: true : deux <input type="number" min="0" step="1"> (nombre de femmes / nombre d'hommes), libellés reformulés : "Nombre de femmes parmi les cadres dirigeants" / "Nombre d'hommes parmi les cadres dirigeants" (N°37 — lever l'ambiguïté).
- Cas
computable: true — affichage des pourcentages calculés : un conteneur <div aria-live="polite" aria-atomic="true"> contenant deux champs calculés rendus comme <input type="text" readOnly tabIndex={0}> (focusables mais non éditables, cf. RGAA N°37) avec aria-describedby précisant "Champ calculé automatiquement". Les libellés : "Pourcentage de femmes parmi les cadres dirigeants" / "Pourcentage d'hommes…". Calcul : women / (women + men) * 100, arrondi à 2 décimales, vide tant que women + men === 0.
- Cas
computable: false : <fieldset> avec radios, options = valeurs de la constante correspondante (NOT_COMPUTABLE_REASONS_EXECUTIVE → 2 choix ; NOT_COMPUTABLE_REASONS_MEMBER → affichage différent géré dans le Step3, voir ticket dédié). Pour Step2, les 2 motifs des cadres sont exposés en radios.
- Callout DSFR au-dessus des radios avec la définition légale de l'art. L.3111-2 (exact verbatim à copier depuis
step-2-cadres.html).
Step2ExecutiveGap :
- Utilise
GapCalculator kind="executive".
useZodForm avec updateExecutiveGapSchema.
- Appelle
api.representationEquilibree.updateStep2.useMutation, puis router.push("/representation-equilibree/etape/3").
FormActions primaryLabel="Enregistrer et continuer" + bouton Précédent /etape/1.
- Tests
GapCalculator.test.tsx :
- Rendu cas calculable : saisir 30 F / 70 H → container
aria-live contient "30 %" et "70 %".
- Les champs % ont
tabIndex=0 (focusables) et readOnly.
- Rendu cas non calculable (kind=executive) : 2 options radio.
- Changement de radio computable Oui→Non masque les champs F/H et affiche le fieldset motif.
- StepPageClient : branche
<Step2ExecutiveGap initialData={...} /> à case 2:.
Scénarios de test
- S5 — Saisie 30/70 → les % s'affichent et sont annoncés (test unitaire
aria-live + contenu).
- S6 — Coche "non calculable" + sélection motif "Aucun cadre dirigeant" → soumission réussie, navigation vers étape 3.
- S14 (critique RGAA N°37) — Champs % focusables : test unitaire
tabIndex + readOnly.
Références visuelles
Desktop

Mobile

Annexe pipeline (lecture locale par code-dev / design-validator) :
/tmp/egapro-mocks/epic-3287/screenshots/step-2-cadres-desktop.png
/tmp/egapro-mocks/epic-3287/screenshots/step-2-cadres-mobile.png
Critères d'acceptation
Contexte
Implémente l'étape 2 du funnel repeq : écarts cadres dirigeants avec deux cas (calculable / non calculable). Point RGAA critique N°37 : les pourcentages calculés doivent être annoncés au lecteur d'écran via
aria-live, les champs doivent être focusables, et les libellés reformulés pour lever l'ambiguïté. Issue parent #3287. Dépend de #TICKET_ROUTER, #TICKET_INFRA_FUNNEL.Fichiers impactés
packages/app/src/modules/representation-equilibree/steps/Step2ExecutiveGap.tsx(création)packages/app/src/modules/representation-equilibree/shared/GapCalculator.tsx(création — composant réutilisable par Step2 + Step3)packages/app/src/modules/representation-equilibree/shared/__tests__/GapCalculator.test.tsx(création)packages/app/src/modules/representation-equilibree/steps/__tests__/Step2ExecutiveGap.test.tsx(création)packages/app/src/modules/representation-equilibree/StepPageClient.tsx(modification)packages/app/src/modules/representation-equilibree/index.ts(modification — exports)Changement attendu
GapCalculator(réutilisé par Step2 & Step3) :{ kind: "executive" | "member", initialData, onSubmit }.<fieldset>+<legend>"Pouvez-vous calculer les écarts de représentation parmi les cadres dirigeants / les membres des instances dirigeantes ?".computable: true: deux<input type="number" min="0" step="1">(nombre de femmes / nombre d'hommes), libellés reformulés : "Nombre de femmes parmi les cadres dirigeants" / "Nombre d'hommes parmi les cadres dirigeants" (N°37 — lever l'ambiguïté).computable: true— affichage des pourcentages calculés : un conteneur<div aria-live="polite" aria-atomic="true">contenant deux champs calculés rendus comme<input type="text" readOnly tabIndex={0}>(focusables mais non éditables, cf. RGAA N°37) avecaria-describedbyprécisant "Champ calculé automatiquement". Les libellés : "Pourcentage de femmes parmi les cadres dirigeants" / "Pourcentage d'hommes…". Calcul :women / (women + men) * 100, arrondi à 2 décimales, vide tant quewomen + men === 0.computable: false:<fieldset>avec radios, options = valeurs de la constante correspondante (NOT_COMPUTABLE_REASONS_EXECUTIVE→ 2 choix ;NOT_COMPUTABLE_REASONS_MEMBER→ affichage différent géré dans le Step3, voir ticket dédié). Pour Step2, les 2 motifs des cadres sont exposés en radios.step-2-cadres.html).Step2ExecutiveGap:GapCalculator kind="executive".useZodFormavecupdateExecutiveGapSchema.api.representationEquilibree.updateStep2.useMutation, puisrouter.push("/representation-equilibree/etape/3").FormActions primaryLabel="Enregistrer et continuer"+ bouton Précédent/etape/1.GapCalculator.test.tsx:aria-livecontient "30 %" et "70 %".tabIndex=0(focusables) etreadOnly.<Step2ExecutiveGap initialData={...} />àcase 2:.Scénarios de test
aria-live+ contenu).tabIndex+readOnly.Références visuelles
Desktop

Mobile

Annexe pipeline (lecture locale par code-dev / design-validator) :
/tmp/egapro-mocks/epic-3287/screenshots/step-2-cadres-desktop.png/tmp/egapro-mocks/epic-3287/screenshots/step-2-cadres-mobile.pngCritères d'acceptation
GapCalculatorimplémente les 2 cas avecaria-livesur le bloc des pourcentagesreadOnly+tabIndex=0)pnpm typecheck+pnpm lint:check+pnpm format:check+pnpm testvertsstep-2-cadres-*.png