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
@@ -2,17 +2,25 @@
import { useState } from 'react'
import { useParams } from 'next/navigation'
import { Component } from './_components/types'
import { Component, buildTree } from './_components/types'
import { ComponentTreeNode } from './_components/ComponentTreeNode'
import { ComponentForm } from './_components/ComponentForm'
import { ComponentLibraryModal } from './_components/ComponentLibraryModal'
import { PresenceSection } from './_components/PresenceSection'
import { useComponents } from './_hooks/useComponents'
export default function ComponentsPage() {
const params = useParams()
const projectId = params.projectId as string
const { loading, tree, handleSubmit, handleDelete, handleAddFromLibrary } = useComponents(projectId)
const { loading, components, handleSubmit, handleDelete, handleAddFromLibrary, setPresence, setCEMarked } = useComponents(projectId)
// Split auto-detected components by presence so the expert can review the
// engine's best-effort negation verdicts and move items in both directions.
const present = components.filter((c) => !c.presence_status || c.presence_status === 'vorhanden')
const negated = components.filter((c) => c.presence_status === 'nicht_vorhanden')
const deleted = components.filter((c) => c.presence_status === 'geloescht')
const tree = buildTree(present)
const [showForm, setShowForm] = useState(false)
const [editingComponent, setEditingComponent] = useState<Component | null>(null)
@@ -110,7 +118,9 @@ export default function ComponentsPage() {
<div className="py-1">
{tree.map((component) => (
<ComponentTreeNode key={component.id} component={component} depth={0}
onEdit={handleEdit} onDelete={handleDelete} onAddChild={handleAddChild} />
onEdit={handleEdit} onDelete={handleDelete} onAddChild={handleAddChild}
onMarkAbsent={(id) => setPresence(id, 'nicht_vorhanden')}
onToggleCE={setCEMarked} />
))}
</div>
</div>
@@ -140,6 +150,27 @@ export default function ComponentsPage() {
</div>
)
)}
<PresenceSection
title="Nicht vorhanden"
hint={'Vom System als verneint erkannt (z. B. „keine Pneumatik"). Pruefen Sie und verschieben Sie bei Bedarf zu Vorhanden.'}
accent="border-amber-400"
components={negated}
actions={[
{ label: '→ Vorhanden', variant: 'primary', onClick: (id) => setPresence(id, 'vorhanden') },
{ label: 'Loeschen', variant: 'danger', onClick: handleDelete },
]}
/>
<PresenceSection
title="Geloescht"
hint="Entfernte Komponenten bleiben zur Nachvollziehbarkeit sichtbar und koennen wiederhergestellt werden."
accent="border-gray-400"
components={deleted}
actions={[
{ label: 'Wiederherstellen', variant: 'neutral', onClick: (id) => setPresence(id, 'vorhanden') },
]}
/>
</div>
)
}