From a2c86247b606b8bb8af8adafa270bb0fdf276aaf Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Sat, 28 Mar 2026 13:11:05 +0100 Subject: [PATCH] feat(VocabLessonView): enhance exercise access and vocab trainer experience - Added conditional access to exercises based on user progress and previous vocab completion. - Introduced visual indicators for exercise availability and review priorities to guide users. - Updated vocab trainer descriptions and button labels to reflect the user's current state and encourage engagement. - Implemented methods to manage exercise unlock conditions and improve user feedback on progress. --- frontend/src/views/social/VocabLessonView.vue | 112 ++++++++++++++++-- 1 file changed, 104 insertions(+), 8 deletions(-) diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 4280b2a..2756f82 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -19,8 +19,9 @@ {{ $t('socialnetwork.vocab.courses.learn') }}
@@ -293,8 +302,8 @@
-
-
@@ -692,6 +701,7 @@ export default { vocabTrainerPhase: 'current', // 'current' = aktuelle Lektion, 'mixed' = gemischt mit alten vocabTrainerMixedPool: [], // Pool aus alten Lektionsvokabeln vocabTrainerMixedAttempts: 0, // Zähler für Mixed-Phase + exercisePreparationCompleted: false, currentVocabQuestion: null, vocabTrainerAnswer: '', vocabTrainerSelectedChoice: null, @@ -727,6 +737,20 @@ export default { const exercises = this.effectiveExercises; return exercises && Array.isArray(exercises) && exercises.length > 0; }, + hasPreviousVocab() { + return Array.isArray(this.previousVocab) && this.previousVocab.length > 0; + }, + canAccessExercises() { + if (!this.hasExercises) return false; + const isReview = this.lesson?.lessonType === 'review' || this.lesson?.lessonType === 'vocab_review'; + return isReview || this.exercisePreparationCompleted; + }, + exerciseUnlockHint() { + if (this.hasPreviousVocab) { + return 'Beantworte zuerst einige Wiederholungsfragen aus älteren Lektionen richtig. Danach wird die Kapitel-Prüfung freigeschaltet.'; + } + return 'Arbeite zuerst kurz mit dem Vokabeltrainer der aktuellen Lektion. Danach wird die Kapitel-Prüfung freigeschaltet.'; + }, /** Für Wiederholungslektionen: Übungen aus vorherigen Lektionen (Kapitelprüfung). Sonst: eigene Grammatik-Übungen. */ effectiveExercises() { if (!this.lesson) return []; @@ -836,6 +860,40 @@ export default { } }, methods: { + openExercisesTab() { + if (!this.canAccessExercises) { + this.activeTab = 'learn'; + this.showErrorDialog = true; + this.errorMessage = this.exerciseUnlockHint; + return; + } + this.activeTab = 'exercises'; + }, + updateExerciseUnlockState() { + if (this.exercisePreparationCompleted) { + return; + } + if (!this.hasExercises) { + this.exercisePreparationCompleted = true; + return; + } + + const minimumAttempts = this.hasPreviousVocab ? 8 : 6; + const successRate = this.vocabTrainerTotalAttempts > 0 + ? (this.vocabTrainerCorrect / this.vocabTrainerTotalAttempts) * 100 + : 0; + + if (this.hasPreviousVocab) { + if (this.vocabTrainerPhase === 'mixed' && this.vocabTrainerTotalAttempts >= minimumAttempts && successRate >= 70) { + this.exercisePreparationCompleted = true; + } + return; + } + + if (this.vocabTrainerTotalAttempts >= minimumAttempts && successRate >= 70) { + this.exercisePreparationCompleted = true; + } + }, _extractVocabFromExercises(exercises) { // Sicherstellen, dass exercises ein Array ist if (!exercises) { @@ -989,6 +1047,11 @@ export default { this.assistantMessages = []; this.assistantInput = ''; this.assistantError = ''; + this.exercisePreparationCompleted = false; + this.vocabTrainerActive = false; + this.vocabTrainerPool = []; + this.vocabTrainerMixedPool = []; + this.vocabTrainerPhase = 'current'; // Reset Flags this.isCheckingLessonCompletion = false; this.isNavigatingToNext = false; @@ -998,6 +1061,9 @@ export default { if (tabParam === 'learn') { this.activeTab = 'learn'; } + if (tabParam === 'exercises') { + this.activeTab = 'learn'; + } if (this.$route.query.assistant) { this.activeTab = 'learn'; } @@ -1406,10 +1472,13 @@ 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(); + this.vocabTrainerPhase = this.vocabTrainerMixedPool.length > 0 ? 'mixed' : 'current'; + this.vocabTrainerMixedAttempts = 0; + this.vocabTrainerPool = this.vocabTrainerPhase === 'mixed' + ? [...this.vocabTrainerMixedPool] + : [...this.importantVocab]; debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln'); debugLog('[VocabLessonView] Rufe nextVocabQuestion auf'); this.$nextTick(() => { @@ -1451,6 +1520,8 @@ export default { const MC_THRESHOLD = 60; // Multiple Choice Versuche pro Phase const MIXED_LIMIT = 40; // Anzahl gemischter Vokabeln aus alten Lektionen + this.updateExerciseUnlockState(); + 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) { @@ -2349,6 +2420,31 @@ export default { color: #333; } +.review-priority-note, +.exercise-lock-note { + margin-bottom: 12px; + padding: 12px; + border-radius: 4px; +} + +.review-priority-note { + background: #eef7ff; + border: 1px solid #b9d8ff; + color: #234a72; +} + +.exercise-lock-note { + background: #fff3cd; + border: 1px solid #ffc107; + color: #856404; +} + +.review-priority-note strong, +.exercise-lock-note strong { + display: block; + margin-bottom: 4px; +} + .vocab-trainer-start { text-align: center; }