Design: Absence Management (ABSMGT)¶
Brand: BOTH (Papillon HR Suite + ALTARYS ENTERPRISE HR Suite) Date: 12/03/2026 Version: 1.1 Author: DESIGNER (Claude — Senior UX/UI Designer) PRD Source: docs/prd/absence-management-prd.md v1.0
For the DEVELOPER: This is your single source of truth for all UI/UX in Absence Management. It references tokens from
frontend/src/theme/tokens.cssand components fromdocs/standards/design-system.md. Use existing components — never rebuild. If a needed component doesn't exist, build it ascomponents/ui/first.Key rules: - Papillon brand = warm amber, mobile-first, generous whitespace, guided - Enterprise brand = navy, desktop-first, dense, power-user - All dates: DD/MM/YYYY — French locale - Leave balances: display in tenant's configured unit (jours calendaires default) - All UI text in French (see §11 Micro-copy) - Offline: full read + leave request creation offline, auto-submit on sync - Touch targets: minimum 48×48px on mobile - Numbers: space as thousands separator
Table of Contents¶
- Scope & Screens Index
- Design Decisions Log
- User Flow Diagrams
- Screen Specifications
- 4.1 Employee Home — Mes congés
- 4.2 Leave Request Form
- 4.3 Leave Request Detail
- 4.4 Manager — Pending Approvals
- 4.5 Approval Detail & Action
- 4.6 Team Calendar
- 4.7 HR Dashboard
- 4.8 HR — Employee Balance Detail
- 4.9 HR — Maternity Declaration
- 4.10 HR — Manual Balance Adjustment
- 4.11 Settings — Leave Configuration
- 4.12 Settings — Public Holidays
- Responsive Behavior
- Offline Behavior
- Motion Choreography
- Loading, Empty, Error & Success States
- Component Specifications
- Enterprise Brand Divergence
- French Micro-copy
- Brand Compliance Checklist
1. Scope & Screens Index¶
Personas Served¶
| Persona | Access Level | Primary Screens |
|---|---|---|
| Employé (P4a) | Self-service: balance, request, cancel, team calendar (presence only) | S1, S2, S3, S6 (presence-only) |
| Manager (P1a) | Approve/reject direct reports + team calendar (direct reports) | S4, S5, S6 |
| RH Manager (P2a) | Full: all balances, all approvals, maternity, adjustments, config | All screens |
| DG (P1b) | Full + self-approval | All screens |
Screen Map¶
| # | Screen | Route | Access | PRD Reference |
|---|---|---|---|---|
| S1 | Employee Home — Mes congés | /absences |
All | US-ABS-006, US-ABS-001 |
| S2 | Leave Request Form | /absences/demande |
Employee+HR | US-ABS-001, US-ABS-005, US-ABS-007 |
| S3 | Leave Request Detail | /absences/demande/:id |
Owner+Approver+HR | US-ABS-009 |
| S4 | Pending Approvals | /absences/approbations |
Manager+HR+DG | US-ABS-002 |
| S5 | Approval Detail | /absences/approbations/:id |
Manager+HR+DG | US-ABS-002 |
| S6 | Team Calendar | /absences/calendrier |
Manager+HR+DG (presence for Employee) | US-ABS-003 |
| S7 | HR Dashboard | /absences/tableau-de-bord |
HR+DG | US-ABS-008 |
| S8 | Employee Balance Detail | /absences/employes/:id/solde |
HR+DG | US-ABS-008 |
| S9 | Maternity Declaration | /absences/maternite/nouvelle |
HR+DG | US-ABS-004 |
| S10 | Manual Balance Adjustment | /absences/employes/:id/ajustement |
HR+DG | FR-ABS-120 |
| S11 | Leave Configuration | /absences/parametres/types |
HR+DG | FR-ABS-131 |
| S12 | Public Holidays | /absences/parametres/jours-feries |
HR+DG | FR-ABS-100–104 |
Module Navigation¶
The Absence module lives under "Congés" in the host shell sidebar (group: Quotidien) and bottom nav tab 2. Within the module, navigation is role-adaptive:
Employee sees: Tabs → Mes congés | Calendrier
Manager sees: Tabs → Mes congés | Approbations (badge count) | Calendrier
HR/DG sees: Tabs → Tableau de bord | Approbations (badge count) | Calendrier | Paramètres
Uses Tabs component (to be built — see §9). The "Mes congés" personal view is always accessible via the user avatar/profile area for HR/DG who also have a dashboard.
2. Design Decisions Log¶
Every key UX decision is logged here with rationale.
| # | Decision | Choice | Rationale |
|---|---|---|---|
| DD-01 | Leave request flow | Smart hybrid: single compact form for simple types (annual, unpaid), 2-step for complex types (exceptional → event picker first; sick → document step) | PRD says "3 taps to request leave." Annual leave is ~70% of requests. Single form = type + dates + submit. Complex types need progressive disclosure to avoid overwhelming users with event pickers and document uploads they don't need. Hick's Law: show only relevant fields per type. |
| DD-02 | Team calendar visualization | Timeline/Gantt: rows = people, columns = days, bars = absences | Managers need to spot coverage gaps at a glance. Timeline is the only view that shows overlapping absences spatially. Month grid gets crowded with 5+ people. List view loses the "who overlaps with whom" pattern. Mobile: horizontally scrollable with today pinned left. |
| DD-03 | Balance display | Hero number prominently (total available days), breakdown on tap/expand | Employee's #1 question: "How many days do I have?" Answer it instantly with a large display number. Breakdown (base + ancienneté + children − taken) is secondary — available via expandable section. Progressive disclosure prevents cognitive overload for first-time users. |
| DD-04 | Module tab structure | Role-adaptive tabs (Employee: 2 tabs, Manager: 3, HR: 4) | Employees don't need to see "Approbations" — it would confuse them. Managers don't need settings. Showing only relevant tabs follows the principle of minimal exposure (only show what serves the user's role). Consistent with host shell's role-based sidebar filtering. |
| DD-05 | Approval UX | Card-based list with inline Approve (primary) + Reject (ghost) buttons; swipe on mobile with 2-second undo confirmation | Consistent with budget module's approval pattern (DD-06 in budget design). Swipe-to-approve is familiar from M-Pesa/mobile banking in CI. Rejection always opens a BottomSheet for mandatory reason input. Desktop uses explicit buttons (no hover-dependent gestures). v1.1 enhancement: Swipe-to-approve now shows a 2-second floating undo bar instead of committing instantly — this prevents accidental approvals (a legal HR action that deducts balance, fires LeaveApproved event to Payroll, and sends notifications). On 360px screens, the 40% swipe threshold is only ~144px, easily triggered by a scroll misalignment. The undo bar provides error recovery without slowing down power users (Don Norman: error prevention > error recovery). |
| DD-06 | Half-day toggle | Inline checkboxes below each date field, revealed only after both dates are selected | Half-days are an edge case (~5% of requests). Showing toggles before dates are picked adds noise. Revealing them after date selection keeps the form clean. Checkbox labels clearly state: "Demi-journée (après-midi uniquement)" for start, "Demi-journée (matin uniquement)" for end. |
| DD-07 | Soft warning presentation | Amber inline banner below the form field that triggered it, with info icon + text | Non-blocking warnings (15-day notice, 14-consecutive-day block) must be visible but not feel like errors. Amber (warning color) with info icon distinguishes from red error banners. Banner sits contextually near the trigger (e.g., under the date field), not at the top of the form where it might be missed. |
| DD-08 | Permission exceptionnelle flow | Step 1: large tappable event-type cards (icon + name + "X jours"), grouped by context (Deuil / Événements heureux) and ordered by statistical frequency → Step 2: dates auto-filled + document upload | Event selection IS the key decision — it determines everything else. Large cards with icons are scannable even for low-literacy users. Showing days on each card helps the employee understand what they're entitled to before committing. End date auto-calculates from start date + event duration. v1.1 enhancement: Cards reordered by Pareto principle — death events (most statistically common in CI) shown first under a "Deuil" group label, then positive events (birth, marriage) under "Événements heureux." The 4 less common events (sibling death, in-law death, child marriage, move) behind "Autres événements" fold. Grouping death events under "Deuil" reduces the emotional weight of seeing 5 death cards — culturally respectful for an employee navigating grief. |
| DD-09 | Document upload timing | Inline in form, with prominent visual CTA when required | PRD has PENDING_DOCUMENT status for missing docs. Rather than a separate step, the upload zone is always visible in the form when the leave type requires it. If submitted without doc: request saves as PENDING_DOCUMENT with a clear card in "Mes congés" showing "En attente de justificatif" + upload button. This respects offline: user can create request offline, upload doc later. |
| DD-10 | Maternity declaration | Dedicated HR-only form, separate from the standard leave request flow | Maternity is NOT a "request" — it's a legal right. Having HR create it through a separate "Déclaration de maternité" flow (not the employee leave request form) reinforces this distinction. The form is simpler: employee picker + expected delivery date → system auto-creates the 14-week block. |
| DD-11 | Request history display | Card list grouped by status (En cours maintenant → À venir → En attente → Passées), with status Badge | Status is the primary dimension employees care about. v1.1 enhancement: Split the former "En cours" group into two semantically distinct groups: (1) "En cours maintenant" = IN_PROGRESS leaves (employee IS currently absent — answers "am I on leave right now?" with a "jour X sur Y" counter), and (2) "À venir" = APPROVED future leaves. This matches the state machine (APPROVED vs. IN_PROGRESS) and addresses CI users' #1 question after balance — especially employees transitioning from WhatsApp-based leave tracking who may not track their own dates precisely (Nielsen's Heuristic #2: match between system and real world). "En cours maintenant" only appears when relevant (no empty state needed). "Passées" group is collapsed by default with "Voir tout" link. |
| DD-12 | HR Dashboard layout | 4 KPI cards + pending approvals list + team calendar widget (2-week) | HR's daily workflow: (1) check pending approvals, (2) monitor absence rate, (3) glance at who's off. KPIs answer the metrics questions. Pending list is actionable — tap to approve. Calendar widget shows 2-week snapshot (not full month — HR can navigate to full calendar tab). Consistent with budget dashboard pattern. |
| DD-13 | Offline leave creation | Full form works offline → saved as DRAFT → auto-submitted on sync | This is a core Papillon differentiator. The form is identical online/offline. On submit offline: Toast shows "Demande enregistrée — sera envoyée automatiquement." Card appears in "Mes congés" with sync-pending icon. On reconnect: auto-submits, status updates to PENDING_APPROVAL or PENDING_DOCUMENT. If validation fails server-side: notification with reason + link to edit. |
| DD-14 | Balance unit display | Show tenant's configured unit with small helper showing conversion | Most tenants use jours calendaires. The balance shows "24,5 jours calendaires" prominently. A small text below shows the equivalent in jours ouvrables for those who know the legal reference. No unit picker for employees — that's a settings-level decision by HR. |
3. User Flow Diagrams¶
3.1 Employee — Submit Leave Request¶
flowchart TD
A[Employé ouvre module Congés] --> B[Voit solde + historique]
B --> C[Tape '+ Nouvelle demande']
C --> D[Sélectionne type de congé]
D --> E{Type complexe?}
E -->|ANNUAL / UNPAID| F[Formulaire simple:<br>dates + demi-journée + motif]
E -->|EXCEPTIONAL| G[Étape 1: Choix événement<br>cartes avec icônes]
E -->|SICK| H[Formulaire + zone upload<br>certificat médical]
G --> I[Étape 2: Dates auto-calculées<br>+ upload justificatif]
F --> J{Validation côté client}
H --> J
I --> J
J -->|Erreur bloquante| K[Message rouge inline:<br>solde insuffisant / 12 mois requis]
J -->|Warning souple| L[Banner ambre:<br>préavis 15j / bloc 14j consécutifs]
L --> M{Connecté?}
J -->|OK| M
M -->|En ligne| N[POST /api/v1/absences/requests]
M -->|Hors ligne| O[Sauvegarde IndexedDB<br>status = DRAFT]
N --> P{Document requis?}
P -->|Oui, pas uploadé| Q[Status: PENDING_DOCUMENT<br>Carte avec bouton Upload]
P -->|Non ou déjà uploadé| R[Status: PENDING_APPROVAL]
O --> S[Toast: 'Demande enregistrée —<br>sera envoyée automatiquement']
S --> T[Icône sync-pending sur la carte]
T --> U{Reconnexion}
U --> V[Auto-submit]
V --> W{Validation serveur OK?}
W -->|Oui| R
W -->|Non| X[Notification: erreur +<br>lien vers modification]
R --> Y[Toast succès + animation check ✓]
Y --> Z[Notification push au manager]
Q --> AA[Employé uploade plus tard]
AA --> R
3.2 Manager — Approve/Reject Leave¶
flowchart TD
A[Manager ouvre Congés] --> B{Demandes en attente?}
B -->|Oui| C[Badge rouge sur tab Approbations]
B -->|Non| D[Tab Approbations: état vide<br>'Aucune demande en attente']
C --> E[Liste de cartes: une par demande]
E --> F[Tape sur une carte]
F --> G[Détail demande:<br>nom, type, dates, jours,<br>solde employé, calendrier équipe]
G --> H{Document requis?}
H -->|Oui, présent| I[Indicateur: '📎 Justificatif joint']
H -->|Oui, absent| J[Bouton Approuver désactivé<br>'En attente du justificatif']
H -->|Non requis| K[Boutons actifs]
I --> K
K --> L{Décision}
L -->|Approuver| M[Tap Approuver]
M --> N[Animation succès ✓<br>Toast: 'Congé approuvé']
N --> O[LeaveApproved event → Payroll]
N --> P[Notification → employé]
L -->|Rejeter| Q[Tap Rejeter]
Q --> R[BottomSheet: motif obligatoire]
R --> S{Motif saisi?}
S -->|Non| T[Bouton Confirmer désactivé]
S -->|Oui| U[Confirmer rejet]
U --> V[Toast: 'Demande rejetée']
V --> W[Notification → employé avec motif]
%% Swipe gesture (mobile)
E --> X[Swipe droite → Approuver<br>Swipe gauche → Rejeter]
X --> M
X --> Q
3.3 Employee — Cancel Leave¶
flowchart TD
A[Employé ouvre détail demande] --> B{Status actuel?}
B -->|PENDING_APPROVAL<br>PENDING_DOCUMENT| C[Bouton 'Annuler la demande']
C --> D[Confirmation: 'Êtes-vous sûr ?'<br>+ bouton Annuler avec undo 8s]
D --> E[Status → CANCELLED_BY_EMPLOYEE]
E --> F[Toast avec Undo 8 secondes]
B -->|APPROVED<br>pas encore commencé| G[Bouton 'Annuler le congé']
G --> H[Confirmation renforcée:<br>'Ce congé approuvé sera annulé.<br>Vos jours seront recrédités.']
H --> I[Status → CANCELLED_BY_EMPLOYEE<br>Balance re-créditée]
I --> J[Toast succès + notification manager+RH]
B -->|IN_PROGRESS| K[Pas de bouton Annuler<br>Message: 'Contactez les RH<br>pour un retour anticipé']
B -->|COMPLETED<br>REJECTED<br>CANCELLED| L[Lecture seule — aucune action]
3.4 HR — Maternity Declaration¶
flowchart TD
A[RH ouvre Déclaration maternité] --> B[Sélectionne employée]
B --> C[Saisit date présumée d'accouchement]
C --> D[Système calcule automatiquement:<br>Début = DPA − 42 jours<br>Fin = DPA + 56 jours<br>= 14 semaines total]
D --> E[Affiche résumé:<br>période, chevauchements éventuels]
E --> F{Chevauchement avec congé annuel?}
F -->|Oui| G[Info: 'X jours de congé annuel<br>seront recrédités automatiquement']
F -->|Non| H[OK]
G --> H
H --> I[Bouton 'Enregistrer la déclaration']
I --> J[Status: AUTO_APPROVED<br>Congé créé automatiquement]
J --> K[Notification → employée + manager]
K --> L[Toast succès avec animation spéciale 🎉]
3.5 Offline Sync Flow¶
flowchart TD
A[Utilisateur hors ligne] --> B[Crée demande de congé]
B --> C[Sauvegarde IndexedDB<br>status = DRAFT<br>offline_created = true]
C --> D[Carte dans 'Mes congés'<br>avec icône ⟳ sync pending]
D --> E{Reconnexion détectée}
E --> F[Service Worker déclenche sync]
F --> G[POST /api/v1/absences/requests<br>pour chaque DRAFT]
G --> H{Résultat serveur}
H -->|201 Created| I[Mise à jour status local<br>DRAFT → PENDING_APPROVAL<br>ou PENDING_DOCUMENT]
I --> J[Icône sync → ✓ synced<br>Toast: 'Demande envoyée']
H -->|409 Conflict| K[Solde insuffisant depuis offline<br>ou chevauchement créé entre-temps]
K --> L[Notification in-app:<br>'Votre demande n'a pas pu être envoyée'<br>+ raison + lien Modifier]
L --> M[Carte passe en état erreur<br>avec bouton Modifier]
H -->|500 Error| N[Retry avec backoff exponentiel<br>max 3 tentatives]
N -->|Échec définitif| L
%% Lecture offline
O[Données préchargées au dernier sync] --> P[Solde, historique, calendrier<br>disponibles en lecture]
P --> Q[Banner subtil: 'Dernière synchro: il y a 2h']
4. Screen Specifications¶
4.1 Employee Home — Mes congés¶
Route: /absences
Access: All roles (employees see own data; HR sees this as sub-view via profile)
Decisions applied: DD-03 (balance display), DD-11 (history grouping), DD-13 (offline), DD-14 (unit display)
Layout — Mobile (360px)¶
┌─────────────────────────────────────┐
│ ← Congés & Absences │ ← Page title (h1)
│ │
│ ┌──────────────────────────────────┐ │
│ │ ☀️ Votre solde │ │ ← Card padding=spacious
│ │ │ │
│ │ 24,5 │ │ ← font-size-display, bold
│ │ jours calendaires │ │ ← font-size-body, secondary
│ │ │ │
│ │ ≡ 19,6 jours ouvrables │ │ ← font-size-caption, tertiary
│ │ │ │
│ │ ▾ Voir le détail │ │ ← ghost link, expandable
│ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │
│ │ Acquis base : 27,5 JC │ │ ← expanded content
│ │ + Ancienneté : +1,25 JC │ │
│ │ + Enfants (2) : +2,5 JC │ │
│ │ − Pris : −6,75 JC │ │
│ │ ───────────────────────── │ │
│ │ = Disponible : 24,5 JC │ │
│ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │ ← Button variant=primary
│ │ + Nouvelle demande │ │ size=lg, fullWidth
│ └──────────────────────────────────┘ │
│ │
│ │ ← v1.1: 4-group history (DD-11)
│ ─ 🔴 En cours maintenant ──────────│ ← Only shown when employee IS
│ │ on leave (contextual section)
│ ┌──────────────────────────────────┐ │
│ │ 🏖 Congé annuel │ │ ← Card, bg=primary-light
│ │ 10/03 → 21/03/2026 │ │ (elevated appearance)
│ │ 10 jours calendaires │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ 🔴 En cours · jour 4/10│ │ │ ← Badge variant=info + counter
│ │ └─────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ 📅 À venir ──────────────────────│ ← APPROVED future leaves
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🏖 Congé annuel │ │ ← Card interactive
│ │ 15/04 → 22/04/2026 │ │
│ │ 6 jours calendaires │ │
│ │ ┌─────────────────┐ │ │
│ │ │ ● Approuvé │ │ │ ← Badge variant=success
│ │ └─────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ ⏳ En attente ───────────────────│ ← PENDING_APPROVAL + PENDING_DOC
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🤒 Congé maladie │ │
│ │ 03/04 → 04/04/2026 │ │
│ │ 2 jours calendaires │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ ◐ En attente justif. │ │ │ ← Badge variant=warning
│ │ └───────────────────────┘ │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ 📎 Joindre certificat │ │ │ ← Button variant=secondary
│ │ └──────────────────────────┘ │ │ size=sm
│ └──────────────────────────────────┘ │
│ │
│ ─ 📋 Passées ──────────────────────│ ← Collapsed by default
│ (3 demandes) [Voir tout ▾] │ tap "Voir tout" to expand
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🏖 Congé annuel │ │ ← Card, muted appearance
│ │ 10/01 → 17/01/2026 │ │
│ │ 6 jours calendaires │ │
│ │ ┌─────────────────┐ │ │
│ │ │ ✓ Terminé │ │ │ ← Badge variant=neutral
│ │ └─────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ Voir tout l'historique → │ ← ghost link
│ │
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ ← Bottom nav (host shell)
└─────────────────────────────────────┘
Layout — Desktop (1024px+)¶
┌─────────────────────────────────────────────────────────────────┐
│ Sidebar │ Congés & Absences [Mes congés ↗] │
│ │ │
│ │ ┌─── Balance Card ──────┐ ┌─── Quick Stats ──────┐│
│ │ │ ☀️ Votre solde │ │ Pris cette année ││
│ │ │ 24,5 JC │ │ 18 ││
│ │ │ ≡ 19,6 JO │ │ jours calendaires ││
│ │ │ ▾ Voir le détail │ │ ││
│ │ │ ... │ │ Restant ││
│ │ │ │ │ 24,5 ││
│ │ │ [+ Nouvelle demande] │ │ jours calendaires ││
│ │ └───────────────────────┘ └───────────────────────┘│
│ │ │
│ │ ─ Mes demandes ─────────────────────────────────────│
│ │ │
│ │ [🔴 En cours] [📅 À venir (2)] [⏳ En attente (1)] [📋 Passées] ← Tabs│
│ │ │
│ │ ┌──────────┬──────────┬─────────┬──────────┐ │
│ │ │ Type │ Dates │ Jours │ Statut │ │
│ │ ├──────────┼──────────┼─────────┼──────────┤ │
│ │ │🏖 Annuel │15-22/04 │ 6 JC │● Approuvé│ │
│ │ │🤒 Maladie│03-04/04 │ 2 JC │◐ Att.doc │ │
│ │ └──────────┴──────────┴─────────┴──────────┘ │
│ │ │
└─────────────────────────────────────────────────────────────────┘
Component Usage¶
| Element | Component | Props/Variant |
|---|---|---|
| Balance card | Card | padding="spacious" with custom BalanceCard content |
| Balance number | Token | --font-size-display, --font-weight-bold |
| Detail expand | Custom disclosure | Animated height transition, --duration-normal |
| New request button | Button | variant="primary" size="lg" fullWidth (mobile) |
| Request cards | Card | interactive onClick={navigate} |
| Status badges | Badge (new — §9) | variant="success|warning|error|neutral" |
| Upload button | Button | variant="secondary" size="sm" leftIcon="paperclip" |
| History tabs (desktop) | Tabs (new — §9) | 3 tabs with counts |
| History table (desktop) | DataTable (new — §9) | Sortable, clickable rows |
States¶
| State | Behavior |
|---|---|
| Loading | Skeleton shimmer: 1 large card (balance) + 3 medium cards (history). Never bare spinner. |
| Empty (no requests yet) | Balance card shows "0 jours" + EmptyState below: illustration of calendar + "Vous n'avez pas encore de demande de congé" + primary CTA "Faire ma première demande" |
| Empty (new employee <12 months) | Balance card shows accrued amount + amber Banner: "Vos congés seront disponibles à partir du {date}. Vos jours s'accumulent." CTA still shows for permissions exceptionnelles. |
| Offline | All data from last sync displayed. Subtle banner at top: "Hors connexion — dernière synchro : {time}". "Nouvelle demande" button still works (DD-13). Cards with pending sync show pulsing sync icon. |
| Error (API failure) | Toast error: "Impossible de charger vos congés. Réessayez." + Retry button. Cached data shown if available. |
| Success (after request) | Return to this screen with new card at top, brief scale-bounce entrance animation. |
4.2 Leave Request Form¶
Route: /absences/demande (new) or /absences/demande/:id/modifier (edit draft)
Access: Employee (own), HR (on behalf of any employee)
Decisions applied: DD-01 (smart hybrid), DD-06 (half-day), DD-07 (soft warnings), DD-08 (exceptional flow), DD-09 (document upload)
Flow Logic¶
User taps "+ Nouvelle demande"
→ If HR acting on behalf: employee picker shown first (Select searchable)
→ v1.1: After selection, a **persistent "acting on behalf" banner** sticks
to the top of the form throughout the flow (Nielsen's Heuristic #1:
System Status Visibility). Prevents mode errors where HR loses context
of WHO they're creating the request for after scrolling past the picker.
┌──────────────────────────────────┐
│ 👤 Demande pour DIALLO Fatou │ ← sticky banner, bg=primary-subtle
│ Solde: 24,5 JC │ collapses to one line on scroll
└──────────────────────────────────┘
This is critical for CI S1-S2 companies (2-12 employees) where the DG
IS the HR manager — they may create requests for themselves vs. employees,
and the distinction matters legally (DG self-approves, employee needs N+1).
→ Select leave type (Select component)
→ Form adapts based on type:
ANNUAL / UNPAID:
[Type] → [Start date] → [End date] → [Half-day toggles] → [Reason] → [Submit]
= Single scrollable form
SICK:
[Type] → [Start date] → [End date] → [Half-day toggles] → [Document upload zone] → [Submit]
= Single form with prominent upload zone
Note: can submit without doc → status = PENDING_DOCUMENT
EXCEPTIONAL:
Step 1: [Event type cards — large tappable] → [Continue]
Step 2: [Start date (end auto-calc)] → [Document upload] → [Submit]
= 2-step wizard with step indicator
PATERNITY:
[Type auto-selected] → [Start date] → [End date = start + 2 days] → [Birth certificate upload] → [Submit]
= Single form, end date auto-filled
UNJUSTIFIED (HR only):
[Employee] → [Dates] → [Notes] → [Record]
= HR recording form, no approval
Layout — Mobile (360px) — Annual Leave (simple form)¶
┌─────────────────────────────────────┐
│ ← Nouvelle demande │
│ │
│ Type de congé │ ← FormField + Select
│ ┌──────────────────────────────────┐ │
│ │ 🏖 Congé annuel ▾ │ │
│ └──────────────────────────────────┘ │
│ │
│ Solde disponible: 24,5 JC │ ← helper text, color-success
│ │
│ Date de début Date de fin │ ← FormField + DatePicker (new)
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 📅 15/04/2026 │ │ 📅 22/04/2026│ │
│ └───────────────┘ └───────────────┘ │
│ │
│ ☐ Demi-journée début (après-midi) │ ← Checkbox (after dates filled)
│ ☐ Demi-journée fin (matin) │ ← Checkbox
│ │
│ ┌──────────────────────────────────┐ │ ← Card padding=compact
│ │ 📅 6 jours calendaires │ │ bg = primary-subtle
│ │ Solde après demande: 18,5 JC │ │
│ │ (dont 1 jour férié non compté) │ │ ← if holidays overlap
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │ ← Soft warning (DD-07)
│ │ ⚠ Le préavis légal est de │ │ bg = warning-light
│ │ 15 jours avant le début │ │ border-left = warning
│ │ du congé. │ │
│ └──────────────────────────────────┘ │
│ │
│ Motif (optionnel) │ ← FormField + Input (textarea)
│ ┌──────────────────────────────────┐ │
│ │ │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │ ← Button variant=primary
│ │ ✓ Envoyer la demande │ │ size=lg, fullWidth
│ └──────────────────────────────────┘ │
│ │
│ Brouillon sauvegardé automatiquement│ ← caption, tertiary text
└─────────────────────────────────────┘
Layout — Mobile — Permission Exceptionnelle (2-step)¶
Step 1: Event Selection
┌─────────────────────────────────────┐
│ ← Permission exceptionnelle │
│ Étape 1/2 ●───○ │ ← StepIndicator (new — §9)
│ │
│ Quel événement ? │
│ │
│ │ ← v1.1: Reordered by frequency
│ │ (Pareto 80/20 rule) + grief
│ │ sensitivity grouping
│ │
│ ─ Deuil ─────────────────────────── │ ← Subtle group label for death
│ │ events (culturally respectful —
│ ┌──────────────────────────────────┐ │ we're helping an employee in a
│ │ 🕊 Décès du conjoint │ │ moment of grief)
│ │ 5 jours ouvrables │ │ ← Card interactive, 48px+ height
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🕊 Décès d'un enfant │ │
│ │ 5 jours ouvrables │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🕊 Décès père/mère │ │
│ │ 5 jours ouvrables │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Événements heureux ───────────── │ ← Emotional shift: positive events
│ │
│ ┌──────────────────────────────────┐ │
│ │ 👶 Naissance d'un enfant │ │
│ │ 2 jours ouvrables │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 💍 Mariage du salarié │ │
│ │ 4 jours ouvrables │ │
│ └──────────────────────────────────┘ │
│ │
│ ▾ Autres événements │ ← Expand for remaining 4:
│ │ 🕊 Décès frère/sœur (2j)
│ │ 🕊 Décès beau-père/belle-mère (2j)
│ │ 💍 Mariage d'un enfant (2j)
│ │ 🏠 Déménagement (2j)
│ │
│ Restant cette année: 8/10 jours │ ← helper text
└─────────────────────────────────────┘
Step 2: Dates + Document
┌─────────────────────────────────────┐
│ ← Permission exceptionnelle │
│ Étape 2/2 ●───● ✓ │
│ │
│ 💍 Mariage du salarié — 4 jours │ ← Summary of step 1
│ │
│ Date de début │ ← Only start date needed
│ ┌──────────────────────────────────┐ │
│ │ 📅 15/04/2026 │ │
│ └──────────────────────────────────┘ │
│ │
│ Date de fin (calculée) │
│ ┌──────────────────────────────────┐ │ ← Read-only, auto-calculated
│ │ 📅 18/04/2026 🔒 │ │
│ └──────────────────────────────────┘ │
│ Fin calculée: début + 4 jours ouvr. │
│ │
│ Justificatif requis │ ← FormField label
│ ┌──────────────────────────────────┐ │ ← FileUpload zone (new — §9)
│ │ │ │
│ │ 📎 Joindre un document │ │
│ │ Photo, PDF — max 10 Mo │ │
│ │ │ │
│ └──────────────────────────────────┘ │
│ │
│ Motif (optionnel) │
│ ┌──────────────────────────────────┐ │
│ │ │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ ✓ Envoyer la demande │ │
│ └──────────────────────────────────┘ │
│ │
│ Vous pouvez envoyer sans document. │ ← caption: explains PENDING_DOC
│ La demande restera en attente du │
│ justificatif. │
└─────────────────────────────────────┘
Validation Behavior¶
| Rule | Trigger | UI Response |
|---|---|---|
| FR-ABS-040: <12 months service | Type = ANNUAL selected | Hard block: Select resets. Inline error below type field: "Vous devez compléter 12 mois de service avant de prendre un congé annuel. Disponible à partir du {date}." CTA changes to suggest permission exceptionnelle if applicable. |
| FR-ABS-041: Insufficient balance | Days computed > balance | Hard block: Red error below day computation card: "Solde insuffisant. Votre solde disponible est de {balance} jours." |
| FR-ABS-042: Exceptional >10 days/year | Total would exceed 10 | Hard block: Error below event cards: "Vous avez atteint le plafond annuel de 10 jours de permissions exceptionnelles." Shows remaining days. |
| FR-ABS-044: <15 days notice | Start date < today+15 | Soft warning: Amber banner below date fields (DD-07). Form still submittable. |
| FR-ABS-045: No 14-day block yet | Annual leave + no 14-day block this year | Soft warning: Amber banner below date fields. |
| FR-ABS-046: Overlap with approved | Dates overlap existing leave | Context-dependent: If new=SICK/MATERNITY over ANNUAL → info banner explaining auto-split. If same priority → hard block: "Vous avez déjà un congé approuvé sur cette période. Veuillez l'annuler d'abord." |
| FR-ABS-043: Retroactive | Start date < today | Soft flag: Info text near date: "Demande rétroactive — sera signalée dans l'audit." Not blocked. |
Component Usage¶
| Element | Component | Props/Variant |
|---|---|---|
| On-behalf banner (HR only) | Custom OnBehalfBanner | employeeName balance — sticky, bg=--color-primary-subtle, collapses on scroll. v1.1 addition. |
| Leave type picker | Select | options={leaveTypes} placeholder="Choisir un type" |
| Date fields | DatePicker (new — §9) | locale="fr" format="DD/MM/YYYY" min={today or past for retro} |
| Half-day checkboxes | Checkbox | label="Demi-journée début (après-midi)" |
| Day computation card | Card | padding="compact" custom bg --color-primary-subtle |
| Warning banners | Custom InlineBanner | variant="warning|error|info" with icon |
| Event type cards | Card | interactive onClick — large touch target |
| File upload | FileUpload (new — §9) | accept="image/*,.pdf" maxSize={10MB} |
| Step indicator | StepIndicator (new — §9) | steps={2} current={1} |
| Submit button | Button | variant="primary" size="lg" fullWidth |
| Reason field | FormField + Input | type="textarea" label="Motif (optionnel)" |
Offline Behavior¶
- Form works identically offline. All fields, validation (client-side only), and submission.
- On submit offline: saved to IndexedDB as DRAFT. Toast: "Demande enregistrée — sera envoyée automatiquement."
- Document upload offline: file stored in IndexedDB blob. Uploaded on sync.
- Auto-save: every field change is persisted to IndexedDB. Form survives app restart, tab close, or crash.
4.3 Leave Request Detail¶
Route: /absences/demande/:id
Access: Request owner, assigned approver, HR, DG
Layout — Mobile¶
┌─────────────────────────────────────┐
│ ← Détail de la demande │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🏖 Congé annuel │ │ ← Card header
│ │ ┌─────────────────┐ │ │
│ │ │ ● Approuvé │ │ │ ← Badge
│ │ └─────────────────┘ │ │
│ │ │ │
│ │ Du 15/04/2026 │ │ ← Label + value pairs
│ │ Au 22/04/2026 │ │
│ │ Durée 6 jours calendaires │ │
│ │ Demi-j. Non │ │
│ │ │ │
│ │ Motif Vacances en famille │ │
│ │ │ │
│ │ Soumise le 01/04/2026 │ │
│ │ Approuvée 02/04/2026 │ │
│ │ Par KONÉ Amara │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Historique ───────────────────────│
│ │
│ 01/04 09:12 Demande soumise │ ← Timeline component
│ ● │
│ 02/04 14:30 Approuvée par KONÉ A. │
│ ● │
│ │
│ ┌──────────────────────────────────┐ │ ← Conditional: only if
│ │ Annuler le congé │ │ status allows (DD-11)
│ └──────────────────────────────────┘ │ ← Button variant=danger
│ │ size=lg, fullWidth
└─────────────────────────────────────┘
Conditional sections: - PENDING_DOCUMENT: shows upload zone prominently at top with amber background - REJECTED: shows rejection reason in a red-tinted Card - IN_PROGRESS: shows "Contactez les RH pour un retour anticipé" info banner - Offline-created (DRAFT, syncing): shows sync status indicator with pulsing animation
4.4 Manager — Pending Approvals¶
Route: /absences/approbations
Access: Manager (direct reports), HR (all), DG (all)
Decisions applied: DD-05 (approval UX)
Layout — Mobile¶
┌─────────────────────────────────────┐
│ Approbations (3) │ ← Count badge
│ │
│ ┌──────────────────────────────────┐ │
│ │ DIALLO Fatou │ │ ← Card interactive
│ │ 🏖 Congé annuel │ │
│ │ 15/04 → 22/04 · 6 JC │ │
│ │ Solde: 24,5 JC │ │
│ │ Soumise il y a 2h │ │
│ │ │ │
│ │ [Approuver ✓] [Rejeter ✗] │ │ ← Button primary + ghost
│ │ │ │
│ │ ← Swipe → pour décider │ │ ← hint text, first time only
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ TOURÉ Moussa │ │
│ │ 🤒 Congé maladie │ │
│ │ 03/04 → 04/04 · 2 JC │ │
│ │ 📎 Certificat joint │ │
│ │ Soumise il y a 1j │ │
│ │ │ │
│ │ [Approuver ✓] [Rejeter ✗] │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ YAO Kouadio │ │
│ │ 🏖 Congé annuel │ │
│ │ 10/04 → 11/04 · 2 JC │ │
│ │ Solde: 12 JC │ │
│ │ │ │
│ │ ⚠ 📎 En attente du justificatif│ │ ← If doc missing
│ │ [Approuver] (désactivé) │ │ ← disabled state
│ └──────────────────────────────────┘ │
│ │
│ ─ Traitées récemment ──────────────│
│ │
│ ┌──────────────────────────────────┐ │ ← Muted card
│ │ BAMBA Seydou · Annuel · 5 JC │ │
│ │ ● Approuvé le 10/03 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────┘
Swipe Gesture (Mobile) — v1.1: With Undo Confirmation¶
- Swipe right → green background reveals "✓ Approuver" → on release past threshold:
- Floating undo bar appears at bottom (2-second countdown):
- If user taps "Annuler" → card restores with reverse slide animation, no action taken
- If countdown expires → action commits (LeaveApproved event fires)
- Why: Approving leave is a legal HR action (deducts balance, notifies payroll). On 360px, 40% threshold = ~144px — easily triggered by a scroll misalignment. The undo bar is faster than a confirmation modal but still provides error recovery.
- Swipe left → red background reveals "✗ Rejeter" → release opens BottomSheet for reason (no change — rejection already has a confirmation step via mandatory reason input)
- Swipe hint shown only on first visit (persisted in localStorage)
- Swipe threshold: 40% of card width to commit (prevents accidental)
- If document required but missing: swipe right disabled, card shows lock icon
Keyboard Navigation (Desktop) — v1.1 Addition¶
For Enterprise readiness and accessibility (also benefits Papillon desktop users):
- Tab through approval cards (each card is focusable)
- Enter on focused card = open detail view
- A key on focused card = approve (triggers same undo confirmation as swipe)
- R key on focused card = reject (opens Modal for reason)
- Shortcuts only active when approval list has focus (not global — prevents conflicts)
- Visual: focused card shows --shadow-focus ring
Desktop Layout¶
On desktop (1024px+), approval list becomes a DataTable with inline action buttons:
┌──────────────────────────────────────────────────────────────┐
│ Approbations en attente (3) │
│ │
│ ┌───────┬──────────┬────────┬───────┬────────┬─────────────┐ │
│ │ Nom │ Type │ Dates │ Jours │ Solde │ Actions │ │
│ ├───────┼──────────┼────────┼───────┼────────┼─────────────┤ │
│ │DIALLO │🏖 Annuel │15-22/04│ 6 JC │ 24,5 │[✓] [✗] [→]│ │
│ │TOURÉ │🤒 Maladie│03-04/04│ 2 JC │ — │[✓] [✗] [→]│ │
│ │YAO │🏖 Annuel │10-11/04│ 2 JC │ 12 │[🔒] [✗] [→]│ │
│ └───────┴──────────┴────────┴───────┴────────┴─────────────┘ │
│ │
│ [→] = Voir détail [🔒] = En attente justificatif │
└──────────────────────────────────────────────────────────────┘
4.5 Approval Detail & Action¶
Route: /absences/approbations/:id
Access: Assigned approver, HR, DG
Layout — Mobile¶
┌─────────────────────────────────────┐
│ ← Demande de DIALLO Fatou │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 🏖 Congé annuel │ │
│ │ │ │
│ │ Du 15/04/2026 │ │
│ │ Au 22/04/2026 │ │
│ │ Durée 6 jours calendaires │ │
│ │ │ │
│ │ Solde actuel 24,5 JC │ │
│ │ Solde après 18,5 JC │ │
│ │ │ │
│ │ Motif: Vacances en famille │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │ ← Card: mini team calendar
│ │ 📅 Calendrier équipe (15-22/04) │ │ (embedded timeline)
│ │ │ │
│ │ 15 16 17 18 19 20 21 22 │ │
│ │ DIALLO ██ ██ ██ ██ ██ ░░ ░░ ██ │ │ ← the request
│ │ TOURÉ ░░ ░░ ░░ ░░ ░░ ░░ ░░ ░░ │ │
│ │ BAMBA ░░ ░░ ██ ██ ░░ ░░ ░░ ░░ │ │ ← other team absences
│ │ YAO ░░ ░░ ░░ ░░ ░░ ░░ ░░ ░░ │ │
│ │ │ │
│ │ 1 autre personne absente │ │ ← coverage summary
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 📎 Justificatif: certificat.pdf │ │ ← If document present
│ │ (téléchargé le 01/04) │ │ Only visible to HR/DG
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │ ← Sticky action bar
│ │ [ Rejeter ] [✓ Approuver ] │ │ Button ghost + primary
│ └──────────────────────────────────┘ │
└─────────────────────────────────────┘
Reject Flow: Tap "Rejeter" → BottomSheet slides up:
┌─────────────────────────────────────┐
│ ═══════ (drag handle) │ ← BottomSheet
│ │
│ Motif du refus │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Expliquez la raison du refus... │ │ ← Input type=textarea
│ │ │ │ required
│ │ │ │
│ └──────────────────────────────────┘ │
│ │
│ L'employé recevra ce motif dans │ ← helper text
│ sa notification. │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Confirmer le refus │ │ ← Button variant=danger
│ └──────────────────────────────────┘ │ disabled until text entered
│ │
│ ┌──────────────────────────────────┐ │
│ │ Annuler │ │ ← Button variant=ghost
│ └──────────────────────────────────┘ │
└─────────────────────────────────────┘
4.6 Team Calendar¶
Route: /absences/calendrier
Access: Manager (direct reports), Employee (presence only, no leave type), HR/DG (all, with filters)
Decisions applied: DD-02 (timeline/Gantt)
Layout — Mobile (360px)¶
┌─────────────────────────────────────┐
│ Calendrier d'équipe │
│ │
│ ┌──────────────────────────────────┐ │
│ │ ◀ Mars 2026 ▶ │ │ ← Month nav
│ └──────────────────────────────────┘ │
│ │
│ [Département ▾] (HR/DG only) │ ← Select filter
│ │
│ ← scroll → │
│ │
│ [Aujourd'hui] │ ← v1.1: Snap-back chip. Appears
│ │ when user scrolls away from
│ ↓ today │ today. Tapping snaps view back.
│ 10 11 12 13 14 15 16 │ ← Day headers (scrollable)
│ Lu Ma Me Je Ve Sa Di │ Rolling 7-day window centered
│ ┌──────────────────────────────────┐ │ on today (not static month)
│ │ 🟤 ░░ ░░ ░░ ░░ ░░ ·· ·· │ │ ← v1.1: 3-char abbreviation +
│ │KON │ │ colored avatar circle instead
│ │ 🟢 ░░ ██ ██ ██ ░░ ·· ·· │ │ of full "KONÉ-BAMBA" (doesn't
│ │DIA │ │ fit at 360px). Tap → BottomSheet
│ │ 🔵 ░░ ░░ ░░ ░░ ██ ·· ·· │ │ with full name + details.
│ │TOU │ │
│ │ 🟡 ░░ ░░ ░░ ░░ ░░ ·· ·· │ │ ← ·· = weekend (compressed width,
│ │BAM │ │ dimmed bg)
│ │ 🟣 ██ ██ ░░ ░░ ░░ ·· ·· │ │ ← ██ = Absent, ░░ = Present
│ │YAO │ │
│ └──────────────────────────────────┘ │
│ │
│ Légende: │
│ ██ Absent ░░ Présent 🔴 Aujourd'hui│
│ │
│ Tap un nom pour voir les détails │ ← hint
│ │
│ ─ Résumé ───────────────────────────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ 📊 Taux d'absence: 12% │ │
│ │ 👥 Absents aujourd'hui: 1/5 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────┘
Layout — Desktop (1024px+)¶
┌─────────────────────────────────────────────────────────────────┐
│ Sidebar │ Calendrier d'équipe │
│ │ │
│ │ ◀ Mars 2026 ▶ [Département ▾] [Exporter CSV ↓] │
│ │ │
│ │ 01 02 03 04 05 06 07 08 09 10 11 12 13 ... 31 │
│ │ Lu Ma Me Je Ve Sa Di Lu Ma Me Je Ve Sa Ma │
│ │ ┌─────────────────────────────────────────────────────┐│
│ │ │ KONÉ A. ░░ ░░ ░░ ░░ ░░ ·· ·· ░░ ░░ ░░ ░░ ░░ ││
│ │ │ DIALLO F. ░░ ░░ ░░ ░░ ░░ ·· ·· ░░ ░░ ░░ ██ ██ ││
│ │ │ TOURÉ M. ░░ ░░ ░░ ░░ ░░ ·· ·· ░░ ░░ ░░ ░░ ░░ ││
│ │ │ BAMBA S. ██ ██ ██ ██ ██ ·· ·· ░░ ░░ ░░ ░░ ░░ ││
│ │ │ YAO K. ░░ ░░ ░░ ░░ ░░ ·· ·· ░░ ░░ ██ ██ ░░ ││
│ │ │ CISSÉ D. ░░ ░░ ░░ ░░ ░░ ·· ·· ░░ ░░ ░░ ░░ ░░ ││
│ │ └─────────────────────────────────────────────────────┘│
│ │ │
│ │ ██ Absent ░░ Présent ·· Weekend 🟡 Jour férié │
│ │ 🔴 = Aujourd'hui (column highlight) │
│ │ │
│ │ Hover sur barre → tooltip: "DIALLO F. — Absent 11-13" │
│ │ Click nom → navigate to employee balance (HR only) │
└─────────────────────────────────────────────────────────────────┘
Privacy Rules¶
| Viewer Role | Sees |
|---|---|
| EMPLOYEE | Own team members: "Absent" or "Présent" only. No leave type, no dates detail. |
| MANAGER | Direct reports: "Absent" (colored bar), date range on hover/tap. No leave type. |
| HR_MANAGER / DG | All employees: "Absent" with leave type visible on hover/tap. Can filter by department. |
Interaction¶
- Tap on name (mobile): BottomSheet with person's absences for the visible month
- Tap on absence bar (mobile): BottomSheet with absence dates
- Hover on bar (desktop): Tooltip with dates + leave type (if permitted)
- Click name (desktop, HR only): navigate to employee balance detail (S8)
- Swipe horizontally (mobile): scroll timeline. Today column is highlighted with
--color-primary-lightbackground. - Month navigation: ◀ ▶ arrows. Swipe on calendar area does NOT change month (reserved for horizontal day scroll).
4.7 HR Dashboard¶
Route: /absences/tableau-de-bord
Access: HR_MANAGER, DG
Decisions applied: DD-12 (dashboard layout)
Layout — Mobile (360px)¶
┌─────────────────────────────────────┐
│ Tableau de bord — Absences │
│ │
│ ┌──────────────────────────────────┐ │
│ │ ⏳ En attente │ │ ← KPI Card, tappable
│ │ 3 │ │ font-size-display
│ │ demandes à traiter │ │ bg = warning-light
│ └──────────────────────────────────┘ │
│ │
│ ┌───────────────┐ ┌───────────────┐ │ ← 2-column grid
│ │ 📊 Taux │ │ 🏖 Absents │ │
│ │ 8,5% │ │ 4 │ │
│ │ d'absence │ │ aujourd'hui │ │
│ └───────────────┘ └───────────────┘ │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 📅 Congés │ │ ⚠ Soldes bas │ │
│ │ 7 │ │ 2 │ │
│ │ cette sem. │ │ employés │ │
│ └───────────────┘ └───────────────┘ │
│ │
│ ─ Demandes récentes ────────────────│
│ │
│ ┌──────────────────────────────────┐ │ ← Approval card (compact)
│ │ DIALLO F. · Annuel · 6 JC │ │
│ │ 15-22/04 · il y a 2h │ │
│ │ [Approuver ✓] [Voir →] │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ TOURÉ M. · Maladie · 2 JC │ │
│ │ 03-04/04 · il y a 1j │ │
│ │ [Approuver ✓] [Voir →] │ │
│ └──────────────────────────────────┘ │
│ │
│ Voir toutes les approbations → │
│ │
│ ─ Calendrier (2 semaines) ──────────│
│ │
│ ┌──────────────────────────────────┐ │ ← Mini timeline (2 weeks)
│ │ 10 11 12 13 14 .. 23 24 │ │
│ │ KON ░░ ░░ ░░ ░░ ░░ ░░ ░░ │ │
│ │ DIA ░░ ██ ██ ██ ░░ ░░ ░░ │ │
│ │ TOU ░░ ░░ ░░ ░░ ░░ ██ ██ │ │
│ └──────────────────────────────────┘ │
│ │
│ Voir le calendrier complet → │
└─────────────────────────────────────┘
Layout — Desktop (1024px+)¶
Desktop uses a 3-column layout:
┌─────────────────────────────────────────────────────────────────┐
│ Sidebar │ Tableau de bord — Absences │
│ │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ← 4 KPI cards │
│ │ │⏳ 3 │ │📊8,5%│ │🏖 4 │ │⚠ 2 │ │
│ │ │En att│ │Taux │ │Abs. │ │Soldes│ │
│ │ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │ │
│ │ ┌─── Demandes en attente ──┐ ┌── Calendrier (2 sem)──┐│
│ │ │ (Approval cards list) │ │ (Mini timeline) ││
│ │ │ DIALLO F. · 6 JC · 2h │ │ 10 11 12 13 ... ││
│ │ │ [✓ Approuver] [→ Voir] │ │ K ░░ ░░ ░░ ░░ ││
│ │ │ │ │ D ░░ ██ ██ ██ ││
│ │ │ TOURÉ M. · 2 JC · 1j │ │ T ░░ ░░ ░░ ░░ ││
│ │ │ [✓ Approuver] [→ Voir] │ │ B ░░ ░░ ██ ██ ││
│ │ │ │ │ ││
│ │ │ Voir toutes → │ │ Voir complet → ││
│ │ └──────────────────────────┘ └────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
4.8 HR — Employee Balance Detail¶
Route: /absences/employes/:id/solde
Access: HR_MANAGER, DG
Layout — Mobile¶
┌─────────────────────────────────────┐
│ ← Solde de DIALLO Fatou │
│ │
│ ┌──────────────────────────────────┐ │
│ │ 👤 DIALLO Fatou │ │
│ │ Embauchée: 15/03/2022 │ │
│ │ Ancienneté: 4 ans │ │
│ │ Catégorie: Employé │ │
│ │ Enfants < 21 ans: 2 │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Congé annuel ─────────────────────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ 24,5 JC │ │ ← display size
│ │ Solde disponible │ │
│ │ │ │
│ │ Acquis base : 27,5 JC │ │
│ │ + Ancienneté : +0 JC │ │ ← 4 ans < seuil 5 ans
│ │ + Enfants (2) : +2,5 JC │ │
│ │ − Pris : −5,5 JC │ │
│ │ ───────────────────── │ │
│ │ = Disponible : 24,5 JC │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Permissions exceptionnelles ──────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ Utilisées: 2 / 10 jours │ │ ← Progress bar
│ │ ████░░░░░░░░░░░░░░░░ 20% │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Congé maladie (12 mois glissants)─│
│ │
│ ┌──────────────────────────────────┐ │
│ │ Jours pris: 5 │ │
│ │ Phase actuelle: Plein salaire │ │ ← Badge variant=success
│ │ Reste avant demi-salaire: 25j │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Grand livre (ledger) ─────────────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ 01/03 MONTHLY_ACCRUAL +2,75 JC│ │ ← Ledger entries
│ │ 15/02 LEAVE_DEBIT −5,5 JC │ │
│ │ 01/02 MONTHLY_ACCRUAL +2,75 JC│ │
│ │ 01/01 SENIORITY_BONUS +0 JC │ │
│ │ 01/01 CHILDREN_BONUS +2,5 JC │ │
│ │ 01/01 CARRY_OVER +15 JC │ │
│ │ │ │
│ │ Voir tout → │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Ajuster le solde │ │ ← Button variant=secondary
│ └──────────────────────────────────┘ │
└─────────────────────────────────────┘
4.9 HR — Maternity Declaration¶
Route: /absences/maternite/nouvelle
Access: HR_MANAGER, DG
Decision applied: DD-10 (separate flow)
Layout — Mobile¶
┌─────────────────────────────────────┐
│ ← Déclaration de maternité │
│ │
│ ℹ️ La maternité est un droit légal. │ ← Info banner, bg=info-light
│ Ce formulaire crée automatiquement │
│ un congé de 14 semaines. │
│ │
│ Employée │ ← FormField + Select searchable
│ ┌──────────────────────────────────┐ │
│ │ 🔍 Rechercher une employée... ▾ │ │
│ └──────────────────────────────────┘ │
│ │
│ Date présumée d'accouchement │ ← FormField + DatePicker
│ ┌──────────────────────────────────┐ │
│ │ 📅 JJ/MM/AAAA │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │ ← Auto-calculated summary
│ │ 📋 Résumé du congé │ │ Card bg=primary-subtle
│ │ │ │
│ │ Début: 04/05/2026 (DPA − 42j) │ │
│ │ Fin: 13/08/2026 (DPA + 56j) │ │
│ │ Durée: 14 semaines (98 jours) │ │
│ │ │ │
│ │ ⚠ 3 jours de congé annuel │ │ ← if overlap detected
│ │ approuvé seront recrédités │ │
│ │ automatiquement. │ │
│ └──────────────────────────────────┘ │
│ │
│ Attestation médicale │ ← FormField + FileUpload
│ ┌──────────────────────────────────┐ │
│ │ 📎 Joindre l'attestation │ │ ← Required
│ │ Photo, PDF — max 10 Mo │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ ✓ Enregistrer la déclaration │ │ ← Button primary, lg, fullWidth
│ └──────────────────────────────────┘ │
│ │
│ L'employée et son manager seront │
│ notifiés automatiquement. │
└─────────────────────────────────────┘
4.10 HR — Manual Balance Adjustment¶
Route: /absences/employes/:id/ajustement
Access: HR_MANAGER, DG
┌─────────────────────────────────────┐
│ ← Ajustement de solde │
│ DIALLO Fatou │
│ │
│ Solde actuel: 24,5 JC │
│ │
│ Type d'ajustement │
│ ┌──────────────────────────────────┐ │
│ │ ○ Ajouter des jours │ │ ← RadioGroup
│ │ ○ Retirer des jours │ │
│ └──────────────────────────────────┘ │
│ │
│ Nombre de jours (calendaires) │
│ ┌──────────────────────────────────┐ │
│ │ 0 │ │ ← Input type=number
│ └──────────────────────────────────┘ │
│ │
│ Nouveau solde: 24,5 JC │ ← Live calculation
│ │
│ Motif (obligatoire) │
│ ┌──────────────────────────────────┐ │
│ │ Expliquez la raison... │ │ ← Input type=textarea
│ │ │ │ required
│ └──────────────────────────────────┘ │
│ │
│ ⚠ Cet ajustement sera enregistré │ ← warning banner
│ dans l'historique d'audit. │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Confirmer l'ajustement │ │ ← Button primary
│ └──────────────────────────────────┘ │
└─────────────────────────────────────┘
4.11 Settings — Leave Configuration¶
Route: /absences/parametres/types
Access: HR_MANAGER, DG
Layout — Mobile¶
┌─────────────────────────────────────┐
│ ← Paramètres des congés │
│ │
│ ─ Politique générale ───────────────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ Unité de décompte │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ Jours calendaires ▾ │ │ │ ← Select
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ Période de référence │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ Année civile (jan-déc) ▾ │ │ │ ← Select
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ Report de congés │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ Expiration stricte (loi) ▾│ │ │ ← Select
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Enregistrer │ │ │ ← Button primary
│ │ └──────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Permissions exceptionnelles ──────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ 💍 Mariage du salarié │ │ ← Card for each event type
│ │ Durée: [4] jours (min: 4) │ │ Input inline
│ │ │ │
│ │ 👶 Naissance d'un enfant │ │
│ │ Durée: [2] jours (min: 2) │ │
│ │ │ │
│ │ 🕊 Décès du conjoint │ │
│ │ Durée: [5] jours (min: 5) │ │
│ │ │ │
│ │ ... (all 9 event types) │ │
│ │ │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Enregistrer │ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ ℹ️ Les durées minimales sont fixées │ ← Info text
│ par la CCI. Vous pouvez les │
│ augmenter mais pas les réduire. │
└─────────────────────────────────────┘
Validation: If HR enters a value below CCI minimum → inline error: "La durée minimale légale (CCI) pour cet événement est de {min} jours." Input reverts to minimum on blur.
4.12 Settings — Public Holidays¶
Route: /absences/parametres/jours-feries
Access: HR_MANAGER, DG
┌─────────────────────────────────────┐
│ ← Jours fériés — 2026 │
│ [◀ 2025] [2027 ▶]│
│ │
│ ─ Jours fériés fixes ──────────────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ 01/01 Jour de l'An 🔒 │ │ ← 🔒 = non modifiable
│ │ 01/05 Fête du Travail 🔒 │ │
│ │ 07/08 Fête de l'Indépendance🔒 │ │
│ │ 15/08 Assomption 🔒 │ │
│ │ 01/11 Toussaint 🔒 │ │
│ │ 15/11 Journée de la Paix 🔒 │ │
│ │ 25/12 Noël 🔒 │ │
│ └──────────────────────────────────┘ │
│ │
│ ─ Jours fériés variables ──────────│
│ │
│ ┌──────────────────────────────────┐ │
│ │ Lundi de Pâques │ │
│ │ ┌────────────────────┐ │ │
│ │ │ 📅 06/04/2026 │ ✏️ │ │ ← Editable DatePicker
│ │ └────────────────────┘ │ │
│ │ Défaut plateforme: 06/04/2026 │ │ ← helper: shows platform default
│ │ │ │
│ │ Aïd el-Fitr │ │
│ │ ┌────────────────────┐ │ │
│ │ │ 📅 30/03/2026 │ ✏️ │ │
│ │ └────────────────────┘ │ │
│ │ Défaut plateforme: 30/03/2026 │ │
│ │ │ │
│ │ ... (all variable holidays) │ │
│ │ │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Enregistrer │ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
│ │
│ ℹ️ Les jours fériés fixes sont │
│ définis par la loi. Vous pouvez │
│ modifier les dates des fêtes │
│ religieuses variables. │
└─────────────────────────────────────┘
5. Responsive Behavior¶
| Breakpoint | Width | Behavior |
|---|---|---|
| Mobile | 360–767px | Single column. Bottom nav. Cards stacked. Team calendar scrolls horizontally. Action buttons fullWidth. Balance card full-bleed. Tabs scroll horizontally if needed. |
| Tablet | 768–1023px | 2-column grid for KPI cards. Balance + stats side by side. Team calendar shows 2 weeks visible. Approval cards in 2-column grid. |
| Desktop | 1024px+ | Sidebar visible. 3-4 column KPI grid. DataTable replaces cards for approvals and history. Team calendar shows full month. Approval detail shows calendar inline (not in separate section). Modal replaces BottomSheet for rejection reason. |
Key responsive transformations:¶
| Element | Mobile | Desktop |
|---|---|---|
| Leave request form | Full-screen page | Modal (600px wide) or side panel |
| Approval action | Swipe gesture + buttons | Inline buttons in DataTable row |
| Rejection reason | BottomSheet | Modal |
| Request history | Card list | DataTable with sort/filter |
| Team calendar | Horizontal scroll, 7 days visible | Full month, all 31 days |
| Balance breakdown | Expandable section | Always visible in sidebar card |
| Navigation | Bottom tabs (2-4 role-based) | Sidebar + in-module tabs |
6. Offline Behavior¶
What works offline¶
| Feature | Offline Capability | Sync Behavior |
|---|---|---|
| View balance | ✅ Cached from last sync | Refreshes on reconnect |
| View request history | ✅ Cached | Refreshes on reconnect |
| Create leave request | ✅ Full form, saves as DRAFT | Auto-submits on reconnect |
| Upload document | ✅ Stored in IndexedDB blob | Uploaded on reconnect |
| Cancel pending request | ✅ Queued | Executes on reconnect |
| View team calendar | ✅ Cached (may be stale) | Refreshes on reconnect |
| Approve/reject | ❌ Online only | N/A — manager action requires server validation |
| HR configuration | ❌ Online only | N/A — admin settings require server |
| Maternity declaration | ❌ Online only | N/A — creates auto-approved leave |
Offline indicators¶
- Global: Host shell sync indicator (green/amber/pulsing per host-shell-design.md §8)
- Module-level: Subtle banner at top of content area: "Hors connexion — dernière synchro : il y a {duration}"
- Background:
--color-bg-tertiary - Text:
--color-text-secondary - NOT alarming — just informational
- Per-card: Requests created offline show pulsing sync icon (⟳) using
--color-sync-pendingwith--duration-sync-pulseanimation - Stale data: After 24h without sync, banner changes to amber: "Données potentiellement obsolètes — connectez-vous pour mettre à jour"
Conflict resolution¶
Conflicts are rare (employee creates request offline, balance changes server-side before sync). Resolution:
- On sync, server validates the queued request
- If validation fails (e.g., insufficient balance now): request stays as DRAFT with error flag
- User sees notification: "Votre demande de congé n'a pas pu être envoyée : {raison}"
- Card in "Mes congés" shows error state with "Modifier" button
- User can edit dates/type and resubmit
No side-by-side merge UI needed — leave requests are atomic (not field-level conflicts).
Prefetch strategy¶
When online, prefetch: - Own balance + history (always) - Team calendar for current + next month (managers) - Pending approvals list (managers) — but approval actions are online-only - Public holidays for current year
7. Motion Choreography¶
Screen entrances¶
| Screen | Animation |
|---|---|
| Employee Home (S1) | Balance card slides up (50ms) → request cards stagger in (50ms each) |
| Leave Request Form (S2) | Slide up from bottom with fade, --duration-slow |
| Approval List (S4) | Cards stagger in from left, 60ms apart |
| Team Calendar (S6) | Calendar grid fades in, then bars animate width from 0 to full, 80ms stagger per row |
| HR Dashboard (S7) | KPI cards scale from 0.95→1 with stagger (50ms), then content sections fade in |
Interactions¶
| Action | Animation |
|---|---|
| Submit leave request | Button shows loading spinner → success check animation (scale bounce from 0→1.2→1) → navigate back with slide-down |
| Approve leave | Card background flashes --color-success-light → card slides out right → next card slides up |
| Reject leave | BottomSheet slides up (--easing-enter) → on confirm, card background flashes --color-error-light → card slides out left |
| Swipe approve (mobile) | Green background revealed progressively → on release past threshold: haptic feedback (if available) + floating undo bar slides up from bottom (--easing-enter, --duration-normal) with 2-second progress bar shrinking. If undo tapped: bar slides down + card restores with reverse slide. If countdown expires: card slides out right. |
| Swipe reject (mobile) | Red background revealed → on release: BottomSheet slides up for reason |
| Cancel leave | Undo toast slides in from bottom → 8s countdown bar → on undo: card restores with reverse animation |
| Balance expand | Height animates from 0 to auto, --duration-normal, --easing-enter |
| Maternity save | Confetti burst (subtle, 1.5s) + success check + Toast |
Loading states¶
| Screen | Skeleton |
|---|---|
| Employee Home | 1 large shimmer rectangle (balance) + 3 card-shaped shimmer rectangles |
| Approval List | 3 card-shaped shimmer rectangles |
| Team Calendar | Grid of shimmer cells matching timeline layout |
| HR Dashboard | 4 small shimmer rectangles (KPIs) + 2 medium rectangles (lists) |
All skeletons use shimmer animation (gradient sweep left-to-right), --duration-slow, on --color-bg-tertiary base.
8. Loading, Empty, Error & Success States¶
Empty States¶
| Screen | Illustration | Message | CTA |
|---|---|---|---|
| Mes congés (no requests) | Calendar with sunshine | "Vous n'avez pas encore de demande de congé" | "Faire ma première demande" → S2 |
| Approbations (none pending) | Checkmark in circle | "Toutes les demandes ont été traitées !" | "Voir le calendrier d'équipe" → S6 |
| Team calendar (no absences this month) | People at desks | "Toute l'équipe est présente ce mois-ci" | "◀ Mois précédent" |
| HR Dashboard (new tenant, no data) | Rocket launching | "Bienvenue dans la gestion des absences" | v1.1: Contextual onboarding checklist card (see below) instead of a single CTA |
v1.1 Enhancement — HR First-Time Onboarding Card
When HR lands on the Absence module for the first time (new tenant, no configuration), the empty state shows a guided setup checklist instead of just "Configurer les paramètres" (which is meaningless without context — violates Nielsen's Heuristic #6: Recognition over Recall).
┌──────────────────────────────────┐
│ 🚀 Préparez votre gestion │ ← Card padding=spacious
│ des absences │ bg = warm gradient (subtle)
│ │
│ ✓ 1. Vérifier les jours fériés │ ← completed = green check + strikethrough
│ → /absences/parametres/ │ link to S12
│ jours-feries │
│ │
│ ○ 2. Configurer les types │ ← current step = highlighted,
│ de congé │ primary border-left
│ → /absences/parametres/types │ link to S11
│ │
│ ○ 3. Inviter vos employés │ ← future = dimmed text
│ → module Core HR │ link to Core HR module
│ │
│ ┌──────────────────────────────┐ │
│ │ Continuer l'installation → │ │ ← Button primary, points to
│ └──────────────────────────────┘ │ current incomplete step
│ │
│ Ignorer pour l'instant │ ← ghost link, dismisses card
└──────────────────────────────────┘ (stores in localStorage)
Behavior: - Stored in localStorage (UX hint, not business data — doesn't sync) - Each step links to the relevant settings page - Completing a step auto-checks it on return (checks if holidays exist, leave types configured, employees invited) - "Ignorer" dismisses permanently - Completing all 3 steps triggers a success celebration (confetti + "Vous êtes prêt !") - Card survives page refresh but NOT app uninstall (localStorage)
Why this matters: Papillon's philosophy is "Votre assistant RH de confiance." A trusted assistant doesn't hand you a manual — it guides you through setup step by step. For HR managers transitioning from Excel/paper in CI, the difference between "here's a button" and "here are 3 clear steps" is the difference between adoption and abandonment.
Empty state component: EmptyState (new — §9) with illustration, title, description, actionLabel, onAction props.
Error States¶
| Scenario | UI |
|---|---|
| API failure (list load) | Toast error + retry button. If cached data exists, show it with stale banner. |
| API failure (form submit, online) | Toast: "Impossible d'envoyer la demande. Réessayez." Button re-enabled. Form data preserved. |
| Validation error (server-side) | Inline field errors (red) below relevant fields. Scroll to first error. |
| Sync failure (offline request) | Card shows error badge + "Modifier" CTA. Notification explains reason. |
| Permission denied | Redirect to module home with Toast: "Vous n'avez pas accès à cette page." |
Success States¶
| Action | Feedback |
|---|---|
| Request submitted | Check animation + Toast: "Demande envoyée !" + navigate to S1 |
| Request submitted offline | Sync icon + Toast: "Demande enregistrée — sera envoyée automatiquement" |
| Leave approved | Check animation + card slides out + Toast: "Congé approuvé" |
| Leave rejected | Card slides out + Toast: "Demande rejetée" |
| Leave cancelled | Undo toast (8s) + card fades out after undo window |
| Maternity declared | Confetti + Toast: "Déclaration enregistrée — congé créé automatiquement" |
| Settings saved | Toast: "Paramètres enregistrés" |
| Balance adjusted | Toast: "Solde ajusté — {+/-X} jours" |
Edge Cases — v1.1 Addition¶
These specifications prevent implementation ambiguity for non-obvious scenarios.
Concurrent Request Prevention¶
Scenario: Employee opens two browser tabs and submits the same leave request twice.
| Step | UI Behavior |
|---|---|
| First submit click | Button enters loading state (disabled + spinner). Form fields become read-only. |
| Server returns 201 | Normal success flow. |
| Second tab submits same dates | Server returns 409 Conflict. |
| 409 handling | Toast: "Cette demande a déjà été envoyée." Button re-enables. Navigate to request detail. |
Implementation note: submit button MUST disable on first click. Client-side dedup via requestId (UUID generated on form mount, sent with POST, stored in IndexedDB for offline).
Long Absence on Team Calendar¶
Scenario: Employee has 14-week maternity leave — the bar extends across multiple visible months.
| View | Behavior |
|---|---|
| Mobile (7-day window) | Bar extends to right edge of visible area with a "→" indicator showing it continues. Tap bar → BottomSheet shows full date range. |
| Desktop (full month) | Bar extends to right edge with "→". On month change, bar appears from left edge with "←" indicator. Hover shows tooltip: "DIALLO F. — Congé maternité — 04/05 au 13/08/2026". |
Balance = Exactly 0 Days¶
Scenario: Employee's annual leave balance is exactly 0 (not negative, which shouldn't happen).
| Element | Behavior |
|---|---|
| Balance hero number | Display "0" in --color-text-secondary (not primary amber — 0 is a valid but neutral state). NOT error red (0 is legal). |
| Balance breakdown | Still expandable, shows full computation. |
| "Nouvelle demande" CTA | Still visible and enabled. Employee may want to request congé sans solde or permission exceptionnelle (separate balance). |
| Inline helper | Below balance: "Solde épuisé. Vous pouvez demander un congé sans solde ou une permission exceptionnelle." |
Request Created Offline Then Employee Goes On Leave Before Sync¶
Scenario: Employee creates a future leave request offline. Before syncing, the leave start date arrives.
| Step | Behavior |
|---|---|
| Offline DRAFT card | Shows in "En attente" with sync-pending icon. |
| Start date arrives | Card does NOT auto-move to "En cours" — it's still a DRAFT. |
| Employee reconnects | Auto-sync submits the request. Server may return error if dates are now in the past (retroactive request). |
| If retroactive allowed | Request proceeds with info.retroactive flag. Card updates normally. |
| If retroactive denied | Sync error notification: "Votre demande n'a pas pu être envoyée : les dates sont passées." Card shows error state + "Modifier" CTA. |
9. Component Specifications¶
Existing Components Used¶
| Component | Usage in this Module |
|---|---|
| Button | All CTAs. Primary for submit/approve. Ghost for cancel/back. Danger for reject/cancel leave. Secondary for upload/adjust. |
| Input | Reason fields (textarea mode), number inputs for adjustments |
| Select | Leave type picker, department filter, employee picker (searchable), deduction unit, period type, carry-over policy |
| Card | Balance card, request cards, approval cards, KPI cards, settings sections |
| Modal | Rejection reason (desktop), leave request form (desktop) |
| BottomSheet | Rejection reason (mobile), calendar person detail (mobile) |
| FormField | All form field wrappers |
| Checkbox | Half-day toggles |
| Radio | Balance adjustment direction (add/remove) |
| Toast | All success/error/info feedback |
New Components Required¶
These must be built as reusable components/ui/ components with Storybook stories before use in this module.
1. DatePicker (HIGH priority — already in design system "Needed" list)¶
| Prop | Type | Description |
|---|---|---|
value |
string \| null |
Selected date (DD/MM/YYYY string) |
onChange |
(date: string) => void |
Change handler |
locale |
string |
"fr" — French day/month names |
format |
string |
"DD/MM/YYYY" (display format) |
min |
string \| null |
Minimum selectable date |
max |
string \| null |
Maximum selectable date |
placeholder |
string |
Default: "JJ/MM/AAAA" |
disabled |
boolean |
Disabled state |
error |
string \| null |
Error message |
highlightDates |
string[] |
Dates to highlight (e.g., holidays) |
disabledDates |
string[] |
Specific dates to disable |
Behavior: - Mobile: tapping opens a scrollable month calendar in a BottomSheet - Desktop: tapping opens a dropdown calendar popover - French locale: "Lun Mar Mer Jeu Ven Sam Dim", "Janvier Février..." - Public holidays shown with dot indicator + tooltip - Keyboard: arrow keys to navigate, Enter to select, Escape to close - Touch targets: each day cell is 48×48px minimum
Accessibility: aria-label="Sélectionner une date", aria-expanded, role="dialog" on calendar popover.
2. Badge (MEDIUM priority — already in design system "Needed" list)¶
| Prop | Type | Description |
|---|---|---|
variant |
"success" \| "warning" \| "error" \| "info" \| "neutral" |
Semantic color |
size |
"sm" \| "md" |
Small (caption size) or medium (small size) |
icon |
ReactNode \| null |
Optional leading icon |
children |
string |
Label text |
dot |
boolean |
Show as dot-only (no text) — for notification counts |
count |
number \| null |
If set, shows count bubble (e.g., "3" on tab) |
Variants mapping for Absence module:
- success → Approuvé, Terminé, Auto-approuvé
- warning → En attente, En attente justificatif, Sync pending
- error → Rejeté, Annulé, Erreur sync
- info → En cours, Brouillon
- neutral → Passé, Lecture seule
Accessibility: Color is NOT the only indicator — each variant has a distinct icon (✓, ●, ✗, ℹ, —).
3. Tabs (MEDIUM priority — already in design system "Needed" list)¶
| Prop | Type | Description |
|---|---|---|
tabs |
{ id: string, label: string, count?: number, icon?: ReactNode }[] |
Tab definitions |
activeTab |
string |
Currently active tab ID |
onChange |
(tabId: string) => void |
Tab change handler |
variant |
"underline" \| "pill" |
Visual style. Default: "underline" |
Behavior:
- Mobile: horizontal scroll if tabs overflow. Active tab indicator slides with animation (--duration-normal).
- Desktop: all tabs visible. Underline variant for in-page navigation. Pill variant for filter-like usage.
- Touch targets: each tab is 48px tall minimum
- Count badges render as small amber circles (uses Badge with count prop)
Accessibility: role="tablist", role="tab", aria-selected, arrow key navigation.
4. DataTable (HIGH priority — already in design system "Needed" list)¶
| Prop | Type | Description |
|---|---|---|
columns |
Column[] |
Column definitions (label, key, sortable, width) |
data |
Row[] |
Data rows |
onRowClick |
(row: Row) => void |
Row click handler |
sortBy |
string \| null |
Current sort column |
sortDir |
"asc" \| "desc" |
Sort direction |
onSort |
(column: string) => void |
Sort handler |
loading |
boolean |
Show skeleton rows |
emptyState |
ReactNode |
What to show when data is empty |
mobileCard |
(row: Row) => ReactNode |
Render function for mobile card layout |
Behavior:
- Desktop (1024px+): standard table rows
- Mobile (<1024px): renders each row as a Card using mobileCard render prop
- Sortable columns show sort indicator (▲/▼)
- Clickable rows have hover state (--shadow-md on hover)
- Loading: skeleton shimmer rows matching column widths
Used in: approval list (desktop), request history (desktop), HR employee balance list.
5. FileUpload (NEW — not in design system yet)¶
| Prop | Type | Description |
|---|---|---|
accept |
string |
Accepted file types: "image/*,.pdf" |
maxSize |
number |
Max file size in bytes (default: 10MB) |
value |
File \| null |
Currently selected file |
onChange |
(file: File \| null) => void |
Change handler |
required |
boolean |
Whether upload is required |
label |
string |
Upload zone label |
helperText |
string |
Format/size hint |
error |
string \| null |
Error message |
disabled |
boolean |
Disabled state |
States: - Empty: Dashed border zone with icon + "Joindre un document" + helper text. Tappable (48px+ height). - Selected: Shows filename + file size + remove (✗) button. Thumbnail preview for images. - Uploading: Progress bar inside the zone. - Error: Red border + error message (e.g., "Fichier trop volumineux — max 10 Mo"). - Offline: File stored locally. Shows sync-pending icon.
Behavior: - Mobile: tapping opens device file picker (camera option included for photos) - Desktop: drag-and-drop zone + click to browse - Offline: file stored as blob in IndexedDB, uploaded on sync
Accessibility: aria-label="Joindre un fichier", keyboard focusable, Enter to open picker.
6. StepIndicator (NEW — not in design system yet)¶
| Prop | Type | Description |
|---|---|---|
steps |
number |
Total number of steps |
current |
number |
Current step (1-based) |
labels |
string[] |
Optional step labels |
Visual: Connected dots (●───○). Completed steps show checkmark. Current step is filled with --color-primary. Future steps are outlined.
Sizing: Compact — fits in a single line below the page title. Dots are 12px with 24px spacing.
7. EmptyState (MEDIUM priority — already in design system "Needed" list)¶
| Prop | Type | Description |
|---|---|---|
illustration |
"calendar" \| "checkmark" \| "people" \| "rocket" \| string |
Named illustration or custom |
title |
string |
Main message |
description |
string \| null |
Optional sub-text |
actionLabel |
string \| null |
CTA button label |
onAction |
() => void \| null |
CTA handler |
Visual: Centered layout. Illustration (64px SVG icon with --color-primary accent), title in --font-size-h3, description in --font-size-body --color-text-secondary, optional Button variant="primary".
8. Skeleton (MEDIUM priority — already in design system "Needed" list)¶
| Prop | Type | Description |
|---|---|---|
variant |
"text" \| "card" \| "circle" \| "rectangle" |
Shape |
width |
string \| number |
Width |
height |
string \| number |
Height |
count |
number |
Repeat N times (for lists) |
Visual: Shimmer animation (gradient sweep left→right). Base color: --color-bg-tertiary. Shimmer highlight: --color-bg-secondary. Animation: --duration-slow loop.
Design System Updates Required¶
After building these components, update docs/standards/design-system.md:
1. Move DatePicker, Badge, Tabs, DataTable, Skeleton, EmptyState from "Needed" to "Available"
2. Add FileUpload and StepIndicator to "Available"
3. Add InlineBanner to "Needed" list (used for soft warnings — could be extracted as reusable)
10. Enterprise Brand Divergence¶
The ABSMGT module serves BOTH brands. The Papillon specs above are the primary design. Enterprise diverges as follows:
| Aspect | Papillon | Enterprise |
|---|---|---|
| Layout | Mobile-first, single column, generous whitespace | Desktop-first 1440px, multi-panel |
| Balance display | Hero number + expandable breakdown | Always-visible sidebar panel with full breakdown + ledger |
| Request form | Full-screen page (mobile), modal (desktop) | Side panel that doesn't leave the current context |
| Approval list | Card-based with swipe | DataTable with batch checkboxes + bulk approve |
| Team calendar | Timeline, scroll horizontal | Full-width Gantt with department grouping + coverage % overlay |
| Dashboard | 4 KPIs + action list | 6-8 KPIs + advanced filters + export buttons |
| Navigation | Bottom tabs (role-adaptive) | Sidebar sub-menu under "Absences" |
| Density | Low — generous --space-6 between sections |
High — compact --space-3 between sections |
| Colors | Amber primary, warm backgrounds | Navy primary (#1E3A5F), clean white/grey |
| Onboarding | Guided tooltips on first visit | Minimal — professional users |
| Keyboard shortcuts | v1.1: A = approve, R = reject on focused card (desktop only, progressive enhancement) |
A = approve, R = reject, N = new request, ←/→ = navigate approvals, Ctrl+Enter = submit form |
| Batch operations | N/A (V2) | Bulk approve/reject with Checkbox + action bar |
| Export | N/A (V2) | CSV/Excel/PDF export on all list views |
Enterprise wireframes are NOT produced for MVP (Month 12+ per CLAUDE.md). The API is designed to support both from day one. When Enterprise development starts, this section serves as the design brief.
11. French Micro-copy¶
Navigation & Headers¶
| Key | Text |
|---|---|
nav.absences |
Congés & Absences |
tab.my_leaves |
Mes congés |
tab.approvals |
Approbations |
tab.calendar |
Calendrier |
tab.dashboard |
Tableau de bord |
tab.settings |
Paramètres |
page.new_request |
Nouvelle demande |
page.request_detail |
Détail de la demande |
page.maternity |
Déclaration de maternité |
page.adjustment |
Ajustement de solde |
page.holidays |
Jours fériés |
page.leave_config |
Paramètres des congés |
Balance¶
| Key | Text |
|---|---|
balance.title |
Votre solde |
balance.available |
jours calendaires |
balance.equivalent |
≡ {n} jours ouvrables |
balance.expand |
Voir le détail |
balance.base |
Acquis base |
balance.seniority |
Ancienneté |
balance.children |
Enfants ({n}) |
balance.taken |
Pris |
balance.total |
Disponible |
balance.not_available |
Disponible après 12 mois de service |
balance.available_from |
Vos congés seront disponibles à partir du {date} |
Leave Request Form¶
| Key | Text |
|---|---|
form.type_label |
Type de congé |
form.type_placeholder |
Choisir un type de congé |
form.start_date |
Date de début |
form.end_date |
Date de fin |
form.half_day_start |
Demi-journée début (après-midi uniquement) |
form.half_day_end |
Demi-journée fin (matin uniquement) |
form.reason |
Motif (optionnel) |
form.reason_placeholder |
Décrivez la raison de votre demande... |
form.days_computed |
{n} jours calendaires |
form.balance_after |
Solde après demande : {n} JC |
form.holiday_excluded |
dont {n} jour(s) férié(s) non compté(s) |
form.submit |
Envoyer la demande |
form.autosave |
Brouillon sauvegardé automatiquement |
form.submit_without_doc |
Vous pouvez envoyer sans document. La demande restera en attente du justificatif. |
Leave Types¶
| Key | Text |
|---|---|
type.ANNUAL |
Congé annuel |
type.SICK |
Congé maladie |
type.MATERNITY |
Congé maternité |
type.PATERNITY |
Congé paternité |
type.EXCEPTIONAL |
Permission exceptionnelle |
type.UNPAID |
Congé sans solde |
type.UNJUSTIFIED |
Absence injustifiée |
Event Types (Permissions Exceptionnelles)¶
| Key | Text |
|---|---|
event.MARRIAGE_SELF |
Mariage du salarié |
event.MARRIAGE_CHILD |
Mariage d'un enfant |
event.BIRTH_CHILD |
Naissance d'un enfant |
event.DEATH_SPOUSE |
Décès du conjoint |
event.DEATH_CHILD |
Décès d'un enfant |
event.DEATH_PARENT |
Décès père/mère |
event.DEATH_SIBLING |
Décès frère/sœur |
event.DEATH_IN_LAW |
Décès beau-père/belle-mère |
event.MOVE |
Déménagement |
Statuses¶
| Key | Text |
|---|---|
status.DRAFT |
Brouillon |
status.PENDING_DOCUMENT |
En attente de justificatif |
status.PENDING_APPROVAL |
En attente d'approbation |
status.APPROVED |
Approuvé |
status.AUTO_APPROVED |
Approuvé automatiquement |
status.SELF_APPROVED |
Auto-approuvé (DG) |
status.REJECTED |
Rejeté |
status.CANCELLED_BY_EMPLOYEE |
Annulé |
status.CANCELLED_BY_HR |
Annulé par les RH |
status.IN_PROGRESS |
En cours |
status.COMPLETED |
Terminé |
status.EARLY_RETURN |
Retour anticipé |
status.IN_PROGRESS_COUNTER |
En cours · jour {current} sur {total} |
History Groups (v1.1)¶
| Key | Text |
|---|---|
history.in_progress |
En cours maintenant |
history.upcoming |
À venir |
history.pending |
En attente |
history.past |
Passées |
history.past_count |
({n} demandes) |
history.show_all |
Voir tout |
On-Behalf Banner (v1.1)¶
| Key | Text |
|---|---|
form.on_behalf_title |
Demande pour {name} |
form.on_behalf_balance |
Solde : {n} JC |
Validation Messages¶
| Key | Text |
|---|---|
error.insufficient_balance |
Solde insuffisant. Votre solde disponible est de {balance} jours. |
error.12_months_required |
Vous devez compléter 12 mois de service avant de prendre un congé annuel. Disponible à partir du {date}. |
error.exceptional_cap |
Vous avez atteint le plafond annuel de 10 jours de permissions exceptionnelles. |
error.overlap_same |
Vous avez déjà un congé approuvé sur cette période. Veuillez l'annuler d'abord. |
error.maternity_overlap |
Vous êtes en congé maternité sur cette période. |
error.cci_minimum |
La durée minimale légale (CCI) pour cet événement est de {min} jours. |
error.rejection_required |
Veuillez indiquer le motif du refus. |
warning.15_day_notice |
Attention : le préavis légal est de 15 jours avant le début du congé. |
warning.14_day_block |
Rappel : la loi impose un congé principal d'au moins 14 jours ouvrables consécutifs par an. |
info.retroactive |
Demande rétroactive — sera signalée dans l'historique d'audit. |
info.overlap_split |
Les jours de congé annuel chevauchant cette période seront automatiquement recrédités. |
Approvals¶
| Key | Text |
|---|---|
approval.approve |
Approuver |
approval.reject |
Rejeter |
approval.reason_label |
Motif du refus |
approval.reason_placeholder |
Expliquez la raison du refus... |
approval.reason_helper |
L'employé recevra ce motif dans sa notification. |
approval.confirm_reject |
Confirmer le refus |
approval.doc_missing |
En attente du justificatif |
approval.swipe_hint |
Glissez pour approuver ou rejeter |
approval.team_impact |
{n} autre(s) personne(s) absente(s) sur cette période |
approval.undo_confirm |
Congé approuvé |
approval.undo_action |
Annuler |
approval.keyboard_hint |
A = approuver, R = rejeter, Entrée = voir détail |
Offline & Sync¶
| Key | Text |
|---|---|
offline.banner |
Hors connexion — dernière synchro : {time} |
offline.stale |
Données potentiellement obsolètes — connectez-vous pour mettre à jour |
offline.saved |
Demande enregistrée — sera envoyée automatiquement |
offline.sync_failed |
Votre demande n'a pas pu être envoyée : {reason} |
offline.action_blocked |
Cette action nécessite une connexion internet |
Calendar (v1.1)¶
| Key | Text |
|---|---|
calendar.snap_today |
Aujourd'hui |
calendar.long_absence_continues |
→ suite |
calendar.long_absence_started |
← début |
Edge Cases (v1.1)¶
| Key | Text |
|---|---|
error.duplicate_request |
Cette demande a déjà été envoyée. |
balance.zero_helper |
Solde épuisé. Vous pouvez demander un congé sans solde ou une permission exceptionnelle. |
event_group.grief |
Deuil |
event_group.happy |
Événements heureux |
event_group.other |
Autres événements |
Success Messages¶
| Key | Text |
|---|---|
success.request_sent |
Demande envoyée ! |
success.approved |
Congé approuvé |
success.rejected |
Demande rejetée |
success.cancelled |
Demande annulée |
success.maternity |
Déclaration enregistrée — congé créé automatiquement |
success.settings_saved |
Paramètres enregistrés |
success.balance_adjusted |
Solde ajusté — {sign}{n} jours |
success.undo |
Annuler (restant : {seconds}s) |
Empty States¶
| Key | Text |
|---|---|
empty.no_requests |
Vous n'avez pas encore de demande de congé |
empty.no_requests_cta |
Faire ma première demande |
empty.no_approvals |
Toutes les demandes ont été traitées ! |
empty.no_approvals_cta |
Voir le calendrier d'équipe |
empty.calendar_clear |
Toute l'équipe est présente ce mois-ci |
empty.welcome |
Bienvenue dans la gestion des absences |
empty.welcome_cta |
Configurer les paramètres |
empty.onboarding_title |
Préparez votre gestion des absences |
empty.onboarding_step1 |
Vérifier les jours fériés |
empty.onboarding_step2 |
Configurer les types de congé |
empty.onboarding_step3 |
Inviter vos employés |
empty.onboarding_continue |
Continuer l'installation |
empty.onboarding_skip |
Ignorer pour l'instant |
empty.onboarding_complete |
Vous êtes prêt ! |
Maternity¶
| Key | Text |
|---|---|
maternity.info |
La maternité est un droit légal. Ce formulaire crée automatiquement un congé de 14 semaines. |
maternity.employee |
Employée |
maternity.search |
Rechercher une employée... |
maternity.dpa |
Date présumée d'accouchement |
maternity.summary |
Résumé du congé |
maternity.start |
Début : {date} (DPA − 42 jours) |
maternity.end |
Fin : {date} (DPA + 56 jours) |
maternity.duration |
Durée : 14 semaines (98 jours) |
maternity.overlap |
{n} jours de congé annuel approuvé seront recrédités automatiquement. |
maternity.attestation |
Attestation médicale |
maternity.submit |
Enregistrer la déclaration |
maternity.notify |
L'employée et son manager seront notifiés automatiquement. |
12. Brand Compliance Checklist¶
Papillon HR Suite ✓¶
- [x] Amber/gold primary — all CTAs, active states, focus rings use
--color-primary - [x] Mobile-first 360px — all wireframes designed at 360px first
- [x] Offline states for all writes — leave request creation, cancellation, document upload all work offline
- [x] 48px touch targets — all interactive elements (buttons, cards, tabs, checkboxes, calendar cells)
- [x] Warm French copy — all micro-copy in natural, non-technical French
- [x] Loading skeletons — every screen has defined skeleton state, no bare spinners
- [x] Generous whitespace —
--space-6between sections,--space-4between fields - [x] Celebration animations — maternity declaration success (confetti), leave approval (check bounce), request submission (scale bounce + check)
- [x] Progressive disclosure — balance breakdown expandable, half-day toggles revealed after dates, event types as step 1
- [x] Forgiving interface — undo on cancel (8s), auto-save on field change, soft warnings not hard blocks for non-critical rules
Enterprise (design brief for Month 12+)¶
- [x] Navy/corporate — documented in §10 divergence
- [x] Desktop-first 1440px — documented
- [x] Information density — documented (compact spacing, always-visible breakdown, multi-panel)
- [x] Batch operations — documented (bulk approve/reject)
- [x] Keyboard shortcuts — documented (A/R/N/arrows)
- [x] Advanced filters — documented (department, date range, status, type)
- [x] CSV/Excel/PDF export — documented on all list views
Changelog¶
v1.1 — 13/03/2026¶
7 targeted enhancements to improve safety, usability, and cultural sensitivity. No structural changes.
| # | Change | Section(s) | UX Principle |
|---|---|---|---|
| 1 | Swipe approval undo bar — 2-second floating confirmation instead of instant commit | DD-05, §4.4, §7 | Error prevention > error recovery (Don Norman) |
| 2 | On-behalf persistent banner — sticky indicator showing WHO HR is creating a request for | §4.2 | System status visibility (Nielsen #1) |
| 3 | 4-group history — split "En cours" into "En cours maintenant" + "À venir" | DD-11, §4.1 | Match system and real world (Nielsen #2) |
| 4 | Mobile calendar improvements — 3-char abbreviations, "Aujourd'hui" snap-back chip | §4.6 | Name truncation at 360px, context recovery |
| 5 | HR onboarding checklist — guided 3-step setup card for first-time HR users | §8 | Recognition over recall (Nielsen #6) |
| 6 | Permission exceptionnelle reordering — frequency-based + grief-sensitive grouping | DD-08, §4.2 | Pareto principle + cultural empathy |
| 7 | Edge case specifications — concurrent requests, long absences, zero balance, keyboard nav | §4.4, §8 | Implementation clarity + accessibility |
End of Design Document — Absence Management (ABSMGT) v1.1