refactor(admin): split advisory-board page.tsx into colocated components
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
currentStep: number
|
||||
isSubmitting: boolean
|
||||
isEditMode: boolean
|
||||
titleEmpty: boolean
|
||||
onBack: () => void
|
||||
onNext: () => void
|
||||
onSubmit: () => void
|
||||
}
|
||||
|
||||
export function NavigationButtons({ currentStep, isSubmitting, isEditMode, titleEmpty, onBack, onNext, onSubmit }: Props) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
onClick={onBack}
|
||||
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
{currentStep === 1 ? 'Abbrechen' : 'Zurueck'}
|
||||
</button>
|
||||
|
||||
{currentStep < 8 ? (
|
||||
<button
|
||||
onClick={onNext}
|
||||
disabled={currentStep === 1 && titleEmpty}
|
||||
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={onSubmit}
|
||||
disabled={isSubmitting || titleEmpty}
|
||||
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...
|
||||
</>
|
||||
) : (
|
||||
isEditMode ? 'Speichern & neu bewerten' : 'Assessment starten'
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { AssessmentResultCard } from '@/components/sdk/use-case-assessment/AssessmentResultCard'
|
||||
|
||||
interface Props {
|
||||
result: unknown
|
||||
onGoToAssessment: (id: string) => void
|
||||
onGoToOverview: () => void
|
||||
}
|
||||
|
||||
export function ResultView({ result, onGoToAssessment, onGoToOverview }: Props) {
|
||||
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={() => onGoToAssessment(r.assessment!.id)}
|
||||
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"
|
||||
>
|
||||
Zum Assessment
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onGoToOverview}
|
||||
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 unknown as Parameters<typeof AssessmentResultCard>[0]['result']} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { AI_USE_CATEGORIES } from '../_data'
|
||||
|
||||
interface Props extends StepProps {
|
||||
profileIndustry: string | string[] | undefined
|
||||
}
|
||||
|
||||
export function Step1Basics({ form, updateForm, profileIndustry }: Props) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Grundlegende Informationen</h2>
|
||||
|
||||
{/* Branche aus Profil (nur Anzeige) */}
|
||||
{profileIndustry && (Array.isArray(profileIndustry) ? profileIndustry.length > 0 : true) && (
|
||||
<div className="bg-gray-50 rounded-lg border border-gray-200 px-4 py-3">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">Branche (aus Unternehmensprofil)</span>
|
||||
<p className="text-sm text-gray-900 mt-0.5">
|
||||
{Array.isArray(profileIndustry) ? profileIndustry.join(', ') : profileIndustry}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Titel des Anwendungsfalls</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>
|
||||
|
||||
{/* KI-Anwendungskategorie als Kacheln */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
In welchem Bereich kommt KI zum Einsatz?
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 mb-3">Waehlen Sie die passende Kategorie fuer Ihren Anwendungsfall.</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{AI_USE_CATEGORIES.map(cat => (
|
||||
<button
|
||||
key={cat.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ category: cat.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.category === cat.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{cat.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{cat.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{cat.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { DATA_CATEGORY_GROUPS } from '../_data-categories'
|
||||
import { toggleInArray } from '../_data'
|
||||
|
||||
export function Step2DataCategories({ form, updateForm }: StepProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Welche Daten werden verarbeitet?</h2>
|
||||
<p className="text-sm text-gray-500">Waehlen Sie alle Datenkategorien, die in diesem Use Case verarbeitet werden.</p>
|
||||
|
||||
{DATA_CATEGORY_GROUPS.map(group => (
|
||||
<div key={group.group}>
|
||||
<h3 className={`text-sm font-semibold mb-2 ${group.art9 ? 'text-orange-700' : 'text-gray-700'}`}>
|
||||
{group.art9 && '⚠️ '}{group.group}
|
||||
</h3>
|
||||
{group.art9 && (
|
||||
<p className="text-xs text-orange-600 mb-2">Besonders schutzwuerdig — erhoehte Anforderungen an Rechtsgrundlage und TOM</p>
|
||||
)}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 mb-4">
|
||||
{group.items.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ data_categories: toggleInArray(form.data_categories, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.data_categories.includes(item.value)
|
||||
? group.art9
|
||||
? 'border-orange-500 bg-orange-50 ring-1 ring-orange-300'
|
||||
: 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Sonstige Datentypen */}
|
||||
<div className="border border-gray-200 rounded-lg p-4 space-y-3">
|
||||
<div className="font-medium text-gray-900">Sonstige Datentypen</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
Falls Ihre Datenkategorie oben nicht aufgefuehrt ist, koennen Sie sie hier ergaenzen.
|
||||
</p>
|
||||
{form.custom_data_types.map((dt, idx) => (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={dt}
|
||||
onChange={e => {
|
||||
const updated = [...form.custom_data_types]
|
||||
updated[idx] = e.target.value
|
||||
updateForm({ custom_data_types: updated })
|
||||
}}
|
||||
placeholder="Datentyp eingeben..."
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
<button
|
||||
onClick={() => updateForm({ custom_data_types: form.custom_data_types.filter((_, i) => i !== idx) })}
|
||||
className="p-2 text-red-500 hover:bg-red-50 rounded-lg"
|
||||
title="Entfernen"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={() => updateForm({ custom_data_types: [...form.custom_data_types, ''] })}
|
||||
className="flex items-center gap-1 text-sm text-purple-600 hover:text-purple-700 font-medium"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
|
||||
Weiteren Datentyp hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{form.data_categories.length > 0 && (
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg px-4 py-3 text-sm text-purple-800">
|
||||
<span className="font-medium">{form.data_categories.length}</span> Datenkategorie{form.data_categories.length !== 1 ? 'n' : ''} ausgewaehlt
|
||||
{form.data_categories.some(c => DATA_CATEGORY_GROUPS.find(g => g.art9)?.items.some(i => i.value === c)) && (
|
||||
<span className="ml-2 text-orange-700 font-medium">— inkl. besonderer Kategorien (Art. 9)</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { PURPOSE_TILES } from '../_tiles'
|
||||
import { toggleInArray } from '../_data'
|
||||
|
||||
export function Step3Purposes({ form, updateForm }: StepProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Zweck der Verarbeitung</h2>
|
||||
<p className="text-sm text-gray-500">Waehlen Sie alle zutreffenden Verarbeitungszwecke. Die passende Rechtsgrundlage wird vom SDK automatisch ermittelt.</p>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{PURPOSE_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ purposes: toggleInArray(form.purposes, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.purposes.includes(item.value)
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{form.purposes.includes('profiling') && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 text-sm text-amber-800">
|
||||
<div className="font-medium mb-1">Hinweis: Profiling</div>
|
||||
<p>Profiling unterliegt besonderen Anforderungen nach Art. 22 DSGVO. Betroffene haben das Recht auf Information und Widerspruch.</p>
|
||||
</div>
|
||||
)}
|
||||
{form.purposes.includes('automated_decision') && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-sm text-red-800">
|
||||
<div className="font-medium mb-1">Achtung: Automatisierte Entscheidung</div>
|
||||
<p>Art. 22 DSGVO: Vollautomatisierte Entscheidungen mit rechtlicher Wirkung erfordern besondere Schutzmassnahmen, Informationspflichten und das Recht auf menschliche Ueberpruefung.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { AUTOMATION_TILES } from '../_tiles'
|
||||
|
||||
export function Step4Automation({ form, updateForm }: StepProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Grad der Automatisierung</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
Wie stark greift die KI in Entscheidungen ein? Je hoeher der Automatisierungsgrad, desto strenger die regulatorischen Anforderungen.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{AUTOMATION_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ automation: item.value })}
|
||||
className={`p-4 rounded-xl border-2 text-left transition-all ${
|
||||
form.automation === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<span className="text-2xl">{item.icon}</span>
|
||||
<span className="text-sm font-semibold text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 ml-11">{item.desc}</p>
|
||||
<p className="text-xs text-gray-400 ml-11 mt-1">Beispiele: {item.examples}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-sm text-blue-800">
|
||||
<div className="font-medium mb-1">Warum ist das wichtig?</div>
|
||||
<p>
|
||||
Art. 22 DSGVO regelt automatisierte Einzelentscheidungen. Vollautomatisierte Systeme, die Personen
|
||||
erheblich beeinflussen (z.B. Kreditvergabe, Bewerbungsauswahl), unterliegen strengen Auflagen:
|
||||
Informationspflicht, Recht auf menschliche Ueberpruefung und Anfechtungsmoeglichkeit.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { HOSTING_PROVIDER_TILES, HOSTING_REGION_TILES, MODEL_USAGE_TILES } from '../_tiles'
|
||||
import { toggleInArray } from '../_data'
|
||||
|
||||
export function Step5Hosting({ form, updateForm }: StepProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Technische Details</h2>
|
||||
|
||||
{/* Hosting Provider */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Hosting-Anbieter</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{HOSTING_PROVIDER_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ hosting_provider: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.hosting_provider === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hosting Region */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Hosting-Region</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{HOSTING_REGION_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ hosting_region: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.hosting_region === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Model Usage */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-1">Wie wird das KI-Modell genutzt?</h3>
|
||||
<p className="text-sm text-gray-500 mb-3">Waehlen Sie alle zutreffenden Optionen.</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{MODEL_USAGE_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ model_usage: toggleInArray(form.model_usage, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.model_usage.includes(item.value)
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info-Box: Begriffe erklaert */}
|
||||
<details className="bg-amber-50 border border-amber-200 rounded-lg overflow-hidden">
|
||||
<summary className="px-4 py-3 text-sm font-medium text-amber-800 cursor-pointer hover:bg-amber-100">
|
||||
Begriffe erklaert: ML, DL, NLP, LLM — Was bedeutet das?
|
||||
</summary>
|
||||
<div className="px-4 pb-4 space-y-3 text-sm text-amber-900">
|
||||
<div><span className="font-semibold">ML (Machine Learning)</span> — Computer lernt Muster aus Daten. Beispiel: Spam-Filter.</div>
|
||||
<div><span className="font-semibold">DL (Deep Learning)</span> — ML mit neuronalen Netzen. Beispiel: Bilderkennung, Spracherkennung.</div>
|
||||
<div><span className="font-semibold">NLP (Natural Language Processing)</span> — KI versteht Sprache. Beispiel: ChatGPT, DeepL.</div>
|
||||
<div><span className="font-semibold">LLM (Large Language Model)</span> — Grosses Sprachmodell. Beispiel: GPT-4, Claude, Llama.</div>
|
||||
<div><span className="font-semibold">RAG</span> — LLM erhaelt Kontext aus eigener Datenbank. Vorteil: Aktuelle, firmenspezifische Antworten.</div>
|
||||
<div><span className="font-semibold">Fine-Tuning</span> — Bestehendes Modell mit eigenen Daten weitertrainieren. Achtung: Daten werden Teil des Modells.</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { TRANSFER_TARGET_TILES, TRANSFER_MECHANISM_TILES } from '../_tiles'
|
||||
import { toggleInArray } from '../_data'
|
||||
|
||||
export function Step6Transfer({ form, updateForm }: StepProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Internationaler Datentransfer</h2>
|
||||
<p className="text-sm text-gray-500">Wohin werden die Daten uebermittelt? Waehlen Sie alle zutreffenden Ziellaender/-regionen.</p>
|
||||
|
||||
{/* Transfer Targets */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Datentransfer-Ziele</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{TRANSFER_TARGET_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ transfer_targets: toggleInArray(form.transfer_targets, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.transfer_targets.includes(item.value)
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Transfer Mechanism — only if not "no_transfer" only */}
|
||||
{form.transfer_targets.length > 0 && !form.transfer_targets.every(t => t === 'no_transfer') && (
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Transfer-Mechanismus</h3>
|
||||
<p className="text-sm text-gray-500 mb-3">Welche Schutzgarantie nutzen Sie fuer den Drittlandtransfer?</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{TRANSFER_MECHANISM_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ transfer_mechanism: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.transfer_mechanism === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Specific countries text input */}
|
||||
{form.transfer_targets.some(t => !['no_transfer'].includes(t)) && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Konkrete Ziellaender (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={form.transfer_countries.join(', ')}
|
||||
onChange={e => updateForm({ transfer_countries: e.target.value.split(',').map(s => s.trim()).filter(Boolean) })}
|
||||
placeholder="z.B. USA, UK, Schweiz, Japan"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Kommagetrennte Laendernamen oder -kuerzel</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { RETENTION_TILES } from '../_tiles'
|
||||
|
||||
export function Step7Retention({ form, updateForm }: StepProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Datenhaltung & Aufbewahrung</h2>
|
||||
<p className="text-sm text-gray-500">Wie lange sollen die Daten gespeichert werden?</p>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{RETENTION_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ retention_period: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.retention_period === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Zweck der Aufbewahrung (optional)
|
||||
</label>
|
||||
<textarea
|
||||
value={form.retention_purpose}
|
||||
onChange={e => updateForm({ retention_purpose: e.target.value })}
|
||||
rows={2}
|
||||
placeholder="z.B. Vertragliche Pflichten, gesetzliche Aufbewahrungsfristen..."
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{form.retention_period === 'indefinite' && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 text-sm text-amber-800">
|
||||
<div className="font-medium mb-1">Hinweis: Unbefristete Speicherung</div>
|
||||
<p>Die DSGVO fordert Datenminimierung und Speicherbegrenzung (Art. 5 Abs. 1e). Unbefristete Speicherung muss besonders gut begruendet sein.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { StepProps } from '../_types'
|
||||
import { CONTRACT_TILES } from '../_tiles'
|
||||
import { toggleInArray } from '../_data'
|
||||
|
||||
export function Step8Contracts({ form, updateForm }: StepProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Vertraege & Compliance-Dokumentation</h2>
|
||||
<p className="text-sm text-gray-500">Welche Compliance-Dokumente liegen bereits vor? (Mehrfachauswahl moeglich)</p>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{CONTRACT_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ contracts: toggleInArray(form.contracts, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.contracts.includes(item.value)
|
||||
? item.value === 'none'
|
||||
? 'border-amber-500 bg-amber-50 ring-1 ring-amber-300'
|
||||
: 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Subprozessoren (optional)</label>
|
||||
<textarea
|
||||
value={form.subprocessors}
|
||||
onChange={e => updateForm({ subprocessors: e.target.value })}
|
||||
rows={2}
|
||||
placeholder="z.B. OpenAI (USA, SCC), Hetzner Cloud (DE)..."
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { WIZARD_STEPS } from '../_data'
|
||||
|
||||
interface Props {
|
||||
currentStep: number
|
||||
onStepClick: (id: number) => void
|
||||
}
|
||||
|
||||
export function StepIndicator({ currentStep, onStepClick }: Props) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{WIZARD_STEPS.map((step, idx) => (
|
||||
<React.Fragment key={step.id}>
|
||||
<button
|
||||
onClick={() => onStepClick(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>
|
||||
)
|
||||
}
|
||||
112
admin-compliance/app/sdk/advisory-board/_data-categories.ts
Normal file
112
admin-compliance/app/sdk/advisory-board/_data-categories.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
// =============================================================================
|
||||
// DATA CATEGORIES (Step 2) — grouped tile selection
|
||||
// =============================================================================
|
||||
|
||||
export const DATA_CATEGORY_GROUPS = [
|
||||
{
|
||||
group: 'Stamm- & Kontaktdaten',
|
||||
items: [
|
||||
{ value: 'basic_identity', label: 'Name & Identitaet', icon: '👤', desc: 'Vor-/Nachname, Geburtsdatum, Geschlecht' },
|
||||
{ value: 'contact_data', label: 'Kontaktdaten', icon: '📧', desc: 'E-Mail, Telefon, Fax' },
|
||||
{ value: 'address_data', label: 'Adressdaten', icon: '🏠', desc: 'Wohn-/Meldeadresse, PLZ, Lieferadresse' },
|
||||
{ value: 'government_ids', label: 'Ausweisdaten', icon: '🪪', desc: 'Personalausweis-Nr., Reisepass, Fuehrerschein' },
|
||||
{ value: 'customer_ids', label: 'Kundennummern', icon: '🏷️', desc: 'Kunden-ID, Vertrags-Nr., Mitgliedsnummer' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Besondere Kategorien (Art. 9 DSGVO)',
|
||||
art9: true,
|
||||
items: [
|
||||
{ value: 'health_data', label: 'Gesundheitsdaten', icon: '🏥', desc: 'Diagnosen, Medikation, AU, Pflegegrad' },
|
||||
{ value: 'biometric_data', label: 'Biometrische Daten', icon: '🔐', desc: 'Fingerabdruck, Gesichtserkennung, Iris-Scan' },
|
||||
{ value: 'genetic_data', label: 'Genetische Daten', icon: '🧬', desc: 'DNA-Profil, Genomsequenzen, Erbkrankheitstests' },
|
||||
{ value: 'racial_ethnic', label: 'Ethnische Herkunft', icon: '🌍', desc: 'Rassische/ethnische Zugehoerigkeit' },
|
||||
{ value: 'political_opinions', label: 'Politische Meinungen', icon: '🗳️', desc: 'Politische Ueberzeugungen, Parteizugehoerigkeit' },
|
||||
{ value: 'religious_beliefs', label: 'Religion', icon: '🕊️', desc: 'Religionszugehoerigkeit, Weltanschauung' },
|
||||
{ value: 'trade_union', label: 'Gewerkschaft', icon: '🤝', desc: 'Gewerkschaftsmitgliedschaft' },
|
||||
{ value: 'sexual_orientation', label: 'Sexuelle Orientierung', icon: '🏳️🌈', desc: 'Sexualleben und Orientierung' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Finanz- & Steuerdaten',
|
||||
items: [
|
||||
{ value: 'bank_account', label: 'Bankverbindung', icon: '🏦', desc: 'IBAN, BIC, Kontonummer' },
|
||||
{ value: 'payment_card', label: 'Zahlungskarten', icon: '💳', desc: 'Kreditkarten-Nr., CVV (PCI-DSS)' },
|
||||
{ value: 'transaction_data', label: 'Transaktionsdaten', icon: '🧾', desc: 'Zahlungshistorie, Ueberweisungen, Kaufhistorie' },
|
||||
{ value: 'credit_score', label: 'Bonitaet / Schufa', icon: '📈', desc: 'Kreditwuerdigkeit, Schuldenhistorie' },
|
||||
{ value: 'income_salary', label: 'Einkommen & Gehalt', icon: '💰', desc: 'Bruttogehalt, Nettolohn, Boni' },
|
||||
{ value: 'tax_ids', label: 'Steuer-IDs', icon: '📋', desc: 'Steuer-ID, Steuernummer, USt-IdNr.' },
|
||||
{ value: 'insurance_data', label: 'Versicherungsdaten', icon: '☂️', desc: 'Versicherungsnummern, Policen, Schadenmeldungen' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Fahrzeug- & Mobilitaetsdaten',
|
||||
items: [
|
||||
{ value: 'vehicle_ids', label: 'Fahrzeug-IDs (VIN)', icon: '🚗', desc: 'Fahrgestellnummer (VIN/FIN), Fahrzeugschein' },
|
||||
{ value: 'license_plates', label: 'Kennzeichen', icon: '🔢', desc: 'Amtliches Kennzeichen, Wunschkennzeichen' },
|
||||
{ value: 'gps_tracking', label: 'GPS & Routen', icon: '📍', desc: 'Echtzeitposition, Fahrtenprotokolle' },
|
||||
{ value: 'telematics', label: 'Telematikdaten', icon: '📡', desc: 'Fahrverhalten, Geschwindigkeit, Motordiagnose' },
|
||||
{ value: 'fleet_data', label: 'Fuhrpark / Logistik', icon: '🚛', desc: 'Einsatzzeiten, Kilometerstand, Fahrerzuweisung' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Technische Identifikatoren',
|
||||
items: [
|
||||
{ value: 'ip_address', label: 'IP-Adresse', icon: '🌐', desc: 'IPv4/IPv6 (EuGH: personenbezogen)' },
|
||||
{ value: 'device_ids', label: 'Geraete-IDs', icon: '📱', desc: 'IMEI, UUID, Advertising-ID, Seriennummer' },
|
||||
{ value: 'cookies_tracking', label: 'Cookies & Tracking', icon: '🍪', desc: 'Session-/Persistent Cookies, Pixel-Tags' },
|
||||
{ value: 'browser_fingerprint', label: 'Browser-Fingerprint', icon: '🔎', desc: 'Browser-Typ, OS, Plugins, Canvas-Fingerprint' },
|
||||
{ value: 'mac_address', label: 'MAC-Adresse', icon: '📶', desc: 'Netzwerkadapter-Kennung, WLAN-Praesenz' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Verhaltens- & Nutzungsdaten',
|
||||
items: [
|
||||
{ value: 'clickstream', label: 'Klick- & Nutzungspfade', icon: '🖱️', desc: 'Klickpfade, Scrolltiefe, Verweildauer, Heatmaps' },
|
||||
{ value: 'purchase_history', label: 'Kaufverhalten', icon: '🛒', desc: 'Bestellhistorie, Warenkorb, Wunschlisten' },
|
||||
{ value: 'app_usage', label: 'App-Nutzung', icon: '📲', desc: 'Genutzte Apps, Nutzungsdauer, In-App-Aktivitaeten' },
|
||||
{ value: 'profiling_scores', label: 'Profiling / Scoring', icon: '📊', desc: 'KI-generierte Profile, Segmente, Affinitaetsscores' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Kommunikation & Medien',
|
||||
items: [
|
||||
{ value: 'email_content', label: 'E-Mail-Inhalte', icon: '✉️', desc: 'E-Mail-Texte, Anhaenge, Metadaten' },
|
||||
{ value: 'chat_messages', label: 'Chat & Messaging', icon: '💬', desc: 'Textnachrichten, Messenger, Teams, Slack' },
|
||||
{ value: 'call_recordings', label: 'Telefonaufzeichnungen', icon: '📞', desc: 'Gespraeche, Transkripte, Anrufmetadaten' },
|
||||
{ value: 'video_conference', label: 'Videokonferenzen', icon: '📹', desc: 'Meeting-Aufzeichnungen, Teilnehmerlisten' },
|
||||
{ value: 'photographs', label: 'Fotos & Bilder', icon: '📷', desc: 'Portraitfotos, Profilbilder, Produktfotos' },
|
||||
{ value: 'cctv_surveillance', label: 'Videoueberwachung', icon: '📹', desc: 'CCTV-Aufnahmen, Zutrittskontrolle' },
|
||||
{ value: 'voice_recordings', label: 'Sprachaufnahmen', icon: '🎙️', desc: 'Voicemails, Sprachmemos, Diktate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'HR & Beschaeftigung',
|
||||
items: [
|
||||
{ value: 'employment_data', label: 'Beschaeftigungsdaten', icon: '💼', desc: 'Arbeitgeber, Berufsbezeichnung, Vertragsart' },
|
||||
{ value: 'performance_data', label: 'Leistungsbeurteilungen', icon: '🏆', desc: 'Zielerreichung, Feedback, Abmahnungen' },
|
||||
{ value: 'work_time', label: 'Arbeitszeit', icon: '⏰', desc: 'Zeiterfassung, Ueberstunden, Schichtplaene' },
|
||||
{ value: 'candidate_data', label: 'Bewerberdaten', icon: '📝', desc: 'Lebenslaeufe, Interviews, Assessment-Ergebnisse' },
|
||||
{ value: 'social_security', label: 'Sozialversicherungs-Nr.', icon: '🛡️', desc: 'RVNR (Art. 9 — kodiert Geburtsdatum/Geschlecht)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'IoT & Sensordaten',
|
||||
items: [
|
||||
{ value: 'industrial_sensor', label: 'Industriesensoren', icon: '🏭', desc: 'Maschinendaten, Fehlerprotokolle, Produktionsmesswerte' },
|
||||
{ value: 'wearable_data', label: 'Wearable-Daten', icon: '⌚', desc: 'Herzfrequenz, Schritte, Schlaf (Art. 9 — Gesundheit)' },
|
||||
{ value: 'smart_home', label: 'Smart-Home', icon: '🏡', desc: 'Heizung, Licht, Bewegungsmelder, Nutzungszeiten' },
|
||||
{ value: 'energy_data', label: 'Energieverbrauch', icon: '🔌', desc: 'Smart-Meter, Verbrauchsprofil (enthuellt Verhalten)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Sonstige Kategorien',
|
||||
items: [
|
||||
{ value: 'children_data', label: 'Kinderdaten (unter 16)', icon: '👶', desc: 'Besonderer Schutz, Eltern-Einwilligung erforderlich' },
|
||||
{ value: 'criminal_data', label: 'Strafrechtliche Daten', icon: '⚖️', desc: 'Vorstrafen, Ermittlungsverfahren (Art. 10 DSGVO)' },
|
||||
{ value: 'location_data', label: 'Standortdaten', icon: '📍', desc: 'GPS, Mobilfunk, WLAN-Ortung, Bewegungsprofile' },
|
||||
{ value: 'social_media', label: 'Social-Media-Daten', icon: '📱', desc: 'Profile, Posts, Follower, Interaktionen' },
|
||||
{ value: 'auth_credentials', label: 'Login & Zugangsdaten', icon: '🔑', desc: 'Passwoerter, 2FA, Session-Tokens, Zugriffsprotokolle' },
|
||||
],
|
||||
},
|
||||
]
|
||||
62
admin-compliance/app/sdk/advisory-board/_data.ts
Normal file
62
admin-compliance/app/sdk/advisory-board/_data.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// =============================================================================
|
||||
// WIZARD STEPS CONFIG
|
||||
// =============================================================================
|
||||
|
||||
export const WIZARD_STEPS = [
|
||||
{ id: 1, title: 'Grundlegendes', description: 'Titel, Beschreibung und KI-Kategorie' },
|
||||
{ id: 2, title: 'Datenkategorien', description: 'Welche Daten werden verarbeitet?' },
|
||||
{ id: 3, title: 'Verarbeitungszweck', description: 'Zweck der Datenverarbeitung' },
|
||||
{ id: 4, title: 'Automatisierung', description: 'Grad der Automatisierung' },
|
||||
{ id: 5, title: 'Hosting & Modell', description: 'Technische Details' },
|
||||
{ id: 6, title: 'Datentransfer', description: 'Internationaler Datentransfer' },
|
||||
{ id: 7, title: 'Datenhaltung', description: 'Aufbewahrung und Speicherung' },
|
||||
{ id: 8, title: 'Vertraege', description: 'Compliance und Vereinbarungen' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// KI-Anwendungskategorien als Auswahlkacheln
|
||||
// =============================================================================
|
||||
|
||||
export const AI_USE_CATEGORIES = [
|
||||
{ value: 'content_generation', label: 'Content-Erstellung', icon: '✍️', desc: 'Texte, Berichte, E-Mails, Dokumentation automatisch erstellen' },
|
||||
{ value: 'image_generation', label: 'Bilder erstellen', icon: '🎨', desc: 'KI-generierte Bilder, Grafiken, Produktfotos' },
|
||||
{ value: 'marketing_material', label: 'Marketingmaterial', icon: '📢', desc: 'Werbetexte, Social Media Posts, Newsletter generieren' },
|
||||
{ value: 'customer_service', label: 'Kundenservice / Chatbot', icon: '💬', desc: 'Automatisierte Kundenanfragen, FAQ-Bots, Support-Tickets' },
|
||||
{ value: 'crm_analytics', label: 'CRM & Kundenanalyse', icon: '👥', desc: 'Kundensegmentierung, Churn-Vorhersage, Lead-Scoring' },
|
||||
{ value: 'hr_recruiting', label: 'Bewerberauswahl / HR', icon: '🧑💼', desc: 'CV-Screening, Matching, Mitarbeiteranalysen' },
|
||||
{ value: 'financial_analysis', label: 'Finanzdaten analysieren', icon: '📊', desc: 'Buchhaltung, Forecasting, Betrugserkennung, Risikobewertung' },
|
||||
{ value: 'predictive_maintenance', label: 'Predictive Maintenance', icon: '🔧', desc: 'Vorausschauende Wartung, Ausfallvorhersage, IoT-Sensoranalyse' },
|
||||
{ value: 'production_analytics', label: 'Produktionsdatenauswertung', icon: '🏭', desc: 'Qualitaetskontrolle, Prozessoptimierung, OEE-Analyse' },
|
||||
{ value: 'document_analysis', label: 'Dokumentenanalyse', icon: '📄', desc: 'Vertraege, Rechnungen, PDFs automatisch auswerten und klassifizieren' },
|
||||
{ value: 'code_development', label: 'Softwareentwicklung', icon: '💻', desc: 'Code-Generierung, Code-Review, Test-Erstellung, Dokumentation' },
|
||||
{ value: 'translation', label: 'Uebersetzung', icon: '🌍', desc: 'Automatische Uebersetzung von Texten, Dokumenten, Webinhalten' },
|
||||
{ value: 'search_knowledge', label: 'Wissensmanagement / Suche', icon: '🔍', desc: 'Interne Wissensdatenbank, RAG-basierte Suche, FAQ-Systeme' },
|
||||
{ value: 'data_extraction', label: 'Datenextraktion', icon: '⛏️', desc: 'OCR, Formularerkennung, strukturierte Daten aus Freitext' },
|
||||
{ value: 'risk_compliance', label: 'Risiko & Compliance', icon: '⚖️', desc: 'Compliance-Pruefung, Risikobewertung, Audit-Unterstuetzung' },
|
||||
{ value: 'supply_chain', label: 'Lieferkette & Logistik', icon: '🚛', desc: 'Bedarfsprognose, Routenoptimierung, Bestandsmanagement' },
|
||||
{ value: 'medical_health', label: 'Medizin & Gesundheit', icon: '🏥', desc: 'Diagnoseunterstuetzung, Bildanalyse, Patientendaten' },
|
||||
{ value: 'security_monitoring', label: 'Sicherheit & Monitoring', icon: '🛡️', desc: 'Anomalieerkennung, Bedrohungsanalyse, Zugriffskontrolle' },
|
||||
{ value: 'personalization', label: 'Personalisierung', icon: '🎯', desc: 'Produktempfehlungen, dynamische Preisgestaltung, A/B-Testing' },
|
||||
{ value: 'voice_speech', label: 'Sprache & Audio', icon: '🎙️', desc: 'Spracherkennung, Text-to-Speech, Meeting-Transkription' },
|
||||
{ value: 'other', label: 'Sonstiges', icon: '➕', desc: 'Anderer KI-Anwendungsfall' },
|
||||
]
|
||||
|
||||
// Map Profil-Branche to domain value for backend compatibility
|
||||
export function industryToDomain(industries: string[]): string {
|
||||
if (!industries || industries.length === 0) return 'general'
|
||||
const first = industries[0].toLowerCase()
|
||||
if (first.includes('gesundheit') || first.includes('pharma')) return 'healthcare'
|
||||
if (first.includes('finanz') || first.includes('versicherung')) return 'finance'
|
||||
if (first.includes('bildung')) return 'education'
|
||||
if (first.includes('handel') || first.includes('commerce')) return 'retail'
|
||||
if (first.includes('it') || first.includes('technologie')) return 'it_services'
|
||||
if (first.includes('beratung') || first.includes('consulting')) return 'consulting'
|
||||
if (first.includes('produktion') || first.includes('industrie') || first.includes('maschinenbau')) return 'manufacturing'
|
||||
if (first.includes('marketing') || first.includes('agentur')) return 'marketing'
|
||||
if (first.includes('recht')) return 'legal'
|
||||
return 'general'
|
||||
}
|
||||
|
||||
export function toggleInArray(arr: string[], value: string): string[] {
|
||||
return arr.includes(value) ? arr.filter(v => v !== value) : [...arr, value]
|
||||
}
|
||||
110
admin-compliance/app/sdk/advisory-board/_tiles.ts
Normal file
110
admin-compliance/app/sdk/advisory-board/_tiles.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// =============================================================================
|
||||
// PROCESSING PURPOSES (Step 3) — tile selection
|
||||
// =============================================================================
|
||||
|
||||
export const PURPOSE_TILES = [
|
||||
{ value: 'service_delivery', label: 'Serviceerbringung', icon: '⚙️', desc: 'Kernfunktion des Produkts oder Services' },
|
||||
{ value: 'analytics', label: 'Analyse & BI', icon: '📊', desc: 'Statistische Auswertung, Business Intelligence, Reporting' },
|
||||
{ value: 'marketing', label: 'Marketing & Werbung', icon: '📢', desc: 'Werbung, Personalisierung, Targeting, Newsletter' },
|
||||
{ value: 'profiling', label: 'Profiling', icon: '🎯', desc: 'Automatisierte Analyse personenbezogener Aspekte' },
|
||||
{ value: 'automated_decision', label: 'Automatisierte Entscheidung', icon: '🤖', desc: 'Art. 22 DSGVO — Entscheidung ohne menschliches Zutun' },
|
||||
{ value: 'customer_support', label: 'Kundensupport', icon: '🎧', desc: 'Anfragenbearbeitung, Ticketsystem, Chatbot' },
|
||||
{ value: 'quality_control', label: 'Qualitaetskontrolle', icon: '✅', desc: 'Produktpruefung, Fehleranalyse, Prozessoptimierung' },
|
||||
{ value: 'hr_management', label: 'Personalverwaltung', icon: '👥', desc: 'Recruiting, Onboarding, Mitarbeiterentwicklung' },
|
||||
{ value: 'fraud_detection', label: 'Betrugserkennung', icon: '🕵️', desc: 'Anomalieerkennung, Transaktionsueberwachung' },
|
||||
{ value: 'research', label: 'Forschung & Entwicklung', icon: '🔬', desc: 'Wissenschaftliche Auswertung, Produktentwicklung' },
|
||||
{ value: 'compliance_audit', label: 'Compliance & Audit', icon: '📜', desc: 'Regulatorische Pruefung, Dokumentation, Audit-Trail' },
|
||||
{ value: 'communication', label: 'Kommunikation', icon: '💬', desc: 'Interne/externe Kommunikation, Uebersetzung' },
|
||||
{ value: 'content_creation', label: 'Content-Erstellung', icon: '✍️', desc: 'Text-, Bild-, Video-Generierung' },
|
||||
{ value: 'predictive', label: 'Vorhersage & Prognose', icon: '🔮', desc: 'Demand Forecasting, Predictive Analytics, Wartungsvorhersage' },
|
||||
{ value: 'security', label: 'IT-Sicherheit', icon: '🛡️', desc: 'Bedrohungserkennung, Zugriffskontrolle, Monitoring' },
|
||||
{ value: 'archiving', label: 'Archivierung', icon: '🗄️', desc: 'Gesetzliche Aufbewahrung, Dokumentenarchiv' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// AUTOMATION LEVELS (Step 4) — single-select tiles
|
||||
// =============================================================================
|
||||
|
||||
export const AUTOMATION_TILES = [
|
||||
{ value: 'assistive', label: 'Assistiv (Mensch entscheidet)', icon: '🧑💻', desc: 'KI liefert Vorschlaege, Mensch trifft Entscheidung', examples: 'Rechtschreibkorrektur, Suchvorschlaege, Zusammenfassungen' },
|
||||
{ value: 'semi_automated', label: 'Teilautomatisiert (Mensch prueft)', icon: '🤝', desc: 'KI erstellt Ergebnisse, Mensch prueft und bestaetigt', examples: 'E-Mail-Entwuerfe mit Freigabe, KI-Vertraege mit juristischer Pruefung' },
|
||||
{ value: 'fully_automated', label: 'Vollautomatisiert (KI entscheidet)', icon: '🤖', desc: 'KI trifft Entscheidungen eigenstaendig', examples: 'Automatische Kreditentscheidungen, autonome Chatbot-Antworten' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// HOSTING & MODEL (Step 5) — tiles
|
||||
// =============================================================================
|
||||
|
||||
export const HOSTING_PROVIDER_TILES = [
|
||||
{ value: 'self_hosted', label: 'Eigenes Hosting', icon: '🏢', desc: 'On-Premise oder eigene Server' },
|
||||
{ value: 'hetzner', label: 'Hetzner (DE)', icon: '🇩🇪', desc: 'Deutsche Cloud-Infrastruktur' },
|
||||
{ value: 'aws', label: 'AWS', icon: '☁️', desc: 'Amazon Web Services' },
|
||||
{ value: 'azure', label: 'Microsoft Azure', icon: '🔷', desc: 'Microsoft Cloud' },
|
||||
{ value: 'gcp', label: 'Google Cloud', icon: '🔵', desc: 'Google Cloud Platform' },
|
||||
{ value: 'other', label: 'Anderer Anbieter', icon: '🌐', desc: 'Sonstiger Cloud-Anbieter' },
|
||||
]
|
||||
|
||||
export const HOSTING_REGION_TILES = [
|
||||
{ value: 'de', label: 'Deutschland', icon: '🇩🇪', desc: 'Rechenzentrum in Deutschland' },
|
||||
{ value: 'eu', label: 'EU / EWR', icon: '🇪🇺', desc: 'Innerhalb der Europaeischen Union' },
|
||||
{ value: 'us', label: 'USA', icon: '🇺🇸', desc: 'Vereinigte Staaten' },
|
||||
{ value: 'other', label: 'Andere Region', icon: '🌍', desc: 'Drittland ausserhalb EU/USA' },
|
||||
]
|
||||
|
||||
export const MODEL_USAGE_TILES = [
|
||||
{ value: 'inference', label: 'Inferenz', icon: '⚡', desc: 'Fertiges Modell direkt nutzen (z.B. ChatGPT, Claude, DeepL)' },
|
||||
{ value: 'rag', label: 'RAG', icon: '📚', desc: 'Modell erhaelt Kontext aus eigenen Dokumenten' },
|
||||
{ value: 'finetune', label: 'Fine-Tuning', icon: '🎛️', desc: 'Bestehendes Modell mit eigenen Daten nachtrainieren' },
|
||||
{ value: 'training', label: 'Eigenes Modell trainieren', icon: '🧠', desc: 'Komplett eigenes KI-Modell von Grund auf' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// DATA TRANSFER (Step 6) — tiles
|
||||
// =============================================================================
|
||||
|
||||
export const TRANSFER_TARGET_TILES = [
|
||||
{ value: 'no_transfer', label: 'Kein Drittlandtransfer', icon: '🇪🇺', desc: 'Daten verbleiben in der EU/EWR' },
|
||||
{ value: 'usa', label: 'USA', icon: '🇺🇸', desc: 'Datentransfer in die USA' },
|
||||
{ value: 'uk', label: 'Grossbritannien', icon: '🇬🇧', desc: 'Datentransfer nach UK (Angemessenheitsbeschluss)' },
|
||||
{ value: 'switzerland', label: 'Schweiz', icon: '🇨🇭', desc: 'Datentransfer in die Schweiz (Angemessenheitsbeschluss)' },
|
||||
{ value: 'other_adequate', label: 'Anderes Land (Angemessenheit)', icon: '✅', desc: 'Land mit Angemessenheitsbeschluss der EU' },
|
||||
{ value: 'other_third', label: 'Sonstiges Drittland', icon: '🌍', desc: 'Land ohne Angemessenheitsbeschluss' },
|
||||
]
|
||||
|
||||
export const TRANSFER_MECHANISM_TILES = [
|
||||
{ value: 'not_needed', label: 'Nicht erforderlich', icon: '✅', desc: 'Kein Drittlandtransfer oder Angemessenheit' },
|
||||
{ value: 'scc', label: 'Standardvertragsklauseln', icon: '📝', desc: 'SCC nach Art. 46 Abs. 2c DSGVO' },
|
||||
{ value: 'bcr', label: 'Binding Corporate Rules', icon: '🏛️', desc: 'BCR nach Art. 47 DSGVO' },
|
||||
{ value: 'adequacy', label: 'Angemessenheitsbeschluss', icon: '🤝', desc: 'EU-Kommissionsbeschluss (z.B. EU-US DPF)' },
|
||||
{ value: 'derogation', label: 'Ausnahme (Art. 49)', icon: '⚠️', desc: 'Einwilligung oder zwingende Interessen' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// RETENTION (Step 7) — tiles
|
||||
// =============================================================================
|
||||
|
||||
export const RETENTION_TILES = [
|
||||
{ value: 'session', label: 'Nur waehrend Session', icon: '⏱️', desc: 'Daten werden nach Sitzungsende geloescht' },
|
||||
{ value: '30_days', label: '30 Tage', icon: '📅', desc: 'Kurzfristige Aufbewahrung' },
|
||||
{ value: '90_days', label: '90 Tage', icon: '📅', desc: 'Standardaufbewahrung' },
|
||||
{ value: '1_year', label: '1 Jahr', icon: '📆', desc: 'Jaehrliche Aufbewahrung' },
|
||||
{ value: '3_years', label: '3 Jahre', icon: '📆', desc: 'Mittelfristige Aufbewahrung' },
|
||||
{ value: '6_years', label: '6 Jahre', icon: '📆', desc: 'Handelsrechtliche Aufbewahrungsfrist' },
|
||||
{ value: '10_years', label: '10 Jahre', icon: '📆', desc: 'Steuerrechtliche Aufbewahrungsfrist' },
|
||||
{ value: 'indefinite', label: 'Unbefristet', icon: '♾️', desc: 'Keine zeitliche Begrenzung' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// CONTRACTS (Step 8) — tiles
|
||||
// =============================================================================
|
||||
|
||||
export const CONTRACT_TILES = [
|
||||
{ value: 'has_dpa', label: 'AVV / DPA vorhanden', icon: '📄', desc: 'Auftragsverarbeitungsvertrag nach Art. 28 DSGVO' },
|
||||
{ value: 'has_aia_doc', label: 'AI Act Dokumentation', icon: '🤖', desc: 'Risikoklassifizierung und technische Doku nach EU AI Act' },
|
||||
{ value: 'has_dsfa', label: 'DSFA durchgefuehrt', icon: '📋', desc: 'Datenschutz-Folgenabschaetzung nach Art. 35 DSGVO' },
|
||||
{ value: 'has_tia', label: 'TIA durchgefuehrt', icon: '🌍', desc: 'Transfer Impact Assessment fuer Drittlandtransfers' },
|
||||
{ value: 'has_tom', label: 'TOM dokumentiert', icon: '🔒', desc: 'Technisch-organisatorische Massnahmen nach Art. 32 DSGVO' },
|
||||
{ value: 'has_vvt', label: 'Im VVT erfasst', icon: '📚', desc: 'Im Verzeichnis von Verarbeitungstaetigkeiten eingetragen' },
|
||||
{ value: 'has_consent', label: 'Einwilligungen eingeholt', icon: '✅', desc: 'Nutzereinwilligungen vorhanden und dokumentiert' },
|
||||
{ value: 'none', label: 'Noch keine Dokumente', icon: '⚠️', desc: 'Compliance-Dokumentation steht noch aus' },
|
||||
]
|
||||
25
admin-compliance/app/sdk/advisory-board/_types.ts
Normal file
25
admin-compliance/app/sdk/advisory-board/_types.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export interface AdvisoryForm {
|
||||
title: string
|
||||
use_case_text: string
|
||||
domain: string
|
||||
category: string
|
||||
data_categories: string[]
|
||||
custom_data_types: string[]
|
||||
purposes: string[]
|
||||
automation: string
|
||||
hosting_provider: string
|
||||
hosting_region: string
|
||||
model_usage: string[]
|
||||
transfer_targets: string[]
|
||||
transfer_countries: string[]
|
||||
transfer_mechanism: string
|
||||
retention_period: string
|
||||
retention_purpose: string
|
||||
contracts: string[]
|
||||
subprocessors: string
|
||||
}
|
||||
|
||||
export interface StepProps {
|
||||
form: AdvisoryForm
|
||||
updateForm: (updates: Partial<AdvisoryForm>) => void
|
||||
}
|
||||
@@ -4,298 +4,19 @@ import React, { useState, useEffect, Suspense } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { useSDK } from '@/lib/sdk'
|
||||
import { AssessmentResultCard } from '@/components/sdk/use-case-assessment/AssessmentResultCard'
|
||||
|
||||
// =============================================================================
|
||||
// WIZARD STEPS CONFIG
|
||||
// =============================================================================
|
||||
|
||||
const WIZARD_STEPS = [
|
||||
{ id: 1, title: 'Grundlegendes', description: 'Titel, Beschreibung und KI-Kategorie' },
|
||||
{ id: 2, title: 'Datenkategorien', description: 'Welche Daten werden verarbeitet?' },
|
||||
{ id: 3, title: 'Verarbeitungszweck', description: 'Zweck der Datenverarbeitung' },
|
||||
{ id: 4, title: 'Automatisierung', description: 'Grad der Automatisierung' },
|
||||
{ id: 5, title: 'Hosting & Modell', description: 'Technische Details' },
|
||||
{ id: 6, title: 'Datentransfer', description: 'Internationaler Datentransfer' },
|
||||
{ id: 7, title: 'Datenhaltung', description: 'Aufbewahrung und Speicherung' },
|
||||
{ id: 8, title: 'Vertraege', description: 'Compliance und Vereinbarungen' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// KI-Anwendungskategorien als Auswahlkacheln
|
||||
// =============================================================================
|
||||
|
||||
const AI_USE_CATEGORIES = [
|
||||
{ value: 'content_generation', label: 'Content-Erstellung', icon: '✍️', desc: 'Texte, Berichte, E-Mails, Dokumentation automatisch erstellen' },
|
||||
{ value: 'image_generation', label: 'Bilder erstellen', icon: '🎨', desc: 'KI-generierte Bilder, Grafiken, Produktfotos' },
|
||||
{ value: 'marketing_material', label: 'Marketingmaterial', icon: '📢', desc: 'Werbetexte, Social Media Posts, Newsletter generieren' },
|
||||
{ value: 'customer_service', label: 'Kundenservice / Chatbot', icon: '💬', desc: 'Automatisierte Kundenanfragen, FAQ-Bots, Support-Tickets' },
|
||||
{ value: 'crm_analytics', label: 'CRM & Kundenanalyse', icon: '👥', desc: 'Kundensegmentierung, Churn-Vorhersage, Lead-Scoring' },
|
||||
{ value: 'hr_recruiting', label: 'Bewerberauswahl / HR', icon: '🧑💼', desc: 'CV-Screening, Matching, Mitarbeiteranalysen' },
|
||||
{ value: 'financial_analysis', label: 'Finanzdaten analysieren', icon: '📊', desc: 'Buchhaltung, Forecasting, Betrugserkennung, Risikobewertung' },
|
||||
{ value: 'predictive_maintenance', label: 'Predictive Maintenance', icon: '🔧', desc: 'Vorausschauende Wartung, Ausfallvorhersage, IoT-Sensoranalyse' },
|
||||
{ value: 'production_analytics', label: 'Produktionsdatenauswertung', icon: '🏭', desc: 'Qualitaetskontrolle, Prozessoptimierung, OEE-Analyse' },
|
||||
{ value: 'document_analysis', label: 'Dokumentenanalyse', icon: '📄', desc: 'Vertraege, Rechnungen, PDFs automatisch auswerten und klassifizieren' },
|
||||
{ value: 'code_development', label: 'Softwareentwicklung', icon: '💻', desc: 'Code-Generierung, Code-Review, Test-Erstellung, Dokumentation' },
|
||||
{ value: 'translation', label: 'Uebersetzung', icon: '🌍', desc: 'Automatische Uebersetzung von Texten, Dokumenten, Webinhalten' },
|
||||
{ value: 'search_knowledge', label: 'Wissensmanagement / Suche', icon: '🔍', desc: 'Interne Wissensdatenbank, RAG-basierte Suche, FAQ-Systeme' },
|
||||
{ value: 'data_extraction', label: 'Datenextraktion', icon: '⛏️', desc: 'OCR, Formularerkennung, strukturierte Daten aus Freitext' },
|
||||
{ value: 'risk_compliance', label: 'Risiko & Compliance', icon: '⚖️', desc: 'Compliance-Pruefung, Risikobewertung, Audit-Unterstuetzung' },
|
||||
{ value: 'supply_chain', label: 'Lieferkette & Logistik', icon: '🚛', desc: 'Bedarfsprognose, Routenoptimierung, Bestandsmanagement' },
|
||||
{ value: 'medical_health', label: 'Medizin & Gesundheit', icon: '🏥', desc: 'Diagnoseunterstuetzung, Bildanalyse, Patientendaten' },
|
||||
{ value: 'security_monitoring', label: 'Sicherheit & Monitoring', icon: '🛡️', desc: 'Anomalieerkennung, Bedrohungsanalyse, Zugriffskontrolle' },
|
||||
{ value: 'personalization', label: 'Personalisierung', icon: '🎯', desc: 'Produktempfehlungen, dynamische Preisgestaltung, A/B-Testing' },
|
||||
{ value: 'voice_speech', label: 'Sprache & Audio', icon: '🎙️', desc: 'Spracherkennung, Text-to-Speech, Meeting-Transkription' },
|
||||
{ value: 'other', label: 'Sonstiges', icon: '➕', desc: 'Anderer KI-Anwendungsfall' },
|
||||
]
|
||||
|
||||
// Map Profil-Branche to domain value for backend compatibility
|
||||
function industryToDomain(industries: string[]): string {
|
||||
if (!industries || industries.length === 0) return 'general'
|
||||
const first = industries[0].toLowerCase()
|
||||
if (first.includes('gesundheit') || first.includes('pharma')) return 'healthcare'
|
||||
if (first.includes('finanz') || first.includes('versicherung')) return 'finance'
|
||||
if (first.includes('bildung')) return 'education'
|
||||
if (first.includes('handel') || first.includes('commerce')) return 'retail'
|
||||
if (first.includes('it') || first.includes('technologie')) return 'it_services'
|
||||
if (first.includes('beratung') || first.includes('consulting')) return 'consulting'
|
||||
if (first.includes('produktion') || first.includes('industrie') || first.includes('maschinenbau')) return 'manufacturing'
|
||||
if (first.includes('marketing') || first.includes('agentur')) return 'marketing'
|
||||
if (first.includes('recht')) return 'legal'
|
||||
return 'general'
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DATA CATEGORIES (Step 2) — grouped tile selection
|
||||
// =============================================================================
|
||||
|
||||
const DATA_CATEGORY_GROUPS = [
|
||||
{
|
||||
group: 'Stamm- & Kontaktdaten',
|
||||
items: [
|
||||
{ value: 'basic_identity', label: 'Name & Identitaet', icon: '👤', desc: 'Vor-/Nachname, Geburtsdatum, Geschlecht' },
|
||||
{ value: 'contact_data', label: 'Kontaktdaten', icon: '📧', desc: 'E-Mail, Telefon, Fax' },
|
||||
{ value: 'address_data', label: 'Adressdaten', icon: '🏠', desc: 'Wohn-/Meldeadresse, PLZ, Lieferadresse' },
|
||||
{ value: 'government_ids', label: 'Ausweisdaten', icon: '🪪', desc: 'Personalausweis-Nr., Reisepass, Fuehrerschein' },
|
||||
{ value: 'customer_ids', label: 'Kundennummern', icon: '🏷️', desc: 'Kunden-ID, Vertrags-Nr., Mitgliedsnummer' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Besondere Kategorien (Art. 9 DSGVO)',
|
||||
art9: true,
|
||||
items: [
|
||||
{ value: 'health_data', label: 'Gesundheitsdaten', icon: '🏥', desc: 'Diagnosen, Medikation, AU, Pflegegrad' },
|
||||
{ value: 'biometric_data', label: 'Biometrische Daten', icon: '🔐', desc: 'Fingerabdruck, Gesichtserkennung, Iris-Scan' },
|
||||
{ value: 'genetic_data', label: 'Genetische Daten', icon: '🧬', desc: 'DNA-Profil, Genomsequenzen, Erbkrankheitstests' },
|
||||
{ value: 'racial_ethnic', label: 'Ethnische Herkunft', icon: '🌍', desc: 'Rassische/ethnische Zugehoerigkeit' },
|
||||
{ value: 'political_opinions', label: 'Politische Meinungen', icon: '🗳️', desc: 'Politische Ueberzeugungen, Parteizugehoerigkeit' },
|
||||
{ value: 'religious_beliefs', label: 'Religion', icon: '🕊️', desc: 'Religionszugehoerigkeit, Weltanschauung' },
|
||||
{ value: 'trade_union', label: 'Gewerkschaft', icon: '🤝', desc: 'Gewerkschaftsmitgliedschaft' },
|
||||
{ value: 'sexual_orientation', label: 'Sexuelle Orientierung', icon: '🏳️🌈', desc: 'Sexualleben und Orientierung' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Finanz- & Steuerdaten',
|
||||
items: [
|
||||
{ value: 'bank_account', label: 'Bankverbindung', icon: '🏦', desc: 'IBAN, BIC, Kontonummer' },
|
||||
{ value: 'payment_card', label: 'Zahlungskarten', icon: '💳', desc: 'Kreditkarten-Nr., CVV (PCI-DSS)' },
|
||||
{ value: 'transaction_data', label: 'Transaktionsdaten', icon: '🧾', desc: 'Zahlungshistorie, Ueberweisungen, Kaufhistorie' },
|
||||
{ value: 'credit_score', label: 'Bonitaet / Schufa', icon: '📈', desc: 'Kreditwuerdigkeit, Schuldenhistorie' },
|
||||
{ value: 'income_salary', label: 'Einkommen & Gehalt', icon: '💰', desc: 'Bruttogehalt, Nettolohn, Boni' },
|
||||
{ value: 'tax_ids', label: 'Steuer-IDs', icon: '📋', desc: 'Steuer-ID, Steuernummer, USt-IdNr.' },
|
||||
{ value: 'insurance_data', label: 'Versicherungsdaten', icon: '☂️', desc: 'Versicherungsnummern, Policen, Schadenmeldungen' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Fahrzeug- & Mobilitaetsdaten',
|
||||
items: [
|
||||
{ value: 'vehicle_ids', label: 'Fahrzeug-IDs (VIN)', icon: '🚗', desc: 'Fahrgestellnummer (VIN/FIN), Fahrzeugschein' },
|
||||
{ value: 'license_plates', label: 'Kennzeichen', icon: '🔢', desc: 'Amtliches Kennzeichen, Wunschkennzeichen' },
|
||||
{ value: 'gps_tracking', label: 'GPS & Routen', icon: '📍', desc: 'Echtzeitposition, Fahrtenprotokolle' },
|
||||
{ value: 'telematics', label: 'Telematikdaten', icon: '📡', desc: 'Fahrverhalten, Geschwindigkeit, Motordiagnose' },
|
||||
{ value: 'fleet_data', label: 'Fuhrpark / Logistik', icon: '🚛', desc: 'Einsatzzeiten, Kilometerstand, Fahrerzuweisung' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Technische Identifikatoren',
|
||||
items: [
|
||||
{ value: 'ip_address', label: 'IP-Adresse', icon: '🌐', desc: 'IPv4/IPv6 (EuGH: personenbezogen)' },
|
||||
{ value: 'device_ids', label: 'Geraete-IDs', icon: '📱', desc: 'IMEI, UUID, Advertising-ID, Seriennummer' },
|
||||
{ value: 'cookies_tracking', label: 'Cookies & Tracking', icon: '🍪', desc: 'Session-/Persistent Cookies, Pixel-Tags' },
|
||||
{ value: 'browser_fingerprint', label: 'Browser-Fingerprint', icon: '🔎', desc: 'Browser-Typ, OS, Plugins, Canvas-Fingerprint' },
|
||||
{ value: 'mac_address', label: 'MAC-Adresse', icon: '📶', desc: 'Netzwerkadapter-Kennung, WLAN-Praesenz' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Verhaltens- & Nutzungsdaten',
|
||||
items: [
|
||||
{ value: 'clickstream', label: 'Klick- & Nutzungspfade', icon: '🖱️', desc: 'Klickpfade, Scrolltiefe, Verweildauer, Heatmaps' },
|
||||
{ value: 'purchase_history', label: 'Kaufverhalten', icon: '🛒', desc: 'Bestellhistorie, Warenkorb, Wunschlisten' },
|
||||
{ value: 'app_usage', label: 'App-Nutzung', icon: '📲', desc: 'Genutzte Apps, Nutzungsdauer, In-App-Aktivitaeten' },
|
||||
{ value: 'profiling_scores', label: 'Profiling / Scoring', icon: '📊', desc: 'KI-generierte Profile, Segmente, Affinitaetsscores' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Kommunikation & Medien',
|
||||
items: [
|
||||
{ value: 'email_content', label: 'E-Mail-Inhalte', icon: '✉️', desc: 'E-Mail-Texte, Anhaenge, Metadaten' },
|
||||
{ value: 'chat_messages', label: 'Chat & Messaging', icon: '💬', desc: 'Textnachrichten, Messenger, Teams, Slack' },
|
||||
{ value: 'call_recordings', label: 'Telefonaufzeichnungen', icon: '📞', desc: 'Gespraeche, Transkripte, Anrufmetadaten' },
|
||||
{ value: 'video_conference', label: 'Videokonferenzen', icon: '📹', desc: 'Meeting-Aufzeichnungen, Teilnehmerlisten' },
|
||||
{ value: 'photographs', label: 'Fotos & Bilder', icon: '📷', desc: 'Portraitfotos, Profilbilder, Produktfotos' },
|
||||
{ value: 'cctv_surveillance', label: 'Videoueberwachung', icon: '📹', desc: 'CCTV-Aufnahmen, Zutrittskontrolle' },
|
||||
{ value: 'voice_recordings', label: 'Sprachaufnahmen', icon: '🎙️', desc: 'Voicemails, Sprachmemos, Diktate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'HR & Beschaeftigung',
|
||||
items: [
|
||||
{ value: 'employment_data', label: 'Beschaeftigungsdaten', icon: '💼', desc: 'Arbeitgeber, Berufsbezeichnung, Vertragsart' },
|
||||
{ value: 'performance_data', label: 'Leistungsbeurteilungen', icon: '🏆', desc: 'Zielerreichung, Feedback, Abmahnungen' },
|
||||
{ value: 'work_time', label: 'Arbeitszeit', icon: '⏰', desc: 'Zeiterfassung, Ueberstunden, Schichtplaene' },
|
||||
{ value: 'candidate_data', label: 'Bewerberdaten', icon: '📝', desc: 'Lebenslaeufe, Interviews, Assessment-Ergebnisse' },
|
||||
{ value: 'social_security', label: 'Sozialversicherungs-Nr.', icon: '🛡️', desc: 'RVNR (Art. 9 — kodiert Geburtsdatum/Geschlecht)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'IoT & Sensordaten',
|
||||
items: [
|
||||
{ value: 'industrial_sensor', label: 'Industriesensoren', icon: '🏭', desc: 'Maschinendaten, Fehlerprotokolle, Produktionsmesswerte' },
|
||||
{ value: 'wearable_data', label: 'Wearable-Daten', icon: '⌚', desc: 'Herzfrequenz, Schritte, Schlaf (Art. 9 — Gesundheit)' },
|
||||
{ value: 'smart_home', label: 'Smart-Home', icon: '🏡', desc: 'Heizung, Licht, Bewegungsmelder, Nutzungszeiten' },
|
||||
{ value: 'energy_data', label: 'Energieverbrauch', icon: '🔌', desc: 'Smart-Meter, Verbrauchsprofil (enthuellt Verhalten)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Sonstige Kategorien',
|
||||
items: [
|
||||
{ value: 'children_data', label: 'Kinderdaten (unter 16)', icon: '👶', desc: 'Besonderer Schutz, Eltern-Einwilligung erforderlich' },
|
||||
{ value: 'criminal_data', label: 'Strafrechtliche Daten', icon: '⚖️', desc: 'Vorstrafen, Ermittlungsverfahren (Art. 10 DSGVO)' },
|
||||
{ value: 'location_data', label: 'Standortdaten', icon: '📍', desc: 'GPS, Mobilfunk, WLAN-Ortung, Bewegungsprofile' },
|
||||
{ value: 'social_media', label: 'Social-Media-Daten', icon: '📱', desc: 'Profile, Posts, Follower, Interaktionen' },
|
||||
{ value: 'auth_credentials', label: 'Login & Zugangsdaten', icon: '🔑', desc: 'Passwoerter, 2FA, Session-Tokens, Zugriffsprotokolle' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// PROCESSING PURPOSES (Step 3) — tile selection
|
||||
// =============================================================================
|
||||
|
||||
const PURPOSE_TILES = [
|
||||
{ value: 'service_delivery', label: 'Serviceerbringung', icon: '⚙️', desc: 'Kernfunktion des Produkts oder Services' },
|
||||
{ value: 'analytics', label: 'Analyse & BI', icon: '📊', desc: 'Statistische Auswertung, Business Intelligence, Reporting' },
|
||||
{ value: 'marketing', label: 'Marketing & Werbung', icon: '📢', desc: 'Werbung, Personalisierung, Targeting, Newsletter' },
|
||||
{ value: 'profiling', label: 'Profiling', icon: '🎯', desc: 'Automatisierte Analyse personenbezogener Aspekte' },
|
||||
{ value: 'automated_decision', label: 'Automatisierte Entscheidung', icon: '🤖', desc: 'Art. 22 DSGVO — Entscheidung ohne menschliches Zutun' },
|
||||
{ value: 'customer_support', label: 'Kundensupport', icon: '🎧', desc: 'Anfragenbearbeitung, Ticketsystem, Chatbot' },
|
||||
{ value: 'quality_control', label: 'Qualitaetskontrolle', icon: '✅', desc: 'Produktpruefung, Fehleranalyse, Prozessoptimierung' },
|
||||
{ value: 'hr_management', label: 'Personalverwaltung', icon: '👥', desc: 'Recruiting, Onboarding, Mitarbeiterentwicklung' },
|
||||
{ value: 'fraud_detection', label: 'Betrugserkennung', icon: '🕵️', desc: 'Anomalieerkennung, Transaktionsueberwachung' },
|
||||
{ value: 'research', label: 'Forschung & Entwicklung', icon: '🔬', desc: 'Wissenschaftliche Auswertung, Produktentwicklung' },
|
||||
{ value: 'compliance_audit', label: 'Compliance & Audit', icon: '📜', desc: 'Regulatorische Pruefung, Dokumentation, Audit-Trail' },
|
||||
{ value: 'communication', label: 'Kommunikation', icon: '💬', desc: 'Interne/externe Kommunikation, Uebersetzung' },
|
||||
{ value: 'content_creation', label: 'Content-Erstellung', icon: '✍️', desc: 'Text-, Bild-, Video-Generierung' },
|
||||
{ value: 'predictive', label: 'Vorhersage & Prognose', icon: '🔮', desc: 'Demand Forecasting, Predictive Analytics, Wartungsvorhersage' },
|
||||
{ value: 'security', label: 'IT-Sicherheit', icon: '🛡️', desc: 'Bedrohungserkennung, Zugriffskontrolle, Monitoring' },
|
||||
{ value: 'archiving', label: 'Archivierung', icon: '🗄️', desc: 'Gesetzliche Aufbewahrung, Dokumentenarchiv' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// AUTOMATION LEVELS (Step 4) — single-select tiles
|
||||
// =============================================================================
|
||||
|
||||
const AUTOMATION_TILES = [
|
||||
{ value: 'assistive', label: 'Assistiv (Mensch entscheidet)', icon: '🧑💻', desc: 'KI liefert Vorschlaege, Mensch trifft Entscheidung', examples: 'Rechtschreibkorrektur, Suchvorschlaege, Zusammenfassungen' },
|
||||
{ value: 'semi_automated', label: 'Teilautomatisiert (Mensch prueft)', icon: '🤝', desc: 'KI erstellt Ergebnisse, Mensch prueft und bestaetigt', examples: 'E-Mail-Entwuerfe mit Freigabe, KI-Vertraege mit juristischer Pruefung' },
|
||||
{ value: 'fully_automated', label: 'Vollautomatisiert (KI entscheidet)', icon: '🤖', desc: 'KI trifft Entscheidungen eigenstaendig', examples: 'Automatische Kreditentscheidungen, autonome Chatbot-Antworten' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// HOSTING & MODEL (Step 5) — tiles
|
||||
// =============================================================================
|
||||
|
||||
const HOSTING_PROVIDER_TILES = [
|
||||
{ value: 'self_hosted', label: 'Eigenes Hosting', icon: '🏢', desc: 'On-Premise oder eigene Server' },
|
||||
{ value: 'hetzner', label: 'Hetzner (DE)', icon: '🇩🇪', desc: 'Deutsche Cloud-Infrastruktur' },
|
||||
{ value: 'aws', label: 'AWS', icon: '☁️', desc: 'Amazon Web Services' },
|
||||
{ value: 'azure', label: 'Microsoft Azure', icon: '🔷', desc: 'Microsoft Cloud' },
|
||||
{ value: 'gcp', label: 'Google Cloud', icon: '🔵', desc: 'Google Cloud Platform' },
|
||||
{ value: 'other', label: 'Anderer Anbieter', icon: '🌐', desc: 'Sonstiger Cloud-Anbieter' },
|
||||
]
|
||||
|
||||
const HOSTING_REGION_TILES = [
|
||||
{ value: 'de', label: 'Deutschland', icon: '🇩🇪', desc: 'Rechenzentrum in Deutschland' },
|
||||
{ value: 'eu', label: 'EU / EWR', icon: '🇪🇺', desc: 'Innerhalb der Europaeischen Union' },
|
||||
{ value: 'us', label: 'USA', icon: '🇺🇸', desc: 'Vereinigte Staaten' },
|
||||
{ value: 'other', label: 'Andere Region', icon: '🌍', desc: 'Drittland ausserhalb EU/USA' },
|
||||
]
|
||||
|
||||
const MODEL_USAGE_TILES = [
|
||||
{ value: 'inference', label: 'Inferenz', icon: '⚡', desc: 'Fertiges Modell direkt nutzen (z.B. ChatGPT, Claude, DeepL)' },
|
||||
{ value: 'rag', label: 'RAG', icon: '📚', desc: 'Modell erhaelt Kontext aus eigenen Dokumenten' },
|
||||
{ value: 'finetune', label: 'Fine-Tuning', icon: '🎛️', desc: 'Bestehendes Modell mit eigenen Daten nachtrainieren' },
|
||||
{ value: 'training', label: 'Eigenes Modell trainieren', icon: '🧠', desc: 'Komplett eigenes KI-Modell von Grund auf' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// DATA TRANSFER (Step 6) — tiles
|
||||
// =============================================================================
|
||||
|
||||
const TRANSFER_TARGET_TILES = [
|
||||
{ value: 'no_transfer', label: 'Kein Drittlandtransfer', icon: '🇪🇺', desc: 'Daten verbleiben in der EU/EWR' },
|
||||
{ value: 'usa', label: 'USA', icon: '🇺🇸', desc: 'Datentransfer in die USA' },
|
||||
{ value: 'uk', label: 'Grossbritannien', icon: '🇬🇧', desc: 'Datentransfer nach UK (Angemessenheitsbeschluss)' },
|
||||
{ value: 'switzerland', label: 'Schweiz', icon: '🇨🇭', desc: 'Datentransfer in die Schweiz (Angemessenheitsbeschluss)' },
|
||||
{ value: 'other_adequate', label: 'Anderes Land (Angemessenheit)', icon: '✅', desc: 'Land mit Angemessenheitsbeschluss der EU' },
|
||||
{ value: 'other_third', label: 'Sonstiges Drittland', icon: '🌍', desc: 'Land ohne Angemessenheitsbeschluss' },
|
||||
]
|
||||
|
||||
const TRANSFER_MECHANISM_TILES = [
|
||||
{ value: 'not_needed', label: 'Nicht erforderlich', icon: '✅', desc: 'Kein Drittlandtransfer oder Angemessenheit' },
|
||||
{ value: 'scc', label: 'Standardvertragsklauseln', icon: '📝', desc: 'SCC nach Art. 46 Abs. 2c DSGVO' },
|
||||
{ value: 'bcr', label: 'Binding Corporate Rules', icon: '🏛️', desc: 'BCR nach Art. 47 DSGVO' },
|
||||
{ value: 'adequacy', label: 'Angemessenheitsbeschluss', icon: '🤝', desc: 'EU-Kommissionsbeschluss (z.B. EU-US DPF)' },
|
||||
{ value: 'derogation', label: 'Ausnahme (Art. 49)', icon: '⚠️', desc: 'Einwilligung oder zwingende Interessen' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// RETENTION (Step 7) — tiles
|
||||
// =============================================================================
|
||||
|
||||
const RETENTION_TILES = [
|
||||
{ value: 'session', label: 'Nur waehrend Session', icon: '⏱️', desc: 'Daten werden nach Sitzungsende geloescht' },
|
||||
{ value: '30_days', label: '30 Tage', icon: '📅', desc: 'Kurzfristige Aufbewahrung' },
|
||||
{ value: '90_days', label: '90 Tage', icon: '📅', desc: 'Standardaufbewahrung' },
|
||||
{ value: '1_year', label: '1 Jahr', icon: '📆', desc: 'Jaehrliche Aufbewahrung' },
|
||||
{ value: '3_years', label: '3 Jahre', icon: '📆', desc: 'Mittelfristige Aufbewahrung' },
|
||||
{ value: '6_years', label: '6 Jahre', icon: '📆', desc: 'Handelsrechtliche Aufbewahrungsfrist' },
|
||||
{ value: '10_years', label: '10 Jahre', icon: '📆', desc: 'Steuerrechtliche Aufbewahrungsfrist' },
|
||||
{ value: 'indefinite', label: 'Unbefristet', icon: '♾️', desc: 'Keine zeitliche Begrenzung' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// CONTRACTS (Step 8) — tiles
|
||||
// =============================================================================
|
||||
|
||||
const CONTRACT_TILES = [
|
||||
{ value: 'has_dpa', label: 'AVV / DPA vorhanden', icon: '📄', desc: 'Auftragsverarbeitungsvertrag nach Art. 28 DSGVO' },
|
||||
{ value: 'has_aia_doc', label: 'AI Act Dokumentation', icon: '🤖', desc: 'Risikoklassifizierung und technische Doku nach EU AI Act' },
|
||||
{ value: 'has_dsfa', label: 'DSFA durchgefuehrt', icon: '📋', desc: 'Datenschutz-Folgenabschaetzung nach Art. 35 DSGVO' },
|
||||
{ value: 'has_tia', label: 'TIA durchgefuehrt', icon: '🌍', desc: 'Transfer Impact Assessment fuer Drittlandtransfers' },
|
||||
{ value: 'has_tom', label: 'TOM dokumentiert', icon: '🔒', desc: 'Technisch-organisatorische Massnahmen nach Art. 32 DSGVO' },
|
||||
{ value: 'has_vvt', label: 'Im VVT erfasst', icon: '📚', desc: 'Im Verzeichnis von Verarbeitungstaetigkeiten eingetragen' },
|
||||
{ value: 'has_consent', label: 'Einwilligungen eingeholt', icon: '✅', desc: 'Nutzereinwilligungen vorhanden und dokumentiert' },
|
||||
{ value: 'none', label: 'Noch keine Dokumente', icon: '⚠️', desc: 'Compliance-Dokumentation steht noch aus' },
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// SHARED TILE TOGGLE HELPER
|
||||
// =============================================================================
|
||||
|
||||
function toggleInArray(arr: string[], value: string): string[] {
|
||||
return arr.includes(value) ? arr.filter(v => v !== value) : [...arr, value]
|
||||
}
|
||||
import type { AdvisoryForm } from './_types'
|
||||
import { industryToDomain } from './_data'
|
||||
import { StepIndicator } from './_components/StepIndicator'
|
||||
import { Step1Basics } from './_components/Step1Basics'
|
||||
import { Step2DataCategories } from './_components/Step2DataCategories'
|
||||
import { Step3Purposes } from './_components/Step3Purposes'
|
||||
import { Step4Automation } from './_components/Step4Automation'
|
||||
import { Step5Hosting } from './_components/Step5Hosting'
|
||||
import { Step6Transfer } from './_components/Step6Transfer'
|
||||
import { Step7Retention } from './_components/Step7Retention'
|
||||
import { Step8Contracts } from './_components/Step8Contracts'
|
||||
import { NavigationButtons } from './_components/NavigationButtons'
|
||||
import { ResultView } from './_components/ResultView'
|
||||
|
||||
// =============================================================================
|
||||
// MAIN COMPONENT
|
||||
@@ -321,36 +42,28 @@ function AdvisoryBoardPageInner() {
|
||||
)
|
||||
|
||||
// Form state — tile-based multi-select via arrays
|
||||
const [form, setForm] = useState({
|
||||
const [form, setForm] = useState<AdvisoryForm>({
|
||||
title: '',
|
||||
use_case_text: '',
|
||||
domain: 'general',
|
||||
category: '' as string,
|
||||
// Data categories (multi-select tiles)
|
||||
data_categories: [] as string[],
|
||||
custom_data_types: [] as string[],
|
||||
// Purpose (multi-select tiles)
|
||||
purposes: [] as string[],
|
||||
// Automation (single-select tile)
|
||||
automation: '' as string,
|
||||
// Hosting (single-select tile)
|
||||
hosting_provider: '' as string,
|
||||
hosting_region: '' as string,
|
||||
// Model Usage (multi-select tiles)
|
||||
model_usage: [] as string[],
|
||||
// Data Transfer (Step 6 — tiles)
|
||||
transfer_targets: [] as string[],
|
||||
transfer_countries: [] as string[],
|
||||
transfer_mechanism: '' as string,
|
||||
// Retention (Step 7)
|
||||
retention_period: '' as string,
|
||||
category: '',
|
||||
data_categories: [],
|
||||
custom_data_types: [],
|
||||
purposes: [],
|
||||
automation: '',
|
||||
hosting_provider: '',
|
||||
hosting_region: '',
|
||||
model_usage: [],
|
||||
transfer_targets: [],
|
||||
transfer_countries: [],
|
||||
transfer_mechanism: '',
|
||||
retention_period: '',
|
||||
retention_purpose: '',
|
||||
// Contracts (Step 8 — multi-select tiles)
|
||||
contracts: [] as string[],
|
||||
contracts: [],
|
||||
subprocessors: '',
|
||||
})
|
||||
|
||||
const updateForm = (updates: Partial<typeof form>) => {
|
||||
const updateForm = (updates: Partial<AdvisoryForm>) => {
|
||||
setForm(prev => ({ ...prev, ...updates }))
|
||||
}
|
||||
|
||||
@@ -455,32 +168,12 @@ function AdvisoryBoardPageInner() {
|
||||
|
||||
// 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 unknown as Parameters<typeof AssessmentResultCard>[0]['result']} />
|
||||
)}
|
||||
</div>
|
||||
<ResultView
|
||||
result={result}
|
||||
onGoToAssessment={(id) => router.push(`/sdk/use-cases/${id}`)}
|
||||
onGoToOverview={() => router.push('/sdk/use-cases')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -513,29 +206,7 @@ function AdvisoryBoardPageInner() {
|
||||
</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>
|
||||
<StepIndicator currentStep={currentStep} onStepClick={setCurrentStep} />
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
@@ -544,545 +215,27 @@ function AdvisoryBoardPageInner() {
|
||||
|
||||
{/* Step Content */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
{/* Step 1: Grundlegendes */}
|
||||
{currentStep === 1 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Grundlegende Informationen</h2>
|
||||
|
||||
{/* Branche aus Profil (nur Anzeige) */}
|
||||
{profileIndustry && (Array.isArray(profileIndustry) ? profileIndustry.length > 0 : true) && (
|
||||
<div className="bg-gray-50 rounded-lg border border-gray-200 px-4 py-3">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">Branche (aus Unternehmensprofil)</span>
|
||||
<p className="text-sm text-gray-900 mt-0.5">
|
||||
{Array.isArray(profileIndustry) ? profileIndustry.join(', ') : profileIndustry}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Titel des Anwendungsfalls</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>
|
||||
|
||||
{/* KI-Anwendungskategorie als Kacheln */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
In welchem Bereich kommt KI zum Einsatz?
|
||||
</label>
|
||||
<p className="text-sm text-gray-500 mb-3">Waehlen Sie die passende Kategorie fuer Ihren Anwendungsfall.</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{AI_USE_CATEGORIES.map(cat => (
|
||||
<button
|
||||
key={cat.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ category: cat.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.category === cat.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{cat.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{cat.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{cat.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 2: Datenkategorien */}
|
||||
{currentStep === 2 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Welche Daten werden verarbeitet?</h2>
|
||||
<p className="text-sm text-gray-500">Waehlen Sie alle Datenkategorien, die in diesem Use Case verarbeitet werden.</p>
|
||||
|
||||
{DATA_CATEGORY_GROUPS.map(group => (
|
||||
<div key={group.group}>
|
||||
<h3 className={`text-sm font-semibold mb-2 ${group.art9 ? 'text-orange-700' : 'text-gray-700'}`}>
|
||||
{group.art9 && '⚠️ '}{group.group}
|
||||
</h3>
|
||||
{group.art9 && (
|
||||
<p className="text-xs text-orange-600 mb-2">Besonders schutzwuerdig — erhoehte Anforderungen an Rechtsgrundlage und TOM</p>
|
||||
)}
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 mb-4">
|
||||
{group.items.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ data_categories: toggleInArray(form.data_categories, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.data_categories.includes(item.value)
|
||||
? group.art9
|
||||
? 'border-orange-500 bg-orange-50 ring-1 ring-orange-300'
|
||||
: 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Sonstige Datentypen */}
|
||||
<div className="border border-gray-200 rounded-lg p-4 space-y-3">
|
||||
<div className="font-medium text-gray-900">Sonstige Datentypen</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
Falls Ihre Datenkategorie oben nicht aufgefuehrt ist, koennen Sie sie hier ergaenzen.
|
||||
</p>
|
||||
{form.custom_data_types.map((dt, idx) => (
|
||||
<div key={idx} className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={dt}
|
||||
onChange={e => {
|
||||
const updated = [...form.custom_data_types]
|
||||
updated[idx] = e.target.value
|
||||
updateForm({ custom_data_types: updated })
|
||||
}}
|
||||
placeholder="Datentyp eingeben..."
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
<button
|
||||
onClick={() => updateForm({ custom_data_types: form.custom_data_types.filter((_, i) => i !== idx) })}
|
||||
className="p-2 text-red-500 hover:bg-red-50 rounded-lg"
|
||||
title="Entfernen"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
onClick={() => updateForm({ custom_data_types: [...form.custom_data_types, ''] })}
|
||||
className="flex items-center gap-1 text-sm text-purple-600 hover:text-purple-700 font-medium"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
|
||||
Weiteren Datentyp hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{form.data_categories.length > 0 && (
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg px-4 py-3 text-sm text-purple-800">
|
||||
<span className="font-medium">{form.data_categories.length}</span> Datenkategorie{form.data_categories.length !== 1 ? 'n' : ''} ausgewaehlt
|
||||
{form.data_categories.some(c => DATA_CATEGORY_GROUPS.find(g => g.art9)?.items.some(i => i.value === c)) && (
|
||||
<span className="ml-2 text-orange-700 font-medium">— inkl. besonderer Kategorien (Art. 9)</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 3: Verarbeitungszweck */}
|
||||
{currentStep === 3 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Zweck der Verarbeitung</h2>
|
||||
<p className="text-sm text-gray-500">Waehlen Sie alle zutreffenden Verarbeitungszwecke. Die passende Rechtsgrundlage wird vom SDK automatisch ermittelt.</p>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{PURPOSE_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ purposes: toggleInArray(form.purposes, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.purposes.includes(item.value)
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{form.purposes.includes('profiling') && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 text-sm text-amber-800">
|
||||
<div className="font-medium mb-1">Hinweis: Profiling</div>
|
||||
<p>Profiling unterliegt besonderen Anforderungen nach Art. 22 DSGVO. Betroffene haben das Recht auf Information und Widerspruch.</p>
|
||||
</div>
|
||||
)}
|
||||
{form.purposes.includes('automated_decision') && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-sm text-red-800">
|
||||
<div className="font-medium mb-1">Achtung: Automatisierte Entscheidung</div>
|
||||
<p>Art. 22 DSGVO: Vollautomatisierte Entscheidungen mit rechtlicher Wirkung erfordern besondere Schutzmassnahmen, Informationspflichten und das Recht auf menschliche Ueberpruefung.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 4: Automatisierung */}
|
||||
{currentStep === 4 && (
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Grad der Automatisierung</h2>
|
||||
<p className="text-sm text-gray-600">
|
||||
Wie stark greift die KI in Entscheidungen ein? Je hoeher der Automatisierungsgrad, desto strenger die regulatorischen Anforderungen.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
{AUTOMATION_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ automation: item.value })}
|
||||
className={`p-4 rounded-xl border-2 text-left transition-all ${
|
||||
form.automation === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<span className="text-2xl">{item.icon}</span>
|
||||
<span className="text-sm font-semibold text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 ml-11">{item.desc}</p>
|
||||
<p className="text-xs text-gray-400 ml-11 mt-1">Beispiele: {item.examples}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 text-sm text-blue-800">
|
||||
<div className="font-medium mb-1">Warum ist das wichtig?</div>
|
||||
<p>
|
||||
Art. 22 DSGVO regelt automatisierte Einzelentscheidungen. Vollautomatisierte Systeme, die Personen
|
||||
erheblich beeinflussen (z.B. Kreditvergabe, Bewerbungsauswahl), unterliegen strengen Auflagen:
|
||||
Informationspflicht, Recht auf menschliche Ueberpruefung und Anfechtungsmoeglichkeit.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 5: Hosting & Modell */}
|
||||
{currentStep === 5 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Technische Details</h2>
|
||||
|
||||
{/* Hosting Provider */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Hosting-Anbieter</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{HOSTING_PROVIDER_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ hosting_provider: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.hosting_provider === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Hosting Region */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Hosting-Region</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{HOSTING_REGION_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ hosting_region: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.hosting_region === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Model Usage */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-1">Wie wird das KI-Modell genutzt?</h3>
|
||||
<p className="text-sm text-gray-500 mb-3">Waehlen Sie alle zutreffenden Optionen.</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{MODEL_USAGE_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ model_usage: toggleInArray(form.model_usage, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.model_usage.includes(item.value)
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info-Box: Begriffe erklaert */}
|
||||
<details className="bg-amber-50 border border-amber-200 rounded-lg overflow-hidden">
|
||||
<summary className="px-4 py-3 text-sm font-medium text-amber-800 cursor-pointer hover:bg-amber-100">
|
||||
Begriffe erklaert: ML, DL, NLP, LLM — Was bedeutet das?
|
||||
</summary>
|
||||
<div className="px-4 pb-4 space-y-3 text-sm text-amber-900">
|
||||
<div><span className="font-semibold">ML (Machine Learning)</span> — Computer lernt Muster aus Daten. Beispiel: Spam-Filter.</div>
|
||||
<div><span className="font-semibold">DL (Deep Learning)</span> — ML mit neuronalen Netzen. Beispiel: Bilderkennung, Spracherkennung.</div>
|
||||
<div><span className="font-semibold">NLP (Natural Language Processing)</span> — KI versteht Sprache. Beispiel: ChatGPT, DeepL.</div>
|
||||
<div><span className="font-semibold">LLM (Large Language Model)</span> — Grosses Sprachmodell. Beispiel: GPT-4, Claude, Llama.</div>
|
||||
<div><span className="font-semibold">RAG</span> — LLM erhaelt Kontext aus eigener Datenbank. Vorteil: Aktuelle, firmenspezifische Antworten.</div>
|
||||
<div><span className="font-semibold">Fine-Tuning</span> — Bestehendes Modell mit eigenen Daten weitertrainieren. Achtung: Daten werden Teil des Modells.</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 6: Internationaler Datentransfer */}
|
||||
{currentStep === 6 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Internationaler Datentransfer</h2>
|
||||
<p className="text-sm text-gray-500">Wohin werden die Daten uebermittelt? Waehlen Sie alle zutreffenden Ziellaender/-regionen.</p>
|
||||
|
||||
{/* Transfer Targets */}
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Datentransfer-Ziele</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{TRANSFER_TARGET_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ transfer_targets: toggleInArray(form.transfer_targets, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.transfer_targets.includes(item.value)
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Transfer Mechanism — only if not "no_transfer" only */}
|
||||
{form.transfer_targets.length > 0 && !form.transfer_targets.every(t => t === 'no_transfer') && (
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 mb-2">Transfer-Mechanismus</h3>
|
||||
<p className="text-sm text-gray-500 mb-3">Welche Schutzgarantie nutzen Sie fuer den Drittlandtransfer?</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{TRANSFER_MECHANISM_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ transfer_mechanism: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.transfer_mechanism === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Specific countries text input */}
|
||||
{form.transfer_targets.some(t => !['no_transfer'].includes(t)) && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Konkrete Ziellaender (optional)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={form.transfer_countries.join(', ')}
|
||||
onChange={e => updateForm({ transfer_countries: e.target.value.split(',').map(s => s.trim()).filter(Boolean) })}
|
||||
placeholder="z.B. USA, UK, Schweiz, Japan"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">Kommagetrennte Laendernamen oder -kuerzel</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 7: Datenhaltung */}
|
||||
{currentStep === 7 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Datenhaltung & Aufbewahrung</h2>
|
||||
<p className="text-sm text-gray-500">Wie lange sollen die Daten gespeichert werden?</p>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{RETENTION_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ retention_period: item.value })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.retention_period === item.value
|
||||
? 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Zweck der Aufbewahrung (optional)
|
||||
</label>
|
||||
<textarea
|
||||
value={form.retention_purpose}
|
||||
onChange={e => updateForm({ retention_purpose: e.target.value })}
|
||||
rows={2}
|
||||
placeholder="z.B. Vertragliche Pflichten, gesetzliche Aufbewahrungsfristen..."
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{form.retention_period === 'indefinite' && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 text-sm text-amber-800">
|
||||
<div className="font-medium mb-1">Hinweis: Unbefristete Speicherung</div>
|
||||
<p>Die DSGVO fordert Datenminimierung und Speicherbegrenzung (Art. 5 Abs. 1e). Unbefristete Speicherung muss besonders gut begruendet sein.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 8: Vertraege & Compliance */}
|
||||
{currentStep === 8 && (
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Vertraege & Compliance-Dokumentation</h2>
|
||||
<p className="text-sm text-gray-500">Welche Compliance-Dokumente liegen bereits vor? (Mehrfachauswahl moeglich)</p>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||
{CONTRACT_TILES.map(item => (
|
||||
<button
|
||||
key={item.value}
|
||||
type="button"
|
||||
onClick={() => updateForm({ contracts: toggleInArray(form.contracts, item.value) })}
|
||||
className={`p-3 rounded-xl border-2 text-left transition-all ${
|
||||
form.contracts.includes(item.value)
|
||||
? item.value === 'none'
|
||||
? 'border-amber-500 bg-amber-50 ring-1 ring-amber-300'
|
||||
: 'border-purple-500 bg-purple-50 ring-1 ring-purple-300'
|
||||
: 'border-gray-200 hover:border-purple-300 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<span className="text-lg">{item.icon}</span>
|
||||
<span className="text-sm font-medium text-gray-900">{item.label}</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 leading-tight">{item.desc}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Subprozessoren (optional)</label>
|
||||
<textarea
|
||||
value={form.subprocessors}
|
||||
onChange={e => updateForm({ subprocessors: e.target.value })}
|
||||
rows={2}
|
||||
placeholder="z.B. OpenAI (USA, SCC), Hetzner Cloud (DE)..."
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Step1Basics form={form} updateForm={updateForm} profileIndustry={profileIndustry} />
|
||||
)}
|
||||
{currentStep === 2 && <Step2DataCategories form={form} updateForm={updateForm} />}
|
||||
{currentStep === 3 && <Step3Purposes form={form} updateForm={updateForm} />}
|
||||
{currentStep === 4 && <Step4Automation form={form} updateForm={updateForm} />}
|
||||
{currentStep === 5 && <Step5Hosting form={form} updateForm={updateForm} />}
|
||||
{currentStep === 6 && <Step6Transfer form={form} updateForm={updateForm} />}
|
||||
{currentStep === 7 && <Step7Retention form={form} updateForm={updateForm} />}
|
||||
{currentStep === 8 && <Step8Contracts form={form} updateForm={updateForm} />}
|
||||
</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 < 8 ? (
|
||||
<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...
|
||||
</>
|
||||
) : (
|
||||
isEditMode ? 'Speichern & neu bewerten' : 'Assessment starten'
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<NavigationButtons
|
||||
currentStep={currentStep}
|
||||
isSubmitting={isSubmitting}
|
||||
isEditMode={isEditMode}
|
||||
titleEmpty={!form.title}
|
||||
onBack={() => currentStep > 1 ? setCurrentStep(currentStep - 1) : router.push('/sdk/use-cases')}
|
||||
onNext={() => setCurrentStep(currentStep + 1)}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user