[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:
208
website/components/klausur-korrektur/ErstellenTab.tsx
Normal file
208
website/components/klausur-korrektur/ErstellenTab.tsx
Normal file
@@ -0,0 +1,208 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Create new Klausur form tab.
|
||||
* Supports both Abitur and Vorabitur modes with EH template selection.
|
||||
*/
|
||||
|
||||
import type { TabId, CreateKlausurForm, VorabiturEHForm, EHTemplate } from './list-types'
|
||||
|
||||
interface ErstellenTabProps {
|
||||
form: CreateKlausurForm
|
||||
ehForm: VorabiturEHForm
|
||||
templates: EHTemplate[]
|
||||
creating: boolean
|
||||
loadingTemplates: boolean
|
||||
onFormChange: (form: CreateKlausurForm) => void
|
||||
onEhFormChange: (form: VorabiturEHForm) => void
|
||||
onSubmit: (e: React.FormEvent) => void
|
||||
onCancel: () => void
|
||||
}
|
||||
|
||||
export default function ErstellenTab({
|
||||
form, ehForm, templates, creating, loadingTemplates,
|
||||
onFormChange, onEhFormChange, onSubmit, onCancel,
|
||||
}: ErstellenTabProps) {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="bg-white rounded-lg border border-slate-200 shadow-sm p-6">
|
||||
<h2 className="text-lg font-semibold text-slate-800 mb-6">Neue Klausur erstellen</h2>
|
||||
|
||||
<form onSubmit={onSubmit} className="space-y-4">
|
||||
{/* Title */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Titel der Klausur *</label>
|
||||
<input
|
||||
type="text"
|
||||
value={form.title}
|
||||
onChange={(e) => onFormChange({ ...form, title: e.target.value })}
|
||||
placeholder="z.B. Deutsch LK Abitur 2025 - Kurs D1"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Subject + Year */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Fach</label>
|
||||
<select
|
||||
value={form.subject}
|
||||
onChange={(e) => onFormChange({ ...form, subject: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
>
|
||||
<option value="Deutsch">Deutsch</option>
|
||||
<option value="Englisch">Englisch</option>
|
||||
<option value="Mathematik">Mathematik</option>
|
||||
<option value="Geschichte">Geschichte</option>
|
||||
<option value="Biologie">Biologie</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Jahr</label>
|
||||
<input
|
||||
type="number"
|
||||
value={form.year}
|
||||
onChange={(e) => onFormChange({ ...form, year: parseInt(e.target.value) })}
|
||||
min={2020} max={2030}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Semester + Modus */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Semester / Pruefung</label>
|
||||
<select
|
||||
value={form.semester}
|
||||
onChange={(e) => onFormChange({ ...form, semester: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
>
|
||||
<option value="Abitur">Abitur</option>
|
||||
<option value="Q1">Q1 (11/1)</option>
|
||||
<option value="Q2">Q2 (11/2)</option>
|
||||
<option value="Q3">Q3 (12/1)</option>
|
||||
<option value="Q4">Q4 (12/2)</option>
|
||||
<option value="Vorabitur">Vorabitur</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Modus</label>
|
||||
<select
|
||||
value={form.modus}
|
||||
onChange={(e) => onFormChange({ ...form, modus: e.target.value as 'abitur' | 'vorabitur' })}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
>
|
||||
<option value="abitur">Abitur (mit offiziellem EH)</option>
|
||||
<option value="vorabitur">Vorabitur (eigener EH)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Vorabitur EH Form */}
|
||||
{form.modus === 'vorabitur' && (
|
||||
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg space-y-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<svg className="w-5 h-5 text-blue-600 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<div className="text-sm text-blue-800">
|
||||
<p className="font-medium mb-1">Eigenen Erwartungshorizont erstellen</p>
|
||||
<p>Waehlen Sie einen Aufgabentyp und beschreiben Sie die Aufgabenstellung. Der EH wird automatisch mit Ihrer Klausur verknuepft.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Aufgabentyp */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Aufgabentyp *</label>
|
||||
{loadingTemplates ? (
|
||||
<div className="flex items-center gap-2 text-sm text-slate-500">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600"></div>
|
||||
Lade Vorlagen...
|
||||
</div>
|
||||
) : (
|
||||
<select
|
||||
value={ehForm.aufgabentyp}
|
||||
onChange={(e) => onEhFormChange({ ...ehForm, aufgabentyp: e.target.value })}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white"
|
||||
>
|
||||
<option value="">-- Aufgabentyp waehlen --</option>
|
||||
{templates.map(t => (
|
||||
<option key={t.aufgabentyp} value={t.aufgabentyp}>{t.name}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
{ehForm.aufgabentyp && templates.find(t => t.aufgabentyp === ehForm.aufgabentyp) && (
|
||||
<p className="mt-1 text-xs text-slate-500">
|
||||
{templates.find(t => t.aufgabentyp === ehForm.aufgabentyp)?.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Text Details */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Texttitel (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ehForm.text_titel}
|
||||
onChange={(e) => onEhFormChange({ ...ehForm, text_titel: e.target.value })}
|
||||
placeholder="z.B. 'Die Verwandlung'"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Autor (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ehForm.text_autor}
|
||||
onChange={(e) => onEhFormChange({ ...ehForm, text_autor: e.target.value })}
|
||||
placeholder="z.B. 'Franz Kafka'"
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Aufgabenstellung */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Aufgabenstellung *</label>
|
||||
<textarea
|
||||
value={ehForm.aufgabenstellung}
|
||||
onChange={(e) => onEhFormChange({ ...ehForm, aufgabenstellung: e.target.value })}
|
||||
placeholder="Beschreiben Sie hier die konkrete Aufgabenstellung fuer die Schueler..."
|
||||
rows={4}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent resize-none"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-slate-500">
|
||||
Die Aufgabenstellung wird zusammen mit dem Template in den Erwartungshorizont eingebunden.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button type="button" onClick={onCancel} className="px-4 py-2 text-slate-600 hover:bg-slate-100 rounded-lg">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={creating}
|
||||
className="flex-1 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
{creating ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
||||
Erstelle...
|
||||
</>
|
||||
) : (
|
||||
'Klausur erstellen'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user