Story TENANT-006: Agency management UI (cabinet admin self-service)¶
Module: tenant-admin Slice: S9 (Vertical Slice Decomposition §11, frontend) Side: [OPERATOR] Version target: [V0.0.2] Priority: 6 Depends on: TENANT-005 Can develop concurrently with: TENANT-007, TENANT-008 Merge order: After TENANT-005 Estimated complexity: M PRD User Stories: TENANT-US-08 Wireframe: docs/wireframes/tenant-admin/TENANT-006/ (design ref OP-002 — Gestion des agences)
Objective¶
Give the cabinet admin (CABINET_ADMIN) a self-service screen to manage their domestic branch network: list agencies, create/edit them via a modal, and disable/reactivate them. The disable action on the last active agency is preventively disabled (button inert + tooltip), not allowed-then-rejected. This is the only cabinet-admin (OPERATOR) UI shipped in V0 — branding and tenant-settings editing are V1.
Backend Scope¶
None new — consumes the endpoints from TENANT-005 (GET/POST/PATCH /tenant/me/agencies, /disable, /reactivate).
Multi-Tenant Considerations¶
All calls are tenant-scoped via the caller's tenant_id claim; the UI never sends a tenant id. The cabinet admin sees only their own agencies.
Frontend Scope¶
Pages & Routes¶
/tenant/settings/agences→CabinetAgenciesPage. Role-gatedCABINET_ADMIN. Lives under the existing OperatorShell. Add the sidebar nav item « Mon cabinet » (icon 🏢, roleCABINET_ADMIN) per design §10. The page renders the « Agences » tab. (The « Identité » and « Paramètres » tabs are V1; in V0 the cabinet-admin area shows only the Agencies tab, or shows the other tabs disabled — keep the tab bar minimal.)
Components¶
- Reuse design-system
DataTable,Button,Modal,Input,Badge,EmptyState,Skeleton,InlineBanner, plus a tooltip on the disabled « Désactiver » button. - Modal
AgencyFormModalfor create + edit (same fields).
UI States (ALL REQUIRED)¶
| State | Behavior | French Copy |
|---|---|---|
| Loading | 3 Skeleton rows in DataTable | — |
| Empty | EmptyState | « Aucune agence créée. L'agence PRINCIPAL est créée automatiquement à l'activation. » |
| Error | Modal: Input error under « Code » on duplicate; network submit → Toast error |
« Ce code est déjà utilisé par une autre agence du cabinet. » |
| Offline / low network | Create blocked (server validation required immediately) | « Création impossible hors ligne. » |
| Success | Toast; modal closes; DataTable refreshes | « Agence {code} créée. » / « Agence {code} mise à jour. » / « Agence {code} désactivée. » / « Agence {code} réactivée. » |
Responsive Behavior¶
Cabinet admin is desktop/tablet. 1024–1440 comfortable; 768 full experience, sidebar collapsed; 360–767 tabs scroll horizontally, card layout 1-col, modal full-width minus 32 px, save sticky bottom. Touch targets ≥ 48 px.
Connectivity Behavior (CUSTOMER stories only)¶
N/A — operator-side, online-first. Agency creation requires immediate server validation (unique code), so it is blocked offline rather than queued.
Interactions¶
- « + Nouvelle agence » opens the create modal (fields: Code, Nom, Ville*, Adresse, Téléphone with
+225prefix, Responsable). - « Modifier » opens the modal pre-filled.
- « Désactiver » is
disabledwitharia-disabled="true"+ tooltip « Au moins une agence active est requise par cabinet. » whenactiveCount === 1(design D7 — preventive, not error-recovery). - « Réactiver » for INACTIVE rows.
- Status badge: ACTIVE → ● Active (success); INACTIVE → ⊘ Inactive (neutral).
Micro-copy reference (OP-002)¶
- Section title: « Agences du cabinet » · Bouton créer: « + Nouvelle agence » · Compteur: « {n} agence(s) ».
- Modal create title « Nouvelle agence » / edit « Modifier {agency_name} ».
- Labels: « Code * » (helper « Identifiant unique au sein du cabinet (ex. ABJ-SUD). Lettres, chiffres et tirets uniquement. »), « Nom * », « Ville * », « Adresse », « Téléphone », « Responsable ».
- Buttons: « Créer l'agence » / « Enregistrer les modifications » / « Annuler ».
Acceptance Criteria¶
(UI realisation of TENANT-US-08; backend rules verified in TENANT-005.)
AC-08.1: Required fields + unique code (UI)
Given the agency create modal
When I submit without code, name, or city
Then inline French validation blocks submission
And when the server returns a duplicate-code error, the « Code » field shows « Ce code est déjà utilisé par une autre agence du cabinet. »
AC-08.2: Disable is soft (UI)
When I disable an agency with more than one active agency
Then the row moves to ⊘ Inactive, a success toast « Agence {code} désactivée. » appears, and a « Réactiver » action is shown
AC-08.3: Cannot disable the only active agency (preventive UI)
Given the tenant has exactly one ACTIVE agency
Then that agency's « Désactiver » button is rendered disabled with aria-disabled="true" and a tooltip « Au moins une agence active est requise par cabinet. »
And the destructive call is never issued from the UI
Compliance Rules¶
- None specific to this UI. Agencies are an internal organizational layer.
Standards & Conventions¶
docs/standards/react-typescript-guidelines.md— TanStack Query, mutations, optimistic refresh.docs/standards/design-system.md— DataTable, Modal, Button, Badge, EmptyState, Skeleton, InlineBanner.docs/standards/design-principles.md— D7 error prevention (disabled-button + tooltip over allow-then-400).docs/standards/connectivity-low-bandwidth.md— operator-side online-first note (TENANT not subject to customer 200 KB rule).docs/standards/critical-rules.md— tenant isolation (UI never sends tenant id).
Testing Requirements¶
Unit Tests¶
- Disabled-button rendering when
activeCount === 1(aria-disabled + tooltip). - Client-side required-field validation.
Integration Tests¶
- Create/edit/disable/reactivate happy paths against TENANT-005 endpoints (mocked); duplicate-code surfaces under « Code ».
- Cabinet admin sees only their own agencies.
What QA Will Validate¶
- Renders at 360/768/1024; all 5 UI states; French copy matches OP-002; last-active button truly inert.
Out of Scope¶
- Branding / visual identity editing (US-07) — V1.
- Tenant settings editing (US-09 PATCH) — V1 (GET settings exists in V0.0.0 backend).
- The « Identité » and « Paramètres » cabinet-admin tabs (V1).
- Assigning users to agencies (AUTH).
Definition of Done¶
- [ ]
/tenant/settings/agencesrenders at 360 / 768 / 1024 px under OperatorShell, gatedCABINET_ADMIN - [ ] Sidebar « Mon cabinet » nav item added
- [ ] All 5 UI states implemented
- [ ] French micro-copy matches OP-002 spec exactly
- [ ] AC-08.1, AC-08.2, AC-08.3 (UI) pass manually
- [ ] Last-active « Désactiver » button preventively disabled with tooltip; no destructive call issued
- [ ] Tenant isolation verified (UI never sends a tenant id; only own agencies shown)
- [ ] Unit + integration tests passing; no silent failure on network errors