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
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>
668 lines
27 KiB
TypeScript
668 lines
27 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import Link from 'next/link'
|
|
import { useSDK, UseCaseAssessment } from '@/lib/sdk'
|
|
|
|
// =============================================================================
|
|
// WIZARD STEPS
|
|
// =============================================================================
|
|
|
|
const WIZARD_STEPS = [
|
|
{ id: 1, name: 'Grunddaten', description: 'Name und Beschreibung des Use Cases' },
|
|
{ id: 2, name: 'Datenkategorien', description: 'Welche Daten werden verarbeitet?' },
|
|
{ id: 3, name: 'Technologie', description: 'Eingesetzte KI-Technologien' },
|
|
{ id: 4, name: 'Risikobewertung', description: 'Erste Risikoeinschätzung' },
|
|
{ id: 5, name: 'Zusammenfassung', description: 'Überprüfung und Abschluss' },
|
|
]
|
|
|
|
// =============================================================================
|
|
// USE CASE CARD
|
|
// =============================================================================
|
|
|
|
function UseCaseCard({
|
|
useCase,
|
|
isActive,
|
|
onSelect,
|
|
onDelete,
|
|
}: {
|
|
useCase: UseCaseAssessment
|
|
isActive: boolean
|
|
onSelect: () => void
|
|
onDelete: () => void
|
|
}) {
|
|
const completionPercent = Math.round((useCase.stepsCompleted / 5) * 100)
|
|
|
|
return (
|
|
<div
|
|
className={`relative bg-white rounded-xl border-2 p-6 transition-all cursor-pointer ${
|
|
isActive ? 'border-purple-500 shadow-lg' : 'border-gray-200 hover:border-purple-300'
|
|
}`}
|
|
onClick={onSelect}
|
|
>
|
|
{/* Delete Button */}
|
|
<button
|
|
onClick={e => {
|
|
e.stopPropagation()
|
|
onDelete()
|
|
}}
|
|
className="absolute top-4 right-4 p-1 text-gray-400 hover:text-red-500 transition-colors"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
</button>
|
|
|
|
<div className="flex items-start gap-4">
|
|
<div
|
|
className={`w-12 h-12 rounded-xl flex items-center justify-center ${
|
|
completionPercent === 100
|
|
? 'bg-green-100 text-green-600'
|
|
: 'bg-purple-100 text-purple-600'
|
|
}`}
|
|
>
|
|
{completionPercent === 100 ? (
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
) : (
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="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 2" />
|
|
</svg>
|
|
)}
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="text-lg font-semibold text-gray-900 truncate">{useCase.name}</h3>
|
|
<p className="text-sm text-gray-500 line-clamp-2">{useCase.description}</p>
|
|
<div className="mt-3">
|
|
<div className="flex items-center justify-between text-sm mb-1">
|
|
<span className="text-gray-500">Fortschritt</span>
|
|
<span className="font-medium">{completionPercent}%</span>
|
|
</div>
|
|
<div className="h-2 bg-gray-100 rounded-full overflow-hidden">
|
|
<div
|
|
className={`h-full rounded-full transition-all ${
|
|
completionPercent === 100 ? 'bg-green-500' : 'bg-purple-600'
|
|
}`}
|
|
style={{ width: `${completionPercent}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
{useCase.assessmentResult && (
|
|
<div className="mt-3 flex items-center gap-2">
|
|
<span
|
|
className={`px-2 py-1 text-xs rounded-full ${
|
|
useCase.assessmentResult.riskLevel === 'CRITICAL'
|
|
? 'bg-red-100 text-red-700'
|
|
: useCase.assessmentResult.riskLevel === 'HIGH'
|
|
? 'bg-orange-100 text-orange-700'
|
|
: useCase.assessmentResult.riskLevel === 'MEDIUM'
|
|
? 'bg-yellow-100 text-yellow-700'
|
|
: 'bg-green-100 text-green-700'
|
|
}`}
|
|
>
|
|
Risiko: {useCase.assessmentResult.riskLevel}
|
|
</span>
|
|
{useCase.assessmentResult.dsfaRequired && (
|
|
<span className="px-2 py-1 text-xs bg-purple-100 text-purple-700 rounded-full">
|
|
DSFA erforderlich
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// WIZARD
|
|
// =============================================================================
|
|
|
|
interface WizardFormData {
|
|
name: string
|
|
description: string
|
|
category: string
|
|
dataCategories: string[]
|
|
processesPersonalData: boolean
|
|
specialCategories: boolean
|
|
aiTechnologies: string[]
|
|
dataVolume: string
|
|
riskLevel: string
|
|
notes: string
|
|
}
|
|
|
|
function UseCaseWizard({
|
|
onComplete,
|
|
onCancel,
|
|
}: {
|
|
onComplete: (useCase: UseCaseAssessment) => void
|
|
onCancel: () => void
|
|
}) {
|
|
const [currentStep, setCurrentStep] = useState(1)
|
|
const [formData, setFormData] = useState<WizardFormData>({
|
|
name: '',
|
|
description: '',
|
|
category: '',
|
|
dataCategories: [],
|
|
processesPersonalData: false,
|
|
specialCategories: false,
|
|
aiTechnologies: [],
|
|
dataVolume: 'medium',
|
|
riskLevel: 'medium',
|
|
notes: '',
|
|
})
|
|
|
|
const updateFormData = (updates: Partial<WizardFormData>) => {
|
|
setFormData(prev => ({ ...prev, ...updates }))
|
|
}
|
|
|
|
const handleNext = () => {
|
|
if (currentStep < 5) {
|
|
setCurrentStep(prev => prev + 1)
|
|
} else {
|
|
// Create use case
|
|
const newUseCase: UseCaseAssessment = {
|
|
id: `uc-${Date.now()}`,
|
|
name: formData.name,
|
|
description: formData.description,
|
|
category: formData.category,
|
|
stepsCompleted: 5,
|
|
steps: WIZARD_STEPS.map(s => ({
|
|
id: `step-${s.id}`,
|
|
name: s.name,
|
|
completed: true,
|
|
data: {},
|
|
})),
|
|
assessmentResult: {
|
|
riskLevel: formData.riskLevel as 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL',
|
|
applicableRegulations: ['DSGVO', 'AI Act'],
|
|
recommendedControls: ['Datenschutz-Folgenabschätzung', 'Technische Maßnahmen'],
|
|
dsfaRequired: formData.specialCategories || formData.riskLevel === 'HIGH',
|
|
aiActClassification: formData.aiTechnologies.length > 0 ? 'LIMITED' : 'MINIMAL',
|
|
},
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
}
|
|
onComplete(newUseCase)
|
|
}
|
|
}
|
|
|
|
const handleBack = () => {
|
|
if (currentStep > 1) {
|
|
setCurrentStep(prev => prev - 1)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
|
{/* Header */}
|
|
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50">
|
|
<div className="flex items-center justify-between">
|
|
<h2 className="text-lg font-semibold text-gray-900">Neuer Use Case</h2>
|
|
<button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
|
|
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
{/* Progress */}
|
|
<div className="mt-4 flex items-center gap-2">
|
|
{WIZARD_STEPS.map((step, index) => (
|
|
<React.Fragment key={step.id}>
|
|
<div
|
|
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${
|
|
step.id < currentStep
|
|
? 'bg-green-500 text-white'
|
|
: step.id === currentStep
|
|
? 'bg-purple-600 text-white'
|
|
: 'bg-gray-200 text-gray-500'
|
|
}`}
|
|
>
|
|
{step.id < currentStep ? (
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
</svg>
|
|
) : (
|
|
step.id
|
|
)}
|
|
</div>
|
|
{index < WIZARD_STEPS.length - 1 && (
|
|
<div
|
|
className={`flex-1 h-1 rounded ${
|
|
step.id < currentStep ? 'bg-green-500' : 'bg-gray-200'
|
|
}`}
|
|
/>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</div>
|
|
<p className="mt-2 text-sm text-gray-500">
|
|
Schritt {currentStep}: {WIZARD_STEPS[currentStep - 1].description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-6">
|
|
{currentStep === 1 && (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Name des Use Cases *</label>
|
|
<input
|
|
type="text"
|
|
value={formData.name}
|
|
onChange={e => updateFormData({ name: e.target.value })}
|
|
placeholder="z.B. Marketing-KI für Kundensegmentierung"
|
|
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={formData.description}
|
|
onChange={e => updateFormData({ description: e.target.value })}
|
|
placeholder="Beschreiben Sie den Anwendungsfall und den Geschäftszweck..."
|
|
rows={4}
|
|
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">Kategorie</label>
|
|
<select
|
|
value={formData.category}
|
|
onChange={e => updateFormData({ category: e.target.value })}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
>
|
|
<option value="">Kategorie wählen...</option>
|
|
<option value="marketing">Marketing & Vertrieb</option>
|
|
<option value="hr">Personal & HR</option>
|
|
<option value="finance">Finanzen & Controlling</option>
|
|
<option value="operations">Betrieb & Produktion</option>
|
|
<option value="customer">Kundenservice</option>
|
|
<option value="other">Sonstiges</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{currentStep === 2 && (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Werden personenbezogene Daten verarbeitet?
|
|
</label>
|
|
<div className="flex items-center gap-4">
|
|
<label className="flex items-center gap-2">
|
|
<input
|
|
type="radio"
|
|
checked={formData.processesPersonalData}
|
|
onChange={() => updateFormData({ processesPersonalData: true })}
|
|
className="w-4 h-4 text-purple-600"
|
|
/>
|
|
<span>Ja</span>
|
|
</label>
|
|
<label className="flex items-center gap-2">
|
|
<input
|
|
type="radio"
|
|
checked={!formData.processesPersonalData}
|
|
onChange={() => updateFormData({ processesPersonalData: false })}
|
|
className="w-4 h-4 text-purple-600"
|
|
/>
|
|
<span>Nein</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{formData.processesPersonalData && (
|
|
<>
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Welche Datenkategorien? (Mehrfachauswahl)
|
|
</label>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{['Name/Kontakt', 'E-Mail', 'Adresse', 'Telefon', 'Geburtsdatum', 'Finanzdaten', 'Standort', 'Nutzungsverhalten'].map(
|
|
cat => (
|
|
<label key={cat} className="flex items-center gap-2 p-2 border rounded-lg hover:bg-gray-50">
|
|
<input
|
|
type="checkbox"
|
|
checked={formData.dataCategories.includes(cat)}
|
|
onChange={e => {
|
|
if (e.target.checked) {
|
|
updateFormData({ dataCategories: [...formData.dataCategories, cat] })
|
|
} else {
|
|
updateFormData({
|
|
dataCategories: formData.dataCategories.filter(c => c !== cat),
|
|
})
|
|
}
|
|
}}
|
|
className="w-4 h-4 text-purple-600"
|
|
/>
|
|
<span className="text-sm">{cat}</span>
|
|
</label>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="flex items-center gap-2">
|
|
<input
|
|
type="checkbox"
|
|
checked={formData.specialCategories}
|
|
onChange={e => updateFormData({ specialCategories: e.target.checked })}
|
|
className="w-4 h-4 text-purple-600"
|
|
/>
|
|
<span className="text-sm font-medium text-gray-700">
|
|
Besondere Kategorien (Art. 9 DSGVO): Gesundheit, Biometrie, Religion, etc.
|
|
</span>
|
|
</label>
|
|
{formData.specialCategories && (
|
|
<p className="mt-2 text-sm text-amber-600 bg-amber-50 p-3 rounded-lg">
|
|
Bei besonderen Kategorien ist eine DSFA in der Regel erforderlich!
|
|
</p>
|
|
)}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{currentStep === 3 && (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Eingesetzte KI-Technologien (Mehrfachauswahl)
|
|
</label>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{[
|
|
'Machine Learning',
|
|
'Deep Learning',
|
|
'Natural Language Processing',
|
|
'Computer Vision',
|
|
'Generative AI (LLM)',
|
|
'Empfehlungssysteme',
|
|
'Predictive Analytics',
|
|
'Chatbots/Assistenten',
|
|
].map(tech => (
|
|
<label key={tech} className="flex items-center gap-2 p-2 border rounded-lg hover:bg-gray-50">
|
|
<input
|
|
type="checkbox"
|
|
checked={formData.aiTechnologies.includes(tech)}
|
|
onChange={e => {
|
|
if (e.target.checked) {
|
|
updateFormData({ aiTechnologies: [...formData.aiTechnologies, tech] })
|
|
} else {
|
|
updateFormData({
|
|
aiTechnologies: formData.aiTechnologies.filter(t => t !== tech),
|
|
})
|
|
}
|
|
}}
|
|
className="w-4 h-4 text-purple-600"
|
|
/>
|
|
<span className="text-sm">{tech}</span>
|
|
</label>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Erwartetes Datenvolumen</label>
|
|
<select
|
|
value={formData.dataVolume}
|
|
onChange={e => updateFormData({ dataVolume: e.target.value })}
|
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
>
|
|
<option value="small">Klein (< 1.000 Datensätze)</option>
|
|
<option value="medium">Mittel (1.000 - 100.000 Datensätze)</option>
|
|
<option value="large">Groß (100.000 - 1 Mio. Datensätze)</option>
|
|
<option value="xlarge">Sehr groß (> 1 Mio. Datensätze)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{currentStep === 4 && (
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
Erste Risikoeinschätzung
|
|
</label>
|
|
<div className="space-y-2">
|
|
{[
|
|
{ value: 'low', label: 'Niedrig', description: 'Keine personenbezogenen Daten, kein kritischer Einsatz' },
|
|
{ value: 'medium', label: 'Mittel', description: 'Personenbezogene Daten, aber kein kritischer Einsatz' },
|
|
{ value: 'high', label: 'Hoch', description: 'Besondere Kategorien oder automatisierte Entscheidungen' },
|
|
{ value: 'critical', label: 'Kritisch', description: 'Hochrisiko-KI nach AI Act' },
|
|
].map(option => (
|
|
<label
|
|
key={option.value}
|
|
className={`flex items-start gap-3 p-4 border rounded-lg cursor-pointer transition-colors ${
|
|
formData.riskLevel === option.value
|
|
? 'border-purple-500 bg-purple-50'
|
|
: 'hover:bg-gray-50'
|
|
}`}
|
|
>
|
|
<input
|
|
type="radio"
|
|
checked={formData.riskLevel === option.value}
|
|
onChange={() => updateFormData({ riskLevel: option.value })}
|
|
className="mt-1 w-4 h-4 text-purple-600"
|
|
/>
|
|
<div>
|
|
<div className="font-medium">{option.label}</div>
|
|
<div className="text-sm text-gray-500">{option.description}</div>
|
|
</div>
|
|
</label>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Notizen</label>
|
|
<textarea
|
|
value={formData.notes}
|
|
onChange={e => updateFormData({ notes: e.target.value })}
|
|
placeholder="Zusätzliche Anmerkungen zur Risikobewertung..."
|
|
rows={3}
|
|
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>
|
|
)}
|
|
|
|
{currentStep === 5 && (
|
|
<div className="space-y-6">
|
|
<div className="bg-gray-50 rounded-lg p-6">
|
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Zusammenfassung</h3>
|
|
<dl className="space-y-3">
|
|
<div className="flex justify-between">
|
|
<dt className="text-gray-500">Name:</dt>
|
|
<dd className="font-medium text-gray-900">{formData.name || '-'}</dd>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<dt className="text-gray-500">Kategorie:</dt>
|
|
<dd className="font-medium text-gray-900">{formData.category || '-'}</dd>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<dt className="text-gray-500">Personenbezogene Daten:</dt>
|
|
<dd className="font-medium text-gray-900">
|
|
{formData.processesPersonalData ? 'Ja' : 'Nein'}
|
|
</dd>
|
|
</div>
|
|
{formData.processesPersonalData && (
|
|
<div className="flex justify-between">
|
|
<dt className="text-gray-500">Datenkategorien:</dt>
|
|
<dd className="font-medium text-gray-900">{formData.dataCategories.join(', ') || '-'}</dd>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between">
|
|
<dt className="text-gray-500">KI-Technologien:</dt>
|
|
<dd className="font-medium text-gray-900">{formData.aiTechnologies.join(', ') || '-'}</dd>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<dt className="text-gray-500">Risikostufe:</dt>
|
|
<dd
|
|
className={`font-medium px-2 py-0.5 rounded ${
|
|
formData.riskLevel === 'critical'
|
|
? 'bg-red-100 text-red-700'
|
|
: formData.riskLevel === 'high'
|
|
? 'bg-orange-100 text-orange-700'
|
|
: formData.riskLevel === 'medium'
|
|
? 'bg-yellow-100 text-yellow-700'
|
|
: 'bg-green-100 text-green-700'
|
|
}`}
|
|
>
|
|
{formData.riskLevel.toUpperCase()}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
{(formData.specialCategories || formData.riskLevel === 'high' || formData.riskLevel === 'critical') && (
|
|
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
|
<div className="flex items-start gap-3">
|
|
<svg
|
|
className="w-5 h-5 text-amber-600 mt-0.5"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="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"
|
|
/>
|
|
</svg>
|
|
<div>
|
|
<h4 className="font-medium text-amber-800">DSFA erforderlich</h4>
|
|
<p className="text-sm text-amber-700 mt-1">
|
|
Basierend auf Ihrer Eingabe wird eine Datenschutz-Folgenabschätzung empfohlen.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="px-6 py-4 border-t border-gray-200 bg-gray-50 flex items-center justify-between">
|
|
<button
|
|
onClick={currentStep === 1 ? onCancel : handleBack}
|
|
className="px-4 py-2 text-gray-600 hover:text-gray-900"
|
|
>
|
|
{currentStep === 1 ? 'Abbrechen' : 'Zurück'}
|
|
</button>
|
|
<button
|
|
onClick={handleNext}
|
|
disabled={currentStep === 1 && !formData.name}
|
|
className={`px-6 py-2 rounded-lg font-medium transition-colors ${
|
|
currentStep === 1 && !formData.name
|
|
? 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
|
: 'bg-purple-600 text-white hover:bg-purple-700'
|
|
}`}
|
|
>
|
|
{currentStep === 5 ? 'Abschließen' : 'Weiter'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// MAIN PAGE
|
|
// =============================================================================
|
|
|
|
export default function AdvisoryBoardPage() {
|
|
const { state, dispatch } = useSDK()
|
|
const [showWizard, setShowWizard] = useState(false)
|
|
|
|
const handleCreateUseCase = (useCase: UseCaseAssessment) => {
|
|
dispatch({ type: 'ADD_USE_CASE', payload: useCase })
|
|
dispatch({ type: 'SET_ACTIVE_USE_CASE', payload: useCase.id })
|
|
setShowWizard(false)
|
|
}
|
|
|
|
const handleDeleteUseCase = (id: string) => {
|
|
if (confirm('Möchten Sie diesen Use Case wirklich löschen?')) {
|
|
dispatch({ type: 'DELETE_USE_CASE', payload: id })
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900">Use Case Workshop</h1>
|
|
<p className="mt-1 text-gray-500">
|
|
Erfassen Sie Ihre KI-Anwendungsfälle und erhalten Sie eine erste Compliance-Bewertung
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<Link
|
|
href="/sdk/advisory-board/documentation"
|
|
className="inline-flex items-center gap-2 px-4 py-2 text-sm text-purple-600 hover:text-purple-700 hover:bg-purple-50 border border-purple-300 rounded-lg transition-colors"
|
|
>
|
|
UCCA-System Dokumentation ansehen
|
|
</Link>
|
|
{!showWizard && (
|
|
<button
|
|
onClick={() => setShowWizard(true)}
|
|
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
</svg>
|
|
Neuer Use Case
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Wizard or List */}
|
|
{showWizard ? (
|
|
<UseCaseWizard onComplete={handleCreateUseCase} onCancel={() => setShowWizard(false)} />
|
|
) : state.useCases.length === 0 ? (
|
|
<div className="bg-white rounded-xl border border-gray-200 p-12 text-center">
|
|
<div className="w-16 h-16 mx-auto bg-purple-100 rounded-full flex items-center justify-center mb-4">
|
|
<svg className="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="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 2"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-lg font-semibold text-gray-900">Noch keine Use Cases</h3>
|
|
<p className="mt-2 text-gray-500 max-w-md mx-auto">
|
|
Erstellen Sie Ihren ersten Use Case, um mit dem Compliance Assessment zu beginnen.
|
|
</p>
|
|
<button
|
|
onClick={() => setShowWizard(true)}
|
|
className="mt-6 px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
|
>
|
|
Ersten Use Case erstellen
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{state.useCases.map(useCase => (
|
|
<UseCaseCard
|
|
key={useCase.id}
|
|
useCase={useCase}
|
|
isActive={state.activeUseCase === useCase.id}
|
|
onSelect={() => dispatch({ type: 'SET_ACTIVE_USE_CASE', payload: useCase.id })}
|
|
onDelete={() => handleDeleteUseCase(useCase.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|