'use client' /** * Risk Matrix Page * * Features: * - Visual 5x5 risk matrix * - Risk list with CRUD * - Risk assessment / update */ import { useState, useEffect } from 'react' import Link from 'next/link' import AdminLayout from '@/components/admin/AdminLayout' interface Risk { id: string risk_id: string title: string description: string category: string likelihood: number impact: number inherent_risk: string mitigating_controls: string[] | null residual_likelihood: number | null residual_impact: number | null residual_risk: string | null owner: string status: string treatment_plan: string } const RISK_COLORS: Record = { low: 'bg-green-500', medium: 'bg-yellow-500', high: 'bg-orange-500', critical: 'bg-red-500', } const RISK_BG_COLORS: Record = { low: 'bg-green-100 border-green-300', medium: 'bg-yellow-100 border-yellow-300', high: 'bg-orange-100 border-orange-300', critical: 'bg-red-100 border-red-300', } const STATUS_OPTIONS = ['open', 'mitigated', 'accepted', 'transferred'] const CATEGORY_OPTIONS = [ { value: 'data_breach', label: 'Datenschutzverletzung' }, { value: 'compliance_gap', label: 'Compliance-Luecke' }, { value: 'vendor_risk', label: 'Lieferantenrisiko' }, { value: 'operational', label: 'Betriebsrisiko' }, { value: 'technical', label: 'Technisches Risiko' }, { value: 'legal', label: 'Rechtliches Risiko' }, ] const calculateRiskLevel = (likelihood: number, impact: number): string => { const score = likelihood * impact if (score >= 20) return 'critical' if (score >= 12) return 'high' if (score >= 6) return 'medium' return 'low' } export default function RisksPage() { const [risks, setRisks] = useState([]) const [loading, setLoading] = useState(true) const [viewMode, setViewMode] = useState<'matrix' | 'list'>('matrix') const [selectedRisk, setSelectedRisk] = useState(null) const [editModalOpen, setEditModalOpen] = useState(false) const [createModalOpen, setCreateModalOpen] = useState(false) const [formData, setFormData] = useState({ risk_id: '', title: '', description: '', category: 'compliance_gap', likelihood: 3, impact: 3, owner: '', treatment_plan: '', status: 'open', mitigating_controls: [] as string[], residual_likelihood: null as number | null, residual_impact: null as number | null, }) const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000' useEffect(() => { loadRisks() }, []) const loadRisks = async () => { setLoading(true) try { const res = await fetch(`${BACKEND_URL}/api/v1/compliance/risks`) if (res.ok) { const data = await res.json() setRisks(data.risks || []) } } catch (error) { console.error('Failed to load risks:', error) } finally { setLoading(false) } } const openCreateModal = () => { setFormData({ risk_id: `RISK-${String(risks.length + 1).padStart(3, '0')}`, title: '', description: '', category: 'compliance_gap', likelihood: 3, impact: 3, owner: '', treatment_plan: '', status: 'open', mitigating_controls: [], residual_likelihood: null, residual_impact: null, }) setCreateModalOpen(true) } const openEditModal = (risk: Risk) => { setSelectedRisk(risk) setFormData({ risk_id: risk.risk_id, title: risk.title, description: risk.description || '', category: risk.category, likelihood: risk.likelihood, impact: risk.impact, owner: risk.owner || '', treatment_plan: risk.treatment_plan || '', status: risk.status, mitigating_controls: risk.mitigating_controls || [], residual_likelihood: risk.residual_likelihood, residual_impact: risk.residual_impact, }) setEditModalOpen(true) } const handleCreate = async () => { try { const res = await fetch(`${BACKEND_URL}/api/v1/compliance/risks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ risk_id: formData.risk_id, title: formData.title, description: formData.description, category: formData.category, likelihood: formData.likelihood, impact: formData.impact, owner: formData.owner, treatment_plan: formData.treatment_plan, mitigating_controls: formData.mitigating_controls, }), }) if (res.ok) { setCreateModalOpen(false) loadRisks() } else { const error = await res.text() alert(`Fehler: ${error}`) } } catch (error) { console.error('Create failed:', error) alert('Fehler beim Erstellen') } } const handleUpdate = async () => { if (!selectedRisk) return try { const res = await fetch(`${BACKEND_URL}/api/v1/compliance/risks/${selectedRisk.risk_id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: formData.title, description: formData.description, category: formData.category, likelihood: formData.likelihood, impact: formData.impact, owner: formData.owner, treatment_plan: formData.treatment_plan, status: formData.status, mitigating_controls: formData.mitigating_controls, residual_likelihood: formData.residual_likelihood, residual_impact: formData.residual_impact, }), }) if (res.ok) { setEditModalOpen(false) loadRisks() } else { const error = await res.text() alert(`Fehler: ${error}`) } } catch (error) { console.error('Update failed:', error) alert('Fehler beim Aktualisieren') } } // Build matrix data structure const buildMatrix = () => { const matrix: Record> = {} for (let l = 1; l <= 5; l++) { matrix[l] = {} for (let i = 1; i <= 5; i++) { matrix[l][i] = [] } } risks.forEach((risk) => { if (matrix[risk.likelihood] && matrix[risk.likelihood][risk.impact]) { matrix[risk.likelihood][risk.impact].push(risk) } }) return matrix } const renderMatrix = () => { const matrix = buildMatrix() return (

Risk Matrix (Likelihood x Impact)

{/* Column headers (Impact) */}
{[1, 2, 3, 4, 5].map((i) => (
Impact {i}
))}
{/* Matrix rows */} {[5, 4, 3, 2, 1].map((likelihood) => (
L{likelihood}
{[1, 2, 3, 4, 5].map((impact) => { const level = calculateRiskLevel(likelihood, impact) const cellRisks = matrix[likelihood][impact] return (
{cellRisks.length > 0 && (
{cellRisks.map((r) => ( ))}
)}
) })}
))}
{/* Legend */}
Low (1-5)
Medium (6-11)
High (12-19)
Critical (20-25)
) } const renderList = () => (
{risks.map((risk) => ( ))}
ID Titel Kategorie L x I Risiko Status Aktionen
{risk.risk_id}

{risk.title}

{risk.description && (

{risk.description}

)}
{CATEGORY_OPTIONS.find((c) => c.value === risk.category)?.label || risk.category} {risk.likelihood} x {risk.impact} {risk.inherent_risk} {risk.status}
) const renderForm = (isCreate: boolean) => (
{isCreate && (
setFormData({ ...formData, risk_id: e.target.value })} className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-primary-500" />
)}
setFormData({ ...formData, title: e.target.value })} className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-primary-500" />