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
+19 -4
View File
@@ -134,9 +134,14 @@ export default function IACEDashboardPage() {
machine_type: '',
manufacturer: '',
})
const [machineTypes, setMachineTypes] = useState<{ key: string; label_de: string; group: string }[]>([])
useEffect(() => {
fetchProjects()
fetch('/api/sdk/v1/iace/machine-types')
.then((r) => (r.ok ? r.json() : null))
.then((j) => j && setMachineTypes(j.machine_types || []))
.catch((err) => console.error('Failed to fetch machine types:', err))
}, [])
async function fetchProjects() {
@@ -308,13 +313,23 @@ export default function IACEDashboardPage() {
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Maschinentyp
</label>
<input
type="text"
<select
value={formData.machine_type}
onChange={(e) => setFormData({ ...formData, machine_type: e.target.value })}
placeholder="z.B. Industrieroboter"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
>
<option value=""> Maschinentyp wählen </option>
{Array.from(new Set(machineTypes.map((m) => m.group))).map((group) => (
<optgroup key={group} label={group}>
{machineTypes.filter((m) => m.group === group).map((m) => (
<option key={m.key} value={m.key}>{m.label_de}</option>
))}
</optgroup>
))}
</select>
<p className="mt-1 text-xs text-gray-400">
Steuert, welche maschinenspezifischen Gefährdungs-Patterns greifen bitte aus der Liste wählen.
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">