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 sousfrontend-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¶
- Principes directeurs OP
- Architecture d'information & navigation
- User Flow Diagram (Mermaid)
- Wireframes par écran
- Spécifications d'interaction — 5 états par écran
- Comportement responsive
- Comportement connectivité
- Micro-copy FR
- Composants — inventaire & specs nouveaux
- UI conformité (CIMA / ARTCI)
- 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 dgo dashboard ;g cgo campagnes ;g ago anomalies ;nnouvelle campagne ;Escferme 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+ heartbeatGET /api/healthtoutes 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¶
- Bandeau passe au vert 2 s : "Connexion rétablie. Synchronisation…"
- React Query invalide les queries stale → refresh silencieux des listes visibles.
- Notes localStorage non synchronisées → POST batch
/operator-notes. - SSE/WS tente une reconnexion (back-off exponentiel : 1 s, 2 s, 4 s, … 30 s max).
- Toast
info3 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/counterstoutes 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
undo5 s. - Tab order suit l'ordre visuel : sidebar → topbar → contenu → action bar.
- Tables :
<th>avecscope="col",aria-sortquand triable. - Drawer + Modal : focus trap, restitution du focus au déclencheur à la fermeture.
- Status pills :
aria-labelcomplet ("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é
**/**/YYYYdans 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.