From e5d4a5f95f0d93699b3c4ef14b0703e79369631d Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Tue, 20 Jan 2026 14:16:22 +0100 Subject: [PATCH] Refactor VocabCourseView to enhance lesson display and user interaction - Replaced the lesson item layout with a structured table format for improved readability and organization of lesson information. - Updated lesson status indicators to include completion badges and scores, providing clearer feedback on progress. - Enhanced action buttons with distinct styles for better user experience and accessibility. - Improved CSS styles for a more modern and responsive design, ensuring a consistent look and feel across the application. --- .../update-feelings-affection-exercises.js | 522 ++++++++++++++++++ frontend/src/views/social/VocabCourseView.vue | 211 +++++-- 2 files changed, 687 insertions(+), 46 deletions(-) create mode 100755 backend/scripts/update-feelings-affection-exercises.js diff --git a/backend/scripts/update-feelings-affection-exercises.js b/backend/scripts/update-feelings-affection-exercises.js new file mode 100755 index 0000000..6df1365 --- /dev/null +++ b/backend/scripts/update-feelings-affection-exercises.js @@ -0,0 +1,522 @@ +#!/usr/bin/env node +/** + * Script zum Erstellen von Übungen für die "Gefühle & Zuneigung" Lektion + * + * Verwendung: + * node backend/scripts/update-feelings-affection-exercises.js + * + * Erstellt Gesprächsübungen für die "Gefühle & Zuneigung" Lektion in allen Bisaya-Kursen. + */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; +import VocabCourse from '../models/community/vocab_course.js'; +import User from '../models/community/user.js'; +import crypto from 'crypto'; +import bcrypt from 'bcryptjs'; +import { Op } from 'sequelize'; + +// Gefühle & Zuneigung auf Bisaya mit verschiedenen Muttersprachen +const FEELINGS_AFFECTION = { + // Deutsch -> Bisaya + 'Deutsch': { + conversations: [ + { + bisaya: 'Gihigugma ko ikaw.', + native: 'Ich liebe dich.', + explanation: '"Gihigugma" bedeutet "lieben", "ko" ist "ich", "ikaw" ist "dich"' + }, + { + bisaya: 'Nahigugma ko nimo.', + native: 'Ich liebe dich. (alternativ)', + explanation: '"Nahigugma" ist eine andere Form von "lieben", "nimo" ist "dich" (informell)' + }, + { + bisaya: 'Ganahan ko nimo.', + native: 'Ich mag dich.', + explanation: '"Ganahan" bedeutet "mögen/gefallen", "ko" ist "ich", "nimo" ist "dich"' + }, + { + bisaya: 'Nalipay ko nga nakita ka.', + native: 'Ich bin glücklich, dich zu sehen.', + explanation: '"Nalipay" bedeutet "glücklich", "ko" ist "ich", "nga nakita ka" ist "dich zu sehen"' + }, + { + bisaya: 'Gimingaw ko nimo.', + native: 'Ich vermisse dich.', + explanation: '"Gimingaw" bedeutet "vermissen", "ko" ist "ich", "nimo" ist "dich"' + }, + { + bisaya: 'Nalipay ko.', + native: 'Ich bin glücklich.', + explanation: '"Nalipay" bedeutet "glücklich", "ko" ist "ich"' + }, + { + bisaya: 'Nasubo ko.', + native: 'Ich bin traurig.', + explanation: '"Nasubo" bedeutet "traurig", "ko" ist "ich"' + }, + { + bisaya: 'Nalipay ko nga naa ka dinhi.', + native: 'Ich bin glücklich, dass du hier bist.', + explanation: '"Nalipay" ist "glücklich", "nga naa ka dinhi" bedeutet "dass du hier bist"' + } + ] + }, + // Englisch -> Bisaya + 'Englisch': { + conversations: [ + { + bisaya: 'Gihigugma ko ikaw.', + native: 'I love you.', + explanation: '"Gihigugma" means "love", "ko" is "I", "ikaw" is "you"' + }, + { + bisaya: 'Nahigugma ko nimo.', + native: 'I love you. (alternative)', + explanation: '"Nahigugma" is another form of "love", "nimo" is "you" (informal)' + }, + { + bisaya: 'Ganahan ko nimo.', + native: 'I like you.', + explanation: '"Ganahan" means "like", "ko" is "I", "nimo" is "you"' + }, + { + bisaya: 'Nalipay ko nga nakita ka.', + native: 'I am happy to see you.', + explanation: '"Nalipay" means "happy", "ko" is "I", "nga nakita ka" is "to see you"' + }, + { + bisaya: 'Gimingaw ko nimo.', + native: 'I miss you.', + explanation: '"Gimingaw" means "miss", "ko" is "I", "nimo" is "you"' + }, + { + bisaya: 'Nalipay ko.', + native: 'I am happy.', + explanation: '"Nalipay" means "happy", "ko" is "I"' + }, + { + bisaya: 'Nasubo ko.', + native: 'I am sad.', + explanation: '"Nasubo" means "sad", "ko" is "I"' + }, + { + bisaya: 'Nalipay ko nga naa ka dinhi.', + native: 'I am happy that you are here.', + explanation: '"Nalipay" is "happy", "nga naa ka dinhi" means "that you are here"' + } + ] + }, + // Spanisch -> Bisaya + 'Spanisch': { + conversations: [ + { + bisaya: 'Gihigugma ko ikaw.', + native: 'Te amo.', + explanation: '"Gihigugma" significa "amar", "ko" es "yo", "ikaw" es "tú"' + }, + { + bisaya: 'Nahigugma ko nimo.', + native: 'Te amo. (alternativa)', + explanation: '"Nahigugma" es otra forma de "amar", "nimo" es "tú" (informal)' + }, + { + bisaya: 'Ganahan ko nimo.', + native: 'Me gustas.', + explanation: '"Ganahan" significa "gustar", "ko" es "yo", "nimo" es "tú"' + }, + { + bisaya: 'Nalipay ko nga nakita ka.', + native: 'Estoy feliz de verte.', + explanation: '"Nalipay" significa "feliz", "ko" es "yo", "nga nakita ka" es "verte"' + }, + { + bisaya: 'Gimingaw ko nimo.', + native: 'Te extraño.', + explanation: '"Gimingaw" significa "extrañar", "ko" es "yo", "nimo" es "tú"' + }, + { + bisaya: 'Nalipay ko.', + native: 'Estoy feliz.', + explanation: '"Nalipay" significa "feliz", "ko" es "yo"' + }, + { + bisaya: 'Nasubo ko.', + native: 'Estoy triste.', + explanation: '"Nasubo" significa "triste", "ko" es "yo"' + }, + { + bisaya: 'Nalipay ko nga naa ka dinhi.', + native: 'Estoy feliz de que estés aquí.', + explanation: '"Nalipay" es "feliz", "nga naa ka dinhi" significa "que estés aquí"' + } + ] + }, + // Französisch -> Bisaya + 'Französisch': { + conversations: [ + { + bisaya: 'Gihigugma ko ikaw.', + native: 'Je t\'aime.', + explanation: '"Gihigugma" signifie "aimer", "ko" est "je", "ikaw" est "tu"' + }, + { + bisaya: 'Nahigugma ko nimo.', + native: 'Je t\'aime. (alternative)', + explanation: '"Nahigugma" est une autre forme de "aimer", "nimo" est "tu" (informel)' + }, + { + bisaya: 'Ganahan ko nimo.', + native: 'Je t\'aime bien.', + explanation: '"Ganahan" signifie "aimer bien", "ko" est "je", "nimo" est "tu"' + }, + { + bisaya: 'Nalipay ko nga nakita ka.', + native: 'Je suis heureux de te voir.', + explanation: '"Nalipay" signifie "heureux", "ko" est "je", "nga nakita ka" est "te voir"' + }, + { + bisaya: 'Gimingaw ko nimo.', + native: 'Tu me manques.', + explanation: '"Gimingaw" signifie "manquer", "ko" est "je", "nimo" est "tu"' + }, + { + bisaya: 'Nalipay ko.', + native: 'Je suis heureux.', + explanation: '"Nalipay" signifie "heureux", "ko" est "je"' + }, + { + bisaya: 'Nasubo ko.', + native: 'Je suis triste.', + explanation: '"Nasubo" signifie "triste", "ko" est "je"' + }, + { + bisaya: 'Nalipay ko nga naa ka dinhi.', + native: 'Je suis heureux que tu sois ici.', + explanation: '"Nalipay" est "heureux", "nga naa ka dinhi" signifie "que tu sois ici"' + } + ] + }, + // Italienisch -> Bisaya + 'Italienisch': { + conversations: [ + { + bisaya: 'Gihigugma ko ikaw.', + native: 'Ti amo.', + explanation: '"Gihigugma" significa "amare", "ko" è "io", "ikaw" è "tu"' + }, + { + bisaya: 'Nahigugma ko nimo.', + native: 'Ti amo. (alternativa)', + explanation: '"Nahigugma" è un\'altra forma di "amare", "nimo" è "tu" (informale)' + }, + { + bisaya: 'Ganahan ko nimo.', + native: 'Mi piaci.', + explanation: '"Ganahan" significa "piacere", "ko" è "io", "nimo" è "tu"' + }, + { + bisaya: 'Nalipay ko nga nakita ka.', + native: 'Sono felice di vederti.', + explanation: '"Nalipay" significa "felice", "ko" è "io", "nga nakita ka" è "vederti"' + }, + { + bisaya: 'Gimingaw ko nimo.', + native: 'Mi manchi.', + explanation: '"Gimingaw" significa "mancare", "ko" è "io", "nimo" è "tu"' + }, + { + bisaya: 'Nalipay ko.', + native: 'Sono felice.', + explanation: '"Nalipay" significa "felice", "ko" è "io"' + }, + { + bisaya: 'Nasubo ko.', + native: 'Sono triste.', + explanation: '"Nasubo" significa "triste", "ko" è "io"' + }, + { + bisaya: 'Nalipay ko nga naa ka dinhi.', + native: 'Sono felice che tu sia qui.', + explanation: '"Nalipay" è "felice", "nga naa ka dinhi" significa "che tu sia qui"' + } + ] + }, + // Portugiesisch -> Bisaya + 'Portugiesisch': { + conversations: [ + { + bisaya: 'Gihigugma ko ikaw.', + native: 'Eu te amo.', + explanation: '"Gihigugma" significa "amar", "ko" é "eu", "ikaw" é "você"' + }, + { + bisaya: 'Nahigugma ko nimo.', + native: 'Eu te amo. (alternativa)', + explanation: '"Nahigugma" é outra forma de "amar", "nimo" é "você" (informal)' + }, + { + bisaya: 'Ganahan ko nimo.', + native: 'Eu gosto de você.', + explanation: '"Ganahan" significa "gostar", "ko" é "eu", "nimo" é "você"' + }, + { + bisaya: 'Nalipay ko nga nakita ka.', + native: 'Estou feliz em te ver.', + explanation: '"Nalipay" significa "feliz", "ko" é "eu", "nga nakita ka" é "te ver"' + }, + { + bisaya: 'Gimingaw ko nimo.', + native: 'Eu sinto sua falta.', + explanation: '"Gimingaw" significa "sentir falta", "ko" é "eu", "nimo" é "você"' + }, + { + bisaya: 'Nalipay ko.', + native: 'Estou feliz.', + explanation: '"Nalipay" significa "feliz", "ko" é "eu"' + }, + { + bisaya: 'Nasubo ko.', + native: 'Estou triste.', + explanation: '"Nasubo" significa "triste", "ko" é "eu"' + }, + { + bisaya: 'Nalipay ko nga naa ka dinhi.', + native: 'Estou feliz que você esteja aqui.', + explanation: '"Nalipay" é "feliz", "nga naa ka dinhi" significa "que você esteja aqui"' + } + ] + } +}; + +async function findOrCreateSystemUser() { + // Versuche zuerst einen System-Benutzer zu finden (z.B. mit username "system" oder "admin") + let systemUser = await User.findOne({ + where: { + username: { [sequelize.Sequelize.Op.in]: ['system', 'admin', 'System', 'Admin'] } + } + }); + + if (!systemUser) { + // Erstelle einen System-Benutzer + const password = crypto.randomBytes(32).toString('hex'); + const hashedPassword = await bcrypt.hash(password, 10); + const hashedId = crypto.createHash('sha256').update(`system-${Date.now()}`).digest('hex'); + + systemUser = await User.create({ + username: 'system', + password: hashedPassword, + hashedId: hashedId, + email: 'system@your-part.de' + }); + console.log('✅ System-Benutzer erstellt:', systemUser.hashedId); + } else { + console.log('✅ System-Benutzer gefunden:', systemUser.hashedId); + } + + return systemUser; +} + +function createFeelingsAffectionExercises(nativeLanguageName) { + const exercises = []; + const conversations = FEELINGS_AFFECTION[nativeLanguageName]?.conversations || []; + + if (conversations.length === 0) { + console.warn(`⚠️ Keine Gespräche für Muttersprache "${nativeLanguageName}" gefunden. Verwende Deutsch als Fallback.`); + return createFeelingsAffectionExercises('Deutsch'); + } + + let exerciseNum = 1; + + // Multiple Choice: Übersetze Bisaya-Satz in Muttersprache + conversations.forEach((conv, idx) => { + if (idx < 4) { // Erste 4 als Multiple Choice + exercises.push({ + exerciseTypeId: 2, // multiple_choice + exerciseNumber: exerciseNum++, + title: `Gefühle & Zuneigung ${idx + 1} - Übersetzung`, + instruction: 'Übersetze den Bisaya-Satz ins ' + nativeLanguageName, + questionData: JSON.stringify({ + type: 'multiple_choice', + question: `Wie sagt man "${conv.bisaya}" auf ${nativeLanguageName}?`, + options: [ + conv.native, + conversations[(idx + 1) % conversations.length].native, + conversations[(idx + 2) % conversations.length].native, + conversations[(idx + 3) % conversations.length].native + ] + }), + answerData: JSON.stringify({ + type: 'multiple_choice', + correctAnswer: 0 + }), + explanation: conv.explanation + }); + } + }); + + // Gap Fill: Vervollständige Gefühlsausdrücke + exercises.push({ + exerciseTypeId: 1, // gap_fill + exerciseNumber: exerciseNum++, + title: 'Gefühle & Zuneigung vervollständigen', + instruction: 'Vervollständige den Satz mit den richtigen Bisaya-Wörtern.', + questionData: JSON.stringify({ + type: 'gap_fill', + text: 'Person A: {gap} ko ikaw. (Ich liebe dich)\nPerson B: {gap} ko pud. (Ich liebe dich auch)', + gaps: 2 + }), + answerData: JSON.stringify({ + type: 'gap_fill', + answers: ['Gihigugma', 'Gihigugma'] + }), + explanation: '"Gihigugma" bedeutet "lieben" und wird wiederholt, um "auch" auszudrücken' + }); + + // Transformation: Übersetze Muttersprache-Satz nach Bisaya + exercises.push({ + exerciseTypeId: 3, // transformation + exerciseNumber: exerciseNum++, + title: 'Gefühle & Zuneigung - Übersetzung nach Bisaya', + instruction: 'Übersetze den Satz ins Bisaya.', + questionData: JSON.stringify({ + type: 'transformation', + text: conversations[0].native + }), + answerData: JSON.stringify({ + type: 'transformation', + correctAnswer: conversations[0].bisaya + }), + explanation: `"${conversations[0].bisaya}" bedeutet "${conversations[0].native}" auf Bisaya. ${conversations[0].explanation}` + }); + + // Weitere Multiple Choice: Rückwärts-Übersetzung + exercises.push({ + exerciseTypeId: 2, // multiple_choice + exerciseNumber: exerciseNum++, + title: 'Gefühle & Zuneigung - Was bedeutet dieser Satz?', + instruction: 'Was bedeutet dieser Bisaya-Satz?', + questionData: JSON.stringify({ + type: 'multiple_choice', + question: `Was bedeutet "${conversations[2].bisaya}"?`, + options: [ + conversations[2].native, + conversations[3].native, + conversations[4].native, + conversations[5].native + ] + }), + answerData: JSON.stringify({ + type: 'multiple_choice', + correctAnswer: 0 + }), + explanation: conversations[2].explanation + }); + + return exercises; +} + +async function updateFeelingsAffectionExercises() { + await sequelize.authenticate(); + console.log('✅ Datenbankverbindung erfolgreich hergestellt.\n'); + + const systemUser = await findOrCreateSystemUser(); + + // Finde Bisaya-Sprache mit SQL + const [bisayaLangResult] = await sequelize.query( + `SELECT id FROM community.vocab_language WHERE name = 'Bisaya' LIMIT 1`, + { type: sequelize.QueryTypes.SELECT } + ); + + if (!bisayaLangResult) { + console.error('❌ Bisaya-Sprache nicht gefunden.'); + return; + } + + const bisayaLanguageId = bisayaLangResult.id; + + // Hole alle Bisaya-Kurse mit native language info + const courses = await sequelize.query( + `SELECT + c.id, + c.title, + c.native_language_id, + nl.name as native_language_name + FROM community.vocab_course c + LEFT JOIN community.vocab_language nl ON c.native_language_id = nl.id + WHERE c.language_id = :bisayaLanguageId`, + { + replacements: { bisayaLanguageId }, + type: sequelize.QueryTypes.SELECT + } + ); + + console.log(`📚 Gefunden: ${courses.length} Bisaya-Kurse\n`); + + let totalExercisesCreated = 0; + let totalLessonsProcessed = 0; + + for (const course of courses) { + console.log(`📖 Kurs: ${course.title} (ID: ${course.id})`); + + // Finde native language name + const nativeLanguageName = course.native_language_name || 'Deutsch'; + console.log(` Muttersprache: ${nativeLanguageName}`); + + // Finde "Gefühle & Zuneigung" Lektion + const lessons = await VocabCourseLesson.findAll({ + where: { + courseId: course.id, + title: 'Gefühle & Zuneigung' + }, + attributes: ['id', 'title', 'lessonNumber'] + }); + + console.log(` ${lessons.length} "Gefühle & Zuneigung"-Lektion(en) gefunden`); + + for (const lesson of lessons) { + // Lösche vorhandene Übungen + const deletedCount = await VocabGrammarExercise.destroy({ + where: { lessonId: lesson.id } + }); + console.log(` 🗑️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${deletedCount} alte Übung(en) gelöscht`); + + // Erstelle neue Übungen + const exercises = createFeelingsAffectionExercises(nativeLanguageName); + + if (exercises.length > 0) { + const exercisesToCreate = exercises.map(ex => ({ + ...ex, + lessonId: lesson.id, + createdByUserId: systemUser.id + })); + + await VocabGrammarExercise.bulkCreate(exercisesToCreate); + totalExercisesCreated += exercisesToCreate.length; + console.log(` ✅ ${exercisesToCreate.length} neue Übung(en) erstellt`); + } else { + console.log(` ⚠️ Keine Übungen erstellt`); + } + + totalLessonsProcessed++; + } + console.log(''); + } + + console.log(`\n🎉 Zusammenfassung:`); + console.log(` ${totalLessonsProcessed} "Gefühle & Zuneigung"-Lektion(en) verarbeitet`); + console.log(` ${totalExercisesCreated} Grammatik-Übungen erstellt`); +} + +updateFeelingsAffectionExercises() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/frontend/src/views/social/VocabCourseView.vue b/frontend/src/views/social/VocabCourseView.vue index 5ef2bd4..35d57d0 100644 --- a/frontend/src/views/social/VocabCourseView.vue +++ b/frontend/src/views/social/VocabCourseView.vue @@ -20,26 +20,43 @@

{{ $t('socialnetwork.vocab.courses.lessons') }}

-
-
- {{ lesson.lessonNumber }}. -

{{ lesson.title }}

- - {{ $t('socialnetwork.vocab.courses.completed') }} - - - {{ $t('socialnetwork.vocab.courses.score') }}: {{ getLessonProgress(lesson.id).score }} - -
-

{{ lesson.description }}

-
- - - -
-
+ + + + + + + + + + + + + + + + + +
{{ $t('socialnetwork.vocab.courses.lessonNumber') }}{{ $t('socialnetwork.vocab.courses.title') }}StatusAktionen
{{ lesson.lessonNumber }} + {{ lesson.title }} + {{ lesson.description }} + + + {{ $t('socialnetwork.vocab.courses.completed') }} + + + {{ $t('socialnetwork.vocab.courses.score') }}: {{ getLessonProgress(lesson.id).score }}% + + + Nicht begonnen + + + + + +

{{ $t('socialnetwork.vocab.courses.noLessons') }}

@@ -233,56 +250,158 @@ export default { margin-top: 30px; } -.lesson-item { - background: white; - padding: 15px; - border: 1px solid #ddd; - border-radius: 4px; - margin-bottom: 15px; +.lessons-table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; } -.lesson-header { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 10px; +.lessons-table thead { + background: #f8f9fa; +} + +.lessons-table th { + padding: 12px 15px; + text-align: left; + font-weight: 600; + color: #333; + font-size: 0.9em; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.lessons-table th.col-number { + width: 60px; +} + +.lessons-table th.col-title { + width: auto; +} + +.lessons-table th.col-status { + width: 180px; +} + +.lessons-table th.col-actions { + width: 200px; +} + +.lessons-table tbody tr { + transition: background-color 0.2s ease; +} + +.lessons-table tbody tr:hover { + background-color: #f8f9fa; +} + +.lessons-table td { + padding: 15px; + vertical-align: top; } .lesson-number { - font-weight: bold; + font-weight: 600; color: #666; + font-size: 0.95em; } .lesson-title { - cursor: pointer; - margin: 0; - flex: 1; - color: #0066cc; - text-decoration: underline; + display: flex; + flex-direction: column; + gap: 5px; +} + +.title-label { + font-weight: 500; + color: #333; + font-size: 1em; +} + +.lesson-description { + color: #666; + font-size: 0.85em; + line-height: 1.4; +} + +.lesson-status { + display: flex; + flex-direction: column; + gap: 5px; + align-items: flex-start; } .badge.completed { background: #4CAF50; color: white; - padding: 4px 8px; - border-radius: 3px; - font-size: 0.85em; + padding: 4px 10px; + border-radius: 12px; + font-size: 0.8em; + font-weight: 500; + white-space: nowrap; } .score { color: #666; - font-size: 0.9em; + font-size: 0.85em; } -.lesson-description { - color: #666; - margin: 10px 0; +.status-new { + color: #999; + font-size: 0.85em; + font-style: italic; } .lesson-actions { display: flex; - gap: 10px; - margin-top: 10px; + gap: 8px; + flex-wrap: wrap; + align-items: center; +} + +.btn-start { + padding: 8px 16px; + background: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.9em; + font-weight: 500; + transition: background-color 0.2s ease; +} + +.btn-start:hover { + background: #0056b3; +} + +.btn-edit { + padding: 6px 12px; + background: #ffc107; + color: #333; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.85em; + transition: background-color 0.2s ease; +} + +.btn-edit:hover { + background: #e0a800; +} + +.btn-delete { + padding: 6px 12px; + background: #dc3545; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.85em; + transition: background-color 0.2s ease; +} + +.btn-delete:hover { + background: #c82333; } .dialog-overlay {