Aller au contenu

Operator Console — Design Spec

Module : OP — operator-console Side : [OPERATOR] Plane : Application Cible visuelle : Desktop 1280 px (utilisable ≥ 1024 px), tablet 768 px secondaire Langue : Français uniquement Date : 2026-05-22 Statut : v1.0 — design contract for V1 PRD, V0 envelope additive

Le PRD source est docs/prd/operator-console-prd.md.
Le scaffold initial existe sous frontend-operator/ (OperatorShell, Sidebar, TopBar, AuthBoundary, Button, Badge, DataTable, DashboardPage, ConnexionPage, AdminPage).
Ce document est le contrat UX/UI qui décrit COMMENT remplir ce scaffold.


Note sur l'interview UX

L'utilisateur a explicitement autorisé un parcours direct sans interview ("do it straight to the end. Do not wait for my approval. I trust you.").
Les décisions UX non triviales qui auraient normalement fait l'objet d'une AskUserQuestion sont annotées [Décision : …] à l'endroit où elles s'appliquent, avec la justification courte et l'alternative écartée.
Cela conserve la valeur pédagogique du document.


Sommaire

  1. Principes directeurs OP
  2. Architecture d'information & navigation
  3. User Flow Diagram (Mermaid)
  4. Wireframes par écran
  5. Spécifications d'interaction — 5 états par écran
  6. Comportement responsive
  7. Comportement connectivité
  8. Micro-copy FR
  9. Composants — inventaire & specs nouveaux
  10. UI conformité (CIMA / ARTCI)
  11. Phase 3 — Prompts de wireframe par story

1. Principes directeurs OP

  • Desktop-first, online-first, calme et professionnel. Densité MOYENNE-HAUTE (tables, filtres, pills, batch actions). Aucun élément flashy.
  • Une priorité unique au-dessus de la liste : la barre de filtres se replie en sticky, mais la table reste reine.
  • Compteurs partout : "12 dossiers, 3 anomalies bloquantes". Visible avant l'action, pas après.
  • Status pill = un seul vocabulaire de couleurs (cf. §9). La couleur n'est jamais le seul indicateur (texte court FR + parfois pictogramme).
  • Keyboard shortcuts : / focus la barre de recherche globale ; g d go dashboard ; g c go campagnes ; g a go anomalies ; n nouvelle campagne ; Esc ferme modale/drawer.
  • Filtres persistants : tous les filtres + tri + pagination survivent à un reload (encodés dans l'URL en query string).
  • Action destructive = double pas : modale de confirmation avec compteur d'impact ("12 liens seront révoqués").
  • 30 min d'inactivité → déconnexion (Keycloak). Toast d'avertissement à 28 min : "Votre session expire dans 2 min. [Rester connecté]".

[Décision : URL = source de vérité pour l'état de liste] Plutôt que cookie/localStorage. Justification : un superviseur copie une URL filtrée pour la partager à un collègue → expérience naturelle.
Alternative écartée : state local React + sessionStorage (perdu sur partage).


2. Architecture d'information & navigation

OperatorShell (existant)
├── TopBar  : logo Papillon · breadcrumb · barre recherche globale (/) · cloche notif · avatar (menu : profil, déconnexion)
├── Sidebar : 7 entrées
│   ├── 🏠 Tableau de bord       (US-OP-09)
│   ├── 📥 Imports                (US-OP-01)
│   ├── 📣 Campagnes              (US-OP-02..07,14)   ← entrée principale
│   ├── ⚠️ Anomalies             (US-OP-08)  + badge compteur si > 0
│   ├── 💳 Paiements              (lecture statut + hors-plateforme global, US-OP-06 ré-utilisé)
│   ├── 📤 Exports                (US-OP-11)              [supervisor / cabinet_admin uniquement]
│   └── ⚙️ Administration         (US-OP-12, US-OP-13)    [cabinet_admin uniquement]
└── Outlet (page courante)

[Décision : 7 entrées de sidebar, pas de regroupement par sections] Hick's Law : 7 items lus en bloc reste sous la limite de scan rapide. Une hiérarchie à 2 niveaux ajouterait un clic à chaque navigation. Alternative écartée : sections "Travail quotidien" / "Administration".

[Décision : "Imports" ET "Campagnes" séparés] Bien que liés (un import alimente la cohort), ce sont deux moments cognitifs distincts : "j'amène les données" vs "je fais mon travail de relance". Alternative écartée : tout sous "Campagnes" avec un sous-onglet "Imports". Réjetée car l'import est une action de début de cycle souvent faite par un collaborateur différent.

Visibilité par rôle (RBAC dans la sidebar)

Item agency_operator supervisor cabinet_admin
Tableau de bord ✅ (scope agence) ✅ (cross-agences) ✅ (cross)
Imports
Campagnes ✅ (agence) ✅ (cross) ✅ (cross)
Anomalies ✅ (agence) ✅ (cross) ✅ (cross)
Paiements ✅ (agence) ✅ (cross) ✅ (cross)
Exports
Administration

[Décision : on cache l'item plutôt que de le griser] Less is more pour les opérateurs ; ce n'est pas un produit où l'on cherche à vendre des features non incluses dans le rôle. Alternative écartée : grisé avec tooltip "Réservé aux administrateurs" — ajoute du bruit visuel inutile dans le quotidien.

Filtre d'agence (pour rôles cross-agences)

Pour supervisor et cabinet_admin, un sélecteur d'agence apparaît dans la TopBar à droite de la recherche : Toutes les agences ▼ | Agence Plateau | Agence Cocody | …. La sélection est globale (s'applique à toutes les pages) et survit au reload (querystring ?agency=<id> ou ?agency=all).

[Décision : sélecteur d'agence dans la TopBar, pas dans chaque page] Cognitive load : un superviseur change d'agence comme contexte de travail, pas comme filtre par page. Alternative écartée : un filtre Agence répété dans chaque barre de filtres → friction.


3. User Flow Diagram (Mermaid)

flowchart TD
    Login[Connexion Keycloak] --> Dashboard

    Dashboard -- "Contrats urgents" --> CampaignList
    Dashboard -- "Anomalies bloquantes" --> Anomalies
    Dashboard -- "Pipeline campagnes" --> CampaignList
    Dashboard -- "Tunnel paiement" --> Payments

    Sidebar((Sidebar)) --> Imports
    Sidebar --> CampaignList[Liste campagnes]
    Sidebar --> Anomalies[Queue anomalies]
    Sidebar --> Payments[Paiements]
    Sidebar --> Exports
    Sidebar --> Admin

    Imports -->|Upload OK| ImportResult[Résultat import]
    Imports -.->|Hash duplicate| DuplicateWarn[Warning fichier déjà importé]
    DuplicateWarn -->|Continuer| ImportResult
    ImportResult -->|Voir erreurs| Anomalies

    CampaignList -->|Nouvelle campagne| NewCampaign[Sélecteur contrats ad hoc]
    NewCampaign -->|Créer| CampaignDraft[Détail campagne — BROUILLON]

    CampaignList -->|Clic campagne BROUILLON| CampaignDraft
    CampaignList -->|Clic campagne ENVOYÉE/CLÔTURÉE| CampaignSent[Détail campagne — ENVOYÉE/CLÔTURÉE]

    CampaignDraft -->|Corriger ligne| ContractDrawer[Drawer contrat — édition]
    ContractDrawer -->|Sauver| CampaignDraft
    CampaignDraft -->|Exclure N| BulkExclude[Modale exclure en lot]
    BulkExclude --> CampaignDraft
    CampaignDraft -->|Valider & Envoyer| ValidationGuard{0 anomalie bloquante ?}
    ValidationGuard -->|Non| BlockingMsg[Message bloquant + lien anomalies]
    BlockingMsg --> CampaignDraft
    ValidationGuard -->|Oui| ConfirmSend[Modale Envoyer à N clients ?]
    ConfirmSend --> CampaignSent

    CampaignSent -->|Clic contrat| ContractDrawer2[Drawer contrat — lecture + actions]
    ContractDrawer2 -->|Envoyer rappel| Reminder[POST NOTIF + nouveau lien AUTH]
    Reminder --> CampaignSent
    ContractDrawer2 -->|Régler hors-plateforme| HpModal[Modale hors-plateforme]
    HpModal --> CampaignSent
    CampaignSent -->|Ajouter un contrat| AddContract[Sélecteur contrats éligibles]
    AddContract --> CampaignSent
    CampaignSent -->|Annuler campagne| CancelModal[Modale révocation X liens]
    CancelModal --> CampaignList

    Anomalies -->|Clic anomalie| ContractDrawer3[Drawer contrat — focus champ défectueux]
    ContractDrawer3 -->|Marquer résolu| Anomalies

    %% --- Offline / dégradation §20 PRD V1 ---
    subgraph Offline_min["Offline minimum §20 (online-first)"]
      OfflineBanner[Bandeau « Connexion perdue »]
      ReadOnly[Listes déjà chargées = lecture seule]
      LocalNotes[Notes localStorage]
      WriteBlocked[Toutes actions d'écriture bloquées + toast]
    end

    Dashboard -.->|Perte connexion| OfflineBanner
    CampaignList -.->|Perte connexion| OfflineBanner
    CampaignDraft -.->|Perte connexion| WriteBlocked
    OfflineBanner --> ReadOnly
    OfflineBanner --> LocalNotes
    ReadOnly -.->|Reconnexion| Resync[Re-fetch + sync notes]
    LocalNotes -.->|Reconnexion| Resync

    %% --- Dégradation back-end ---
    CampaignList -.->|ING/NOTIF/PAY timeout 10s| ApiError[Erreur + Réessayer]
    ApiError -.->|Réessayer OK| CampaignList

    %% --- SSE/WS fallback ---
    CampaignSent -.->|SSE drop| Polling[Fallback polling 30s + backoff]
    Polling -.->|SSE up| CampaignSent

4. Wireframes par écran

ASCII basse fidélité, viewport desktop 1280 px sauf mention contraire. Pour chaque écran : décisions d'interview qui l'ont façonné, layout, et notes responsive.

4.1 Connexion (US implicite, déjà scaffoldé)

Décisions appliquées : page Keycloak hébergée OU formulaire intégré ? On garde page intégrée (ConnexionPage.tsx existante) car déjà scaffoldée. Logo Papillon + cabinet ressort de la cohérence visuelle.

┌────────────────────────────────────────────────────────────┐
│                                                            │
│              [LOGO Papillon Collection]                    │
│                                                            │
│           Connexion à la console opérateur                 │
│                                                            │
│     ┌──────────────────────────────────────────────┐       │
│     │ Email                                        │       │
│     │ [_________________________________________]  │       │
│     │                                              │       │
│     │ Mot de passe                                 │       │
│     │ [_________________________________________] 👁│       │
│     │                                              │       │
│     │ [          Se connecter          ]           │       │
│     │                                              │       │
│     │  Mot de passe oublié ?                       │       │
│     └──────────────────────────────────────────────┘       │
│                                                            │
│   Papillon Collection Solution — version X.Y.Z             │
└────────────────────────────────────────────────────────────┘

4.2 Tableau de bord (US-OP-09) [V1]

Décisions appliquées : - 4 widgets côte-à-côte sur desktop, empilés sur tablet (déjà imposé par AC1, mais l'ordre = "ce que je dois faire maintenant" → "vue d'ensemble" → "détail temporel" → "santé qualité"). - [Décision : Contrats urgents et Anomalies bloquantes sont les widgets pleine largeur prioritaires en haut, Pipeline et Tunnel viennent en dessous] Justification : "design the unhappy paths first" — un opérateur ouvre OP pour traiter de l'urgent, pas pour admirer un funnel.

┌────────────────────────────────────────────────────────────────────────────┐
│ TopBar [Logo] Tableau de bord       🔍 [Rechercher (/)]    [Agence ▼]  🔔 👤│
├────────┬───────────────────────────────────────────────────────────────────┤
│ 🏠     │ Bonjour Marie. Vous êtes sur Cabinet Acme CI · Agence Plateau     │
│ 📥     │                                                                   │
│ 📣  3  │ ┌─── Contrats urgents (≤ 7 j) ──┐ ┌─── Anomalies bloquantes ───┐ │
│ ⚠️ 12  │ │                                │ │                            │ │
│ 💳     │ │      27 contrats                │ │      12 anomalies         │ │
│ 📤     │ │      à échéance ≤ 7 j           │ │      à corriger           │ │
│ ⚙️     │ │                                 │ │                           │ │
│        │ │ [Voir la liste →]               │ │ [Voir les anomalies →]    │ │
│        │ └────────────────────────────────┘ └─────────────────────────────┘ │
│        │                                                                   │
│        │ ┌─── Pipeline campagnes ────────┐ ┌─── Tunnel paiement (30 j) ─┐ │
│        │ │ Brouillon         3            │ │  Envoyés     412           │ │
│        │ │ Envoyée           5            │ │  Payés       287 (69,6 %) │ │
│        │ │ Clôturée         24            │ │  Échoués      18           │ │
│        │ │ Annulée           1            │ │  Hors-plat.   34           │ │
│        │ │ [Voir les campagnes →]         │ │ [Voir les paiements →]    │ │
│        │ └────────────────────────────────┘ └─────────────────────────────┘ │
│        │                                                                   │
│        │ Dernière synchro : il y a 14 s                                    │
└────────┴───────────────────────────────────────────────────────────────────┘

Notes responsive : - ≥ 1280 px : 2 colonnes (2×2). - 1024–1279 px : 2 colonnes, padding réduit. - 768–1023 px (tablet) : 1 colonne, widgets empilés, ordre = urgents → anomalies → pipeline → tunnel.


4.3 Imports (US-OP-01) [V1]

Décisions appliquées : - [Décision : drop-zone pleine largeur + historique des imports en dessous, pas de tab] Justification : 80 % du temps l'utilisateur vient ici pour uploader, pas pour consulter. L'historique récent est secondaire mais visible (preuve sociale du travail effectué). - [Décision : warning de duplicate hash inline dans la drop-zone, pas en modale] Évite l'effet "popup surprise" et permet de continuer ou annuler dans le contexte.

┌─────────────────────────────────────────────────────────────────────────┐
│ Imports                                                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌──────────────────────────────────────────────────────────────────┐ │
│   │                                                                  │ │
│   │              📁                                                  │ │
│   │   Glissez votre fichier ici ou [Parcourir]                       │ │
│   │   Formats acceptés : CSV, XLSX — Taille max : 20 Mo              │ │
│   │                                                                  │ │
│   └──────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│   ─── Pendant l'upload ───                                              │
│   ┌──────────────────────────────────────────────────────────────────┐ │
│   │ 📄 contrats_mai_2026.xlsx · 2,3 Mo                               │ │
│   │ [████████████████░░░░░░░░░░░░] 62 %  Téléversement…              │ │
│   └──────────────────────────────────────────────────────────────────┘ │
│                                                                         │
│   ─── Si duplicate hash ───                                             │
│   ⚠️ Ce fichier a déjà été importé le 18/05/2026 par K. Brou.          │
│      [Continuer quand même]  [Annuler]                                  │
│                                                                         │
│   Imports récents                                                       │
│   ┌──────────────────────────────────────────────────────────────────┐ │
│   │ Fichier              Date           Valides Erreurs Anomalies    │ │
│   │ ──────────────────── ────────────── ─────── ─────── ────────     │ │
│   │ contrats_mai.xlsx    20/05 14:32    412     3       12  → [Voir] │ │
│   │ contrats_avr.csv     15/04 09:11    389     0       4   → [Voir] │ │
│   └──────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

Écran « Résultat import » (après traitement ING) :

Import du 20/05/2026 — contrats_mai_2026.xlsx
─────────────────────────────────────────────
✅ 412 contrats valides
❌ 3 erreurs bloquantes              → [Voir les 3 erreurs]
⚠️ 12 anomalies non bloquantes      → [Voir les 12 anomalies]

[Retour aux imports]

4.4 Liste des campagnes (US-OP-02) [V1]

Décisions appliquées : - [Décision : DataTable plein écran avec filter bar sticky en haut, pas de cartes] Densité élevée volontaire — c'est l'écran que l'opérateur regarde 20 fois par jour. - [Décision : statut affiché en pill + colonne « contrats » avec badge urgent en rouge si ≥ 1 contrat URGENT dans la campagne] Permet de prioriser visuellement sans cliquer.

┌────────────────────────────────────────────────────────────────────────────┐
│ Campagnes                                            [+ Nouvelle campagne] │
├────────────────────────────────────────────────────────────────────────────┤
│ 🔍 [Rechercher dans les campagnes…]                                        │
│ Statut: [Tous ▼]  Période: [30 derniers j ▼]  Branche: [Toutes ▼]          │
│ Agence: [Plateau ▼]  ← visible si supervisor/cabinet_admin                 │
│                                                                            │
│ 33 campagnes · 3 brouillons · 5 envoyées                  [Effacer filtres]│
├────────────────────────────────────────────────────────────────────────────┤
│ Nom                          Agence    Contrats  Statut      Créée le      │
│ ──────────────────────────── ───────── ──────── ──────────── ──────────    │
│ Campagne 2026-05-22 - Plat.  Plateau   42  🔴3  ● Brouillon  22/05 09:14   │
│ Campagne 2026-05-20 - Plat.  Plateau   38       ● Envoyée    20/05 11:02   │
│ Campagne 2026-05-15 - Plat.  Plateau   55       ● Clôturée   15/05 08:30   │
│ Campagne 2026-05-12 - Cocody Cocody    28       ● Annulée    12/05 15:47   │
│ …                                                                          │
│                                                                            │
│ [« Préc]   Page 1 / 2   [Suiv »]                          25 par page ▼   │
└────────────────────────────────────────────────────────────────────────────┘

Pills statut : - Brouillon = neutre (gris #888 — token --color-text-secondary sur fond --color-bg-tertiary) - Envoyée = info (bleu — token --color-info) - Clôturée = success (vert — --color-success) - Annulée = warning neutre (orange clair, jamais rouge — --color-warning)

[Décision : Annulée n'est pas rouge] Le rouge est réservé aux états bloquants/dangereux (anomalie bloquante, URGENT). Une annulation est intentionnelle, pas une erreur.


4.5 Nouvelle campagne ad hoc (US-OP-03) [V1]

Décisions appliquées : - [Décision : modale plein écran (full-page sheet) plutôt que page dédiée] L'utilisateur revient toujours à la liste après création/annulation. Une modale conserve le contexte (la liste reste derrière). Alternative écartée : page /campagnes/nouvelle → ajoute un retour à gérer. - [Décision : sélecteur de contrats = table avec checkboxes + filter bar — pas de wizard multi-étape] Une seule étape mentale : « choisir N contrats parmi les éligibles ». Pas de pré-segmentation à la "wizard" (Hick's Law). - [Décision : contrats inéligibles GRISÉS mais visibles, avec tooltip explicatif] Plutôt que les masquer — l'opérateur cherche un contrat précis, il doit comprendre pourquoi il n'apparaît pas sélectionnable.

┌─── Modale plein écran ─────────────────────────────────────────────────────┐
│ × Nouvelle campagne                                                        │
├────────────────────────────────────────────────────────────────────────────┤
│ Sélectionnez les contrats à inclure dans cette campagne.                   │
│                                                                            │
│ Branche: [Toutes ▼]  Échéance: [du __ au __]  Montant: [min __  max __]    │
│ 🔍 [Rechercher client / référence…]                                        │
│                                                                            │
│ 247 contrats éligibles · 12 sélectionnés                  [Tout désélect.] │
├────────────────────────────────────────────────────────────────────────────┤
│ ☐ Client          Téléphone        Branche  Montant   Échéance    Statut  │
│ ─ ──────────────  ──────────────── ────────  ──────── ──────────  ─────── │
│ ☑ KOUAKOU Aya    +225 07 02 11 22 Auto      85 000   29/05/2026   ✓ Élig. │
│ ☐ BROU N'Goran   +225 07 11 33 44 Habitat  120 000   30/05/2026   ✓ Élig. │
│ ▫ TRAORÉ Bintou  +225 ▒▒ ▒▒ ▒▒ ▒▒ Vie       55 000   02/06/2026   ⓘ Déjà  │
│   (grisé)                                                          dans   │
│                                                                Campagne   │
│                                                                2026-05-20 │
│ …                                                                          │
│                                                                            │
├────────────────────────────────────────────────────────────────────────────┤
│                                  [Annuler]    [Créer la campagne (12)]    │
└────────────────────────────────────────────────────────────────────────────┘

4.6 Détail campagne — BROUILLON (US-OP-04) [V1]

Décisions appliquées : - [Décision : édition contrat dans un drawer latéral plutôt que modale] Permet de garder la liste visible et naviguer rapidement entre contrats sans fermer/rouvrir. Aussi : Fitts's Law — la croix de fermeture est à un coin prévisible. - [Décision : barre d'actions bulk apparait en bas (action bar) quand ≥ 1 ligne cochée] Mémoire courte : l'utilisateur voit l'action contextuelle pile au moment où elle devient utile, sans encombrer la barre de filtres le reste du temps. - [Décision : « Valider et Envoyer » sticky en haut à droite + grisé avec compteur d'anomalies] Bouton primaire toujours visible — c'est la sortie principale de l'écran.

┌────────────────────────────────────────────────────────────────────────────┐
│ ← Campagne 2026-05-22 - Plateau · 42 contrats · ● Brouillon                │
│                          [⋯ Annuler la campagne]  [Valider et Envoyer →]   │
├────────────────────────────────────────────────────────────────────────────┤
│ ⚠️ 3 contrats avec anomalies bloquantes. Corrigez ou excluez avant envoi.  │
│   [Voir les 3 anomalies]                                                   │
├────────────────────────────────────────────────────────────────────────────┤
│ Recherche: [______]  Anomalies: [Toutes ▼]  Branche: [Toutes ▼]            │
│ 42 contrats · 3 bloquantes · 5 non bloquantes                              │
├────────────────────────────────────────────────────────────────────────────┤
│ ☐ Client          DOB        Téléphone        Br.  Montant   Échéance  ⚠️ │
│ ─ ──────────────  ─────────  ──────────────── ───  ────────  ────────  ── │
│ ☐ KOUAKOU Aya     **/**/1989 +225 07 02 11 22 Auto  85 000   29/05      0 │
│ ☐ BROU N'Goran    **/**/1972 +225 07 11 33 44 Habit 120 000  30/05    🔴1 │
│ ☐ TRAORÉ Bintou   **/**/1995 ⓘ manquant        Vie     —     02/06    🔴1 │
│ …                                                                          │
├────────────────────────────────────────────────────────────────────────────┤
│ ─── Action bar (visible si N≥1 cochés) ───                                 │
│ 5 sélectionnés          [Exclure les 5]   [Désélectionner]                 │
└────────────────────────────────────────────────────────────────────────────┘

Drawer latéral d'édition contrat (clic sur ligne ou anomalie) :

                                  ┌───── Drawer (420 px desktop, 100 % tablet) ──┐
                                  │ × KOUAKOU Aya                                │
                                  ├──────────────────────────────────────────────┤
                                  │ Réf. contrat : ACME-2024-0042  (lecture seule)│
                                  │ Branche      : Auto  (lecture seule)         │
                                  │ Date naissance : 14/03/1989                  │
                                  │                                              │
                                  │ ─── Champs éditables ───                     │
                                  │ Téléphone *                                  │
                                  │ [+225 07 02 11 22                ]           │
                                  │                                              │
                                  │ Montant (XOF) *                              │
                                  │ [85 000                          ]           │
                                  │                                              │
                                  │ Date d'échéance *                            │
│ [29/05/2026                      ]📅          │
                                  │                                              │
                                  │ ⚠️ Anomalies sur ce contrat                  │
                                  │ • Téléphone manquant (bloquante)             │
                                  │   [Marquer comme résolu]                     │
                                  │                                              │
                                  │ ─── Notes (privées, agence) ───              │
                                  │ [_____________________________________]      │
                                  │ Sauvegarde locale automatique                │
                                  ├──────────────────────────────────────────────┤
                                  │ [Annuler]                  [Enregistrer]     │
                                  └──────────────────────────────────────────────┘

Modale de confirmation envoi :

┌─── Confirmer l'envoi ─────────────────────────────────┐
│                                                       │
│   Envoyer la campagne à 39 client(s) ?                │
│   (3 contrats avec anomalies bloquantes exclus)       │
│                                                       │
│   Chaque client recevra une notification              │
│   WhatsApp ou SMS avec un lien sécurisé               │
│   valable 72 h.                                       │
│                                                       │
│           [Annuler]    [Confirmer l'envoi]            │
└───────────────────────────────────────────────────────┘

4.7 Détail campagne — ENVOYÉE / CLÔTURÉE (US-OP-05, 06, 07, 14) [V1]

Décisions appliquées : - [Décision : KPI strip en haut (compteurs + % conversion) + table en dessous avec colonne « Dernière activité »] Vue de monitoring : on lit les KPI d'abord, on plonge dans la table ensuite. - [Décision : statuts cycle de vie complet en pills colorées homogènes (cf. §9)] - [Décision : le bouton « URGENT 🔴 » devient une colonne dédiée, pas un badge perdu dans le nom] Tri rapide par urgence possible. - [Décision : actions par ligne via menu kebab (⋯) à droite de chaque ligne] Évite 5 boutons par ligne dans une table dense.

┌────────────────────────────────────────────────────────────────────────────┐
│ ← Campagne 2026-05-20 - Plateau · 38 contrats · ● Envoyée                  │
│                      [+ Ajouter un contrat]  [⋯ Annuler la campagne]       │
├────────────────────────────────────────────────────────────────────────────┤
│ ┌─Envoyés─┐ ┌─Livrés─┐ ┌─Liens ouverts─┐ ┌─Payés─┐ ┌─Hors plat.─┐ ┌─Échec─┐│
│ │   38    │ │   36   │ │      22       │ │  19   │ │      4     │ │   2   ││
│ │         │ │   95%  │ │     61%       │ │  50%  │ │     11%    │ │   5%  ││
│ └─────────┘ └────────┘ └───────────────┘ └───────┘ └────────────┘ └───────┘│
│   Mis à jour il y a 3 s                                              ●live │
├────────────────────────────────────────────────────────────────────────────┤
│ Statut: [Tous ▼]  Urgence: [Tous ▼]  Recherche: [______]                  │
├────────────────────────────────────────────────────────────────────────────┤
│ Client          Téléphone        Statut              Échéance  Dern. act.  │
│ ──────────────  ──────────────── ──────────────────── ────────  ─────────  │
│ KOUAKOU Aya    +225 07 02 11 22 ● Payé              29/05      payé 14:02⋯│
│ BROU N'Goran   +225 07 11 33 44 ● Lien ouvert       30/05      ouvert 12h⋯│
│ TRAORÉ Bintou  +225 07 22 44 88 ● Lien expiré 🔴URG 25/05 ⚠️    expiré 5h ⋯│
│ DIALLO Fatim   +225 07 09 88 77 ● Notif échouée     02/06      échec 1j  ⋯│
│ …                                                                          │
└────────────────────────────────────────────────────────────────────────────┘

Menu kebab par ligne (varie selon TD-3 du PRD §B.3) :

⋯ → ┌────────────────────────────────────┐
    │ Voir la fiche               │
    │ Envoyer un rappel           │  ← actif si statut ∈ {NOTIF_ÉCHOUÉ, LIEN_EXPIRÉ, …}
    │ Régler hors-plateforme      │  ← actif si non terminal & non payé
    │ ─────────────────           │
    │ Voir l'historique audit     │
    └────────────────────────────────────┘

Modale « Régler hors-plateforme » :

┌─── Régler hors-plateforme : KOUAKOU Aya ──────────┐
│                                                   │
│ Méthode de paiement *                             │
│ ○ Mobile Money   ○ Espèces   ○ Chèque   ○ Virement│
│                                                   │
│ Date de paiement *                                │
│ [22/05/2026                              ]📅       │
│                                                   │
│ Montant payé (XOF) *                              │
│ [85 000                                  ]        │
│                                                   │
│ Référence (optionnel)                             │
│ [_______________________________________]         │
│                                                   │
│ ℹ️ Cette action est définitive et journalisée.    │
│                                                   │
│        [Annuler]      [Enregistrer le paiement]   │
└───────────────────────────────────────────────────┘

Modale « Annuler la campagne » :

┌─── Annuler la campagne ───────────────────────────┐
│                                                   │
│         16 liens seront révoqués                  │
│                                                   │
│ Les clients qui cliqueront sur leur lien verront  │
│ le message : « Ce lien n'est plus valide.         │
│ Veuillez contacter votre cabinet. »               │
│                                                   │
│ Cette action est irréversible.                    │
│                                                   │
│         [Non, garder]   [Oui, annuler]            │
└───────────────────────────────────────────────────┘

4.8 Anomalies (US-OP-08) [V1]

Décisions appliquées : - [Décision : queue groupée par type (Bloquante / Non bloquante / Surveillance) sous forme de tabs, pas de tri par colonne uniquement] L'opérateur attaque toujours les bloquantes d'abord — la hiérarchisation cognitive doit être structurelle, pas optionnelle. - [Décision : clic ouvre directement le drawer contrat focalisé sur le champ défectueux] Réduit les paliers cognitifs : "je veux corriger ce numéro" → un seul clic → champ déjà highlight + focus.

┌────────────────────────────────────────────────────────────────────────────┐
│ Anomalies                                                                  │
├────────────────────────────────────────────────────────────────────────────┤
│  ╔═══════════════════╗ ╔══════════════════╗ ╔═══════════════════════╗     │
│  ║ 🔴 Bloquantes  12 ║ ║ 🟠 Non bloq.  5  ║ ║ 🟡 Surveillance   3   ║     │
│  ╚═══════════════════╝ ╚══════════════════╝ ╚═══════════════════════╝     │
│                                                                            │
│ Statut: [Ouvertes ▼]  Recherche client/réf: [______]                       │
├────────────────────────────────────────────────────────────────────────────┤
│ Type bloquante                                                             │
│ ─────────────────                                                          │
│ • Téléphone manquant  · TRAORÉ Bintou · ACME-2024-0099   → [Ouvrir]        │
│ • Téléphone invalide  · KOUASSI K.    · ACME-2024-0102   → [Ouvrir]        │
│ • Montant ≤ 0         · YAO Hervé     · ACME-2024-0108   → [Ouvrir]        │
│ …                                                                          │
└────────────────────────────────────────────────────────────────────────────┘

Drawer = même composant que §4.6, avec champ défectueux highlight (outline: 2px solid var(--color-error)).


4.9 Recherche globale (US-OP-10) [V1]

Composant dans la TopBar (raccourci /).

🔍 [Rechercher client, téléphone, référence…]
   ┌────────────────────────────────────────────────┐
   │ KOUAKOU Aya — +225 07 02 11 22 — Auto          │  ← Enter pour ouvrir
   │ KOUAKOU Akissi — +225 07 88 11 09 — Habitat    │
   │ Ref. ACME-2024-0042 — KOUAKOU Aya              │
   │ ─────────────────────────────────────────────  │
   │ Aucun autre résultat                           │
   └────────────────────────────────────────────────┘

Debounce 300 ms. Au moins 2 caractères. Résultats max 10. Flèches haut/bas pour naviguer, Enter ouvre la fiche.


4.10 Exports (US-OP-11) [V1]

Décisions appliquées : - [Décision : pas d'écran "Exports" isolé — bouton « Exporter » sur les écrans Liste campagnes & Paiements directement] Réduit la friction : l'utilisateur exporte CE qu'il regarde, pas une re-saisie de filtres. - [Décision : modale de confirmation montre le scope + le compte] "Vous allez exporter 1 247 lignes" — évite les surprises.

┌─── Exporter ──────────────────────────────────────┐
│                                                   │
│ Vous allez exporter 1 247 lignes                  │
│ Filtres appliqués :                               │
│  • Agence : Plateau                               │
│  • Période : 01/05 → 22/05/2026                   │
│  • Statut : Envoyée, Clôturée                     │
│                                                   │
│ Format : ○ CSV   ● XLSX                           │
│                                                   │
│ ⓘ Le lien de téléchargement expirera dans 10 min. │
│                                                   │
│         [Annuler]            [Exporter]           │
└───────────────────────────────────────────────────┘

Si > 10 000 lignes : warning inline rouge « L'export sera tronqué à 10 000 lignes sur 12 458. »


4.11 Paramètres cabinet (US-OP-12) [V1] [ADMIN]

┌────────────────────────────────────────────────────────────────────────────┐
│ Administration › Paramètres cabinet                                        │
├────────────────────────────────────────────────────────────────────────────┤
│  ─── Fenêtre de détection de renouvellement ───                            │
│                                                                            │
│  Détecter les contrats à renouveler [ 30 ] jours avant l'échéance.         │
│  Min 7 jours · Max 90 jours                                                │
│  ⓘ La prochaine cohort utilisera la nouvelle valeur.                       │
│                                                                            │
│  ─── Informations légales CIMA ───                                         │
│                                                                            │
│  Nom du cabinet *                                                          │
│  [Cabinet Acme CI                                              ]           │
│                                                                            │
│  Adresse *                                                                 │
│  [_______________________________________________________________]         │
│  [_______________________________________________________________]         │
│                                                                            │
│  Numéro d'agrément CIMA *                                                  │
│  [CIMA-CI-2018-0042                                            ]           │
│                                                                            │
│  ⓘ Ces informations apparaissent dans les mentions légales affichées       │
│    aux clients sur le portail de paiement. La structure des paragraphes    │
│    CIMA obligatoires est gérée par la plateforme.                          │
│                                                                            │
│                                          [Annuler]    [Enregistrer]        │
└────────────────────────────────────────────────────────────────────────────┘

4.12 Gestion utilisateurs (US-OP-13) [V1] [ADMIN]

Décisions appliquées : - [Décision : liste + drawer d'édition (même pattern que contrats)] Cohérence inter-écrans. - [Décision : désactivation = bouton dans le drawer avec modale de confirmation, jamais inline] Action destructive → friction volontaire.

┌────────────────────────────────────────────────────────────────────────────┐
│ Administration › Utilisateurs                       [+ Nouvel utilisateur] │
├────────────────────────────────────────────────────────────────────────────┤
│ 🔍 [Rechercher…]   Rôle: [Tous ▼]   Agence: [Toutes ▼]   Statut: [Actifs ▼]│
├────────────────────────────────────────────────────────────────────────────┤
│ Nom              Email                    Rôle              Agence  Statut │
│ ──────────────── ─────────────────────── ───────────────── ─────── ────── │
│ Marie KONÉ       [email protected]       cabinet_admin    —       ● Actif│
│ Kouassi BROU     [email protected]           agency_operator  Plateau ● Actif│
│ Aya N'GUESSAN    [email protected]            supervisor       —       ● Désa.│
│ …                                                                          │
└────────────────────────────────────────────────────────────────────────────┘

Modale « Nouvel utilisateur » :

┌─── Nouvel utilisateur ────────────────────────────┐
│ Prénom *           [_____________________]        │
│ Nom *              [_____________________]        │
│ Email *            [_____________________]        │
│ Rôle *             [agency_operator      ▼]       │
│ Agence *           [Plateau              ▼]       │
│ (Obligatoire pour agency_operator)                │
│                                                   │
│ ℹ️ Un email d'invitation sera envoyé.             │
│                                                   │
│        [Annuler]            [Créer]               │
└───────────────────────────────────────────────────┘

5. Spécifications d'interaction — 5 états par écran

Pour chaque écran majeur, les 5 états sont loading / empty / error / offline-low-network / success. La liste ci-dessous code chaque écran. Les détails de loading utilisent Skeleton (composant à créer §9), pas des spinners.

5.1 Tableau de bord

État Comportement
Loading 4 skeletons de cartes (220 × 140 px), shimmer 1,2 s. Chiffres remplacés par barres grises.
Empty "Aucune campagne, aucun contrat urgent. [Importer un fichier]" — illustration discrète SVG (< 8 KB).
Error Banner rouge en haut de la page : « Impossible de charger le tableau de bord. [Réessayer] » Détails techniques masqués (audit côté serveur).
Offline/low-network Bandeau global noir : « Connexion perdue. Les données affichées datent de … » + horodatage. Les widgets affichent leur dernière valeur en opacity: 0.6.
Success Compteurs animés en fade-in 200 ms ; horodatage "Dernière synchro : il y a Xs" rafraîchi toutes les 10 s.

5.2 Imports

État Comportement
Loading (en upload) Barre de progression avec % et octets. Boutons désactivés.
Empty (historique) "Aucun import récent. Glissez votre premier fichier ci-dessus."
Error Inline error rouge sous la drop-zone : « Le format du fichier n'est pas reconnu. Acceptés : CSV, XLSX. » Bouton « Réessayer » au lieu de « Parcourir ».
Offline/low-network Drop-zone grisée + tooltip : « Connexion perdue. L'import sera disponible à la reconnexion. » Historique reste en lecture.
Success Toast success 4 s : « Fichier importé : 412 contrats. [Voir les résultats] » + redirection auto vers Résultat.

5.3 Liste campagnes

État Comportement
Loading 8 skeleton rows (DataTable v2). Filter bar reste interactive.
Empty (aucun filtre) "Aucune campagne pour le moment. Importez des contrats puis créez une campagne ad hoc. [+ Nouvelle campagne]"
Empty (filtre appliqué) "Aucun résultat pour ces filtres. [Effacer les filtres]"
Error InlineBanner rouge au-dessus de la table : « Erreur de chargement. [Réessayer] ». Données précédentes restent visibles si présentes.
Offline/low-network Bandeau global + bouton "+ Nouvelle campagne" grisé. Filtres et tri locaux fonctionnent sur les données en cache mémoire.
Success Pagination + count synchronisés. Nouveau résultat de filtre apparaît en fade 150 ms.

5.4 Détail campagne (Brouillon)

État Comportement
Loading Skeleton du header (titre + status pill + boutons) + skeleton des rows.
Empty Si la campagne n'a aucun contrat : « Cette campagne est vide. [Supprimer la campagne] »
Error Bandeau au-dessus + "Réessayer". Les boutons d'action sont grisés.
Offline/low-network Bandeau + bouton « Valider et Envoyer » grisé avec tooltip "Indisponible hors-ligne". Edition contrat possible en lecture seule (le drawer affiche « Connexion requise pour modifier »).
Success Après « Valider et Envoyer » → modale ferme + redirection vers même page mais status Envoyée + toast success.

5.5 Détail campagne (Envoyée)

État Comportement
Loading KPI strip en skeleton (6 carrés) + rows skeleton.
Empty Impossible structurellement (campagne envoyée ⇒ ≥ 1 contrat). Fallback : message générique.
Error KPI strip masquée si erreur d'agrégat, table reste si données présentes. Banner "Mises à jour temps réel indisponibles. [Réessayer]".
Offline/low-network Indicateur live remplacé par "● hors-ligne" en gris. Polling pause. Actions kebab grisées.
Success Indicateur "● live" vert clignotant doux (opacity 0.6 → 1 → 0.6 sur 2 s, respect prefers-reduced-motion). KPI animés ±1 sur changement.

5.6 Anomalies

État Comportement
Loading 3 tabs en skeleton + liste skeleton 6 lignes.
Empty "🎉 Aucune anomalie ouverte. Bon travail." illustration discrète.
Error Banner + retry.
Offline/low-network Liste en cache lisible, boutons « Marquer résolu » grisés.
Success Anomalie résolue → la ligne disparaît avec slide-out 200 ms + compteur du tab décrémenté en fade. Toast success.

5.7 Paramètres / Utilisateurs

Pattern formulaire standard. Loading = skeletons sur les champs. Error = inline sous le champ ou banner global. Success = toast + retour à la liste. Offline = formulaire entièrement grisé + bandeau global.


6. Comportement responsive

Breakpoint Comportement
≥ 1280 px (cible) Layout complet : sidebar 240 px + contenu 1040 px. Tables full-width avec toutes les colonnes. Drawer 420 px à droite.
1024–1279 px Sidebar conserve 240 px ; contenu réduit. Tables peuvent masquer la colonne "Dernière activité" (visible au scroll horizontal ou hover).
768–1023 px (tablet) Sidebar repliée en icônes (60 px), label en tooltip. Drawer devient modal centré 90 % largeur. Dashboard widgets en 1 colonne. Filter bars : scroll horizontal des filtres.
< 768 px Hors scope V1 (PRD §A.4). Page d'accueil mobile : "La console opérateur est optimisée pour ordinateur. Veuillez utiliser un écran ≥ 768 px."

Touch targets ≥ 48 × 48 px partout (même sur desktop — meilleure ergonomie pour souris imprécise).


7. Comportement connectivité

OP est online-first (§C.4 PRD). Ce qui suit décrit le minimum §20 + dégradation gracieuse.

7.1 Bandeau de perte de connexion (global)

┌──────────────────────────────────────────────────────────────────────┐
│ ⚠️ Connexion perdue. Certaines fonctions sont indisponibles.         │
│    Dernière synchro : il y a 32 s.   [Réessayer]                     │
└──────────────────────────────────────────────────────────────────────┘
  • Détection : navigator.onLine + heartbeat GET /api/health toutes les 30 s.
  • Toutes les actions d'écriture sont désactivées (Button disabled) + tooltip "Indisponible hors-ligne".
  • Listes déjà chargées (cache React Query) restent consultables.
  • Notes locales : Input texte du drawer reste actif → écrit dans localStorage (FR-OP-15).

7.2 À la reconnexion

  1. Bandeau passe au vert 2 s : "Connexion rétablie. Synchronisation…"
  2. React Query invalide les queries stale → refresh silencieux des listes visibles.
  3. Notes localStorage non synchronisées → POST batch /operator-notes.
  4. SSE/WS tente une reconnexion (back-off exponentiel : 1 s, 2 s, 4 s, … 30 s max).
  5. Toast info 3 s : "Synchronisation terminée."

7.3 Dégradation par dépendance back-end

Dépendance manquante Effet UI
ING down Imports & sélecteur ad hoc : banner "Service d'ingestion indisponible."
NOTIF down Boutons « Valider et Envoyer », « Envoyer un rappel » grisés + tooltip explicite.
PAY down KPI tunnel paiement remplacés par "—". Page paiements : banner d'erreur.
AUTH down Reset password / création user / révocation lien : grisés.
AUDIT down Toutes les actions d'écriture sont bloquées (audit log synchrone — §C.3 PRD). Banner critique rouge.

7.4 SSE/WebSocket → polling fallback

  • SSE/WS coupé → polling auto GET /campaigns/:id/counters toutes les 30 s.
  • Indicateur live ● live (vert) → ● polling (orange) → ● hors-ligne (gris).
  • Reconnexion SSE en arrière-plan en back-off.

8. Micro-copy FR

Toutes les chaînes passent par i18n. Ce qui suit est la version FR de référence. Aucun fallback EN.

8.1 Navigation & shell

Clé i18n FR
nav.dashboard Tableau de bord
nav.imports Imports
nav.campaigns Campagnes
nav.anomalies Anomalies
nav.payments Paiements
nav.exports Exports
nav.admin Administration
topbar.search.placeholder Rechercher client, téléphone, référence…
topbar.agency.all Toutes les agences
topbar.session.expiring Votre session expire dans {min} min.
topbar.session.stay Rester connecté

8.2 Statuts

Clé FR Couleur token
status.campaign.draft Brouillon --color-text-secondary
status.campaign.sent Envoyée --color-info
status.campaign.closed Clôturée --color-success
status.campaign.cancelled Annulée --color-warning
status.contract.included Inclus --color-text-secondary
status.contract.excluded Exclu --color-text-tertiary
status.contract.notif_pending Notif. en attente --color-info
status.contract.notif_delivered Notif. livrée --color-info
status.contract.notif_failed Notif. échouée --color-error
status.contract.link_opened Lien ouvert --color-info
status.contract.paid Payé --color-success
status.contract.payment_failed Paiement échoué --color-error
status.contract.offplatform_settled Réglé hors-plateforme --color-success
status.contract.link_expired Lien expiré --color-warning
status.contract.link_revoked Lien révoqué --color-warning
urgency.urgent URGENT --color-error

8.3 Erreurs & vides

Clé FR
error.generic Une erreur est survenue. {action}
error.action.retry Réessayer
error.network Connexion perdue. Certaines fonctions sont indisponibles.
error.network.action Réessayer la connexion
error.timeout Le serveur met trop de temps à répondre. {action}
error.permission Vous n'avez pas les droits pour cette action.
empty.campaigns.none Aucune campagne pour le moment. Importez des contrats puis créez une campagne ad hoc.
empty.campaigns.filtered Aucun résultat pour ces filtres.
empty.anomalies.none 🎉 Aucune anomalie ouverte. Bon travail.
empty.search.none Aucun résultat. Essayez un autre terme.

8.4 Confirmations

Clé FR
confirm.send.title Confirmer l'envoi
confirm.send.body Envoyer la campagne à {n} client(s) ?
confirm.send.action Confirmer l'envoi
confirm.cancel_campaign.title Annuler la campagne
confirm.cancel_campaign.body {n} lien(s) seront révoqués. Cette action est irréversible.
confirm.cancel_campaign.action Oui, annuler
confirm.cancel_campaign.cancel Non, garder
confirm.exclude.title Exclure {n} contrat(s) ?
confirm.exclude.action Exclure
confirm.deactivate_user.body {name} ne pourra plus se connecter. Son historique sera conservé.
validation.blocking.title Anomalies bloquantes
validation.blocking.body {n} contrat(s) présentent des anomalies bloquantes. Corrigez les données ou excluez les contrats avant d'envoyer.
validation.blocking.action Voir les {n} anomalies

8.5 Succès

Clé FR
success.import Fichier importé : {n} contrats.
success.campaign.created Campagne créée.
success.campaign.sent Campagne envoyée à {n} client(s).
success.campaign.cancelled Campagne annulée. {n} lien(s) révoqués.
success.contract.corrected Modifications enregistrées.
success.anomaly.resolved Anomalie marquée comme résolue.
success.hp.recorded Paiement hors-plateforme enregistré.
success.reminder.sent Rappel envoyé. Nouveau lien valable 72 h.
success.export.ready Export prêt. [Télécharger] (lien valable 10 min)
success.user.created Utilisateur créé. Email d'invitation envoyé.
success.settings.saved Paramètres enregistrés.

8.6 Champs & formulaires

Clé FR
field.required Champ obligatoire
field.phone.placeholder +225 XX XX XX XX
field.amount.unit XOF
field.date.format JJ/MM/AAAA
validation.phone.invalid Numéro de téléphone invalide. Format attendu : +225 suivi de 10 chiffres.
validation.amount.positive Le montant doit être supérieur à 0.
validation.amount.integer Le montant doit être un entier (pas de décimale).
validation.date.past La date doit être dans le futur.
validation.window.range Doit être entre 7 et 90 jours.

9. Composants — inventaire & specs nouveaux

9.1 Composants existants à réutiliser (frontend-operator/src/components/ui/)

Composant Usage OP Notes
Button Tous CTAs variants : primary (Valider, Confirmer), secondary (Annuler), ghost (Retour, kebab), danger (Annuler la campagne), success (rare). Tailles : lg pour CTA principal de page, md partout ailleurs, sm dans les tables.
Badge Pills de statut + compteurs sidebar Variants à brancher sur tokens de §8.2.
DataTable Liste campagnes, contrats, anomalies, utilisateurs À étendre : tri par colonne, checkboxes bulk, sticky header, skeleton rows.

9.2 Composants à construire (issus du backlog design-system.md)

Priorités spécifiques pour OP V1 :

Composant Priorité OP Specs
Skeleton CRITIQUE Variants : text, row, card, kpi-tile. Shimmer 1,2 s. Respect prefers-reduced-motion → fond statique gris.
EmptyState CRITIQUE Props : illustration?, title, description, action?. Illustration SVG inline < 8 KB.
Pagination HAUTE Cursor-based. Page size selector (25 / 50 / 100). Survit au reload via URL.
DatePicker HAUTE DD/MM/YYYY FR. Range support pour filtres de période. Clavier accessible.
FileUpload HAUTE Drag-drop + click. Progress bar. Reject sur taille/format. Inline error.
Tabs MOYENNE Pour la queue anomalies (Bloquante/Non/Surveillance). Horizontal scroll en tablet.
Drawer CRITIQUE Nouveau : panneau latéral droite 420 px (desktop), modal full sur tablet. Fermeture : ×, Esc, clic backdrop. Focus trap.
InlineBanner HAUTE Variants : info, warning, error, success. Avec ou sans action. Utilisé pour erreurs/avertissements en haut des pages.
GlobalBanner HAUTE Bandeau plein écran en haut (au-dessus de la TopBar) pour offline / maintenance.
KbdShortcut BASSE Affichage Ctrl+K style dans tooltips / menu d'aide.

9.3 Composant nouveau spécifique OP : KpiTile

Variants  : default | warning | error | success
Props     : label, value, secondary (taux %), action (href), trend (optional ±%)
States    : loading (skeleton), error ("—"), success
Anim      : fade-in 200 ms à l'arrivée d'une valeur
A11y      : aria-label = "{label} : {value} {secondary}"

9.4 Composant nouveau spécifique OP : StatusPill

Props     : status (enum string), size (sm | md)
Render    : <span> avec dot colorée + label FR
Tokens    : utilise --color-* selon mapping §8.2
A11y      : aria-label = full status FR
Non-color : la dot est doublée d'un mini-pictogramme (✓, ⏳, ⚠, ✕) pour ne pas dépendre de la couleur seule.

9.5 Accessibilité — règles transversales OP

  • Toutes les actions destructives ont une confirmation modale OU un toast undo 5 s.
  • Tab order suit l'ordre visuel : sidebar → topbar → contenu → action bar.
  • Tables : <th> avec scope="col", aria-sort quand triable.
  • Drawer + Modal : focus trap, restitution du focus au déclencheur à la fermeture.
  • Status pills : aria-label complet ("Statut : Brouillon").

10. UI conformité (CIMA / ARTCI)

10.1 Affichage des mentions CIMA (côté client, généré par OP)

OP n'affiche pas les mentions client — c'est CP qui les rend. Mais OP est la source de vérité de leur contenu : - cabinet_settings.cima_legal_name, cima_legal_address, cima_license_number saisis dans Paramètres (§4.11). - OP affiche dans le formulaire un encart d'aide : « Ces informations apparaissent dans les mentions légales affichées aux clients sur le portail de paiement. » - Statut TV-01 PRÉLIMINAIRE → bannière [LEGAL-REVIEW] visible uniquement aux super-admins plateforme, pas aux cabinet_admin.

10.2 ARTCI — minimisation des données

  • DOB masqué **/**/YYYY dans toutes les vues liste, dévoilé sur fiche contrat individuelle uniquement (FR-OP-06).
  • Question OQ-03 (masquage partiel des téléphones en vues liste) : non implémenté par défaut en V1 — un opérateur a besoin du numéro pour rappeler. Si l'arbitrage légal exige le masquage, ajout d'un toggle "Afficher / masquer" par rôle dans une story V1.x.
  • Tout accès à une fiche contrat individuelle déclenche un AUDIT.event = CONTRACT_VIEWED (vue audit accessible aux supervisors).

10.3 ARTCI — transfert WhatsApp (TV-03 PRÉLIMINAIRE)

Hors code applicatif (cf. PRD §C.1). OP n'affiche aucune mention spécifique à ce sujet en V1. Tracé d'audit suffisant.

10.4 Audit visible

Sur la fiche contrat (drawer), un onglet "Historique" liste les événements d'audit pour ce contrat :

Historique du contrat
─────────────────────
22/05 14:32 · Marie K. (cabinet_admin) · Modification téléphone : +225 07 02 11 22 → +225 07 02 11 99
20/05 11:02 · Système · Notification envoyée (WhatsApp)
20/05 11:00 · K. Brou (agency_operator) · Validation campagne 2026-05-20

11. Phase 3 — Prompts de wireframe par story

Chaque entrée ci-dessous est un brief auto-suffisant pour /wireframe op <STORY-ID>. Le développeur ou le designer peut lancer la commande à la demande sans re-briefer. Chaque prototype DOIT inclure les 5 états et un sélecteur d'état dev-only.

Tokens : utiliser frontend-operator/src/theme/tokens.css (existant). Composants : importer depuis frontend-operator/src/components/ui/ quand disponibles ; sinon créer le composant nécessaire dans le prototype et le marquer pour promotion ultérieure.

OP-001 — Liste campagnes [V0.0.0]

side: OPERATOR
story: OP-001 (V0.0.0 + V1 base list)
screen: /campagnes (liste)
acceptance_criteria:
  - AC1: liste triée par date desc, colonnes Nom/Date/Agence/Contrats/Statut (pill couleur)
  - AC2: filtres Statut / Période / Branche (URL-persistés)
  - AC3: colonne Agence + filtre Agence visibles si supervisor/cabinet_admin
brand: OPERATOR (desktop-first, calm, dense)
viewport: 1280 primary, 1024 supported
states: [loading, empty, filtered-empty, error, offline, success]
key_components: DataTable, StatusPill, FilterBar (à créer), Pagination, Button
microcopy_keys: see §8.2 status.campaign.*, §8.3 empty.campaigns.*
notes:
  - statut "Brouillon" = neutre gris, "Annulée" = warning orange (NOT rouge)
  - "+ Nouvelle campagne" bouton primaire top-right
  - dev-only state selector top-left (caché en prod)

OP-002 — Détail campagne BROUILLON [V0.0.1]

side: OPERATOR
story: OP-002 (V0.0.1 inline edit) extending to V1 US-OP-04
screen: /campagnes/:id (BROUILLON)
acceptance_criteria:
  - AC1: ligne contrat = client, DOB masqué, téléphone, branche, montant, échéance, badge anomalies
  - AC2: bouton « Valider et Envoyer » désactivé si ≥ 1 anomalie bloquante
  - AC3: clic ligne → drawer édition (téléphone, montant, date d'échéance uniquement)
  - AC4: bulk exclude (checkboxes + action bar)
  - AC5: modale de confirmation envoi
brand: OPERATOR
states: [loading, empty, error, offline, success-after-send]
key_components: DataTable (avec checkboxes), Drawer (NOUVEAU), Modal, Checkbox, Button, InlineBanner, StatusPill
critical_ux:
  - action bar bottom apparaît si N≥1 cochés
  - drawer 420px desktop, focus trap, Esc ferme
  - "Valider et Envoyer" sticky top-right, grisé avec compteur si bloquant

OP-003 — Multi-agence routing [V0.0.2]

side: OPERATOR
story: OP-003 routing par agence
screen: TopBar agency selector + scoping global
acceptance_criteria:
  - sélecteur agence visible si supervisor/cabinet_admin
  - agency_operator ne voit que son agence (sélecteur masqué)
  - sélection persiste via URL ?agency=<id>
key_components: Select (existant), TopBar (existant, à étendre)
states: [default, loading-switching, error, offline]

OP-004 — Actions post-envoi & annulation [V0.0.3]

side: OPERATOR
story: OP-004 + V1 US-OP-05/06/07/14
screen: /campagnes/:id (ENVOYÉE)
acceptance_criteria:
  - KPI strip (6 tuiles : Envoyés, Livrés, Liens ouverts, Payés, Hors-plat., Échec) avec % et indicateur ●live
  - Table contrats avec statut pill complet, badge URGENT pour échéance ≤ 7j
  - Menu kebab par ligne (Voir / Envoyer rappel / Régler hors-plateforme / Voir historique)
  - Bouton « + Ajouter un contrat » → sélecteur contrats éligibles
  - Modale « Annuler la campagne » : montre X liens à révoquer
brand: OPERATOR
states: [loading, empty(N/A), error, offline-with-stale-kpi, success]
key_components: KpiTile (NOUVEAU), DataTable, StatusPill, Drawer, Modal, Menu/Dropdown (NOUVEAU)
critical_ux:
  - indicateur ●live (vert pulse 2s) / ●polling (orange) / ●hors-ligne (gris)
  - modale annulation montre compteur d'impact

OP-005 — Dashboard [V1 US-OP-09]

side: OPERATOR
story: US-OP-09
screen: /
acceptance_criteria:
  - 4 widgets : Contrats urgents / Pipeline campagnes / Tunnel paiement / Anomalies bloquantes
  - agency_operator scope agence ; supervisor/cabinet_admin cross-agences avec filtre
brand: OPERATOR
viewport: 1280 (2x2), 768 (1 col)
states: [loading-skeletons, empty, error, offline-stale, success-with-live-update]
key_components: KpiTile, Card, Button, Skeleton
notes: ordre des widgets = urgents/anomalies en haut, pipeline/tunnel en bas

OP-006 — Imports [V1 US-OP-01]

side: OPERATOR
story: US-OP-01
screen: /imports
acceptance_criteria:
  - drop-zone + bouton Parcourir
  - barre de progression upload
  - écran résultat : X valides / Y erreurs / Z anomalies + liens
  - duplicate hash → warning inline avec "Continuer / Annuler"
key_components: FileUpload (NOUVEAU), Progress (NOUVEAU si pas existant), InlineBanner, DataTable (historique), Button
states: [empty-history, uploading, success, duplicate-warning, format-error, offline, network-fail]

OP-007 — Anomalies [V1 US-OP-08]

side: OPERATOR
story: US-OP-08
screen: /anomalies
acceptance_criteria:
  - tabs Bloquantes / Non bloquantes / Surveillance avec compteurs
  - filtres statut + recherche
  - clic ouvre drawer contrat sur champ défectueux highlight
  - Surveillance : note obligatoire pour marquer résolu
key_components: Tabs (NOUVEAU), DataTable, Drawer, Input, Button
states: [loading, empty (🎉), error, offline, success-after-resolve]

OP-008 — Recherche globale [V1 US-OP-10]

side: OPERATOR
story: US-OP-10
screen: TopBar component
acceptance_criteria:
  - shortcut "/" focus la barre
  - debounce 300ms, min 2 chars, max 10 résultats
  - résultats par catégorie (Clients, Téléphones, Références)
  - clavier flèches + Enter
key_components: TopBar, Input, Dropdown
states: [idle, typing-loading, results, no-results, error, offline]

OP-009 — Exports [V1 US-OP-11]

side: OPERATOR
story: US-OP-11
screen: modale depuis liste campagnes ou paiements
acceptance_criteria:
  - rappel filtres appliqués + count
  - format CSV / XLSX
  - warning si > 10 000 lignes (tronqué)
  - lien signé 10 min
key_components: Modal, Radio, Button, InlineBanner
states: [idle, generating, ready-with-link, error, offline-disabled]
visibility: hidden for agency_operator

OP-010 — Paramètres cabinet [V1 US-OP-12]

side: OPERATOR ADMIN
story: US-OP-12
screen: /administration/parametres
acceptance_criteria:
  - input renewal_detection_window_days [7-90]
  - inputs CIMA : nom cabinet, adresse, numéro agrément
  - audit SETTINGS_CHANGED à la sauvegarde
key_components: FormField, Input, Button, InlineBanner
states: [loading, dirty (unsaved warning), error-validation, error-server, offline-disabled, success-toast]
visibility: cabinet_admin only

OP-011 — Utilisateurs [V1 US-OP-13]

side: OPERATOR ADMIN
story: US-OP-13
screen: /administration/utilisateurs
acceptance_criteria:
  - liste utilisateurs cabinet
  - + Nouvel utilisateur (modale)
  - drawer édition (modifier rôle/agence/désactiver/reset password)
  - agency obligatoire si role=agency_operator
key_components: DataTable, Drawer, Modal, FormField, Select, Button
states: [loading, empty (impossible mais fallback), error, offline-disabled, success]

OP-012 — Notes locales (FR-OP-15)

side: OPERATOR
story: FR-OP-15
screen: composant Note dans Drawer contrat
acceptance_criteria:
  - Input texte multi-ligne
  - sauvegarde locale automatique (localStorage)
  - statut "Brouillon local" / "Synchronisé"
  - sync à la reconnexion
key_components: Textarea (NOUVEAU si absent), small status text
states: [empty, typing, local-saved, syncing, synced, conflict-both-kept, offline]

Annexes

A. Mapping écran → user story → composants nouveaux

Écran US Nouveaux composants requis
Dashboard US-OP-09 KpiTile, Skeleton, EmptyState
Imports US-OP-01 FileUpload, InlineBanner, Skeleton
Liste campagnes US-OP-02 StatusPill, Pagination, FilterBar
Nouvelle campagne US-OP-03 Modal (existant), Checkbox row, DateRange
Détail BROUILLON US-OP-04, 07 Drawer, action bar, Modal de confirmation
Détail ENVOYÉE US-OP-05, 06, 07, 14 KpiTile, Menu, live-indicator
Anomalies US-OP-08 Tabs, Drawer
Recherche US-OP-10 Dropdown results, keyboard nav
Exports US-OP-11 Modal (réuse)
Paramètres US-OP-12 FormField, NumberInput
Utilisateurs US-OP-13 Drawer (réuse)

B. Backlog UX V2 (post-V1)

  • Personnalisation de l'ordre des widgets dashboard.
  • Saved views (filtres + tri sauvegardés par utilisateur).
  • Vue Kanban des campagnes (alternative à la liste).
  • Onglet "Activité" temps réel des autres opérateurs (présence).
  • Touche ? ouvre une cheat-sheet des raccourcis clavier.
  • Mode dark complet (tokens prêts, à valider visuellement).

FIN du document

Document maintenu par la personnalité DESIGNER. Toute modification structurelle (ordre des écrans, ajout d'une décision UX majeure) doit être annotée en tête de section avec date et auteur.