feat: Variantenmanagement — Sub-Projekte mit GAP-Analyse

Backend:
- parent_project_id auf iace_projects (DB + Go Struct)
- POST/GET /variants + GET /variant-gap Endpoints
- GAP-Analyse: Differenz Hazards/Massnahmen/Kategorien

Frontend:
- VariantPanel auf Projekt-Uebersicht
- Variante erstellen Dialog
- Sidebar-Anzeige (Variantenanzahl / Basis-Link)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-09 10:47:01 +02:00
parent 2143840ee7
commit 8682522212
8 changed files with 592 additions and 11 deletions
+44 -1
View File
@@ -108,12 +108,40 @@ export default function IACELayout({ children }: { children: React.ReactNode })
const params = useParams()
const projectId = params?.projectId as string | undefined
const [projectName, setProjectName] = React.useState('')
const [variantInfo, setVariantInfo] = React.useState<{
parentProjectId?: string; parentName?: string; variantCount?: number
}>({})
React.useEffect(() => {
if (!projectId) return
fetch(`/api/sdk/v1/iace/projects/${projectId}`)
.then(r => r.ok ? r.json() : null)
.then(d => { if (d?.machine_name) setProjectName(d.machine_name) })
.then(async (d) => {
if (!d?.machine_name) return
setProjectName(d.machine_name)
// Resolve variant info
if (d.parent_project_id) {
try {
const pRes = await fetch(`/api/sdk/v1/iace/projects/${d.parent_project_id}`)
if (pRes.ok) {
const pj = await pRes.json()
setVariantInfo({ parentProjectId: d.parent_project_id, parentName: pj.machine_name })
} else {
setVariantInfo({ parentProjectId: d.parent_project_id })
}
} catch { setVariantInfo({ parentProjectId: d.parent_project_id }) }
} else {
// Check if this project has variants
try {
const vRes = await fetch(`/api/sdk/v1/iace/projects/${projectId}/variants`)
if (vRes.ok) {
const vj = await vRes.json()
const list = vj.variants || vj.projects || []
if (list.length > 0) setVariantInfo({ variantCount: list.length })
}
} catch { /* no variants endpoint yet */ }
}
})
.catch(() => {})
}, [projectId])
@@ -148,6 +176,21 @@ export default function IACELayout({ children }: { children: React.ReactNode })
{projectName}
</p>
)}
{variantInfo.parentProjectId && (
<Link
href={`/sdk/iace/${variantInfo.parentProjectId}`}
className="mt-1 flex items-center gap-1 text-[10px] text-orange-600 hover:text-orange-700 dark:text-orange-400 truncate"
title={variantInfo.parentName ? `Variante von: ${variantInfo.parentName}` : 'Variante'}
>
<svg className="w-3 h-3 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16V4m0 0L3 8m4-4l4 4" />
</svg>
Variante von: {variantInfo.parentName || 'Basis'}
</Link>
)}
{variantInfo.variantCount != null && variantInfo.variantCount > 0 && (
<p className="mt-1 text-[10px] text-gray-400">({variantInfo.variantCount} Varianten)</p>
)}
<p className="text-[10px] text-gray-400 mt-0.5">CE-Compliance</p>
<Link
href="/sdk/iace/lines"