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:
Torsten Schulz (local)
2026-01-20 14:16:22 +01:00
parent d4a0f78cd0
commit e5d4a5f95f
2 changed files with 687 additions and 46 deletions

View 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);
});

View File

@@ -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 {