Implement dialog prompts for lesson navigation and error handling in VocabLessonView

- Removed the manual check answer button, transitioning to automatic answer verification upon option selection.
- Added dialog overlays for navigating to the next lesson, course completion notifications, and error messages, enhancing user interaction.
- Introduced methods to handle dialog confirmations and cancellations, improving the flow of lesson transitions and error management.
- Updated styles for dialog components to ensure a consistent and user-friendly interface.
This commit is contained in:
Torsten Schulz (local)
2026-01-19 23:28:45 +01:00
parent cf1b5e7f71
commit fe2e6a53e9

View File

@@ -106,9 +106,7 @@
{{ option }} {{ option }}
</button> </button>
</div> </div>
<button @click="checkVocabAnswer" :disabled="!vocabTrainerSelectedChoice" class="btn-check"> <!-- Button entfernt: Prüfung erfolgt automatisch beim Klick auf Option -->
{{ $t('socialnetwork.vocab.courses.checkAnswer') }}
</button>
</div> </div>
<!-- Texteingabe Modus --> <!-- Texteingabe Modus -->
<div v-else class="vocab-answer-area typing"> <div v-else class="vocab-answer-area typing">
@@ -257,6 +255,55 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Dialog für Navigation zur nächsten Lektion -->
<div v-if="showNextLessonDialog" class="dialog-overlay" @click.self="cancelNavigateToNextLesson">
<div class="dialog" style="width: 400px; height: auto;">
<div class="dialog-header">
<span class="dialog-title">{{ $t('socialnetwork.vocab.courses.lessonCompleted') }}</span>
<span class="dialog-close" @click="cancelNavigateToNextLesson"></span>
</div>
<div class="dialog-body">
<p>{{ $t('socialnetwork.vocab.courses.goToNextLesson') }}</p>
</div>
<div class="dialog-footer">
<button @click="confirmNavigateToNextLesson" class="dialog-button">{{ $t('general.yes') }}</button>
<button @click="cancelNavigateToNextLesson" class="dialog-button">{{ $t('general.no') }}</button>
</div>
</div>
</div>
<!-- Dialog für Kurs-Abschluss -->
<div v-if="showCompletionDialog" class="dialog-overlay" @click.self="closeCompletionDialog">
<div class="dialog" style="width: 400px; height: auto;">
<div class="dialog-header">
<span class="dialog-title">{{ $t('socialnetwork.vocab.courses.allLessonsCompleted') }}</span>
<span class="dialog-close" @click="closeCompletionDialog"></span>
</div>
<div class="dialog-body">
<p>{{ $t('socialnetwork.vocab.courses.allLessonsCompleted') }}</p>
</div>
<div class="dialog-footer">
<button @click="closeCompletionDialog" class="dialog-button">{{ $t('general.ok') }}</button>
</div>
</div>
</div>
<!-- Dialog für Fehler -->
<div v-if="showErrorDialog" class="dialog-overlay" @click.self="closeErrorDialog">
<div class="dialog" style="width: 400px; height: auto;">
<div class="dialog-header">
<span class="dialog-title">{{ $t('error-title') }}</span>
<span class="dialog-close" @click="closeErrorDialog"></span>
</div>
<div class="dialog-body">
<p>{{ errorMessage }}</p>
</div>
<div class="dialog-footer">
<button @click="closeErrorDialog" class="dialog-button">{{ $t('general.ok') }}</button>
</div>
</div>
</div>
</template> </template>
<script> <script>
@@ -298,7 +345,12 @@ export default {
vocabTrainerLastCorrect: false, vocabTrainerLastCorrect: false,
vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning vocabTrainerDirection: 'L2R', // L2R: learning->reference, R2L: reference->learning
isCheckingLessonCompletion: false, // Flag um Endlosschleife zu verhindern isCheckingLessonCompletion: false, // Flag um Endlosschleife zu verhindern
isNavigatingToNext: false // Flag um mehrfache Navigation zu verhindern isNavigatingToNext: false, // Flag um mehrfache Navigation zu verhindern
showNextLessonDialog: false,
nextLessonId: null,
showCompletionDialog: false,
showErrorDialog: false,
errorMessage: ''
}; };
}, },
computed: { computed: {
@@ -463,6 +515,12 @@ export default {
this.isCheckingLessonCompletion = false; this.isCheckingLessonCompletion = false;
this.isNavigatingToNext = false; this.isNavigatingToNext = false;
// Prüfe ob 'tab' Query-Parameter vorhanden ist (für Navigation zur nächsten Lektion)
const tabParam = this.$route.query.tab;
if (tabParam === 'learn') {
this.activeTab = 'learn';
}
try { try {
const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`); const res = await apiClient.get(`/api/vocab/lessons/${this.lessonId}`);
this.lesson = res.data; this.lesson = res.data;
@@ -614,7 +672,8 @@ export default {
}); });
} catch (e) { } catch (e) {
console.error('Fehler beim Prüfen der Antwort:', e); console.error('Fehler beim Prüfen der Antwort:', e);
alert(e.response?.data?.error || 'Fehler beim Prüfen der Antwort'); this.showErrorDialog = true;
this.errorMessage = e.response?.data?.error || 'Fehler beim Prüfen der Antwort';
} }
}, },
async checkLessonCompletion() { async checkLessonCompletion() {
@@ -702,25 +761,13 @@ export default {
console.log('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id); console.log('[VocabLessonView] Nächste Lektion gefunden:', nextLesson.id);
// Zeige Erfolgs-Meldung und leite weiter // Zeige Erfolgs-Meldung und leite weiter
const shouldNavigate = confirm(this.$t('socialnetwork.vocab.courses.lessonCompleted') + '\n' + this.$t('socialnetwork.vocab.courses.goToNextLesson')); // Verwende Dialog statt confirm
this.showNextLessonDialog = true;
if (shouldNavigate) { this.nextLessonId = nextLesson.id;
console.log('[VocabLessonView] Navigiere zur nächsten Lektion:', nextLesson.id);
// Setze Flags zurück BEVOR die Navigation stattfindet
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
// Verwende replace statt push, um die History nicht zu belasten
this.$router.replace(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${nextLesson.id}`);
} else {
// Benutzer hat abgebrochen - Flag zurücksetzen
console.log('[VocabLessonView] Navigation abgebrochen');
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
}
} else { } else {
// Letzte Lektion - zeige Abschluss-Meldung // Letzte Lektion - zeige Abschluss-Meldung
console.log('[VocabLessonView] Letzte Lektion erreicht'); console.log('[VocabLessonView] Letzte Lektion erreicht');
alert(this.$t('socialnetwork.vocab.courses.allLessonsCompleted')); this.showCompletionDialog = true;
this.isNavigatingToNext = false; this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false; this.isCheckingLessonCompletion = false;
} }
@@ -733,6 +780,32 @@ export default {
back() { back() {
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}`); this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}`);
}, },
confirmNavigateToNextLesson() {
if (this.nextLessonId) {
console.log('[VocabLessonView] Navigiere zur nächsten Lektion:', this.nextLessonId);
// Setze Flags zurück BEVOR die Navigation stattfindet
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
this.showNextLessonDialog = false;
// Verwende replace statt push, um die History nicht zu belasten
// Setze activeTab auf 'learn' für die nächste Lektion via Query-Parameter
this.$router.replace(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${this.nextLessonId}?tab=learn`);
}
},
cancelNavigateToNextLesson() {
console.log('[VocabLessonView] Navigation abgebrochen');
this.isNavigatingToNext = false;
this.isCheckingLessonCompletion = false;
this.showNextLessonDialog = false;
this.nextLessonId = null;
},
closeCompletionDialog() {
this.showCompletionDialog = false;
},
closeErrorDialog() {
this.showErrorDialog = false;
this.errorMessage = '';
},
// Vokabeltrainer-Methoden // Vokabeltrainer-Methoden
startVocabTrainer() { startVocabTrainer() {
console.log('[VocabLessonView] startVocabTrainer aufgerufen'); console.log('[VocabLessonView] startVocabTrainer aufgerufen');
@@ -884,6 +957,10 @@ export default {
}, },
selectVocabChoice(option) { selectVocabChoice(option) {
this.vocabTrainerSelectedChoice = option; this.vocabTrainerSelectedChoice = option;
// Bei Multiple Choice: Sofort prüfen
if (this.vocabTrainerMode === 'multiple_choice') {
this.checkVocabAnswer();
}
}, },
normalizeVocab(s) { normalizeVocab(s) {
return String(s || '').trim().toLowerCase().replace(/\s+/g, ' '); return String(s || '').trim().toLowerCase().replace(/\s+/g, ' ');
@@ -919,16 +996,17 @@ export default {
this.vocabTrainerAnswered = true; this.vocabTrainerAnswered = true;
// Im Typing-Modus: Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort) // Automatisch zur nächsten Frage nach kurzer Pause (nur bei richtiger Antwort)
if (this.vocabTrainerMode === 'typing' && this.vocabTrainerLastCorrect) { if (this.vocabTrainerLastCorrect) {
// Prüfe ob noch Fragen vorhanden sind // Prüfe ob noch Fragen vorhanden sind
if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0) { if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0) {
const delay = this.vocabTrainerMode === 'multiple_choice' ? 2000 : 500; // 2 Sekunden für Multiple Choice, 500ms für Typing
setTimeout(() => { setTimeout(() => {
// Prüfe erneut, ob noch Fragen vorhanden sind (könnte sich geändert haben) // Prüfe erneut, ob noch Fragen vorhanden sind (könnte sich geändert haben)
if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0 && this.vocabTrainerActive) { if (this.vocabTrainerPool && this.vocabTrainerPool.length > 0 && this.vocabTrainerActive) {
this.nextVocabQuestion(); this.nextVocabQuestion();
} }
}, 500); }, delay);
} }
} }
@@ -1498,4 +1576,77 @@ export default {
.btn-continue:hover { .btn-continue:hover {
background: #0056b3; background: #0056b3;
} }
/* Dialog Styles */
.dialog-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
background: rgba(0, 0, 0, 0.5);
}
.dialog {
background: white;
display: flex;
flex-direction: column;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
max-width: 90%;
max-height: 90%;
}
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
border-bottom: 1px solid #ddd;
background-color: #F9A22C;
}
.dialog-title {
flex-grow: 1;
font-size: 1.2em;
font-weight: bold;
}
.dialog-close {
cursor: pointer;
font-size: 1.5em;
margin-left: 10px;
color: #000;
}
.dialog-body {
padding: 20px;
overflow-y: auto;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 10px 20px;
border-top: 1px solid #ddd;
}
.dialog-button {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
}
.dialog-button:hover {
background: #0056b3;
}
</style> </style>