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.
This commit is contained in:
522
backend/scripts/update-feelings-affection-exercises.js
Executable file
522
backend/scripts/update-feelings-affection-exercises.js
Executable file
@@ -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);
|
||||||
|
});
|
||||||
@@ -20,26 +20,43 @@
|
|||||||
|
|
||||||
<div v-if="course.lessons && course.lessons.length > 0" class="lessons-list">
|
<div v-if="course.lessons && course.lessons.length > 0" class="lessons-list">
|
||||||
<h3>{{ $t('socialnetwork.vocab.courses.lessons') }}</h3>
|
<h3>{{ $t('socialnetwork.vocab.courses.lessons') }}</h3>
|
||||||
<div v-for="lesson in course.lessons" :key="lesson.id" class="lesson-item">
|
<table class="lessons-table">
|
||||||
<div class="lesson-header">
|
<thead>
|
||||||
<span class="lesson-number">{{ lesson.lessonNumber }}.</span>
|
<tr>
|
||||||
<h4 @click="openLesson(lesson.id)" class="lesson-title">{{ lesson.title }}</h4>
|
<th class="col-number">{{ $t('socialnetwork.vocab.courses.lessonNumber') }}</th>
|
||||||
<span v-if="getLessonProgress(lesson.id)?.completed" class="badge completed">
|
<th class="col-title">{{ $t('socialnetwork.vocab.courses.title') }}</th>
|
||||||
{{ $t('socialnetwork.vocab.courses.completed') }}
|
<th class="col-status">Status</th>
|
||||||
</span>
|
<th class="col-actions">Aktionen</th>
|
||||||
<span v-if="getLessonProgress(lesson.id)?.score" class="score">
|
</tr>
|
||||||
{{ $t('socialnetwork.vocab.courses.score') }}: {{ getLessonProgress(lesson.id).score }}
|
</thead>
|
||||||
</span>
|
<tbody>
|
||||||
</div>
|
<tr v-for="lesson in course.lessons" :key="lesson.id" class="lesson-row">
|
||||||
<p v-if="lesson.description" class="lesson-description">{{ lesson.description }}</p>
|
<td class="lesson-number">{{ lesson.lessonNumber }}</td>
|
||||||
<div class="lesson-actions">
|
<td class="lesson-title">
|
||||||
<button @click="openLesson(lesson.id)">
|
<span class="title-label">{{ lesson.title }}</span>
|
||||||
{{ getLessonProgress(lesson.id)?.completed ? $t('socialnetwork.vocab.courses.review') : $t('socialnetwork.vocab.courses.start') }}
|
<span v-if="lesson.description" class="lesson-description">{{ lesson.description }}</span>
|
||||||
</button>
|
</td>
|
||||||
<button v-if="isOwner" @click="editLesson(lesson.id)">{{ $t('socialnetwork.vocab.courses.edit') }}</button>
|
<td class="lesson-status">
|
||||||
<button v-if="isOwner" @click="deleteLesson(lesson.id)">{{ $t('general.delete') }}</button>
|
<span v-if="getLessonProgress(lesson.id)?.completed" class="badge completed">
|
||||||
</div>
|
{{ $t('socialnetwork.vocab.courses.completed') }}
|
||||||
</div>
|
</span>
|
||||||
|
<span v-if="getLessonProgress(lesson.id)?.score" class="score">
|
||||||
|
{{ $t('socialnetwork.vocab.courses.score') }}: {{ getLessonProgress(lesson.id).score }}%
|
||||||
|
</span>
|
||||||
|
<span v-else-if="!getLessonProgress(lesson.id)" class="status-new">
|
||||||
|
Nicht begonnen
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="lesson-actions">
|
||||||
|
<button @click="openLesson(lesson.id)" class="btn-start">
|
||||||
|
{{ getLessonProgress(lesson.id)?.completed ? $t('socialnetwork.vocab.courses.review') : $t('socialnetwork.vocab.courses.start') }}
|
||||||
|
</button>
|
||||||
|
<button v-if="isOwner" @click="editLesson(lesson.id)" class="btn-edit">{{ $t('socialnetwork.vocab.courses.edit') }}</button>
|
||||||
|
<button v-if="isOwner" @click="deleteLesson(lesson.id)" class="btn-delete">{{ $t('general.delete') }}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<p>{{ $t('socialnetwork.vocab.courses.noLessons') }}</p>
|
<p>{{ $t('socialnetwork.vocab.courses.noLessons') }}</p>
|
||||||
@@ -233,56 +250,158 @@ export default {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lesson-item {
|
.lessons-table {
|
||||||
background: white;
|
width: 100%;
|
||||||
padding: 15px;
|
border-collapse: collapse;
|
||||||
border: 1px solid #ddd;
|
border-spacing: 0;
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lesson-header {
|
.lessons-table thead {
|
||||||
display: flex;
|
background: #f8f9fa;
|
||||||
align-items: center;
|
}
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
.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 {
|
.lesson-number {
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lesson-title {
|
.lesson-title {
|
||||||
cursor: pointer;
|
display: flex;
|
||||||
margin: 0;
|
flex-direction: column;
|
||||||
flex: 1;
|
gap: 5px;
|
||||||
color: #0066cc;
|
}
|
||||||
text-decoration: underline;
|
|
||||||
|
.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 {
|
.badge.completed {
|
||||||
background: #4CAF50;
|
background: #4CAF50;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 4px 8px;
|
padding: 4px 10px;
|
||||||
border-radius: 3px;
|
border-radius: 12px;
|
||||||
font-size: 0.85em;
|
font-size: 0.8em;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.score {
|
.score {
|
||||||
color: #666;
|
color: #666;
|
||||||
font-size: 0.9em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lesson-description {
|
.status-new {
|
||||||
color: #666;
|
color: #999;
|
||||||
margin: 10px 0;
|
font-size: 0.85em;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lesson-actions {
|
.lesson-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
margin-top: 10px;
|
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 {
|
.dialog-overlay {
|
||||||
|
|||||||
Reference in New Issue
Block a user