diff --git a/admin-compliance/app/(sdk)/sdk/company-profile/page.tsx b/admin-compliance/app/(sdk)/sdk/company-profile/page.tsx index 1ed0ee6..c7e26cd 100644 --- a/admin-compliance/app/(sdk)/sdk/company-profile/page.tsx +++ b/admin-compliance/app/(sdk)/sdk/company-profile/page.tsx @@ -1248,6 +1248,33 @@ export default function CompanyProfilePage() { 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) { diff --git a/admin-compliance/app/(sdk)/sdk/import/page.tsx b/admin-compliance/app/(sdk)/sdk/import/page.tsx index 855746b..68e194d 100644 --- a/admin-compliance/app/(sdk)/sdk/import/page.tsx +++ b/admin-compliance/app/(sdk)/sdk/import/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useCallback } from 'react' +import { useState, useCallback, useEffect } from 'react' import { useRouter } from 'next/navigation' import { useSDK } from '@/lib/sdk' import type { ImportedDocument, ImportedDocumentType, GapAnalysis, GapItem } from '@/lib/sdk/types' @@ -216,7 +216,15 @@ function FileItem({ Analysiere... )} - {file.status === 'complete' && ( + {file.status === 'complete' && file.error === 'offline' && ( +
+ + + + Offline — nicht analysiert +
+ )} + {file.status === 'complete' && file.error !== 'offline' && (
@@ -334,6 +342,42 @@ export default function ImportPage() { const [files, setFiles] = useState([]) const [isAnalyzing, setIsAnalyzing] = useState(false) const [analysisResult, setAnalysisResult] = useState(null) + const [importHistory, setImportHistory] = useState([]) + const [historyLoading, setHistoryLoading] = useState(false) + const [objectUrls, setObjectUrls] = useState([]) + + // 4.1: Load import history + useEffect(() => { + const loadHistory = async () => { + setHistoryLoading(true) + try { + const response = await fetch('/api/sdk/v1/import?tenant_id=default') + if (response.ok) { + const data = await response.json() + setImportHistory(Array.isArray(data) ? data : data.items || []) + } + } catch (err) { + console.error('Failed to load import history:', err) + } finally { + setHistoryLoading(false) + } + } + loadHistory() + }, [analysisResult]) + + // 4.4: Cleanup ObjectURLs on unmount + useEffect(() => { + return () => { + objectUrls.forEach(url => URL.revokeObjectURL(url)) + } + }, [objectUrls]) + + // Helper to create and track ObjectURLs + const createTrackedObjectURL = useCallback((file: File) => { + const url = URL.createObjectURL(file) + setObjectUrls(prev => [...prev, url]) + return url + }, []) const handleFilesAdded = useCallback((newFiles: File[]) => { const uploadedFiles: UploadedFile[] = newFiles.map(file => ({ @@ -391,7 +435,7 @@ export default function ImportPage() { id: result.document_id || file.id, name: file.file.name, type: result.detected_type || file.type, - fileUrl: URL.createObjectURL(file.file), + fileUrl: createTrackedObjectURL(file.file), uploadedAt: new Date(), analyzedAt: new Date(), analysisResult: { @@ -430,7 +474,7 @@ export default function ImportPage() { id: file.id, name: file.file.name, type: file.type, - fileUrl: URL.createObjectURL(file.file), + fileUrl: createTrackedObjectURL(file.file), uploadedAt: new Date(), analyzedAt: new Date(), analysisResult: { @@ -442,7 +486,7 @@ export default function ImportPage() { }, } addImportedDocument(doc) - setFiles(prev => prev.map(f => (f.id === file.id ? { ...f, progress: 100, status: 'complete' as const } : f))) + setFiles(prev => prev.map(f => (f.id === file.id ? { ...f, progress: 100, status: 'complete' as const, error: 'offline' } : f))) } } @@ -565,6 +609,56 @@ export default function ImportPage() {
)} + + {/* Import-Verlauf (4.1) */} + {importHistory.length > 0 && ( +
+
+

Import-Verlauf

+

{importHistory.length} fruehere Imports

+
+
+ {importHistory.map((item: any, idx: number) => ( +
+
+
+ + + +
+
+

{item.name || item.filename || `Import #${idx + 1}`}

+

+ {item.document_type || item.type || 'Unbekannt'} — {item.uploaded_at ? new Date(item.uploaded_at).toLocaleString('de-DE') : 'Unbekannt'} +

+
+
+ +
+ ))} +
+
+ )} + {historyLoading && ( +
Import-Verlauf wird geladen...
+ )} ) } diff --git a/admin-compliance/app/(sdk)/sdk/modules/[moduleId]/page.tsx b/admin-compliance/app/(sdk)/sdk/modules/[moduleId]/page.tsx new file mode 100644 index 0000000..286b056 --- /dev/null +++ b/admin-compliance/app/(sdk)/sdk/modules/[moduleId]/page.tsx @@ -0,0 +1,209 @@ +'use client' + +import React, { useState, useEffect } from 'react' +import { useParams, useRouter } from 'next/navigation' +import Link from 'next/link' + +interface ModuleDetail { + id: string + name: string + display_name: string + description: string + is_active: boolean + criticality: string + processes_pii: boolean + ai_components: boolean + compliance_score: number | null + regulation_count: number + risk_count: number + requirements?: Array<{ + id: string + title: string + status: string + regulation: string + }> + controls?: Array<{ + id: string + title: string + status: string + description: string + }> + regulations?: string[] +} + +export default function ModuleDetailPage() { + const params = useParams() + const router = useRouter() + const moduleId = params.moduleId as string + + const [module, setModule] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + async function load() { + try { + const response = await fetch(`/api/sdk/v1/modules/${encodeURIComponent(moduleId)}`) + if (!response.ok) { + throw new Error('Modul nicht gefunden') + } + const data = await response.json() + setModule(data) + } catch (err) { + setError(err instanceof Error ? err.message : 'Fehler beim Laden') + } finally { + setLoading(false) + } + } + if (moduleId) load() + }, [moduleId]) + + if (loading) { + return ( +
+
Lade Modul-Details...
+
+ ) + } + + if (error || !module) { + return ( +
+
+

Fehler

+

{error || 'Modul nicht gefunden'}

+
+ + Zurueck zur Uebersicht + +
+ ) + } + + const criticalityColors: Record = { + HIGH: 'bg-red-100 text-red-700', + MEDIUM: 'bg-yellow-100 text-yellow-700', + LOW: 'bg-green-100 text-green-700', + } + + return ( +
+ {/* Breadcrumb */} +
+ Module + / + {module.display_name || module.name} +
+ + {/* Header */} +
+
+

{module.display_name || module.name}

+

{module.description}

+
+ + {module.is_active ? 'Aktiv' : 'Inaktiv'} + + + {module.criticality} + + {module.processes_pii && ( + PII + )} + {module.ai_components && ( + KI + )} +
+
+ +
+ + {/* Stats */} +
+
+
Compliance Score
+
+ {module.compliance_score != null ? `${module.compliance_score}%` : '—'} +
+
+
+
Regulierungen
+
{module.regulation_count || 0}
+
+
+
Risiken
+
{module.risk_count || 0}
+
+
+ + {/* Requirements */} + {module.requirements && module.requirements.length > 0 && ( +
+
+

Anforderungen

+
+
+ {module.requirements.map(req => ( +
+
+

{req.title}

+

{req.regulation}

+
+ + {req.status} + +
+ ))} +
+
+ )} + + {/* Controls */} + {module.controls && module.controls.length > 0 && ( +
+
+

Kontrollen

+
+
+ {module.controls.map(ctrl => ( +
+
+

{ctrl.title}

+ + {ctrl.status} + +
+

{ctrl.description}

+
+ ))} +
+
+ )} + + {/* Regulations */} + {module.regulations && module.regulations.length > 0 && ( +
+

Zugeordnete Regulierungen

+
+ {module.regulations.map(reg => ( + {reg} + ))} +
+
+ )} +
+ ) +} diff --git a/admin-compliance/app/(sdk)/sdk/modules/page.tsx b/admin-compliance/app/(sdk)/sdk/modules/page.tsx index 62269b6..337d7b2 100644 --- a/admin-compliance/app/(sdk)/sdk/modules/page.tsx +++ b/admin-compliance/app/(sdk)/sdk/modules/page.tsx @@ -1,6 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' +import { useRouter } from 'next/navigation' import { useSDK, ServiceModule } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader' @@ -126,11 +127,13 @@ function ModuleCard({ isActive, onActivate, onDeactivate, + onConfigure, }: { module: DisplayModule isActive: boolean onActivate: () => void onDeactivate: () => void + onConfigure: () => void }) { const categoryColors = { gdpr: 'bg-blue-100 text-blue-700', @@ -208,7 +211,10 @@ function ModuleCard({
{isActive ? ( <> - + {/* Error Toast */} + {actionError && ( +
+
+ + + +

{actionError}

+
+
+ )} + + {/* Create Module Modal */} + {showCreateModal && ( +
+
+

Eigenes Modul erstellen

+
+
+ + setNewModuleName(e.target.value)} + placeholder="z.B. ISO 42001 AI Management" + className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500" + /> +
+
+ + +
+
+ +