'use client' import { useState, useEffect, useCallback, useRef } from 'react' import { useRouter, useParams } from 'next/navigation' import { useTheme } from '@/lib/ThemeContext' import { Sidebar } from '@/components/Sidebar' import { ThemeToggle } from '@/components/ThemeToggle' import { LanguageDropdown } from '@/components/LanguageDropdown' import { QRCodeUpload, UploadedFile } from '@/components/QRCodeUpload' import { korrekturApi } from '@/lib/korrektur/api' import type { Klausur, StudentWork, StudentStatus } from '../types' import { STATUS_COLORS, STATUS_LABELS, getGradeLabel } from '../types' // LocalStorage Key for upload session const SESSION_ID_KEY = 'bp_korrektur_student_session' // ============================================================================= // GLASS CARD // ============================================================================= interface GlassCardProps { children: React.ReactNode className?: string onClick?: () => void size?: 'sm' | 'md' | 'lg' delay?: number } function GlassCard({ children, className = '', onClick, size = 'md', delay = 0, isDark = true }: GlassCardProps & { isDark?: boolean }) { const [isVisible, setIsVisible] = useState(false) const [isHovered, setIsHovered] = useState(false) useEffect(() => { const timer = setTimeout(() => setIsVisible(true), delay) return () => clearTimeout(timer) }, [delay]) const sizeClasses = { sm: 'p-4', md: 'p-5', lg: 'p-6', } return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={onClick} > {children}
) } // ============================================================================= // STUDENT CARD // ============================================================================= interface StudentCardProps { student: StudentWork index: number onClick: () => void delay?: number isDark?: boolean } function StudentCard({ student, index, onClick, delay = 0, isDark = true }: StudentCardProps) { const statusColor = STATUS_COLORS[student.status] || '#6b7280' const statusLabel = STATUS_LABELS[student.status] || student.status const hasGrade = student.status === 'COMPLETED' || student.status === 'FIRST_EXAMINER' || student.status === 'SECOND_EXAMINER' return (
{/* Index/Number */}
{index + 1}
{/* Info */}

{student.anonym_id}

{statusLabel} {hasGrade && student.grade_points > 0 && ( {student.grade_points} P ({getGradeLabel(student.grade_points)}) )}
{/* Arrow */}
) } // ============================================================================= // UPLOAD MODAL // ============================================================================= interface UploadModalProps { isOpen: boolean onClose: () => void onUpload: (files: File[], anonymIds: string[]) => void isUploading: boolean } function UploadModal({ isOpen, onClose, onUpload, isUploading }: UploadModalProps) { const [files, setFiles] = useState([]) const [anonymIds, setAnonymIds] = useState([]) const fileInputRef = useRef(null) if (!isOpen) return null const handleFileSelect = (selectedFiles: FileList | null) => { if (!selectedFiles) return const newFiles = Array.from(selectedFiles) setFiles((prev) => [...prev, ...newFiles]) // Generate default anonym IDs setAnonymIds((prev) => [ ...prev, ...newFiles.map((_, i) => `Arbeit-${prev.length + i + 1}`), ]) } const handleDrop = (e: React.DragEvent) => { e.preventDefault() handleFileSelect(e.dataTransfer.files) } const removeFile = (index: number) => { setFiles((prev) => prev.filter((_, i) => i !== index)) setAnonymIds((prev) => prev.filter((_, i) => i !== index)) } const updateAnonymId = (index: number, value: string) => { setAnonymIds((prev) => { const updated = [...prev] updated[index] = value return updated }) } const handleSubmit = () => { if (files.length > 0) { onUpload(files, anonymIds) } } return (

Arbeiten hochladen

{/* Drop Zone */}
e.preventDefault()} onClick={() => fileInputRef.current?.click()} > handleFileSelect(e.target.files)} className="hidden" />

Dateien hierher ziehen

oder klicken zum Auswaehlen

{/* File List */} {files.length > 0 && (
{files.map((file, index) => (
{file.type.startsWith('image/') ? '🖼️' : '📄'}

{file.name}

updateAnonymId(index, e.target.value)} placeholder="Anonym-ID" className="mt-1 w-full px-2 py-1 rounded bg-white/10 border border-white/10 text-white text-sm placeholder-white/40 focus:outline-none focus:border-purple-500" />
))}
)} {/* Actions */}
) } // ============================================================================= // MAIN PAGE // ============================================================================= export default function KlausurDetailPage() { const { isDark } = useTheme() const router = useRouter() const params = useParams() const klausurId = params.klausurId as string // State const [klausur, setKlausur] = useState(null) const [students, setStudents] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) // Modal states const [showUploadModal, setShowUploadModal] = useState(false) const [showQRModal, setShowQRModal] = useState(false) const [isUploading, setIsUploading] = useState(false) const [uploadSessionId, setUploadSessionId] = useState('') // Initialize session ID useEffect(() => { let storedSessionId = localStorage.getItem(SESSION_ID_KEY) if (!storedSessionId) { storedSessionId = `student-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` localStorage.setItem(SESSION_ID_KEY, storedSessionId) } setUploadSessionId(storedSessionId) }, []) // Load data const loadData = useCallback(async () => { if (!klausurId) return setIsLoading(true) setError(null) try { const [klausurData, studentsData] = await Promise.all([ korrekturApi.getKlausur(klausurId), korrekturApi.getStudents(klausurId), ]) setKlausur(klausurData) setStudents(studentsData) } catch (err) { console.error('Failed to load data:', err) setError(err instanceof Error ? err.message : 'Laden fehlgeschlagen') } finally { setIsLoading(false) } }, [klausurId]) useEffect(() => { loadData() }, [loadData]) // Handle upload const handleUpload = async (files: File[], anonymIds: string[]) => { setIsUploading(true) try { for (let i = 0; i < files.length; i++) { await korrekturApi.uploadStudentWork(klausurId, files[i], anonymIds[i]) } setShowUploadModal(false) loadData() // Refresh the list } catch (err) { console.error('Upload failed:', err) setError(err instanceof Error ? err.message : 'Upload fehlgeschlagen') } finally { setIsUploading(false) } } // Calculate progress const completedCount = students.filter(s => s.status === 'COMPLETED').length const progress = students.length > 0 ? Math.round((completedCount / students.length) * 100) : 0 return (
{/* Animated Background Blobs */}
{/* Sidebar */}
{/* Main Content */}
{/* Header */}

{klausur?.title || 'Klausur'}

{klausur ? `${klausur.subject} ${klausur.semester} ${klausur.year}` : ''}

{/* Stats Row */} {!isLoading && klausur && (

{students.length}

Arbeiten

{completedCount}

Abgeschlossen

{students.length - completedCount}

Offen

{progress}%

Fortschritt

)} {/* Progress Bar */} {!isLoading && students.length > 0 && (
Gesamtfortschritt {completedCount}/{students.length} korrigiert
)} {/* Error Display */} {error && (
{error}
)} {/* Loading */} {isLoading && (
)} {/* Action Buttons */} {!isLoading && (
{students.length > 0 && ( )}
)} {/* Students List */} {!isLoading && students.length === 0 && (

Keine Arbeiten vorhanden

Laden Sie Schuelerarbeiten hoch, um mit der Korrektur zu beginnen.

)} {!isLoading && students.length > 0 && (
{students.map((student, index) => ( router.push(`/korrektur/${klausurId}/${student.id}`)} delay={350 + index * 30} isDark={isDark} /> ))}
)}
{/* Upload Modal */} setShowUploadModal(false)} onUpload={handleUpload} isUploading={isUploading} /> {/* QR Code Modal */} {showQRModal && (
setShowQRModal(false)} />
setShowQRModal(false)} onFilesChanged={(files) => { // Handle mobile uploaded files if (files.length > 0) { // Could auto-process the files here } }} />
)}
) }