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>
166 lines
6.8 KiB
TypeScript
166 lines
6.8 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Workflow-aware action buttons for the criteria panel.
|
|
* Handles Erstkorrektur, Zweitkorrektur, Einigung, and completed states.
|
|
*/
|
|
|
|
import type { ExaminerWorkflow } from './workspace-types'
|
|
import { GRADE_LABELS } from './workspace-types'
|
|
|
|
interface WorkflowActionsProps {
|
|
workflow: ExaminerWorkflow | null
|
|
gutachten: string
|
|
generatingGutachten: boolean
|
|
submittingWorkflow: boolean
|
|
totals: { gradePoints: number }
|
|
onGenerateGutachten: () => void
|
|
onSubmitErstkorrektur: () => void
|
|
onStartZweitkorrektur: (id: string) => void
|
|
onSubmitZweitkorrektur: () => void
|
|
onShowEinigungModal: () => void
|
|
}
|
|
|
|
export default function WorkflowActions({
|
|
workflow, gutachten, generatingGutachten, submittingWorkflow, totals,
|
|
onGenerateGutachten, onSubmitErstkorrektur, onStartZweitkorrektur,
|
|
onSubmitZweitkorrektur, onShowEinigungModal,
|
|
}: WorkflowActionsProps) {
|
|
return (
|
|
<div className="space-y-2">
|
|
{/* Generate Gutachten button */}
|
|
<button
|
|
onClick={onGenerateGutachten}
|
|
disabled={generatingGutachten}
|
|
className="w-full py-2 bg-slate-100 text-slate-700 rounded-lg hover:bg-slate-200 disabled:opacity-50 flex items-center justify-center gap-2 text-sm"
|
|
>
|
|
{generatingGutachten ? (
|
|
<>
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-slate-700"></div>
|
|
Generiere Gutachten...
|
|
</>
|
|
) : (
|
|
<>
|
|
<svg className="w-4 h-4" 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>
|
|
Gutachten generieren
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
{/* Erstkorrektur */}
|
|
{(!workflow || workflow.workflow_status === 'not_started' || workflow.workflow_status === 'ek_in_progress') && (
|
|
<button
|
|
onClick={onSubmitErstkorrektur}
|
|
disabled={submittingWorkflow || !gutachten}
|
|
className="w-full py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 flex items-center justify-center gap-2"
|
|
>
|
|
{submittingWorkflow ? (
|
|
<>
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
|
Wird abgeschlossen...
|
|
</>
|
|
) : (
|
|
<>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
Erstkorrektur abschliessen
|
|
</>
|
|
)}
|
|
</button>
|
|
)}
|
|
|
|
{/* Start Zweitkorrektur */}
|
|
{workflow?.workflow_status === 'ek_completed' && workflow.user_role === 'ek' && (
|
|
<button
|
|
onClick={() => {
|
|
const zkId = prompt('Zweitkorrektor-ID eingeben:')
|
|
if (zkId) onStartZweitkorrektur(zkId)
|
|
}}
|
|
disabled={submittingWorkflow}
|
|
className="w-full py-3 bg-amber-600 text-white rounded-lg hover:bg-amber-700 disabled:opacity-50 flex items-center justify-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="M17 8l4 4m0 0l-4 4m4-4H3" />
|
|
</svg>
|
|
Zur Zweitkorrektur weiterleiten
|
|
</button>
|
|
)}
|
|
|
|
{/* Submit Zweitkorrektur */}
|
|
{(workflow?.workflow_status === 'zk_assigned' || workflow?.workflow_status === 'zk_in_progress') &&
|
|
workflow?.user_role === 'zk' && (
|
|
<button
|
|
onClick={onSubmitZweitkorrektur}
|
|
disabled={submittingWorkflow || !gutachten}
|
|
className="w-full py-3 bg-amber-600 text-white rounded-lg hover:bg-amber-700 disabled:opacity-50 flex items-center justify-center gap-2"
|
|
>
|
|
{submittingWorkflow ? (
|
|
<>
|
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
|
Wird abgeschlossen...
|
|
</>
|
|
) : (
|
|
<>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
Zweitkorrektur abschliessen
|
|
</>
|
|
)}
|
|
</button>
|
|
)}
|
|
|
|
{/* Einigung */}
|
|
{workflow?.workflow_status === 'einigung_required' && (
|
|
<button
|
|
onClick={onShowEinigungModal}
|
|
className="w-full py-3 bg-orange-600 text-white rounded-lg hover:bg-orange-700 flex items-center justify-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="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
|
</svg>
|
|
Einigung starten
|
|
</button>
|
|
)}
|
|
|
|
{/* Completed */}
|
|
{workflow?.workflow_status === 'completed' && (
|
|
<div className="bg-green-100 text-green-800 p-4 rounded-lg text-center">
|
|
<div className="text-2xl font-bold">
|
|
Endnote: {workflow.final_grade} Punkte
|
|
</div>
|
|
<div className="text-sm mt-1">
|
|
({GRADE_LABELS[workflow.final_grade || 0]}) - {workflow.consensus_type === 'auto' ? 'Auto-Konsens' : workflow.consensus_type === 'drittkorrektur' ? 'Drittkorrektur' : 'Einigung'}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* EK/ZK comparison */}
|
|
{workflow?.first_result && workflow?.second_result && workflow?.workflow_status !== 'completed' && (
|
|
<div className="bg-slate-50 rounded-lg p-3 mt-2">
|
|
<div className="text-xs text-slate-500 mb-2">Notenvergleich</div>
|
|
<div className="flex justify-between">
|
|
<div className="text-center">
|
|
<div className="text-sm text-slate-500">EK</div>
|
|
<div className="font-bold text-blue-600">{workflow.first_result.grade_points}P</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-sm text-slate-500">ZK</div>
|
|
<div className="font-bold text-amber-600">{workflow.second_result.grade_points}P</div>
|
|
</div>
|
|
<div className="text-center">
|
|
<div className="text-sm text-slate-500">Diff</div>
|
|
<div className={`font-bold ${(workflow.grade_difference || 0) >= 4 ? 'text-red-600' : 'text-slate-700'}`}>
|
|
{workflow.grade_difference}P
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|