diff --git a/admin-compliance/app/sdk/company-profile/page.tsx b/admin-compliance/app/sdk/company-profile/page.tsx index a9df8f7..836d1d7 100644 --- a/admin-compliance/app/sdk/company-profile/page.tsx +++ b/admin-compliance/app/sdk/company-profile/page.tsx @@ -1544,6 +1544,94 @@ export default function CompanyProfilePage() { setFormData(prev => ({ ...prev, ...updates })) } + // Shared payload builder for draft saves and final save (DRY) + const buildProfilePayload = (isComplete: boolean) => ({ + 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: isComplete, + // Phase 2 extended fields + processing_systems: (formData as any).processingSystems || [], + ai_systems: (formData as any).aiSystems || [], + technical_contacts: (formData as any).technicalContacts || [], + subject_to_nis2: (formData as any).subjectToNis2 || false, + subject_to_ai_act: (formData as any).subjectToAiAct || false, + subject_to_iso27001: (formData as any).subjectToIso27001 || false, + supervisory_authority: (formData as any).supervisoryAuthority || '', + review_cycle_months: (formData as any).reviewCycleMonths || 12, + repos: (formData as any).repos || [], + document_sources: (formData as any).documentSources || [], + // 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, + }, + } : {}), + }) + + // Auto-save draft to backend (fire-and-forget, non-blocking) + const [draftSaveStatus, setDraftSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle') + const draftSaveTimerRef = React.useRef | null>(null) + + const saveProfileDraft = async () => { + setDraftSaveStatus('saving') + try { + await fetch('/api/sdk/v1/company-profile', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(buildProfilePayload(false)), + }) + setDraftSaveStatus('saved') + // Reset status after 3 seconds + if (draftSaveTimerRef.current) clearTimeout(draftSaveTimerRef.current) + draftSaveTimerRef.current = setTimeout(() => setDraftSaveStatus('idle'), 3000) + } catch (err) { + console.error('Draft save failed:', err) + setDraftSaveStatus('error') + if (draftSaveTimerRef.current) clearTimeout(draftSaveTimerRef.current) + draftSaveTimerRef.current = setTimeout(() => setDraftSaveStatus('idle'), 5000) + } + } + const handleNext = () => { if (currentStep < lastStep) { // Skip step 8 if not a machine builder @@ -1553,6 +1641,7 @@ export default function CompanyProfilePage() { completeAndSaveProfile() return } + saveProfileDraft() setCurrentStep(nextStep) } else { // Complete profile @@ -1575,68 +1664,7 @@ export default function CompanyProfilePage() { 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, - // Phase 2 extended fields - processing_systems: (formData as any).processingSystems || [], - ai_systems: (formData as any).aiSystems || [], - technical_contacts: (formData as any).technicalContacts || [], - subject_to_nis2: (formData as any).subjectToNis2 || false, - subject_to_ai_act: (formData as any).subjectToAiAct || false, - subject_to_iso27001: (formData as any).subjectToIso27001 || false, - supervisory_authority: (formData as any).supervisoryAuthority || '', - review_cycle_months: (formData as any).reviewCycleMonths || 12, - repos: (formData as any).repos || [], - document_sources: (formData as any).documentSources || [], - // 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, - }, - } : {}), - }), + body: JSON.stringify(buildProfilePayload(true)), }) } catch (err) { console.error('Failed to save company profile to backend:', err) @@ -1647,6 +1675,7 @@ export default function CompanyProfilePage() { const handleBack = () => { if (currentStep > 1) { + saveProfileDraft() setCurrentStep(prev => prev - 1) } } @@ -1806,7 +1835,7 @@ export default function CompanyProfilePage() { {currentStep === 8 && showMachineBuilderStep && } {/* Navigation */} -
+
+ {/* Draft save status */} + {draftSaveStatus !== 'idle' && ( + + {draftSaveStatus === 'saving' && 'Speichern...'} + {draftSaveStatus === 'saved' && '✓ Gespeichert'} + {draftSaveStatus === 'error' && 'Speichern fehlgeschlagen'} + + )}
+ + {/* Session Info Bar */} +
+ {/* Version */} + v{state.version} + + | + + {/* Current step / last activity */} + + Zuletzt: {currentStep?.name || 'Dashboard'} + + + | + + {/* Last saved time */} + + {formatTimeAgo(syncState.lastSyncedAt ? new Date(syncState.lastSyncedAt) : state.lastModified ? new Date(state.lastModified) : null)} + + + | + + {/* Sync status dot */} + + + {syncConfig.label} + + + {/* User (only if not default) */} + {state.userId && state.userId !== 'default' && ( + <> + | + Bearbeiter: {state.userId} + + )} +
) }