feat(iace-ui): component presence/CE review + machine-type dropdown

- Components view: three presence sections (Vorhanden / Nicht vorhanden /
  Geloescht) with bidirectional move + soft-delete (audit-visible, restorable),
  so the expert corrects the engine's best-effort negation in both directions.
- CE marking per component (bought robot/actuator/SPS) with a clear
  "validate the integrated safety function (PL/SIL)" note when also safety-relevant.
  Safe semantics: hazards are not suppressed, only provenance is surfaced.
- Project-create form: machine type is now a grouped dropdown from the engine's
  controlled vocabulary (GET /machine-types) instead of free text.
- Knowledge graph: component→hazard edges use the real component_id.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-06-10 17:16:35 +02:00
parent afb3f83f30
commit 170691ef96
7 changed files with 201 additions and 17 deletions
@@ -3,7 +3,7 @@
import { useState, useEffect, useMemo } from 'react'
interface Component { id: string; name: string; component_type: string }
interface Hazard { id: string; name: string; category: string; operational_states?: string[] }
interface Hazard { id: string; name: string; category: string; operational_states?: string[]; component_id?: string }
interface Mitigation { id: string; name?: string; title?: string; reduction_type: string; hazard_id?: string; linked_hazard_ids?: string[] }
export interface GraphNode {
@@ -56,6 +56,7 @@ export function useKnowledgeGraph(projectId: string) {
setHazards((j.hazards || j || []).map((h: Record<string, unknown>) => ({
id: h.id as string, name: h.name as string, category: h.category as string || '',
operational_states: (h.operational_states || []) as string[],
component_id: (h.component_id || '') as string,
})))
}
if (mitRes.ok) {
@@ -89,17 +90,20 @@ export function useKnowledgeGraph(projectId: string) {
})
// Hazard nodes
const compIdSet = new Set(components.map((c) => c.id))
hazards.forEach((h) => {
graphNodes.push({
id: `haz-${h.id}`, type: 'hazard',
label: h.name, subLabel: h.category,
color: NODE_COLORS.hazard,
})
// Edge: first component → hazard (simplified — could be per component_id)
if (components.length > 0) {
// Edge: the component that actually causes this hazard → hazard.
// Only drawn when the hazard carries a component_id that maps to a known
// component node (no synthetic "all from the first component" edges).
if (h.component_id && compIdSet.has(h.component_id)) {
graphEdges.push({
id: `e-comp-haz-${h.id}`,
source: `comp-${components[0].id}`,
source: `comp-${h.component_id}`,
target: `haz-${h.id}`,
label: 'erzeugt',
})