Refactor feedback handling across components: Replace alert and confirm calls with centralized feedback functions for improved user experience. Update various components to utilize showError, showSuccess, and confirmAction for consistent messaging and confirmation dialogs. Enhance UI responsiveness and maintainability by streamlining feedback logic.

This commit is contained in:
Torsten Schulz (local)
2026-03-19 16:18:51 +01:00
parent 2c58ef37c4
commit 1774d7df88
35 changed files with 1097 additions and 1017 deletions

View File

@@ -352,8 +352,8 @@
<!-- Fallback für unbekannte Typen -->
<div v-else class="unknown-exercise">
<p>Übungstyp: {{ getExerciseType(exercise) }}</p>
<pre>{{ JSON.stringify(exercise, null, 2) }}</pre>
<p>Dieser Übungstyp wird in der aktuellen Ansicht noch nicht interaktiv dargestellt.</p>
<p class="unknown-exercise__type">Typ: {{ getExerciseType(exercise) }}</p>
</div>
</div>
</div>
@@ -418,6 +418,8 @@
import { mapGetters } from 'vuex';
import apiClient from '@/utils/axios.js';
const debugLog = () => {};
export default {
name: 'VocabLessonView',
props: {
@@ -533,7 +535,7 @@ export default {
// Normale Lektion: Verwende effectiveExercises (grammarExercises)
const exercises = this.effectiveExercises;
if (!exercises || !Array.isArray(exercises) || exercises.length === 0) {
console.log('[importantVocab] Keine Übungen vorhanden');
debugLog('[importantVocab] Keine Übungen vorhanden');
return [];
}
return this._extractVocabFromExercises(exercises);
@@ -576,7 +578,7 @@ export default {
const isArrayLike = exercises.length !== undefined && typeof exercises.length === 'number';
if (!Array.isArray(exercises) && isArrayLike) {
console.log('[_extractVocabFromExercises] exercises ist kein Array, aber array-like, konvertiere...');
debugLog('[_extractVocabFromExercises] exercises ist kein Array, aber array-like, konvertiere...');
// Versuche Array.from (funktioniert mit iterierbaren Objekten und Array-like Objekten)
try {
exercisesArray = Array.from(exercises);
@@ -601,13 +603,13 @@ export default {
exercisesArray.forEach((exercise, idx) => {
try {
console.log(`[importantVocab] Verarbeite Übung ${idx + 1}:`, exercise.title);
debugLog(`[importantVocab] Verarbeite Übung ${idx + 1}:`, exercise.title);
// Extrahiere aus questionData
const qData = this.getQuestionData(exercise);
const aData = this.getAnswerData(exercise);
console.log(`[importantVocab] qData:`, qData);
console.log(`[importantVocab] aData:`, aData);
debugLog(`[importantVocab] qData:`, qData);
debugLog(`[importantVocab] aData:`, aData);
if (qData && aData) {
// Für Multiple Choice: Extrahiere Optionen und richtige Antwort
@@ -616,12 +618,12 @@ export default {
const correctIndex = aData.correctAnswer !== undefined ? aData.correctAnswer : (aData.correct || 0);
const correctAnswer = options[correctIndex] || '';
console.log(`[importantVocab] Multiple Choice - options:`, options, `correctIndex:`, correctIndex, `correctAnswer:`, correctAnswer);
debugLog(`[importantVocab] Multiple Choice - options:`, options, `correctIndex:`, correctIndex, `correctAnswer:`, correctAnswer);
if (correctAnswer) {
// Versuche die Frage zu analysieren (z.B. "Wie sagt man 'X' auf Bisaya?" oder "Was bedeutet 'X'?")
const question = qData.question || qData.text || '';
console.log(`[importantVocab] Frage:`, question);
debugLog(`[importantVocab] Frage:`, question);
// Pattern 1: "Wie sagt man 'X' auf Bisaya?" -> X ist Muttersprache (z.B. "Großmutter"), correctAnswer ist Bisaya (z.B. "Lola")
let match = question.match(/Wie sagt man ['"]([^'"]+)['"]/i);
@@ -629,11 +631,11 @@ export default {
const nativeWord = match[1]; // Das Wort in der Muttersprache
// Nur hinzufügen, wenn Muttersprache und Bisaya unterschiedlich sind (verhindert "ko" -> "ko")
if (nativeWord && correctAnswer && nativeWord.trim() !== correctAnswer.trim()) {
console.log(`[importantVocab] Pattern 1 gefunden - Muttersprache:`, nativeWord, `Bisaya:`, correctAnswer);
debugLog(`[importantVocab] Pattern 1 gefunden - Muttersprache:`, nativeWord, `Bisaya:`, correctAnswer);
// learning = Muttersprache (was man lernt), reference = Bisaya (Zielsprache)
vocabMap.set(`${nativeWord}-${correctAnswer}`, { learning: nativeWord, reference: correctAnswer });
} else {
console.log(`[importantVocab] Pattern 1 übersprungen - Muttersprache und Bisaya sind gleich:`, nativeWord, correctAnswer);
debugLog(`[importantVocab] Pattern 1 übersprungen - Muttersprache und Bisaya sind gleich:`, nativeWord, correctAnswer);
}
} else {
// Pattern 2: "Was bedeutet 'X'?" -> X ist Bisaya, correctAnswer ist Muttersprache
@@ -642,14 +644,14 @@ export default {
const bisayaWord = match[1];
// Nur hinzufügen, wenn Bisaya und Muttersprache unterschiedlich sind (verhindert "ko" -> "ko")
if (bisayaWord && correctAnswer && bisayaWord.trim() !== correctAnswer.trim()) {
console.log(`[importantVocab] Pattern 2 gefunden - Bisaya:`, bisayaWord, `Muttersprache:`, correctAnswer);
debugLog(`[importantVocab] Pattern 2 gefunden - Bisaya:`, bisayaWord, `Muttersprache:`, correctAnswer);
// learning = Muttersprache (was man lernt), reference = Bisaya (Zielsprache)
vocabMap.set(`${correctAnswer}-${bisayaWord}`, { learning: correctAnswer, reference: bisayaWord });
} else {
console.log(`[importantVocab] Pattern 2 übersprungen - Bisaya und Muttersprache sind gleich:`, bisayaWord, correctAnswer);
debugLog(`[importantVocab] Pattern 2 übersprungen - Bisaya und Muttersprache sind gleich:`, bisayaWord, correctAnswer);
}
} else {
console.log(`[importantVocab] Kein Pattern gefunden, Überspringe diese Übung`);
debugLog(`[importantVocab] Kein Pattern gefunden, Überspringe diese Übung`);
// Überspringe, wenn wir die Richtung nicht erkennen können
}
}
@@ -659,7 +661,7 @@ export default {
// Für Gap Fill: Extrahiere richtige Antworten
if (this.getExerciseType(exercise) === 'gap_fill') {
const answers = aData.answers || (aData.correct ? (Array.isArray(aData.correct) ? aData.correct : [aData.correct]) : []);
console.log(`[importantVocab] Gap Fill - answers:`, answers);
debugLog(`[importantVocab] Gap Fill - answers:`, answers);
if (answers.length > 0) {
// Versuche aus dem Text Kontext zu extrahieren
// Gap Fill hat normalerweise Format: "{gap} (Muttersprache) | {gap} (Muttersprache) | ..."
@@ -668,7 +670,7 @@ export default {
const matches = text.matchAll(/\(([^)]+)\)/g);
const nativeWords = Array.from(matches, m => m[1]);
console.log(`[importantVocab] Gap Fill - text:`, text, `nativeWords:`, nativeWords);
debugLog(`[importantVocab] Gap Fill - text:`, text, `nativeWords:`, nativeWords);
// Nur extrahieren, wenn Muttersprache-Hinweise (Klammern) vorhanden sind
if (nativeWords.length > 0) {
@@ -679,14 +681,14 @@ export default {
// Die answer ist normalerweise Bisaya, nativeWord ist Muttersprache
// Nur hinzufügen, wenn sie unterschiedlich sind (verhindert "ko" -> "ko")
vocabMap.set(`${nativeWord}-${answer}`, { learning: nativeWord, reference: answer });
console.log(`[importantVocab] Gap Fill extrahiert - Muttersprache:`, nativeWord, `Bisaya:`, answer);
debugLog(`[importantVocab] Gap Fill extrahiert - Muttersprache:`, nativeWord, `Bisaya:`, answer);
} else {
console.log(`[importantVocab] Gap Fill übersprungen - keine Muttersprache oder gleich:`, nativeWord, answer);
debugLog(`[importantVocab] Gap Fill übersprungen - keine Muttersprache oder gleich:`, nativeWord, answer);
}
}
});
} else {
console.log(`[importantVocab] Gap Fill übersprungen - keine Muttersprache-Hinweise (Klammern) gefunden`);
debugLog(`[importantVocab] Gap Fill übersprungen - keine Muttersprache-Hinweise (Klammern) gefunden`);
}
}
}
@@ -697,17 +699,17 @@ export default {
});
const result = Array.from(vocabMap.values());
console.log(`[_extractVocabFromExercises] Ergebnis:`, result.length, 'Vokabeln');
debugLog(`[_extractVocabFromExercises] Ergebnis:`, result.length, 'Vokabeln');
return result;
},
async loadLesson() {
// Verhindere mehrfaches Laden
if (this.loading) {
console.log('[VocabLessonView] loadLesson übersprungen - bereits am Laden');
debugLog('[VocabLessonView] loadLesson übersprungen - bereits am Laden');
return;
}
console.log('[VocabLessonView] loadLesson gestartet für lessonId:', this.lessonId);
debugLog('[VocabLessonView] loadLesson gestartet für lessonId:', this.lessonId);
this.loading = true;
// Setze Antworten und Ergebnisse zurück
this.exerciseAnswers = {};
@@ -725,19 +727,19 @@ export default {
try {
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
this.lesson = res.data;
console.log('[VocabLessonView] Geladene Lektion:', this.lesson?.id, this.lesson?.title);
debugLog('[VocabLessonView] Geladene Lektion:', this.lesson?.id, this.lesson?.title);
// Initialisiere mit effectiveExercises (für Review: reviewVocabExercises, sonst: grammarExercises)
this.$nextTick(async () => {
const exercises = this.effectiveExercises;
if (exercises && exercises.length > 0) {
console.log('[VocabLessonView] Übungen für Kapitel-Prüfung:', exercises.length);
debugLog('[VocabLessonView] Übungen für Kapitel-Prüfung:', exercises.length);
this.initializeExercises(exercises);
} else {
console.log('[VocabLessonView] Lade Übungen separat...');
debugLog('[VocabLessonView] Lade Übungen separat...');
await this.loadGrammarExercises();
}
});
console.log('[VocabLessonView] loadLesson abgeschlossen');
debugLog('[VocabLessonView] loadLesson abgeschlossen');
} catch (e) {
console.error('[VocabLessonView] Fehler beim Laden der Lektion:', e);
} finally {
@@ -883,18 +885,18 @@ export default {
async checkLessonCompletion() {
// Verhindere mehrfache Ausführung
if (this.isCheckingLessonCompletion || this.isNavigatingToNext) {
console.log('[VocabLessonView] checkLessonCompletion übersprungen - bereits in Ausführung');
debugLog('[VocabLessonView] checkLessonCompletion übersprungen - bereits in Ausführung');
return;
}
// Prüfe ob alle Übungen korrekt beantwortet wurden (effectiveExercises = Kapitel-Prüfung)
const allExercises = this.effectiveExercises;
if (!this.lesson || !allExercises || allExercises.length === 0) {
console.log('[VocabLessonView] checkLessonCompletion übersprungen - keine Lektion/Übungen');
debugLog('[VocabLessonView] checkLessonCompletion übersprungen - keine Lektion/Übungen');
return;
}
if (allExercises.length === 0) {
console.log('[VocabLessonView] checkLessonCompletion übersprungen - keine Übungen');
debugLog('[VocabLessonView] checkLessonCompletion übersprungen - keine Übungen');
return;
}
@@ -904,11 +906,11 @@ export default {
return result && result.correct;
});
console.log('[VocabLessonView] checkLessonCompletion - allCompleted:', allCompleted, 'Übungen:', allExercises.length, 'Korrekt:', allExercises.filter(ex => this.exerciseResults[ex.id]?.correct).length);
debugLog('[VocabLessonView] checkLessonCompletion - allCompleted:', allCompleted, 'Übungen:', allExercises.length, 'Korrekt:', allExercises.filter(ex => this.exerciseResults[ex.id]?.correct).length);
if (allCompleted && !this.isCheckingLessonCompletion) {
this.isCheckingLessonCompletion = true;
console.log('[VocabLessonView] Alle Übungen abgeschlossen - starte Fortschritts-Update');
debugLog('[VocabLessonView] Alle Übungen abgeschlossen - starte Fortschritts-Update');
try {
// Berechne Gesamt-Score
@@ -916,7 +918,7 @@ export default {
const correctExercises = allExercises.filter(ex => this.exerciseResults[ex.id]?.correct).length;
const score = Math.round((correctExercises / totalExercises) * 100);
console.log('[VocabLessonView] Score berechnet:', score, '%');
debugLog('[VocabLessonView] Score berechnet:', score, '%');
// Aktualisiere Fortschritt
await apiClient.put(`/api/vocab/lessons/${this.lessonId}/progress`, {
@@ -925,7 +927,7 @@ export default {
timeSpentMinutes: 0 // TODO: Zeit tracken
});
console.log('[VocabLessonView] Fortschritt aktualisiert - starte Navigation');
debugLog('[VocabLessonView] Fortschritt aktualisiert - starte Navigation');
// Weiterleitung zur nächsten Lektion
await this.navigateToNextLesson();
@@ -938,7 +940,7 @@ export default {
async navigateToNextLesson() {
// Verhindere mehrfache Navigation
if (this.isNavigatingToNext) {
console.log('[VocabLessonView] Navigation bereits in Gang, überspringe...');
debugLog('[VocabLessonView] Navigation bereits in Gang, überspringe...');
return;
}
this.isNavigatingToNext = true;
@@ -949,7 +951,7 @@ export default {
const course = courseRes.data;
if (!course.lessons || course.lessons.length === 0) {
console.log('[VocabLessonView] Keine Lektionen gefunden');
debugLog('[VocabLessonView] Keine Lektionen gefunden');
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
return;
@@ -961,7 +963,7 @@ export default {
if (currentLessonIndex >= 0 && currentLessonIndex < course.lessons.length - 1) {
// Nächste Lektion gefunden
const nextLesson = course.lessons[currentLessonIndex + 1];
console.log('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id);
debugLog('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id);
// Zeige Erfolgs-Meldung und leite weiter
// Verwende Dialog statt confirm
@@ -969,7 +971,7 @@ export default {
this.nextLessonId = nextLesson.id;
} else {
// Letzte Lektion - zeige Abschluss-Meldung
console.log('[VocabLessonView] Letzte Lektion erreicht');
debugLog('[VocabLessonView] Letzte Lektion erreicht');
this.showCompletionDialog = true;
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
@@ -985,7 +987,7 @@ export default {
},
confirmNavigateToNextLesson() {
if (this.nextLessonId) {
console.log('[VocabLessonView] Navigiere zur nächsten Lektion:', this.nextLessonId);
debugLog('[VocabLessonView] Navigiere zur nächsten Lektion:', this.nextLessonId);
// Setze Flags zurück BEVOR die Navigation stattfindet
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
@@ -996,7 +998,7 @@ export default {
}
},
cancelNavigateToNextLesson() {
console.log('[VocabLessonView] Navigation abgebrochen');
debugLog('[VocabLessonView] Navigation abgebrochen');
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
this.showNextLessonDialog = false;
@@ -1011,13 +1013,13 @@ export default {
},
// Vokabeltrainer-Methoden
startVocabTrainer() {
console.log('[VocabLessonView] startVocabTrainer aufgerufen');
debugLog('[VocabLessonView] startVocabTrainer aufgerufen');
if (!this.importantVocab || this.importantVocab.length === 0) {
console.log('[VocabLessonView] Keine Vokabeln vorhanden');
debugLog('[VocabLessonView] Keine Vokabeln vorhanden');
return;
}
console.log('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length);
console.log('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0);
debugLog('[VocabLessonView] Vokabeln gefunden:', this.importantVocab.length);
debugLog('[VocabLessonView] Alte Vokabeln:', this.previousVocab?.length || 0);
this.vocabTrainerActive = true;
this.vocabTrainerPool = [...this.importantVocab];
this.vocabTrainerMode = 'multiple_choice';
@@ -1030,8 +1032,8 @@ export default {
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');
debugLog('[VocabLessonView] Mixed-Pool:', this.vocabTrainerMixedPool.length, 'Vokabeln');
debugLog('[VocabLessonView] Rufe nextVocabQuestion auf');
this.$nextTick(() => {
this.nextVocabQuestion();
});
@@ -1078,7 +1080,7 @@ export default {
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');
debugLog('[VocabLessonView] Wechsel zu Mixed-Phase mit', this.vocabTrainerMixedPool.length, 'alten Vokabeln');
this.vocabTrainerPhase = 'mixed';
this.vocabTrainerPool = [...this.vocabTrainerMixedPool];
this.vocabTrainerMixedAttempts = 0;
@@ -1100,7 +1102,7 @@ export default {
} 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');
debugLog('[VocabLessonView] Mixed-Phase abgeschlossen, wechsle zu Typing');
this.vocabTrainerMode = 'typing';
this.vocabTrainerAutoSwitchedToTyping = true;
// Im Typing: Pool aus aktuellen + alten Vokabeln kombinieren
@@ -1206,9 +1208,9 @@ export default {
return arr;
},
nextVocabQuestion() {
console.log('[VocabLessonView] nextVocabQuestion aufgerufen');
debugLog('[VocabLessonView] nextVocabQuestion aufgerufen');
if (!this.vocabTrainerPool || this.vocabTrainerPool.length === 0) {
console.log('[VocabLessonView] Keine Vokabeln im Pool');
debugLog('[VocabLessonView] Keine Vokabeln im Pool');
this.currentVocabQuestion = null;
return;
}
@@ -1227,7 +1229,7 @@ export default {
key: this.getVocabKey(vocab)
};
console.log('[VocabLessonView] Neue Frage erstellt:', this.currentVocabQuestion.prompt);
debugLog('[VocabLessonView] Neue Frage erstellt:', this.currentVocabQuestion.prompt);
// Reset UI
this.vocabTrainerAnswer = '';
@@ -1236,16 +1238,16 @@ export default {
// Erstelle Choice-Optionen für Multiple Choice
if (this.vocabTrainerMode === 'multiple_choice') {
console.log('[VocabLessonView] Erstelle Choice-Optionen...');
console.log('[VocabLessonView] Prompt:', this.currentVocabQuestion.prompt);
console.log('[VocabLessonView] Answer:', this.currentVocabQuestion.answer);
debugLog('[VocabLessonView] Erstelle Choice-Optionen...');
debugLog('[VocabLessonView] Prompt:', this.currentVocabQuestion.prompt);
debugLog('[VocabLessonView] Answer:', this.currentVocabQuestion.answer);
// Wichtig: Der Prompt (die Frage) darf nicht in den Optionen erscheinen
this.vocabTrainerChoiceOptions = this.buildChoiceOptions(
this.currentVocabQuestion.answer,
this.vocabTrainerPool,
this.currentVocabQuestion.prompt // Exkludiere den Prompt
);
console.log('[VocabLessonView] Choice-Optionen erstellt:', this.vocabTrainerChoiceOptions);
debugLog('[VocabLessonView] Choice-Optionen erstellt:', this.vocabTrainerChoiceOptions);
}
// Fokussiere Eingabefeld im Typing-Modus
@@ -1597,10 +1599,10 @@ export default {
margin-top: 10px;
}
.unknown-exercise pre {
margin-top: 10px;
font-size: 0.85em;
overflow-x: auto;
.unknown-exercise__type {
margin-top: 8px;
font-size: 0.9em;
color: var(--color-text-secondary);
}
/* Tabs */