'use client'
import React, { useState, useEffect, useRef } from 'react'
import { useParams } from 'next/navigation'
import { TechFileEditor } from '@/components/sdk/iace/TechFileEditor'
import { ReportGenerator } from './_components/ReportGenerator'
import { SECTION_TYPES, STATUS_CONFIG, EXPORT_FORMATS } from './_constants'
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
}
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()
const raw = json.sections || json || []
// Map html_content → content for frontend compatibility
setSections(raw.map((s: Record) => ({
...s,
content: s.content || s.html_content || null,
})))
}
} 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.
{/* Risk Report Export (PDF + Excel) — always available */}
{/* Tech-File Export Dropdown — requires all sections approved */}
{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.
)}
)
}