Aller au contenu

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 selon campaign.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éutilise Drawer OP-008) en lecture seule : informations contrat, statut détaillé NOTIF / PAY, historique audit (lien vers événements).
  • hooks/useCampaignStatusPolling.ts — utilise useOperatorPolling (OP-000) avec interval 30 s sur GET /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.ts AC-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