Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Failing after 39s
CI / test-python-backend-compliance (push) Successful in 44s
CI / test-python-document-crawler (push) Successful in 29s
CI / test-python-dsms-gateway (push) Successful in 24s
Advisory-Board page komplett auf tile-basierte UI umgestellt (wie use-cases/new): - 11 Kachel-Konstanten (50+ Datenkategorien, 16 Zwecke, Hosting, Transfer, etc.) - Array-basiertes Formular statt einzelner Booleans - Client-seitige Risikobewertung entfernt — nur UCCA-Backend - Edit-Modus via ?edit=id (laedt Assessment, sendet PUT) - use-cases/new durch Redirect zu advisory-board ersetzt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1097 lines
59 KiB
TypeScript
1097 lines
59 KiB
TypeScript
'use client'
|
||
|
||
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]
|
||
}
|
||
|
||
// =============================================================================
|
||
// MAIN COMPONENT
|
||
// =============================================================================
|
||
|
||
function AdvisoryBoardPageInner() {
|
||
const router = useRouter()
|
||
const searchParams = useSearchParams()
|
||
const { state: sdkState } = useSDK()
|
||
const editId = searchParams.get('edit')
|
||
const isEditMode = !!editId
|
||
|
||
const [currentStep, setCurrentStep] = useState(1)
|
||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||
const [editLoading, setEditLoading] = useState(false)
|
||
const [result, setResult] = useState<unknown>(null)
|
||
const [error, setError] = useState<string | null>(null)
|
||
|
||
// Derive domain from company profile industry
|
||
const profileIndustry = sdkState.companyProfile?.industry
|
||
const derivedDomain = industryToDomain(
|
||
Array.isArray(profileIndustry) ? profileIndustry : profileIndustry ? [profileIndustry] : []
|
||
)
|
||
|
||
// Form state — tile-based multi-select via arrays
|
||
const [form, setForm] = useState({
|
||
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,
|
||
retention_purpose: '',
|
||
// Contracts (Step 8 — multi-select tiles)
|
||
contracts: [] as string[],
|
||
subprocessors: '',
|
||
})
|
||
|
||
const updateForm = (updates: Partial<typeof form>) => {
|
||
setForm(prev => ({ ...prev, ...updates }))
|
||
}
|
||
|
||
// Auto-set domain from profile
|
||
useEffect(() => {
|
||
if (!isEditMode && derivedDomain !== 'general') {
|
||
updateForm({ domain: derivedDomain })
|
||
}
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [derivedDomain])
|
||
|
||
// Pre-fill form when in edit mode
|
||
useEffect(() => {
|
||
if (!editId) return
|
||
setEditLoading(true)
|
||
fetch(`/api/sdk/v1/ucca/assessments/${editId}`)
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
const intake = data.intake || {}
|
||
setForm({
|
||
title: data.title || '',
|
||
use_case_text: intake.use_case_text || '',
|
||
domain: data.domain || 'general',
|
||
category: data.category || intake.category || '',
|
||
data_categories: intake.data_categories || [],
|
||
custom_data_types: intake.data_types?.custom_data_types || intake.custom_data_types || [],
|
||
purposes: intake.purposes || [],
|
||
automation: intake.automation || '',
|
||
hosting_provider: intake.hosting?.provider || '',
|
||
hosting_region: intake.hosting?.region || '',
|
||
model_usage: intake.model_usage_list || [],
|
||
transfer_targets: intake.transfer_targets || [],
|
||
transfer_countries: intake.international_transfer?.countries || intake.transfer_countries || [],
|
||
transfer_mechanism: intake.transfer_mechanism || intake.international_transfer?.mechanism || '',
|
||
retention_period: intake.retention_period || '',
|
||
retention_purpose: intake.retention?.purpose || intake.retention_purpose || '',
|
||
contracts: intake.contracts_list || [],
|
||
subprocessors: intake.contracts?.subprocessors || intake.subprocessors || '',
|
||
})
|
||
})
|
||
.catch(() => {})
|
||
.finally(() => setEditLoading(false))
|
||
}, [editId])
|
||
|
||
const handleSubmit = async () => {
|
||
setIsSubmitting(true)
|
||
setError(null)
|
||
try {
|
||
const intake = {
|
||
title: form.title,
|
||
use_case_text: form.use_case_text,
|
||
domain: form.domain,
|
||
category: form.category,
|
||
data_categories: form.data_categories,
|
||
custom_data_types: form.custom_data_types.filter(s => s.trim()),
|
||
purposes: form.purposes,
|
||
automation: form.automation,
|
||
hosting: {
|
||
provider: form.hosting_provider,
|
||
region: form.hosting_region,
|
||
},
|
||
model_usage_list: form.model_usage,
|
||
transfer_targets: form.transfer_targets,
|
||
transfer_countries: form.transfer_countries,
|
||
transfer_mechanism: form.transfer_mechanism,
|
||
retention_period: form.retention_period,
|
||
retention_purpose: form.retention_purpose,
|
||
contracts_list: form.contracts,
|
||
subprocessors: form.subprocessors,
|
||
store_raw_text: true,
|
||
}
|
||
|
||
const url = isEditMode
|
||
? `/api/sdk/v1/ucca/assessments/${editId}`
|
||
: '/api/sdk/v1/ucca/assess'
|
||
const method = isEditMode ? 'PUT' : 'POST'
|
||
|
||
const response = await fetch(url, {
|
||
method,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(intake),
|
||
})
|
||
|
||
if (!response.ok) {
|
||
const errData = await response.json().catch(() => null)
|
||
throw new Error(errData?.error || `HTTP ${response.status}`)
|
||
}
|
||
|
||
if (isEditMode) {
|
||
router.push(`/sdk/use-cases/${editId}`)
|
||
return
|
||
}
|
||
|
||
const data = await response.json()
|
||
setResult(data)
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : 'Fehler bei der Bewertung')
|
||
} finally {
|
||
setIsSubmitting(false)
|
||
}
|
||
}
|
||
|
||
// If we have a result, show it
|
||
if (result) {
|
||
const r = result as { assessment?: { id: string }; result?: Record<string, unknown> }
|
||
return (
|
||
<div className="max-w-4xl mx-auto space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<h1 className="text-2xl font-bold text-gray-900">Assessment Ergebnis</h1>
|
||
<div className="flex gap-2">
|
||
{r.assessment?.id && (
|
||
<button
|
||
onClick={() => router.push(`/sdk/use-cases/${r.assessment!.id}`)}
|
||
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"
|
||
>
|
||
Zum Assessment
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={() => router.push('/sdk/use-cases')}
|
||
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200"
|
||
>
|
||
Zur Uebersicht
|
||
</button>
|
||
</div>
|
||
</div>
|
||
{r.result && (
|
||
<AssessmentResultCard result={r.result as unknown as Parameters<typeof AssessmentResultCard>[0]['result']} />
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div className="max-w-3xl mx-auto space-y-6">
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-gray-900">
|
||
{isEditMode ? 'Assessment bearbeiten' : 'Use Case Workshop'}
|
||
</h1>
|
||
<p className="mt-1 text-gray-500">
|
||
{isEditMode
|
||
? 'Angaben anpassen und Assessment neu bewerten'
|
||
: 'Erfassen Sie Ihren KI-Anwendungsfall Schritt fuer Schritt'}
|
||
</p>
|
||
</div>
|
||
<Link
|
||
href="/sdk/advisory-board/documentation"
|
||
className="inline-flex items-center gap-2 px-4 py-2 text-sm text-purple-600 hover:text-purple-700 hover:bg-purple-50 border border-purple-300 rounded-lg transition-colors"
|
||
>
|
||
UCCA-Dokumentation
|
||
</Link>
|
||
</div>
|
||
|
||
{/* Edit loading indicator */}
|
||
{editLoading && (
|
||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4 text-purple-700 text-sm">
|
||
Lade Assessment-Daten...
|
||
</div>
|
||
)}
|
||
|
||
{/* Step Indicator */}
|
||
<div className="flex items-center gap-2">
|
||
{WIZARD_STEPS.map((step, idx) => (
|
||
<React.Fragment key={step.id}>
|
||
<button
|
||
onClick={() => setCurrentStep(step.id)}
|
||
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-colors ${
|
||
currentStep === step.id
|
||
? 'bg-purple-600 text-white'
|
||
: currentStep > step.id
|
||
? 'bg-green-100 text-green-700'
|
||
: 'bg-gray-100 text-gray-500'
|
||
}`}
|
||
>
|
||
<span className="w-6 h-6 rounded-full bg-white/20 flex items-center justify-center text-xs font-bold">
|
||
{currentStep > step.id ? '✓' : step.id}
|
||
</span>
|
||
<span className="hidden md:inline">{step.title}</span>
|
||
</button>
|
||
{idx < WIZARD_STEPS.length - 1 && <div className="flex-1 h-px bg-gray-200" />}
|
||
</React.Fragment>
|
||
))}
|
||
</div>
|
||
|
||
{/* Error */}
|
||
{error && (
|
||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">{error}</div>
|
||
)}
|
||
|
||
{/* Step Content */}
|
||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||
{/* Step 1: Grundlegendes */}
|
||
{currentStep === 1 && (
|
||
<div className="space-y-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>
|
||
)}
|
||
</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>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default function AdvisoryBoardPage() {
|
||
return (
|
||
<Suspense fallback={<div className="flex items-center justify-center h-64 text-gray-500">Lade...</div>}>
|
||
<AdvisoryBoardPageInner />
|
||
</Suspense>
|
||
)
|
||
}
|