[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:
131
website/components/klausur-korrektur/KlausurenTab.tsx
Normal file
131
website/components/klausur-korrektur/KlausurenTab.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Klausuren list tab - shows all exams in a grid with progress bars.
|
||||
*/
|
||||
|
||||
import Link from 'next/link'
|
||||
import type { Klausur } from '../../app/admin/klausur-korrektur/types'
|
||||
import type { TabId } from './list-types'
|
||||
|
||||
interface KlausurenTabProps {
|
||||
klausuren: Klausur[]
|
||||
loading: boolean
|
||||
basePath: string
|
||||
onNavigate: (tab: TabId) => void
|
||||
onDelete: (id: string) => void
|
||||
}
|
||||
|
||||
export default function KlausurenTab({
|
||||
klausuren, loading, basePath, onNavigate, onDelete,
|
||||
}: KlausurenTabProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-800">Alle Klausuren</h2>
|
||||
<p className="text-sm text-slate-500">{klausuren.length} Klausuren insgesamt</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onNavigate('erstellen')}
|
||||
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 flex items-center gap-2"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Neue Klausur
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Klausuren Grid */}
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||
</div>
|
||||
) : klausuren.length === 0 ? (
|
||||
<div className="text-center py-12 bg-white rounded-lg border border-slate-200">
|
||||
<svg className="mx-auto h-12 w-12 text-slate-400" 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>
|
||||
<h3 className="mt-2 text-sm font-medium text-slate-900">Keine Klausuren</h3>
|
||||
<p className="mt-1 text-sm text-slate-500">Erstellen Sie Ihre erste Klausur zum Korrigieren.</p>
|
||||
<button
|
||||
onClick={() => onNavigate('erstellen')}
|
||||
className="mt-4 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700"
|
||||
>
|
||||
Klausur erstellen
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{klausuren.map((klausur) => (
|
||||
<div key={klausur.id} className="bg-white rounded-lg border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<h3 className="font-semibold text-slate-800 truncate">{klausur.title}</h3>
|
||||
<p className="text-sm text-slate-500">{klausur.subject} - {klausur.year}</p>
|
||||
</div>
|
||||
<span className={`px-2 py-1 text-xs font-medium rounded-full ${
|
||||
klausur.modus === 'abitur' ? 'bg-purple-100 text-purple-800' : 'bg-blue-100 text-blue-800'
|
||||
}`}>
|
||||
{klausur.modus === 'abitur' ? 'Abitur' : 'Vorabitur'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-sm text-slate-600 mb-4">
|
||||
<div className="flex items-center gap-1">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
<span>{klausur.student_count || 0} Arbeiten</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>{klausur.completed_count || 0} fertig</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(klausur.student_count || 0) > 0 && (
|
||||
<div className="mb-4">
|
||||
<div className="flex justify-between text-xs text-slate-500 mb-1">
|
||||
<span>Fortschritt</span>
|
||||
<span>{Math.round(((klausur.completed_count || 0) / (klausur.student_count || 1)) * 100)}%</span>
|
||||
</div>
|
||||
<div className="h-2 bg-slate-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-green-500 rounded-full transition-all"
|
||||
style={{ width: `${((klausur.completed_count || 0) / (klausur.student_count || 1)) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Link
|
||||
href={`${basePath}/${klausur.id}`}
|
||||
className="flex-1 px-3 py-2 bg-primary-600 text-white text-sm text-center rounded-lg hover:bg-primary-700"
|
||||
>
|
||||
Korrigieren
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => onDelete(klausur.id)}
|
||||
className="px-3 py-2 text-red-600 hover:bg-red-50 rounded-lg"
|
||||
title="Loeschen"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user