feat(iace): sync IACE frontend, API routes, and scope engine updates from breakpilot-pwa
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 29s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 21s
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 29s
CI / test-python-document-crawler (push) Successful in 24s
CI / test-python-dsms-gateway (push) Successful in 21s
- Add IACE project pages (classification, evidence, hazards, mitigations, monitoring, tech-file, verification) - Add IACE API catch-all route - Update compliance-scope-engine with IACE AI Act product triggers - Update compliance-scope-types, navigation, roles, and sidebar for IACE - Update company-profile page
This commit is contained in:
@@ -9,10 +9,19 @@ import {
|
||||
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'
|
||||
|
||||
@@ -20,14 +29,26 @@ import {
|
||||
// WIZARD STEPS
|
||||
// =============================================================================
|
||||
|
||||
const WIZARD_STEPS = [
|
||||
const BASE_WIZARD_STEPS = [
|
||||
{ id: 1, name: 'Basisinfos', description: 'Firmenname und Rechtsform' },
|
||||
{ id: 2, name: 'Geschäftsmodell', description: 'B2B, B2C und Angebote' },
|
||||
{ id: 3, name: 'Firmengröße', description: 'Mitarbeiter und Umsatz' },
|
||||
{ id: 4, name: 'Standorte', description: 'Hauptsitz und Zielmärkte' },
|
||||
{ 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
|
||||
// =============================================================================
|
||||
@@ -61,9 +82,25 @@ const INDUSTRIES = [
|
||||
'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
|
||||
// =============================================================================
|
||||
@@ -504,6 +541,423 @@ function StepDataProtection({
|
||||
)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 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
|
||||
// =============================================================================
|
||||
@@ -671,6 +1125,11 @@ export default function CompanyProfilePage() {
|
||||
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
|
||||
useEffect(() => {
|
||||
if (state.companyProfile) {
|
||||
@@ -687,22 +1146,33 @@ export default function CompanyProfilePage() {
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
if (currentStep < 5) {
|
||||
setCurrentStep(prev => prev + 1)
|
||||
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
|
||||
const completeProfile: CompanyProfile = {
|
||||
...formData,
|
||||
isComplete: true,
|
||||
completedAt: new Date(),
|
||||
} as CompanyProfile
|
||||
|
||||
setCompanyProfile(completeProfile)
|
||||
dispatch({ type: 'COMPLETE_STEP', payload: 'company-profile' })
|
||||
goToNextStep()
|
||||
completeAndSaveProfile()
|
||||
}
|
||||
}
|
||||
|
||||
const completeAndSaveProfile = () => {
|
||||
const completeProfile: CompanyProfile = {
|
||||
...formData,
|
||||
isComplete: true,
|
||||
completedAt: new Date(),
|
||||
} as CompanyProfile
|
||||
|
||||
setCompanyProfile(completeProfile)
|
||||
dispatch({ type: 'COMPLETE_STEP', payload: 'company-profile' })
|
||||
goToNextStep()
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
if (currentStep > 1) {
|
||||
setCurrentStep(prev => prev - 1)
|
||||
@@ -721,11 +1191,16 @@ export default function CompanyProfilePage() {
|
||||
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">
|
||||
@@ -741,7 +1216,7 @@ export default function CompanyProfilePage() {
|
||||
{/* Progress Steps */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center justify-between">
|
||||
{WIZARD_STEPS.map((step, index) => (
|
||||
{wizardSteps.map((step, index) => (
|
||||
<React.Fragment key={step.id}>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
@@ -775,7 +1250,7 @@ export default function CompanyProfilePage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{index < WIZARD_STEPS.length - 1 && (
|
||||
{index < wizardSteps.length - 1 && (
|
||||
<div
|
||||
className={`flex-1 h-0.5 mx-4 ${
|
||||
step.id < currentStep ? 'bg-purple-600' : 'bg-gray-200'
|
||||
@@ -794,9 +1269,9 @@ export default function CompanyProfilePage() {
|
||||
<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">
|
||||
{WIZARD_STEPS[currentStep - 1].name}
|
||||
{(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).name}
|
||||
</h2>
|
||||
<p className="text-gray-500">{WIZARD_STEPS[currentStep - 1].description}</p>
|
||||
<p className="text-gray-500">{(wizardSteps.find(s => s.id === currentStep) || wizardSteps[0]).description}</p>
|
||||
</div>
|
||||
|
||||
{currentStep === 1 && <StepBasicInfo data={formData} onChange={updateFormData} />}
|
||||
@@ -804,6 +1279,7 @@ export default function CompanyProfilePage() {
|
||||
{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">
|
||||
@@ -819,7 +1295,7 @@ export default function CompanyProfilePage() {
|
||||
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"
|
||||
>
|
||||
{currentStep === 5 ? 'Profil speichern & weiter' : 'Weiter'}
|
||||
{isLastStep ? 'Profil speichern & weiter' : 'Weiter'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user