# 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 `<ProUpgradePrompt />` (réutiliser depuis api-keys/)
  - [x] 4.3 Si `tier === "pro"`: afficher `<GlossariesManager />`
  - [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" → <ProUpgradePrompt />          ││
│  │   └─ tier === "pro" → <GlossariesManager />         ││
│  └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│                 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
