Story OP-009: Frontend — Détail campagne ENVOYÉE (polling 30s + KPI strip)¶
Module: operator-console Slice: Architecture §11 OP-002 (vue statut post-envoi) + §5.2 polling Side: [OPERATOR] Version target: V0.0.1 Priority: 10 Depends on: OP-005 (StatusPill, route /campagnes), OP-006 (GET /status), OP-008 (CampaignDetailPage routing + ContractsTable) Can develop concurrently with: OP-008 Merge order: After OP-006 + OP-008 Estimated complexity: M PRD User Stories: US-OP-05 AC1 (polling subset), US-OP-05 AC3 (statut détaillé contrat) Wireframe: docs/wireframes/operator-console/OP-009/ (brief design §11 OP-004 partial)
Objective¶
Livrer la vue /campagnes/:id quand status ∈ {ENVOYÉE, CLÔTURÉE, ANNULÉE} : KPI strip en haut (compteurs agrégés via polling 30 s OP-006), table contrats avec statut complet en pills, drawer lecture seule. Actions (rappel, annulation, hors-plateforme) sont reportées aux stories V0.0.3 ; cette story fournit la vue d'observation.
Backend Scope¶
Aucun nouveau endpoint. Consomme :
- GET /api/operator/campaigns/{id} (OP-008)
- GET /api/operator/campaigns/{id}/contracts?cursor=&pageSize=50 (OP-008)
- GET /api/operator/campaigns/{id}/status (OP-006) — polling 30 s
Frontend Scope¶
Pages & Routes¶
- Vue ENVOYÉE / CLÔTURÉE / ANNULÉE sous
/campagnes/:id— sélectionnée seloncampaign.status(cas BROUILLON traité OP-008).
Components¶
Sous frontend-operator/src/features/campaigns/detail/:
CampaignDetailSentPage.tsx— orchestrateur (header + KPI strip + table + drawer).CampaignSentHeader.tsx— title + status pill + breadcrumb + boutons (placeholders V0.0.1 : « + Ajouter contrat » et « ⋯ Annuler » désactivés ; activés OP-015 / OP-016).KpiStrip.tsx— strip de 6 tuiles : Envoyés / Livrés / Liens ouverts / Payés / Hors plateforme / Échec ; chacune affiche valeur + pourcentage. Indicateur● liveà droite (vert si polling actif, gris si offline).KpiTile.tsx— primitive nouvelle ici (props : label, value, secondary, variant, loading state).ContractsSentTable.tsx— DataTable avec colonnes : Client, Téléphone, Statut (pill complète), Échéance, Dernière activité, kebab menu (⋯) avec items désactivés V0.0.1 (« Voir la fiche » seul actif → ouvre drawer lecture).MenuDropdown.tsx— primitive nouvelle ici (menu kebab accessible, fermeture Esc).ContractReadOnlyDrawer.tsx— drawer (réutiliseDrawerOP-008) en lecture seule : informations contrat, statut détaillé NOTIF / PAY, historique audit (lien vers événements).hooks/useCampaignStatusPolling.ts— utiliseuseOperatorPolling(OP-000) avec interval 30 s surGET /campaigns/{id}/status.
UI States¶
| State | Behavior | French Copy |
|---|---|---|
| Loading | Skeleton KPI strip (6 carrés) + rows skeleton | — |
| Empty | Impossible (campagne envoyée ⇒ ≥ 1 cc) | fallback générique |
| Error | KPI strip masquée si erreur agrégat, table reste si données présentes ; banner | « Mises à jour temps réel indisponibles. [Réessayer] » |
| Offline / low network | Indicateur ● live → ● hors-ligne (gris) ; polling en pause ; actions grisées |
« Connexion perdue. Les données affichées datent de {timestamp}. » |
| Success | Indicateur ● live vert pulse 2 s (respect prefers-reduced-motion) ; KPI animés ±1 sur changement |
— |
Responsive Behavior¶
- ≥ 1280 px : KPI strip 6 colonnes.
- 1024–1279 px : KPI strip 6 colonnes serrées.
- 768–1023 px : KPI strip 3 × 2 ; table scroll horizontal.
Connectivity Behavior¶
- Polling actif quand onglet visible (
document.visibilityState === 'visible'). - Polling pause à la perte de focus + offline.
- Reconnexion → re-fetch immédiat puis reprise du cycle 30 s.
Interactions¶
- Click row → ContractReadOnlyDrawer.
- Kebab menu : V0.0.1 = uniquement « Voir la fiche » actif ; autres items présents mais désactivés avec tooltip « Disponible en V0.0.3 ».
- Pas d'actions destructives V0.0.1.
Acceptance Criteria¶
AC-001: Polling 30s rafraîchit les KPI sans intervention
Given une campagne ENVOYÉE avec 12 cc
When je charge /campagnes/{id} et je laisse l'onglet visible
Then la KPI strip affiche 6 tuiles (Envoyés/Livrés/Liens ouverts/Payés/Hors plat./Échec)
And toutes les 30s un GET /status est émis (vérifié via DevTools Network)
And l'indicateur "● live" est vert.
AC-002: Pause polling à la perte de focus
Given le polling est actif
When je passe sur un autre onglet
Then aucun GET /status n'est émis pendant l'absence
And au retour, un GET /status est émis immédiatement.
AC-003: Indicateur passe à "● hors-ligne" si déconnexion
Given polling actif
When je passe offline (DevTools)
Then l'indicateur "● live" devient "● hors-ligne" (gris)
And le GlobalBanner offline s'affiche
And les KPI restent affichés avec opacity 0.6.
AC-004: ContractReadOnlyDrawer affiche le statut détaillé (US-OP-05 AC3)
Given un cc en statut NOTIF_LIVRÉ avec payment_status=PAYÉ
When je clique sur la row
Then le drawer affiche : statut NOTIF, statut PAY, expiration lien, historique audit
And aucun champ n'est éditable.
Compliance Rules¶
- OP-ADR-06 : polling 30 s V0.0.0–V0.0.5.
VÉRIFIÉ. - PRD §A.4 : latence < 5s SSE (V1) ; polling 30s V0.
VÉRIFIÉ. prefers-reduced-motion: pulse désactivé si user pref.VÉRIFIÉ.
Standards & Conventions¶
docs/standards/connectivity-low-bandwidth.md— polling 30s, suspend on hidden.docs/standards/design-system.md— KpiTile, MenuDropdown tokens.
Testing Requirements¶
Unit Tests¶
useCampaignStatusPolling: pause on hidden, reprise on focus.KpiTileTest: variants + loading.MenuDropdownTest: Esc ferme, a11y.
Integration Tests¶
- Playwright
polling-flow.spec.tsAC-001..AC-003.
What QA Will Validate¶
- Polling visible dans Network panel : 1 req / 30 s, pas plus.
- Pulse ● live respecte prefers-reduced-motion.
- Rendu responsive 768/1024/1280/1440.
Out of Scope¶
- Rappel individuel → V1 (FR-OP-09).
- Annulation campagne → OP-016 (V0.0.3).
- Hors-plateforme modale → OP-017 (V0.0.3).
- Ajout contrat à envoyée → V1 (US-OP-14).
- SSE temps réel → V1.
- Badge URGENT (TD-2) → V1.
Definition of Done¶
- [ ] Vue ENVOYÉE/CLÔTURÉE/ANNULÉE rend à /campagnes/:id
- [ ] AC-001..AC-004 passent
- [ ] Polling 30s pause on hidden, reprise on focus
- [ ] KpiStrip + KpiTile + MenuDropdown primitives mergées
- [ ] Indicateur ● live / polling / hors-ligne
- [ ] Drawer lecture seule complet
- [ ] 5 états UI implémentés
- [ ] Rendu 768/1024/1280/1440
- [ ] Aucune action destructive activée V0.0.1
- [ ] Tests verts