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;