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
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:
@@ -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(),
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user