refactor(admin): split consent-management, control-library, incidents, training pages
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>
This commit is contained in:
125
admin-compliance/app/sdk/incidents/_components/IncidentCard.tsx
Normal file
125
admin-compliance/app/sdk/incidents/_components/IncidentCard.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user