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.1 KiB
TypeScript
86 lines
4.1 KiB
TypeScript
'use client'
|
|
|
|
import type { TrainingAssignment } from '@/lib/sdk/training/types'
|
|
import { STATUS_LABELS, STATUS_COLORS } from '@/lib/sdk/training/types'
|
|
|
|
interface AssignmentsTabProps {
|
|
assignments: TrainingAssignment[]
|
|
statusFilter: string
|
|
onStatusFilterChange: (value: string) => void
|
|
onAssignmentClick: (a: TrainingAssignment) => void
|
|
}
|
|
|
|
export function AssignmentsTab({ assignments, statusFilter, onStatusFilterChange, onAssignmentClick }: AssignmentsTabProps) {
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex gap-3">
|
|
<select value={statusFilter} onChange={e => onStatusFilterChange(e.target.value)} className="px-3 py-1.5 text-sm border rounded-lg bg-white">
|
|
<option value="">Alle Status</option>
|
|
{Object.entries(STATUS_LABELS).map(([key, label]) => (
|
|
<option key={key} value={key}>{label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-sm border-collapse">
|
|
<thead>
|
|
<tr className="bg-gray-50">
|
|
<th className="text-left p-2 border font-medium text-gray-700">Modul</th>
|
|
<th className="text-left p-2 border font-medium text-gray-700">Mitarbeiter</th>
|
|
<th className="text-left p-2 border font-medium text-gray-700">Rolle</th>
|
|
<th className="text-center p-2 border font-medium text-gray-700">Fortschritt</th>
|
|
<th className="text-center p-2 border font-medium text-gray-700">Status</th>
|
|
<th className="text-center p-2 border font-medium text-gray-700">Quiz</th>
|
|
<th className="text-left p-2 border font-medium text-gray-700">Deadline</th>
|
|
<th className="text-center p-2 border font-medium text-gray-700">Eskalation</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{assignments.map(a => (
|
|
<tr
|
|
key={a.id}
|
|
onClick={() => onAssignmentClick(a)}
|
|
className="border-b hover:bg-gray-50 cursor-pointer"
|
|
>
|
|
<td className="p-2 border">
|
|
<div className="font-medium">{a.module_title || a.module_code}</div>
|
|
<div className="text-xs text-gray-500">{a.module_code}</div>
|
|
</td>
|
|
<td className="p-2 border">
|
|
<div>{a.user_name}</div>
|
|
<div className="text-xs text-gray-500">{a.user_email}</div>
|
|
</td>
|
|
<td className="p-2 border text-xs">{a.role_code || '-'}</td>
|
|
<td className="p-2 border text-center">
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex-1 bg-gray-200 rounded-full h-2">
|
|
<div className="bg-blue-500 h-2 rounded-full" style={{ width: `${a.progress_percent}%` }} />
|
|
</div>
|
|
<span className="text-xs w-8">{a.progress_percent}%</span>
|
|
</div>
|
|
</td>
|
|
<td className="p-2 border text-center">
|
|
<span className={`px-2 py-0.5 rounded text-xs ${STATUS_COLORS[a.status]?.bg || ''} ${STATUS_COLORS[a.status]?.text || ''}`}>
|
|
{STATUS_LABELS[a.status] || a.status}
|
|
</span>
|
|
</td>
|
|
<td className="p-2 border text-center text-xs">
|
|
{a.quiz_score != null ? (
|
|
<span className={`font-medium ${a.quiz_passed ? 'text-green-600' : 'text-red-600'}`}>{a.quiz_score.toFixed(0)}%</span>
|
|
) : '-'}
|
|
</td>
|
|
<td className="p-2 border text-xs">{new Date(a.deadline).toLocaleDateString('de-DE')}</td>
|
|
<td className="p-2 border text-center">
|
|
{a.escalation_level > 0 ? <span className="px-2 py-0.5 rounded text-xs bg-red-100 text-red-700">L{a.escalation_level}</span> : '-'}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{assignments.length === 0 && <p className="text-center text-gray-500 py-8">Keine Zuweisungen</p>}
|
|
</div>
|
|
)
|
|
}
|