/** * KorrekturPage — orchestrator for the Klausur correction workflow. * * Split into sub-components: * KorrekturConstants — grades, criteria, types * KorrekturSidebar — collapsible left sidebar * KorrekturDocumentViewer — center document display * KorrekturWizardSteps — wizard step panels (korrektur, bewertung, gutachten) * KorrekturModals — upload + EH prompt modals */ import { useState, useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { useKlausur } from '../hooks/useKlausur' import { klausurApi, uploadStudentWork, klausurEHApi, LinkedEHInfo } from '../services/api' import EHUploadWizard from '../components/EHUploadWizard' import KorrekturSidebar from '../components/KorrekturSidebar' import KorrekturDocumentViewer from '../components/KorrekturDocumentViewer' import { KorrekturStep, BewertungStep, GutachtenStep } from '../components/KorrekturWizardSteps' import { UploadModal, EHPromptModal } from '../components/KorrekturModals' import { CRITERIA, WizardStep, calculateGradePoints } from './KorrekturConstants' export default function KorrekturPage() { const { klausurId } = useParams<{ klausurId: string }>() const navigate = useNavigate() const { currentKlausur, currentStudent, selectKlausur, selectStudent, refreshAndSelectStudent, loading, error } = useKlausur() // 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>([]) // 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: '', hauptteil: '', fazit: '' }) const [savingGutachten, setSavingGutachten] = useState(false) const [finalizingStudent, setFinalizingStudent] = useState(false) // BYOEH state const [showEHPrompt, setShowEHPrompt] = useState(false) const [showEHWizard, setShowEHWizard] = useState(false) const [linkedEHs, setLinkedEHs] = useState([]) const [ehPromptDismissed, setEhPromptDismissed] = 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) { fetch(`/api/school/classes/${currentKlausur.class_id}/students`) .then(r => r.ok ? r.json() : []) .then(setClassStudents) .catch(() => {}) } }, [uploadModalOpen, currentKlausur?.class_id]) // Load linked EHs const loadLinkedEHs = async () => { if (!klausurId) return try { setLinkedEHs(await klausurEHApi.getLinkedEH(klausurId)) } catch { /* ignore */ } } useEffect(() => { if (klausurId) loadLinkedEHs() }, [klausurId]) // Show EH prompt after first student upload useEffect(() => { if (currentKlausur && currentKlausur.students.length === 1 && linkedEHs.length === 0 && !ehPromptDismissed && !uploadModalOpen && !showEHWizard) { if (!localStorage.getItem(`eh_prompt_dismissed_${klausurId}`)) setShowEHPrompt(true) } }, [currentKlausur?.students.length, linkedEHs.length, uploadModalOpen, showEHWizard, ehPromptDismissed]) // 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 || '' }) setWizardStep('korrektur') setKorrekturNotes('') } }, [currentStudent?.id]) // --- Handlers --- const handleUpload = async () => { if (!klausurId || !studentName || !selectedFile) return setUploading(true) try { const newStudent = await uploadStudentWork(klausurId, studentName, selectedFile) await refreshAndSelectStudent(klausurId, newStudent.id) setUploadModalOpen(false); setStudentName(''); setSelectedFile(null) } catch { 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 { /* ignore */ } } 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 { /* ignore */ } finally { setSavingCriteria(false) } } const allCriteriaFilled = CRITERIA.every(c => (localScores[c.key] || 0) > 0) const handleGenerateGutachten = async () => { if (!currentStudent) return setGeneratingGutachten(true) try { const g = await klausurApi.generateGutachten(currentStudent.id, { include_strengths: true, include_weaknesses: true, tone: 'formal' }) setLocalGutachten({ einleitung: g.einleitung, hauptteil: g.hauptteil, fazit: g.fazit }) } catch { alert('Fehler bei der KI-Generierung') } finally { setGeneratingGutachten(false) } } const handleSaveGutachten = async () => { if (!currentStudent) return setSavingGutachten(true) try { await klausurApi.updateGutachten(currentStudent.id, localGutachten); if (klausurId) await selectKlausur(klausurId, true) } catch { alert('Fehler beim Speichern') } finally { setSavingGutachten(false) } } const handleFinalizeStudent = async () => { if (!currentStudent || !confirm('Bewertung wirklich abschliessen?')) return setFinalizingStudent(true) try { await klausurApi.finalizeStudent(currentStudent.id); if (klausurId) await selectKlausur(klausurId, true) } catch { alert('Fehler beim Abschliessen') } 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) } const handleEHPromptDismiss = () => { setShowEHPrompt(false); setEhPromptDismissed(true) if (klausurId) localStorage.setItem(`eh_prompt_dismissed_${klausurId}`, 'true') } // --- Loading/Error states --- 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) // --- Wizard content --- const renderWizardContent = () => { if (!currentStudent) { return (
{'\uD83D\uDCCB'}
Waehlen Sie eine Schuelerarbeit aus, um die Bewertung zu beginnen
) } switch (wizardStep) { case 'korrektur': return setWizardStep('bewertung')} /> case 'bewertung': return setWizardStep('korrektur')} onComplete={() => { if (!allCriteriaFilled) { alert('Bitte alle Bewertungskriterien ausfuellen'); return }; setWizardStep('gutachten') }} /> case 'gutachten': return setLocalGutachten(prev => ({ ...prev, [f]: v }))} onGenerate={handleGenerateGutachten} onSave={handleSaveGutachten} onFinalize={handleFinalizeStudent} onBack={() => setWizardStep('bewertung')} /> } } return (
setSidebarCollapsed(!sidebarCollapsed)} klausurTitle={currentKlausur.title} klausurModus={currentKlausur.modus} students={currentKlausur.students} currentStudentId={currentStudent?.id} onSelectStudent={selectStudent} onDeleteStudent={handleDeleteStudent} onUploadClick={() => setUploadModalOpen(true)} />
{renderWizardContent()}
{linkedEHs.length > 0 && (
setShowEHWizard(true)}> {'\uD83D\uDCCB'} {linkedEHs.length} Erwartungshorizont{linkedEHs.length > 1 ? 'e' : ''} verknuepft
)} setUploadModalOpen(false)} studentName={studentName} onStudentNameChange={setStudentName} classStudents={classStudents} onUpload={handleUpload} uploading={uploading} selectedFile={selectedFile} onFileSelect={(e) => { if (e.target.files?.[0]) setSelectedFile(e.target.files[0]) }} /> { setShowEHPrompt(false); setShowEHWizard(true) }} onDismiss={handleEHPromptDismiss} /> {showEHWizard && currentKlausur && ( setShowEHWizard(false)} onComplete={async () => { setShowEHWizard(false); await loadLinkedEHs() }} defaultSubject={currentKlausur.subject} defaultYear={currentKlausur.year} klausurId={klausurId} /> )}
) }