'use client' import React, { useState, useEffect, useMemo, useCallback } from 'react' import { useSDK } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' import { Incident, IncidentSeverity, IncidentStatus, IncidentCategory, IncidentStatistics, INCIDENT_SEVERITY_INFO, INCIDENT_STATUS_INFO, INCIDENT_CATEGORY_INFO, getHoursUntil72hDeadline, is72hDeadlineExpired } from '@/lib/sdk/incidents/types' import { fetchSDKIncidentList, createMockIncidents, createMockStatistics } from '@/lib/sdk/incidents/api' // ============================================================================= // TYPES // ============================================================================= type TabId = 'overview' | 'active' | 'notification' | 'closed' | 'settings' interface Tab { id: TabId label: string count?: number countColor?: string } // ============================================================================= // COMPONENTS // ============================================================================= function TabNavigation({ tabs, activeTab, onTabChange }: { tabs: Tab[] activeTab: TabId onTabChange: (tab: TabId) => void }) { return (
) } function StatCard({ label, value, color = 'gray', icon, trend }: { label: string value: number | string color?: 'gray' | 'blue' | 'yellow' | 'red' | 'green' | 'purple' | 'orange' icon?: React.ReactNode trend?: { value: number; label: string } }) { const colorClasses: Record = { gray: 'border-gray-200 text-gray-900', blue: 'border-blue-200 text-blue-600', yellow: 'border-yellow-200 text-yellow-600', red: 'border-red-200 text-red-600', green: 'border-green-200 text-green-600', purple: 'border-purple-200 text-purple-600', orange: 'border-orange-200 text-orange-600' } return (
{label}
{value}
{trend && (
= 0 ? 'text-green-600' : 'text-red-600'}`}> {trend.value >= 0 ? '+' : ''}{trend.value} {trend.label}
)}
{icon && (
{icon}
)}
) } function FilterBar({ selectedSeverity, selectedStatus, selectedCategory, onSeverityChange, onStatusChange, onCategoryChange, onClear }: { selectedSeverity: IncidentSeverity | 'all' selectedStatus: IncidentStatus | 'all' selectedCategory: IncidentCategory | 'all' onSeverityChange: (severity: IncidentSeverity | 'all') => void onStatusChange: (status: IncidentStatus | 'all') => void onCategoryChange: (category: IncidentCategory | 'all') => void onClear: () => void }) { const hasFilters = selectedSeverity !== 'all' || selectedStatus !== 'all' || selectedCategory !== 'all' return (
Filter: {/* Severity Filter */} {/* Status Filter */} {/* Category Filter */} {/* Clear Filters */} {hasFilters && ( )}
) } /** * 72h-Countdown-Anzeige mit visueller Farbkodierung * Gruen > 48h, Gelb > 24h, Orange > 12h, Rot < 12h oder abgelaufen */ function CountdownTimer({ incident }: { incident: Incident }) { const hoursRemaining = getHoursUntil72hDeadline(incident.detectedAt) const expired = is72hDeadlineExpired(incident.detectedAt) // Nicht relevant fuer abgeschlossene Vorfaelle if (incident.status === 'closed') return null // Bereits gemeldet if (incident.authorityNotification && (incident.authorityNotification.status === 'submitted' || incident.authorityNotification.status === 'acknowledged')) { return ( Gemeldet ) } // Keine Meldepflicht festgestellt if (incident.riskAssessment && !incident.riskAssessment.notificationRequired) { return ( Keine Meldepflicht ) } // Abgelaufen if (expired) { const overdueHours = Math.abs(hoursRemaining) return ( {overdueHours.toFixed(0)}h ueberfaellig ) } // Farbkodierung: gruen > 48h, gelb > 24h, orange > 12h, rot < 12h let colorClass: string if (hoursRemaining > 48) { colorClass = 'bg-green-100 text-green-700' } else if (hoursRemaining > 24) { colorClass = 'bg-yellow-100 text-yellow-700' } else if (hoursRemaining > 12) { colorClass = 'bg-orange-100 text-orange-700' } else { colorClass = 'bg-red-100 text-red-700' } return ( {hoursRemaining.toFixed(0)}h verbleibend ) } function Badge({ bgColor, color, label }: { bgColor: string; color: string; label: string }) { return {label} } 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 = { 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 (
{/* Header Badges */}
{incident.referenceNumber}
{/* Title */}

{incident.title}

{incident.description}

{/* 72h Countdown - prominent */}
{/* Right Side - Key Numbers */}
Betroffene
{incident.estimatedAffectedPersons.toLocaleString('de-DE')}
{new Date(incident.detectedAt).toLocaleDateString('de-DE')}
{/* Footer */}
{completedMeasures}/{measuresCount} Massnahmen {incident.timeline.length} Eintraege
{incident.assignedTo ? `Zugewiesen: ${incident.assignedTo}` : 'Nicht zugewiesen' } {incident.status !== 'closed' ? ( Bearbeiten ) : ( Details )}
) } // ============================================================================= // INCIDENT CREATE MODAL // ============================================================================= function IncidentCreateModal({ onClose, onSuccess }: { onClose: () => void onSuccess: () => void }) { const [title, setTitle] = useState('') const [category, setCategory] = useState('data_breach') const [severity, setSeverity] = useState('medium') const [description, setDescription] = useState('') const [detectedBy, setDetectedBy] = useState('') const [affectedSystems, setAffectedSystems] = useState('') const [estimatedAffectedPersons, setEstimatedAffectedPersons] = useState('0') const [isSaving, setIsSaving] = useState(false) const [error, setError] = useState(null) const handleSave = async () => { if (!title.trim()) { setError('Titel ist erforderlich.') return } setIsSaving(true) setError(null) try { const res = await fetch('/api/sdk/v1/incidents', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, category, severity, description, detectedBy, affectedSystems: affectedSystems.split(',').map(s => s.trim()).filter(Boolean), estimatedAffectedPersons: Number(estimatedAffectedPersons) }) }) if (!res.ok) { const data = await res.json().catch(() => ({})) throw new Error(data.detail || data.message || `Fehler: ${res.status}`) } onSuccess() } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Unbekannter Fehler') } finally { setIsSaving(false) } } return (
{/* Backdrop */}
{/* Modal */}

Neuen Vorfall erfassen

{error && (
{error}
)}
{/* Title */}
setTitle(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-sm" placeholder="Kurze Beschreibung des Vorfalls" />
{/* Category */}
{/* Severity */}
{/* Description */}