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:
@@ -0,0 +1,172 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
interface NormStats {
|
||||
total: number
|
||||
byType: Record<string, number>
|
||||
categories: string[]
|
||||
}
|
||||
|
||||
const TYPE_INFO: Record<string, { label: string; color: string }> = {
|
||||
A: { label: 'A-Normen (Grundnormen)', color: 'bg-red-50 text-red-800 border-red-200' },
|
||||
B1: { label: 'B1-Normen (Sicherheitsgrundnormen)', color: 'bg-blue-50 text-blue-800 border-blue-200' },
|
||||
B2: { label: 'B2-Normen (Sicherheitsfachgrundnormen)', color: 'bg-green-50 text-green-800 border-green-200' },
|
||||
C: { label: 'C-Normen (Maschinenspezifisch)', color: 'bg-purple-50 text-purple-800 border-purple-200' },
|
||||
}
|
||||
|
||||
const CATEGORY_DESCRIPTIONS: Record<string, string> = {
|
||||
A: 'ISO 12100 (Grundnorm Risikobeurteilung)',
|
||||
B1: 'ISO 13849-1/2, IEC 62061, IEC 61508 (SIL/PL, Funktionale Sicherheit)',
|
||||
B2: 'Elektrik, Ergonomie, Vibration, Laerm, Brandschutz, Hydraulik/Pneumatik, Software-Safety, Emissionen, Schutzeinrichtungen, Zugaenge, Signale',
|
||||
C: '',
|
||||
}
|
||||
|
||||
export function NormsCoverage() {
|
||||
const [stats, setStats] = useState<NormStats | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/sdk/v1/iace/norms-library')
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((json) => {
|
||||
if (!json?.norms) return
|
||||
const norms = json.norms as Array<{ norm_type: string; machine_types?: string[] }>
|
||||
const byType: Record<string, number> = {}
|
||||
const machineTypes = new Set<string>()
|
||||
for (const n of norms) {
|
||||
byType[n.norm_type] = (byType[n.norm_type] || 0) + 1
|
||||
if (n.machine_types) {
|
||||
for (const mt of n.machine_types) machineTypes.add(mt)
|
||||
}
|
||||
}
|
||||
// Group machine types into readable categories
|
||||
const catMap: Record<string, string> = {
|
||||
press: 'Pressen', hydraulic_press: 'Pressen', mechanical_press: 'Pressen', press_brake: 'Pressen',
|
||||
robot: 'Roboter', industrial_robot: 'Roboter', robot_cell: 'Roboter',
|
||||
collaborative_robot: 'Kollaborierende Roboter', cobot: 'Kollaborierende Roboter',
|
||||
woodworking: 'Holzbearbeitung', saw: 'Holzbearbeitung', circular_saw: 'Holzbearbeitung',
|
||||
panel_saw: 'Holzbearbeitung', table_saw: 'Holzbearbeitung', miter_saw: 'Holzbearbeitung',
|
||||
log_saw: 'Holzbearbeitung', planer: 'Holzbearbeitung', router: 'Holzbearbeitung',
|
||||
lathe: 'Metallbearbeitung', turning_machine: 'Metallbearbeitung', large_lathe: 'Metallbearbeitung',
|
||||
small_lathe: 'Metallbearbeitung', milling_machine: 'Metallbearbeitung', drilling_machine: 'Metallbearbeitung',
|
||||
grinding_machine: 'Metallbearbeitung', metal_saw: 'Metallbearbeitung', band_saw: 'Metallbearbeitung',
|
||||
cold_saw: 'Metallbearbeitung', shearing_machine: 'Metallbearbeitung', bending_machine: 'Metallbearbeitung',
|
||||
cnc: 'Metallbearbeitung', machining_center: 'Metallbearbeitung', transfer_machine: 'Metallbearbeitung',
|
||||
injection_molding: 'Kunststoff/Gummi', plastics_machine: 'Kunststoff/Gummi',
|
||||
compression_molding: 'Kunststoff/Gummi', blow_molding: 'Kunststoff/Gummi',
|
||||
extruder: 'Kunststoff/Gummi', plastics_press: 'Kunststoff/Gummi',
|
||||
rubber_machine: 'Kunststoff/Gummi', two_roll_mill: 'Kunststoff/Gummi',
|
||||
reaction_molding: 'Kunststoff/Gummi', calender: 'Kunststoff/Gummi',
|
||||
food_machine: 'Lebensmittel', meat_grinder: 'Lebensmittel', bread_slicer: 'Lebensmittel',
|
||||
bakery: 'Lebensmittel', mixer: 'Lebensmittel', cooker: 'Lebensmittel',
|
||||
cutter: 'Lebensmittel', food_cutter: 'Lebensmittel', filling_machine: 'Lebensmittel',
|
||||
packaging_machine: 'Verpackung', palletizer: 'Verpackung', pallet_wrapper: 'Verpackung',
|
||||
wrapping_machine: 'Verpackung', strapping_machine: 'Verpackung',
|
||||
textile_machine: 'Textilmaschinen', spinning_machine: 'Textilmaschinen',
|
||||
weaving_machine: 'Textilmaschinen', dyeing_machine: 'Textilmaschinen',
|
||||
nonwoven_machine: 'Textilmaschinen',
|
||||
agricultural_machine: 'Landmaschinen', combine_harvester: 'Landmaschinen',
|
||||
mower: 'Landmaschinen', baler: 'Landmaschinen', sprayer: 'Landmaschinen', tiller: 'Landmaschinen',
|
||||
crane: 'Krane/Hebezeuge', bridge_crane: 'Krane/Hebezeuge', gantry_crane: 'Krane/Hebezeuge',
|
||||
tower_crane: 'Krane/Hebezeuge', mobile_crane: 'Krane/Hebezeuge', hoist: 'Krane/Hebezeuge',
|
||||
winch: 'Krane/Hebezeuge', slewing_crane: 'Krane/Hebezeuge',
|
||||
elevator: 'Aufzuege', lift: 'Aufzuege', construction_hoist: 'Aufzuege',
|
||||
conveyor: 'Foerdertechnik', belt_conveyor: 'Foerdertechnik', screw_conveyor: 'Foerdertechnik',
|
||||
transfer_system: 'Foerdertechnik', rotary_transfer_machine: 'Foerdertechnik',
|
||||
forklift: 'Flurfoerderzeuge', industrial_truck: 'Flurfoerderzeuge',
|
||||
earth_moving: 'Erdbaumaschinen', excavator: 'Erdbaumaschinen',
|
||||
wheel_loader: 'Erdbaumaschinen', bulldozer: 'Erdbaumaschinen',
|
||||
welding_machine: 'Schweissmaschinen', arc_welder: 'Schweissmaschinen',
|
||||
printing_press: 'Druckmaschinen', coating_machine: 'Druckmaschinen',
|
||||
pump: 'Pumpen/Kompressoren', compressor: 'Pumpen/Kompressoren', vacuum_pump: 'Pumpen/Kompressoren',
|
||||
foundry_machine: 'Giesserei', casting_machine: 'Giesserei', die_casting: 'Giesserei',
|
||||
industrial_furnace: 'Industrieoefen', heat_treatment: 'Industrieoefen',
|
||||
dryer: 'Trockner/Oefen', oven: 'Trockner/Oefen', kiln: 'Trockner/Oefen',
|
||||
paper_machine: 'Papiermaschinen', slitter_rewinder: 'Papiermaschinen', pulper: 'Papiermaschinen',
|
||||
centrifuge: 'Zentrifugen',
|
||||
aerial_platform: 'Hubarbeitsbuehnen', cherry_picker: 'Hubarbeitsbuehnen',
|
||||
scissor_lift: 'Hubtische', lift_table: 'Hubtische',
|
||||
powered_gate: 'Tore/Tueren', industrial_door: 'Tore/Tueren',
|
||||
laser_machine: 'Lasermaschinen', laser_cutter: 'Lasermaschinen',
|
||||
silo: 'Schuettgutanlagen', bunker: 'Schuettgutanlagen',
|
||||
suspended_platform: 'Haengebuehnen', scaffold: 'Haengebuehnen',
|
||||
storage_retrieval: 'Lagertechnik', automated_warehouse: 'Lagertechnik',
|
||||
pressure_vessel: 'Druckbehaelter', hydraulic_accumulator: 'Druckbehaelter',
|
||||
}
|
||||
const cats = new Set<string>()
|
||||
for (const mt of machineTypes) {
|
||||
cats.add(catMap[mt] || mt)
|
||||
}
|
||||
const sortedCats = Array.from(cats).sort()
|
||||
setStats({ total: norms.length, byType, categories: sortedCats })
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
if (loading || !stats) return null
|
||||
|
||||
const cDesc = stats.categories.join(', ')
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-purple-50 dark:bg-purple-900/10 border border-purple-200 dark:border-purple-800 rounded-lg">
|
||||
<button onClick={() => setExpanded(!expanded)} className="w-full text-left">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-5 h-5 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-purple-800 dark:text-purple-300">
|
||||
Normen-Bibliothek: {stats.total} Normen in {stats.categories.length} Branchen
|
||||
</span>
|
||||
</div>
|
||||
<svg className={`w-4 h-4 text-purple-400 transition-transform ${expanded ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{expanded && (
|
||||
<div className="mt-3 space-y-2">
|
||||
<table className="w-full text-xs">
|
||||
<thead>
|
||||
<tr className="border-b border-purple-200 dark:border-purple-700">
|
||||
<th className="text-left py-1.5 px-2 font-semibold text-purple-800 dark:text-purple-300 w-48">Typ</th>
|
||||
<th className="text-center py-1.5 px-2 font-semibold text-purple-800 dark:text-purple-300 w-16">Anzahl</th>
|
||||
<th className="text-left py-1.5 px-2 font-semibold text-purple-800 dark:text-purple-300">Abdeckung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(['A', 'B1', 'B2', 'C'] as const).map((type) => {
|
||||
const info = TYPE_INFO[type]
|
||||
const count = stats.byType[type] || 0
|
||||
const desc = type === 'C' ? cDesc : CATEGORY_DESCRIPTIONS[type]
|
||||
return (
|
||||
<tr key={type} className="border-b border-purple-100 dark:border-purple-800/50">
|
||||
<td className="py-2 px-2">
|
||||
<span className={`inline-block px-2 py-0.5 rounded border text-xs font-medium ${info.color}`}>
|
||||
{info.label}
|
||||
</span>
|
||||
</td>
|
||||
<td className="py-2 px-2 text-center font-bold text-purple-900 dark:text-purple-200">{count}</td>
|
||||
<td className="py-2 px-2 text-gray-700 dark:text-gray-300">{desc}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="pt-2 text-xs text-purple-600 dark:text-purple-400">
|
||||
Alle Normen mit Abschnittsnummern und{' '}
|
||||
<a href="https://www.beuth.de" target="_blank" rel="noopener noreferrer" className="underline font-medium hover:text-purple-800">
|
||||
Beuth-Kauflinks
|
||||
</a>{' '}
|
||||
hinterlegt. Die vollstaendige Bibliothek ist unter "Normenrecherche" in jedem Projekt einsehbar.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user