import { useState, useEffect, useRef } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { useKlausur } from '../hooks/useKlausur' import { klausurApi, uploadStudentWork, StudentKlausur, klausurEHApi, LinkedEHInfo } from '../services/api' import EHUploadWizard from '../components/EHUploadWizard' // Grade calculation const GRADE_THRESHOLDS: Record = { 15: 95, 14: 90, 13: 85, 12: 80, 11: 75, 10: 70, 9: 65, 8: 60, 7: 55, 6: 50, 5: 45, 4: 40, 3: 33, 2: 27, 1: 20, 0: 0 } const GRADE_LABELS: Record = { 15: '1+', 14: '1', 13: '1-', 12: '2+', 11: '2', 10: '2-', 9: '3+', 8: '3', 7: '3-', 6: '4+', 5: '4', 4: '4-', 3: '5+', 2: '5', 1: '5-', 0: '6' } const CRITERIA = [ { key: 'inhalt', label: 'Inhaltliche Leistung', weight: 0.40 }, { key: 'struktur', label: 'Aufbau & Struktur', weight: 0.15 }, { key: 'stil', label: 'Ausdruck & Stil', weight: 0.15 }, { key: 'grammatik', label: 'Grammatik', weight: 0.15 }, { key: 'rechtschreibung', label: 'Rechtschreibung', weight: 0.15 } ] // Wizard steps type WizardStep = 'korrektur' | 'bewertung' | 'gutachten' function calculateGradePoints(percentage: number): number { for (const [points, threshold] of Object.entries(GRADE_THRESHOLDS).sort((a, b) => Number(b[0]) - Number(a[0]))) { if (percentage >= threshold) { return Number(points) } } return 0 } export default function KorrekturPage() { const { klausurId } = useParams<{ klausurId: string }>() const navigate = useNavigate() const { currentKlausur, currentStudent, selectKlausur, selectStudent, refreshAndSelectStudent, loading, error } = useKlausur() const fileInputRef = useRef(null) // Wizard state const [wizardStep, setWizardStep] = useState('korrektur') const [sidebarCollapsed, setSidebarCollapsed] = useState(false) // Upload state const [uploading, setUploading] = useState(false) const [uploadModalOpen, setUploadModalOpen] = useState(false) const [studentName, setStudentName] = useState('') const [selectedFile, setSelectedFile] = useState(null) const [classStudents, setClassStudents] = useState>([]) const [useStudentDropdown, setUseStudentDropdown] = useState(true) // Korrektur state (Step 1) const [korrekturNotes, setKorrekturNotes] = useState('') // Bewertung state (Step 2) const [localScores, setLocalScores] = useState>({}) const [savingCriteria, setSavingCriteria] = useState(false) // Gutachten state (Step 3) const [generatingGutachten, setGeneratingGutachten] = useState(false) const [localGutachten, setLocalGutachten] = useState<{ einleitung: string hauptteil: string fazit: string }>({ einleitung: '', hauptteil: '', fazit: '' }) const [savingGutachten, setSavingGutachten] = useState(false) const [finalizingStudent, setFinalizingStudent] = useState(false) // BYOEH state - Erwartungshorizont integration const [showEHPrompt, setShowEHPrompt] = useState(false) const [showEHWizard, setShowEHWizard] = useState(false) const [linkedEHs, setLinkedEHs] = useState([]) const [ehPromptDismissed, setEhPromptDismissed] = useState(false) const [_loadingEHs, setLoadingEHs] = useState(false) // Load klausur on mount useEffect(() => { if (klausurId) { selectKlausur(klausurId) } }, [klausurId, selectKlausur]) // Load class students when upload modal opens useEffect(() => { if (uploadModalOpen && currentKlausur?.class_id) { loadClassStudents(currentKlausur.class_id) } }, [uploadModalOpen, currentKlausur?.class_id]) const loadClassStudents = async (classId: string) => { try { const resp = await fetch(`/api/school/classes/${classId}/students`) if (resp.ok) { const data = await resp.json() setClassStudents(data) } } catch (e) { console.error('Failed to load class students:', e) } } // Load linked Erwartungshorizonte for this Klausur const loadLinkedEHs = async () => { if (!klausurId) return setLoadingEHs(true) try { const ehs = await klausurEHApi.getLinkedEH(klausurId) setLinkedEHs(ehs) } catch (e) { console.error('Failed to load linked EHs:', e) } finally { setLoadingEHs(false) } } // Load linked EHs when klausur changes useEffect(() => { if (klausurId) { loadLinkedEHs() } }, [klausurId]) // Show EH prompt after first student upload (if no EH is linked yet) useEffect(() => { // After upload is complete and modal is closed if ( currentKlausur && currentKlausur.students.length === 1 && linkedEHs.length === 0 && !ehPromptDismissed && !uploadModalOpen && !showEHWizard ) { // Check localStorage to see if prompt was already shown for this klausur const dismissedKey = `eh_prompt_dismissed_${klausurId}` if (!localStorage.getItem(dismissedKey)) { setShowEHPrompt(true) } } }, [currentKlausur?.students.length, linkedEHs.length, uploadModalOpen, showEHWizard, ehPromptDismissed]) // Handle EH prompt responses const handleEHPromptUpload = () => { setShowEHPrompt(false) setShowEHWizard(true) } const handleEHPromptDismiss = () => { setShowEHPrompt(false) setEhPromptDismissed(true) if (klausurId) { localStorage.setItem(`eh_prompt_dismissed_${klausurId}`, 'true') } } // Handle EH wizard completion const handleEHWizardComplete = async () => { setShowEHWizard(false) // Reload linked EHs await loadLinkedEHs() } // Sync local state with current student useEffect(() => { if (currentStudent) { const scores: Record = {} for (const c of CRITERIA) { scores[c.key] = currentStudent.criteria_scores?.[c.key]?.score ?? 0 } setLocalScores(scores) setLocalGutachten({ einleitung: currentStudent.gutachten?.einleitung || '', hauptteil: currentStudent.gutachten?.hauptteil || '', fazit: currentStudent.gutachten?.fazit || '' }) // Reset wizard to first step when selecting new student setWizardStep('korrektur') setKorrekturNotes('') } }, [currentStudent?.id]) const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files?.[0]) { setSelectedFile(e.target.files[0]) } } const handleUpload = async () => { if (!klausurId || !studentName || !selectedFile) return setUploading(true) try { const newStudent = await uploadStudentWork(klausurId, studentName, selectedFile) // Refresh klausur and auto-select the newly uploaded student await refreshAndSelectStudent(klausurId, newStudent.id) setUploadModalOpen(false) setStudentName('') setSelectedFile(null) } catch (e) { console.error('Upload failed:', e) alert('Fehler beim Hochladen') } finally { setUploading(false) } } const handleDeleteStudent = async (studentId: string, e: React.MouseEvent) => { e.stopPropagation() if (!confirm('Schuelerarbeit wirklich loeschen?')) return try { await klausurApi.deleteStudent(studentId) if (klausurId) { await selectKlausur(klausurId, true) } } catch (e) { console.error('Failed to delete student:', e) } } // Step 1: Complete Korrektur and go to Bewertung const handleKorrekturComplete = () => { setWizardStep('bewertung') } // Step 2: Save criteria scores const handleCriteriaChange = async (criterion: string, value: number) => { setLocalScores(prev => ({ ...prev, [criterion]: value })) if (!currentStudent) return setSavingCriteria(true) try { await klausurApi.updateCriteria(currentStudent.id, criterion, value) if (klausurId) { await selectKlausur(klausurId, true) } } catch (e) { console.error('Failed to update criteria:', e) } finally { setSavingCriteria(false) } } // Check if all criteria are filled const allCriteriaFilled = CRITERIA.every(c => (localScores[c.key] || 0) > 0) // Step 2: Complete Bewertung and go to Gutachten const handleBewertungComplete = () => { if (!allCriteriaFilled) { alert('Bitte alle Bewertungskriterien ausfuellen') return } setWizardStep('gutachten') } // Step 3: Generate Gutachten const handleGenerateGutachten = async () => { if (!currentStudent) return setGeneratingGutachten(true) try { const generated = await klausurApi.generateGutachten(currentStudent.id, { include_strengths: true, include_weaknesses: true, tone: 'formal' }) setLocalGutachten({ einleitung: generated.einleitung, hauptteil: generated.hauptteil, fazit: generated.fazit }) } catch (e) { console.error('Failed to generate gutachten:', e) alert('Fehler bei der KI-Generierung') } finally { setGeneratingGutachten(false) } } // Step 3: Save Gutachten const handleSaveGutachten = async () => { if (!currentStudent) return setSavingGutachten(true) try { await klausurApi.updateGutachten(currentStudent.id, { einleitung: localGutachten.einleitung, hauptteil: localGutachten.hauptteil, fazit: localGutachten.fazit }) if (klausurId) { await selectKlausur(klausurId, true) } } catch (e) { console.error('Failed to save gutachten:', e) alert('Fehler beim Speichern des Gutachtens') } finally { setSavingGutachten(false) } } // Finalize the correction const handleFinalizeStudent = async () => { if (!currentStudent) return if (!confirm('Bewertung wirklich abschliessen? Dies kann nicht rueckgaengig gemacht werden.')) return setFinalizingStudent(true) try { await klausurApi.finalizeStudent(currentStudent.id) if (klausurId) { await selectKlausur(klausurId, true) } } catch (e) { console.error('Failed to finalize:', e) alert('Fehler beim Abschliessen der Bewertung') } finally { setFinalizingStudent(false) } } const calculateTotalPercentage = (): number => { let total = 0 for (const c of CRITERIA) { total += (localScores[c.key] || 0) * c.weight } return Math.round(total) } if (loading && !currentKlausur) { return (
Klausur wird geladen...
) } if (error) { return (
Fehler: {error}
) } if (!currentKlausur) { return (
Klausur nicht gefunden
) } const totalPercentage = calculateTotalPercentage() const gradePoints = calculateGradePoints(totalPercentage) // Render right panel content based on wizard step const renderWizardContent = () => { if (!currentStudent) { return (
📋
Waehlen Sie eine Schuelerarbeit aus, um die Bewertung zu beginnen
) } switch (wizardStep) { case 'korrektur': return ( <> {/* Step indicator */}
1 Korrektur
2 Bewertung
3 Gutachten
✏️ Korrektur durchfuehren

Lesen Sie die Arbeit sorgfaeltig und machen Sie Anmerkungen direkt im Dokument. Notieren Sie hier Ihre wichtigsten Beobachtungen.