'use client' import React, { useState, useEffect, useRef } from 'react' import { useParams } from 'next/navigation' import { TechFileEditor } from '@/components/sdk/iace/TechFileEditor' interface TechFileSection { id: string section_type: string title: string description: string content: string | null status: 'empty' | 'draft' | 'generated' | 'reviewed' | 'approved' generated_at: string | null approved_at: string | null approved_by: string | null required: boolean } const SECTION_TYPES: Record = { risk_assessment_report: { icon: '📊', description: 'Zusammenfassung der Risikobeurteilung mit allen bewerteten Gefaehrdungen', }, hazard_log: { icon: '⚠️', description: 'Vollstaendiges Gefaehrdungsprotokoll mit S/E/P-Bewertungen', }, component_list: { icon: '🔧', description: 'Verzeichnis aller sicherheitsrelevanten Komponenten', }, classification_report: { icon: '📋', description: 'Regulatorische Klassifikation (AI Act, MVO, CRA, NIS2)', }, mitigation_report: { icon: '🛡️', description: 'Uebersicht aller Schutzmassnahmen nach 3-Stufen-Verfahren', }, verification_report: { icon: '✅', description: 'Verifikationsplan und Ergebnisse aller Nachweisverfahren', }, evidence_index: { icon: '📎', description: 'Index aller Nachweisdokumente mit Verknuepfungen', }, declaration_of_conformity: { icon: '📜', description: 'EU-Konformitaetserklaerung', }, instructions_for_use: { icon: '📖', description: 'Sicherheitshinweise fuer Betriebsanleitung', }, monitoring_plan: { icon: '📡', description: 'Post-Market Surveillance Plan', }, } const STATUS_CONFIG: Record = { empty: { label: 'Leer', color: 'text-gray-500', bgColor: 'bg-gray-100' }, draft: { label: 'Entwurf', color: 'text-yellow-700', bgColor: 'bg-yellow-100' }, generated: { label: 'Generiert', color: 'text-blue-700', bgColor: 'bg-blue-100' }, reviewed: { label: 'Geprueft', color: 'text-orange-700', bgColor: 'bg-orange-100' }, approved: { label: 'Freigegeben', color: 'text-green-700', bgColor: 'bg-green-100' }, } const EXPORT_FORMATS: { value: string; label: string; extension: string }[] = [ { value: 'pdf', label: 'PDF', extension: '.pdf' }, { value: 'xlsx', label: 'Excel', extension: '.xlsx' }, { value: 'docx', label: 'Word', extension: '.docx' }, { value: 'md', label: 'Markdown', extension: '.md' }, { value: 'json', label: 'JSON', extension: '.json' }, ] function StatusBadge({ status }: { status: string }) { const config = STATUS_CONFIG[status] || STATUS_CONFIG.empty return ( {config.label} ) } function SectionViewer({ section, onClose, onApprove, onSave, }: { section: TechFileSection onClose: () => void onApprove: (id: string) => void onSave: (id: string, content: string) => void }) { const [editing, setEditing] = useState(false) return (
{SECTION_TYPES[section.section_type]?.icon || '📄'}

{section.title}

{!editing && section.content && ( )} {editing && ( )} {section.status !== 'approved' && section.content && !editing && ( )}
{section.content ? ( editing ? ( onSave(section.id, html)} /> ) : ( {}} readOnly /> ) ) : (
Kein Inhalt vorhanden. Klicken Sie "Generieren" um den Abschnitt zu erstellen.
)}
) } export default function TechFilePage() { const params = useParams() const projectId = params.projectId as string const [sections, setSections] = useState([]) const [loading, setLoading] = useState(true) const [generatingSection, setGeneratingSection] = useState(null) const [viewingSection, setViewingSection] = useState(null) const [exporting, setExporting] = useState(false) const [showExportMenu, setShowExportMenu] = useState(false) const exportMenuRef = useRef(null) // Close export menu when clicking outside useEffect(() => { function handleClickOutside(event: MouseEvent) { if (exportMenuRef.current && !exportMenuRef.current.contains(event.target as Node)) { setShowExportMenu(false) } } if (showExportMenu) { document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) } }, [showExportMenu]) useEffect(() => { fetchSections() }, [projectId]) async function fetchSections() { try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/tech-file`) if (res.ok) { const json = await res.json() setSections(json.sections || json || []) } } catch (err) { console.error('Failed to fetch tech file sections:', err) } finally { setLoading(false) } } async function handleGenerate(sectionId: string) { setGeneratingSection(sectionId) try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/tech-file/${sectionId}/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }) if (res.ok) { await fetchSections() } } catch (err) { console.error('Failed to generate section:', err) } finally { setGeneratingSection(null) } } async function handleApprove(sectionId: string) { try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/tech-file/${sectionId}/approve`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }) if (res.ok) { await fetchSections() if (viewingSection && viewingSection.id === sectionId) { const updated = sections.find((s) => s.id === sectionId) if (updated) setViewingSection({ ...updated, status: 'approved' }) } } } catch (err) { console.error('Failed to approve section:', err) } } async function handleSave(sectionId: string, content: string) { try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/tech-file/${sectionId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }), }) if (res.ok) { await fetchSections() } } catch (err) { console.error('Failed to save section:', err) } } async function handleExport(format: string) { setExporting(true) setShowExportMenu(false) try { const res = await fetch( `/api/sdk/v1/iace/projects/${projectId}/tech-file/export?format=${format}`, { method: 'GET' } ) if (res.ok) { const blob = await res.blob() const url = window.URL.createObjectURL(blob) const formatConfig = EXPORT_FORMATS.find((f) => f.value === format) const extension = formatConfig?.extension || `.${format}` const a = document.createElement('a') a.href = url a.download = `CE-Akte-${projectId}${extension}` document.body.appendChild(a) a.click() document.body.removeChild(a) window.URL.revokeObjectURL(url) } } catch (err) { console.error('Failed to export:', err) } finally { setExporting(false) } } const approvedCount = sections.filter((s) => s.status === 'approved').length const requiredCount = sections.filter((s) => s.required).length const requiredApproved = sections.filter((s) => s.required && s.status === 'approved').length const allRequiredApproved = requiredApproved === requiredCount && requiredCount > 0 if (loading) { return (
) } return (
{/* Header */}

CE-Akte (Technical File)

Technische Dokumentation gemaess Maschinenverordnung Anhang IV. Generieren, pruefen und freigeben Sie alle erforderlichen Abschnitte.

{/* Export Dropdown */}
{showExportMenu && allRequiredApproved && !exporting && (
{EXPORT_FORMATS.map((fmt) => ( ))}
)}
{/* Progress */}
Fortschritt: {approvedCount} von {sections.length} Abschnitten freigegeben Pflicht: {requiredApproved}/{requiredCount}
0 ? (approvedCount / sections.length) * 100 : 0}%` }} />
{/* Section Viewer */} {viewingSection && ( setViewingSection(null)} onApprove={handleApprove} onSave={handleSave} /> )} {/* Sections List */}
{sections.map((section) => (
{SECTION_TYPES[section.section_type]?.icon || '📄'}

{section.title}

{section.required && ( Pflicht )}

{SECTION_TYPES[section.section_type]?.description || section.description}

{section.approved_at && ( Freigegeben am {new Date(section.approved_at).toLocaleDateString('de-DE')} )}
{section.content && ( )} {section.content && section.status !== 'approved' && ( )}
))}
{sections.length === 0 && (

Keine Abschnitte vorhanden

Die CE-Akte wird automatisch strukturiert, sobald Komponenten und Gefaehrdungen erfasst sind.

)}
) }