feat(vocabLesson): add detailed review section for failed chapter exams
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s

- Introduced a new review section that displays questions, user answers, and correct answers for failed chapter exams.
- Added localization entries for the review section in multiple languages, enhancing user experience and understanding of mistakes.
- Implemented logic to format and present failed exam details, improving feedback for learners.
This commit is contained in:
Torsten Schulz (local)
2026-04-15 14:02:34 +02:00
parent cc791501c9
commit 7b4c9a0b1c
6 changed files with 102 additions and 0 deletions

View File

@@ -572,6 +572,10 @@
"exerciseExamFailedTitle": "Wala naabot ang minimum nga score", "exerciseExamFailedTitle": "Wala naabot ang minimum nga score",
"exerciseExamFailedBody": "Naayo ka ug {score}% (kinahanglan: {target}%). Padayon pag-praktis sa learning section. Human sa {count} pa nga trainer questions ma-unlock na usab ang chapter test — sugdan nimo pag-usab ang tanan nga pangutana gikan sa sinugdanan.", "exerciseExamFailedBody": "Naayo ka ug {score}% (kinahanglan: {target}%). Padayon pag-praktis sa learning section. Human sa {count} pa nga trainer questions ma-unlock na usab ang chapter test — sugdan nimo pag-usab ang tanan nga pangutana gikan sa sinugdanan.",
"exerciseExamFailedOk": "Okay", "exerciseExamFailedOk": "Okay",
"exerciseExamFailedReviewTitle": "Kini nga tubag sayop pa",
"exerciseExamFailedQuestionLabel": "Pangutana",
"exerciseExamFailedYourAnswerLabel": "Imong tubag",
"exerciseExamFailedNoAnswer": "Walay tubag",
"exerciseStatusOpen": "Ablihi", "exerciseStatusOpen": "Ablihi",
"exerciseStatusCorrect": "Done", "exerciseStatusCorrect": "Done",
"exerciseStatusRetry": "Try again", "exerciseStatusRetry": "Try again",

View File

@@ -544,6 +544,10 @@
"exerciseExamFailedTitle": "Mindestziel nicht erreicht", "exerciseExamFailedTitle": "Mindestziel nicht erreicht",
"exerciseExamFailedBody": "Du hast {score}% der Fragen richtig beantwortet (Mindestziel: {target}%). Bitte übe weiter im Lernbereich. Nach {count} weiteren Trainerfragen ist die Kapitel-Prüfung wieder freigeschaltet dann beginnst du mit allen Fragen von vorn.", "exerciseExamFailedBody": "Du hast {score}% der Fragen richtig beantwortet (Mindestziel: {target}%). Bitte übe weiter im Lernbereich. Nach {count} weiteren Trainerfragen ist die Kapitel-Prüfung wieder freigeschaltet dann beginnst du mit allen Fragen von vorn.",
"exerciseExamFailedOk": "Verstanden", "exerciseExamFailedOk": "Verstanden",
"exerciseExamFailedReviewTitle": "Das war noch falsch",
"exerciseExamFailedQuestionLabel": "Frage",
"exerciseExamFailedYourAnswerLabel": "Deine Antwort",
"exerciseExamFailedNoAnswer": "Keine Antwort",
"exerciseStatusOpen": "Offen", "exerciseStatusOpen": "Offen",
"exerciseStatusCorrect": "Erledigt", "exerciseStatusCorrect": "Erledigt",
"exerciseStatusRetry": "Nochmal prüfen", "exerciseStatusRetry": "Nochmal prüfen",

View File

@@ -544,6 +544,10 @@
"exerciseExamFailedTitle": "Minimum score not reached", "exerciseExamFailedTitle": "Minimum score not reached",
"exerciseExamFailedBody": "You scored {score}% correct (required: {target}%). Keep practicing in the learning section. After {count} more trainer questions the chapter test unlocks again—you will then retake all questions from the start.", "exerciseExamFailedBody": "You scored {score}% correct (required: {target}%). Keep practicing in the learning section. After {count} more trainer questions the chapter test unlocks again—you will then retake all questions from the start.",
"exerciseExamFailedOk": "OK", "exerciseExamFailedOk": "OK",
"exerciseExamFailedReviewTitle": "These answers were wrong",
"exerciseExamFailedQuestionLabel": "Question",
"exerciseExamFailedYourAnswerLabel": "Your answer",
"exerciseExamFailedNoAnswer": "No answer",
"exerciseStatusOpen": "Open", "exerciseStatusOpen": "Open",
"exerciseStatusCorrect": "Done", "exerciseStatusCorrect": "Done",
"exerciseStatusRetry": "Try again", "exerciseStatusRetry": "Try again",

View File

@@ -542,6 +542,10 @@
"exerciseExamFailedTitle": "No alcanzaste la puntuación mínima", "exerciseExamFailedTitle": "No alcanzaste la puntuación mínima",
"exerciseExamFailedBody": "Has acertado el {score}% (mínimo: {target}%). Sigue practicando en el área de aprendizaje. Tras {count} preguntas más del entrenador se desbloqueará de nuevo el test del capítulo y repetirás todas las preguntas desde el principio.", "exerciseExamFailedBody": "Has acertado el {score}% (mínimo: {target}%). Sigue practicando en el área de aprendizaje. Tras {count} preguntas más del entrenador se desbloqueará de nuevo el test del capítulo y repetirás todas las preguntas desde el principio.",
"exerciseExamFailedOk": "Entendido", "exerciseExamFailedOk": "Entendido",
"exerciseExamFailedReviewTitle": "Estas respuestas fueron incorrectas",
"exerciseExamFailedQuestionLabel": "Pregunta",
"exerciseExamFailedYourAnswerLabel": "Tu respuesta",
"exerciseExamFailedNoAnswer": "Sin respuesta",
"exerciseStatusOpen": "Pendiente", "exerciseStatusOpen": "Pendiente",
"exerciseStatusCorrect": "Hecha", "exerciseStatusCorrect": "Hecha",
"exerciseStatusRetry": "Revisar otra vez", "exerciseStatusRetry": "Revisar otra vez",

View File

@@ -542,6 +542,10 @@
"exerciseExamFailedTitle": "Score minimum non atteint", "exerciseExamFailedTitle": "Score minimum non atteint",
"exerciseExamFailedBody": "Vous avez {score}% de bonnes réponses (minimum : {target}%). Continuez à vous entraîner dans la partie apprentissage. Après {count} questions supplémentaires dans l'entraîneur, le test du chapitre se débloque à nouveau et vous reprendrez toutes les questions depuis le début.", "exerciseExamFailedBody": "Vous avez {score}% de bonnes réponses (minimum : {target}%). Continuez à vous entraîner dans la partie apprentissage. Après {count} questions supplémentaires dans l'entraîneur, le test du chapitre se débloque à nouveau et vous reprendrez toutes les questions depuis le début.",
"exerciseExamFailedOk": "Compris", "exerciseExamFailedOk": "Compris",
"exerciseExamFailedReviewTitle": "Réponses encore incorrectes",
"exerciseExamFailedQuestionLabel": "Question",
"exerciseExamFailedYourAnswerLabel": "Votre réponse",
"exerciseExamFailedNoAnswer": "Aucune réponse",
"exerciseStatusOpen": "Ouvrir", "exerciseStatusOpen": "Ouvrir",
"exerciseStatusCorrect": "Complété", "exerciseStatusCorrect": "Complété",
"exerciseStatusRetry": "Revérifier", "exerciseStatusRetry": "Revérifier",

View File

@@ -957,6 +957,25 @@
target: exerciseTargetScore, target: exerciseTargetScore,
count: exerciseRetryUnlockAttempts count: exerciseRetryUnlockAttempts
}) }}</p> }) }}</p>
<div v-if="chapterExamFailedDetails.length > 0" class="exam-failed-review">
<h4>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedReviewTitle') }}</h4>
<ul>
<li v-for="(item, index) in chapterExamFailedDetails" :key="`failed-${index}-${item.id}`">
<p class="exam-failed-review__question">
<strong>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedQuestionLabel') }} {{ index + 1 }}:</strong>
{{ item.question }}
</p>
<p class="exam-failed-review__answer">
<strong>{{ $t('socialnetwork.vocab.courses.exerciseExamFailedYourAnswerLabel') }}:</strong>
{{ item.userAnswer || $t('socialnetwork.vocab.courses.exerciseExamFailedNoAnswer') }}
</p>
<p class="exam-failed-review__answer">
<strong>{{ $t('socialnetwork.vocab.courses.correctAnswer') }}:</strong>
{{ item.correctAnswer || '—' }}
</p>
</li>
</ul>
</div>
</div> </div>
<div class="dialog-footer"> <div class="dialog-footer">
<button type="button" class="dialog-button dialog-button--primary" @click="closeChapterExamFailedDialog"> <button type="button" class="dialog-button dialog-button--primary" @click="closeChapterExamFailedDialog">
@@ -1053,6 +1072,7 @@ export default {
exerciseReinforcementMessage: '', exerciseReinforcementMessage: '',
showChapterExamFailedDialog: false, showChapterExamFailedDialog: false,
chapterExamFailedScore: 0, chapterExamFailedScore: 0,
chapterExamFailedDetails: [],
/** Index in scrambledChapterExamExercises bei Ein-Frage-Ansicht */ /** Index in scrambledChapterExamExercises bei Ein-Frage-Ansicht */
exerciseSequentialIndex: 0, exerciseSequentialIndex: 0,
/** Aus vorherigen Lektionen (MC-Optionen nach Fragentyp Ziel-/Muttersprache) */ /** Aus vorherigen Lektionen (MC-Optionen nach Fragentyp Ziel-/Muttersprache) */
@@ -2049,6 +2069,42 @@ export default {
closeChapterExamFailedDialog() { closeChapterExamFailedDialog() {
this.showChapterExamFailedDialog = false; this.showChapterExamFailedDialog = false;
}, },
formatExerciseAnswerForReview(exercise, rawAnswer) {
if (rawAnswer === null || rawAnswer === undefined) return '';
const type = this.getExerciseType(exercise);
if (type === 'multiple_choice') {
const options = this.getOptions(exercise);
const idx = Number(rawAnswer);
if (Number.isInteger(idx) && idx >= 0 && idx < options.length) {
return String(options[idx] ?? '');
}
}
if (Array.isArray(rawAnswer)) {
return rawAnswer
.map((v) => String(v || '').trim())
.filter(Boolean)
.join(' | ');
}
return String(rawAnswer || '').trim();
},
buildChapterExamFailedDetails() {
const all = this.effectiveExercises;
if (!Array.isArray(all) || all.length === 0) return [];
const out = [];
all.forEach((exercise) => {
const result = this.exerciseResults[exercise.id];
if (!result || result.correct) return;
const question = String(this.getQuestionText(exercise) || exercise.title || '').trim();
const userAnswerRaw = this.exerciseAnswers[exercise.id];
out.push({
id: exercise.id,
question,
userAnswer: this.formatExerciseAnswerForReview(exercise, userAnswerRaw),
correctAnswer: String(result.correctAnswer || '').trim()
});
});
return out;
},
clearChapterExamAttemptState() { clearChapterExamAttemptState() {
const all = this.effectiveExercises; const all = this.effectiveExercises;
if (!all.length) return; if (!all.length) return;
@@ -2064,6 +2120,7 @@ export default {
this.buildMcRandomizedOptions(); this.buildMcRandomizedOptions();
}, },
handleChapterExamFailed(score) { handleChapterExamFailed(score) {
this.chapterExamFailedDetails = this.buildChapterExamFailedDetails();
this.exerciseRetryPending = true; this.exerciseRetryPending = true;
this.exerciseRetryPendingSinceAttempts = this.vocabTrainerTotalAttempts; this.exerciseRetryPendingSinceAttempts = this.vocabTrainerTotalAttempts;
this.clearChapterExamAttemptState(); this.clearChapterExamAttemptState();
@@ -4234,6 +4291,31 @@ export default {
font-weight: 700; font-weight: 700;
} }
.exam-failed-review {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid rgba(133, 100, 4, 0.28);
}
.exam-failed-review h4 {
margin: 0 0 8px;
font-size: 0.98rem;
}
.exam-failed-review ul {
margin: 0;
padding-left: 18px;
}
.exam-failed-review li {
margin-bottom: 10px;
}
.exam-failed-review__question,
.exam-failed-review__answer {
margin: 0 0 4px;
}
.exercise-flow-header { .exercise-flow-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;