Aller au contenu

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.css and components from docs/standards/design-system.md. Use existing components — never rebuild. If a needed component doesn't exist, build it as components/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

  1. Scope & Screens Index
  2. Design Decisions Log
  3. User Flow Diagrams
  4. Screen Specifications
  5. 4.1 Employee Home — Mes congés
  6. 4.2 Leave Request Form
  7. 4.3 Leave Request Detail
  8. 4.4 Manager — Pending Approvals
  9. 4.5 Approval Detail & Action
  10. 4.6 Team Calendar
  11. 4.7 HR Dashboard
  12. 4.8 HR — Employee Balance Detail
  13. 4.9 HR — Maternity Declaration
  14. 4.10 HR — Manual Balance Adjustment
  15. 4.11 Settings — Leave Configuration
  16. 4.12 Settings — Public Holidays
  17. Responsive Behavior
  18. Offline Behavior
  19. Motion Choreography
  20. Loading, Empty, Error & Success States
  21. Component Specifications
  22. Enterprise Brand Divergence
  23. French Micro-copy
  24. 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À venirEn attentePassé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):
    ┌──────────────────────────────────┐
    │ ✓ Congé approuvé    [Annuler]   │  ← bg=success, 2s countdown
    │   ████████████░░░░░░            │  ← progress bar shrinking
    └──────────────────────────────────┘
    
  • 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-light background.
  • 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-pending with --duration-sync-pulse animation
  • 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:

  1. On sync, server validates the queued request
  2. If validation fails (e.g., insufficient balance now): request stays as DRAFT with error flag
  3. User sees notification: "Votre demande de congé n'a pas pu être envoyée : {raison}"
  4. Card in "Mes congés" shows error state with "Modifier" button
  5. 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

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-6 between sections, --space-4 between 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