Files
breakpilot-lehrer/website/components/klausur-korrektur/useKlausurList.ts
Benjamin Admin 6811264756 [split-required] Split final batch of monoliths >1000 LOC
Python (6 files in klausur-service):
- rbac.py (1,132 → 4), admin_api.py (1,012 → 4)
- routes/eh.py (1,111 → 4), ocr_pipeline_geometry.py (1,105 → 5)

Python (2 files in backend-lehrer):
- unit_api.py (1,226 → 6), game_api.py (1,129 → 5)

Website (6 page files):
- 4x klausur-korrektur pages (1,249-1,328 LOC each) → shared components
  in website/components/klausur-korrektur/ (17 shared files)
- companion (1,057 → 10), magic-help (1,017 → 8)

All re-export barrels preserve backward compatibility.
Zero import errors verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 23:17:30 +02:00

240 lines
8.7 KiB
TypeScript

'use client'
/**
* Custom hook for the Klausur-Korrektur list page.
* Encapsulates all state and data fetching logic.
*/
import { useState, useEffect, useCallback } from 'react'
import type { Klausur, GradeInfo } from '../../app/admin/klausur-korrektur/types'
import type {
TabId, CreateKlausurForm, VorabiturEHForm, EHTemplate, DirektuploadForm,
} from './list-types'
import { API_BASE } from './list-types'
interface UseKlausurListArgs {
/** Base route path for navigation, e.g. '/admin/klausur-korrektur' or '/lehrer/klausur-korrektur' */
basePath: string
}
export function useKlausurList({ basePath }: UseKlausurListArgs) {
const [activeTab, setActiveTab] = useState<TabId>(() => {
if (typeof window !== 'undefined') {
const hasVisited = localStorage.getItem('klausur_korrektur_visited')
return hasVisited ? 'klausuren' : 'willkommen'
}
return 'willkommen'
})
const [klausuren, setKlausuren] = useState<Klausur[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [creating, setCreating] = useState(false)
const [gradeInfo, setGradeInfo] = useState<GradeInfo | null>(null)
// Vorabitur templates
const [templates, setTemplates] = useState<EHTemplate[]>([])
const [loadingTemplates, setLoadingTemplates] = useState(false)
// Create form state
const [form, setForm] = useState<CreateKlausurForm>({
title: '', subject: 'Deutsch', year: new Date().getFullYear(),
semester: 'Abitur', modus: 'abitur',
})
const [ehForm, setEhForm] = useState<VorabiturEHForm>({
aufgabentyp: '', titel: '', text_titel: '', text_autor: '', aufgabenstellung: '',
})
// Direktupload form
const [direktForm, setDirektForm] = useState<DirektuploadForm>({
files: [], ehFile: null, ehText: '', aufgabentyp: '',
klausurTitle: `Schnellkorrektur ${new Date().toLocaleDateString('de-DE')}`,
})
const [direktStep, setDirektStep] = useState<1 | 2 | 3>(1)
const [uploading, setUploading] = useState(false)
// Fetch klausuren
const fetchKlausuren = useCallback(async () => {
try {
setLoading(true)
const res = await fetch(`${API_BASE}/api/v1/klausuren`)
if (res.ok) {
const data = await res.json()
setKlausuren(Array.isArray(data) ? data : data.klausuren || [])
setError(null)
} else {
setError(`Fehler beim Laden: ${res.status}`)
}
} catch (err) {
console.error('Failed to fetch klausuren:', err)
setError('Verbindung zum Klausur-Service fehlgeschlagen')
} finally {
setLoading(false)
}
}, [])
// Fetch grade info
const fetchGradeInfo = useCallback(async () => {
try {
const res = await fetch(`${API_BASE}/api/v1/grade-info`)
if (res.ok) setGradeInfo(await res.json())
} catch (err) {
console.error('Failed to fetch grade info:', err)
}
}, [])
// Fetch templates
const fetchTemplates = useCallback(async () => {
try {
setLoadingTemplates(true)
const res = await fetch(`${API_BASE}/api/v1/vorabitur/templates`)
if (res.ok) {
const data = await res.json()
setTemplates(data.templates || [])
}
} catch (err) {
console.error('Failed to fetch templates:', err)
} finally {
setLoadingTemplates(false)
}
}, [])
useEffect(() => { fetchKlausuren(); fetchGradeInfo() }, [fetchKlausuren, fetchGradeInfo])
useEffect(() => {
if (form.modus === 'vorabitur' && templates.length === 0) fetchTemplates()
}, [form.modus, templates.length, fetchTemplates])
const markAsVisited = () => {
if (typeof window !== 'undefined') localStorage.setItem('klausur_korrektur_visited', 'true')
}
// Create new Klausur
const handleCreateKlausur = async (e: React.FormEvent) => {
e.preventDefault()
if (!form.title.trim()) { setError('Bitte einen Titel eingeben'); return }
if (form.modus === 'vorabitur') {
if (!ehForm.aufgabentyp) { setError('Bitte einen Aufgabentyp auswaehlen'); return }
if (!ehForm.aufgabenstellung.trim()) { setError('Bitte die Aufgabenstellung eingeben'); return }
}
try {
setCreating(true)
const res = await fetch(`${API_BASE}/api/v1/klausuren`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form),
})
if (!res.ok) {
const errorData = await res.json()
setError(errorData.detail || 'Fehler beim Erstellen'); return
}
const newKlausur = await res.json()
if (form.modus === 'vorabitur') {
const ehRes = await fetch(`${API_BASE}/api/v1/klausuren/${newKlausur.id}/vorabitur-eh`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
aufgabentyp: ehForm.aufgabentyp, titel: ehForm.titel || `EH: ${form.title}`,
text_titel: ehForm.text_titel || null, text_autor: ehForm.text_autor || null,
aufgabenstellung: ehForm.aufgabenstellung,
}),
})
if (!ehRes.ok) {
console.error('Failed to create EH:', await ehRes.text())
setError('Klausur erstellt, aber Erwartungshorizont konnte nicht erstellt werden.')
}
}
setKlausuren(prev => [newKlausur, ...prev])
setForm({ title: '', subject: 'Deutsch', year: new Date().getFullYear(), semester: 'Abitur', modus: 'abitur' })
setEhForm({ aufgabentyp: '', titel: '', text_titel: '', text_autor: '', aufgabenstellung: '' })
setActiveTab('klausuren')
if (!error) setError(null)
} catch (err) {
console.error('Failed to create klausur:', err)
setError('Fehler beim Erstellen der Klausur')
} finally {
setCreating(false)
}
}
// Delete Klausur
const handleDeleteKlausur = async (id: string) => {
if (!confirm('Klausur wirklich loeschen? Alle Studentenarbeiten werden ebenfalls geloescht.')) return
try {
const res = await fetch(`${API_BASE}/api/v1/klausuren/${id}`, { method: 'DELETE' })
if (res.ok) setKlausuren(prev => prev.filter(k => k.id !== id))
else setError('Fehler beim Loeschen')
} catch (err) {
console.error('Failed to delete klausur:', err)
setError('Fehler beim Loeschen der Klausur')
}
}
// Direktupload
const handleDirektupload = async () => {
if (direktForm.files.length === 0) { setError('Bitte mindestens eine Arbeit hochladen'); return }
try {
setUploading(true)
const klausurRes = await fetch(`${API_BASE}/api/v1/klausuren`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: direktForm.klausurTitle, subject: 'Deutsch',
year: new Date().getFullYear(), semester: 'Vorabitur', modus: 'vorabitur',
}),
})
if (!klausurRes.ok) {
const err = await klausurRes.json()
throw new Error(err.detail || 'Klausur erstellen fehlgeschlagen')
}
const newKlausur = await klausurRes.json()
if (direktForm.ehText.trim() || direktForm.aufgabentyp) {
const ehRes = await fetch(`${API_BASE}/api/v1/klausuren/${newKlausur.id}/vorabitur-eh`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
aufgabentyp: direktForm.aufgabentyp || 'textanalyse_pragmatisch',
titel: `EH: ${direktForm.klausurTitle}`,
aufgabenstellung: direktForm.ehText || 'Individuelle Aufgabenstellung',
}),
})
if (!ehRes.ok) console.error('EH creation failed, continuing with upload')
}
for (let i = 0; i < direktForm.files.length; i++) {
const file = direktForm.files[i]
const formData = new FormData()
formData.append('file', file)
formData.append('anonym_id', `Arbeit-${i + 1}`)
const uploadRes = await fetch(`${API_BASE}/api/v1/klausuren/${newKlausur.id}/students`, {
method: 'POST', body: formData,
})
if (!uploadRes.ok) console.error(`Upload failed for file ${i + 1}:`, file.name)
}
setKlausuren(prev => [newKlausur, ...prev])
markAsVisited()
window.location.href = `${basePath}/${newKlausur.id}`
} catch (err) {
console.error('Direktupload failed:', err)
setError(err instanceof Error ? err.message : 'Fehler beim Direktupload')
} finally {
setUploading(false)
}
}
return {
// Data
klausuren, gradeInfo, templates,
// UI state
activeTab, loading, error, creating, loadingTemplates,
form, ehForm, direktForm, direktStep, uploading,
// Setters
setActiveTab, setError, setForm, setEhForm, setDirektForm, setDirektStep,
// Actions
markAsVisited, handleCreateKlausur, handleDeleteKlausur, handleDirektupload,
// Route config
basePath,
}
}