'use client' /** * ExpiredEvidenceAlert - Alert-Komponente fuer abgelaufene Nachweise * * Zeigt eine Warnung wenn Evidence-Dokumente abgelaufen sind oder bald ablaufen. * Kann im Dashboard, in der Evidence-Page oder als globaler Alert verwendet werden. */ import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import { Language } from '@/lib/compliance-i18n' export interface ExpiredEvidence { id: string title: string control_id: string control_name: string expired_at: string | null // Ablaufdatum days_expired: number | null // Tage seit Ablauf (positiv = abgelaufen) days_until_expiry: number | null // Tage bis Ablauf (negativ wenn abgelaufen) status: 'expired' | 'expiring_soon' | 'valid' artifact_type: string } interface ExpiredEvidenceAlertProps { // Liste der abgelaufenen/ablaufenden Evidence-Eintraege evidenceList?: ExpiredEvidence[] // Auto-fetch vom Backend? autoFetch?: boolean // Variante: 'banner' fuer volle Breite, 'card' fuer kompakt, 'minimal' fuer nur Icon variant?: 'banner' | 'card' | 'minimal' // Sprache language?: Language // Max. Anzahl anzuzeigender Items (fuer 'card' Variante) maxItems?: number // Callback wenn auf "Alle anzeigen" geklickt wird onViewAll?: () => void // Callback wenn auf einzelnes Item geklickt wird onItemClick?: (evidence: ExpiredEvidence) => void // Zusaetzliche CSS-Klassen className?: string } // Mock-Daten fuer Demonstration const MOCK_EXPIRED_EVIDENCE: ExpiredEvidence[] = [ { id: '1', title: 'SAST Scan Report Q4/2025', control_id: 'SDLC-001', control_name: 'SAST Scanning', expired_at: '2025-12-31', days_expired: 18, days_until_expiry: -18, status: 'expired', artifact_type: 'scan_report' }, { id: '2', title: 'Penetration Test Report 2025', control_id: 'SDLC-004', control_name: 'Security Testing', expired_at: '2026-01-15', days_expired: 3, days_until_expiry: -3, status: 'expired', artifact_type: 'pentest_report' }, { id: '3', title: 'Backup Recovery Test', control_id: 'OPS-002', control_name: 'Backup & Recovery', expired_at: '2026-01-25', days_expired: null, days_until_expiry: 7, status: 'expiring_soon', artifact_type: 'test_result' }, { id: '4', title: 'Privacy Policy v3.2', control_id: 'PRIV-001', control_name: 'Verarbeitungsverzeichnis', expired_at: '2026-01-30', days_expired: null, days_until_expiry: 12, status: 'expiring_soon', artifact_type: 'policy' } ] export default function ExpiredEvidenceAlert({ evidenceList, autoFetch = false, variant = 'banner', language = 'de', maxItems = 5, onViewAll, onItemClick, className = '' }: ExpiredEvidenceAlertProps) { const router = useRouter() const [evidence, setEvidence] = useState(evidenceList || []) const [loading, setLoading] = useState(autoFetch) const [dismissed, setDismissed] = useState(false) const [expanded, setExpanded] = useState(false) useEffect(() => { if (autoFetch) { fetchExpiredEvidence() } else if (evidenceList) { setEvidence(evidenceList) } else { // Demo-Modus mit Mock-Daten setEvidence(MOCK_EXPIRED_EVIDENCE) } }, [autoFetch, evidenceList]) const fetchExpiredEvidence = async () => { try { setLoading(true) const response = await fetch('/api/v1/compliance/evidence/expiring') if (response.ok) { const data = await response.json() setEvidence(data.items || []) } } catch (error) { console.error('Failed to fetch expired evidence:', error) // Fallback zu Mock-Daten setEvidence(MOCK_EXPIRED_EVIDENCE) } finally { setLoading(false) } } // Aufteilen in abgelaufen und bald ablaufend const expiredItems = evidence.filter(e => e.status === 'expired') const expiringItems = evidence.filter(e => e.status === 'expiring_soon') const totalCount = evidence.length const expiredCount = expiredItems.length const expiringCount = expiringItems.length // Nichts anzeigen wenn keine problematischen Items if (totalCount === 0 || dismissed) { return null } const handleItemClick = (item: ExpiredEvidence) => { if (onItemClick) { onItemClick(item) } else { router.push(`/admin/compliance/evidence?control=${item.control_id}`) } } const handleViewAll = () => { if (onViewAll) { onViewAll() } else { router.push('/admin/compliance/evidence?status=expired,expiring_soon') } } // Minimal-Variante: Nur Icon mit Badge if (variant === 'minimal') { return ( ) } // Card-Variante: Kompakte Karte if (variant === 'card') { return (
{/* Header */}
{language === 'de' ? 'Nachweis-Warnungen' : 'Evidence Warnings'}
{expiredCount > 0 && ( {expiredCount} {language === 'de' ? 'abgelaufen' : 'expired'} )} {expiringCount > 0 && ( {expiringCount} {language === 'de' ? 'bald' : 'soon'} )}
{/* Liste */}
{evidence.slice(0, maxItems).map((item) => ( ))}
{/* Footer */} {totalCount > maxItems && (
)}
) } // Banner-Variante (default): Volle Breite return (
{/* Main Alert */}
0 ? 'bg-red-500/10 border border-red-500/50' : 'bg-orange-500/10 border border-orange-500/50' }`}>
{/* Icon */}
0 ? 'bg-red-500/20' : 'bg-orange-500/20'}`}> 0 ? 'text-red-500' : 'text-orange-500'}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
{/* Text */}

0 ? 'text-red-400' : 'text-orange-400'}`}> {language === 'de' ? `${totalCount} Nachweis${totalCount !== 1 ? 'e' : ''} erfordern Aufmerksamkeit` : `${totalCount} evidence item${totalCount !== 1 ? 's' : ''} require attention` }

{expiredCount > 0 && ( {expiredCount} {language === 'de' ? 'abgelaufen' : 'expired'} )} {expiredCount > 0 && expiringCount > 0 && ' | '} {expiringCount > 0 && ( {expiringCount} {language === 'de' ? 'laufen bald ab' : 'expiring soon'} )}

{/* Actions */}
{/* Expanded Details */} {expanded && (
{evidence.map((item) => ( ))}
)}
) }