feat: IACE CE-Compliance Module — Normen, Risikobewertung, Production Lines

Major features:
- 215 norms library with section references + Beuth URLs (A/B1/B2/C norms)
- 173 hazard patterns with detail fields (scenario, trigger, harm, zone)
- Deterministic pattern matching: Component × Lifecycle × Pattern cross-product
- SIL/PL auto-calculation from S×E×P risk graph
- Risk assessment table with editable S/E/P dropdowns
- Production Line Dashboard with animated station flow (Running Dots)
- IACE process flow + norms coverage on start page
- Non-blocking cookie banner, ProcessFlow SSR fix
- 104 Playwright E2E tests passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-07 10:53:26 +02:00
parent 3853a0838a
commit e7f2f98da3
59 changed files with 8326 additions and 525 deletions
@@ -1,17 +1,21 @@
'use client'
import React from 'react'
import React, { useState } from 'react'
import { useParams } from 'next/navigation'
import { HazardForm } from './_components/HazardForm'
import { HazardTable } from './_components/HazardTable'
import { RiskAssessmentTable } from './_components/RiskAssessmentTable'
import { LibraryModal } from './_components/LibraryModal'
import { AutoSuggestPanel } from './_components/AutoSuggestPanel'
import { useHazards } from './_hooks/useHazards'
type ViewMode = 'list' | 'risk'
export default function HazardsPage() {
const params = useParams()
const projectId = params.projectId as string
const h = useHazards(projectId)
const [view, setView] = useState<ViewMode>('list')
if (h.loading) {
return (
@@ -29,6 +33,16 @@ export default function HazardsPage() {
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Gefaehrdungsanalyse mit 4-Faktor-Risikobewertung (S x F x P x A).
</p>
<div className="mt-2 flex rounded-lg border border-gray-200 dark:border-gray-600 overflow-hidden text-xs">
<button onClick={() => setView('list')}
className={`px-3 py-1.5 font-medium transition-colors ${view === 'list' ? 'bg-purple-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-50'}`}>
Hazard-Liste
</button>
<button onClick={() => setView('risk')}
className={`px-3 py-1.5 font-medium transition-colors border-l border-gray-200 dark:border-gray-600 ${view === 'risk' ? 'bg-purple-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-50'}`}>
Risikobewertung
</button>
</div>
</div>
<div className="flex items-center gap-2">
<button onClick={h.handlePatternMatching} disabled={h.matchingPatterns}
@@ -70,12 +84,12 @@ export default function HazardsPage() {
</div>
</div>
{h.matchResult && h.matchResult.matched_patterns.length > 0 && (
{h.matchResult && h.matchResult.matched_patterns?.length > 0 && (
<AutoSuggestPanel projectId={projectId} matchResult={h.matchResult} applying={h.applyingPatterns}
onApply={h.handleApplyPatterns} onClose={() => h.setMatchResult(null)} />
)}
{h.matchResult && h.matchResult.matched_patterns.length === 0 && (
{h.matchResult && (!h.matchResult.matched_patterns || h.matchResult.matched_patterns.length === 0) && (
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-4 flex items-start gap-3">
<svg className="w-5 h-5 text-yellow-600 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
@@ -121,7 +135,11 @@ export default function HazardsPage() {
)}
{h.hazards.length > 0 ? (
<HazardTable hazards={h.hazards} lifecyclePhases={h.lifecyclePhases} onDelete={h.handleDelete} />
view === 'risk' ? (
<RiskAssessmentTable projectId={projectId} hazards={h.hazards} onReassess={h.refetch} />
) : (
<HazardTable hazards={h.hazards} lifecyclePhases={h.lifecyclePhases} onDelete={h.handleDelete} />
)
) : (
!h.showForm && (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-12 text-center">