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 sirole ∈ {supervisor, cabinet_admin}.- Charge
GET /agenciesau 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 retourneagencyId | 'all' | null(null si agency_operator — pas concerné).CampaignTable.tsx(OP-005) — extension : ajoute colonneAgencevisible sirole ∈ {supervisor, cabinet_admin}. HelpersidebarRbac(OP-000) déjà gère la visibilité conditionnelle.useCampaigns(OP-005) — passe désormaisagencyIddepuisuseCurrentAgency()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.
Escferme 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.tsAC-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 ciblepour cabinet_admin/supervisor - [ ] Responsive 768/1024/1280
- [ ] A11y : selector navigable clavier
- [ ] Tests verts