Aller au contenu

Story OP-019: Frontend — Page Imports (drop-zone + résultat + historique)

Module: operator-console Slice: Architecture §11 OP-004d (frontend) Side: [OPERATOR] Version target: V0.0.3 Priority: 20 Depends on: OP-015 (POST/GET /imports backend) Can develop concurrently with: OP-016, OP-017, OP-018 Merge order: After OP-015 Estimated complexity: M PRD User Stories: US-OP-01 AC1+AC2+AC3+AC4 Wireframe: docs/wireframes/operator-console/OP-019/ (brief design §4.3)


Objective

Livrer la page /imports : drop-zone pleine largeur (drag-drop + clic Parcourir), barre de progression upload, gestion warning duplicate inline, écran résultat avec X valides / Y erreurs / Z anomalies + liens, historique des imports récents.


Frontend Scope

Pages & Routes

  • Route /imports (TanStack Router).
  • Route /imports/{id} — écran résultat.

Components

Sous frontend-operator/src/features/imports/:

  • ImportsPage.tsx — orchestrateur (drop-zone + historique).
  • FileUploadDropzone.tsx — primitive nouvelle ici :
  • Drag-drop + clic Parcourir.
  • Accept : .csv, .xlsx + MIME check.
  • Taille max 20 Mo (client-side validation).
  • Progress bar pendant upload (Axios onUploadProgress).
  • Reject sur format/taille → InlineBanner error.
  • DuplicateWarningInline.tsx — apparait dans le drop-zone quand POST /imports retourne duplicateOfImportId :
  • Texte : « ⚠️ Ce fichier a déjà été importé le {date} par {operator}. [Continuer quand même] [Annuler] »
  • Continuer → POST /imports?forceDuplicate=true.
  • RecentImportsList.tsx — DataTable existante avec colonnes : Fichier, Date, Valides, Erreurs, Anomalies, [Voir résultat].
  • ImportResultPage.tsx — écran /imports/{id} :
  • Header : nom fichier + date.
  • 3 lignes : ✅ {validCount} contrats valides, ❌ {errorCount} erreurs bloquantes → [Voir les erreurs], ⚠️ {anomalyCount} anomalies non bloquantes → [Voir les anomalies].
  • Liens vers /anomalies?import={id}&type=BLOCKING|NON_BLOCKING.
  • Bouton « Retour aux imports » → /imports.
  • useImportUpload.ts + useImportResult.ts — mutations + queries.
  • ProgressBar.tsx — primitive nouvelle ici (générique, branchée sur 0–100 %).

UI States (ImportsPage)

State Behavior French Copy
Loading (en upload) Progress bar + "Téléversement…" + boutons disabled
Empty (historique) EmptyState « Aucun import récent. Glissez votre premier fichier ci-dessus. »
Error format/taille InlineBanner sous drop-zone « Le format du fichier n'est pas reconnu. Acceptés : CSV, XLSX. » / « Le fichier dépasse la taille maximale (20 Mo). »
Error backend InlineBanner message backend
Duplicate warning DuplicateWarningInline (pas modale) « Ce fichier a déjà été importé le {date}. »
Offline Drop-zone grisée + tooltip « Connexion perdue. L'import sera disponible à la reconnexion. »
Success Toast 4s + redirect vers /imports/{id} « Fichier importé : {n} contrats. »

UI States (ImportResultPage)

State Behavior French Copy
Loading (import EN_COURS) Polling 5s + Progress indéterminé « Traitement en cours… »
Success (TERMINÉ) Résultat affiché
Error (ÉCHEC) InlineBanner message ING

Responsive Behavior

  • ≥ 1024 px : drop-zone pleine largeur (max 800 px) + historique en dessous.
  • 768–1023 px : layout identique compact.

Interactions

  • Drag enter / leave : feedback visuel (border dashed → solid).
  • File select : preview nom + taille.
  • Upload cancel → AbortController.
  • Polling sur /imports/{id} toutes les 5 s tant que status==EN_COURS.

Acceptance Criteria

AC-001: Drop-zone upload + progress bar (US-OP-01 AC1)
Given je suis sur /imports
When je drag-drop un CSV valide 2.3 Mo
Then une preview "📄 contrats_mai.csv · 2.3 Mo" apparait
And la progress bar progresse pendant l'upload
And après réponse 200, redirection vers /imports/{id}.

AC-002: Écran résultat affiche les compteurs + liens (US-OP-01 AC2+AC3)
Given un import TERMINÉ avec validCount=412, errorCount=3, anomalyCount=12
When je charge /imports/{id}
Then les 3 lignes affichent les bons compteurs
And cliquer "Voir les erreurs" navigue vers /anomalies?import={id}&type=BLOCKING.

AC-003: Duplicate hash → warning inline (US-OP-01 AC4)
Given un fichier déjà importé le 18/05
When je réuploade le même fichier
Then la warning "⚠️ Ce fichier a déjà été importé le 18/05/2026 par K. Brou." apparait
And cliquer "Continuer quand même" relance POST avec forceDuplicate=true
And cliquer "Annuler" reset la drop-zone.

AC-004: Format/taille invalide → reject client-side
Given je drag-drop un .pdf
Then InlineBanner "Le format du fichier n'est pas reconnu. Acceptés : CSV, XLSX."
And aucun POST émis.
Given je drag-drop un .xlsx de 25 Mo
Then InlineBanner "Le fichier dépasse la taille maximale (20 Mo)."
And aucun POST émis.

Compliance Rules

  • US-OP-01 AC4 : duplicate warning inline (PAS modale). VÉRIFIÉ.
  • PRD design §4.3 : layout drop-zone + historique. VÉRIFIÉ.

Standards & Conventions

  • docs/standards/design-system.md — FileUpload, ProgressBar, EmptyState.
  • docs/standards/react-typescript-guidelines.md

Testing Requirements

Unit Tests

  • FileUploadDropzoneTest : reject format/taille, drag events.
  • DuplicateWarningInlineTest : 2 actions.

Integration Tests

  • Playwright import-flow.spec.ts AC-001..AC-004.

What QA Will Validate

  • Upload réel d'un CSV de test.
  • Polling status fonctionne pendant traitement.
  • Cancel upload mid-stream OK.

Out of Scope

  • Backend imports → OP-015.
  • Édition champs d'import → ING.

Definition of Done

  • [ ] Route /imports + /imports/{id} mergées
  • [ ] AC-001..AC-004 passent
  • [ ] FileUploadDropzone + ProgressBar primitives mergées
  • [ ] Polling 5s sur /imports/{id} EN_COURS
  • [ ] 7 états UI implémentés (incl. duplicate-warning)
  • [ ] Rendu 768/1024/1280/1440
  • [ ] Cancel upload via AbortController fonctionne
  • [ ] Tests verts