# Story 4.10: Dashboard - Glossaries Editor (Pro) Status: done ## Story En tant qu'**utilisateur Pro**, Je veux **créer et éditer des glossaires dans mon dashboard**, de sorte que **je puisse personnaliser les traductions LLM avec ma terminologie métier**. ## Acceptance Criteria 1. **Pro Access Only**: La page `/dashboard/glossaries` n'est accessible qu'aux utilisateurs avec `tier === "pro"` 2. **Free User Prompt**: Les utilisateurs Free voient un message d'upgrade avec CTA vers upgrade au lieu de l'éditeur 3. **List Glossaries**: Afficher la liste des glossaires existants avec: name, terms_count, created_at 4. **Create Glossary**: Au clic sur "Create New Glossary", ouvrir un dialog/form pour entrer le nom et les termes initiaux 5. **Edit Glossary**: Cliquer sur un glossaire pour l'éditer (modifier le nom, ajouter/supprimer/modifier des termes) 6. **Delete Glossary**: Supprimer un glossary avec confirmation dialog → DELETE `/api/v1/glossaries/{id}` 7. **Term Editor**: Interface pour gérer les paires source→target avec boutons Add/Remove 8. **CSV Import/Export**: Bouton pour exporter le glossaire en CSV et importer depuis un CSV 9. **Max Terms**: Si 500 termes atteint par glossaire, désactiver "Add Term" avec message 10. **Error Handling**: Afficher toast d'erreur si API retourne erreur (403, 404, etc.) 11. **Loading States**: Skeleton/loading pendant les appels API 12. **Colocation**: Tous les fichiers dans `frontend/src/app/dashboard/glossaries/` ## Tasks / Subtasks - [x] **Task 1: Créer la structure de dossiers** (AC: #12) - [x] 1.1 Créer `frontend/src/app/dashboard/glossaries/page.tsx` (fichier spécial: minuscules!) - [x] 1.2 Créer `frontend/src/app/dashboard/glossaries/types.ts` - [x] 1.3 Créer `frontend/src/app/dashboard/glossaries/useGlossaries.ts` - [x] **Task 2: Définir les types TypeScript** (AC: #3) - [x] 2.1 `GlossaryTerm` interface: id, source, target, created_at - [x] 2.2 `Glossary` interface: id, name, terms (GlossaryTerm[]), created_at, updated_at - [x] 2.3 `GlossaryListItem` interface: id, name, terms_count, created_at - [x] 2.4 `GlossaryListResponse` interface: data (GlossaryListItem[]), meta (total, page, per_page, total_pages) - [x] 2.5 `GlossaryDetailResponse` interface: data (Glossary), meta ({}) - [x] **Task 3: Créer useGlossaries.ts hook** (AC: #3, #4, #6, #10, #11) - [x] 3.1 `useQuery` pour GET `/api/v1/glossaries` avec TanStack Query - [x] 3.2 `createGlossary(name, terms[])`: mutation POST `/api/v1/glossaries` - [x] 3.3 `updateGlossary(id, {name?, terms?})`: mutation PATCH `/api/v1/glossaries/{id}` - [x] 3.4 `deleteGlossary(id)`: mutation DELETE `/api/v1/glossaries/{id}` - [x] 3.5 Gestion erreurs: 403 → Pro requis, 404 → Non trouvé - [x] 3.6 Loading states: isCreating, isUpdating, isDeleting - [x] 3.7 Cache invalidation après create/update/delete - [x] **Task 4: Créer la page principale** (AC: #1, #2, #3, #4, #5, #6, #11) - [x] 4.1 Récupérer `user.tier` via `useUser()` hook existant - [x] 4.2 Si `tier !== "pro"`: afficher `` (réutiliser depuis api-keys/) - [x] 4.3 Si `tier === "pro"`: afficher `` - [x] 4.4 `GlossariesManager`: Grille/Liste de cartes avec glossaires - [x] 4.5 Chaque carte: nom, nombre de termes, date création, boutons Edit/Delete - [x] 4.6 "Create New Glossary" button → ouvre CreateGlossaryDialog - [x] 4.7 Click sur carte → ouvre EditGlossaryDialog ou navigue vers édition - [x] **Task 5: Créer les composants UI** (AC: #4, #5, #7, #9) - [x] 5.1 `GlossaryCard.tsx`: Carte individuelle avec nom, terms_count, actions - [x] 5.2 `CreateGlossaryDialog.tsx`: Dialog pour créer un nouveau glossaire - [x] 5.3 `EditGlossaryDialog.tsx`: Dialog pour éditer nom + termes - [x] 5.4 `TermEditor.tsx`: Éditeur de paires source→target avec add/remove - [x] 5.5 `DeleteGlossaryDialog.tsx`: Confirmation dialog pour suppression - [x] **Task 6: Implémenter CSV Import/Export** (AC: #8) - [x] 6.1 `exportGlossaryToCsv(glossary)`: Génère fichier CSV et déclenche download - [x] 6.2 `parseCsvToTerms(file)`: Parse fichier CSV uploadé en GlossaryTerm[] - [x] 6.3 Bouton "Export CSV" dans EditGlossaryDialog - [x] 6.4 Bouton "Import CSV" avec file input dans EditGlossaryDialog - [x] 6.5 Format CSV: `source,target` (header optionnel) - [x] **Task 7: Intégration et tests** (AC: Tous) - [x] 7.1 `npm run build` → 0 erreurs TypeScript - [x] 7.2 Tester création glossaire → apparaît dans la liste - [x] 7.3 Tester édition termes → modifications sauvegardées - [x] 7.4 Tester suppression → glossaire retiré de la liste - [x] 7.5 Tester Free user → upgrade prompt affiché - [x] 7.6 Tester CSV export → fichier téléchargé correct - [x] 7.7 Tester CSV import → termes importés correct ## Dev Notes ### 🏗️ Stack Technique | Technologie | Version | |-------------|---------| | Next.js | 16.0.6 (App Router) | | React | 19.2.0 | | TanStack Query | v5.90.21 | | Tailwind CSS | configuré | | shadcn/ui | Card, Dialog, Button, Input, Badge, Toast, Separator | | Lucide React | BookText, Plus, Trash2, ArrowRight, Download, Upload, Save | ### 📁 Structure Cible (Colocation Pattern) ``` frontend/src/app/dashboard/glossaries/ ├── page.tsx # Page principale ├── types.ts # TypeScript interfaces ├── useGlossaries.ts # TanStack Query hook ├── GlossaryCard.tsx # Carte individuelle ├── CreateGlossaryDialog.tsx # Dialog création ├── EditGlossaryDialog.tsx # Dialog édition complète ├── TermEditor.tsx # Éditeur de paires source→target ├── DeleteGlossaryDialog.tsx # Confirmation suppression ├── csvUtils.ts # Fonctions CSV import/export └── ProUpgradePrompt.tsx # Copié/adapté depuis api-keys/ ``` **⚠️ Règle absolue (architecture.md):** ``` 🚨 FICHIERS SPÉCIAUX: page.tsx → TOUJOURS minuscules 🚨 COLOCATION: Components/hooks/types dans le dossier de leur page ``` ### 🔗 API Endpoints Backend (Déjà implémentés) | Endpoint | Méthode | Request | Response | |----------|---------|---------|----------| | `/api/v1/glossaries` | GET | `?page=1&per_page=50` | 200: `{data: [GlossaryListItem...], meta: {total, page, per_page, total_pages}}` | | `/api/v1/glossaries` | POST | `{name, terms: [{source, target}]}` | 201: `{data: Glossary, meta: {}}` | | `/api/v1/glossaries/{id}` | GET | — | 200: `{data: Glossary, meta: {}}` | | `/api/v1/glossaries/{id}` | PATCH | `{name?, terms?}` | 200: `{data: Glossary, meta: {}}` | | `/api/v1/glossaries/{id}` | DELETE | — | 204: No Content | **Codes erreur:** - 401: Token invalide → rediriger vers login - 403: `PRO_FEATURE_REQUIRED` → afficher upgrade prompt - 404: `GLOSSARY_NOT_FOUND` → glossaire non trouvé - 400: `TERMS_LIMIT_EXCEEDED` → max 500 termes - 400: `INVALID_GLOSSARY_ID` → UUID invalide ### 📊 API Response Examples **GET /api/v1/glossaries (200):** ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Glossaire Technique FR-EN", "terms_count": 25, "created_at": "2026-02-19T10:30:00Z" } ], "meta": { "total": 3, "page": 1, "per_page": 50, "total_pages": 1 } } ``` **GET /api/v1/glossaries/{id} (200):** ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Glossaire Technique FR-EN", "terms": [ { "id": "term-uuid-1", "source": "serveur", "target": "server", "created_at": "2026-02-19T10:30:00Z" }, { "id": "term-uuid-2", "source": "base de données", "target": "database", "created_at": "2026-02-19T10:31:00Z" } ], "created_at": "2026-02-19T10:30:00Z", "updated_at": "2026-02-20T14:00:00Z" }, "meta": {} } ``` **POST /api/v1/glossaries (201):** ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440001", "name": "Mon Nouveau Glossaire", "terms": [ {"id": "term-uuid-3", "source": "bonjour", "target": "hello", "created_at": "2026-02-23T14:30:00Z"} ], "created_at": "2026-02-23T14:30:00Z", "updated_at": "2026-02-23T14:30:00Z" }, "meta": {} } ``` ### 🎨 Patterns UI à Réutiliser **Depuis `glossary-context-card.tsx` (office-translator-landing-page):** - Layout grille pour TermEditor: `grid-cols-[1fr_32px_1fr_36px]` - ArrowRight icon entre source et target - Input avec `font-mono text-xs` - Button Trash2 avec opacity-0 group-hover:opacity-100 - Add Term button avec `variant="outline" border-dashed` **Depuis `api-keys/page.tsx` (frontend):** - Structure page: titre + description en haut - Card avec header icône + titre + description - ProUpgradePrompt component - Loading skeleton avec animate-spin - useToast pour les notifications **Depuis `DashboardSidebar.tsx`:** - Badge Pro avec style: `border border-accent/20 bg-accent/10 text-accent` ### 🔄 State Flow ``` ┌─────────────────────────────────────────────────────────┐ │ page.tsx │ │ ┌─────────────────────────────────────────────────────┐│ │ │ useUser() → tier check ││ │ │ ├─ tier !== "pro" → ││ │ │ └─ tier === "pro" → ││ │ └─────────────────────────────────────────────────────┘│ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ GlossariesManager │ │ ┌─────────────────────────────────────────────────────┐│ │ │ useGlossaries() hook ││ │ │ ├─ glossaries: GlossaryListItem[] ││ │ │ ├─ isLoading, isCreating, isUpdating, isDeleting ││ │ │ ├─ createGlossary(name, terms) ││ │ │ ├─ updateGlossary(id, data) ││ │ │ └─ deleteGlossary(id) ││ │ └─────────────────────────────────────────────────────┘│ │ │ │ ┌─────────────────────────────────────────────────────┐│ │ │ GlossaryGrid ││ │ │ └─ GlossaryCard × N ││ │ │ ├─ name, terms_count, created_at ││ │ │ └─ Actions: Edit, Delete ││ │ └─────────────────────────────────────────────────────┘│ │ │ │ [Create New Glossary] → opens CreateGlossaryDialog │ │ [Edit] → opens EditGlossaryDialog │ │ [Delete] → opens DeleteGlossaryDialog │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ EditGlossaryDialog │ │ ┌─────────────────────────────────────────────────────┐│ │ │ Name Input ││ │ └─────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────┐│ │ │ TermEditor ││ │ │ ├─ Row: Source Input → Target Input × N ││ │ │ └─ [Add Term] button ││ │ └─────────────────────────────────────────────────────┘│ │ ┌─────────────────────────────────────────────────────┐│ │ │ CSV Actions ││ │ │ ├─ [Export CSV] button ││ │ │ └─ [Import CSV] file input ││ │ └─────────────────────────────────────────────────────┘│ │ [Save Changes] → PATCH /api/v1/glossaries/{id} │ └─────────────────────────────────────────────────────────┘ ``` ### ⚠️ Points d'Attention Critiques 1. **Max 500 termes par glossaire**: Le backend renvoie 400 `TERMS_LIMIT_EXCEEDED`. Désactiver le bouton "Add Term" côté client quand on atteint 500. 2. **PATCH vs PUT**: Utiliser PATCH pour les updates (partiels). Le backend accepte `{name}` seul ou `{terms}` seul ou les deux. 3. **Cache invalidation**: Après create/update/delete, invalider le cache TanStack Query: ```typescript queryClient.invalidateQueries({ queryKey: ['glossaries'] }); ``` 4. **CSV Format**: - Export: Télécharger avec `text/csv` Content-Type et filename `{glossary_name}.csv` - Import: Accepter header optionnel `source,target` ou données directes 5. **Optimistic Updates**: Optionnel - pour UX fluide, on peut mettre à jour la liste immédiatement après création/update. 6. **UUID validation côté client**: Le backend valide les UUID mais on peut pré-valider pour UX. ### 📋 CSV Import/Export Format **Export (glossary_name.csv):** ```csv source,target serveur,server base de données,database client,client ``` **Import (accepte avec ou sans header):** ```csv serveur,server base de données,database ``` ### 📋 Checklist de Validation Avant Dev - [ ] Backend API `/api/v1/glossaries` est fonctionnel (testé via curl/Postman) - [ ] useUser() hook retourne bien `tier` field - [ ] shadcn/ui components installés: Card, Dialog, Button, Input, Badge, Toast, Separator - [ ] apiClient.ts gère correctement les erreurs 403/404 - [ ] Navigation sidebar affiche déjà "Glossaries" pour users Pro ### References - [Source: _bmad-output/planning-artifacts/epics.md#Story-4.10] — Story requirements - [Source: _bmad-output/planning-artifacts/architecture.md#Frontend-Architecture] — TanStack Query + colocation - [Source: _bmad-output/planning-artifacts/architecture.md#API-Response-Formats] — Format réponse API - [Source: routes/glossary_routes.py] — Backend API implementation (GET, POST, PATCH, DELETE) - [Source: schemas/glossary_schemas.py] — Pydantic schemas for validation - [Source: office-translator-landing-page/components/glossary-context-card.tsx] — UI patterns TermEditor à réutiliser - [Source: frontend/src/app/dashboard/api-keys/page.tsx] — Pattern page dashboard Pro - [Source: frontend/src/app/dashboard/api-keys/useApiKeys.ts] — Pattern TanStack Query hook - [Source: frontend/src/app/dashboard/useUser.ts] — Hook existant pour tier check - [Source: frontend/src/lib/apiClient.ts] — API client existant - [Source: frontend/src/app/dashboard/constants.ts] — Navigation items (Glossaries déjà présent pour Pro) - [Source: _bmad-output/implementation-artifacts/4-9-dashboard-api-keys-management-pro.md] — Story précédente, patterns à réutiliser ## Dev Agent Record ### Agent Model Used zai-anthropic/glm-5 ### Debug Log References ### Completion Notes List - 2026-02-23: Completed full implementation of Glossaries Editor dashboard page - Created all TypeScript types matching backend API schemas - Implemented TanStack Query hooks for CRUD operations with cache invalidation - Built complete UI with Pro upgrade prompt for free users - Implemented TermEditor with max 500 terms limit enforcement - Added CSV import/export functionality with proper parsing - Fixed TypeScript errors in api-keys/useApiKeys.ts and translate/page.tsx - Build passes with 0 errors - 2026-02-23: Code review fixes applied - [FIX] Added proper API error code parsing (TERMS_LIMIT_EXCEEDED, PRO_FEATURE_REQUIRED, etc.) - [FIX] Added CSV import validation for max 500 terms limit with user feedback - [FIX] Added pagination support (page, perPage parameters) - [FIX] Added automatic 401 redirect to login page - [FIX] Improved React key stability in TermEditor using content-based keys - [FIX] Added error handling for CSV file read failures ### File List **Created files:** - frontend/src/app/dashboard/glossaries/page.tsx - frontend/src/app/dashboard/glossaries/types.ts - frontend/src/app/dashboard/glossaries/useGlossaries.ts - frontend/src/app/dashboard/glossaries/GlossaryCard.tsx - frontend/src/app/dashboard/glossaries/CreateGlossaryDialog.tsx - frontend/src/app/dashboard/glossaries/EditGlossaryDialog.tsx - frontend/src/app/dashboard/glossaries/TermEditor.tsx - frontend/src/app/dashboard/glossaries/DeleteGlossaryDialog.tsx - frontend/src/app/dashboard/glossaries/csvUtils.ts - frontend/src/app/dashboard/glossaries/ProUpgradePrompt.tsx **Modified files:** - frontend/src/app/dashboard/api-keys/useApiKeys.ts (fixed TypeScript error) - frontend/src/app/dashboard/translate/page.tsx (fixed TypeScript error) ## Change Log - 2026-02-23: Story created - ready for development - 2026-02-23: Implementation complete - all ACs satisfied, ready for review - 2026-02-23: Code review completed - 5 issues fixed (2 HIGH, 3 MEDIUM), story marked done