'use client' import { useEffect, useState, useCallback } from 'react' import { getAssignments, getContent, getModuleMedia, getQuiz, submitQuiz, startAssignment, generateCertificate, listCertificates, downloadCertificatePDF, getMediaStreamURL, getInteractiveManifest, completeAssignment, } from '@/lib/sdk/training/api' import type { TrainingAssignment, ModuleContent, TrainingMedia, QuizSubmitResponse, InteractiveVideoManifest, } from '@/lib/sdk/training/types' import { STATUS_LABELS, STATUS_COLORS, REGULATION_LABELS, } from '@/lib/sdk/training/types' import InteractiveVideoPlayer from '@/components/training/InteractiveVideoPlayer' type Tab = 'assignments' | 'content' | 'quiz' | 'certificates' interface QuizQuestionItem { id: string question: string options: string[] difficulty: string } export default function LearnerPage() { const [activeTab, setActiveTab] = useState('assignments') const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Assignments const [assignments, setAssignments] = useState([]) // Content const [selectedAssignment, setSelectedAssignment] = useState(null) const [content, setContent] = useState(null) const [media, setMedia] = useState([]) // Quiz const [questions, setQuestions] = useState([]) const [answers, setAnswers] = useState>({}) const [quizResult, setQuizResult] = useState(null) const [quizSubmitting, setQuizSubmitting] = useState(false) const [quizTimer, setQuizTimer] = useState(0) const [quizActive, setQuizActive] = useState(false) // Certificates const [certificates, setCertificates] = useState([]) const [certGenerating, setCertGenerating] = useState(false) // Interactive Video const [interactiveManifest, setInteractiveManifest] = useState(null) // User simulation const [userId] = useState('00000000-0000-0000-0000-000000000001') const loadAssignments = useCallback(async () => { setLoading(true) try { const data = await getAssignments({ user_id: userId, limit: 100 }) setAssignments(data.assignments || []) } catch (e) { setError(e instanceof Error ? e.message : 'Fehler beim Laden') } finally { setLoading(false) } }, [userId]) const loadCertificates = useCallback(async () => { try { const data = await listCertificates() setCertificates(data.certificates || []) } catch { // Certificates may not exist yet } }, []) useEffect(() => { loadAssignments() loadCertificates() }, [loadAssignments, loadCertificates]) // Quiz timer useEffect(() => { if (!quizActive) return const interval = setInterval(() => setQuizTimer(t => t + 1), 1000) return () => clearInterval(interval) }, [quizActive]) async function loadInteractiveManifest(moduleId: string, assignmentId: string) { try { const manifest = await getInteractiveManifest(moduleId, assignmentId) if (manifest && manifest.checkpoints && manifest.checkpoints.length > 0) { setInteractiveManifest(manifest) } else { setInteractiveManifest(null) } } catch { setInteractiveManifest(null) } } async function handleStartAssignment(assignment: TrainingAssignment) { try { await startAssignment(assignment.id) setSelectedAssignment({ ...assignment, status: 'in_progress' }) // Load content const [contentData, mediaData] = await Promise.all([ getContent(assignment.module_id).catch(() => null), getModuleMedia(assignment.module_id).catch(() => ({ media: [] })), ]) setContent(contentData) setMedia(mediaData.media || []) await loadInteractiveManifest(assignment.module_id, assignment.id) setActiveTab('content') loadAssignments() } catch (e) { setError(e instanceof Error ? e.message : 'Fehler beim Starten') } } async function handleResumeContent(assignment: TrainingAssignment) { setSelectedAssignment(assignment) try { const [contentData, mediaData] = await Promise.all([ getContent(assignment.module_id).catch(() => null), getModuleMedia(assignment.module_id).catch(() => ({ media: [] })), ]) setContent(contentData) setMedia(mediaData.media || []) await loadInteractiveManifest(assignment.module_id, assignment.id) setActiveTab('content') } catch (e) { setError(e instanceof Error ? e.message : 'Fehler beim Laden') } } async function handleAllCheckpointsPassed() { if (!selectedAssignment) return try { await completeAssignment(selectedAssignment.id) setSelectedAssignment({ ...selectedAssignment, status: 'completed' }) loadAssignments() } catch { // Assignment completion may already be handled } } async function handleStartQuiz() { if (!selectedAssignment) return try { const data = await getQuiz(selectedAssignment.module_id) setQuestions(data.questions || []) setAnswers({}) setQuizResult(null) setQuizTimer(0) setQuizActive(true) setActiveTab('quiz') } catch (e) { setError(e instanceof Error ? e.message : 'Fehler beim Quiz-Laden') } } async function handleSubmitQuiz() { if (!selectedAssignment || questions.length === 0) return setQuizSubmitting(true) setQuizActive(false) try { const answerList = questions.map(q => ({ question_id: q.id, selected_index: answers[q.id] ?? -1, })) const result = await submitQuiz(selectedAssignment.module_id, { assignment_id: selectedAssignment.id, answers: answerList, duration_seconds: quizTimer, }) setQuizResult(result) loadAssignments() } catch (e) { setError(e instanceof Error ? e.message : 'Quiz-Abgabe fehlgeschlagen') } finally { setQuizSubmitting(false) } } async function handleGenerateCertificate(assignmentId: string) { setCertGenerating(true) try { const data = await generateCertificate(assignmentId) if (data.certificate_id) { const blob = await downloadCertificatePDF(data.certificate_id) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `zertifikat-${data.certificate_id.substring(0, 8)}.pdf` a.click() URL.revokeObjectURL(url) } loadAssignments() loadCertificates() } catch (e) { setError(e instanceof Error ? e.message : 'Zertifikat-Erstellung fehlgeschlagen') } finally { setCertGenerating(false) } } async function handleDownloadPDF(certId: string) { try { const blob = await downloadCertificatePDF(certId) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `zertifikat-${certId.substring(0, 8)}.pdf` a.click() URL.revokeObjectURL(url) } catch (e) { setError(e instanceof Error ? e.message : 'PDF-Download fehlgeschlagen') } } function simpleMarkdownToHtml(md: string): string { return md .replace(/^### (.+)$/gm, '

$1

') .replace(/^## (.+)$/gm, '

$1

') .replace(/^# (.+)$/gm, '

$1

') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/^- (.+)$/gm, '
  • $1
  • ') .replace(/^(\d+)\. (.+)$/gm, '
  • $2
  • ') .replace(/\n\n/g, '

    ') } function formatTimer(seconds: number): string { const m = Math.floor(seconds / 60) const s = seconds % 60 return `${m}:${s.toString().padStart(2, '0')}` } const tabs: { key: Tab; label: string }[] = [ { key: 'assignments', label: 'Meine Schulungen' }, { key: 'content', label: 'Schulungsinhalt' }, { key: 'quiz', label: 'Quiz' }, { key: 'certificates', label: 'Zertifikate' }, ] return (

    Learner Portal

    Absolvieren Sie Ihre Compliance-Schulungen

    {error && (
    {error}
    )} {/* Tabs */}
    {tabs.map(tab => ( ))}
    {/* Tab: Meine Schulungen */} {activeTab === 'assignments' && (
    {loading ? (
    Lade Schulungen...
    ) : assignments.length === 0 ? (
    Keine Schulungen zugewiesen
    ) : (
    {assignments.map(a => (

    {a.module_title || a.module_code}

    {STATUS_LABELS[a.status] || a.status}

    Code: {a.module_code} | Deadline: {new Date(a.deadline).toLocaleDateString('de-DE')} {a.quiz_score != null && ` | Quiz: ${Math.round(a.quiz_score)}%`}

    {/* Progress bar */}

    {a.progress_percent}% abgeschlossen

    {a.status === 'pending' && ( )} {a.status === 'in_progress' && ( )} {a.status === 'completed' && a.quiz_passed && !a.certificate_id && ( )} {a.certificate_id && ( )}
    ))}
    )}
    )} {/* Tab: Schulungsinhalt */} {activeTab === 'content' && (
    {!selectedAssignment ? (
    Waehlen Sie eine Schulung aus dem Tab "Meine Schulungen"
    ) : (

    {selectedAssignment.module_title}

    {/* Interactive Video Player */} {interactiveManifest && selectedAssignment && (

    Interaktive Video-Schulung

    Interaktiv
    )} {/* Media players (standard audio/video) */} {media.length > 0 && (
    {media.filter(m => m.media_type === 'audio' && m.status === 'completed').map(m => (

    Audio-Schulung

    ))} {media.filter(m => m.media_type === 'video' && m.status === 'completed' && m.generated_by !== 'tts_ffmpeg_interactive').map(m => (

    Video-Schulung

    ))}
    )} {/* Content body */} {content ? (
    ) : (
    Kein Schulungsinhalt verfuegbar
    )}
    )}
    )} {/* Tab: Quiz */} {activeTab === 'quiz' && (
    {questions.length === 0 ? (
    Starten Sie ein Quiz aus dem Schulungsinhalt-Tab
    ) : quizResult ? ( /* Quiz Results */
    {quizResult.passed ? '\u2705' : '\u274C'}

    {quizResult.passed ? 'Bestanden!' : 'Nicht bestanden'}

    {quizResult.correct_count} von {quizResult.total_count} richtig ({Math.round(quizResult.score)}%)

    Bestehensgrenze: {quizResult.threshold}% | Zeit: {formatTimer(quizTimer)}

    {quizResult.passed && selectedAssignment && !selectedAssignment.certificate_id && ( )} {!quizResult.passed && ( )}
    ) : ( /* Quiz Questions */

    Quiz — {selectedAssignment?.module_title}

    {formatTimer(quizTimer)}
    {questions.map((q, idx) => (

    Frage {idx + 1}. {q.question}

    {q.options.map((opt, oi) => ( ))}
    ))}
    )}
    )} {/* Tab: Zertifikate */} {activeTab === 'certificates' && (
    {certificates.length === 0 ? (
    Noch keine Zertifikate vorhanden. Schliessen Sie eine Schulung mit Quiz ab.
    ) : (
    {certificates.map(cert => (

    {cert.module_title}

    Bestanden

    Mitarbeiter: {cert.user_name}

    Abschluss: {cert.completed_at ? new Date(cert.completed_at).toLocaleDateString('de-DE') : '-'}

    {cert.quiz_score != null &&

    Ergebnis: {Math.round(cert.quiz_score)}%

    }

    ID: {cert.certificate_id?.substring(0, 12)}

    {cert.certificate_id && ( )}
    ))}
    )}
    )}
    ) }