diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index 0cb2332..8f80fd9 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -885,10 +885,14 @@ export default class VocabService { const plainLesson = lesson.get({ plain: true }); - // Bei Wiederholungslektionen: Lade Vokabeln aus vorherigen Lektionen + // Lade Vokabeln aus vorherigen Lektionen (für Wiederholung UND für gemischten Vokabeltrainer) + if (plainLesson.lessonNumber > 1) { + plainLesson.previousLessonExercises = await this._getReviewVocabExercises(plainLesson.courseId, plainLesson.lessonNumber); + } + // Bei Wiederholungslektionen: Auch Lektions-Liste für Anzeige if (plainLesson.lessonType === 'review' || plainLesson.lessonType === 'vocab_review') { plainLesson.reviewLessons = await this._getReviewLessons(plainLesson.courseId, plainLesson.lessonNumber); - plainLesson.reviewVocabExercises = await this._getReviewVocabExercises(plainLesson.courseId, plainLesson.lessonNumber); + plainLesson.reviewVocabExercises = plainLesson.previousLessonExercises || []; } console.log(`[getLesson] Lektion ${lessonId} geladen:`, { @@ -897,7 +901,8 @@ export default class VocabService { lessonType: plainLesson.lessonType, exerciseCount: plainLesson.grammarExercises ? plainLesson.grammarExercises.length : 0, reviewLessonsCount: plainLesson.reviewLessons ? plainLesson.reviewLessons.length : 0, - reviewVocabExercisesCount: plainLesson.reviewVocabExercises ? plainLesson.reviewVocabExercises.length : 0 + reviewVocabExercisesCount: plainLesson.reviewVocabExercises ? plainLesson.reviewVocabExercises.length : 0, + previousLessonExercisesCount: plainLesson.previousLessonExercises ? plainLesson.previousLessonExercises.length : 0 }); return plainLesson; } diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json index 987244a..bf0d0ab 100644 --- a/frontend/src/i18n/locales/de/socialnetwork.json +++ b/frontend/src/i18n/locales/de/socialnetwork.json @@ -387,6 +387,8 @@ "successRate": "Erfolgsrate", "modeMultipleChoice": "Multiple Choice", "modeTyping": "Texteingabe", + "currentLesson": "Aktuelle Lektion", + "mixedReview": "Wiederholung", "lessonCompleted": "Lektion abgeschlossen!", "goToNextLesson": "Zur nächsten Lektion wechseln?", "allLessonsCompleted": "Alle Lektionen abgeschlossen!", diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json index 7e3c913..2dccfe1 100644 --- a/frontend/src/i18n/locales/en/socialnetwork.json +++ b/frontend/src/i18n/locales/en/socialnetwork.json @@ -387,6 +387,8 @@ "successRate": "Success Rate", "modeMultipleChoice": "Multiple Choice", "modeTyping": "Text Input", + "currentLesson": "Current Lesson", + "mixedReview": "Review", "lessonCompleted": "Lesson completed!", "goToNextLesson": "Go to next lesson?", "allLessonsCompleted": "All lessons completed!", diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 318f7fa..fc9d878 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -73,6 +73,12 @@
+ + {{ $t('socialnetwork.vocab.courses.currentLesson') || 'Aktuelle Lektion' }} + + + {{ $t('socialnetwork.vocab.courses.mixedReview') || 'Wiederholung' }} + {{ $t('socialnetwork.vocab.courses.modeMultipleChoice') }} @@ -441,6 +447,9 @@ export default { vocabTrainerTotalAttempts: 0, vocabTrainerStats: {}, // { [vocabKey]: { attempts: 0, correct: 0, wrong: 0 } } vocabTrainerChoiceOptions: [], + vocabTrainerPhase: 'current', // 'current' = aktuelle Lektion, 'mixed' = gemischt mit alten + vocabTrainerMixedPool: [], // Pool aus alten Lektionsvokabeln + vocabTrainerMixedAttempts: 0, // Zähler für Mixed-Phase currentVocabQuestion: null, vocabTrainerAnswer: '', vocabTrainerSelectedChoice: null, @@ -507,6 +516,16 @@ export default { return []; } }, + /** Vokabeln aus vorherigen Lektionen (für gemischte Wiederholung im Vokabeltrainer) */ + previousVocab() { + try { + if (!this.lesson || !this.lesson.previousLessonExercises) return []; + return this._extractVocabFromExercises(this.lesson.previousLessonExercises); + } catch (e) { + console.error('Fehler in previousVocab:', e); + return []; + } + }, importantVocab() { // Extrahiere wichtige Begriffe aus den Übungen try { @@ -998,6 +1017,7 @@ export default { return; } console.log('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length); + console.log('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0); this.vocabTrainerActive = true; this.vocabTrainerPool = [...this.importantVocab]; this.vocabTrainerMode = 'multiple_choice'; @@ -1006,6 +1026,11 @@ export default { this.vocabTrainerWrong = 0; this.vocabTrainerTotalAttempts = 0; this.vocabTrainerStats = {}; + this.vocabTrainerPhase = 'current'; + this.vocabTrainerMixedAttempts = 0; + // Bereite Mixed-Pool aus alten Vokabeln vor (ohne Duplikate aus aktueller Lektion) + this.vocabTrainerMixedPool = this._buildMixedPool(); + console.log('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln'); console.log('[VocabLessonView] Rufe nextVocabQuestion auf'); this.$nextTick(() => { this.nextVocabQuestion(); @@ -1015,11 +1040,23 @@ export default { this.vocabTrainerActive = false; this.vocabTrainerMode = 'multiple_choice'; this.vocabTrainerAutoSwitchedToTyping = false; + this.vocabTrainerPhase = 'current'; + this.vocabTrainerMixedAttempts = 0; + this.vocabTrainerMixedPool = []; this.currentVocabQuestion = null; this.vocabTrainerAnswer = ''; this.vocabTrainerSelectedChoice = null; this.vocabTrainerAnswered = false; }, + /** Erstellt den Mixed-Pool aus vorherigen Lektions-Vokabeln (ohne Duplikate der aktuellen Lektion) */ + _buildMixedPool() { + if (!this.previousVocab || this.previousVocab.length === 0) return []; + const currentKeys = new Set(this.importantVocab.map(v => this.getVocabKey(v))); + const filtered = this.previousVocab.filter(v => !currentKeys.has(this.getVocabKey(v))); + // Zufällig mischen und auf 40 begrenzen + const shuffled = [...filtered].sort(() => Math.random() - 0.5); + return shuffled.slice(0, 40); + }, getVocabKey(vocab) { return `${vocab.learning}|${vocab.reference}`; }, @@ -1031,13 +1068,43 @@ export default { return this.vocabTrainerStats[key]; }, checkVocabModeSwitch() { - // Wechsle zu Texteingabe wenn 80% erreicht und mindestens 20 Versuche - if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= 20) { - const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100; - if (successRate >= 80) { + const MC_THRESHOLD = 60; // Multiple Choice Versuche pro Phase + const MIXED_LIMIT = 40; // Anzahl gemischter Vokabeln aus alten Lektionen + + if (this.vocabTrainerPhase === 'current') { + // Phase 1: Aktuelle Lektion - nach MC_THRESHOLD Versuchen mit 80% → Wechsel zu Mixed oder Typing + if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MC_THRESHOLD) { + const successRate = (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100; + if (successRate >= 80) { + // Wechsel zur Mixed-Phase (falls alte Vokabeln vorhanden) + if (this.vocabTrainerMixedPool.length > 0) { + console.log('[VocabLessonView] Wechsel zu Mixed-Phase mit', this.vocabTrainerMixedPool.length, 'alten Vokabeln'); + this.vocabTrainerPhase = 'mixed'; + this.vocabTrainerPool = [...this.vocabTrainerMixedPool]; + this.vocabTrainerMixedAttempts = 0; + // Stats zurücksetzen für neue Phase + this.vocabTrainerCorrect = 0; + this.vocabTrainerWrong = 0; + this.vocabTrainerTotalAttempts = 0; + } else { + // Kein Mixed-Pool → direkt zu Typing + this.vocabTrainerMode = 'typing'; + this.vocabTrainerAutoSwitchedToTyping = true; + this.vocabTrainerPool = [...this.importantVocab]; + this.vocabTrainerCorrect = 0; + this.vocabTrainerWrong = 0; + this.vocabTrainerTotalAttempts = 0; + } + } + } + } else if (this.vocabTrainerPhase === 'mixed') { + // Phase 2: Gemischte Wiederholung - nach MIXED_LIMIT Versuchen → Wechsel zu Typing mit allen Vokabeln + if (this.vocabTrainerMode === 'multiple_choice' && this.vocabTrainerTotalAttempts >= MIXED_LIMIT) { + console.log('[VocabLessonView] Mixed-Phase abgeschlossen, wechsle zu Typing'); this.vocabTrainerMode = 'typing'; - this.vocabTrainerAutoSwitchedToTyping = true; // Markiere als automatisch gewechselt - // Reset Stats für Texteingabe-Modus + this.vocabTrainerAutoSwitchedToTyping = true; + // Im Typing: Pool aus aktuellen + alten Vokabeln kombinieren + this.vocabTrainerPool = [...this.importantVocab, ...this.vocabTrainerMixedPool]; this.vocabTrainerCorrect = 0; this.vocabTrainerWrong = 0; this.vocabTrainerTotalAttempts = 0; @@ -1048,6 +1115,12 @@ export default { // Wechsle zurück zu Multiple Choice this.vocabTrainerMode = 'multiple_choice'; this.vocabTrainerAutoSwitchedToTyping = false; + // Zurück zur aktuellen Phase mit passendem Pool + if (this.vocabTrainerPhase === 'mixed') { + this.vocabTrainerPool = [...this.vocabTrainerMixedPool]; + } else { + this.vocabTrainerPool = [...this.importantVocab]; + } // Reset Stats für Multiple Choice Modus this.vocabTrainerCorrect = 0; this.vocabTrainerWrong = 0;