Date: 2025-11-25 Objectif: Permettre aux utilisateurs de créer, modifier et supprimer des catégories avec icônes et couleurs personnalisées.
Avant: Les catégories sont des simples strings stockées dans les questions.
Après: Les catégories deviennent des objets structurés avec:
interface Category {
id: string // unique identifier
label: string // nom affichable
icon: string // nom de l'icône Phosphor (ex: "Code", "Database")
color: TailwindColor // couleur Tailwind (ex: "indigo", "blue")
}-
Persistance:
- Nouvelle table IndexedDB
categories - Repository
categoryRepositorypour CRUD
- Nouvelle table IndexedDB
-
State Management:
- Étendre
useDataStoreavec logique de gestion des catégories - Getter pour récupérer une catégorie par label
- Étendre
-
UI:
- Nouvelle page
/settings/categoriespour CRUD - Icône settings dans le header
- Mise à jour Home.vue et RandomConfig.vue
- Modal de confirmation suppression
- Sélecteur de catégorie lors de l'import JSON
- Nouvelle page
Fichier: src/types/models.ts
Modifications:
- Ajouter interface
Categoryavec propriétés:id,label,icon,color - Ajouter type
TailwindColor(union des 12 couleurs Tailwind: slate, red, orange, amber, yellow, lime, green, emerald, teal, cyan, blue, indigo, purple, pink) - Modifier Question pour garder
categorie: string(référence au label, pas à l'id)
Fichier: src/types/constants.ts
Modifications:
- Ajouter
DEFAULT_CATEGORIES: Category[]avec les catégories initiales :- TypeScript (icon: "Code", color: "blue")
- React (icon: "React", color: "cyan")
- Next.js (icon: "Rocket", color: "slate")
- Node.js (icon: "Node", color: "green")
- CSS (icon: "Palette", color: "purple")
- Database (icon: "Database", color: "orange")
- Entretien (icon: "Chat", color: "indigo")
-
- 5 autres pour les questions par défaut (Maths, Géographie, etc.)
À tester:
- Les interfaces TypeScript compilent sans erreur
- Les types sont corrects
Fichier: src/db/config.ts
Modifications:
- Ajouter
'categories'dansDB_CONFIG.STORES - Définir le schéma: keyPath
'id', index sur'label'(unique)
Fichier: src/db/repositories.ts
Modifications:
- Créer
categoryRepositoryavec méthodes:getAll(): Promise<Category[]>getByLabel(label: string): Promise<Category | undefined>save(category: Category): Promise<void>update(category: Category): Promise<void>delete(id: string): Promise<void>deleteByLabel(label: string): Promise<void>
À tester:
- Créer, lire, mettre à jour, supprimer une catégorie en IndexedDB
- Vérifier que les données persisten après rechargement de page
- L'index sur
labelempêche les doublons
Fichier: src/stores/useDataStore.ts
Modifications:
- Ajouter state
categories: Category[] - Ajouter actions:
loadCategories(): charge depuis IndexedDB au démarrageaddCategory(category: Category): Promise<void>: avec validation (label unique)updateCategory(category: Category): Promise<void>: validation labeldeleteCategory(categoryId: string): Promise<void>: supprime aussi les questions avec cette catégoriegetCategoryByLabel(label: string): Category | undefinedresetCategories(): réinitialise avec les catégories par défaut
- Ajouter getter
allCategories
Logique de suppression:
async deleteCategory(categoryId: string) {
const category = this.categories.find(c => c.id === categoryId)
if (!category) return
// Supprimer les questions de cette catégorie
this.questions = this.questions.filter(q => q.categorie !== category.label)
await questionRepository.saveMany(this.questions)
// Supprimer la catégorie
await categoryRepository.delete(categoryId)
this.categories = this.categories.filter(c => c.id !== categoryId)
}À tester:
- Les catégories se chargent au démarrage de l'app
- Ajouter une catégorie l'ajoute au state et à IndexedDB
- Modifier une catégorie met à jour le state et IndexedDB
- Supprimer une catégorie supprime aussi les questions associées
- Validation des labels uniques (empêcher les doublons)
Fichier: package.json
Modification:
- Ajouter dépendance:
phosphor-vue@^2.x(ou version compatible)
Commande:
npm install phosphor-vueFichier: src/main.ts
Modification:
- Importer et enregistrer le plugin Phosphor global:
import { PhosphorIcon } from 'phosphor-vue'
app.component('PhosphorIcon', PhosphorIcon)Utilisation dans templates:
<PhosphorIcon :weight="'bold'" :size="24">
{{ iconName }}
</PhosphorIcon>À tester:
- Les icônes s'affichent correctement
- Pas d'erreurs de compilation
- Les icônes Phosphor sont bien chargées (inspecter l'app)
Fichier à créer: src/views/settings/Categories.vue
Structure:
<template>
<div class="space-y-6">
<!-- En-tête -->
<div>
<h1 class="text-2xl font-bold">Gestion des catégories</h1>
<p class="text-slate-600">Créer, modifier ou supprimer des catégories</p>
</div>
<!-- Formulaire d'ajout/édition -->
<FormCategorie
:categorie="editingCategory"
@submit="handleSaveCategory"
@cancel="editingCategory = null"
/>
<!-- Liste des catégories -->
<ListeCategories
:categories="dataStore.allCategories"
@edit="editingCategory = $event"
@delete="handleDeleteCategory"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDataStore } from '@/stores/useDataStore'
import FormCategorie from '@/components/settings/FormCategorie.vue'
import ListeCategories from '@/components/settings/ListeCategories.vue'
const dataStore = useDataStore()
const editingCategory = ref(null)
const handleSaveCategory = async (category) => {
if (editingCategory.value?.id) {
await dataStore.updateCategory(category)
} else {
await dataStore.addCategory(category)
}
editingCategory.value = null
}
const handleDeleteCategory = async (categoryId) => {
if (confirm('Êtes-vous sûr? Toutes les questions de cette catégorie seront supprimées.')) {
await dataStore.deleteCategory(categoryId)
}
}
</script>Fichier à créer: src/components/settings/FormCategorie.vue
Features:
- Champs: label (input text), icon (select Phosphor), color (select Tailwind)
- Boutons: Sauvegarder, Annuler
- Validation: label non-vide et unique (sauf en édition)
- Mode création/édition
Fichier à créer: src/components/settings/ListeCategories.vue
Features:
- Tableau affichant: icône + couleur + label
- Actions: Éditer (remplis le formulaire), Supprimer (avec modale de confirmation)
- Affichage du nombre de questions par catégorie
À tester:
- Ajouter une catégorie (validée en IndexedDB)
- Modifier une catégorie (label change aussi dans les questions)
- Supprimer une catégorie (modale de confirmation, questions supprimées)
- Validation des champs (label unique)
- Navigation fonctionne correctement
Fichier: src/components/layout/AppHeader.vue
Modifications:
- Ajouter un bouton settings à côté du bouton stats
- Router link vers
/settings/categories - Icône:
GearouSlidersde Phosphor
Template:
<RouterLink to="/settings/categories" class="...">
<PhosphorIcon :weight="'bold'" :size="24">Gear</PhosphorIcon>
</RouterLink>À tester:
- L'icône s'affiche dans le header
- Clic redirige vers
/settings/categories - Navigation fonctionne sur tous les écrans
Fichier: src/router/index.ts
Modifications:
- Ajouter route:
/settings/categories→Categories.vue
À tester:
- URL
/settings/categoriescharge la page - Navigation par RouterLink fonctionne
Fichier: src/views/settings/Import.vue
Modifications:
- Avant de lancer l'import, afficher un modal pour sélectionner une catégorie cible
- Le modal liste toutes les catégories existantes
- Option pour créer une nouvelle catégorie à la volée
- Une fois sélectionnée, assigner cette catégorie à TOUTES les questions du JSON
Logique:
- L'utilisateur sélectionne un fichier JSON
- Modal s'affiche avec liste des catégories
- Utilisateur sélectionne une catégorie
- Import exécuté avec assignment de catégorie
Fichier à créer: src/components/settings/ModalSelectCategory.vue
Features:
- Liste des catégories existantes
- Champ texte pour créer nouvelle catégorie + select icon/color
- Boutons: Importer, Annuler
À tester:
- Modal s'affiche au bon moment
- Sélectionner une catégorie existante fonctionne
- Créer une nouvelle catégorie à la volée fonctionne
- L'import assigne bien la catégorie à TOUTES les questions
- Données en IndexedDB sont correctes
Fichier: src/views/quiz/Home.vue
Modifications:
- Récupérer les catégories depuis le store au lieu d'extraire des questions
- Afficher l'icône Phosphor et couleur de chaque catégorie
- Dynamiser les classes Tailwind basées sur la couleur
Logique de couleur:
<div
:class="{
'bg-indigo-50': cat.color === 'indigo',
'bg-blue-50': cat.color === 'blue',
// ... pour les 12 couleurs
}"
>
<PhosphorIcon :weight="'bold'" :size="24">{{ cat.icon }}</PhosphorIcon>
</div>À tester:
- Les catégories s'affichent avec les bonnes icônes
- Les couleurs sont cohérentes
- Cliquer sur une catégorie lance un quiz avec les bonnes questions
- Les questions des catégories supprimées ne s'affichent pas
Fichier: src/views/quiz/RandomConfig.vue
Modifications:
- Afficher les icônes et couleurs pour chaque catégorie
- Utiliser le même style que Home.vue pour cohérence
À tester:
- Les catégories s'affichent avec icônes/couleurs
- Multi-sélection fonctionne
- Lancer un quiz avec sélection aléatoire fonctionne
Tests manuels:
-
Workflow création catégorie:
- Naviguer vers settings/categories
- Créer une nouvelle catégorie avec label, icône, couleur
- Vérifier en IndexedDB que c'est persisté
- Vérifier que le label s'ajoute aux choix d'import
-
Workflow modification catégorie:
- Modifier une catégorie existante
- Changer son label
- Vérifier que les questions mises à jour reflètent le nouveau label
-
Workflow suppression catégorie:
- Importer des questions avec une catégorie
- Supprimer la catégorie
- Modale de confirmation s'affiche
- Vérifier que les questions sont supprimées
- Vérifier que les questions ne s'affichent plus en Home.vue
-
Workflow import JSON:
- Aller à /settings/import
- Sélectionner un fichier JSON
- Modal de sélection catégorie s'affiche
- Sélectionner une catégorie
- Import réussit
- Vérifier que les questions ont la bonne catégorie
-
Workflow quiz normal:
- Home.vue affiche les catégories avec icônes/couleurs
- Sélectionner une catégorie et lancer un quiz
- Quiz fonctionne correctement
- Résultats sauvegardés
-
Tests de validation:
- Impossible de créer deux catégories avec le même label
- Impossible de créer une catégorie sans label
- Modification d'une catégorie n'affecte pas les autres
-
Tests de persistance:
- Créer une catégorie
- Rafraîchir la page
- Catégorie existe toujours
- Questions importées sont toujours là
À tester:
- Tous les workflows ci-dessus fonctionnent
- Pas d'erreurs console
- Pas de mutations involontaires en IndexedDB
- Performances acceptables avec beaucoup de catégories/questions
{
"phosphor-vue": "^2.x"
}src/views/settings/Categories.vuesrc/components/settings/FormCategorie.vuesrc/components/settings/ListeCategories.vuesrc/components/settings/ModalSelectCategory.vue
src/types/models.ts- Ajouter interfaceCategorysrc/types/constants.ts- AjouterDEFAULT_CATEGORIESsrc/db/config.ts- Ajouter store 'categories'src/db/repositories.ts- AjoutercategoryRepositorysrc/stores/useDataStore.ts- Ajouter logique catégoriessrc/components/layout/AppHeader.vue- Ajouter bouton settingssrc/router/index.ts- Ajouter route /settings/categoriessrc/views/settings/Import.vue- Ajouter modal sélection catégoriesrc/views/quiz/Home.vue- Afficher icônes/couleurssrc/views/quiz/RandomConfig.vue- Afficher icônes/couleurssrc/main.ts- Enregistrer plugin Phosphor
slate, red, orange, amber, yellow, lime, green, emerald, teal, cyan, blue, indigo, purple, pink
- TypeScript: "Code", "Brackets"
- React: "React"
- Next.js: "Rocket"
- Node.js: "Cpu"
- CSS: "Palette"
- Database: "Database"
- Entretien: "Chat", "Microphone"
- Maths: "Calculator"
- Science: "Microscope"
- Géographie: "Globe"
- Art: "Palette"
- Label de catégorie: non-vide, unique (case-sensitive)
- Color: doit être dans la liste des 12 couleurs Tailwind
- Icon: doit être un nom valide d'icône Phosphor
- Import JSON avec catégorie n'existant pas → créer la catégorie
- Suppression catégorie avec questions → confirmation modale, supprimer questions
- Mise à jour catégorie → mettre à jour toutes les questions avec l'ancien label
- ✅ ÉTAPE 1 : Types
- ✅ ÉTAPE 2 : IndexedDB + Repository
- ✅ ÉTAPE 3 : Store Pinia
- ✅ ÉTAPE 4 : Phosphor Vue
- ✅ ÉTAPE 5-7 : Page + Route
- ✅ ÉTAPE 8 : Import JSON
- ✅ ÉTAPE 9-10 : UI Home + RandomConfig
- ✅ ÉTAPE 6 : Header
- ✅ ÉTAPE 11 : Tests
Chaque étape est testable indépendamment avant de passer à la suivante.