[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>
This commit is contained in:
239
website/components/klausur-korrektur/useKlausurList.ts
Normal file
239
website/components/klausur-korrektur/useKlausurList.ts
Normal file
@@ -0,0 +1,239 @@
|
||||
'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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user