- Implemented a new method in AdminService to fetch potential fathers for a given character based on existing relationships. - Updated AdminController to expose this functionality via a new API endpoint. - Enhanced adminRouter to include the route for retrieving potential fathers. - Modified frontend components to allow selection of potential fathers during pregnancy and birth management. - Updated internationalization files to include new translation keys related to father selection.
776 lines
21 KiB
Vue
776 lines
21 KiB
Vue
<template>
|
|
<div class="vocab-course-view">
|
|
<div v-if="loading" class="surface-card course-state">{{ $t('general.loading') }}</div>
|
|
<div v-else-if="course">
|
|
<section class="course-hero surface-card">
|
|
<div>
|
|
<span class="course-kicker">Lernkurs</span>
|
|
<h2>{{ course.title }}</h2>
|
|
<p v-if="course.description">{{ course.description }}</p>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="course-info surface-card">
|
|
<span>{{ $t('socialnetwork.vocab.courses.difficulty') }}: {{ course.difficultyLevel }}</span>
|
|
<span v-if="course.isPublic">{{ $t('socialnetwork.vocab.courses.public') }}</span>
|
|
<span v-if="course.shareCode && isOwner" class="share-code">
|
|
{{ $t('socialnetwork.vocab.courses.shareCode') }}: <code>{{ course.shareCode }}</code>
|
|
</span>
|
|
</div>
|
|
|
|
<section class="surface-card course-assistant">
|
|
<div>
|
|
<span class="course-assistant__eyebrow">{{ $t('socialnetwork.vocab.courses.languageAssistantEyebrow') }}</span>
|
|
<h3>{{ $t('socialnetwork.vocab.courses.languageAssistantCourseTitle') }}</h3>
|
|
<p>{{ assistantAvailable ? $t('socialnetwork.vocab.courses.languageAssistantCourseReady') : $t('socialnetwork.vocab.courses.languageAssistantCourseSetup') }}</p>
|
|
</div>
|
|
<div class="course-assistant__actions">
|
|
<button type="button" class="button-secondary" @click="openLanguageAssistantSettings">
|
|
{{ $t('socialnetwork.vocab.courses.languageAssistantSettings') }}
|
|
</button>
|
|
<button
|
|
v-if="assistantAvailable && currentLesson"
|
|
type="button"
|
|
@click="openLessonAssistant(currentLesson.id)"
|
|
>
|
|
{{ $t('socialnetwork.vocab.courses.languageAssistantOpenLesson') }}
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<div v-if="isOwner" class="owner-actions">
|
|
<button @click="showAddLessonDialog = true">{{ $t('socialnetwork.vocab.courses.addLesson') }}</button>
|
|
<button @click="editCourse">{{ $t('socialnetwork.vocab.courses.edit') }}</button>
|
|
</div>
|
|
|
|
<div v-if="course.lessons && course.lessons.length > 0" class="lessons-list surface-card">
|
|
<div class="current-lesson-section" v-if="currentLesson">
|
|
<button @click="openLesson(currentLesson.id)" class="btn-current-lesson">
|
|
{{ $t('socialnetwork.vocab.courses.continueCurrentLesson') }}
|
|
</button>
|
|
</div>
|
|
<div class="lessons-header">
|
|
<h3>{{ $t('socialnetwork.vocab.courses.lessons') }}</h3>
|
|
<span class="lessons-count">{{ course.lessons.length }} Lektionen</span>
|
|
</div>
|
|
<div class="lesson-cards">
|
|
<article v-for="lesson in course.lessons" :key="lesson.id" class="lesson-card">
|
|
<div class="lesson-card__header">
|
|
<span class="lesson-number">#{{ lesson.lessonNumber }}</span>
|
|
<div class="lesson-status-content">
|
|
<span v-if="getLessonProgress(lesson.id)?.completed" class="badge completed">
|
|
{{ $t('socialnetwork.vocab.courses.completed') }}
|
|
</span>
|
|
<span v-else-if="getLessonProgress(lesson.id)?.score" class="score">
|
|
{{ $t('socialnetwork.vocab.courses.score') }}: {{ getLessonProgress(lesson.id).score }}%
|
|
</span>
|
|
<span v-else class="status-new">
|
|
{{ $t('socialnetwork.vocab.courses.notStarted') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="lesson-title-content">
|
|
<span class="title-label">{{ lesson.title }}</span>
|
|
<span v-if="lesson.description" class="lesson-description">{{ lesson.description }}</span>
|
|
</div>
|
|
<div class="lesson-pedagogy" v-if="lesson.pedagogy">
|
|
<span class="lesson-chip lesson-chip--phase">{{ getPhaseLabel(lesson.pedagogy.phaseLabel) }}</span>
|
|
<span class="lesson-chip lesson-chip--mode">{{ getDidacticModeLabel(lesson.pedagogy.didacticMode) }}</span>
|
|
<span v-if="lesson.pedagogy.blockNumber" class="lesson-chip lesson-chip--block">Block {{ lesson.pedagogy.blockNumber }}</span>
|
|
<span v-if="lesson.pedagogy.isIntensiveReview" class="lesson-chip lesson-chip--intensive">Intensive Wiederholung</span>
|
|
</div>
|
|
<div class="lesson-actions-content">
|
|
<button
|
|
@click="openLesson(lesson.id)"
|
|
class="btn-start"
|
|
:disabled="!canStartLesson(lesson)"
|
|
:title="!canStartLesson(lesson) ? $t('socialnetwork.vocab.courses.previousLessonRequired') : ''"
|
|
>
|
|
{{ 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>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<p class="surface-card course-state">{{ $t('socialnetwork.vocab.courses.noLessons') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Lesson Dialog -->
|
|
<div v-if="showAddLessonDialog" class="dialog-overlay" @click="showAddLessonDialog = false">
|
|
<div class="dialog" @click.stop>
|
|
<h3>{{ $t('socialnetwork.vocab.courses.addLesson') }}</h3>
|
|
<form @submit.prevent="addLesson">
|
|
<div class="form-group form-field">
|
|
<label>{{ $t('socialnetwork.vocab.courses.lessonNumber') }}</label>
|
|
<input type="number" v-model.number="newLesson.lessonNumber" min="1" required :class="{ 'field-error': lessonFormTouched && !isLessonNumberValid }" />
|
|
</div>
|
|
<div class="form-group form-field">
|
|
<label>{{ $t('socialnetwork.vocab.courses.title') }}</label>
|
|
<input v-model="newLesson.title" required :class="{ 'field-error': lessonFormTouched && !isLessonTitleValid }" />
|
|
</div>
|
|
<div class="form-group form-field">
|
|
<label>{{ $t('socialnetwork.vocab.courses.description') }}</label>
|
|
<textarea v-model="newLesson.description"></textarea>
|
|
</div>
|
|
<div class="form-group form-field">
|
|
<label>{{ $t('socialnetwork.vocab.courses.chapter') }}</label>
|
|
<select v-model="newLesson.chapterId" required :class="{ 'field-error': lessonFormTouched && !isLessonChapterValid }">
|
|
<option value="">{{ $t('socialnetwork.vocab.courses.selectChapter') }}</option>
|
|
<option v-for="chapter in chapters" :key="chapter.id" :value="chapter.id">{{ chapter.title }}</option>
|
|
</select>
|
|
</div>
|
|
<span v-if="lessonFormTouched && !canCreateLesson" class="form-error">Bitte Nummer, Titel und Kapitel vollständig angeben.</span>
|
|
<div class="form-actions form-actions-row">
|
|
<button type="submit" :disabled="!canCreateLesson">{{ $t('general.create') }}</button>
|
|
<button type="button" @click="showAddLessonDialog = false" class="button-secondary">{{ $t('general.cancel') }}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { mapGetters } from 'vuex';
|
|
import apiClient from '@/utils/axios.js';
|
|
import { confirmAction, showApiError, showInfo, showSuccess } from '@/utils/feedback.js';
|
|
|
|
export default {
|
|
name: 'VocabCourseView',
|
|
props: {
|
|
courseId: {
|
|
type: String,
|
|
required: true
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
course: null,
|
|
progress: [],
|
|
chapters: [],
|
|
showAddLessonDialog: false,
|
|
assistantSettings: null,
|
|
lessonFormTouched: false,
|
|
newLesson: {
|
|
lessonNumber: 1,
|
|
title: '',
|
|
description: '',
|
|
chapterId: null
|
|
}
|
|
};
|
|
},
|
|
computed: {
|
|
...mapGetters(['user']),
|
|
isOwner() {
|
|
return this.course && this.course.ownerUserId === this.user?.id;
|
|
},
|
|
currentLesson() {
|
|
if (!this.course || !this.course.lessons || this.course.lessons.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// Sortiere Lektionen nach lessonNumber
|
|
const sortedLessons = [...this.course.lessons].sort((a, b) => a.lessonNumber - b.lessonNumber);
|
|
|
|
// Finde die erste nicht abgeschlossene Lektion
|
|
for (const lesson of sortedLessons) {
|
|
const progress = this.getLessonProgress(lesson.id);
|
|
if (!progress || !progress.completed) {
|
|
return lesson;
|
|
}
|
|
}
|
|
|
|
// Alle Lektionen abgeschlossen - zeige die letzte Lektion
|
|
return sortedLessons[sortedLessons.length - 1];
|
|
},
|
|
isLessonNumberValid() {
|
|
return Number(this.newLesson.lessonNumber) > 0;
|
|
},
|
|
isLessonTitleValid() {
|
|
return this.newLesson.title.trim().length >= 3;
|
|
},
|
|
isLessonChapterValid() {
|
|
return Boolean(this.newLesson.chapterId);
|
|
},
|
|
canCreateLesson() {
|
|
return this.isLessonNumberValid && this.isLessonTitleValid && this.isLessonChapterValid;
|
|
},
|
|
assistantAvailable() {
|
|
if (!this.assistantSettings) {
|
|
return false;
|
|
}
|
|
const enabled = this.assistantSettings.enabled !== false;
|
|
const hasBaseUrl = Boolean(this.assistantSettings.baseUrl);
|
|
return enabled && (this.assistantSettings.hasKey || hasBaseUrl);
|
|
}
|
|
},
|
|
watch: {
|
|
courseId() {
|
|
this.loadCourse();
|
|
}
|
|
},
|
|
methods: {
|
|
async loadCourse() {
|
|
this.loading = true;
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/courses/${this.courseId}`);
|
|
this.course = res.data;
|
|
await this.loadProgress();
|
|
if (this.course.languageId) {
|
|
await this.loadChapters();
|
|
}
|
|
} catch (e) {
|
|
console.error('Konnte Kurs nicht laden:', e);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
async loadProgress() {
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/courses/${this.courseId}/progress`);
|
|
this.progress = res.data || [];
|
|
} catch (e) {
|
|
// Nicht eingeschrieben? Progress ist leer
|
|
this.progress = [];
|
|
}
|
|
},
|
|
async loadChapters() {
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/languages/${this.course.languageId}/chapters`);
|
|
this.chapters = res.data?.chapters || [];
|
|
} catch (e) {
|
|
console.error('Konnte Kapitel nicht laden:', e);
|
|
}
|
|
},
|
|
async loadAssistantSettings() {
|
|
try {
|
|
const { data } = await apiClient.get('/api/settings/llm');
|
|
this.assistantSettings = data;
|
|
} catch (e) {
|
|
this.assistantSettings = null;
|
|
}
|
|
},
|
|
getLessonProgress(lessonId) {
|
|
return this.progress.find(p => p.lessonId === lessonId);
|
|
},
|
|
canStartLesson(lesson) {
|
|
if (!this.course || !this.course.lessons) {
|
|
return false;
|
|
}
|
|
|
|
// Sortiere Lektionen nach lessonNumber
|
|
const sortedLessons = [...this.course.lessons].sort((a, b) => a.lessonNumber - b.lessonNumber);
|
|
|
|
// Finde den Index der aktuellen Lektion
|
|
const currentIndex = sortedLessons.findIndex(l => l.id === lesson.id);
|
|
|
|
// Die erste Lektion kann immer gestartet werden
|
|
if (currentIndex === 0) {
|
|
return true;
|
|
}
|
|
|
|
// Wenn es nicht die erste Lektion ist, prüfe ob die vorherige abgeschlossen wurde
|
|
if (currentIndex > 0) {
|
|
const previousLesson = sortedLessons[currentIndex - 1];
|
|
const previousProgress = this.getLessonProgress(previousLesson.id);
|
|
return previousProgress && previousProgress.completed;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
async addLesson() {
|
|
this.lessonFormTouched = true;
|
|
if (!this.canCreateLesson) {
|
|
return;
|
|
}
|
|
try {
|
|
await apiClient.post(`/api/vocab/courses/${this.courseId}/lessons`, this.newLesson);
|
|
this.showAddLessonDialog = false;
|
|
this.lessonFormTouched = false;
|
|
this.newLesson = {
|
|
lessonNumber: 1,
|
|
title: '',
|
|
description: '',
|
|
chapterId: null
|
|
};
|
|
await this.loadCourse();
|
|
showSuccess(this, 'Lektion erfolgreich angelegt.');
|
|
} catch (e) {
|
|
console.error('Fehler beim Hinzufügen der Lektion:', e);
|
|
showApiError(this, e, 'Fehler beim Hinzufügen der Lektion');
|
|
}
|
|
},
|
|
async deleteLesson(lessonId) {
|
|
const confirmed = await confirmAction(this, {
|
|
title: 'Lektion löschen',
|
|
message: this.$t('socialnetwork.vocab.courses.confirmDelete')
|
|
});
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
try {
|
|
await apiClient.delete(`/api/vocab/lessons/${lessonId}`);
|
|
await this.loadCourse();
|
|
showSuccess(this, 'Lektion erfolgreich gelöscht.');
|
|
} catch (e) {
|
|
console.error('Fehler beim Löschen der Lektion:', e);
|
|
showApiError(this, e, 'Fehler beim Löschen der Lektion');
|
|
}
|
|
},
|
|
openLesson(lessonId) {
|
|
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${lessonId}`);
|
|
},
|
|
openLessonAssistant(lessonId) {
|
|
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/lessons/${lessonId}?assistant=1`);
|
|
},
|
|
editCourse() {
|
|
this.$router.push(`/socialnetwork/vocab/courses/${this.courseId}/edit`);
|
|
},
|
|
openLanguageAssistantSettings() {
|
|
this.$router.push('/settings/language-assistant');
|
|
},
|
|
getPhaseLabel(phaseLabel) {
|
|
switch (phaseLabel) {
|
|
case 'quickstart':
|
|
return 'Schnellstart';
|
|
case 'daily_life':
|
|
return 'Alltag';
|
|
case 'stabilization':
|
|
return 'Stabilisierung';
|
|
default:
|
|
return 'Lernphase';
|
|
}
|
|
},
|
|
getDidacticModeLabel(didacticMode) {
|
|
switch (didacticMode) {
|
|
case 'core_input':
|
|
return 'Neuer Stoff';
|
|
case 'guided_dialogue':
|
|
return 'Geführter Dialog';
|
|
case 'pattern_drill':
|
|
return 'Mustertraining';
|
|
case 'real_life_scenario':
|
|
return 'Alltagsszenario';
|
|
case 'intensive_review':
|
|
return 'Wiederholungsphase';
|
|
case 'checkpoint':
|
|
return 'Checkpoint';
|
|
default:
|
|
return 'Lerneinheit';
|
|
}
|
|
},
|
|
editLesson() {
|
|
showInfo(this, 'Die Bearbeitung einzelner Lektionen folgt noch.');
|
|
}
|
|
},
|
|
async mounted() {
|
|
await Promise.all([
|
|
this.loadCourse(),
|
|
this.loadAssistantSettings()
|
|
]);
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.vocab-course-view {
|
|
max-width: var(--content-max-width);
|
|
margin: 0 auto;
|
|
padding: 0 0 24px;
|
|
}
|
|
|
|
.course-hero,
|
|
.course-info,
|
|
.course-assistant,
|
|
.lessons-list,
|
|
.course-state {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.course-hero {
|
|
padding: 24px 26px;
|
|
}
|
|
|
|
.course-kicker {
|
|
display: inline-block;
|
|
margin-bottom: 10px;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
background: rgba(248, 162, 43, 0.14);
|
|
color: #8a5411;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
.course-hero p {
|
|
margin: 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.course-info {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin: 0 0 16px;
|
|
color: #666;
|
|
flex-wrap: wrap;
|
|
padding: 16px 18px;
|
|
}
|
|
|
|
.course-assistant {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 18px 20px;
|
|
}
|
|
|
|
.course-assistant__eyebrow {
|
|
display: inline-block;
|
|
margin-bottom: 8px;
|
|
font-size: 0.75rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--color-text-muted);
|
|
}
|
|
|
|
.course-assistant h3,
|
|
.course-assistant p {
|
|
margin: 0;
|
|
}
|
|
|
|
.course-assistant p {
|
|
margin-top: 6px;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.course-assistant__actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.share-code {
|
|
font-family: monospace;
|
|
}
|
|
|
|
.share-code code {
|
|
background: #f0f0f0;
|
|
padding: 2px 6px;
|
|
border-radius: 3px;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.owner-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.lessons-list {
|
|
margin-top: 0;
|
|
padding: 20px;
|
|
}
|
|
|
|
.lessons-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.lessons-count {
|
|
color: var(--color-text-muted);
|
|
font-size: 0.84rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.course-state {
|
|
padding: 18px;
|
|
text-align: center;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.current-lesson-section {
|
|
margin-bottom: 20px;
|
|
padding: 15px;
|
|
background: #f8f9fa;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.btn-current-lesson {
|
|
padding: 12px 24px;
|
|
background: var(--color-primary-orange);
|
|
color: #000000;
|
|
border: 1px solid var(--color-primary-orange);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
font-weight: 600;
|
|
transition: background 0.05s;
|
|
}
|
|
|
|
.btn-current-lesson:hover {
|
|
background: #FFF4F0;
|
|
color: #5D4037;
|
|
border: 1px solid #5D4037;
|
|
}
|
|
|
|
.lesson-cards {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.lesson-number {
|
|
font-weight: 600;
|
|
color: #666;
|
|
font-size: 0.95em;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 48px;
|
|
padding: 6px 10px;
|
|
border-radius: 999px;
|
|
background: rgba(248, 162, 43, 0.12);
|
|
}
|
|
|
|
.lesson-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
padding: 16px 18px;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
background: rgba(255, 255, 255, 0.7);
|
|
}
|
|
|
|
.lesson-pedagogy {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
.lesson-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
font-size: 0.78rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.lesson-chip--phase {
|
|
background: rgba(120, 195, 138, 0.16);
|
|
color: #42634e;
|
|
}
|
|
|
|
.lesson-chip--mode {
|
|
background: rgba(248, 162, 43, 0.16);
|
|
color: #8a5411;
|
|
}
|
|
|
|
.lesson-chip--block {
|
|
background: rgba(93, 64, 55, 0.09);
|
|
color: #6d5446;
|
|
}
|
|
|
|
.lesson-chip--intensive {
|
|
background: rgba(207, 78, 78, 0.14);
|
|
color: #a13f3f;
|
|
}
|
|
|
|
.lesson-card__header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.lesson-title-content {
|
|
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-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
align-items: flex-start;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.badge.completed {
|
|
background: #4CAF50;
|
|
color: white;
|
|
padding: 4px 10px;
|
|
border-radius: 12px;
|
|
font-size: 0.8em;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.score {
|
|
color: #666;
|
|
font-size: 0.85em;
|
|
}
|
|
|
|
.status-new {
|
|
color: #999;
|
|
font-size: 0.85em;
|
|
font-style: italic;
|
|
}
|
|
|
|
.lesson-actions {
|
|
display: block;
|
|
}
|
|
|
|
.lesson-actions-content {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
align-items: flex-start;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.btn-start {
|
|
padding: 8px 16px;
|
|
background: var(--color-primary-orange);
|
|
color: #000000;
|
|
border: 1px solid var(--color-primary-orange);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
font-weight: 500;
|
|
transition: background 0.05s;
|
|
}
|
|
|
|
.btn-start:hover:not(:disabled) {
|
|
background: #FFF4F0;
|
|
color: #5D4037;
|
|
border: 1px solid #5D4037;
|
|
}
|
|
|
|
.btn-start:disabled {
|
|
background: #e0e0e0;
|
|
color: #999;
|
|
border: 1px solid #ccc;
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.btn-edit {
|
|
padding: 6px 12px;
|
|
background: var(--color-primary-orange);
|
|
color: #000000;
|
|
border: 1px solid var(--color-primary-orange);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.85em;
|
|
transition: background 0.05s;
|
|
}
|
|
|
|
.btn-edit:hover {
|
|
background: #FFF4F0;
|
|
color: #5D4037;
|
|
border: 1px solid #5D4037;
|
|
}
|
|
|
|
.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 {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.dialog {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
max-width: 500px;
|
|
width: 90%;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.form-group {
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.form-group input,
|
|
.form-group textarea,
|
|
.form-group select {
|
|
width: 100%;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.form-group textarea {
|
|
min-height: 80px;
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: flex-end;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.course-assistant {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|