Aller au contenu

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/agencesCabinetAgenciesPage. Role-gated CABINET_ADMIN. Lives under the existing OperatorShell. Add the sidebar nav item « Mon cabinet » (icon 🏢, role CABINET_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 AgencyFormModal for 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 +225 prefix, Responsable).
  • « Modifier » opens the modal pre-filled.
  • « Désactiver » is disabled with aria-disabled="true" + tooltip « Au moins une agence active est requise par cabinet. » when activeCount === 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/agences renders at 360 / 768 / 1024 px under OperatorShell, gated CABINET_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