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>
109 lines
4.0 KiB
TypeScript
109 lines
4.0 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Top navigation bar for the Korrektur-Workspace.
|
|
* Shows back link, student navigation, workflow status, and grade.
|
|
*/
|
|
|
|
import Link from 'next/link'
|
|
import type { ExaminerWorkflow } from './workspace-types'
|
|
import { WORKFLOW_STATUS_LABELS, ROLE_LABELS, GRADE_LABELS } from './workspace-types'
|
|
|
|
interface WorkspaceTopBarProps {
|
|
klausurId: string
|
|
backPath: string
|
|
currentIndex: number
|
|
studentCount: number
|
|
workflow: ExaminerWorkflow | null
|
|
saving: boolean
|
|
totals: { gradePoints: number; weighted: number }
|
|
onGoToStudent: (direction: 'prev' | 'next') => void
|
|
}
|
|
|
|
export default function WorkspaceTopBar({
|
|
klausurId, backPath, currentIndex, studentCount,
|
|
workflow, saving, totals, onGoToStudent,
|
|
}: WorkspaceTopBarProps) {
|
|
return (
|
|
<div className="bg-white border-b border-slate-200 -mx-6 -mt-6 px-6 py-3 mb-4 flex items-center justify-between sticky top-0 z-10">
|
|
{/* Back link */}
|
|
<Link
|
|
href={backPath}
|
|
className="text-primary-600 hover:text-primary-800 flex items-center gap-1 text-sm"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
Zurueck
|
|
</Link>
|
|
|
|
{/* Student navigation */}
|
|
<div className="flex items-center gap-4">
|
|
<button
|
|
onClick={() => onGoToStudent('prev')}
|
|
disabled={currentIndex <= 0}
|
|
className="p-2 rounded-lg hover:bg-slate-100 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
</button>
|
|
<span className="text-sm font-medium">
|
|
{currentIndex + 1} / {studentCount}
|
|
</span>
|
|
<button
|
|
onClick={() => onGoToStudent('next')}
|
|
disabled={currentIndex >= studentCount - 1}
|
|
className="p-2 rounded-lg hover:bg-slate-100 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Workflow status and role */}
|
|
<div className="flex items-center gap-3">
|
|
{workflow && (
|
|
<div className="flex items-center gap-2">
|
|
<span
|
|
className={`px-2 py-1 text-xs font-medium rounded-full text-white ${
|
|
ROLE_LABELS[workflow.user_role]?.color || 'bg-slate-500'
|
|
}`}
|
|
>
|
|
{ROLE_LABELS[workflow.user_role]?.label || workflow.user_role}
|
|
</span>
|
|
<span
|
|
className={`px-2 py-1 text-xs font-medium rounded-full ${
|
|
WORKFLOW_STATUS_LABELS[workflow.workflow_status]?.color || 'bg-slate-100'
|
|
}`}
|
|
>
|
|
{WORKFLOW_STATUS_LABELS[workflow.workflow_status]?.label || workflow.workflow_status}
|
|
</span>
|
|
{workflow.user_role === 'zk' && workflow.visibility_mode !== 'full' && (
|
|
<span className="px-2 py-1 text-xs bg-purple-100 text-purple-700 rounded-full">
|
|
{workflow.visibility_mode === 'blind' ? 'Blind-Modus' : 'Semi-Blind'}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{saving && (
|
|
<span className="text-sm text-slate-500 flex items-center gap-1">
|
|
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-primary-600"></div>
|
|
Speichern...
|
|
</span>
|
|
)}
|
|
<div className="text-right">
|
|
<div className="text-lg font-bold text-slate-800">
|
|
{totals.gradePoints} Punkte
|
|
</div>
|
|
<div className="text-sm text-slate-500">
|
|
Note: {GRADE_LABELS[totals.gradePoints] || '-'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|