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>
126 lines
5.2 KiB
TypeScript
126 lines
5.2 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import {
|
|
Incident,
|
|
IncidentSeverity,
|
|
INCIDENT_SEVERITY_INFO,
|
|
INCIDENT_STATUS_INFO,
|
|
INCIDENT_CATEGORY_INFO,
|
|
is72hDeadlineExpired
|
|
} from '@/lib/sdk/incidents/types'
|
|
import { CountdownTimer } from './CountdownTimer'
|
|
|
|
function Badge({ bgColor, color, label }: { bgColor: string; color: string; label: string }) {
|
|
return <span className={`px-2 py-1 text-xs rounded-full ${bgColor} ${color}`}>{label}</span>
|
|
}
|
|
|
|
export function IncidentCard({ incident, onClick }: { incident: Incident; onClick?: () => void }) {
|
|
const severityInfo = INCIDENT_SEVERITY_INFO[incident.severity]
|
|
const statusInfo = INCIDENT_STATUS_INFO[incident.status]
|
|
const categoryInfo = INCIDENT_CATEGORY_INFO[incident.category]
|
|
|
|
const expired = is72hDeadlineExpired(incident.detectedAt)
|
|
const isNotified = incident.authorityNotification && (incident.authorityNotification.status === 'submitted' || incident.authorityNotification.status === 'acknowledged')
|
|
|
|
const severityBorderColors: Record<IncidentSeverity, string> = {
|
|
critical: 'border-red-300 hover:border-red-400',
|
|
high: 'border-orange-300 hover:border-orange-400',
|
|
medium: 'border-yellow-300 hover:border-yellow-400',
|
|
low: 'border-green-200 hover:border-green-300'
|
|
}
|
|
|
|
const borderColor = incident.status === 'closed'
|
|
? 'border-green-200 hover:border-green-300'
|
|
: expired && !isNotified
|
|
? 'border-red-400 hover:border-red-500'
|
|
: severityBorderColors[incident.severity]
|
|
|
|
const measuresCount = incident.measures.length
|
|
const completedMeasures = incident.measures.filter(m => m.status === 'completed').length
|
|
|
|
return (
|
|
<div onClick={onClick} className="cursor-pointer">
|
|
<div className={`
|
|
bg-white rounded-xl border-2 p-6 hover:shadow-md transition-all cursor-pointer
|
|
${borderColor}
|
|
`}>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1 min-w-0">
|
|
{/* Header Badges */}
|
|
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
|
<span className="text-xs text-gray-500 font-mono">
|
|
{incident.referenceNumber}
|
|
</span>
|
|
<Badge bgColor={severityInfo.bgColor} color={severityInfo.color} label={severityInfo.label} />
|
|
<Badge bgColor={categoryInfo.bgColor} color={categoryInfo.color} label={`${categoryInfo.icon} ${categoryInfo.label}`} />
|
|
<Badge bgColor={statusInfo.bgColor} color={statusInfo.color} label={statusInfo.label} />
|
|
</div>
|
|
|
|
{/* Title */}
|
|
<h3 className="text-lg font-semibold text-gray-900 truncate">
|
|
{incident.title}
|
|
</h3>
|
|
<p className="text-sm text-gray-500 mt-1 line-clamp-2">
|
|
{incident.description}
|
|
</p>
|
|
|
|
{/* 72h Countdown - prominent */}
|
|
<div className="mt-3">
|
|
<CountdownTimer incident={incident} />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Side - Key Numbers */}
|
|
<div className="text-right ml-4 flex-shrink-0">
|
|
<div className="text-sm text-gray-500">
|
|
Betroffene
|
|
</div>
|
|
<div className="text-xl font-bold text-gray-900">
|
|
{incident.estimatedAffectedPersons.toLocaleString('de-DE')}
|
|
</div>
|
|
<div className="text-xs text-gray-400 mt-1">
|
|
{new Date(incident.detectedAt).toLocaleDateString('de-DE')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="mt-4 pt-4 border-t border-gray-100 flex items-center justify-between">
|
|
<div className="flex items-center gap-4 text-sm text-gray-500">
|
|
<span 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 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
</svg>
|
|
{completedMeasures}/{measuresCount} Massnahmen
|
|
</span>
|
|
<span 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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
{incident.timeline.length} Eintraege
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm text-gray-500">
|
|
{incident.assignedTo
|
|
? `Zugewiesen: ${incident.assignedTo}`
|
|
: 'Nicht zugewiesen'
|
|
}
|
|
</span>
|
|
{incident.status !== 'closed' ? (
|
|
<span className="px-3 py-1 text-sm text-purple-600 hover:bg-purple-50 rounded-lg transition-colors">
|
|
Bearbeiten
|
|
</span>
|
|
) : (
|
|
<span className="px-3 py-1 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
|
|
Details
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|