feat: Academy & Training Module auf 100% — vollständige Implementierung
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 38s
CI / test-python-backend-compliance (push) Successful in 36s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 19s

Paket A — Type Fixes:
- Course Interface: passingScore, isActive, status ergänzt
- BackendCourse: passing_score, status Mapping
- CourseUpdateRequest: passingScore, status ergänzt
- fetchAcademyStatistics: by_category/by_status Felder gemappt

Paket B — Academy Zertifikate-Tab:
- fetchCertificates() API Funktion
- Vollständiger Tab: Stats (Gesamt/Gültig/Abgelaufen), Suche, Tabelle mit Status-Badges, PDF-Download

Paket C — Academy Enrollment + Course Edit + Settings:
- deleteEnrollment(), updateEnrollment() API
- EnrollmentCard: Abschließen/Bearbeiten/Löschen Buttons
- EnrollmentEditModal: Deadline bearbeiten
- CourseCard: Stift-Icon (group-hover) → CourseEditModal
- CourseEditModal: Titel/Beschreibung/Kategorie/Dauer/Bestehensgrenze/Status
- SettingsTab: localStorage-basiert mit Toggles und Zahlen-Inputs + grüne Bestätigung

Paket D — Training Modul-CRUD:
- deleteModule() API Funktion
- "+ Neues Modul" Button im Modules Tab Header
- ModuleCreateModal: module_code, title, description, regulation_area, frequency_type, duration, pass_threshold
- ModuleEditDrawer (Right-Slide): Edit + Aktiv-Toggle + Löschen

Paket E — Training Matrix-Editor:
- Matrix-Tabelle interaktiv: × Button je Badge zum Entfernen
- "+ Hinzufügen" Button je Rolle
- MatrixAddModal: Modul wählen, Pflicht-Toggle, Priorität

Paket F — Training Assignments + UX-Fixes:
- updateAssignment() API Funktion
- AssignmentDetailDrawer (Right-Slide): Details, Status-Aktionen, Deadline-Edit
- handleCheckEscalation: alert() → escalationResult State + blauer Banner (schließbar)
- Media auto-sync useEffect: selectedModuleId → loadModuleMedia

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-03 14:24:13 +01:00
parent 232997deb6
commit d4845adea7
5 changed files with 1203 additions and 119 deletions

View File

@@ -112,7 +112,7 @@ async function fetchWithTimeout<T>(
* Alle Kurse abrufen
*/
export async function fetchCourses(): Promise<Course[]> {
const res = await fetchWithTimeout<{ courses: Course[]; total: number }>(
const res = await fetchWithTimeout<{ courses: BackendCourse[]; total: number }>(
`${ACADEMY_API_BASE}/courses`
)
return mapCoursesFromBackend(res.courses || [])
@@ -137,6 +137,8 @@ interface BackendCourse {
duration_minutes: number
required_for_roles: string[]
is_active: boolean
passing_score?: number
status?: string
lessons?: BackendLesson[]
created_at: string
updated_at: string
@@ -169,6 +171,9 @@ function mapCourseFromBackend(bc: BackendCourse): Course {
description: bc.description || '',
category: bc.category,
durationMinutes: bc.duration_minutes || 0,
passingScore: bc.passing_score ?? 70,
isActive: bc.is_active ?? true,
status: (bc.status as 'draft' | 'published') ?? 'draft',
requiredForRoles: bc.required_for_roles || [],
lessons: (bc.lessons || []).map(l => ({
id: l.id,
@@ -316,6 +321,39 @@ export async function generateCertificate(enrollmentId: string): Promise<Certifi
)
}
/**
* Alle Zertifikate abrufen
*/
export async function fetchCertificates(): Promise<Certificate[]> {
const res = await fetchWithTimeout<{ certificates: Certificate[]; total: number }>(
`${ACADEMY_API_BASE}/certificates`
)
return res.certificates || []
}
/**
* Einschreibung loeschen
*/
export async function deleteEnrollment(id: string): Promise<void> {
await fetchWithTimeout<void>(
`${ACADEMY_API_BASE}/enrollments/${id}`,
{ method: 'DELETE' }
)
}
/**
* Einschreibung aktualisieren (z.B. Deadline)
*/
export async function updateEnrollment(id: string, data: { deadline?: string }): Promise<Enrollment> {
return fetchWithTimeout<Enrollment>(
`${ACADEMY_API_BASE}/enrollments/${id}`,
{
method: 'PUT',
body: JSON.stringify(data),
}
)
}
// =============================================================================
// QUIZ
// =============================================================================
@@ -366,6 +404,8 @@ export async function fetchAcademyStatistics(): Promise<AcademyStatistics> {
completion_rate: number
overdue_count: number
avg_completion_days: number
by_category?: Record<string, number>
by_status?: Record<string, number>
}>(`${ACADEMY_API_BASE}/stats`)
return {
@@ -373,8 +413,8 @@ export async function fetchAcademyStatistics(): Promise<AcademyStatistics> {
totalEnrollments: res.total_enrollments || 0,
completionRate: res.completion_rate || 0,
overdueCount: res.overdue_count || 0,
byCategory: {} as Record<CourseCategory, number>,
byStatus: {} as Record<EnrollmentStatus, number>,
byCategory: (res.by_category || {}) as Record<CourseCategory, number>,
byStatus: (res.by_status || {}) as Record<EnrollmentStatus, number>,
}
}
@@ -484,6 +524,9 @@ export function createMockCourses(): Course[] {
description: 'Umfassende Einfuehrung in die Datenschutz-Grundverordnung. Dieses Pflichttraining vermittelt die wichtigsten Grundsaetze des Datenschutzes, Betroffenenrechte und die korrekte Handhabung personenbezogener Daten im Arbeitsalltag.',
category: 'dsgvo_basics',
durationMinutes: 90,
passingScore: 80,
isActive: true,
status: 'published',
requiredForRoles: ['all'],
createdAt: new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000).toISOString(),
updatedAt: new Date(now.getTime() - 5 * 24 * 60 * 60 * 1000).toISOString(),
@@ -543,6 +586,9 @@ export function createMockCourses(): Course[] {
description: 'Sensibilisierung fuer IT-Sicherheitsrisiken und Best Practices im Umgang mit Phishing, Passwoertern, Social Engineering und sicherer Kommunikation.',
category: 'it_security',
durationMinutes: 60,
passingScore: 75,
isActive: true,
status: 'published',
requiredForRoles: ['all'],
createdAt: new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000).toISOString(),
updatedAt: new Date(now.getTime() - 10 * 24 * 60 * 60 * 1000).toISOString(),
@@ -592,6 +638,9 @@ export function createMockCourses(): Course[] {
description: 'Grundlagen kuenstlicher Intelligenz, EU AI Act, verantwortungsvoller Einsatz von KI-Werkzeugen und Risiken bei der Nutzung von Large Language Models (LLMs) im Unternehmen.',
category: 'ai_literacy',
durationMinutes: 75,
passingScore: 70,
isActive: true,
status: 'draft',
requiredForRoles: ['admin', 'data_protection_officer'],
createdAt: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString(),
updatedAt: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000).toISOString(),

View File

@@ -117,6 +117,9 @@ export interface Course {
category: CourseCategory
lessons: Lesson[]
durationMinutes: number
passingScore: number
isActive: boolean
status: 'draft' | 'published'
requiredForRoles: string[]
createdAt: string
updatedAt: string
@@ -205,6 +208,7 @@ export interface CourseCreateRequest {
description: string
category: CourseCategory
durationMinutes: number
passingScore?: number
requiredForRoles?: string[]
}
@@ -213,6 +217,8 @@ export interface CourseUpdateRequest {
description?: string
category?: CourseCategory
durationMinutes?: number
passingScore?: number
status?: 'draft' | 'published'
requiredForRoles?: string[]
}

View File

@@ -89,6 +89,10 @@ export async function updateModule(id: string, data: Record<string, unknown>): P
})
}
export async function deleteModule(id: string): Promise<void> {
return apiFetch(`/modules/${id}`, { method: 'DELETE' })
}
// =============================================================================
// MATRIX
// =============================================================================
@@ -177,6 +181,13 @@ export async function completeAssignment(id: string): Promise<{ status: string }
return apiFetch(`/assignments/${id}/complete`, { method: 'POST' })
}
export async function updateAssignment(id: string, data: { deadline?: string }): Promise<TrainingAssignment> {
return apiFetch<TrainingAssignment>(`/assignments/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
})
}
// =============================================================================
// QUIZ
// =============================================================================