feat: CE × Compliance Crossover Engine
Automatische Erkennung von DSGVO/AI Act/CRA/NIS2/Data Act Implikationen bei CE-Gefaehrdungen. 50 Trigger-Mappings auf Hazard-Patterns → Compliance-Module mit Modul-Links. - compliance_triggers.go: 50 Pattern→Regulation Mappings - compliance_crossover.go: Engine die Projekt-Hazards gegen Trigger prueft - iace_handler_compliance.go: GET /compliance-triggers API - ComplianceAlerts.tsx: Frontend Alert-Panel auf Projekt-Uebersicht Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface ComplianceTrigger {
|
||||
id: string
|
||||
regulation: string
|
||||
article: string
|
||||
title: string
|
||||
severity: 'high' | 'medium' | 'low'
|
||||
reason: string
|
||||
affected_hazard_count?: number
|
||||
module_path: string
|
||||
module_label: string
|
||||
}
|
||||
|
||||
interface TriggersResponse {
|
||||
triggers: ComplianceTrigger[]
|
||||
total: number
|
||||
}
|
||||
|
||||
const SEVERITY_CONFIG: Record<string, { border: string; bg: string; text: string; badge: string; icon: string }> = {
|
||||
high: {
|
||||
border: 'border-red-200 dark:border-red-800',
|
||||
bg: 'bg-red-50 dark:bg-red-900/20',
|
||||
text: 'text-red-700 dark:text-red-400',
|
||||
badge: 'bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300',
|
||||
icon: 'text-red-500',
|
||||
},
|
||||
medium: {
|
||||
border: 'border-yellow-200 dark:border-yellow-800',
|
||||
bg: 'bg-yellow-50 dark:bg-yellow-900/20',
|
||||
text: 'text-yellow-700 dark:text-yellow-400',
|
||||
badge: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-300',
|
||||
icon: 'text-yellow-500',
|
||||
},
|
||||
low: {
|
||||
border: 'border-blue-200 dark:border-blue-800',
|
||||
bg: 'bg-blue-50 dark:bg-blue-900/20',
|
||||
text: 'text-blue-700 dark:text-blue-400',
|
||||
badge: 'bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300',
|
||||
icon: 'text-blue-500',
|
||||
},
|
||||
}
|
||||
|
||||
const SEVERITY_LABELS: Record<string, string> = {
|
||||
high: 'HOCH',
|
||||
medium: 'MITTEL',
|
||||
low: 'NIEDRIG',
|
||||
}
|
||||
|
||||
const REGULATION_BADGES: { key: string; label: string; activeColor: string }[] = [
|
||||
{ key: 'DSGVO', label: 'DSGVO', activeColor: 'bg-red-100 text-red-800 border-red-300' },
|
||||
{ key: 'AI Act', label: 'AI Act', activeColor: 'bg-orange-100 text-orange-800 border-orange-300' },
|
||||
{ key: 'CRA', label: 'CRA', activeColor: 'bg-yellow-100 text-yellow-800 border-yellow-300' },
|
||||
{ key: 'NIS2', label: 'NIS2', activeColor: 'bg-indigo-100 text-indigo-800 border-indigo-300' },
|
||||
{ key: 'Data Act', label: 'Data Act', activeColor: 'bg-amber-100 text-amber-800 border-amber-300' },
|
||||
]
|
||||
|
||||
function WarningIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4.5c-.77-.833-2.694-.833-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function ChevronIcon({ open }: { open: boolean }) {
|
||||
return (
|
||||
<svg className={`w-4 h-4 text-gray-400 transition-transform ${open ? 'rotate-180' : ''}`}
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ComplianceAlerts({ projectId }: { projectId: string }) {
|
||||
const [data, setData] = useState<TriggersResponse | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set())
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/sdk/v1/iace/projects/${projectId}/compliance-triggers`)
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((json) => {
|
||||
if (json?.triggers) setData(json)
|
||||
else if (Array.isArray(json)) setData({ triggers: json, total: json.length })
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false))
|
||||
}, [projectId])
|
||||
|
||||
if (loading) return null
|
||||
if (!data || data.triggers.length === 0) return null
|
||||
|
||||
const triggers = data.triggers
|
||||
const activeRegulations = new Set(triggers.map((t) => t.regulation))
|
||||
|
||||
function toggleExpanded(id: string) {
|
||||
setExpandedIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(id)) next.delete(id)
|
||||
else next.add(id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl border border-red-200 dark:border-red-800">
|
||||
{/* Header */}
|
||||
<button
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
className="w-full flex items-center justify-between p-6 text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-red-50 dark:bg-red-900/30 rounded-lg flex items-center justify-center">
|
||||
<WarningIcon className="w-5 h-5 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold text-gray-900 dark:text-white">
|
||||
{triggers.length} Compliance-Hinweise erkannt
|
||||
</h2>
|
||||
<p className="text-xs text-gray-500">
|
||||
Basierend auf den identifizierten Gefaehrdungen bestehen rechtliche Implikationen
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0">
|
||||
<ChevronIcon open={!collapsed} />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{!collapsed && (
|
||||
<div className="px-6 pb-6 space-y-4">
|
||||
{/* Regulation summary badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{REGULATION_BADGES.map((reg) => {
|
||||
const active = activeRegulations.has(reg.key)
|
||||
return (
|
||||
<span
|
||||
key={reg.key}
|
||||
className={`px-2.5 py-1 text-xs font-medium rounded-full border ${
|
||||
active
|
||||
? reg.activeColor
|
||||
: 'bg-gray-50 text-gray-400 border-gray-200 dark:bg-gray-700 dark:text-gray-500 dark:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
{reg.label}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Trigger list */}
|
||||
<div className="space-y-2">
|
||||
{triggers.map((trigger) => {
|
||||
const sev = SEVERITY_CONFIG[trigger.severity] || SEVERITY_CONFIG.low
|
||||
const isOpen = expandedIds.has(trigger.id)
|
||||
|
||||
return (
|
||||
<div key={trigger.id} className={`rounded-lg border ${sev.border} ${sev.bg} overflow-hidden`}>
|
||||
{/* Trigger header row */}
|
||||
<button
|
||||
onClick={() => toggleExpanded(trigger.id)}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 text-left"
|
||||
>
|
||||
<ChevronIcon open={isOpen} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{trigger.regulation} {trigger.article} — {trigger.title}
|
||||
</span>
|
||||
</div>
|
||||
<span className={`px-2 py-0.5 text-xs font-bold rounded ${sev.badge}`}>
|
||||
{SEVERITY_LABELS[trigger.severity] || trigger.severity}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Expanded detail */}
|
||||
{isOpen && (
|
||||
<div className="px-4 pb-4 pt-0 ml-7 space-y-2">
|
||||
<p className="text-xs text-gray-700 dark:text-gray-300">
|
||||
<span className="font-medium">Grund:</span> {trigger.reason}
|
||||
</p>
|
||||
{trigger.affected_hazard_count != null && trigger.affected_hazard_count > 0 && (
|
||||
<p className="text-xs text-gray-500">
|
||||
Betroffene Gefaehrdungen: {trigger.affected_hazard_count}
|
||||
</p>
|
||||
)}
|
||||
<Link
|
||||
href={trigger.module_path}
|
||||
className={`inline-flex items-center gap-1.5 text-xs font-medium ${sev.text} hover:underline`}
|
||||
>
|
||||
{trigger.module_label} oeffnen
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
|
||||
</svg>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Disclaimer */}
|
||||
<div className="p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 text-xs text-amber-800 dark:text-amber-300">
|
||||
<strong>Hinweis:</strong> Diese Compliance-Hinweise werden automatisch aus den
|
||||
Gefaehrdungen und Klassifikationen abgeleitet. Der CE-Fachmann muss die
|
||||
regulatorischen Anforderungen im jeweiligen Modul verifizieren.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user