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
@@ -1,7 +1,7 @@
'use client'
import { useState, useEffect } from 'react'
import { Component, ComponentFormData, LibraryComponent, EnergySource, buildTree } from '../_components/types'
import { Component, ComponentFormData, LibraryComponent, EnergySource, PresenceStatus, buildTree } from '../_components/types'
export function useComponents(projectId: string) {
const [components, setComponents] = useState<Component[]>([])
@@ -47,16 +47,43 @@ export function useComponents(projectId: string) {
return false
}
async function handleDelete(id: string) {
if (!confirm('Komponente wirklich loeschen? Unterkomponenten werden ebenfalls entfernt.')) return
// Move a component between presence states (vorhanden / nicht_vorhanden /
// geloescht). Used for the expert's bidirectional review of auto-detected
// components.
async function setPresence(id: string, status: PresenceStatus) {
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/components/${id}`, { method: 'DELETE' })
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/components/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ presence_status: status }),
})
if (res.ok) await fetchComponents()
} catch (err) {
console.error('Failed to delete component:', err)
console.error('Failed to set presence:', err)
}
}
// Mark a component as a bought CE product (robot, actuator, SPS ...). Safe
// semantics: no hazard suppression — only provenance + PL/SIL note.
async function setCEMarked(id: string, value: boolean) {
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/components/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ce_marked: value }),
})
if (res.ok) await fetchComponents()
} catch (err) {
console.error('Failed to set ce_marked:', err)
}
}
// Soft-delete: the component moves to the "Geloescht" list (restorable); it is
// not removed from the project, so nothing silently disappears.
async function handleDelete(id: string) {
await setPresence(id, 'geloescht')
}
async function handleAddFromLibrary(libraryComps: LibraryComponent[], energySrcs: EnergySource[]) {
const energySourceIds = energySrcs.map(e => e.id)
for (const comp of libraryComps) {
@@ -88,5 +115,7 @@ export function useComponents(projectId: string) {
handleSubmit,
handleDelete,
handleAddFromLibrary,
setPresence,
setCEMarked,
}
}