'use client' /** * Compliance Hub Page (SDK Version - Zusatzmodul) * * Central compliance management dashboard with tabs: * - Uebersicht: Score, Stats, Quick Access, Findings * - Roadmap: 4-column Kanban (Quick Wins / Must Have / Should Have / Nice to Have) * - Module: Grid with module cards + progress bars * - Trend: Score history chart */ import { useState, useEffect } from 'react' import Link from 'next/link' import { ConfidenceLevelBadge } from '../evidence/components/anti-fake-badges' // Types interface DashboardData { compliance_score: number total_regulations: number total_requirements: number total_controls: number controls_by_status: Record controls_by_domain: Record> total_evidence: number evidence_by_status: Record total_risks: number risks_by_level: Record multi_score?: { requirement_coverage: number evidence_strength: number validation_quality: number evidence_freshness: number control_effectiveness: number overall_readiness: number hard_blocks: string[] } | null } interface Regulation { id: string code: string name: string full_name: string regulation_type: string effective_date: string | null description: string requirement_count: number } interface MappingsData { total: number by_regulation: Record } interface FindingsData { major_count: number minor_count: number ofi_count: number total: number open_majors: number open_minors: number } interface RoadmapItem { id: string control_id: string title: string status: string domain: string owner: string | null next_review_at: string | null days_overdue: number weight: number } interface RoadmapData { buckets: Record counts: Record } interface ModuleInfo { key: string label: string count: number status: string progress: number } interface ModuleStatusData { modules: ModuleInfo[] total: number started: number complete: number overall_progress: number } interface NextAction { id: string control_id: string title: string status: string domain: string owner: string | null days_overdue: number urgency_score: number reason: string } interface ScoreSnapshot { id: string score: number controls_total: number controls_pass: number snapshot_date: string created_at: string } interface TraceabilityAssertion { id: string sentence_text: string assertion_type: string confidence: number verified: boolean } interface TraceabilityEvidence { id: string title: string evidence_type: string confidence_level: string status: string assertions: TraceabilityAssertion[] } interface TraceabilityCoverage { has_evidence: boolean has_assertions: boolean all_assertions_verified: boolean min_confidence_level: string | null } interface TraceabilityControl { id: string control_id: string title: string status: string domain: string evidence: TraceabilityEvidence[] coverage: TraceabilityCoverage } interface TraceabilityMatrixData { controls: TraceabilityControl[] summary: Record } type TabKey = 'overview' | 'roadmap' | 'modules' | 'trend' | 'traceability' const DOMAIN_LABELS: Record = { gov: 'Governance', priv: 'Datenschutz', iam: 'Identity & Access', crypto: 'Kryptografie', sdlc: 'Secure Dev', ops: 'Operations', ai: 'KI-spezifisch', cra: 'Supply Chain', aud: 'Audit', } const BUCKET_LABELS: Record = { quick_wins: { label: 'Quick Wins', color: 'text-green-700', bg: 'bg-green-50 border-green-200' }, must_have: { label: 'Must Have', color: 'text-red-700', bg: 'bg-red-50 border-red-200' }, should_have: { label: 'Should Have', color: 'text-yellow-700', bg: 'bg-yellow-50 border-yellow-200' }, nice_to_have: { label: 'Nice to Have', color: 'text-slate-700', bg: 'bg-slate-50 border-slate-200' }, } const MODULE_ICONS: Record = { vvt: '๐Ÿ“‹', tom: '๐Ÿ”’', dsfa: 'โš ๏ธ', loeschfristen: '๐Ÿ—‘๏ธ', risks: '๐ŸŽฏ', controls: 'โœ…', evidence: '๐Ÿ“Ž', obligations: '๐Ÿ“œ', incidents: '๐Ÿšจ', vendor: '๐Ÿค', legal_templates: '๐Ÿ“„', training: '๐ŸŽ“', audit: '๐Ÿ”', security_backlog: '๐Ÿ›ก๏ธ', quality: 'โญ', } export default function ComplianceHubPage() { const [activeTab, setActiveTab] = useState('overview') const [dashboard, setDashboard] = useState(null) const [regulations, setRegulations] = useState([]) const [mappings, setMappings] = useState(null) const [findings, setFindings] = useState(null) const [roadmap, setRoadmap] = useState(null) const [moduleStatus, setModuleStatus] = useState(null) const [nextActions, setNextActions] = useState([]) const [scoreHistory, setScoreHistory] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [seeding, setSeeding] = useState(false) const [savingSnapshot, setSavingSnapshot] = useState(false) const [evidenceDistribution, setEvidenceDistribution] = useState<{ by_confidence: Record four_eyes_pending: number total: number } | null>(null) const [traceabilityMatrix, setTraceabilityMatrix] = useState(null) const [traceabilityLoading, setTraceabilityLoading] = useState(false) const [traceabilityFilter, setTraceabilityFilter] = useState<'all' | 'covered' | 'uncovered' | 'fully_verified'>('all') const [traceabilityDomainFilter, setTraceabilityDomainFilter] = useState('all') const [expandedControls, setExpandedControls] = useState>(new Set()) const [expandedEvidence, setExpandedEvidence] = useState>(new Set()) useEffect(() => { loadData() }, []) useEffect(() => { if (activeTab === 'roadmap' && !roadmap) loadRoadmap() if (activeTab === 'modules' && !moduleStatus) loadModuleStatus() if (activeTab === 'trend' && scoreHistory.length === 0) loadScoreHistory() if (activeTab === 'traceability' && !traceabilityMatrix) loadTraceabilityMatrix() }, [activeTab]) // eslint-disable-line react-hooks/exhaustive-deps const loadData = async () => { setLoading(true) setError(null) try { const [dashboardRes, regulationsRes, mappingsRes, findingsRes, actionsRes] = await Promise.all([ fetch('/api/sdk/v1/compliance/dashboard'), fetch('/api/sdk/v1/compliance/regulations'), fetch('/api/sdk/v1/compliance/mappings'), fetch('/api/sdk/v1/isms/findings?status=open'), fetch('/api/sdk/v1/compliance/dashboard/next-actions?limit=5'), ]) if (dashboardRes.ok) setDashboard(await dashboardRes.json()) if (regulationsRes.ok) { const data = await regulationsRes.json() setRegulations(data.regulations || []) } if (mappingsRes.ok) setMappings(await mappingsRes.json()) if (findingsRes.ok) setFindings(await findingsRes.json()) if (actionsRes.ok) { const data = await actionsRes.json() setNextActions(data.actions || []) } // Evidence distribution (Anti-Fake-Evidence Phase 3) try { const evidenceDistRes = await fetch('/api/sdk/v1/compliance/dashboard/evidence-distribution') if (evidenceDistRes.ok) setEvidenceDistribution(await evidenceDistRes.json()) } catch { /* silent */ } } catch (err) { console.error('Failed to load compliance data:', err) setError('Verbindung zum Backend fehlgeschlagen') } finally { setLoading(false) } } const loadRoadmap = async () => { try { const res = await fetch('/api/sdk/v1/compliance/dashboard/roadmap') if (res.ok) setRoadmap(await res.json()) } catch { /* silent */ } } const loadModuleStatus = async () => { try { const res = await fetch('/api/sdk/v1/compliance/dashboard/module-status') if (res.ok) setModuleStatus(await res.json()) } catch { /* silent */ } } const loadScoreHistory = async () => { try { const res = await fetch('/api/sdk/v1/compliance/dashboard/score-history?months=12') if (res.ok) { const data = await res.json() setScoreHistory(data.snapshots || []) } } catch { /* silent */ } } const loadTraceabilityMatrix = async () => { setTraceabilityLoading(true) try { const res = await fetch('/api/sdk/v1/compliance/dashboard/traceability-matrix') if (res.ok) setTraceabilityMatrix(await res.json()) } catch { /* silent */ } finally { setTraceabilityLoading(false) } } const toggleControlExpanded = (id: string) => { setExpandedControls(prev => { const next = new Set(prev) if (next.has(id)) next.delete(id); else next.add(id) return next }) } const toggleEvidenceExpanded = (id: string) => { setExpandedEvidence(prev => { const next = new Set(prev) if (next.has(id)) next.delete(id); else next.add(id) return next }) } const saveSnapshot = async () => { setSavingSnapshot(true) try { const res = await fetch('/api/sdk/v1/compliance/dashboard/snapshot', { method: 'POST' }) if (res.ok) { loadScoreHistory() } } catch { /* silent */ } finally { setSavingSnapshot(false) } } const seedDatabase = async () => { setSeeding(true) try { const res = await fetch('/api/sdk/v1/compliance/seed', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: false }), }) if (res.ok) { const result = await res.json() alert(`Datenbank erfolgreich initialisiert!\n\nRegulations: ${result.counts?.regulations || 0}\nControls: ${result.counts?.controls || 0}\nRequirements: ${result.counts?.requirements || 0}`) loadData() } else { const error = await res.text() alert(`Fehler beim Seeding: ${error}`) } } catch (err) { console.error('Seeding failed:', err) alert('Fehler beim Initialisieren der Datenbank') } finally { setSeeding(false) } } const score = dashboard?.compliance_score || 0 const scoreColor = score >= 80 ? 'text-green-600' : score >= 60 ? 'text-yellow-600' : 'text-red-600' const scoreBgColor = score >= 80 ? 'bg-green-500' : score >= 60 ? 'bg-yellow-500' : 'bg-red-500' const tabs: { key: TabKey; label: string }[] = [ { key: 'overview', label: 'Uebersicht' }, { key: 'roadmap', label: 'Roadmap' }, { key: 'modules', label: 'Module' }, { key: 'trend', label: 'Trend' }, { key: 'traceability', label: 'Traceability' }, ] return (
{/* Title Card */}

Compliance Hub

Zentrale Verwaltung aller Compliance-Anforderungen nach DSGVO, AI Act, BSI TR-03161 und weiteren Regulierungen.

{/* Tabs */}
{tabs.map(tab => ( ))}
{/* Error Banner */} {error && (
{error}
)} {/* Seed Button if no data */} {!loading && (dashboard?.total_controls || 0) === 0 && (

Keine Compliance-Daten vorhanden

Initialisieren Sie die Datenbank mit den Seed-Daten.

)} {loading ? (
) : ( <> {/* ============================================================ */} {/* TAB: Uebersicht */} {/* ============================================================ */} {activeTab === 'overview' && ( <> {/* Quick Actions */}

Schnellzugriff

{[ { href: '/sdk/audit-checklist', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01', label: 'Audit Checkliste', sub: `${dashboard?.total_requirements || '...'} Anforderungen`, color: 'purple' }, { href: '/sdk/controls', icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z', label: 'Controls', sub: `${dashboard?.total_controls || '...'} Massnahmen`, color: 'green' }, { href: '/sdk/evidence', icon: 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z', label: 'Evidence', sub: 'Nachweise', color: 'blue' }, { href: '/sdk/risks', icon: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z', label: 'Risk Matrix', sub: '5x5 Risiken', color: 'red' }, { href: '/sdk/process-tasks', icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4', label: 'Prozesse', sub: 'Aufgaben', color: 'indigo' }, { href: '/sdk/audit-report', icon: 'M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z', label: 'Audit Report', sub: 'PDF Export', color: 'orange' }, ].map(item => (

{item.label}

{item.sub}

))}
{/* Score and Stats Row */}

Compliance Score

{score.toFixed(0)}%

{dashboard?.controls_by_status?.pass || 0} von {dashboard?.total_controls || 0} Controls bestanden

{[ { label: 'Verordnungen', value: dashboard?.total_regulations || 0, sub: `${dashboard?.total_requirements || 0} Anforderungen`, iconColor: 'blue', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' }, { label: 'Controls', value: dashboard?.total_controls || 0, sub: `${dashboard?.controls_by_status?.pass || 0} bestanden`, iconColor: 'green', icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z' }, { label: 'Nachweise', value: dashboard?.total_evidence || 0, sub: `${dashboard?.evidence_by_status?.valid || 0} aktiv`, iconColor: 'purple', icon: 'M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z' }, { label: 'Risiken', value: dashboard?.total_risks || 0, sub: `${(dashboard?.risks_by_level?.high || 0) + (dashboard?.risks_by_level?.critical || 0)} kritisch`, iconColor: 'red', icon: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z' }, ].map(stat => (

{stat.label}

{stat.value}

{stat.sub}

))}
{/* Anti-Fake-Evidence Section (Phase 3) */} {dashboard && (

Anti-Fake-Evidence Status

{/* Confidence Distribution Bar */} {evidenceDistribution && evidenceDistribution.total > 0 && (

Confidence-Verteilung ({evidenceDistribution.total} Nachweise)

{(['E0', 'E1', 'E2', 'E3', 'E4'] as const).map(level => { const count = evidenceDistribution.by_confidence[level] || 0 const pct = (count / evidenceDistribution.total) * 100 if (pct === 0) return null const colors: Record = { E0: 'bg-red-400', E1: 'bg-yellow-400', E2: 'bg-blue-400', E3: 'bg-green-400', E4: 'bg-emerald-400' } return (
{pct >= 10 ? `${level} (${count})` : ''}
) })}
{(['E0', 'E1', 'E2', 'E3', 'E4'] as const).map(level => { const count = evidenceDistribution.by_confidence[level] || 0 const dotColors: Record = { E0: 'bg-red-400', E1: 'bg-yellow-400', E2: 'bg-blue-400', E3: 'bg-green-400', E4: 'bg-emerald-400' } return ( {level}: {count} ) })}
)} {/* Multi-Score Dimensions */} {dashboard.multi_score && (

Multi-dimensionaler Score

{([ { key: 'requirement_coverage', label: 'Anforderungsabdeckung', color: 'bg-blue-500' }, { key: 'evidence_strength', label: 'Evidence-Staerke', color: 'bg-green-500' }, { key: 'validation_quality', label: 'Validierungsqualitaet', color: 'bg-purple-500' }, { key: 'evidence_freshness', label: 'Aktualitaet', color: 'bg-yellow-500' }, { key: 'control_effectiveness', label: 'Control-Wirksamkeit', color: 'bg-indigo-500' }, ] as const).map(dim => { const value = (dashboard.multi_score as Record)[dim.key] || 0 return (
{dim.label}
{typeof value === 'number' ? value.toFixed(0) : value}%
) })}
Audit-Readiness
= 80 ? 'bg-green-500' : (dashboard.multi_score.overall_readiness || 0) >= 60 ? 'bg-yellow-500' : 'bg-red-500' }`} style={{ width: `${dashboard.multi_score.overall_readiness || 0}%` }} />
{typeof dashboard.multi_score.overall_readiness === 'number' ? dashboard.multi_score.overall_readiness.toFixed(0) : 0}%
)} {/* Bottom row: Four-Eyes + Hard Blocks */}
{evidenceDistribution?.four_eyes_pending || 0}
Four-Eyes Reviews ausstehend
{dashboard.multi_score?.hard_blocks && dashboard.multi_score.hard_blocks.length > 0 ? (
Hard Blocks ({dashboard.multi_score.hard_blocks.length})
    {dashboard.multi_score.hard_blocks.slice(0, 3).map((block: string, i: number) => (
  • {block}
  • ))}
) : (
0
Keine Hard Blocks
)}
)} {/* Next Actions + Findings */}
{/* Next Actions */}

Naechste Aktionen

{nextActions.length === 0 ? (

Keine offenen Aktionen.

) : (
{nextActions.map(action => (
0 ? 'bg-red-500' : 'bg-yellow-500' }`} />

{action.title}

{action.control_id} ยท {DOMAIN_LABELS[action.domain] || action.domain} {action.days_overdue > 0 && {action.days_overdue}d ueberfaellig}

{action.status}
))}
)}
{/* Audit Findings */}

Audit Findings

Audit Checkliste โ†’
Hauptabweichungen

{findings?.open_majors || 0}

offen (blockiert Zertifizierung)

Nebenabweichungen

{findings?.open_minors || 0}

offen (erfordert CAPA)

Gesamt: {findings?.total || 0} Findings ({findings?.major_count || 0} Major, {findings?.minor_count || 0} Minor, {findings?.ofi_count || 0} OFI) {(findings?.open_majors || 0) === 0 ? ( Zertifizierung moeglich ) : ( Zertifizierung blockiert )}
{/* Control-Mappings & Domain Chart */}

Control-Mappings

Alle anzeigen โ†’

{mappings?.total || 0}

Mappings gesamt

Nach Verordnung

{mappings?.by_regulation && Object.entries(mappings.by_regulation).slice(0, 5).map(([reg, count]) => ( {reg}: {count} ))} {!mappings?.by_regulation && ( Keine Mappings vorhanden )}

Controls nach Domain

{Object.entries(dashboard?.controls_by_domain || {}).slice(0, 6).map(([domain, stats]) => { const total = stats.total || 0 const pass = stats.pass || 0 const partial = stats.partial || 0 const passPercent = total > 0 ? ((pass + partial * 0.5) / total) * 100 : 0 return (
{DOMAIN_LABELS[domain] || domain}
{passPercent.toFixed(0)}%
) })}
{/* Regulations Table */}

Verordnungen & Standards ({regulations.length})

{regulations.slice(0, 15).map((reg) => ( ))}
Code Name Typ Anforderungen
{reg.code}

{reg.name}

{reg.regulation_type === 'eu_regulation' ? 'EU-VO' : reg.regulation_type === 'eu_directive' ? 'EU-RL' : reg.regulation_type === 'bsi_standard' ? 'BSI' : reg.regulation_type === 'de_law' ? 'DE' : reg.regulation_type} {reg.requirement_count}
)} {/* ============================================================ */} {/* TAB: Roadmap */} {/* ============================================================ */} {activeTab === 'roadmap' && (
{!roadmap ? (
) : (
{(['quick_wins', 'must_have', 'should_have', 'nice_to_have'] as const).map(bucketKey => { const meta = BUCKET_LABELS[bucketKey] const items = roadmap.buckets[bucketKey] || [] return (

{meta.label}

{items.length}
{items.length === 0 ? (

Keine Eintraege

) : ( items.map(item => (

{item.title}

{item.control_id} ยท {DOMAIN_LABELS[item.domain] || item.domain}
{item.days_overdue > 0 && (

{item.days_overdue}d ueberfaellig

)} {item.owner && (

{item.owner}

)}
)) )}
) })}
)}
)} {/* ============================================================ */} {/* TAB: Module */} {/* ============================================================ */} {activeTab === 'modules' && (
{!moduleStatus ? (
) : ( <> {/* Summary */}

Gesamt-Fortschritt

{moduleStatus.overall_progress.toFixed(0)}%

Module gestartet

{moduleStatus.started}/{moduleStatus.total}

Module abgeschlossen

{moduleStatus.complete}/{moduleStatus.total}

{/* Module Grid */}
{moduleStatus.modules.map(mod => (
{MODULE_ICONS[mod.key] || '๐Ÿ“ฆ'}

{mod.label}

{mod.count} Eintraege

{mod.status === 'complete' ? 'Fertig' : mod.status === 'in_progress' ? 'In Arbeit' : 'Offen'}
))}
)}
)} {/* ============================================================ */} {/* TAB: Trend */} {/* ============================================================ */} {activeTab === 'trend' && (

Score-Verlauf

{scoreHistory.length === 0 ? (

Noch keine Score-Snapshots vorhanden.

Klicken Sie auf "Aktuellen Score speichern", um den ersten Datenpunkt zu erstellen.

) : ( <> {/* Simple SVG Line Chart */}
{/* Grid lines */} {[0, 25, 50, 75, 100].map(pct => ( ))} {/* Score line */} { const x = scoreHistory.length === 1 ? 400 : (i / (scoreHistory.length - 1)) * 780 + 10 const y = 200 - (s.score / 100) * 200 return `${x},${y}` }).join(' ')} /> {/* Points */} {scoreHistory.map((s, i) => { const x = scoreHistory.length === 1 ? 400 : (i / (scoreHistory.length - 1)) * 780 + 10 const y = 200 - (s.score / 100) * 200 return ( ) })} {/* Y-axis labels */}
100% 75% 50% 25% 0%
{/* Snapshot Table */}
{scoreHistory.slice().reverse().map(snap => ( ))}
Datum Score Controls Bestanden
{new Date(snap.snapshot_date).toLocaleDateString('de-DE')} = 80 ? 'text-green-600' : snap.score >= 60 ? 'text-yellow-600' : 'text-red-600' }`}> {typeof snap.score === 'number' ? snap.score.toFixed(1) : snap.score}% {snap.controls_total} {snap.controls_pass}
)}
)} {/* Traceability Tab */} {activeTab === 'traceability' && (
{traceabilityLoading ? (
Traceability Matrix wird geladen...
) : !traceabilityMatrix ? (
Keine Daten verfuegbar. Stellen Sie sicher, dass Controls und Evidence vorhanden sind.
) : (() => { const summary = traceabilityMatrix.summary const totalControls = summary.total_controls || 0 const covered = summary.covered || 0 const fullyVerified = summary.fully_verified || 0 const uncovered = summary.uncovered || 0 const filteredControls = (traceabilityMatrix.controls || []).filter(ctrl => { if (traceabilityFilter === 'covered' && !ctrl.coverage.has_evidence) return false if (traceabilityFilter === 'uncovered' && ctrl.coverage.has_evidence) return false if (traceabilityFilter === 'fully_verified' && !ctrl.coverage.all_assertions_verified) return false if (traceabilityDomainFilter !== 'all' && ctrl.domain !== traceabilityDomainFilter) return false return true }) const domains = [...new Set(traceabilityMatrix.controls.map(c => c.domain))].sort() return ( <> {/* Summary Cards */}
{totalControls}
Total Controls
{covered}
Abgedeckt
{fullyVerified}
Vollst. verifiziert
{uncovered}
Unabgedeckt
{/* Filter Bar */}
{([ { key: 'all', label: 'Alle' }, { key: 'covered', label: 'Abgedeckt' }, { key: 'uncovered', label: 'Nicht abgedeckt' }, { key: 'fully_verified', label: 'Vollst. verifiziert' }, ] as const).map(f => ( ))}
{domains.map(d => ( ))}
{/* Controls List */}
{filteredControls.length === 0 ? (
Keine Controls fuer diesen Filter gefunden.
) : filteredControls.map(ctrl => { const isExpanded = expandedControls.has(ctrl.id) const coverageIcon = ctrl.coverage.all_assertions_verified ? { symbol: '\u2713', color: 'text-green-600 bg-green-50' } : ctrl.coverage.has_evidence ? { symbol: '\u25D0', color: 'text-yellow-600 bg-yellow-50' } : { symbol: '\u2717', color: 'text-red-600 bg-red-50' } return (
{/* Control Row */} {/* Expanded: Evidence list */} {isExpanded && (
{ctrl.evidence.length === 0 ? (
Kein Evidence verknuepft.
) : ctrl.evidence.map(ev => { const evExpanded = expandedEvidence.has(ev.id) return (
{/* Expanded: Assertions list */} {evExpanded && ev.assertions.length > 0 && (
{ev.assertions.map(a => ( ))}
Aussage Typ Konfidenz Status
{a.sentence_text} {a.assertion_type} = 0.8 ? 'text-green-600' : a.confidence >= 0.5 ? 'text-yellow-600' : 'text-red-600' }`}> {(a.confidence * 100).toFixed(0)}% {a.verified ? {'\u2713'} : {'\u2717'} }
)}
) })}
)}
) })}
) })()}
)} )}
) }