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 retourneduplicateOfImportId:- 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 questatus==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.tsAC-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