'use client' import React, { useState, useEffect, useMemo } from 'react' import Link from 'next/link' 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 }: { incident: Incident }) { 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 )}
) } // ============================================================================= // MAIN PAGE // ============================================================================= export default function IncidentsPage() { const { state } = useSDK() const [activeTab, setActiveTab] = useState('overview') const [incidents, setIncidents] = useState([]) const [statistics, setStatistics] = useState(null) const [isLoading, setIsLoading] = useState(true) // Filters const [selectedSeverity, setSelectedSeverity] = useState('all') const [selectedStatus, setSelectedStatus] = useState('all') const [selectedCategory, setSelectedCategory] = useState('all') // Load data useEffect(() => { const loadData = async () => { setIsLoading(true) try { const { incidents: loadedIncidents, statistics: loadedStats } = await fetchSDKIncidentList() setIncidents(loadedIncidents) setStatistics(loadedStats) } catch (error) { console.error('Fehler beim Laden der Incident-Daten:', error) // Fallback auf Mock-Daten setIncidents(createMockIncidents()) setStatistics(createMockStatistics()) } finally { setIsLoading(false) } } loadData() }, []) // Calculate tab counts const tabCounts = useMemo(() => { return { active: incidents.filter(i => i.status === 'detected' || i.status === 'assessment' || i.status === 'containment' || i.status === 'remediation' ).length, notification: incidents.filter(i => i.status === 'notification_required' || i.status === 'notification_sent' || (i.authorityNotification !== null && i.authorityNotification.status === 'pending') ).length, closed: incidents.filter(i => i.status === 'closed').length, deadlineExpired: incidents.filter(i => { if (i.status === 'closed') return false if (i.authorityNotification && (i.authorityNotification.status === 'submitted' || i.authorityNotification.status === 'acknowledged')) return false if (i.riskAssessment && !i.riskAssessment.notificationRequired) return false return is72hDeadlineExpired(i.detectedAt) }).length, deadlineApproaching: incidents.filter(i => { if (i.status === 'closed') return false if (i.authorityNotification && (i.authorityNotification.status === 'submitted' || i.authorityNotification.status === 'acknowledged')) return false const hours = getHoursUntil72hDeadline(i.detectedAt) return hours > 0 && hours <= 24 }).length } }, [incidents]) // Filter incidents based on active tab and filters const filteredIncidents = useMemo(() => { let filtered = [...incidents] // Tab-based filtering if (activeTab === 'active') { filtered = filtered.filter(i => i.status === 'detected' || i.status === 'assessment' || i.status === 'containment' || i.status === 'remediation' ) } else if (activeTab === 'notification') { filtered = filtered.filter(i => i.status === 'notification_required' || i.status === 'notification_sent' || (i.authorityNotification !== null && i.authorityNotification.status === 'pending') ) } else if (activeTab === 'closed') { filtered = filtered.filter(i => i.status === 'closed') } // Severity filter if (selectedSeverity !== 'all') { filtered = filtered.filter(i => i.severity === selectedSeverity) } // Status filter if (selectedStatus !== 'all') { filtered = filtered.filter(i => i.status === selectedStatus) } // Category filter if (selectedCategory !== 'all') { filtered = filtered.filter(i => i.category === selectedCategory) } // Sort: most urgent first (overdue > deadline approaching > severity > detected time) const severityOrder: Record = { critical: 0, high: 1, medium: 2, low: 3 } return filtered.sort((a, b) => { // Closed always at the end if (a.status === 'closed' !== (b.status === 'closed')) return a.status === 'closed' ? 1 : -1 // Overdue first const aExpired = is72hDeadlineExpired(a.detectedAt) const bExpired = is72hDeadlineExpired(b.detectedAt) if (aExpired !== bExpired) return aExpired ? -1 : 1 // Then by severity if (severityOrder[a.severity] !== severityOrder[b.severity]) { return severityOrder[a.severity] - severityOrder[b.severity] } // Then by deadline urgency return getHoursUntil72hDeadline(a.detectedAt) - getHoursUntil72hDeadline(b.detectedAt) }) }, [incidents, activeTab, selectedSeverity, selectedStatus, selectedCategory]) const tabs: Tab[] = [ { id: 'overview', label: 'Uebersicht' }, { id: 'active', label: 'Aktiv', count: tabCounts.active, countColor: 'bg-orange-100 text-orange-600' }, { id: 'notification', label: 'Meldepflichtig', count: tabCounts.notification, countColor: 'bg-red-100 text-red-600' }, { id: 'closed', label: 'Abgeschlossen', count: tabCounts.closed, countColor: 'bg-green-100 text-green-600' }, { id: 'settings', label: 'Einstellungen' } ] const stepInfo = STEP_EXPLANATIONS['incidents'] const clearFilters = () => { setSelectedSeverity('all') setSelectedStatus('all') setSelectedCategory('all') } return (
{/* Step Header */} Vorfall melden {/* Tab Navigation */} {/* Loading State */} {isLoading ? (
) : activeTab === 'settings' ? ( /* Settings Tab */

Einstellungen

Incident-Management-Einstellungen, Eskalationswege und Meldevorlagen werden in einer spaeteren Version verfuegbar sein.

) : ( <> {/* Statistics (Overview Tab) */} {activeTab === 'overview' && statistics && (
0 ? 'red' : 'green'} />
)} {/* Critical Alert: 72h deadline approaching or expired */} {(tabCounts.deadlineExpired > 0 || tabCounts.deadlineApproaching > 0) && (

{tabCounts.deadlineExpired > 0 ? `Achtung: ${tabCounts.deadlineExpired} ueberfaellige Meldung(en) - 72-Stunden-Frist ueberschritten!` : `Warnung: ${tabCounts.deadlineApproaching} Meldung(en) mit ablaufender 72-Stunden-Frist` }

{tabCounts.deadlineExpired > 0 ? 'Die gesetzliche Meldefrist nach Art. 33 DSGVO ist abgelaufen. Handeln Sie umgehend, um Bussgelder zu vermeiden. Verspaetete Meldungen muessen begruendet werden.' : 'Die 72-Stunden-Meldefrist nach Art. 33 DSGVO laeuft in Kuerze ab. Fuehren Sie eine Risikobewertung durch und entscheiden Sie ueber die Meldepflicht.' }

)} {/* Info Box (Overview Tab) */} {activeTab === 'overview' && (

Art. 33/34 DSGVO - 72-Stunden-Meldepflicht

Nach Art. 33 DSGVO muessen Datenschutzverletzungen innerhalb von 72 Stunden an die zustaendige Aufsichtsbehoerde gemeldet werden, sofern ein Risiko fuer die Rechte und Freiheiten der betroffenen Personen besteht. Bei hohem Risiko muessen gemaess Art. 34 DSGVO auch die betroffenen Personen benachrichtigt werden. Alle Vorfaelle sind unabhaengig von der Meldepflicht zu dokumentieren (Art. 33 Abs. 5).

)} {/* Filters */} {/* Incidents List */}
{filteredIncidents.map(incident => ( ))}
{/* Empty State */} {filteredIncidents.length === 0 && (

Keine Vorfaelle gefunden

{selectedSeverity !== 'all' || selectedStatus !== 'all' || selectedCategory !== 'all' ? 'Passen Sie die Filter an oder' : 'Es sind noch keine Vorfaelle erfasst worden.' }

{(selectedSeverity !== 'all' || selectedStatus !== 'all' || selectedCategory !== 'all') ? ( ) : ( Ersten Vorfall erfassen )}
)} )}
) }