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 [lastExport, setLastExport] = useState(null)
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)
// DSMS archive metadata is forwarded by the backend in X-DSMS-* headers
// when archiving succeeded. If headers are absent (DSMS gateway down)
// the export still works but no badge is shown.
const cid = res.headers.get('x-dsms-cid')
if (cid) {
setLastExport({
cid,
filename: res.headers.get('x-dsms-filename') || `CE-Akte-${projectId}${extension}`,
size: parseInt(res.headers.get('x-dsms-size') || '0', 10) || blob.size,
format,
})
}
}
} 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.
Hinweis: Unterstuetzte Risikobeurteilung — automatisch erkannte Gefaehrdungen und Massnahmen sind eine qualifizierte Ausgangsbasis.
Vollstaendigkeit muss vom CE-Verantwortlichen validiert werden. Dieses Tool ersetzt nicht die Pflichten des Herstellers nach EU 2023/1230.