Agent-completed splits committed after agents hit rate limits before committing their work. All 4 pages now under 500 LOC: - consent-management: 1303 -> 193 LOC (+ 7 _components, _hooks, _data, _types) - control-library: 1210 -> 298 LOC (+ _components, _types) - incidents: 1150 -> 373 LOC (+ _components) - training: 1127 -> 366 LOC (+ _components) Verification: next build clean (142 pages generated). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
86 lines
4.8 KiB
TypeScript
86 lines
4.8 KiB
TypeScript
'use client'
|
||
|
||
import type { TrainingStats, DeadlineInfo } from '@/lib/sdk/training/types'
|
||
import { KPICard } from './KPICard'
|
||
|
||
interface OverviewTabProps {
|
||
stats: TrainingStats
|
||
deadlines: DeadlineInfo[]
|
||
escalationResult: { total_checked: number; escalated: number } | null
|
||
onDismissEscalation: () => void
|
||
}
|
||
|
||
export function OverviewTab({ stats, deadlines, escalationResult, onDismissEscalation }: OverviewTabProps) {
|
||
return (
|
||
<div className="space-y-6">
|
||
{escalationResult && (
|
||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 flex items-center justify-between">
|
||
<div>
|
||
<span className="font-medium text-blue-900">Eskalation abgeschlossen: </span>
|
||
<span className="text-blue-700">
|
||
{escalationResult.total_checked} Zuweisungen geprueft, {escalationResult.escalated} eskaliert
|
||
</span>
|
||
</div>
|
||
<button onClick={onDismissEscalation} className="text-blue-400 hover:text-blue-600 text-lg font-bold">×</button>
|
||
</div>
|
||
)}
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<KPICard label="Module" value={stats.total_modules} />
|
||
<KPICard label="Zuweisungen" value={stats.total_assignments} />
|
||
<KPICard label="Abschlussrate" value={`${stats.completion_rate.toFixed(1)}%`} color={stats.completion_rate >= 80 ? 'green' : stats.completion_rate >= 50 ? 'yellow' : 'red'} />
|
||
<KPICard label="Ueberfaellig" value={stats.overdue_count} color={stats.overdue_count > 0 ? 'red' : 'green'} />
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<KPICard label="Ausstehend" value={stats.pending_count} />
|
||
<KPICard label="In Bearbeitung" value={stats.in_progress_count} />
|
||
<KPICard label="Avg. Quiz-Score" value={`${stats.avg_quiz_score.toFixed(1)}%`} />
|
||
<KPICard label="Deadlines (7d)" value={stats.upcoming_deadlines} color={stats.upcoming_deadlines > 5 ? 'yellow' : 'green'} />
|
||
</div>
|
||
|
||
{/* Status Bar */}
|
||
<div className="bg-white border rounded-lg p-4">
|
||
<h3 className="text-sm font-medium text-gray-700 mb-3">Status-Verteilung</h3>
|
||
{stats.total_assignments > 0 && (
|
||
<div className="flex gap-1 h-6 rounded-full overflow-hidden bg-gray-100">
|
||
{stats.completed_count > 0 && <div className="bg-green-500" style={{ width: `${(stats.completed_count / stats.total_assignments) * 100}%` }} title={`Abgeschlossen: ${stats.completed_count}`} />}
|
||
{stats.in_progress_count > 0 && <div className="bg-blue-500" style={{ width: `${(stats.in_progress_count / stats.total_assignments) * 100}%` }} title={`In Bearbeitung: ${stats.in_progress_count}`} />}
|
||
{stats.pending_count > 0 && <div className="bg-gray-400" style={{ width: `${(stats.pending_count / stats.total_assignments) * 100}%` }} title={`Ausstehend: ${stats.pending_count}`} />}
|
||
{stats.overdue_count > 0 && <div className="bg-red-500" style={{ width: `${(stats.overdue_count / stats.total_assignments) * 100}%` }} title={`Ueberfaellig: ${stats.overdue_count}`} />}
|
||
</div>
|
||
)}
|
||
<div className="flex gap-4 mt-2 text-xs text-gray-500">
|
||
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded-full bg-green-500 inline-block" /> Abgeschlossen</span>
|
||
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded-full bg-blue-500 inline-block" /> In Bearbeitung</span>
|
||
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded-full bg-gray-400 inline-block" /> Ausstehend</span>
|
||
<span className="flex items-center gap-1"><span className="w-3 h-3 rounded-full bg-red-500 inline-block" /> Ueberfaellig</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Deadlines */}
|
||
{deadlines.length > 0 && (
|
||
<div className="bg-white border rounded-lg p-4">
|
||
<h3 className="text-sm font-medium text-gray-700 mb-3">Naechste Deadlines</h3>
|
||
<div className="space-y-2">
|
||
{deadlines.slice(0, 5).map(d => (
|
||
<div key={d.assignment_id} className="flex items-center justify-between text-sm">
|
||
<div>
|
||
<span className="font-medium">{d.module_title}</span>
|
||
<span className="text-gray-500 ml-2">({d.user_name})</span>
|
||
</div>
|
||
<span className={`px-2 py-0.5 rounded text-xs ${
|
||
d.days_left <= 0 ? 'bg-red-100 text-red-700' :
|
||
d.days_left <= 7 ? 'bg-yellow-100 text-yellow-700' :
|
||
'bg-gray-100 text-gray-600'
|
||
}`}>
|
||
{d.days_left <= 0 ? `${Math.abs(d.days_left)} Tage ueberfaellig` : `${d.days_left} Tage`}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|