'use client' import { useState, useEffect, useCallback } from 'react' import Link from 'next/link' interface VariantProject { id: string machine_name: string description?: string status: string hazard_count?: number parent_project_id?: string } interface VariantGapResponse { base_project: { id: string; name: string; hazard_count: number; measure_count: number } variant: { id: string; name: string; hazard_count: number; measure_count: number } gap: { additional_hazards: number; additional_measures: number; categories_affected: string[] } } interface BaseProjectSummary { hazard_count: number component_count: number mitigation_count: number norms_count: number } interface Props { projectId: string parentProjectId?: string | null parentProjectName?: string } function VariantBanner({ projectId, parentProjectId, parentProjectName }: { projectId: string; parentProjectId: string; parentProjectName?: string }) { const [baseSummary, setBaseSummary] = useState(null) useEffect(() => { async function loadBase() { try { const [projRes, riskRes] = await Promise.all([ fetch(`/api/sdk/v1/iace/projects/${parentProjectId}`), fetch(`/api/sdk/v1/iace/projects/${parentProjectId}/risk-summary`), ]) const proj = projRes.ok ? await projRes.json() : null const risk = riskRes.ok ? await riskRes.json() : null const rs = risk?.risk_summary || risk || {} setBaseSummary({ hazard_count: rs.total_hazards || rs.total || 0, component_count: proj?.components?.length || 0, mitigation_count: rs.total_mitigations || 0, norms_count: 0, }) } catch { /* ignore */ } } loadBase() }, [parentProjectId]) return (

Variante

Diese Seite zeigt nur die varianten-spezifischen Gefaehrdungen und Massnahmen. Die Basis-Risikobeurteilung liegt im Eltern-Projekt.

{parentProjectName || 'Basis-Projekt'}
{baseSummary && (

Basis-Projekt Zusammenfassung

{baseSummary.hazard_count}
Gefaehrdungen
{baseSummary.mitigation_count}
Massnahmen
{baseSummary.component_count}
Komponenten
)}
) } export function VariantPanel({ projectId, parentProjectId, parentProjectName }: Props) { const [variants, setVariants] = useState([]) const [gapMap, setGapMap] = useState>({}) const [loading, setLoading] = useState(true) const [showCreate, setShowCreate] = useState(false) const [creating, setCreating] = useState(false) const [name, setName] = useState('') const [description, setDescription] = useState('') const fetchVariants = useCallback(async () => { try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/variants`) if (!res.ok) { setVariants([]) return } const json = await res.json() const list: VariantProject[] = json.variants || json.projects || [] setVariants(list) // Fetch gap analysis for this project const gapRes = await fetch(`/api/sdk/v1/iace/projects/${projectId}/variant-gap`) if (gapRes.ok) { const gapJson = await gapRes.json() const gaps: Record = {} // Could be a single gap or array — handle both if (Array.isArray(gapJson)) { for (const g of gapJson) { gaps[g.variant?.id] = g } } else if (gapJson.variant) { gaps[gapJson.variant.id] = gapJson } setGapMap(gaps) } } catch { setVariants([]) } finally { setLoading(false) } }, [projectId]) useEffect(() => { fetchVariants() }, [fetchVariants]) async function handleCreate() { if (!name.trim()) return setCreating(true) try { const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/variants`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ machine_name: name.trim(), description: description.trim(), }), }) if (res.ok) { setName('') setDescription('') setShowCreate(false) fetchVariants() } } catch { // silently handle } finally { setCreating(false) } } // If this project IS a variant, show link to base project + base stats if (parentProjectId) { return } if (loading) return null if (variants.length === 0 && !showCreate) { return (

Keine Varianten

Erstellen Sie Varianten fuer verschiedene Betriebsarten

{renderCreateDialog()}
) } function renderCreateDialog() { if (!showCreate) return null return (

Neue Variante erstellen

setName(e.target.value)} className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg bg-white dark:bg-gray-800 dark:border-gray-600 dark:text-white focus:ring-2 focus:ring-purple-500 focus:border-transparent" />