Aller au contenu

tenant-admin — Design Specification

Module: tenant-admin (TENANT) Sides: [ADMIN] (Papillon platform staff) · [OPERATOR] (Cabinet admin self-service) Date: 2026-05-22 Design tokens: docs/standards/design-system.md Scaffold base: frontend-operator/ — OperatorShell · Sidebar · TopBar · AuthBoundary · Badge · Button · DataTable (all live) Stub to evolve: frontend-operator/src/pages/admin/AdminPage.tsx (tenant list stub) Sidebar routing: /admin already gated to PLATFORM_ADMIN role in Sidebar.tsx


0. Design Decisions Log

These decisions were resolved by the DESIGNER. Each decision references the UX principle that drove it.

D1 — App packaging: same shell, separate route subtrees

Both [ADMIN] and [OPERATOR] cabinet-admin views live inside frontend-operator. Platform admin routes: /admin/tenants/*. Cabinet admin self-service: /tenant/settings/*. Same OperatorShell, same design system. Role-gated via existing AuthBoundary pattern. The two audiences never overlap; a CABINET_ADMIN never sees /admin/*.

Why not a separate micro-frontend? Overkill for ~10 Papillon staff. The PLATFORM_ADMIN role guard is already wired in Sidebar.tsx; extending it to sub-routes is the minimal delta.

UX principle: Hick's Law — fewer entry points, lower cognitive cost. Don Norman's constraint — impossible actions are invisible.

D2 — Tenant creation: 5-step wizard, PENDING tenant persisted after step 1

~25 fields + conditional BYO-DB block + mandatory DPA upload + mandatory NOTIF sender IDs. A single long form (Miller's 7±2 violation) would cause abandonment. The wizard groups by concern: identity → contact/address → isolation → compliance/notifications → recap+activate.

Save strategy: Submitting step 1 creates the PENDING tenant (POST). Steps 2–4 PATCH. Step 5 triggers provisioning. If the admin closes mid-wizard, the PENDING tenant appears in the list — they can resume from the "Documents" tab on the tenant detail page.

Activation gate: The "Activer" button in step 5 is disabled until the checklist (DPA ✓ · SMS Sender ID ✓ · WA Business ID ✓) is green. The checklist is visible at the top of step 5, not buried in an error message after submit.

UX principle: Forgiving interface — forms never lose data on accidental close. Progressive disclosure — step 3 (BYO-DB) only asks for JDBC credentials when the "BYO-DB" radio is selected.

D3 — Provisioning pipeline: live 5-step stepper with per-step retry

The provisioning pipeline (TR-002) has 5 named steps. Each can fail independently. Idempotency means "retry" skips completed steps. Showing a single spinner would hide this structure and make failures opaque. The live stepper makes idempotency legible: a ✅ step won't re-run; a ❌ step shows the French error code and a "Relancer" button.

UX principle: Feedback visibility (Don Norman's feedback principle). The operator needs to know WHICH step failed, not just "an error occurred".

D4 — Suspend: confirmation modal with radio reason + prominent consequence warning

Suspension is irreversible in the short term (it revokes Keycloak access within 30 s). Two-layer friction: (a) radio button for reason code (makes the admin think about WHY, not just click through), (b) explicit danger Button labelled "Confirmer la suspension de [nom cabinet]". No "type the name to confirm" — that pattern is English-origin and feels foreign in French professional UI. The modal opens from a "Suspendre" button on the tenant detail action bar, not from the list (prevents fat-finger on wrong row).

UX principle: Don Norman's error prevention. The cost of confirming matches the cost of the action.

D5 — Tenant list: status filter tabs + proactive DPA expiry banner

The most frequent operator action is "show me suspended tenants" or "show me pending ones". Status tabs (Tous / En attente / Actifs / Suspendus / Supprimés) put this one click away without a filter dropdown. "Supprimés" tab is visually de-emphasized (lighter weight) — rare enough to be secondary. DPA expiry warning displays as a global InlineBanner above the table when ≥1 tenant has DPA expiring in ≤30 days — proactive signal, not discoverable only per-row.

UX principle: Fitt's Law — most-used filters at top. "Design the unhappy paths first" — the DPA warning is an unhappy path that must be unmissable.

D6 — Tenant detail: tabbed layout (Identité | Documents | Provisionnement | Agences | Paramètres)

A tenant has ~50 data points across 4 concerns. Tabs organise by concern without depth (no nested navigation). Both PLATFORM_ADMIN and CABINET_ADMIN land on the "Identité" tab; field editability differs by role (locked fields for cabinet admin, editable by platform admin). This avoids duplicating the page tree.

Cabinet admin route /tenant/settings shows only 3 tabs: Identité | Agences | Paramètres. The platform-admin-only tabs (Documents, Provisionnement) are absent from the cabinet admin view.

D7 — Agency "disable last": preventive UI (disabled button + tooltip)

When activeCount === 1, the "Désactiver" action button for that agency is disabled with aria-disabled="true" and a tooltip "Au moins une agence active est requise par cabinet." The button is rendered but inert. This is preferable to allowing the action and returning a 400 — error prevention > error recovery.

UX principle: Don Norman's constraints. Make impossible actions impossible, not just error-prone.

D8 — Cabinet-admin locked fields: distinct "verrou" visual state

Identity fields (legal name, NCC, agrément, country) that CABINET_ADMIN cannot edit render as read-only Input components with a 🔒 icon and helper text "Modifiable par le support Papillon uniquement." They look like inputs but are not interactive. This communicates "this is data, not a label" while explaining why editing is blocked.

D9 — BYO-DB password: write-only, never echoed back

The db_password field uses type="password" with show/hide toggle during entry. After save, the field renders as a placeholder string "••••••••" with a label "Mot de passe (chiffré — non récupérable)". A "Modifier" button replaces it with a fresh input. The real value is never sent to the UI from the API (API returns "***" for this field).

The retention overrides section in Paramètres shows the hard floor next to each input. A [LEGAL-REVIEW] badge (clickable tooltip) reads: "Durées minimales légales — délibération ARTCI sectorielle assurance CI en cours. Ne pas descendre sous ce seuil sans confirmation juridique." This surfaces the TV-03 compliance assumption at the point of use.


1. User Flow Diagrams

1.1 [ADMIN] Platform Admin — Tenant Lifecycle

flowchart TD
  Start([Admin connecté /admin/tenants]) --> List[ADM-001 Liste des cabinets]

  List -->|+ Nouveau cabinet| Wizard[ADM-002 Wizard création]
  Wizard --> W1[Étape 1 — Identité légale\nPOST → tenant PENDING]
  W1 -->|Suivant| W2[Étape 2 — Contact & Adresse\nPATCH]
  W2 -->|Suivant| W3[Étape 3 — Isolation BYO / Shared\nPATCH]
  W3 -->|Suivant| W4[Étape 4 — Conformité & Notifications\nPATCH + DPA upload]
  W4 -->|Suivant| W5[Étape 5 — Récapitulatif]
  W5 -->|Activer — checklist verte| Prov[ADM-003 onglet Provisionnement\nPipeline live 5 steps]
  Prov -->|Succès| Active[Tenant ACTIF → toast → retour liste]
  Prov -->|Échec step N| Retry{Relancer step N ?}
  Retry -->|Oui| Prov
  Retry -->|Non| PendingDetail[ADM-003 Détail tenant PENDING]
  W1 -->|Fermer avant fin| PendingList[Tenant PENDING dans liste — reprise possible]

  List -->|Clic sur ligne| Detail[ADM-003 Détail tenant]
  Detail -->|Onglet Identité| Identity[Voir et Modifier identité]
  Detail -->|Onglet Documents| Docs[Gestion documents versionnés]
  Docs -->|Upload nouveau PDF| DocUpload[Nouvelle version — ancienne archivée]
  Docs -->|DPA expirant 30j| DPAWarn[InlineBanner avertissement]
  Detail -->|Onglet Provisionnement| ProvTab[Statut pipeline]
  ProvTab -->|Relancer si PENDING ou FAILED| Prov
  Detail -->|Onglet Agences| AgencyTab[Liste agences]
  AgencyTab -->|+ Nouvelle agence| AgencyModal[Modal création agence]
  AgencyTab -->|Désactiver — active_count > 1| AgencyDisable[AgencyDisabled]
  AgencyTab -->|Désactiver — active_count = 1| AgencyBlocked[Bouton désactivé + tooltip]
  Detail -->|Onglet Paramètres| SettingsTab[Paramètres tenant]

  Detail -->|Status ACTIVE → Suspendre| SuspModal[ADM-004 Modal suspension]
  SuspModal -->|Annuler| Detail
  SuspModal -->|Confirmer| Suspended[Tenant SUSPENDU]
  Suspended -->|Réactiver| ReactModal[ADM-005 Modal réactivation]
  ReactModal -->|Confirmer| Active2[Tenant ACTIF à nouveau]

  List -->|Onglet Suspendus| SuspList[Tenants suspendus]
  SuspList -->|Grace restante affichée| GraceInfo[X jours avant suppression auto]
  SuspList -->|90j dépassés — job nuit| AutoDeleted[Tenant SUPPRIMÉ soft]
  AutoDeleted -->|Onglet Supprimés| DeletedView[Lecture seule — audit trail accessible]

1.2 [OPERATOR] Cabinet Admin — Self-Service

flowchart TD
  Start([Cabinet admin connecté]) --> Settings[/tenant/settings — Identité]
  Settings -->|Modifier logo| LogoUpload[FileUpload PNG/SVG ≤200Ko]
  LogoUpload -->|Fichier valide| LogoSaved[Logo enregistré → TenantSettingsChanged]
  LogoUpload -->|Invalide taille/format/dims| LogoError[Erreur inline validation]
  Settings -->|Modifier couleur hex| ColorPicker[Input hex + aperçu swatch]
  Settings -->|Modifier support_email| EmailField[Input email]
  Settings -->|Sauvegarder| Saved[Toast succès]

  Settings -->|Onglet Agences| Agencies[/tenant/settings/agences]
  Agencies -->|+ Nouvelle agence| AgencyCreate[Modal — code/nom/ville requis]
  AgencyCreate -->|Code unique| AgencyCreated[Agence créée — AgencyCreated]
  AgencyCreate -->|Code dupliqué| AgencyDupError[Erreur — Code agence déjà utilisé]
  Agencies -->|Modifier ligne| AgencyEdit[Modal pré-remplie]
  Agencies -->|Désactiver — n>1 active| AgencyDisabled[Soft-disable → AgencyDisabled]
  Agencies -->|Désactiver — n=1 active| AgencyBlocked[Bouton disabled + tooltip]
  Agencies -->|Réactiver| AgencyReactivated[Status ACTIVE]

  Settings -->|Onglet Paramètres| Params[/tenant/settings/parametres]
  Params -->|Fenêtre renouvellement hors 7-90| BoundError[Erreur borne inline]
  Params -->|Canal préféré| ChannelRadio[WHATSAPP / SMS RadioGroup]
  Params -->|Template WA| WAInput[Texte libre]
  Params -->|Rétention sous plancher légal| RetentionError[Erreur plancher + LEGAL-REVIEW]
  Params -->|Sauvegarder| SettingsSaved[TenantSettingsChanged]

2. Screen Inventory

[ADMIN] — Platform staff (/admin/tenants/*)

ID Route Stories UI-bearing
ADM-001 /admin/tenants US-01 (list)
ADM-002 /admin/tenants/nouveau US-01, US-02, US-03 (wizard)
ADM-003 /admin/tenants/:id US-01..06 (detail + tabs)
ADM-004 Modal sur ADM-003 US-04 (suspend)
ADM-005 Modal sur ADM-003 US-04 (reactivate)

[OPERATOR] — Cabinet admin (/tenant/settings/*)

ID Route Stories UI-bearing
OP-001 /tenant/settings US-07 (branding)
OP-002 /tenant/settings/agences US-08 (agencies)
OP-003 /tenant/settings/parametres US-09 (settings)

3. Wireframes

All wireframes are desktop-first (1440px). Responsive deltas noted per screen. Components referenced by name from docs/standards/design-system.md. New components spec'd in §8.


ADM-001 — Liste des cabinets

Shaped by: D5 (status tabs + DPA banner)

┌─────────────────────────────────────────────────────────────────────┐
│ OperatorShell (Sidebar 240px + TopBar)                              │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │  Administration plateforme          [+ Nouveau cabinet] (primary)│ │
│ │  Gestion des tenants (cabinets)                                  │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │  ⚠  2 DPA expirent dans moins de 30 jours. → Voir les cabinets  │ │  ← InlineBanner warning (conditionnel)
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │  [🔍 Rechercher un cabinet…]   [Plan : Tous ▾]                  │ │
│ │                                                                  │ │
│ │  [Tous (12)]  [En attente (3)]  [Actifs (7)]  [Suspendus (2)]  [Supprimés (0)]  │ │  ← Tabs, pill variant
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │  12 cabinets                                                     │ │
│ │                                                                  │ │
│ │  Raison sociale ↕  Plan ↕  Pays  Statut        Activé le ↕  Actions │ │
│ │ ─────────────────────────────────────────────────────────────── │ │
│ │  Cabinet Akwaba    GROWTH  CI    ● Actif        12/01/2026   [Voir]  │ │
│ │  Broker Plus       STARTER CI    ● Actif        08/03/2026   [Voir]  │ │
│ │  Société Nsia CI   ENTERPRISE CI ● Actif        01/02/2026   [Voir]  │ │
│ │  Mutuelle Abidjan  STARTER CI    ⏳ En attente  —            [Voir]  │ │
│ │  AssurAfrique SARL GROWTH  CI    ⚠ Suspendu     23/11/2025   [Voir]  │ │
│ │                                                                  │ │
│ │  ◄  1  2  ►   Afficher 20 ▾                                     │ │  ← Pagination
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

Status → Badge mapping:

Statut Badge variant Icône Label
PENDING info En attente
ACTIVE success Actif
SUSPENDED warning Suspendu
DELETED neutral Supprimé

Supprimés tab: rows rendered with opacity: 0.6, italic raison sociale, no "Voir" button — replaced by "Audit" (ghost variant, opens read-only audit trail modal).


ADM-002 — Wizard création tenant (5 étapes)

Shaped by: D2 (wizard, PENDING after step 1), D9 (BYO-DB pw write-only)

┌──────────────────────────────────────────────────────────────────────┐
│ ← Retour à la liste            Nouveau cabinet                       │
├──────────────────────────────────────────────────────────────────────┤
│  ① Identité  ──  ② Contact  ──  ③ Isolation  ──  ④ Conformité  ──  ⑤ Récap  │  ← StepIndicator
├──────────────────────────────────────────────────────────────────────┤

Étape 1 — Identité légale

│ ┌─ Identité légale ──────────────────────────────────────────────┐  │
│ │  Raison sociale *           [________________________________]  │  │
│ │  Nom commercial *           [________________________________]  │  │
│ │  Pays *                     [Côte d'Ivoire (CI) ▾ — désactivé] │  │  ← Select disabled, V1 lock
│ │                             ⓘ V1 : Côte d'Ivoire uniquement    │  │
│ │  Numéro d'agrément CIMA *   [________________________________]  │  │
│ │  NCC *                      [________________________________]  │  │
│ │                             ⓘ Requis pour la facturation FNE   │  │
│ │  Plan tarifaire *           [STARTER ▾]                        │  │
│ └────────────────────────────────────────────────────────────────┘  │
│                                                [Annuler] [Suivant ▸] │

Étape 2 — Contact & Adresse

│ ┌─ Contact principal ────────────────────────────────────────────┐  │
│ │  Nom complet *              [________________________________]  │  │
│ │  Email *                    [________________________________]  │  │
│ │  Téléphone *                [+225 ▾] [_____________________]   │  │  ← préfixe +225 verrouillé V1
│ └────────────────────────────────────────────────────────────────┘  │
│ ┌─ Adresse postale ──────────────────────────────────────────────┐  │
│ │  Adresse (ligne 1) *        [________________________________]  │  │
│ │  Adresse (ligne 2)          [________________________________]  │  │
│ │  Ville *                    [________________________________]  │  │
│ │  Code postal                [________________________________]  │  │
│ └────────────────────────────────────────────────────────────────┘  │
│                                          [◂ Précédent] [Suivant ▸]  │

Étape 3 — Isolation base de données

│ ┌─ Profil d'isolation ───────────────────────────────────────────┐  │
│ │  ● Base partagée Papillon (recommandé)                         │  │
│ │    Isolation par tenant_id. Aucune configuration requise.      │  │
│ │                                                                │  │
│ │  ○ BYO-DB (clause contractuelle requise)                       │  │
│ │    Fournissez votre propre base PostgreSQL.                    │  │
│ │                                                                │  │
│ │  ┌─ Connexion BYO-DB (visible seulement si BYO sélectionné) ─┐ │  │
│ │  │  JDBC URL *           [jdbc:postgresql://host:5432/db]     │ │  │
│ │  │  Utilisateur *        [______________________________]     │ │  │
│ │  │  Mot de passe *       [••••••••••••••••] [👁 Afficher]    │ │  │  ← type=password + toggle
│ │  │  Taille du pool *     [10]                                 │ │  │
│ │  │  ⚠ Les identifiants sont chiffrés (KEK). Ils ne seront    │ │  │
│ │  │    jamais affichés après enregistrement.                   │ │  │
│ │  └────────────────────────────────────────────────────────────┘ │  │
│ └────────────────────────────────────────────────────────────────┘  │
│                                          [◂ Précédent] [Suivant ▸]  │

Étape 4 — Conformité & Notifications

│ ┌─ Documents de conformité ──────────────────────────────────────┐  │
│ │  DPA signé (PDF) *                                             │  │
│ │  ┌──────────────────────────────────────────────────────────┐  │  │
│ │  │  📂 Glissez votre PDF ici ou cliquez pour sélectionner   │  │  │  ← FileUpload
│ │  │     Taille max : 25 Mo — PDF uniquement                  │  │  │
│ │  └──────────────────────────────────────────────────────────┘  │  │
│ │  Date de signature *     [JJ/MM/AAAA]                          │  │
│ │  Date d'expiration       [JJ/MM/AAAA]    ⓘ Optionnel          │  │
│ └────────────────────────────────────────────────────────────────┘  │
│ ┌─ Identifiants notifications ───────────────────────────────────┐  │
│ │  ⚠ Requis pour l'activation du cabinet.                       │  │
│ │  SMS Sender ID *         [______________________________]      │  │
│ │                          ⓘ Identifiant expéditeur SMS approuvé│  │
│ │  WA Business Account ID *[______________________________]      │  │
│ │                          ⓘ Trouvable dans Meta Business Manager│  │
│ │  Template WhatsApp       [______________________________]      │  │
│ │                          ⓘ ID du template Meta approuvé       │  │
│ └────────────────────────────────────────────────────────────────┘  │
│                                          [◂ Précédent] [Suivant ▸]  │

Étape 5 — Récapitulatif & Activation

│ ┌─ Checklist d'activation ───────────────────────────────────────┐  │
│ │  ✅ DPA signé uploadé                                          │  │
│ │  ✅ SMS Sender ID renseigné                                    │  │
│ │  ✅ WA Business Account ID renseigné                           │  │
│ │  ✅ Identité légale complète                                   │  │
│ │  ✅ Contact principal renseigné                                │  │
│ └────────────────────────────────────────────────────────────────┘  │
│ ┌─ Résumé ───────────────────────────────────────────────────────┐  │
│ │  Cabinet Akwaba Assurances · GROWTH · CI                       │  │
│ │  Agrément CIMA : IB-CI-2019-0042 · NCC : 1234567 X            │  │
│ │  Contact : Jean Konan · [email protected] · +225 07 08 09 10      │  │
│ │  Adresse : 12 av. du Commerce, Abidjan, CI                     │  │
│ │  Isolation : Base partagée Papillon                            │  │
│ │  DPA : dpa-akwaba-2026.pdf (signé le 15/01/2026)              │  │
│ └────────────────────────────────────────────────────────────────┘  │
│  ⓘ L'activation créera l'agence PRINCIPAL et enverra un lien        │
│     d'activation à [email protected].                                   │
│                               [◂ Précédent] [✓ Activer le cabinet]  │  ← primary, disabled si checklist incomplète

ADM-003 — Détail tenant (page onglets)

Shaped by: D6 (tabs), D8 (locked fields), D3 (provisioning stepper)

En-tête commun à tous les onglets:

┌──────────────────────────────────────────────────────────────────────┐
│ ← Cabinets     Cabinet Akwaba Assurances   ● Actif   GROWTH         │
│                                 [Modifier]  [Suspendre ▾] (danger)   │
├──────────────────────────────────────────────────────────────────────┤
│  [Identité]  [Documents]  [Provisionnement]  [Agences]  [Paramètres] │  ← Tabs, underline variant
├──────────────────────────────────────────────────────────────────────┤

Onglet Identité (lecture):

│ ┌─ Identité légale ─────────────────────────────────────────────┐  │
│ │  Raison sociale       Cabinet Akwaba Assurances               │  │
│ │  Nom commercial       Akwaba Assurances                       │  │
│ │  Pays                 🇨🇮 Côte d'Ivoire (CI)                  │  │
│ │  Agrément CIMA        IB-CI-2019-0042                         │  │
│ │  NCC                  1234567 X                               │  │
│ │  Plan                 GROWTH                                  │  │
│ │  Slug                 cabinet-akwaba-assurances               │  │
│ └───────────────────────────────────────────────────────────────┘  │
│ ┌─ Contact principal ───────────────────────────────────────────┐  │
│ │  Nom        Jean Konan                                        │  │
│ │  Email      [email protected]                                    │  │
│ │  Téléphone  +225 07 08 09 10                                  │  │
│ └───────────────────────────────────────────────────────────────┘  │
│ ┌─ Identité visuelle ───────────────────────────────────────────┐  │
│ │  Logo         [Aperçu 64×64]   [email protected]              │  │
│ │  Couleur      ██ #3B7A57                                      │  │
│ └───────────────────────────────────────────────────────────────┘  │
│  Créé le 12/01/2026 · Activé le 12/01/2026 · Modifié le 03/05/2026 │

Onglet Documents:

│  ⚠ Le DPA expire dans 18 jours (15/06/2026). Pensez à le renouveler.│  ← InlineBanner warning (conditionnel)
│                                                                      │
│ ┌─ DPA signé ──────────────────────── v2 (active) ─────────────┐  │
│ │  Uploadé le 10/01/2026 par Admin Papillon                     │  │
│ │  SHA-256 : a3f2...9c14                                        │  │
│ │  [📥 Télécharger]  [↑ Nouvelle version]                       │  │
│ │  ▼ Historique (1 version archivée)                            │  │
│ │    v1 — 12/01/2025 — a1b2…  [📥 Télécharger]                │  │
│ └───────────────────────────────────────────────────────────────┘  │
│ ┌─ Agrément CIMA ──────────────────── v1 (active) ─────────────┐  │
│ │  Uploadé le 12/01/2026  SHA-256 : b4e1…2f09                  │  │
│ │  [📥 Télécharger]  [↑ Nouvelle version]                       │  │
│ └───────────────────────────────────────────────────────────────┘  │
│ ┌─ Enregistrement FNE ───────────────── Non uploadé ───────────┐  │
│ │  ⓘ Document facultatif pour la facturation FNE               │  │
│ │  [↑ Uploader]                                                 │  │
│ └───────────────────────────────────────────────────────────────┘  │

Onglet Provisionnement:

│  Dernier provisionnement : 12/01/2026 à 14:32 — ✅ Succès           │
│                                                                      │
│  ✅ 1. Profil pays (CI)                        0.1 s                │
│  ✅ 2. Base de données (Shared — aucune conf.) 0.0 s                │
│  ✅ 3. Keycloak — client créé                  2.3 s                │
│  ✅ 4. Agence PRINCIPAL créée                  0.4 s                │
│  ✅ 5. Invitation admin envoyée (via AUTH)     0.2 s                │
│                                                                      │
│  État d'échec (exemple) :                                            │
│  ✅ 1. Profil pays (CI)                        0.1 s                │
│  ✅ 2. Base de données (BYO)                   8.4 s                │
│  ❌ 3. Keycloak — KEYCLOAK_UNREACHABLE                              │
│     Le service d'authentification est indisponible. Réessayez.       │
│     [Relancer à partir de l'étape 3]                                 │
│  ⊘ 4. Agence PRINCIPAL (ignorée — step 3 bloqué)                    │
│  ⊘ 5. Invitation admin (ignorée — step 3 bloqué)                    │

Onglet Agences:

│                                              [+ Nouvelle agence]    │
│  Code ↕  Nom ↕              Ville ↕   Statut    Créée le   Actions  │
│ ─────────────────────────────────────────────────────────────────   │
│  PRINCIPAL  Akwaba Assur.   Abidjan  ● Active  12/01/26  [Modifier] [Désactiver 🔒]  ← disabled si seule active
│  ABJ-NORD   Agence Nord     Cocody   ● Active  03/03/26  [Modifier] [Désactiver]
│  ABJ-SUD    Agence Sud      Marcory  ⊘ Inactive 15/04/26 [Modifier] [Réactiver]

Onglet Paramètres (identique à OP-003, mais tous champs éditables par platform admin):

│  (cf. wireframe OP-003 — seule différence : platform admin peut       │
│   modifier renewal_window_days et les rétentions sans restriction     │
│   de rôle ; CABINET_ADMIN voit les mêmes champs mais bounded)        │

Tenant SUSPENDU — bannière dans tous les onglets:

│  ⚠ Ce cabinet est suspendu depuis le 15/04/2026.                    │
│     Sans réactivation, il sera supprimé le 14/07/2026.              │
│     Les données d'audit et factures seront conservées 10 ans.        │
│     Les données personnelles des assurés seront supprimées.          │

Tenant DELETED — bannière + tous les champs read-only:

│  ✕ Ce cabinet a été supprimé le 14/07/2026.                         │
│    Les données personnelles ont été purgées. Seul l'audit subsiste.  │

ADM-004 — Modal Suspension

Shaped by: D4 (reason radio + danger confirm)

┌──────────────────────────────────────────────────────────────────────┐
│  ✕   Suspendre Cabinet Akwaba Assurances                             │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ╔══════════════════════════════════════════════════════════════╗    │
│  ║ ⛔ Cette action révoque immédiatement l'accès de tous les    ║    │
│  ║    opérateurs du cabinet et bloque les nouveaux paiements.   ║    │
│  ╚══════════════════════════════════════════════════════════════╝    │
│                                                                      │
│  Motif de suspension *                                               │
│  ● Facturation impayée                                               │
│  ○ Raison légale ou réglementaire                                    │
│  ○ Demande du cabinet                                                │
│  ○ Autre                                                             │
│                                                                      │
│  Détail (facultatif)                                                 │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │                                                                │  │
│  └────────────────────────────────────────────────────────────────┘  │
│                                                                      │
│                [Annuler]    [⛔ Confirmer la suspension]  (danger)   │
└──────────────────────────────────────────────────────────────────────┘

ADM-005 — Modal Réactivation

┌──────────────────────────────────────────────────────────────────────┐
│  ✕   Réactiver Cabinet Akwaba Assurances                             │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ✅ La réactivation rétablira l'accès des opérateurs                 │
│     et relancera les campagnes planifiées.                           │
│                                                                      │
│  Suspendu depuis : 15/04/2026 (37 jours)                            │
│  Motif initial : Facturation impayée                                 │
│                                                                      │
│                [Annuler]    [✅ Réactiver le cabinet]  (success)     │
└──────────────────────────────────────────────────────────────────────┘

OP-001 — Identité du cabinet (cabinet admin)

Shaped by: D8 (locked fields), D1 (cabinet admin subtree)

┌──────────────────────────────────────────────────────────────────────┐
│ OperatorShell (Sidebar — "Mon cabinet" nav item)                     │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Paramètres du cabinet                                            │ │
│ │ [Identité]  [Agences]  [Paramètres]                              │ │  ← Tabs, 3 onglets seulement
│ ├──────────────────────────────────────────────────────────────────┤ │
│ │ ┌─ Identité légale — lecture seule ──────────────────────────┐  │ │
│ │ │  🔒 Raison sociale   Cabinet Akwaba Assurances             │  │ │  ← Input read-only + icône verrou
│ │ │  🔒 Nom commercial   Akwaba Assurances                     │  │ │
│ │ │  🔒 Pays             🇨🇮 Côte d'Ivoire                     │  │ │
│ │ │  🔒 Agrément CIMA    IB-CI-2019-0042                       │  │ │
│ │ │  🔒 NCC              1234567 X                             │  │ │
│ │ │     ⓘ Modifiable par le support Papillon uniquement        │  │ │
│ │ └────────────────────────────────────────────────────────────┘  │ │
│ │ ┌─ Identité visuelle ────────────────────────────────────────┐  │ │
│ │ │  Logo du cabinet                                           │  │ │
│ │ │  ┌────────────────────────────────────────────────────────┐│  │ │
│ │ │  │  [Aperçu logo actuel 96×96 px]                         ││  │ │
│ │ │  │  📂 Glissez votre logo ici ou cliquez pour sélectionner││  │ │  ← FileUpload
│ │ │  │     PNG ou SVG · ≤ 200 Ko · min 64×64 px               ││  │ │
│ │ │  └────────────────────────────────────────────────────────┘│  │ │
│ │ │  Couleur principale    [██ #3B7A57] [__________]  Aperçu ▶ │  │ │
│ │ │  Email d'assistance    [[email protected]          ]        │  │ │
│ │ │                        ⓘ Affiché dans le portail client     │  │ │
│ │ └────────────────────────────────────────────────────────────┘  │ │
│ │                                               [Sauvegarder]     │ │  ← sticky bottom on tablet
│ └──────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘

OP-002 — Gestion des agences

Shaped by: D7 (preventive disable last agency)

┌──────────────────────────────────────────────────────────────────────┐
│ Paramètres du cabinet                                                │
│ [Identité]  [Agences]  [Paramètres]                                  │
├──────────────────────────────────────────────────────────────────────┤
│  3 agences                                  [+ Nouvelle agence]      │
│  Code ↕  Nom ↕              Ville ↕   Statut    Créée le   Actions   │
│ ─────────────────────────────────────────────────────────────────    │
│  PRINCIPAL  Akwaba Assur.   Abidjan  ● Active  12/01/26  [Modifier] [Désactiver — disabled 🔒 si n=1]
│  ABJ-NORD   Agence Nord     Cocody   ● Active  03/03/26  [Modifier] [Désactiver]
│  ABJ-SUD    Agence Sud      Marcory  ⊘ Inactive 15/04/26 [Modifier] [Réactiver]

Modal Nouvelle agence / Modifier agence:

┌──────────────────────────────────────────────────────────────────┐
│  ✕   Nouvelle agence                                             │
├──────────────────────────────────────────────────────────────────┤
│  Code *          [________________________]                       │
│                  ⓘ Identifiant unique au sein du cabinet         │
│  Nom *           [________________________]                       │
│  Ville *         [________________________]                       │
│  Adresse         [________________________]                       │
│  Téléphone       [+225 ▾] [_______________]                       │
│  Responsable     [________________________]                       │
│                                                                   │
│                [Annuler]    [Créer l'agence]  (primary)           │
└──────────────────────────────────────────────────────────────────┘

OP-003 — Paramètres du cabinet

Shaped by: D10 ([LEGAL-REVIEW] inline)

┌──────────────────────────────────────────────────────────────────────┐
│ Paramètres du cabinet                                                │
│ [Identité]  [Agences]  [Paramètres]                                  │
├──────────────────────────────────────────────────────────────────────┤
│ ┌─ Renouvellement ──────────────────────────────────────────────┐   │
│ │  Fenêtre de renouvellement           [30]  jours              │   │
│ │                                      ⓘ Entre 7 et 90 jours   │   │
│ └───────────────────────────────────────────────────────────────┘   │
│ ┌─ Notifications ───────────────────────────────────────────────┐   │
│ │  Canal par défaut   ● WhatsApp   ○ SMS                        │   │
│ │  Template WA        [HX_template_renewal_v2                 ] │   │
│ │  Plafond quotidien  [500]  messages/jour                      │   │
│ └───────────────────────────────────────────────────────────────┘   │
│ ┌─ Niveaux d'authentification ─────────────────────────────────┐   │
│ │  Niveau requis    [1 — Lien signé ▾]  ⓘ Niveaux 2/3 en V2   │   │  ← Select disabled
│ └───────────────────────────────────────────────────────────────┘   │
│ ┌─ Rétention des données  [LEGAL-REVIEW ⓘ] ───────────────────┐   │
│ │  Dossiers sources   ≥ [90 ]  jours   (plancher : 90 jours)   │   │
│ │  Journaux d'audit   ≥ [10 ]  années  (plancher : 10 ans)     │   │
│ │  Quittances         ≥ [10 ]  années  (plancher : 10 ans)     │   │
│ │  ⚠ Durées minimales légales — délibération ARTCI en cours.   │   │
│ │    Ne modifiez pas sans avis juridique.                       │   │
│ └───────────────────────────────────────────────────────────────┘   │
│  Papillon agit en sous-traitant. [Voir la liste des sous-traitants ↗]│  ← Compliance link §9.2
│                                                      [Sauvegarder]  │
└──────────────────────────────────────────────────────────────────────┘

4. Interaction Specs — 5 états par écran

ADM-001 — Liste des cabinets

État Comportement UI
loading 5 rangées Skeleton dans DataTable (colonnes proportionnelles). Bouton "+ Nouveau cabinet" reste actif. Onglets affichés, compteurs en Skeleton.
empty Onglet "Tous" : EmptyState + "Aucun cabinet enregistré. Créez le premier cabinet pour démarrer." + [+ Créer le premier cabinet]. Onglet filtré : "Aucun cabinet {statut} pour le moment." sans illustration.
error InlineBanner error : "Impossible de charger les cabinets. Vérifiez votre connexion." + [Réessayer] (Button ghost). Filtre et bouton création restent actifs.
low-network Toast warning : "Connexion lente — données en cours de chargement." DataTable affiche données en cache TanStack Query (stale-while-revalidate) avec bandeau de données non actualisées.
success État nominal : tableau données fraîches, compteurs onglets à jour, InlineBanner DPA visible si applicable.

ADM-002 — Wizard création

État Comportement UI
loading Soumission étape : bouton "Suivant" → variant loading (spinner), formulaire disabled. Durée typique < 1 s (PATCH).
empty N/A — wizard démarre toujours vide.
error Champ : Input error + message inline (ex. "NCC déjà utilisé : Cabinet Konan SARL. Contactez le superviseur."). Réseau : Toast error + formulaire non démonté (données préservées). Bouton "Suivant" redevient cliquable. Similar name : Modal confirmation "Cabinets similaires détectés : {names}. Continuer ?"
low-network InlineBanner warning : "Connexion instable — votre saisie est conservée." Step 4 (upload DPA) bloque si hors ligne : "Upload impossible hors ligne. Reconnectez-vous."
success Step 5 → Activer → ProvisioningPipeline s'ouvre inline → toast "Cabinet Akwaba Assurances activé." + redirect ADM-003.

ADM-003 — Détail tenant

État Comportement UI
loading Skeleton dans chaque section de l'onglet actif. Tabs rendus immédiatement (structure visible). En-tête (raison sociale + badge) chargé en priorité.
empty Onglet Documents : card FNE "Non uploadé". Onglet Agences vide : EmptyState "L'agence PRINCIPAL est créée automatiquement à l'activation."
error InlineBanner error dans l'onglet actif + [Réessayer]. L'en-tête reste visible.
low-network Toast warning passif. Formulaire modification bloqué avec InlineBanner "Modification impossible hors ligne. Reconnectez-vous."
success Données fraîches. Toast si action vient d'être effectuée (suspension, réactivation, upload doc).

ADM-004 / ADM-005 — Modals Suspend / Réactiver

État Comportement UI
loading Bouton confirmer → variant loading. Modal reste ouverte pendant l'API call.
empty N/A
error InlineBanner error dans la modal : "Impossible de suspendre. Réessayez." Bouton débloqué.
low-network Bouton confirmer bloqué : "Connexion requise pour cette action."
success Modal se ferme. Toast "Cabinet suspendu." / "Cabinet réactivé." Badge sur la page détail mis à jour.

OP-001 — Identité cabinet

État Comportement UI
loading Skeleton pour aperçu logo + champs.
empty Logo non uploadé : placeholder gris avec initiales + "Aucun logo — uploadez un PNG ou SVG."
error Logo : erreur inline sous FileUpload. Save : Toast error "Impossible de sauvegarder. Réessayez."
low-network InlineBanner warning. Save mis en file offline queue avec indicateur sync en attente.
success Toast success "Identité visuelle mise à jour." Aperçu logo rafraîchi.

OP-002 — Agences

État Comportement UI
loading Skeleton 3 rangées dans DataTable.
empty EmptyState "Aucune agence créée. L'agence PRINCIPAL est créée automatiquement lors de l'activation."
error Modal : Input error sous "Code" si doublon. Submit réseau : Toast error.
low-network Création d'agence bloquée : InlineBanner "Création impossible hors ligne." (validation serveur requise immédiatement).
success Toast success "Agence ABJ-NORD créée." Modal se ferme, DataTable rafraîchit.

OP-003 — Paramètres

État Comportement UI
loading Skeleton dans chaque section.
empty N/A — paramètres toujours pré-remplis avec valeurs par défaut.
error Champ hors borne → Input error + message inline immédiat (client-side). Réseau → Toast error.
low-network InlineBanner warning. Save mis en file offline queue.
success Toast success "Paramètres enregistrés."

5. Comportement responsive

TENANT est operator-side. Desktop-primary. Cabinet admin peut accéder depuis tablette.

Breakpoint [ADMIN] (platform admin) [OPERATOR] (cabinet admin)
360–767 (mobile) InlineBanner "Veuillez utiliser un écran plus grand pour l'administration plateforme." Navigation encore fonctionnelle. Tabs scroll horizontal. Card layout 1 col. Formulaires 1 col. FileUpload full-width. Bouton sauvegarder sticky bottom.
768–1023 (tablet) Sidebar collapsed par défaut. DataTable scroll horizontal. Wizard en colonne unique. Tabs scroll horizontal. Full experience. 2-col field grid. Sidebar collapsed.
1024–1439 (desktop) Sidebar expanded. DataTable toutes colonnes. Wizard 2-col fields (étapes 1, 2). Confortable. 2-col field grid.
1440+ (large desktop) Contenu centré, max-width: var(--content-max-width). Idem.

Règle Tabs : overflow-x: auto avec gradient-fade droit si overflow. Touch target minimum 48 px height.

Règle Modal : Width 560 px sur desktop, 100 % viewport minus 32 px sur mobile, centré vertically.


6. Comportement connectivité (operator-side online-first)

TENANT n'est pas soumis aux règles customer low-bandwidth. Néanmoins : les admins en déplacement utilisent parfois une 4G instable.

Scénario Comportement
Chargement lent TanStack Query stale-while-revalidate : données cache affichées immédiatement. Toast warning "Données non actualisées — chargement en cours."
Perte de connexion mid-wizard Données formulaire préservées (état React local). InlineBanner "Connexion perdue. Votre saisie est conservée." Bouton "Suivant" bloqué. Retry automatique sur navigator.onLine = true.
Upload DPA lent Progress bar dans FileUpload (chunked 5 Mo). "Envoi en cours… 60 %". Annulation possible. Sur erreur réseau : "Échec de l'envoi. Réessayez (votre fichier est conservé)" — fichier ne disparaît pas.
Provisionnement > 30 s ProvisioningPipeline affiche le step en cours avec spinner. Bouton "Annuler" (ghost danger) — n'annule pas les steps complétés (idempotent).
BYO-DB validation lente Step 2 du pipeline affiche "Test de connexion… 12 s". Pas de timeout UI avant 60 s (NFR-03).
Reconnexion Toast success "Connexion rétablie." Formulaires débloqués. Offline queue flushée.

7. Micro-copy française — Référence complète

ADM-001 — Liste

Élément Copie
Page title Administration plateforme
Subtitle Gestion des tenants (cabinets)
Bouton créer + Nouveau cabinet
DPA banner {n} DPA expirent dans moins de 30 jours. → Voir les cabinets concernés
Search placeholder Rechercher par raison sociale, NCC…
Plan filter Plan : Tous
Tab Tous Tous ({n})
Tab En attente En attente ({n})
Tab Actifs Actifs ({n})
Tab Suspendus Suspendus ({n})
Tab Supprimés Supprimés ({n})
Compteur {n} cabinet(s)
Empty global Aucun cabinet enregistré. Créez le premier cabinet pour démarrer.
Empty filtre Aucun cabinet {statut} pour le moment.
Error loading Impossible de charger les cabinets. Vérifiez votre connexion.
Badge PENDING En attente
Badge ACTIVE Actif
Badge SUSPENDED Suspendu
Badge DELETED Supprimé
Action voir Voir
Action audit (deleted) Audit

ADM-002 — Wizard

Étape / Élément Copie
Titre page Nouveau cabinet
Back link ← Retour à la liste
Étape 1 Identité légale
Étape 2 Contact & Adresse
Étape 3 Isolation
Étape 4 Conformité
Étape 5 Récapitulatif
Bouton Suivant Suivant ▸
Bouton Précédent ◂ Précédent
Bouton Annuler Annuler
Bouton Activer ✓ Activer le cabinet
Tooltip Activer disabled Checklist incomplète : {liste des éléments manquants}
Label raison sociale Raison sociale *
Label nom commercial Nom commercial *
Label pays Pays *
Tooltip pays locked V1 : Côte d'Ivoire uniquement. D'autres pays CIMA disponibles en V2.
Label agrément Numéro d'agrément CIMA *
Label NCC NCC (Numéro Compte Contribuable) *
Helper NCC Requis pour la facturation électronique FNE.
Label plan Plan tarifaire *
Label contact nom Nom complet du contact principal *
Label contact email Email *
Label contact tel Téléphone *
Préfixe tel +225
Label adresse 1 Adresse (ligne 1) *
Label adresse 2 Adresse (ligne 2)
Label ville Ville *
Label cp Code postal
Radio shared Base partagée Papillon (recommandé)
Radio shared desc Isolation par tenant_id. Aucune configuration requise.
Radio BYO BYO-DB (clause contractuelle requise)
Radio BYO desc Fournissez votre propre base PostgreSQL.
Label jdbc URL JDBC *
Label db user Utilisateur base de données *
Label db pass Mot de passe *
Label pool Taille du pool de connexions *
Warning BYO creds Les identifiants sont chiffrés (KEK). Ils ne seront jamais affichés après enregistrement.
Label DPA upload DPA signé (PDF) *
FileUpload hint Glissez votre PDF ici ou cliquez pour sélectionner. Taille max : 25 Mo — PDF uniquement.
Label DPA signed_at Date de signature *
Label DPA expires_at Date d'expiration (optionnel)
Label SMS sender SMS Sender ID *
Helper SMS sender Identifiant expéditeur approuvé par l'opérateur mobile (ex. AKWABA).
Label WA business WA Business Account ID *
Helper WA business Trouvable dans Meta Business Manager → Comptes WhatsApp Business.
Label WA template Template WhatsApp (ID Meta)
Helper WA template ID du template Meta approuvé pour les rappels de renouvellement.
Warning notif required ⚠ Le SMS Sender ID et le WA Business Account ID sont requis pour l'activation.
Checklist DPA DPA signé uploadé
Checklist SMS SMS Sender ID renseigné
Checklist WA WA Business Account ID renseigné
Checklist identity Identité légale complète
Checklist contact Contact principal renseigné
Recap note L'activation créera l'agence PRINCIPAL et enverra un lien d'activation à {email}.
Error NCC dup Un cabinet avec ce NCC existe déjà : {legal_name}. Contactez le superviseur.
Error pays locked Pays non disponible en V1 (Côte d'Ivoire uniquement).
Warn name similar Cabinets similaires détectés : {names}. Ces cabinets existent déjà. Continuer quand même ?
Error champ requis Ce champ est obligatoire.
Error email invalide Adresse email invalide.
Error phone invalide Numéro de téléphone invalide. Format attendu : +225 XX XX XX XX XX
Error file too large Le fichier dépasse la taille maximale de 25 Mo.
Error file type Format non supporté. Seuls les fichiers PDF sont acceptés.

ADM-003 — Détail

Élément Copie
Back link ← Cabinets
Tab Identité Identité
Tab Documents Documents
Tab Provisionnement Provisionnement
Tab Agences Agences
Tab Paramètres Paramètres
Bouton Modifier Modifier
Bouton Suspendre Suspendre
Bouton Réactiver Réactiver
Infos readonly Créé le {date} · Activé le {date} · Modifié le {date}
Locked field helper Modifiable par le support Papillon uniquement.
DPA expiry warning ⚠ Le DPA expire dans {n} jours ({date}). Pensez à le renouveler.
Doc version active v{n} (active)
Doc uploaded Uploadé le {date} par {admin_name}
Doc dl button 📥 Télécharger
Doc replace button ↑ Nouvelle version
Doc history ▼ Historique ({n} version(s) archivée(s))
Doc not uploaded Non uploadé
Provision last run OK Dernier provisionnement : {date} à {heure} — ✅ Succès
Provision last run KO Dernier provisionnement : {date} à {heure} — ❌ Échec
Pipeline step OK ✅ {n}. {label} — {duration}
Pipeline step FAIL ❌ {n}. {label} — {error_code}
Pipeline step RUNNING ⏳ {n}. {label} — En cours…
Pipeline step SKIPPED ⊘ {n}. {label} (ignoré — déjà complété)
Bouton retry step Relancer à partir de l'étape {n}
Error CP_NOT_SEEDED Profil pays CI introuvable. Contactez l'équipe Papillon.
Error BYO_DB_UNREACHABLE Connexion à la base BYO impossible. Vérifiez l'URL et le mot de passe.
Error BYO_DB_MIGRATION Migration Flyway échouée. Vérifiez les droits (ALTER, CREATE TABLE).
Error BYO_DB_SMOKE Test d'isolation échoué. Vérifiez que le schéma est vide et les droits DML.
Error KEYCLOAK_UNREACHABLE Service d'authentification indisponible. Réessayez dans quelques minutes.
Agency tooltip last active Au moins une agence active est requise par cabinet.
Banner SUSPENDED ⚠ Ce cabinet est suspendu depuis le {date}. Sans réactivation, il sera supprimé le {date+90j}. Les données d'audit et factures seront conservées 10 ans. Les données personnelles des assurés seront supprimées.
Banner DELETED ✕ Ce cabinet a été supprimé le {date}. Les données personnelles ont été purgées. Seul l'audit subsiste.

ADM-004 / ADM-005 — Modals

Élément Copie
Suspend title Suspendre {legal_name} ?
Suspend warning ⛔ Cette action révoque immédiatement l'accès de tous les opérateurs du cabinet et bloque les nouveaux paiements.
Suspend reason label Motif de suspension *
Radio MANUAL_BILLING Facturation impayée
Radio MANUAL_LEGAL Raison légale ou réglementaire
Radio MANUAL_TENANT_REQUEST Demande du cabinet
Radio MANUAL_OTHER Autre
Suspend detail label Détail (facultatif)
Suspend confirm btn ⛔ Confirmer la suspension
Reactivate title Réactiver {legal_name} ?
Reactivate info ✅ La réactivation rétablira l'accès des opérateurs et relancera les campagnes planifiées.
Reactivate since Suspendu depuis : {date} ({n} jours) · Motif : {reason}
Reactivate confirm btn ✅ Réactiver le cabinet
Bouton annuler Annuler

OP-001 — Identité / Branding

Élément Copie
Page title Paramètres du cabinet
Section légal Identité légale — lecture seule
Locked helper Modifiable par le support Papillon uniquement.
Section branding Identité visuelle
Label logo Logo du cabinet
FileUpload hint Glissez votre logo ici ou cliquez. PNG ou SVG · ≤ 200 Ko · min 64×64 px
Error logo size Le fichier dépasse 200 Ko.
Error logo format Format non supporté. Utilisez PNG ou SVG.
Error logo dims Dimensions insuffisantes. Le logo doit faire au minimum 64×64 pixels.
Label couleur Couleur principale
Label support email Email d'assistance
Helper support email Affiché dans le portail client et les notifications envoyées aux assurés.
Bouton sauvegarder Sauvegarder
Success toast Identité visuelle mise à jour.

OP-002 — Agences

Élément Copie
Section title Agences du cabinet
Bouton créer + Nouvelle agence
Compteur {n} agence(s)
Empty Aucune agence créée. L'agence PRINCIPAL est créée automatiquement à l'activation.
Modal title créer Nouvelle agence
Modal title éditer Modifier {agency_name}
Label code Code *
Helper code Identifiant unique au sein du cabinet (ex. ABJ-SUD). Lettres, chiffres et tirets uniquement.
Label nom Nom *
Label ville Ville *
Label adresse Adresse
Label phone Téléphone
Label manager Responsable
Bouton créer modal Créer l'agence
Bouton modifier modal Enregistrer les modifications
Error code dup Ce code est déjà utilisé par une autre agence du cabinet.
Success créer Agence {code} créée.
Success modifier Agence {code} mise à jour.
Tooltip last active Au moins une agence active est requise par cabinet.
Success disable Agence {code} désactivée.
Success reactivate Agence {code} réactivée.

OP-003 — Paramètres

Élément Copie
Section renouvellement Renouvellement
Label window Fenêtre de renouvellement
Helper window Nombre de jours avant l'échéance où les rappels sont envoyés. Entre 7 et 90 jours.
Error window La fenêtre doit être comprise entre 7 et 90 jours.
Section notif Notifications
Label canal Canal de notification par défaut
Radio WA WhatsApp
Radio SMS SMS
Label template Template WhatsApp (ID Meta)
Label daily cap Plafond d'envoi quotidien
Helper daily cap Nombre maximum de messages envoyés par jour pour ce cabinet.
Section auth Niveaux d'authentification
Auth level V1 1 — Lien signé (niveaux 2 et 3 disponibles en V2)
Section retention Rétention des données
LEGAL-REVIEW tooltip Durées minimales légales basées sur une analyse préliminaire — délibération ARTCI sectorielle assurance CI en cours. Ne pas descendre sous ces seuils sans confirmation juridique.
Label source retention Dossiers sources
Helper source floor Plancher légal : 90 jours
Error source below La durée minimale de rétention des dossiers sources est 90 jours.
Label audit retention Journaux d'audit
Helper audit floor Plancher légal : 10 ans
Error audit below La durée minimale de rétention des journaux d'audit est 10 ans.
Label quittance retention Quittances
Helper quittance floor Plancher légal : 10 ans
Error quittance below La durée minimale de rétention des quittances est 10 ans.
Bouton sauvegarder Sauvegarder
Success toast Paramètres enregistrés.
Compliance link Papillon agit en sous-traitant. Voir la liste des sous-traitants ↗

États globaux

État Copie
Loading générique Chargement en cours…
Error générique Une erreur est survenue. Réessayez ou contactez le support Papillon.
Offline Connexion perdue — votre saisie est conservée.
Low-network Connexion instable — chargement en cours.
Reconnected Connexion rétablie.
Sync pending Synchronisation en attente ({n} opération(s))

8. Spécifications nouvelles composantes

Composantes à créer dans frontend-operator/src/components/ui/ avant les pages TENANT. Priorités mises à jour dans docs/standards/design-system.md.

8.1 StepIndicator — Priorité HIGH

Location: components/ui/StepIndicator.tsx + StepIndicator.module.css

interface StepIndicatorProps {
  steps: { label: string }[];
  currentStep: number;      // 0-indexed
  completedSteps: number[]; // indices
}

Rendu visuel: - Dot pending : cercle outline, --color-border, label --color-text-secondary - Dot active : cercle plein --color-primary, label --font-weight-semibold, --color-text-primary - Dot completed : cercle plein --color-success, checkmark SVG inline - Connector : ligne --color-border (pending) ou --color-success (completed)

Responsive: labels tronqués sur tablet (max 8 chars + ellipsis), masqués sur mobile (dots uniquement, aria-label maintenu).

Accessibility: role="list", chaque step role="listitem", aria-current="step" sur active, aria-label="{label} — étape {n} sur {total}".


8.2 InlineBanner — Priorité HIGH

Location: components/ui/InlineBanner.tsx + InlineBanner.module.css

interface InlineBannerProps {
  variant: 'info' | 'warning' | 'error' | 'success';
  children: ReactNode;
  action?: { label: string; onClick: () => void };
  dismissible?: boolean;
  className?: string;
}

Tokens: - info : --color-info teinté, icône ℹ - warning : --color-warning teinté, icône ⚠ - error : --color-error teinté, icône ⛔ - success : --color-success teinté, icône ✅

Accessibility: role="alert" pour error, role="status" pour les autres, aria-live="polite".


8.3 FileUpload — Priorité HIGH

Location: components/ui/FileUpload.tsx + FileUpload.module.css

interface FileUploadProps {
  accept: string;                          // ".pdf" | ".png,.svg"
  maxSizeKb: number;
  minDimensions?: { w: number; h: number };
  onFile: (file: File) => void;
  onClear?: () => void;
  currentFileLabel?: string;
  progress?: number;                       // 0–100 pendant upload
  error?: string;
  disabled?: boolean;
}

États: 1. idle : zone drag-drop, icône 📂, hint text 2. drag-over : bordure --color-primary, fond --color-primary-subtle, transition: --duration-fast 3. has-file : nom fichier + taille + [✕ Supprimer] + [📂 Remplacer] 4. uploading : progress bar --color-primary, pourcentage, [Annuler] 5. error : InlineBanner error sous la zone (fichier sélectionné préservé)

Validation client-side immédiate: taille, MIME type, dimensions (canvas check pour images).


8.4 Tabs — Priorité HIGH

Location: components/ui/Tabs.tsx + Tabs.module.css

interface TabsProps {
  tabs: { id: string; label: string; disabled?: boolean }[];
  activeTab: string;
  onTabChange: (id: string) => void;
  variant?: 'underline' | 'pill';
}

underline (défaut, detail pages): ligne sous l'onglet actif --color-primary, texte --color-primary, --font-weight-semibold.

pill (filtres liste): fond --color-primary sur actif, texte blanc; fond transparent inactif.

Accessibility: role="tablist", role="tab", aria-selected, keyboard Arrow left/right, Home, End.

Responsive: overflow-x: auto avec gradient-fade droit. Touch target 48 px height minimum.


8.5 Skeleton — Priorité HIGH

Location: components/ui/Skeleton.tsx + Skeleton.module.css

interface SkeletonProps {
  width?: string;
  height?: string;
  variant?: 'text' | 'rect' | 'circle';
  className?: string;
}

Animation: shimmer background: linear-gradient(90deg, var(--color-bg-tertiary) 25%, var(--color-bg-secondary) 50%, var(--color-bg-tertiary) 75%), background-size: 200%, animation: shimmer 1.5s infinite.

prefers-reduced-motion: remplace l'animation par un fond statique --color-bg-tertiary.


8.6 EmptyState — Priorité HIGH

Location: components/ui/EmptyState.tsx + EmptyState.module.css

interface EmptyStateProps {
  title: string;
  description?: string;
  action?: { label: string; onClick: () => void };
  icon?: ReactNode; // SVG ≤ 48×48, `aria-hidden="true"`
}

Centré verticalement dans son conteneur. Icône en --color-text-tertiary. Titre --font-size-h3. Description --color-text-secondary. Action = Button variant="secondary".


8.7 Switch — Priorité MEDIUM

Location: components/ui/Switch.tsx + Switch.module.css

interface SwitchProps {
  label: string;
  checked: boolean;
  onChange: (checked: boolean) => void;
  disabled?: boolean;
  helperText?: string;
}

Accessibility: role="switch", aria-checked, label cliquable (htmlFor), touch target 48 px.


8.8 ProvisioningPipeline — Module-specific

Location: modules/tenant-admin/components/ProvisioningPipeline.tsx

type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';

interface PipelineStep {
  id: string;
  label: string;
  status: StepStatus;
  durationMs?: number;
  errorCode?: string;
  errorMessage?: string; // French, from server
  onRetry?: () => void;
}

interface ProvisioningPipelineProps {
  steps: PipelineStep[];
  lastRunAt?: string;
  overallStatus: 'idle' | 'running' | 'success' | 'failed';
  onRetryAll?: () => void;
}

Polling: TanStack Query refetchInterval: 2000 tant que overallStatus === 'running'. Arrêt sur success | failed.

prefers-reduced-motion: remplace spinner SVG par "En cours…" texte.


8.9 Mise à jour Component Inventory

Les composantes suivantes passent de la liste "Needed" à "Available" une fois développées pour TENANT:

Composante Priorité mise à jour Emplacement final
StepIndicator HIGH (était LOW) components/ui/
InlineBanner HIGH (était MEDIUM) components/ui/
FileUpload HIGH (inchangé) components/ui/
Tabs HIGH (était MEDIUM) components/ui/
Skeleton HIGH (était MEDIUM) components/ui/
EmptyState HIGH (était MEDIUM) components/ui/
Switch MEDIUM (inchangé) components/ui/

9. Compliance UI

9.1 DPA obligatoire avant activation (AC-06.3 · Loi 2013-450 art. 1)

FileUpload DPA dans wizard étape 4, mention contextuelle :

« Le Contrat de Traitement des Données (DPA) est obligatoire pour l'activation du cabinet, conformément à la loi ivoirienne n°2013-450 relative à la protection des données personnelles. Le cabinet est responsable de traitement ; Papillon agit en sous-traitant. [Modèle DPA ↗] »

Le bouton "Activer" (étape 5) reste disabled si DPA absent, tooltip : "DPA signé manquant — uploadez le PDF avant activation."

9.2 Liste des sous-traitants (Loi 2013-450 art. 42)

Bas de page OP-003 (Paramètres) :

« Papillon agit en tant que sous-traitant pour votre cabinet. Consultez [la liste de nos sous-traitants ↗] (Twilio, Meta WhatsApp Cloud API, Cinetpay, Orange Money CI, Wave CI). »

Lien → page statique /legal/sous-traitants.

Badge [LEGAL-REVIEW] (InlineBanner warning pliée) dans ADM-003 onglet Paramètres et OP-003, tooltip :

« Durées minimales légales basées sur une analyse préliminaire — délibération ARTCI sectorielle assurance CI en cours. Ne pas descendre sous ces seuils sans confirmation juridique (TV-03). »

9.4 Agrément CIMA — capture (TV-04, Règl. 01-24)

Étape 1 wizard, helper sous "Numéro d'agrément CIMA" :

« Numéro d'agrément délivré par la Commission Régionale de Contrôle des Assurances (CRCA) pour les intermédiaires. Conservé conformément à la Réglementation CIMA 01-24. »

9.5 Information suspension → purge 90j

InlineBanner warning dans tous les onglets d'un tenant SUSPENDU (§7, ADM-003) — texte complet dans la micro-copy.

9.6 Restriction pays V1

Étape 1 wizard et OP-001 : Select "Pays" désactivé. Tooltip : "V1 : Côte d'Ivoire uniquement. Sénégal, Bénin, Cameroun et autres pays CIMA disponibles en V2."


10. Route Map & Structure module

Nouvelles routes frontend-operator

Route Composant Rôle requis Note
/admin/tenants TenantListPage PLATFORM_ADMIN Remplace stub AdminPage
/admin/tenants/nouveau TenantCreationWizardPage PLATFORM_ADMIN
/admin/tenants/:id TenantDetailPage PLATFORM_ADMIN
/tenant/settings CabinetBrandingPage CABINET_ADMIN
/tenant/settings/agences CabinetAgenciesPage CABINET_ADMIN
/tenant/settings/parametres CabinetSettingsPage CABINET_ADMIN
// Dans NAV_ITEMS de Sidebar.tsx
{ to: '/tenant/settings', label: 'Mon cabinet', icon: '🏢', requiredRole: 'CABINET_ADMIN' }

Structure module

frontend-operator/src/modules/tenant-admin/
├── components/
│   └── ProvisioningPipeline.tsx
├── hooks/
│   ├── useTenantList.ts         // GET /api/control/tenants
│   ├── useTenant.ts             // GET /api/control/tenants/:id
│   ├── useTenantCreate.ts       // POST (wizard step 1)
│   ├── useTenantProvision.ts    // POST /provision + polling
│   ├── useTenantSuspend.ts      // POST /suspend
│   ├── useTenantReactivate.ts   // POST /reactivate
│   ├── useComplianceDocs.ts     // GET/POST compliance-docs
│   ├── useAgencies.ts           // GET/POST/PATCH/disable/reactivate agencies
│   └── useTenantSettings.ts    // GET/PATCH me/settings
├── pages/
│   ├── admin/
│   │   ├── TenantListPage.tsx
│   │   ├── TenantCreationWizardPage.tsx
│   │   └── TenantDetailPage.tsx
│   └── cabinet/
│       ├── CabinetBrandingPage.tsx
│       ├── CabinetAgenciesPage.tsx
│       └── CabinetSettingsPage.tsx
├── types/
│   └── tenant.ts               // TenantRow, AgencyRow, TenantSettings, etc.
└── i18n/
    └── fr.json                 // toutes les clés de micro-copy ci-dessus

11. Stories UI-bearing — Inputs pour /wireframe

Pour chaque story, invoquer /wireframe tenant-admin TENANT-NNN avec les inputs ci-dessous.

TENANT-001 [V0.0.0] — ADM-001 + ADM-002 + ADM-003 onglet Identité

Stories couvertes: TENANT-US-01, TENANT-US-02 (étapes 1–3 + activation). ACs: AC-01.1, AC-01.2, AC-01.3, AC-01.4, AC-02.1, AC-02.2, AC-02.3. Screens: ADM-001 (list + skeleton + empty + error + DPA banner), ADM-002 étapes 1–3 + 5 (recap+checklist), ADM-003 onglet Identité (read mode + ProvisioningPipeline success + failed with retry). Brand context: [OPERATOR] (back-office platform admin, desktop-primary). 5 states: loading (skeleton table), empty (first tenant), error (NCC dup + similar name modal), low-network (banner + form preserved), success (PENDING créé → pipeline → ACTIVE).

TENANT-002 [V0.0.2] — OP-001 + OP-002 + OP-003

Stories couvertes: TENANT-US-07, TENANT-US-08, TENANT-US-09. ACs: AC-07.1, AC-07.2, AC-07.3, AC-08.1, AC-08.2, AC-08.3, AC-09.1, AC-09.2, AC-09.3. Screens: OP-001 (branding + locked fields + FileUpload), OP-002 (agency list + modal create + modal edit + disable-last prevention), OP-003 (settings + LEGAL-REVIEW badges + V1 auth lock). Brand context: [OPERATOR] (cabinet admin, desktop/tablet). 5 states: loading, empty (no logo / no agencies beyond PRINCIPAL), error (logo format/size, code dup, bound violation), low-network, success.

TENANT-003 [V0.0.4] — ADM-002 étape 3 BYO-DB + ProvisioningPipeline

Stories couvertes: TENANT-US-02, TENANT-US-03. ACs: AC-03.1, AC-03.2, AC-03.3, AC-02.2 (retry from failed step). Screens: ADM-002 étape 3 (BYO-DB radio sélectionné, JDBC fields visibles, password show/hide), ProvisioningPipeline (running → BYO_DB_UNREACHABLE → retry → BYO_DB_MIGRATION → retry → success). Brand context: [OPERATOR] (platform admin). 5 states: loading (pipeline polling), empty (no run yet), error (BYO_DB_UNREACHABLE, BYO_DB_MIGRATION, KEYCLOAK_UNREACHABLE chacun), low-network (slow JDBC test), success (all steps green).


Fin du document de design — tenant-admin (TENANT) · [ADMIN] + [OPERATOR] · 2026-05-22