Migrer une application Quiz monolithique (HTML + Vue 3 CDN) vers une architecture modulaire et scalable avec Vite, TypeScript, Pinia et Vue Router.
Prototype actuel: 1 fichier HTML (~1447 lignes), état global en mémoire Cible: Architecture complète avec séparation des responsabilités
Créer les interfaces TypeScript pour tous les modèles métier :
- Question { id, intitule, reponses, indexBonneReponse, explication, categorie, difficulte, countApparition, countBonneReponse }
- SessionQuestion { ...Question, ordreReponses[], estSkippe, estCorrecte, estChoisie }
- QuizSession { sessionId, dateDebut, dateFin, questions[], indexQuestionCourante, nbQuestions, scorePondere, scorePondereMax, notePourcentage, difficulteChoisie, categories, dateJour }
- Badge { id, nom, description, statut, icon, dateDebloque }
- GlobalStats { moyenneGlobale, meilleurScore, streakActuel, totalSessions, historiqueSessions[] }
- ImportData (pour validation import JSON)
- Constantes d'énumération (difficultés, statut badges, etc.)
- Données par défaut (DEFAULT_QUESTIONS, DEFAULT_BADGES)
- Configuration IndexedDB : DB_NAME, DB_VERSION, stores
initDatabase(): création des object storesgetDbPromise(): singleton d'accès
Isolation complète de la logique d'accès données :
- saveQuestion(q: Question): Promise
- loadAllQuestions(): Promise<Question[]>
- updateQuestion(q: Question): Promise
- saveSession(session: QuizSession): Promise
- loadAllSessions(): Promise<QuizSession[]>
- deleteSession(sessionId: string): Promise
- updateBadges(badges: Badge[]): Promise
- loadBadges(): Promise<Badge[]>
- clearAllSessions(): Promise
Responsabilité: Gestion des données statiques (questions, badges)
State:
- questions: Question[]
- badges: Badge[]
- isLoading: boolean
- error: string
Actions:
- initData() - charge questions et badges du DB, ou defaults
- importQuestions(json: any[]) - valide et sauvegarde import
- resetBadges() - réinitialise status à "verrouille"
Responsabilité: Gestion d'une session de quiz active
State:
- activeSession: QuizSession | null
- selectedAnswerIndex: number | null
- hasAnswered: boolean
- showResumeModal: boolean
Computed:
- currentQuestion
- currentQuestionIndex
- progressPercent
- isLastQuestion
Actions:
- createQuizSession(categories[], difficulty, count)
- submitAnswer(answerIndex)
- skipQuestion()
- nextQuestion()
- finishQuiz()
- resumePreviousSession()
- abandonSession()
- saveCurrentSession() (aux)
Responsabilité: Calcul des statistiques et logique des badges
State:
- globalStats: GlobalStats
- previousStats: { average }
- newlyUnlockedBadges: Badge[]
Computed:
- badgesNonLus (getter pour notification)
Actions:
- loadStats() - recalcule depuis toutes les sessions
- updateStatsAndBadges(session) - après fin de quiz
- calculateStreak(sessions[]) (helper)
- calculateDailyAverages(sessions[]) (helper)
- checkAndUnlockBadges(session, allSessions) (helper)
- resetAllStats()
Configuration des routes remplaçant currentView :
Routes:
- /home - HomeView
- /quiz/difficulty - DifficultyView
- /quiz/count - QuestionCountView
- /quiz/randomconfig - RandomConfigView
- /quiz/active - QuizView
- /quiz/summary - QuizSummaryView
- /stats - StatsView
- /settings/import - ImportView
- / (redirect to /home)
Navigation patterns:
- selectCategory() → router.push('/quiz/difficulty')
- selectDifficulty() → router.push('/quiz/count')
- startQuiz() → router.push('/quiz/active')
- finishQuiz() → router.push('/quiz/summary')
src/components/
├── layout/
│ ├── AppHeader.vue - Header avec logo + stats button
│ └── AppLayout.vue - Wrapper avec header + <router-view>
└── ...
src/views/
├── quiz/
│ ├── Home.vue - Sélection catégorie + mode aléatoire
│ ├── RandomConfig.vue - Checkbox catégories
│ ├── Difficulty.vue - Choix difficulté
│ ├── Count.vue - Choix nombre questions
│ ├── Active.vue - Quiz actif avec progression
│ └── Summary.vue - Score + badges + comparaison stats
├── stats/
│ └── Index.vue - KPIs + chart + badges
└── settings/
└── Import.vue - Import JSON + Reset
src/components/
├── quiz/
│ ├── QuestionCard.vue - Affichage intitulé + explication
│ ├── AnswerOption.vue - Bouton réponse avec states
│ └── ProgressBar.vue - Barre de progression
├── stats/
│ ├── StatCard.vue - KPI (moyenne, meilleur score, etc.)
│ ├── EvolutionChart.vue - Chart.js wrapper
│ └── BadgesGrid.vue - Grille badges 3 colonnes
└── common/
├── BaseButton.vue - Button réutilisable
├── BaseModal.vue - Modal générique (resume, etc.)
└── LoadingSpinner.vue
<template>
<transition name="slide" mode="out-in">
<router-view />
</transition>
</template>
<style>
.slide-enter-active, .slide-leave-active { transition: all 0.3s ease; }
.slide-enter-from { opacity: 0; transform: translateX(30px); }
.slide-leave-to { opacity: 0; transform: translateX(-30px); }
</style>10 questions par défaut (math, géographie, science, art, physique, etc.)
Export DEFAULT_QUESTIONS, DEFAULT_BADGES, helper pour créer sessions de test
| Logique | Localisation | Type |
|---|---|---|
| State questions/badges | useDataStore |
Store |
| State activeSession | useQuizStore |
Store |
| State globalStats | useStatsStore |
Store |
| IndexedDB operations | db/repositories.ts |
Services |
| Validation import JSON | useDataStore.importQuestions() |
Action Store |
| Calcul streak/badges | useStatsStore.checkAndUnlockBadges() |
Action Store |
| Chart.js rendering | EvolutionChart.vue |
Composant |
| Modal resume | BaseModal.vue (controlled by useQuizStore) |
Composant |
| Navigation | router/index.ts + router.push() |
Router |
- Créer
types/models.tsavec toutes les interfaces - Créer
types/constants.tsavec enums et defaults - Configurer
db/config.tsetdb/repositories.ts
- Implémenter
useDataStore.ts - Implémenter
useQuizStore.ts - Implémenter
useStatsStore.ts
- Configurer
router/index.tsavec 7 routes - Créer
App.vueavec<router-view>et transitions - Créer
AppHeader.vueetAppLayout.vue
- HomeView, DifficultyView, CountView
- RandomConfigView
- QuizView (active)
- SummaryView
- StatsView
- ImportView
- Composants quiz (QuestionCard, AnswerOption, ProgressBar)
- Composants stats (StatCard, EvolutionChart, BadgesGrid)
- Composants common (BaseButton, BaseModal)
- Fixtures/données de test
- Tests unitaires pour stores
- Tests e2e pour workflows clés
- Optimisations CSS/responsiveness
- Resume modal → useQuizStore : Au démarrage de l'app (onMounted), le store vérifie si une session existe en DB
- Transitions → router-view : Transitions automatiques sur les changements de route
- Structure views par feature : Plus facile à maintenir quand la feature grandit
- Chart.js in component :
onMounted()init,onUnmounted()cleanup - Seed données : DEFAULT_QUESTIONS dans
types/constants.ts, chargeables au premier lancement
- Types + Constants
- DB Layer
- Stores (dans cet ordre : DataStore → QuizStore → StatsStore)
- Router setup
- AppLayout + AppHeader
- Vue principale : HomeView (simple structure)
- Composants communs (BaseButton, BaseModal)
- Vues quiz complètes
- Vues stats + composants
- Fixtures + tests
- Polish + optimisations