Files
breakpilot-compliance/admin-compliance/app/sdk/iace/[projectId]/classification/page.tsx
Benjamin Admin 215b95adfa
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 19s
refactor: Admin-Layout komplett entfernt — SDK als einziges Layout
Kaputtes (admin) Layout geloescht (Role-Selection, 404-Sidebar, localhost-Dashboard).
SDK-Flow nach /sdk/sdk-flow verschoben. Route-Gruppe (sdk) aufgeloest.
Root-Seite redirected auf /sdk. ~25 ungenutzte Dateien/Verzeichnisse entfernt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 11:43:00 +01:00

278 lines
9.4 KiB
TypeScript

'use client'
import React, { useState, useEffect } from 'react'
import { useParams } from 'next/navigation'
interface Classification {
id: string
regulation: string
regulation_label: string
classification_result: string
risk_level: string
confidence: number
reasoning: string
classified_at: string | null
}
const REGULATIONS = [
{
key: 'ai_act',
label: 'AI Act',
description: 'EU-Verordnung ueber kuenstliche Intelligenz (2024/1689)',
icon: '🤖',
},
{
key: 'machinery_regulation',
label: 'Maschinenverordnung',
description: 'EU-Maschinenverordnung (2023/1230)',
icon: '⚙️',
},
{
key: 'cra',
label: 'Cyber Resilience Act',
description: 'EU-Verordnung ueber Cyberresilienz',
icon: '🔒',
},
{
key: 'nis2',
label: 'NIS2',
description: 'Richtlinie ueber Netz- und Informationssicherheit',
icon: '🌐',
},
]
function RiskLevelBadge({ level }: { level: string }) {
const colors: Record<string, string> = {
unacceptable: 'bg-black text-white',
high: 'bg-red-100 text-red-700',
limited: 'bg-yellow-100 text-yellow-700',
minimal: 'bg-green-100 text-green-700',
not_applicable: 'bg-gray-100 text-gray-500',
critical: 'bg-red-100 text-red-700',
important: 'bg-orange-100 text-orange-700',
default_category: 'bg-blue-100 text-blue-700',
essential: 'bg-orange-100 text-orange-700',
non_essential: 'bg-green-100 text-green-700',
}
const labels: Record<string, string> = {
unacceptable: 'Unakzeptabel',
high: 'Hochrisiko',
limited: 'Begrenztes Risiko',
minimal: 'Minimales Risiko',
not_applicable: 'Nicht anwendbar',
critical: 'Kritisch',
important: 'Wichtig',
default_category: 'Standard',
essential: 'Wesentlich',
non_essential: 'Nicht wesentlich',
}
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${colors[level] || 'bg-gray-100 text-gray-700'}`}>
{labels[level] || level}
</span>
)
}
function ConfidenceBar({ value }: { value: number }) {
const pct = Math.round(value * 100)
const color = pct >= 80 ? 'bg-green-500' : pct >= 60 ? 'bg-yellow-500' : 'bg-orange-500'
return (
<div className="flex items-center gap-2">
<div className="flex-1 bg-gray-200 rounded-full h-1.5">
<div className={`${color} h-1.5 rounded-full`} style={{ width: `${pct}%` }} />
</div>
<span className="text-xs text-gray-500">{pct}%</span>
</div>
)
}
function ClassificationCard({
regulation,
classification,
onReclassify,
classifying,
}: {
regulation: (typeof REGULATIONS)[number]
classification: Classification | null
onReclassify: (regKey: string) => void
classifying: boolean
}) {
return (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<span className="text-2xl">{regulation.icon}</span>
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">{regulation.label}</h3>
<p className="text-xs text-gray-500">{regulation.description}</p>
</div>
</div>
<button
onClick={() => onReclassify(regulation.key)}
disabled={classifying}
className="text-xs px-3 py-1.5 border border-purple-300 text-purple-700 rounded-lg hover:bg-purple-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{classifying ? 'Laeuft...' : classification ? 'Neu klassifizieren' : 'Klassifizieren'}
</button>
</div>
{classification ? (
<div className="space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Ergebnis</span>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{classification.classification_result}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-500">Risikostufe</span>
<RiskLevelBadge level={classification.risk_level} />
</div>
<div>
<span className="text-sm text-gray-500">Konfidenz</span>
<ConfidenceBar value={classification.confidence} />
</div>
<div className="pt-3 border-t border-gray-100 dark:border-gray-700">
<span className="text-xs text-gray-500 block mb-1">Begruendung</span>
<p className="text-sm text-gray-700 dark:text-gray-300">{classification.reasoning}</p>
</div>
{classification.classified_at && (
<div className="text-xs text-gray-400">
Klassifiziert am: {new Date(classification.classified_at).toLocaleDateString('de-DE')}
</div>
)}
</div>
) : (
<div className="py-8 text-center">
<div className="w-12 h-12 mx-auto bg-gray-100 rounded-full flex items-center justify-center mb-3">
<svg className="w-6 h-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<p className="text-sm text-gray-500">Noch nicht klassifiziert</p>
<p className="text-xs text-gray-400 mt-1">Klicken Sie &quot;Klassifizieren&quot; um die Analyse zu starten</p>
</div>
)}
</div>
)
}
export default function ClassificationPage() {
const params = useParams()
const projectId = params.projectId as string
const [classifications, setClassifications] = useState<Classification[]>([])
const [loading, setLoading] = useState(true)
const [classifyingAll, setClassifyingAll] = useState(false)
const [classifyingReg, setClassifyingReg] = useState<string | null>(null)
useEffect(() => {
fetchClassifications()
}, [projectId])
async function fetchClassifications() {
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/classifications`)
if (res.ok) {
const json = await res.json()
setClassifications(json.classifications || json || [])
}
} catch (err) {
console.error('Failed to fetch classifications:', err)
} finally {
setLoading(false)
}
}
async function handleClassifyAll() {
setClassifyingAll(true)
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/classifications/classify-all`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
if (res.ok) {
await fetchClassifications()
}
} catch (err) {
console.error('Failed to classify all:', err)
} finally {
setClassifyingAll(false)
}
}
async function handleReclassify(regKey: string) {
setClassifyingReg(regKey)
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/classifications/${regKey}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
if (res.ok) {
await fetchClassifications()
}
} catch (err) {
console.error('Failed to reclassify:', err)
} finally {
setClassifyingReg(null)
}
}
function getClassificationForReg(regKey: string): Classification | null {
return classifications.find((c) => c.regulation === regKey) || null
}
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
</div>
)
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Regulatorische Klassifikation</h1>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Automatische Einordnung Ihrer Maschine/Anlage in die relevanten EU-Regulierungsrahmen.
</p>
</div>
<button
onClick={handleClassifyAll}
disabled={classifyingAll}
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{classifyingAll ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" />
Wird klassifiziert...
</>
) : (
<>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
Alle klassifizieren
</>
)}
</button>
</div>
{/* Classification Cards */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{REGULATIONS.map((reg) => (
<ClassificationCard
key={reg.key}
regulation={reg}
classification={getClassificationForReg(reg.key)}
onReclassify={handleReclassify}
classifying={classifyingReg === reg.key || classifyingAll}
/>
))}
</div>
</div>
)
}