diff --git a/admin-compliance/app/sdk/iace/[projectId]/benchmark/_components/CategoryBreakdown.tsx b/admin-compliance/app/sdk/iace/[projectId]/benchmark/_components/CategoryBreakdown.tsx new file mode 100644 index 0000000..c72c3ae --- /dev/null +++ b/admin-compliance/app/sdk/iace/[projectId]/benchmark/_components/CategoryBreakdown.tsx @@ -0,0 +1,46 @@ +'use client' + +import React from 'react' +import type { CategoryScore } from '../_hooks/useBenchmark' + +interface Props { breakdown: CategoryScore[] } + +const CATEGORY_LABELS: Record = { + 'mechanische gefaehrdungen': 'Mechanisch', + 'elektrische gefaehrdungen': 'Elektrisch', + 'thermische gefaehrdungen': 'Thermisch', + 'laerm': 'Laerm', + 'vibration': 'Vibration', + 'strahlung': 'Strahlung', + 'materialien und substanzen': 'Materialien/Substanzen', + 'ergonomische gefaehrdungen': 'Ergonomie', + 'einsatzumgebung': 'Einsatzumgebung', +} + +export function CategoryBreakdown({ breakdown }: Props) { + if (!breakdown || breakdown.length === 0) return null + + return ( +
+

Coverage nach Gefaehrdungsgruppe

+
+ {breakdown.map((cat) => { + const label = CATEGORY_LABELS[cat.category] || cat.category + const pct = Math.round(cat.coverage * 100) + const barColor = pct >= 80 ? 'bg-green-500' : pct >= 50 ? 'bg-yellow-500' : 'bg-red-500' + return ( +
+
+ {label} + {cat.match_count}/{cat.gt_count} ({pct}%) +
+
+
+
+
+ ) + })} +
+
+ ) +} diff --git a/admin-compliance/app/sdk/iace/[projectId]/benchmark/_components/GTImportForm.tsx b/admin-compliance/app/sdk/iace/[projectId]/benchmark/_components/GTImportForm.tsx new file mode 100644 index 0000000..ac69519 --- /dev/null +++ b/admin-compliance/app/sdk/iace/[projectId]/benchmark/_components/GTImportForm.tsx @@ -0,0 +1,121 @@ +'use client' + +import React, { useState, useRef } from 'react' +import type { GroundTruthEntry } from '../_hooks/useBenchmark' + +interface Props { + onImport: (gt: { entries: GroundTruthEntry[]; source_file?: string; description?: string }) => Promise + loading: boolean +} + +export function GTImportForm({ onImport, loading }: Props) { + const [jsonText, setJsonText] = useState('') + const [parseError, setParseError] = useState(null) + const [preview, setPreview] = useState<{ count: number; groups: Record } | null>(null) + const fileRef = useRef(null) + + function tryParse(text: string) { + setJsonText(text) + setParseError(null) + setPreview(null) + if (!text.trim()) return + + try { + const parsed = JSON.parse(text) + const entries: GroundTruthEntry[] = parsed.entries || parsed + if (!Array.isArray(entries) || entries.length === 0) { + setParseError('JSON muss ein Array "entries" enthalten') + return + } + // Validate first entry has required fields + const first = entries[0] + if (!first.hazard_type && !first.hazard_group) { + setParseError('Eintraege muessen hazard_type oder hazard_group enthalten') + return + } + // Build preview + const groups: Record = {} + for (const e of entries) { + const g = e.hazard_group || 'Unbekannt' + groups[g] = (groups[g] || 0) + 1 + } + setPreview({ count: entries.length, groups }) + } catch (err) { + setParseError('Ungueltiges JSON: ' + (err instanceof Error ? err.message : String(err))) + } + } + + async function handleImport() { + if (!jsonText.trim()) return + try { + const parsed = JSON.parse(jsonText) + const gt = parsed.entries ? parsed : { entries: parsed } + await onImport(gt) + setJsonText('') + setPreview(null) + } catch (err) { + setParseError(err instanceof Error ? err.message : 'Import fehlgeschlagen') + } + } + + function handleFileUpload(e: React.ChangeEvent) { + const file = e.target.files?.[0] + if (!file) return + const reader = new FileReader() + reader.onload = (ev) => { + const text = ev.target?.result as string + tryParse(text) + } + reader.readAsText(file) + } + + return ( +
+

Ground Truth importieren

+

+ JSON-Datei mit der professionellen Risikobeurteilung einfuegen oder hochladen. +

+ +
+ + +
+ +