diff --git a/backend/scripts/delete-all-family-words-exercises.js b/backend/scripts/delete-all-family-words-exercises.js new file mode 100755 index 0000000..457525e --- /dev/null +++ b/backend/scripts/delete-all-family-words-exercises.js @@ -0,0 +1,88 @@ +#!/usr/bin/env node +/** + * Script zum Löschen ALLER "Familienwörter"-Übungen aus Bisaya-Kursen + * + * Verwendung: + * node backend/scripts/delete-all-family-words-exercises.js + * + * Löscht alle Grammatik-Übungen von "Familienwörter"-Lektionen, um Platz für neue zu schaffen. + */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; + +async function deleteAllFamilyWordsExercises() { + await sequelize.authenticate(); + console.log('Datenbankverbindung erfolgreich hergestellt.\n'); + + // Finde alle Bisaya-Kurse + const [bisayaLanguage] = await sequelize.query( + `SELECT id FROM community.vocab_language WHERE name = 'Bisaya' LIMIT 1`, + { + type: sequelize.QueryTypes.SELECT + } + ); + + if (!bisayaLanguage) { + console.error('❌ Bisaya-Sprache nicht gefunden.'); + return; + } + + const courses = await sequelize.query( + `SELECT id, title FROM community.vocab_course WHERE language_id = :languageId`, + { + replacements: { languageId: bisayaLanguage.id }, + type: sequelize.QueryTypes.SELECT + } + ); + + console.log(`Gefunden: ${courses.length} Bisaya-Kurse\n`); + + let totalDeleted = 0; + let totalLessons = 0; + + for (const course of courses) { + console.log(`📚 Kurs: ${course.title} (ID: ${course.id})`); + + // Finde "Familienwörter"-Lektionen + const lessons = await VocabCourseLesson.findAll({ + where: { + courseId: course.id, + title: 'Familienwörter' + }, + order: [['lessonNumber', 'ASC']] + }); + + console.log(` ${lessons.length} "Familienwörter"-Lektionen gefunden`); + + for (const lesson of lessons) { + // Lösche ALLE bestehenden Übungen + const deletedCount = await VocabGrammarExercise.destroy({ + where: { lessonId: lesson.id } + }); + + console.log(` 🗑️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${deletedCount} Übung(en) gelöscht`); + totalDeleted += deletedCount; + totalLessons++; + } + + console.log(''); + } + + console.log(`\n🎉 Zusammenfassung:`); + console.log(` ${totalLessons} Lektionen bearbeitet`); + console.log(` ${totalDeleted} Übungen gelöscht`); + console.log(`\n💡 Hinweis: Führe jetzt das update-family-words-exercises.js Script aus, um neue Übungen zu erstellen.`); +} + +deleteAllFamilyWordsExercises() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/frontend/src/views/social/VocabLessonView.vue b/frontend/src/views/social/VocabLessonView.vue index 2cbccc7..341b291 100644 --- a/frontend/src/views/social/VocabLessonView.vue +++ b/frontend/src/views/social/VocabLessonView.vue @@ -541,6 +541,14 @@ export default { if (!qData) return exercise.title; if (qData.question) return qData.question; + + // Für Transformation-Übungen: Formatiere als "Übersetze 'X' ins Bisaya" + if (qData.type === 'transformation' && qData.text) { + const sourceLang = qData.sourceLanguage || 'Deutsch'; + const targetLang = qData.targetLanguage || 'Bisaya'; + return `Übersetze "${qData.text}" ins ${targetLang}`; + } + if (qData.text) return qData.text; return exercise.title; }, @@ -769,18 +777,31 @@ export default { } } }, - buildChoiceOptions(correctAnswer, allVocabs) { + buildChoiceOptions(correctAnswer, allVocabs, excludePrompt = null) { const options = new Set([correctAnswer]); + // Normalisiere alle Werte für den Vergleich (trim + lowercase) + const normalizedExcludeSet = new Set(); + normalizedExcludeSet.add(this.normalizeVocab(correctAnswer)); + // Wichtig: Der Prompt (die Frage) darf nicht in den Optionen erscheinen + if (excludePrompt) { + normalizedExcludeSet.add(this.normalizeVocab(excludePrompt)); + } + // Füge 3 Distraktoren hinzu let attempts = 0; - const maxAttempts = 50; // Verhindere Endlosschleife + const maxAttempts = 100; // Erhöht, da wir mehr Filter haben while (options.size < 4 && allVocabs.length > 1 && attempts < maxAttempts) { attempts++; const randomVocab = allVocabs[Math.floor(Math.random() * allVocabs.length)]; const distractor = this.vocabTrainerDirection === 'L2R' ? randomVocab.reference : randomVocab.learning; - if (distractor !== correctAnswer) { - options.add(distractor); + + // Prüfe: Distraktor darf nicht gleich correctAnswer oder excludePrompt sein (mit Normalisierung) + if (distractor && distractor.trim()) { + const normalizedDistractor = this.normalizeVocab(distractor); + if (!normalizedExcludeSet.has(normalizedDistractor) && !options.has(distractor)) { + options.add(distractor); + } } } @@ -789,8 +810,9 @@ export default { const genericOptions = ['Option A', 'Option B', 'Option C', 'Option D']; let index = 0; while (options.size < 4 && index < genericOptions.length) { - if (!options.has(genericOptions[index])) { - options.add(genericOptions[index]); + const option = genericOptions[index]; + if (!excludeSet.has(option)) { + options.add(option); } index++; } @@ -836,8 +858,15 @@ export default { // Erstelle Choice-Optionen für Multiple Choice if (this.vocabTrainerMode === 'multiple_choice') { console.log('[VocabLessonView] Erstelle Choice-Optionen...'); - this.vocabTrainerChoiceOptions = this.buildChoiceOptions(this.currentVocabQuestion.answer, this.vocabTrainerPool); - console.log('[VocabLessonView] Choice-Optionen erstellt:', this.vocabTrainerChoiceOptions.length); + console.log('[VocabLessonView] Prompt:', this.currentVocabQuestion.prompt); + console.log('[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); } // Fokussiere Eingabefeld im Typing-Modus