Files
breakpilot-compliance/admin-compliance/app/sdk/company-profile/page.tsx
Benjamin Admin dc0d38ea40
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 35s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 23s
CI / test-python-dsms-gateway (push) Successful in 19s
feat: Vorbereitung-Module auf 100% — Compliance-Scope Backend, DELETE-Endpoints, Proxy-Fixes, blocked-content Tab
Paket A — Kritische Blocker:
- compliance_scope_routes.py: GET + POST UPSERT für sdk_states JSONB-Feld
- compliance/api/__init__.py: compliance_scope_router registriert
- import/route.ts: POST-Proxy für multipart/form-data Upload
- screening/route.ts: POST-Proxy für Dependency-File Upload

Paket B — Backend + UI:
- company_profile_routes.py: DELETE-Endpoint (DSGVO Art. 17)
- company-profile/route.ts: DELETE-Proxy
- company-profile/page.tsx: Profil-löschen-Button mit Bestätigungs-Dialog
- source-policy/pii-rules/[id]/route.ts: GET ergänzt
- source-policy/operations/[id]/route.ts: GET + DELETE ergänzt

Paket C — Tests + UI:
- test_compliance_scope_routes.py: 27 Tests (neu)
- test_import_routes.py: +36 Tests → 60 gesamt
- test_screening_routes.py: +28 Tests → 80+ gesamt
- source-policy/page.tsx: "Blockierte Inhalte" Tab mit Tabelle + Remove

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

1537 lines
59 KiB
TypeScript

'use client'
import React, { useState, useEffect } from 'react'
import { useSDK } from '@/lib/sdk'
import {
CompanyProfile,
BusinessModel,
OfferingType,
TargetMarket,
CompanySize,
LegalForm,
MachineBuilderProfile,
MachineProductType,
AIIntegrationType,
HumanOversightLevel,
CriticalSector,
BUSINESS_MODEL_LABELS,
OFFERING_TYPE_LABELS,
TARGET_MARKET_LABELS,
COMPANY_SIZE_LABELS,
MACHINE_PRODUCT_TYPE_LABELS,
AI_INTEGRATION_TYPE_LABELS,
HUMAN_OVERSIGHT_LABELS,
CRITICAL_SECTOR_LABELS,
SDKCoverageAssessment,
} from '@/lib/sdk/types'
// =============================================================================
// WIZARD STEPS
// =============================================================================
const BASE_WIZARD_STEPS = [
{ id: 1, name: 'Basisinfos', description: 'Firmenname und Rechtsform' },
{ id: 2, name: 'Geschaeftsmodell', description: 'B2B, B2C und Angebote' },
{ id: 3, name: 'Firmengroesse', description: 'Mitarbeiter und Umsatz' },
{ id: 4, name: 'Standorte', description: 'Hauptsitz und Zielmaerkte' },
{ id: 5, name: 'Datenschutz', description: 'Rollen und KI-Nutzung' },
]
const MACHINE_BUILDER_STEP = { id: 6, name: 'Produkt & Maschine', description: 'Software, KI und CE in Ihrem Produkt' }
function getWizardSteps(industry: string) {
if (isMachineBuilderIndustry(industry)) {
return [...BASE_WIZARD_STEPS, MACHINE_BUILDER_STEP]
}
return BASE_WIZARD_STEPS
}
// Keep WIZARD_STEPS for backwards compat in static references
const WIZARD_STEPS = BASE_WIZARD_STEPS
// =============================================================================
// LEGAL FORMS
// =============================================================================
const LEGAL_FORM_LABELS: Record<LegalForm, string> = {
einzelunternehmen: 'Einzelunternehmen',
gbr: 'GbR',
ohg: 'OHG',
kg: 'KG',
gmbh: 'GmbH',
ug: 'UG (haftungsbeschränkt)',
ag: 'AG',
gmbh_co_kg: 'GmbH & Co. KG',
ev: 'e.V. (Verein)',
stiftung: 'Stiftung',
other: 'Sonstige',
}
// =============================================================================
// INDUSTRIES
// =============================================================================
const INDUSTRIES = [
'Technologie / IT',
'E-Commerce / Handel',
'Finanzdienstleistungen',
'Gesundheitswesen',
'Bildung',
'Beratung / Consulting',
'Marketing / Agentur',
'Produktion / Industrie',
'Logistik / Transport',
'Immobilien',
'Maschinenbau',
'Anlagenbau',
'Automatisierung',
'Robotik',
'Messtechnik',
'Sonstige',
]
const MACHINE_BUILDER_INDUSTRIES = [
'Maschinenbau',
'Anlagenbau',
'Automatisierung',
'Robotik',
'Messtechnik',
]
const isMachineBuilderIndustry = (industry: string) =>
MACHINE_BUILDER_INDUSTRIES.includes(industry)
// =============================================================================
// HELPER: ASSESS SDK COVERAGE
// =============================================================================
function assessSDKCoverage(profile: Partial<CompanyProfile>): SDKCoverageAssessment {
const coveredRegulations: string[] = ['DSGVO', 'BDSG', 'TTDSG', 'AI Act']
const partiallyCoveredRegulations: string[] = []
const notCoveredRegulations: string[] = []
const reasons: string[] = []
const recommendations: string[] = []
// Check target markets
const targetMarkets = profile.targetMarkets || []
if (targetMarkets.includes('worldwide')) {
notCoveredRegulations.push('CCPA (Kalifornien)', 'LGPD (Brasilien)', 'POPIA (Südafrika)')
reasons.push('Weltweiter Betrieb erfordert Kenntnisse lokaler Datenschutzgesetze')
recommendations.push('Für außereuropäische Märkte empfehlen wir die Konsultation lokaler Rechtsanwälte')
}
if (targetMarkets.includes('eu_uk')) {
partiallyCoveredRegulations.push('UK GDPR', 'UK AI Framework')
reasons.push('UK-Recht weicht nach Brexit teilweise von EU-Recht ab')
recommendations.push('Prüfen Sie UK-spezifische Anpassungen Ihrer Datenschutzerklärung')
}
// Check company size
if (profile.companySize === 'enterprise' || profile.companySize === 'large') {
coveredRegulations.push('NIS2')
reasons.push('Als größeres Unternehmen können NIS2-Pflichten relevant sein')
}
// Check offerings
const offerings = profile.offerings || []
if (offerings.includes('webshop')) {
coveredRegulations.push('Fernabsatzrecht')
recommendations.push('Widerrufsbelehrung und AGB-Generator sind im SDK enthalten')
}
// Determine if fully covered
const requiresLegalCounsel = notCoveredRegulations.length > 0 || targetMarkets.includes('worldwide')
const isFullyCovered = !requiresLegalCounsel && notCoveredRegulations.length === 0
return {
isFullyCovered,
coveredRegulations,
partiallyCoveredRegulations,
notCoveredRegulations,
requiresLegalCounsel,
reasons,
recommendations,
}
}
// =============================================================================
// STEP COMPONENTS
// =============================================================================
function StepBasicInfo({
data,
onChange,
}: {
data: Partial<CompanyProfile>
onChange: (updates: Partial<CompanyProfile>) => void
}) {
return (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Firmenname <span className="text-red-500">*</span>
</label>
<input
type="text"
value={data.companyName || ''}
onChange={e => onChange({ companyName: e.target.value })}
placeholder="Ihre Firma GmbH"
className="w-full px-4 py-3 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-2">
Rechtsform <span className="text-red-500">*</span>
</label>
<select
value={data.legalForm || ''}
onChange={e => onChange({ legalForm: e.target.value as LegalForm })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="">Bitte wählen...</option>
{Object.entries(LEGAL_FORM_LABELS).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Branche</label>
<select
value={data.industry || ''}
onChange={e => onChange({ industry: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="">Bitte wählen...</option>
{INDUSTRIES.map(industry => (
<option key={industry} value={industry}>
{industry}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Gründungsjahr</label>
<input
type="number"
value={data.foundedYear || ''}
onChange={e => onChange({ foundedYear: parseInt(e.target.value) || null })}
placeholder="2020"
min="1800"
max={new Date().getFullYear()}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
</div>
)
}
function StepBusinessModel({
data,
onChange,
}: {
data: Partial<CompanyProfile>
onChange: (updates: Partial<CompanyProfile>) => void
}) {
const toggleOffering = (offering: OfferingType) => {
const current = data.offerings || []
if (current.includes(offering)) {
onChange({ offerings: current.filter(o => o !== offering) })
} else {
onChange({ offerings: [...current, offering] })
}
}
return (
<div className="space-y-8">
<div>
<label className="block text-sm font-medium text-gray-700 mb-4">
Geschäftsmodell <span className="text-red-500">*</span>
</label>
<div className="grid grid-cols-3 gap-4">
{Object.entries(BUSINESS_MODEL_LABELS).map(([value, label]) => (
<button
key={value}
type="button"
onClick={() => onChange({ businessModel: value as BusinessModel })}
className={`p-4 rounded-xl border-2 text-center transition-all ${
data.businessModel === value
? 'border-purple-500 bg-purple-50 text-purple-700'
: 'border-gray-200 hover:border-purple-300'
}`}
>
<div className="text-2xl mb-2">
{value === 'B2B' ? '🏢' : value === 'B2C' ? '👥' : '🏢👥'}
</div>
<div className="font-medium">{label}</div>
</button>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-4">
Was bieten Sie an? <span className="text-gray-400">(Mehrfachauswahl möglich)</span>
</label>
<div className="grid grid-cols-2 gap-3">
{Object.entries(OFFERING_TYPE_LABELS).map(([value, { label, description }]) => (
<button
key={value}
type="button"
onClick={() => toggleOffering(value as OfferingType)}
className={`p-4 rounded-xl border-2 text-left transition-all ${
(data.offerings || []).includes(value as OfferingType)
? 'border-purple-500 bg-purple-50'
: 'border-gray-200 hover:border-purple-300'
}`}
>
<div className="font-medium text-gray-900">{label}</div>
<div className="text-sm text-gray-500">{description}</div>
</button>
))}
</div>
</div>
</div>
)
}
function StepCompanySize({
data,
onChange,
}: {
data: Partial<CompanyProfile>
onChange: (updates: Partial<CompanyProfile>) => void
}) {
return (
<div className="space-y-8">
<div>
<label className="block text-sm font-medium text-gray-700 mb-4">
Unternehmensgröße <span className="text-red-500">*</span>
</label>
<div className="space-y-3">
{Object.entries(COMPANY_SIZE_LABELS).map(([value, label]) => (
<button
key={value}
type="button"
onClick={() => onChange({ companySize: value as CompanySize })}
className={`w-full p-4 rounded-xl border-2 text-left transition-all ${
data.companySize === value
? 'border-purple-500 bg-purple-50'
: 'border-gray-200 hover:border-purple-300'
}`}
>
<div className="font-medium text-gray-900">{label}</div>
</button>
))}
</div>
</div>
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Mitarbeiterzahl</label>
<select
value={data.employeeCount || ''}
onChange={e => onChange({ employeeCount: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="">Bitte wählen...</option>
<option value="1-9">1-9 Mitarbeiter</option>
<option value="10-49">10-49 Mitarbeiter</option>
<option value="50-249">50-249 Mitarbeiter</option>
<option value="250-999">250-999 Mitarbeiter</option>
<option value="1000+">1.000+ Mitarbeiter</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Jahresumsatz</label>
<select
value={data.annualRevenue || ''}
onChange={e => onChange({ annualRevenue: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="">Bitte wählen...</option>
<option value="< 2 Mio">&lt; 2 Mio. Euro</option>
<option value="2-10 Mio">2-10 Mio. Euro</option>
<option value="10-50 Mio">10-50 Mio. Euro</option>
<option value="> 50 Mio">&gt; 50 Mio. Euro</option>
</select>
</div>
</div>
</div>
)
}
function StepLocations({
data,
onChange,
}: {
data: Partial<CompanyProfile>
onChange: (updates: Partial<CompanyProfile>) => void
}) {
const toggleMarket = (market: TargetMarket) => {
const current = data.targetMarkets || []
if (current.includes(market)) {
onChange({ targetMarkets: current.filter(m => m !== market) })
} else {
onChange({ targetMarkets: [...current, market] })
}
}
return (
<div className="space-y-8">
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Land des Hauptsitzes <span className="text-red-500">*</span>
</label>
<select
value={data.headquartersCountry || ''}
onChange={e => onChange({ headquartersCountry: e.target.value })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="">Bitte wählen...</option>
<option value="DE">Deutschland</option>
<option value="AT">Österreich</option>
<option value="CH">Schweiz</option>
<option value="other">Anderes Land</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Stadt</label>
<input
type="text"
value={data.headquartersCity || ''}
onChange={e => onChange({ headquartersCity: e.target.value })}
placeholder="z.B. Berlin"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-4">
Zielmärkte <span className="text-red-500">*</span>
<span className="text-gray-400 font-normal ml-2">Wo verkaufen/operieren Sie?</span>
</label>
<div className="space-y-3">
{Object.entries(TARGET_MARKET_LABELS).map(([value, { label, description, regulations }]) => (
<button
key={value}
type="button"
onClick={() => toggleMarket(value as TargetMarket)}
className={`w-full p-4 rounded-xl border-2 text-left transition-all ${
(data.targetMarkets || []).includes(value as TargetMarket)
? 'border-purple-500 bg-purple-50'
: 'border-gray-200 hover:border-purple-300'
}`}
>
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-gray-900">{label}</div>
<div className="text-sm text-gray-500">{description}</div>
</div>
<div className="text-right">
<div className="text-xs text-gray-400">Relevante Regulierungen:</div>
<div className="text-xs text-purple-600">{regulations.join(', ')}</div>
</div>
</div>
</button>
))}
</div>
</div>
</div>
)
}
function StepDataProtection({
data,
onChange,
}: {
data: Partial<CompanyProfile>
onChange: (updates: Partial<CompanyProfile>) => void
}) {
return (
<div className="space-y-8">
<div>
<label className="block text-sm font-medium text-gray-700 mb-4">
Datenschutz-Rolle nach DSGVO
</label>
<div className="space-y-3">
<label className="flex items-start gap-4 p-4 rounded-xl border-2 border-gray-200 hover:border-purple-300 cursor-pointer">
<input
type="checkbox"
checked={data.isDataController ?? true}
onChange={e => onChange({ isDataController: e.target.checked })}
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">Verantwortlicher (Art. 4 Nr. 7 DSGVO)</div>
<div className="text-sm text-gray-500">
Wir entscheiden selbst über Zwecke und Mittel der Datenverarbeitung
</div>
</div>
</label>
<label className="flex items-start gap-4 p-4 rounded-xl border-2 border-gray-200 hover:border-purple-300 cursor-pointer">
<input
type="checkbox"
checked={data.isDataProcessor ?? false}
onChange={e => onChange({ isDataProcessor: e.target.checked })}
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">Auftragsverarbeiter (Art. 4 Nr. 8 DSGVO)</div>
<div className="text-sm text-gray-500">
Wir verarbeiten personenbezogene Daten im Auftrag anderer Unternehmen
</div>
</div>
</label>
</div>
</div>
<div>
<label className="flex items-start gap-4 p-4 rounded-xl border-2 border-gray-200 hover:border-purple-300 cursor-pointer">
<input
type="checkbox"
checked={data.usesAI ?? false}
onChange={e => onChange({ usesAI: e.target.checked })}
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900">Wir setzen KI/ML-Systeme ein</div>
<div className="text-sm text-gray-500">
Chatbots, Empfehlungssysteme, automatisierte Entscheidungen, etc.
</div>
</div>
</label>
</div>
<div className="grid grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Datenschutzbeauftragter (Name)
</label>
<input
type="text"
value={data.dpoName || ''}
onChange={e => onChange({ dpoName: e.target.value || null })}
placeholder="Optional"
className="w-full px-4 py-3 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-2">DSB E-Mail</label>
<input
type="email"
value={data.dpoEmail || ''}
onChange={e => onChange({ dpoEmail: e.target.value || null })}
placeholder="dsb@firma.de"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
</div>
</div>
)
}
// =============================================================================
// STEP 6: PRODUKT & MASCHINE (nur fuer Maschinenbauer)
// =============================================================================
const EMPTY_MACHINE_BUILDER: MachineBuilderProfile = {
productTypes: [],
productDescription: '',
productPride: '',
containsSoftware: false,
containsFirmware: false,
containsAI: false,
aiIntegrationType: [],
hasSafetyFunction: false,
safetyFunctionDescription: '',
autonomousBehavior: false,
humanOversightLevel: 'full',
isNetworked: false,
hasRemoteAccess: false,
hasOTAUpdates: false,
updateMechanism: '',
exportMarkets: [],
criticalSectorClients: false,
criticalSectors: [],
oemClients: false,
ceMarkingRequired: false,
existingCEProcess: false,
hasRiskAssessment: false,
}
function StepMachineBuilder({
data,
onChange,
}: {
data: Partial<CompanyProfile>
onChange: (updates: Partial<CompanyProfile>) => void
}) {
const mb = data.machineBuilder || EMPTY_MACHINE_BUILDER
const updateMB = (updates: Partial<MachineBuilderProfile>) => {
onChange({ machineBuilder: { ...mb, ...updates } })
}
const toggleProductType = (type: MachineProductType) => {
const current = mb.productTypes || []
if (current.includes(type)) {
updateMB({ productTypes: current.filter(t => t !== type) })
} else {
updateMB({ productTypes: [...current, type] })
}
}
const toggleAIType = (type: AIIntegrationType) => {
const current = mb.aiIntegrationType || []
if (current.includes(type)) {
updateMB({ aiIntegrationType: current.filter(t => t !== type) })
} else {
updateMB({ aiIntegrationType: [...current, type] })
}
}
const toggleCriticalSector = (sector: CriticalSector) => {
const current = mb.criticalSectors || []
if (current.includes(sector)) {
updateMB({ criticalSectors: current.filter(s => s !== sector) })
} else {
updateMB({ criticalSectors: [...current, sector] })
}
}
return (
<div className="space-y-8">
{/* Block 1: Erzaehlen Sie uns von Ihrer Anlage */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-1">Erzaehlen Sie uns von Ihrer Anlage</h3>
<p className="text-sm text-gray-500 mb-4">
Je besser wir Ihr Produkt verstehen, desto praeziser koennen wir die relevanten Vorschriften identifizieren.
</p>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Was baut Ihr Unternehmen? <span className="text-red-500">*</span>
</label>
<textarea
value={mb.productDescription}
onChange={e => updateMB({ productDescription: e.target.value })}
placeholder="z.B. Wir bauen automatisierte Pruefstaende fuer die Qualitaetskontrolle in der Automobilindustrie..."
rows={3}
className="w-full px-4 py-3 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-2">
Was macht Ihre Anlage besonders?
</label>
<textarea
value={mb.productPride}
onChange={e => updateMB({ productPride: e.target.value })}
placeholder="z.B. Unsere Anlage kann 500 Teile/Stunde mit 99.9% Erkennungsrate pruefen..."
rows={2}
className="w-full px-4 py-3 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-3">
Produkttyp <span className="text-gray-400">(Mehrfachauswahl)</span>
</label>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{Object.entries(MACHINE_PRODUCT_TYPE_LABELS).map(([value, label]) => (
<button
key={value}
type="button"
onClick={() => toggleProductType(value as MachineProductType)}
className={`px-4 py-3 rounded-lg border-2 text-sm font-medium transition-all ${
mb.productTypes.includes(value as MachineProductType)
? 'border-purple-500 bg-purple-50 text-purple-700'
: 'border-gray-200 hover:border-purple-300 text-gray-700'
}`}
>
{label}
</button>
))}
</div>
</div>
</div>
</div>
{/* Block 2: Software & KI */}
<div className="border-t border-gray-200 pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Software & KI in Ihrem Produkt</h3>
<div className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{[
{ key: 'containsSoftware', label: 'Enthaelt Software', desc: 'Anwendungssoftware in der Maschine' },
{ key: 'containsFirmware', label: 'Enthaelt Firmware', desc: 'Embedded Software / Steuerung' },
{ key: 'containsAI', label: 'Enthaelt KI/ML', desc: 'Kuenstliche Intelligenz / Machine Learning' },
].map(item => (
<label
key={item.key}
className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
(mb as any)[item.key]
? 'border-purple-500 bg-purple-50'
: 'border-gray-200 hover:border-purple-300'
}`}
>
<input
type="checkbox"
checked={(mb as any)[item.key] ?? false}
onChange={e => updateMB({ [item.key]: e.target.checked } as any)}
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">{item.label}</div>
<div className="text-xs text-gray-500">{item.desc}</div>
</div>
</label>
))}
</div>
{mb.containsAI && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Art der KI-Integration
</label>
<div className="grid grid-cols-2 gap-3">
{Object.entries(AI_INTEGRATION_TYPE_LABELS).map(([value, label]) => (
<button
key={value}
type="button"
onClick={() => toggleAIType(value as AIIntegrationType)}
className={`px-4 py-2 rounded-lg border text-sm transition-all ${
mb.aiIntegrationType.includes(value as AIIntegrationType)
? 'border-purple-500 bg-purple-50 text-purple-700'
: 'border-gray-200 hover:border-purple-300 text-gray-700'
}`}
>
{label}
</button>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
mb.hasSafetyFunction ? 'border-red-400 bg-red-50' : 'border-gray-200 hover:border-gray-300'
}`}>
<input
type="checkbox"
checked={mb.hasSafetyFunction}
onChange={e => updateMB({ hasSafetyFunction: e.target.checked })}
className="mt-1 w-5 h-5 text-red-600 rounded focus:ring-red-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">Sicherheitsrelevante Funktion</div>
<div className="text-xs text-gray-500">KI/SW hat sicherheitsrelevante Funktion</div>
</div>
</label>
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
mb.autonomousBehavior ? 'border-amber-400 bg-amber-50' : 'border-gray-200 hover:border-gray-300'
}`}>
<input
type="checkbox"
checked={mb.autonomousBehavior}
onChange={e => updateMB({ autonomousBehavior: e.target.checked })}
className="mt-1 w-5 h-5 text-amber-600 rounded focus:ring-amber-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">Autonomes Verhalten</div>
<div className="text-xs text-gray-500">System lernt oder handelt eigenstaendig</div>
</div>
</label>
</div>
{mb.hasSafetyFunction && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Beschreibung der Sicherheitsfunktion
</label>
<textarea
value={mb.safetyFunctionDescription}
onChange={e => updateMB({ safetyFunctionDescription: e.target.value })}
placeholder="z.B. KI-Vision ueberwacht den Schutzbereich und stoppt den Roboter bei Personenerkennung..."
rows={2}
className="w-full px-4 py-3 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-2">
Human Oversight Level
</label>
<select
value={mb.humanOversightLevel}
onChange={e => updateMB({ humanOversightLevel: e.target.value as HumanOversightLevel })}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
{Object.entries(HUMAN_OVERSIGHT_LABELS).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</select>
</div>
</div>
</div>
{/* Block 3: Konnektivitaet & Updates */}
<div className="border-t border-gray-200 pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Konnektivitaet & Updates</h3>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-4">
{[
{ key: 'isNetworked', label: 'Vernetzt', desc: 'Maschine ist mit Netzwerk verbunden' },
{ key: 'hasRemoteAccess', label: 'Remote-Zugriff', desc: 'Fernwartung / Remote-Zugang' },
{ key: 'hasOTAUpdates', label: 'OTA-Updates', desc: 'Drahtlose Software-/Firmware-Updates' },
].map(item => (
<label
key={item.key}
className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
(mb as any)[item.key]
? 'border-purple-500 bg-purple-50'
: 'border-gray-200 hover:border-purple-300'
}`}
>
<input
type="checkbox"
checked={(mb as any)[item.key] ?? false}
onChange={e => updateMB({ [item.key]: e.target.checked } as any)}
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">{item.label}</div>
<div className="text-xs text-gray-500">{item.desc}</div>
</div>
</label>
))}
</div>
{(mb.hasOTAUpdates || mb.hasRemoteAccess) && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Wie werden Updates eingespielt?
</label>
<input
type="text"
value={mb.updateMechanism}
onChange={e => updateMB({ updateMechanism: e.target.value })}
placeholder="z.B. VPN-gesicherter Remote-Zugang mit manueller Freigabe..."
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
)}
</div>
{/* Block 4: Markt & Kunden */}
<div className="border-t border-gray-200 pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Markt & Kunden</h3>
<div className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
mb.criticalSectorClients ? 'border-red-400 bg-red-50' : 'border-gray-200 hover:border-gray-300'
}`}>
<input
type="checkbox"
checked={mb.criticalSectorClients}
onChange={e => updateMB({ criticalSectorClients: e.target.checked })}
className="mt-1 w-5 h-5 text-red-600 rounded focus:ring-red-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">Liefert an KRITIS-Betreiber</div>
<div className="text-xs text-gray-500">Kunden in kritischer Infrastruktur</div>
</div>
</label>
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
mb.oemClients ? 'border-purple-500 bg-purple-50' : 'border-gray-200 hover:border-gray-300'
}`}>
<input
type="checkbox"
checked={mb.oemClients}
onChange={e => updateMB({ oemClients: e.target.checked })}
className="mt-1 w-5 h-5 text-purple-600 rounded focus:ring-purple-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">OEM-Zulieferer</div>
<div className="text-xs text-gray-500">Liefern Komponenten an andere Hersteller</div>
</div>
</label>
</div>
{mb.criticalSectorClients && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
Kritische Sektoren Ihrer Kunden
</label>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
{Object.entries(CRITICAL_SECTOR_LABELS).map(([value, label]) => (
<button
key={value}
type="button"
onClick={() => toggleCriticalSector(value as CriticalSector)}
className={`px-3 py-2 rounded-lg border text-sm transition-all ${
mb.criticalSectors.includes(value as CriticalSector)
? 'border-red-400 bg-red-50 text-red-700'
: 'border-gray-200 hover:border-gray-300 text-gray-700'
}`}
>
{label}
</button>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
mb.ceMarkingRequired ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-gray-300'
}`}>
<input
type="checkbox"
checked={mb.ceMarkingRequired}
onChange={e => updateMB({ ceMarkingRequired: e.target.checked })}
className="mt-1 w-5 h-5 text-blue-600 rounded focus:ring-blue-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">CE-Kennzeichnung erforderlich</div>
<div className="text-xs text-gray-500">Produkt benoetigt CE-Zertifizierung</div>
</div>
</label>
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
mb.existingCEProcess ? 'border-green-400 bg-green-50' : 'border-gray-200 hover:border-gray-300'
}`}>
<input
type="checkbox"
checked={mb.existingCEProcess}
onChange={e => updateMB({ existingCEProcess: e.target.checked })}
className="mt-1 w-5 h-5 text-green-600 rounded focus:ring-green-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">Bestehender CE-Prozess</div>
<div className="text-xs text-gray-500">Bereits ein CE-Verfahren etabliert</div>
</div>
</label>
</div>
{mb.ceMarkingRequired && (
<label className={`flex items-start gap-3 p-4 rounded-xl border-2 cursor-pointer transition-all ${
mb.hasRiskAssessment ? 'border-green-400 bg-green-50' : 'border-red-400 bg-red-50'
}`}>
<input
type="checkbox"
checked={mb.hasRiskAssessment}
onChange={e => updateMB({ hasRiskAssessment: e.target.checked })}
className="mt-1 w-5 h-5 text-green-600 rounded focus:ring-green-500"
/>
<div>
<div className="font-medium text-gray-900 text-sm">Bestehende Risikobeurteilung</div>
<div className="text-xs text-gray-500">
{mb.hasRiskAssessment
? 'Risikobeurteilung vorhanden'
: 'Keine bestehende Risikobeurteilung - IACE hilft Ihnen dabei!'}
</div>
</div>
</label>
)}
</div>
</div>
</div>
)
}
// =============================================================================
// COVERAGE ASSESSMENT COMPONENT
// =============================================================================
function CoverageAssessmentPanel({ profile }: { profile: Partial<CompanyProfile> }) {
const assessment = assessSDKCoverage(profile)
return (
<div className="bg-white rounded-xl border border-gray-200 p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">SDK-Abdeckung</h3>
{/* Status */}
<div
className={`p-4 rounded-lg mb-4 ${
assessment.isFullyCovered
? 'bg-green-50 border border-green-200'
: 'bg-amber-50 border border-amber-200'
}`}
>
<div className="flex items-center gap-2">
{assessment.isFullyCovered ? (
<>
<svg
className="w-5 h-5 text-green-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span className="font-medium text-green-800">Vollständig durch SDK abgedeckt</span>
</>
) : (
<>
<svg
className="w-5 h-5 text-amber-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<span className="font-medium text-amber-800">Teilweise Einschränkungen</span>
</>
)}
</div>
</div>
{/* Covered Regulations */}
{assessment.coveredRegulations.length > 0 && (
<div className="mb-4">
<div className="text-sm font-medium text-gray-700 mb-2">Abgedeckte Regulierungen</div>
<div className="flex flex-wrap gap-2">
{assessment.coveredRegulations.map(reg => (
<span key={reg} className="px-2 py-1 bg-green-100 text-green-700 text-sm rounded-full">
{reg}
</span>
))}
</div>
</div>
)}
{/* Not Covered */}
{assessment.notCoveredRegulations.length > 0 && (
<div className="mb-4">
<div className="text-sm font-medium text-gray-700 mb-2">Nicht abgedeckt</div>
<div className="flex flex-wrap gap-2">
{assessment.notCoveredRegulations.map(reg => (
<span key={reg} className="px-2 py-1 bg-red-100 text-red-700 text-sm rounded-full">
{reg}
</span>
))}
</div>
</div>
)}
{/* Recommendations */}
{assessment.recommendations.length > 0 && (
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
<div className="text-sm font-medium text-blue-800 mb-2">Empfehlungen</div>
<ul className="text-sm text-blue-700 space-y-1">
{assessment.recommendations.map((rec, i) => (
<li key={i} className="flex items-start gap-2">
<span></span>
<span>{rec}</span>
</li>
))}
</ul>
</div>
)}
{/* Legal Counsel Warning */}
{assessment.requiresLegalCounsel && (
<div className="mt-4 p-4 bg-amber-50 border border-amber-200 rounded-lg">
<div className="flex items-start gap-3">
<svg
className="w-5 h-5 text-amber-600 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<div>
<div className="font-medium text-amber-800">Rechtsberatung empfohlen</div>
<div className="text-sm text-amber-700 mt-1">
Basierend auf Ihrem Profil empfehlen wir die Konsultation eines spezialisierten
Rechtsanwalts für Bereiche, die über den Scope dieses SDKs hinausgehen.
</div>
</div>
</div>
</div>
)}
</div>
)
}
// =============================================================================
// MAIN COMPONENT
// =============================================================================
export default function CompanyProfilePage() {
const { state, dispatch, setCompanyProfile, goToNextStep } = useSDK()
const [currentStep, setCurrentStep] = useState(1)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const [formData, setFormData] = useState<Partial<CompanyProfile>>({
companyName: '',
legalForm: undefined,
industry: '',
foundedYear: null,
businessModel: undefined,
offerings: [],
companySize: undefined,
employeeCount: '',
annualRevenue: '',
headquartersCountry: 'DE',
headquartersCity: '',
hasInternationalLocations: false,
internationalCountries: [],
targetMarkets: [],
primaryJurisdiction: 'DE',
isDataController: true,
isDataProcessor: false,
usesAI: false,
aiUseCases: [],
dpoName: null,
dpoEmail: null,
legalContactName: null,
legalContactEmail: null,
isComplete: false,
completedAt: null,
})
const showMachineBuilderStep = isMachineBuilderIndustry(formData.industry || '')
const wizardSteps = getWizardSteps(formData.industry || '')
const totalSteps = wizardSteps.length
const lastStep = wizardSteps[wizardSteps.length - 1].id
// Load existing profile: first try backend, then SDK state as fallback
useEffect(() => {
let cancelled = false
async function loadFromBackend() {
try {
const response = await fetch('/api/sdk/v1/company-profile?tenant_id=default')
if (response.ok) {
const data = await response.json()
if (data && !cancelled) {
const backendProfile: Partial<CompanyProfile> = {
companyName: data.company_name || '',
legalForm: data.legal_form || undefined,
industry: data.industry || '',
foundedYear: data.founded_year || undefined,
businessModel: data.business_model || undefined,
offerings: data.offerings || [],
companySize: data.company_size || undefined,
employeeCount: data.employee_count || '',
annualRevenue: data.annual_revenue || '',
headquartersCountry: data.headquarters_country || 'DE',
headquartersCity: data.headquarters_city || '',
hasInternationalLocations: data.has_international_locations || false,
internationalCountries: data.international_countries || [],
targetMarkets: data.target_markets || [],
primaryJurisdiction: data.primary_jurisdiction || 'DE',
isDataController: data.is_data_controller ?? true,
isDataProcessor: data.is_data_processor ?? false,
usesAI: data.uses_ai ?? false,
aiUseCases: data.ai_use_cases || [],
dpoName: data.dpo_name || '',
dpoEmail: data.dpo_email || '',
isComplete: data.is_complete || false,
}
setFormData(backendProfile)
if (backendProfile.isComplete) {
setCurrentStep(5)
}
return
}
}
} catch {
// Backend not available, fall through to SDK state
}
// Fallback: use SDK state
if (!cancelled && state.companyProfile) {
setFormData(state.companyProfile)
if (state.companyProfile.isComplete) {
setCurrentStep(5)
}
}
}
loadFromBackend()
return () => { cancelled = true }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const updateFormData = (updates: Partial<CompanyProfile>) => {
setFormData(prev => ({ ...prev, ...updates }))
}
const handleNext = () => {
if (currentStep < lastStep) {
// Skip step 6 if not a machine builder
const nextStep = currentStep + 1
if (nextStep === 6 && !showMachineBuilderStep) {
// Complete profile (was step 5, last step for non-machine-builders)
completeAndSaveProfile()
return
}
setCurrentStep(nextStep)
} else {
// Complete profile
completeAndSaveProfile()
}
}
const completeAndSaveProfile = async () => {
const completeProfile: CompanyProfile = {
...formData,
isComplete: true,
completedAt: new Date(),
} as CompanyProfile
setCompanyProfile(completeProfile)
dispatch({ type: 'COMPLETE_STEP', payload: 'company-profile' })
// Also persist to dedicated backend endpoint
try {
await fetch('/api/sdk/v1/company-profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
company_name: formData.companyName || '',
legal_form: formData.legalForm || 'GmbH',
industry: formData.industry || '',
founded_year: formData.foundedYear || null,
business_model: formData.businessModel || 'B2B',
offerings: formData.offerings || [],
company_size: formData.companySize || 'small',
employee_count: formData.employeeCount || '',
annual_revenue: formData.annualRevenue || '',
headquarters_country: formData.headquartersCountry || 'DE',
headquarters_city: formData.headquartersCity || '',
has_international_locations: formData.hasInternationalLocations || false,
international_countries: formData.internationalCountries || [],
target_markets: formData.targetMarkets || [],
primary_jurisdiction: formData.primaryJurisdiction || 'DE',
is_data_controller: formData.isDataController ?? true,
is_data_processor: formData.isDataProcessor ?? false,
uses_ai: formData.usesAI ?? false,
ai_use_cases: formData.aiUseCases || [],
dpo_name: formData.dpoName || '',
dpo_email: formData.dpoEmail || '',
is_complete: true,
// Machine builder fields (if applicable)
...(formData.machineBuilder ? {
machine_builder: {
product_types: formData.machineBuilder.productTypes || [],
product_description: formData.machineBuilder.productDescription || '',
product_pride: formData.machineBuilder.productPride || '',
contains_software: formData.machineBuilder.containsSoftware || false,
contains_firmware: formData.machineBuilder.containsFirmware || false,
contains_ai: formData.machineBuilder.containsAI || false,
ai_integration_type: formData.machineBuilder.aiIntegrationType || [],
has_safety_function: formData.machineBuilder.hasSafetyFunction || false,
safety_function_description: formData.machineBuilder.safetyFunctionDescription || '',
autonomous_behavior: formData.machineBuilder.autonomousBehavior || false,
human_oversight_level: formData.machineBuilder.humanOversightLevel || 'full',
is_networked: formData.machineBuilder.isNetworked || false,
has_remote_access: formData.machineBuilder.hasRemoteAccess || false,
has_ota_updates: formData.machineBuilder.hasOTAUpdates || false,
update_mechanism: formData.machineBuilder.updateMechanism || '',
export_markets: formData.machineBuilder.exportMarkets || [],
critical_sector_clients: formData.machineBuilder.criticalSectorClients || false,
critical_sectors: formData.machineBuilder.criticalSectors || [],
oem_clients: formData.machineBuilder.oemClients || false,
ce_marking_required: formData.machineBuilder.ceMarkingRequired || false,
existing_ce_process: formData.machineBuilder.existingCEProcess || false,
has_risk_assessment: formData.machineBuilder.hasRiskAssessment || false,
},
} : {}),
}),
})
} catch (err) {
console.error('Failed to save company profile to backend:', err)
}
goToNextStep()
}
const handleBack = () => {
if (currentStep > 1) {
setCurrentStep(prev => prev - 1)
}
}
const handleDeleteProfile = async () => {
setIsDeleting(true)
try {
const response = await fetch('/api/sdk/v1/company-profile?tenant_id=default', {
method: 'DELETE',
})
if (response.ok) {
// Reset form and SDK state
setFormData({
companyName: '',
legalForm: undefined,
industry: '',
foundedYear: null,
businessModel: undefined,
offerings: [],
companySize: undefined,
employeeCount: '',
annualRevenue: '',
headquartersCountry: 'DE',
headquartersCity: '',
hasInternationalLocations: false,
internationalCountries: [],
targetMarkets: [],
primaryJurisdiction: 'DE',
isDataController: true,
isDataProcessor: false,
usesAI: false,
aiUseCases: [],
dpoName: null,
dpoEmail: null,
legalContactName: null,
legalContactEmail: null,
isComplete: false,
completedAt: null,
})
setCurrentStep(1)
dispatch({ type: 'SET_STATE', payload: { companyProfile: undefined } })
}
} catch (err) {
console.error('Failed to delete company profile:', err)
} finally {
setIsDeleting(false)
setShowDeleteConfirm(false)
}
}
const canProceed = () => {
switch (currentStep) {
case 1:
return formData.companyName && formData.legalForm
case 2:
return formData.businessModel && (formData.offerings?.length || 0) > 0
case 3:
return formData.companySize
case 4:
return formData.headquartersCountry && (formData.targetMarkets?.length || 0) > 0
case 5:
return true
case 6:
// Machine builder step: require at least product description
return (formData.machineBuilder?.productDescription?.length || 0) > 0
default:
return false
}
}
const isLastStep = currentStep === lastStep || (currentStep === 5 && !showMachineBuilderStep)
return (
<div className="min-h-screen bg-gray-50 py-8">
<div className="max-w-6xl mx-auto px-4">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900">Unternehmensprofil</h1>
<p className="text-gray-600 mt-2">
Helfen Sie uns, Ihr Unternehmen zu verstehen, damit wir die relevanten Regulierungen
identifizieren können.
</p>
</div>
{/* Progress Steps */}
<div className="mb-8">
<div className="flex items-center justify-between">
{wizardSteps.map((step, index) => (
<React.Fragment key={step.id}>
<div className="flex items-center">
<div
className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium ${
step.id < currentStep
? 'bg-purple-600 text-white'
: step.id === currentStep
? 'bg-purple-100 text-purple-600 border-2 border-purple-600'
: 'bg-gray-100 text-gray-400'
}`}
>
{step.id < currentStep ? (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
) : (
step.id
)}
</div>
<div className="ml-3 hidden sm:block">
<div
className={`text-sm font-medium ${
step.id <= currentStep ? 'text-gray-900' : 'text-gray-400'
}`}
>
{step.name}
</div>
</div>
</div>
{index < wizardSteps.length - 1 && (
<div
className={`flex-1 h-0.5 mx-4 ${
step.id < currentStep ? 'bg-purple-600' : 'bg-gray-200'
}`}
/>
)}
</React.Fragment>
))}
</div>
</div>
{/* Content */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Form */}
<div className="lg:col-span-2">
<div className="bg-white rounded-xl border border-gray-200 p-8">
<div className="mb-6">
<h2 className="text-xl font-semibold text-gray-900">
{(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).name}
</h2>
<p className="text-gray-500">{(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).description}</p>
</div>
{currentStep === 1 && <StepBasicInfo data={formData} onChange={updateFormData} />}
{currentStep === 2 && <StepBusinessModel data={formData} onChange={updateFormData} />}
{currentStep === 3 && <StepCompanySize data={formData} onChange={updateFormData} />}
{currentStep === 4 && <StepLocations data={formData} onChange={updateFormData} />}
{currentStep === 5 && <StepDataProtection data={formData} onChange={updateFormData} />}
{currentStep === 6 && showMachineBuilderStep && <StepMachineBuilder data={formData} onChange={updateFormData} />}
{/* Navigation */}
<div className="flex justify-between mt-8 pt-6 border-t border-gray-200">
<button
onClick={handleBack}
disabled={currentStep === 1}
className="px-6 py-3 text-gray-600 hover:text-gray-900 disabled:opacity-50 disabled:cursor-not-allowed"
>
Zurück
</button>
<button
onClick={handleNext}
disabled={!canProceed()}
className="px-8 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLastStep ? 'Profil speichern & weiter' : 'Weiter'}
</button>
</div>
</div>
</div>
{/* Delete Confirmation Modal */}
{showDeleteConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-white rounded-xl p-6 w-full max-w-md shadow-2xl">
<h3 className="text-lg font-semibold text-gray-900 mb-2">Profil löschen?</h3>
<p className="text-sm text-gray-600 mb-6">
Alle gespeicherten Unternehmensdaten werden unwiderruflich gelöscht (DSGVO Art. 17).
Diese Aktion kann nicht rückgängig gemacht werden.
</p>
<div className="flex justify-end gap-3">
<button
onClick={() => setShowDeleteConfirm(false)}
className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg"
>
Abbrechen
</button>
<button
onClick={handleDeleteProfile}
disabled={isDeleting}
className="px-4 py-2 text-sm bg-red-600 text-white rounded-lg hover:bg-red-700 disabled:opacity-50"
>
{isDeleting ? 'Lösche...' : 'Endgültig löschen'}
</button>
</div>
</div>
</div>
)}
{/* Sidebar: Coverage Assessment */}
<div className="lg:col-span-1">
<CoverageAssessmentPanel profile={formData} />
{/* Info Box */}
<div className="mt-6 bg-blue-50 rounded-xl border border-blue-200 p-6">
<div className="flex items-start gap-3">
<svg
className="w-5 h-5 text-blue-600 mt-0.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<div>
<div className="font-medium text-blue-800">Warum diese Fragen?</div>
<div className="text-sm text-blue-700 mt-1">
Diese Informationen helfen uns, die für Ihr Unternehmen relevanten Regulierungen
zu identifizieren und ehrlich zu kommunizieren, wo unsere Grenzen liegen.
</div>
</div>
</div>
</div>
{/* Delete Profile Button */}
{formData.companyName && (
<div className="mt-6">
<button
onClick={() => setShowDeleteConfirm(true)}
className="w-full px-4 py-2 text-sm text-red-600 border border-red-200 rounded-lg hover:bg-red-50 transition-colors"
>
Profil löschen (Art. 17 DSGVO)
</button>
</div>
)}
</div>
</div>
</div>
</div>
)
}