diff --git a/frontend/src/i18n/locales/de/socialnetwork.json b/frontend/src/i18n/locales/de/socialnetwork.json index d6548b0..3e05cbc 100644 --- a/frontend/src/i18n/locales/de/socialnetwork.json +++ b/frontend/src/i18n/locales/de/socialnetwork.json @@ -523,6 +523,8 @@ "exerciseWrongTitle": "Noch nicht richtig", "exerciseReinforcementGoPractice": "Zum Üben wechseln", "exerciseReinforcementStay": "Bei der Prüfung bleiben", + "exerciseReinforcementGoPracticeAck": "Gelesen, zum Üben wechseln", + "exerciseReinforcementStayAck": "Gelesen, bei der Prüfung bleiben", "exerciseStatusOpen": "Offen", "exerciseStatusCorrect": "Erledigt", "exerciseStatusRetry": "Nochmal prüfen", diff --git a/frontend/src/i18n/locales/en/socialnetwork.json b/frontend/src/i18n/locales/en/socialnetwork.json index b4cf391..d17f6d0 100644 --- a/frontend/src/i18n/locales/en/socialnetwork.json +++ b/frontend/src/i18n/locales/en/socialnetwork.json @@ -523,6 +523,8 @@ "exerciseWrongTitle": "Not quite right", "exerciseReinforcementGoPractice": "Go to practice", "exerciseReinforcementStay": "Stay on the test", + "exerciseReinforcementGoPracticeAck": "Read, go to practice", + "exerciseReinforcementStayAck": "Read, stay on the test", "exerciseStatusOpen": "Open", "exerciseStatusCorrect": "Done", "exerciseStatusRetry": "Try again", diff --git a/frontend/src/i18n/locales/es/socialnetwork.json b/frontend/src/i18n/locales/es/socialnetwork.json index 90628e1..b299f64 100644 --- a/frontend/src/i18n/locales/es/socialnetwork.json +++ b/frontend/src/i18n/locales/es/socialnetwork.json @@ -521,6 +521,8 @@ "exerciseWrongTitle": "Aún no es correcto", "exerciseReinforcementGoPractice": "Ir a practicar", "exerciseReinforcementStay": "Seguir en la prueba", + "exerciseReinforcementGoPracticeAck": "Leído, ir a practicar", + "exerciseReinforcementStayAck": "Leído, seguir en la prueba", "exerciseStatusOpen": "Pendiente", "exerciseStatusCorrect": "Hecha", "exerciseStatusRetry": "Revisar otra vez", diff --git a/frontend/src/views/social/VocabLessonReviewView.vue b/frontend/src/views/social/VocabLessonReviewView.vue index 4c27a6d..0c3f987 100644 --- a/frontend/src/views/social/VocabLessonReviewView.vue +++ b/frontend/src/views/social/VocabLessonReviewView.vue @@ -49,6 +49,14 @@ > {{ $t('socialnetwork.vocab.courses.checkAnswer') }} +
@@ -80,8 +88,10 @@ export default { typedAnswer: '', feedback: '', feedbackCorrect: false, + needsFeedbackAck: false, correctCount: 0, - reviewDone: false + reviewDone: false, + weakVocabMap: {} }; }, computed: { @@ -103,6 +113,9 @@ export default { normalize(s) { return String(s || '').trim().toLowerCase(); }, + getItemKey(item) { + return `${String(item?.gloss || '').trim()}|${String(item?.target || '').trim()}`; + }, parseCorePatterns() { const raw = this.lesson?.didactics?.corePatterns || []; const out = []; @@ -151,6 +164,7 @@ export default { this.feedback = ''; this.selectedOption = ''; this.typedAnswer = ''; + this.needsFeedbackAck = false; if (!this.currentItem) return; this.mode = this.currentItem.gloss ? 'multiple_choice' : 'typing'; if (this.mode === 'multiple_choice') { @@ -170,26 +184,85 @@ export default { if (isCorrect) { this.correctCount += 1; this.feedback = this.$t('socialnetwork.vocab.courses.correct'); + this.needsFeedbackAck = false; + window.setTimeout(() => { + this.advanceAfterFeedback(); + }, 550); } else { this.feedback = `${this.$t('socialnetwork.vocab.courses.wrong')} - ${this.$t('socialnetwork.vocab.courses.correctAnswer')}: ${this.mode === 'multiple_choice' ? this.currentItem.gloss : this.currentItem.target}`; + this.needsFeedbackAck = true; + const key = this.getItemKey(this.currentItem); + const existing = this.weakVocabMap[key] || { + learning: this.currentItem.gloss || '', + reference: this.currentItem.target || '', + wrongCount: 0, + lastWrongAt: '' + }; + existing.wrongCount += 1; + existing.lastWrongAt = new Date().toISOString(); + this.weakVocabMap[key] = existing; } - - window.setTimeout(async () => { - this.currentIndex += 1; - if (this.currentIndex >= this.reviewQueue.length) { - await this.finishReview(); - return; - } - this.setupCurrent(); - }, 500); + }, + async advanceAfterFeedback() { + this.currentIndex += 1; + if (this.currentIndex >= this.reviewQueue.length) { + await this.finishReview(); + return; + } + this.setupCurrent(); }, async finishReview() { this.reviewDone = true; try { + let mergedWeak = Object.values(this.weakVocabMap); + try { + const { data: progressList } = await apiClient.get(`/api/vocab/courses/${this.courseId}/progress`); + const existingProgress = Array.isArray(progressList) + ? progressList.find((p) => Number(p.lessonId) === Number(this.lessonId)) + : null; + const existingWeak = Array.isArray(existingProgress?.lessonState?.reviewWeakVocab) + ? existingProgress.lessonState.reviewWeakVocab + : []; + const map = new Map(); + existingWeak.forEach((entry) => { + const key = `${String(entry?.learning || '').trim()}|${String(entry?.reference || '').trim()}`; + if (!key) return; + map.set(key, { + learning: String(entry?.learning || '').trim(), + reference: String(entry?.reference || '').trim(), + wrongCount: Math.max(0, Number(entry?.wrongCount) || 0), + lastWrongAt: String(entry?.lastWrongAt || '') + }); + }); + mergedWeak.forEach((entry) => { + const key = `${String(entry?.learning || '').trim()}|${String(entry?.reference || '').trim()}`; + if (!key) return; + const prev = map.get(key); + if (!prev) { + map.set(key, entry); + } else { + map.set(key, { + learning: prev.learning || entry.learning, + reference: prev.reference || entry.reference, + wrongCount: Math.max(0, Number(prev.wrongCount) || 0) + Math.max(0, Number(entry.wrongCount) || 0), + lastWrongAt: entry.lastWrongAt || prev.lastWrongAt + }); + } + }); + mergedWeak = Array.from(map.values()) + .filter((entry) => entry.learning && entry.reference) + .sort((a, b) => (b.wrongCount || 0) - (a.wrongCount || 0)) + .slice(0, 40); + } catch (mergeErr) { + console.warn('Konnte bestehende Review-Schwachstellen nicht laden:', mergeErr); + } await apiClient.put(`/api/vocab/lessons/${this.lessonId}/progress`, { completed: true, score: 100, - timeSpentMinutes: 1 + timeSpentMinutes: 1, + lessonState: { + reviewWeakVocab: mergedWeak + } }); } catch (e) { console.error('Review-Fortschritt konnte nicht gespeichert werden:', e); diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 6cd71f2..b57002c 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -902,11 +902,10 @@ -