Deux lots regroupés sur la branche feat/absence-management. Suppression complète du portail client : - retire ROLE_CLIENT (security.yaml) ; User::getRoles() ajoute toujours ROLE_USER - supprime l'entité ClientTicket (+ repo, states, relations), User.client et User.allowedProjects, NotificationService, ProjectAllowedExtension, le bloc ROLE_CLIENT de MailAccessChecker - front : pages /portal, layout portal, composants client-ticket/, AdminClientTicketTab, services/dto/i18n/docs associés - fixtures : retire les users client-liot / client-acme - migration Version20260522110000 (drop client_ticket, user_allowed_projects, colonnes liées ; task_document.task_id -> NOT NULL) - tests : retire les cas obsolètes testant le blocage des clients sur le mail Module gestion des absences (WIP) : - entités / migrations (Version20260521160000, Version20260522090000) - pages absences.vue / team-absences.vue, composants frontend/components/absence/ - services front, AccrueLeaveCommand, PublicHolidayController Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.1 KiB
Refonte UX du formulaire « Nouvelle demande d'absence »
Date : 2026-05-21
Composant : frontend/components/absence/AbsenceRequestDrawer.vue
Branche : feat/absence-management
Contexte & problème
Le formulaire actuel fonctionne mais est inutilisable côté UX :
VueDatePickerbrut non thématisé (couleur violette par défaut, calendrier au rendu anglo-saxon / semaine au dimanche) → aspect « cassé », rien à voir avec PayFit.<label>et<input type="file">bruts au lieu des composants Malio.- Aucune erreur explicite : le bouton « Valider » est simplement grisé via
canSubmitquand un champ manque, sans dire pourquoi. previewqui échoue en silence (catch { preview.value = null }).- Solde insuffisant signalé par un simple warning ambre.
Cible : reproduire l'ergonomie PayFit (référence fournie par l'utilisateur) — apparition progressive des champs, deux champs date saisissables + popup, pills de demi-journée, lignes de solde, erreurs explicites.
Objectifs
- Look & ergonomie alignés sur PayFit, cohérents avec le design system Malio.
- Apparition progressive des champs au fil des choix.
- Erreurs explicites par champ, affichées au clic « Valider », qui se vident dès correction.
- Champs de date en français
JJ / MM / AAAA, calendrier FR / semaine au lundi. - Justificatif via
MalioInputUpload, affiché seulement si le type l'exige.
Hors périmètre
- Aucun changement backend : payload (
AbsenceRequestWrite), endpointscreate/preview/uploadJustificationinchangés. - Aucune modification des autres composants du module (traités séparément dans les points #2 à #7 de la revue).
Layout cible (drawer max-w-xl, 1 colonne, apparition progressive)
Étape 1 — toujours visible
Type d'absence:MalioSelect(options = policies actives). Erreur inline si vide au submit.
Étape 2 — apparaît dès qu'un type est choisi
Date de début: champ texteJJ / MM / AAAAsaisissable + icône calendrier (popup) + bouton effacer.VueDatePickermono-date, thématisé (voir ci-dessous).- Pills demi-journée début :
Journée entière|Matin|Après-midi(défaut : Journée entière). - Ligne
Solde au <date début> : X jours(valeur =preview.available, alignée à droite, non repliable).
Étape 3 — apparaît dès que la date de début est renseignée
Date de fin: même composant que la date de début.- Pills demi-journée fin :
Journée entière|Matin(défaut : Journée entière). - Ligne
Durée de la demande : N jours(valeur =preview.countedDays). - Ligne
Solde après validation : Y jours(valeur =preview.projectedAvailable, non repliable). Si< 0→ bandeau ambre non bloquant « solde après = … j, demande soumise pour validation ».
Étape 4
Justificatif:MalioInputUpload, affiché uniquement siselectedPolicy.justificationRequired. Label avec*. Erreur inline si requis et absent.Commentaire(optionnel) :MalioInputTextArea, placeholder « Écrire un commentaire… ».
Footer
[ Annuler (tertiary) ] [ Valider (primary) ], bouton toujours actif.
Datepicker — thématisation
Reprendre le pattern de frontend/components/ui/DateFilter.vue :
VueDatePickermono-date,:enable-time-picker="false",auto-apply,text-inputactivé (saisie clavier),format→JJ/MM/AAAA.locale="fr"(chaîne) + semaine au lundi (week-start: 1).- Variables CSS
--dp-primary-color: #222783,--dp-border-color,--dp-hover-color,--dp-font-size,font-family: inherit(scopées au composant). :min-datesur la date de fin = date de début ;:max-datesur la date de début = date de fin.
Pills demi-journée → payload
Segment de boutons (style pill, bordure + fond bleu primaire quand sélectionné).
| Pill début | startHalfDay |
Pill fin | endHalfDay |
|---|---|---|---|
| Journée entière | null |
Journée entière | null |
| Matin | matin |
Matin | matin |
| Après-midi | apres_midi |
— | — |
Cas particulier — demande sur une seule journée (startDate == endDate) : seules les pills de début sont pertinentes (Journée entière / Matin / Après-midi) ; les pills de fin sont masquées et endHalfDay recopie startHalfDay. Le décompte reste calculé par preview côté backend.
Validation
État réactif errors: { type?: string; startDate?: string; endDate?: string; justification?: string }.
validate() (au clic « Valider ») remplit errors :
typemanquant →absences.form.errors.typeRequiredstartDatemanquante →absences.form.errors.startRequiredendDatemanquante →absences.form.errors.endRequiredendDate < startDate→absences.form.errors.endBeforeStartpreview.countedDays <= 0→absences.form.errors.zeroDays- justificatif requis et absent →
absences.form.errors.justificationRequired
Si errors non vide → on stoppe la soumission. Des watch vident chaque message dès que le champ correspondant redevient valide.
Le solde insuffisant n'est pas une erreur bloquante (seul le bandeau ambre).
Erreurs serveur
service.create/uploadJustification: le toast d'erreur deuseApireste ; en plus, un bandeau d'erreur en tête de formulaire affiche le message renvoyé (422 de validation).preview: conserver lecatchpour les coupures réseau, mais ne plus masquer une erreur de validation de période (afficher le message si l'API en renvoie un).
Calcul live (preview)
Inchangé sur le principe : watch debouncé (300 ms) sur [type, startDate, endDate, startHalfDay, endHalfDay] → service.preview(payload). Les lignes de solde et de durée se mettent à jour à partir du résultat (available, countedDays, projectedAvailable).
i18n
Nouvelles clés sous absences.form.errors.* dans frontend/i18n/locales/fr.json :
typeRequired, startRequired, endRequired, endBeforeStart, zeroDays, justificationRequired, plus absences.form.balanceAt (« Solde au {date} ») et absences.form.duration (« Durée de la demande »).
Découpage du composant
AbsenceRequestDrawer.vue orchestre l'état du formulaire. Pour garder le fichier focalisé, extraire :
AbsenceDateField.vue: champ date thématisé + pills demi-journée (props :modelValue,halfValue,halfOptions,label,error,min/max).
Le reste (lignes de solde, bandeau, footer) reste inline dans le drawer.
Critères d'acceptation
- Au chargement, seul « Type d'absence » est visible ; les sections suivantes apparaissent au fur et à mesure.
- Les dates s'affichent et se saisissent en
JJ / MM / AAAA, calendrier en français, semaine au lundi, thème bleu primaire. - Cliquer « Valider » sans remplir → messages d'erreur explicites sous les champs concernés ; ils disparaissent dès correction.
- Un type à justificatif obligatoire affiche le champ d'upload et bloque la soumission tant qu'aucun fichier n'est fourni.
- Une période dépassant le solde affiche le bandeau ambre mais reste soumissible.
- Le payload envoyé au backend est identique à l'actuel.