Files
breakpilot-compliance/admin-compliance/app/(sdk)/sdk/use-cases/new/page.tsx
Benjamin Admin e6d666b89b
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 37s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 18s
feat: Vorbereitung-Module auf 100% — Persistenz, Backend-Services, UCCA Frontend
Phase A: PostgreSQL State Store (sdk_states Tabelle, InMemory-Fallback)
Phase B: Modules dynamisch vom Backend, Scope DB-Persistenz, Source Policy State
Phase C: UCCA Frontend (3 Seiten, Wizard, RiskScoreGauge), Obligations Live-Daten
Phase D: Document Import (PDF/LLM/Gap-Analyse), System Screening (SBOM/OSV.dev)
Phase E: Company Profile CRUD mit Audit-Logging
Phase F: Tests (Python + TypeScript), flow-data.ts DB-Tabellen aktualisiert

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

465 lines
19 KiB
TypeScript

'use client'
import React, { useState } from 'react'
import { useRouter } from 'next/navigation'
import { AssessmentResultCard } from '@/components/sdk/use-case-assessment/AssessmentResultCard'
// =============================================================================
// WIZARD STEPS CONFIG
// =============================================================================
const WIZARD_STEPS = [
{ id: 1, title: 'Grundlegendes', description: 'Titel und Beschreibung' },
{ id: 2, title: 'Datenkategorien', description: 'Welche Daten werden verarbeitet?' },
{ id: 3, title: 'Automatisierung', description: 'Grad der Automatisierung' },
{ id: 4, title: 'Hosting & Modell', description: 'Technische Details' },
{ id: 5, title: 'Datenhaltung', description: 'Aufbewahrung und Speicherung' },
]
const DOMAINS = [
{ value: 'healthcare', label: 'Gesundheit' },
{ value: 'finance', label: 'Finanzen' },
{ value: 'education', label: 'Bildung' },
{ value: 'retail', label: 'Handel' },
{ value: 'it_services', label: 'IT-Dienstleistungen' },
{ value: 'consulting', label: 'Beratung' },
{ value: 'manufacturing', label: 'Produktion' },
{ value: 'hr', label: 'Personalwesen' },
{ value: 'marketing', label: 'Marketing' },
{ value: 'legal', label: 'Recht' },
{ value: 'public', label: 'Oeffentlicher Sektor' },
{ value: 'general', label: 'Allgemein' },
]
// =============================================================================
// MAIN COMPONENT
// =============================================================================
export default function NewUseCasePage() {
const router = useRouter()
const [currentStep, setCurrentStep] = useState(1)
const [isSubmitting, setIsSubmitting] = useState(false)
const [result, setResult] = useState<unknown>(null)
const [error, setError] = useState<string | null>(null)
// Form state
const [form, setForm] = useState({
title: '',
use_case_text: '',
domain: 'general',
// Data Types
personal_data: false,
special_categories: false,
minors_data: false,
health_data: false,
biometric_data: false,
financial_data: false,
// Purpose
purpose_profiling: false,
purpose_automated_decision: false,
purpose_marketing: false,
purpose_analytics: false,
purpose_service_delivery: false,
// Automation
automation: 'assistive' as 'assistive' | 'semi_automated' | 'fully_automated',
// Hosting
hosting_provider: 'self_hosted',
hosting_region: 'eu',
// Model Usage
model_rag: false,
model_finetune: false,
model_training: false,
model_inference: true,
// Retention
retention_days: 90,
retention_purpose: '',
})
const updateForm = (updates: Partial<typeof form>) => {
setForm(prev => ({ ...prev, ...updates }))
}
const handleSubmit = async () => {
setIsSubmitting(true)
setError(null)
try {
const intake = {
title: form.title,
use_case_text: form.use_case_text,
domain: form.domain,
data_types: {
personal_data: form.personal_data,
special_categories: form.special_categories,
minors_data: form.minors_data,
health_data: form.health_data,
biometric_data: form.biometric_data,
financial_data: form.financial_data,
},
purpose: {
profiling: form.purpose_profiling,
automated_decision: form.purpose_automated_decision,
marketing: form.purpose_marketing,
analytics: form.purpose_analytics,
service_delivery: form.purpose_service_delivery,
},
automation: form.automation,
hosting: {
provider: form.hosting_provider,
region: form.hosting_region,
},
model_usage: {
rag: form.model_rag,
finetune: form.model_finetune,
training: form.model_training,
inference: form.model_inference,
},
retention: {
days: form.retention_days,
purpose: form.retention_purpose,
},
store_raw_text: true,
}
const response = await fetch('/api/sdk/v1/ucca/assess', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(intake),
})
if (!response.ok) {
const errData = await response.json().catch(() => null)
throw new Error(errData?.error || `HTTP ${response.status}`)
}
const data = await response.json()
setResult(data)
} catch (err) {
setError(err instanceof Error ? err.message : 'Fehler bei der Bewertung')
} finally {
setIsSubmitting(false)
}
}
// If we have a result, show it
if (result) {
const r = result as { assessment?: { id: string }; result?: Record<string, unknown> }
return (
<div className="max-w-4xl mx-auto space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900">Assessment Ergebnis</h1>
<div className="flex gap-2">
{r.assessment?.id && (
<button
onClick={() => router.push(`/sdk/use-cases/${r.assessment!.id}`)}
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"
>
Zum Assessment
</button>
)}
<button
onClick={() => router.push('/sdk/use-cases')}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200"
>
Zur Uebersicht
</button>
</div>
</div>
{r.result && (
<AssessmentResultCard result={r.result as Parameters<typeof AssessmentResultCard>[0]['result']} />
)}
</div>
)
}
return (
<div className="max-w-3xl mx-auto space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-gray-900">Neues Use Case Assessment</h1>
<p className="mt-1 text-gray-500">
Beschreiben Sie Ihren KI-Anwendungsfall Schritt fuer Schritt
</p>
</div>
{/* Step Indicator */}
<div className="flex items-center gap-2">
{WIZARD_STEPS.map((step, idx) => (
<React.Fragment key={step.id}>
<button
onClick={() => setCurrentStep(step.id)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors ${
currentStep === step.id
? 'bg-purple-600 text-white'
: currentStep > step.id
? 'bg-green-100 text-green-700'
: 'bg-gray-100 text-gray-500'
}`}
>
<span className="w-6 h-6 rounded-full bg-white/20 flex items-center justify-center text-xs font-bold">
{currentStep > step.id ? '✓' : step.id}
</span>
<span className="hidden md:inline">{step.title}</span>
</button>
{idx < WIZARD_STEPS.length - 1 && <div className="flex-1 h-px bg-gray-200" />}
</React.Fragment>
))}
</div>
{/* Error */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">{error}</div>
)}
{/* Step Content */}
<div className="bg-white rounded-xl border border-gray-200 p-6">
{/* Step 1: Grundlegendes */}
{currentStep === 1 && (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-gray-900">Grundlegende Informationen</h2>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Titel</label>
<input
type="text"
value={form.title}
onChange={e => updateForm({ title: e.target.value })}
placeholder="z.B. Chatbot fuer Kundenservice"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Beschreibung</label>
<textarea
value={form.use_case_text}
onChange={e => updateForm({ use_case_text: e.target.value })}
rows={4}
placeholder="Beschreiben Sie den Anwendungsfall..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Branche</label>
<select
value={form.domain}
onChange={e => updateForm({ domain: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500"
>
{DOMAINS.map(d => (
<option key={d.value} value={d.value}>{d.label}</option>
))}
</select>
</div>
</div>
)}
{/* Step 2: Datenkategorien */}
{currentStep === 2 && (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-gray-900">Welche Daten werden verarbeitet?</h2>
{[
{ key: 'personal_data', label: 'Personenbezogene Daten', desc: 'Name, E-Mail, Adresse etc.' },
{ key: 'special_categories', label: 'Besondere Kategorien (Art. 9)', desc: 'Religion, Gesundheit, politische Meinung' },
{ key: 'health_data', label: 'Gesundheitsdaten', desc: 'Diagnosen, Medikation, Fitness' },
{ key: 'biometric_data', label: 'Biometrische Daten', desc: 'Gesichtserkennung, Fingerabdruck, Stimme' },
{ key: 'minors_data', label: 'Daten von Minderjaehrigen', desc: 'Unter 16 Jahren' },
{ key: 'financial_data', label: 'Finanzdaten', desc: 'Kontodaten, Transaktionen, Kreditwuerdigkeit' },
].map(item => (
<label key={item.key} className="flex items-start gap-3 p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100">
<input
type="checkbox"
checked={form[item.key as keyof typeof form] as boolean}
onChange={e => updateForm({ [item.key]: e.target.checked })}
className="mt-1 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">{item.label}</div>
<div className="text-sm text-gray-500">{item.desc}</div>
</div>
</label>
))}
<h3 className="text-sm font-medium text-gray-700 mt-4">Zweck der Verarbeitung</h3>
{[
{ key: 'purpose_profiling', label: 'Profiling', desc: 'Automatisierte Analyse personenbezogener Aspekte' },
{ key: 'purpose_automated_decision', label: 'Automatisierte Entscheidung', desc: 'Art. 22 DSGVO — Entscheidung ohne menschliches Zutun' },
{ key: 'purpose_marketing', label: 'Marketing', desc: 'Werbung, Personalisierung, Targeting' },
{ key: 'purpose_analytics', label: 'Analytics', desc: 'Statistische Auswertung, Business Intelligence' },
{ key: 'purpose_service_delivery', label: 'Serviceerbringung', desc: 'Kernfunktion des Produkts/Services' },
].map(item => (
<label key={item.key} className="flex items-start gap-3 p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100">
<input
type="checkbox"
checked={form[item.key as keyof typeof form] as boolean}
onChange={e => updateForm({ [item.key]: e.target.checked })}
className="mt-1 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">{item.label}</div>
<div className="text-sm text-gray-500">{item.desc}</div>
</div>
</label>
))}
</div>
)}
{/* Step 3: Automatisierung */}
{currentStep === 3 && (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-gray-900">Grad der Automatisierung</h2>
{[
{ value: 'assistive', label: 'Assistiv', desc: 'KI unterstuetzt, Mensch entscheidet immer' },
{ value: 'semi_automated', label: 'Teilautomatisiert', desc: 'KI schlaegt vor, Mensch prueft und bestaetigt' },
{ value: 'fully_automated', label: 'Vollautomatisiert', desc: 'KI entscheidet autonom, Mensch ueberwacht nur' },
].map(item => (
<label
key={item.value}
className={`flex items-start gap-3 p-4 rounded-lg border-2 cursor-pointer transition-all ${
form.automation === item.value
? 'border-purple-500 bg-purple-50'
: 'border-gray-200 hover:border-gray-300'
}`}
>
<input
type="radio"
name="automation"
value={item.value}
checked={form.automation === item.value}
onChange={e => updateForm({ automation: e.target.value as typeof form.automation })}
className="mt-1 text-purple-600 focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">{item.label}</div>
<div className="text-sm text-gray-500">{item.desc}</div>
</div>
</label>
))}
</div>
)}
{/* Step 4: Hosting & Modell */}
{currentStep === 4 && (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-gray-900">Technische Details</h2>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Hosting</label>
<select
value={form.hosting_provider}
onChange={e => updateForm({ hosting_provider: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
>
<option value="self_hosted">Eigenes Hosting</option>
<option value="aws">AWS</option>
<option value="azure">Microsoft Azure</option>
<option value="gcp">Google Cloud</option>
<option value="hetzner">Hetzner (DE)</option>
<option value="other">Anderer Anbieter</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Region</label>
<select
value={form.hosting_region}
onChange={e => updateForm({ hosting_region: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
>
<option value="eu">EU</option>
<option value="de">Deutschland</option>
<option value="us">USA</option>
<option value="other">Andere</option>
</select>
</div>
<h3 className="text-sm font-medium text-gray-700 mt-4">Modell-Nutzung</h3>
{[
{ key: 'model_inference', label: 'Inferenz', desc: 'Vortrainiertes Modell nutzen (Standard)' },
{ key: 'model_rag', label: 'RAG (Retrieval-Augmented)', desc: 'Eigene Daten als Kontext bereitstellen' },
{ key: 'model_finetune', label: 'Fine-Tuning', desc: 'Modell mit eigenen Daten nachtrainieren' },
{ key: 'model_training', label: 'Training', desc: 'Eigenes Modell von Grund auf trainieren' },
].map(item => (
<label key={item.key} className="flex items-start gap-3 p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100">
<input
type="checkbox"
checked={form[item.key as keyof typeof form] as boolean}
onChange={e => updateForm({ [item.key]: e.target.checked })}
className="mt-1 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">{item.label}</div>
<div className="text-sm text-gray-500">{item.desc}</div>
</div>
</label>
))}
</div>
)}
{/* Step 5: Datenhaltung */}
{currentStep === 5 && (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-gray-900">Datenhaltung & Aufbewahrung</h2>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Aufbewahrungsdauer (Tage)
</label>
<input
type="number"
min={0}
value={form.retention_days}
onChange={e => updateForm({ retention_days: parseInt(e.target.value) || 0 })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Zweck der Aufbewahrung
</label>
<textarea
value={form.retention_purpose}
onChange={e => updateForm({ retention_purpose: e.target.value })}
rows={3}
placeholder="z.B. Vertragliche Pflichten, gesetzliche Aufbewahrungsfristen..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg"
/>
</div>
</div>
)}
</div>
{/* Navigation Buttons */}
<div className="flex items-center justify-between">
<button
onClick={() => currentStep > 1 ? setCurrentStep(currentStep - 1) : router.push('/sdk/use-cases')}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
>
{currentStep === 1 ? 'Abbrechen' : 'Zurueck'}
</button>
{currentStep < 5 ? (
<button
onClick={() => setCurrentStep(currentStep + 1)}
disabled={currentStep === 1 && !form.title}
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors disabled:opacity-50"
>
Weiter
</button>
) : (
<button
onClick={handleSubmit}
disabled={isSubmitting || !form.title}
className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50 flex items-center gap-2"
>
{isSubmitting ? (
<>
<svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Bewerte...
</>
) : (
'Assessment starten'
)}
</button>
)}
</div>
</div>
)
}