Aller au contenu

Story OP-011: Frontend — TopBar agency selector + scoping URL-persistant

Module: operator-console Slice: Architecture §11 OP-003 (frontend) Side: [OPERATOR] Version target: V0.0.2 Priority: 12 Depends on: OP-005 (TopBar shell, route /campagnes), OP-010 (GET /agencies + filtre agencyId backend) Can develop concurrently with: aucune Merge order: After OP-010 Estimated complexity: S PRD User Stories: US-OP-02 AC3, design §2 (filtre agence TopBar) Wireframe: docs/wireframes/operator-console/OP-011/ (brief design §11 OP-003)


Objective

Ajouter à la TopBar un sélecteur d'agence visible pour supervisor et cabinet_admin (caché pour agency_operator). La sélection est globale (s'applique à toutes les pages) et survit au reload via querystring ?agency=<id> ou ?agency=all. Colonne « Agence » apparait dans les listes pour ces rôles.


Backend Scope

Aucun nouvel endpoint. Consomme : - GET /api/operator/agencies (OP-010) - Les listes existantes acceptent désormais ?agencyId=<id>.


Frontend Scope

Components

Sous frontend-operator/src/shell/:

  • AgencySelector.tsx — composant TopBar à droite de la search bar. Visible si role ∈ {supervisor, cabinet_admin}.
  • Charge GET /agencies au mount (React Query, staleTime 5 min).
  • Select avec options : Toutes les agences (default), puis chaque agence par nom.
  • Sélection écrit dans URL searchParam ?agency=all|<uuid> via TanStack Router.
  • Persiste à travers la navigation (préserve le param).
  • useCurrentAgency() — hook utilitaire qui lit ?agency= depuis l'URL et retourne agencyId | 'all' | null (null si agency_operator — pas concerné).
  • CampaignTable.tsx (OP-005) — extension : ajoute colonne Agence visible si role ∈ {supervisor, cabinet_admin}. Helper sidebarRbac (OP-000) déjà gère la visibilité conditionnelle.
  • useCampaigns (OP-005) — passe désormais agencyId depuis useCurrentAgency() dans la queryKey ET le query param API.

UI States

State Behavior French Copy
Loading Selector skeleton 120 px
Empty 1 seule agence → selector masqué (cas dégénéré tenant mono-agence)
Error Toast « Impossible de charger les agences. »
Offline Selector désactivé, dernière valeur sticky
Success Sélection persiste, query refetch

Responsive Behavior

  • ≥ 1024 px : selector full label.
  • < 1024 px : selector compact (icône + valeur).
  • < 768 px : selector dans un dropdown burger menu (avec TopBar mobile shell).

Interactions

  • Changement de sélection → URL update + invalidate toutes les queries listes.
  • Esc ferme le dropdown.
  • Click outside ferme.

Acceptance Criteria

AC-001: AgencySelector visible pour cabinet_admin/supervisor, caché pour agency_operator
Given un cabinet_admin connecté
Then le selector apparait à droite de la search bar
Given un agency_operator
Then le selector n'apparait PAS.

AC-002: Sélection URL-persistante
Given je suis cabinet_admin sur /campagnes
When je sélectionne "Agence Plateau" dans le selector
Then l'URL devient /campagnes?agency=<uuid-plateau>
And la liste se met à jour pour ne montrer que Plateau
And un reload conserve la sélection.

AC-003: Liste affiche colonne Agence pour cross-rôles
Given cabinet_admin sur /campagnes?agency=all
When la liste se charge
Then la colonne "Agence" est visible avec le nom de chaque agence
Given agency_operator sur /campagnes
Then la colonne "Agence" n'apparait PAS.

AC-004: Cas mono-agence — selector masqué
Given un tenant avec 1 seule agence
When cabinet_admin charge /campagnes
Then le selector n'apparait pas (UX épurée).

Compliance Rules

  • PRD design §2 : selector dans TopBar, pas dans chaque page. VÉRIFIÉ.

Standards & Conventions

  • docs/standards/react-typescript-guidelines.md — TanStack Router searchParams.
  • docs/standards/design-system.md — Select component existant à étendre.

Testing Requirements

Unit Tests

  • AgencySelectorTest : visibilité par rôle, URL update, persistence reload.
  • useCurrentAgencyTest : lecture du param.

Integration Tests

  • Playwright multi-agency-flow.spec.ts AC-001..AC-004.

What QA Will Validate

  • 3 personas (agency_operator, supervisor, cabinet_admin) : visibilité conforme.
  • URL partage : copier-coller URL avec ?agency=... fonctionne.

Out of Scope

  • Création campagne avec sélecteur d'agence (cabinet_admin choisit l'agence cible) → ajouté à la modale CreateAndSendModal (OP-005) en EXTENSION par cette story.
  • Filtrage cross-pages (anomalies, paiements) → arrive avec les pages respectives en V0.0.3.

Definition of Done

  • [ ] AgencySelector mergé dans TopBar
  • [ ] AC-001..AC-004 passent
  • [ ] URL ?agency=... persiste à travers navigation + reload
  • [ ] Colonne Agence dans CampaignTable conditionnelle au rôle
  • [ ] CreateAndSendModal ajoute champ agence cible pour cabinet_admin/supervisor
  • [ ] Responsive 768/1024/1280
  • [ ] A11y : selector navigable clavier
  • [ ] Tests verts