"use client"; import { useCallback, useEffect, useState } from "react"; import { BarChart3, BookOpen, Copy, Dices, FileText, FolderKanban, GitBranch, LayoutDashboard, Menu, PiggyBank, Plus, SlidersHorizontal, Tornado, Trash2, X, } from "lucide-react"; import { PlanView } from "@/components/PlanView"; import { Dashboard } from "@/components/Dashboard"; import { MonteCarloDialog } from "@/components/MonteCarloDialog"; import { SensitivityDialog } from "@/components/SensitivityDialog"; import { SpecView } from "@/components/SpecView"; import { SystemParametersView } from "@/components/SystemParametersView"; import { PlanTraceDialog } from "@/components/DetailView"; import { computePlan } from "@/lib/calculations"; import { ProfileMenu } from "@/components/ProfileMenu"; import { PlanProfileFields, emptyProfileDraft, type ProfileDraft } from "@/components/PlanProfileFields"; import { api } from "@/lib/api-client"; import { computeScenarioDiff } from "@/lib/diff"; import type { PlanInput, PlanListItem, ScenarioMeta } from "@/lib/types"; import type { PlanComputed } from "@/lib/calculations"; interface ScenarioDetail { plan: PlanInput; // das Szenario selbst (berechenbare Einheit) computed: PlanComputed; base: PlanInput | null; // Eltern-Szenario als Vergleichsbasis meta: ScenarioMeta & { planName: string }; } export function AppShell({ username }: { username: string }) { const [plans, setPlans] = useState([]); const [selectedScenarioId, setSelectedScenarioId] = useState(null); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(true); const [sidebarOpen, setSidebarOpen] = useState(false); const [showNewPlan, setShowNewPlan] = useState(false); const [copyFrom, setCopyFrom] = useState(null); const [showSpec, setShowSpec] = useState(false); const [showCharts, setShowCharts] = useState(false); const [showMonteCarlo, setShowMonteCarlo] = useState(false); const [showSensitivity, setShowSensitivity] = useState(false); const [showSystemParams, setShowSystemParams] = useState(false); const [showPlanTraces, setShowPlanTraces] = useState(false); // Sprungmarke in die SPEZIFIKATION, gesetzt aus einem Rechenweg heraus. const [specAnchor, setSpecAnchor] = useState(null); function openSpecAt(anchor: string) { setSpecAnchor(anchor); setShowSpec(true); setShowSystemParams(false); setSelectedScenarioId(null); } const loadPlans = useCallback(async () => { const data = await api.get<{ plans: PlanListItem[] }>("/api/plans"); setPlans(data.plans); return data.plans; }, []); // silent = Hintergrund-Refresh ohne Loading-Umschaltung: die PlanView bleibt montiert, // damit die Scrollposition (z. B. nach dem Schliessen eines Popups) erhalten bleibt. const loadDetail = useCallback(async (scenarioId: string, silent = false) => { if (!silent) setLoading(true); try { setDetail(await api.get(`/api/scenarios/${scenarioId}`)); } finally { if (!silent) setLoading(false); } }, []); useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect -- asynchroner Datenabruf beim Mount loadPlans().finally(() => setLoading(false)); }, [loadPlans]); useEffect(() => { if (selectedScenarioId) { // eslint-disable-next-line react-hooks/set-state-in-effect -- asynchroner Datenabruf loadDetail(selectedScenarioId); } else { setDetail(null); } }, [selectedScenarioId, loadDetail]); function refreshCurrent() { if (selectedScenarioId) loadDetail(selectedScenarioId, true); } function openScenario(id: string) { setSelectedScenarioId(id); setShowSpec(false); setShowSystemParams(false); setSidebarOpen(false); } async function handleDeletePlan(id: string) { if (!confirm("Diesen Plan mit ALLEN Szenarien wirklich loeschen?")) return; await api.delete(`/api/plans/${id}`); const rest = await loadPlans(); if (!rest.some((p) => p.scenarios.some((s) => s.id === selectedScenarioId))) { setSelectedScenarioId(null); } } async function handleDeleteScenario(s: ScenarioMeta) { if (!confirm(`Szenario "${s.name}" wirklich loeschen?`)) return; try { await api.delete(`/api/scenarios/${s.id}`); } catch (e) { alert(e instanceof Error ? e.message : "Loeschen fehlgeschlagen."); return; } await loadPlans(); if (selectedScenarioId === s.id) setSelectedScenarioId(null); } const activePlan = plans.find((p) => p.scenarios.some((s) => s.id === selectedScenarioId)) ?? null; const sidebar = (
FPT
); const diff = detail ? computeScenarioDiff(detail.plan, detail.base) : null; return (
{sidebarOpen && (
setSidebarOpen(false)} />
)}

{showSystemParams ? "Systemparameter" : showSpec ? "Spezifikation" : detail ? `${detail.meta.planName} · ${detail.meta.name}` : "Uebersicht"}

{showSystemParams && } {showSpec && } {!showSpec && !showSystemParams && loading &&

Laedt…

} {!showSpec && !showSystemParams && !loading && selectedScenarioId === null && ( setShowNewPlan(true)} onDelete={handleDeletePlan} /> )} {!showSpec && !showSystemParams && !loading && detail && selectedScenarioId && (
{!detail.meta.isBase && ( )} {detail.plan.phases.length > 0 && ( <> )} {diff && detail.base && ( {diff.total === 0 ? "Unveraendert gegenueber der Vorlage" : `${diff.total} Abweichung${diff.total === 1 ? "" : "en"} gegenueber der Vorlage`} )}
)}
{showNewPlan && ( { const { scenario } = await api.post<{ plan: { id: string }; scenario: { id: string } }>( "/api/plans", { name, ...profile } ); setShowNewPlan(false); await loadPlans(); openScenario(scenario.id); }} onClose={() => setShowNewPlan(false)} /> )} {showCharts && detail && ( setShowCharts(false)} title={`Grafiken · ${detail.meta.name}`}> s.id !== detail.meta.id)} /> )} {showMonteCarlo && detail && ( setShowMonteCarlo(false)} /> )} {showSensitivity && detail && ( setShowSensitivity(false)} /> )} {showPlanTraces && detail && ( setShowPlanTraces(false)} onOpenSpec={openSpecAt} /> )} {copyFrom && ( setCopyFrom(null)} onCreate={async (name) => { const { scenarioId } = await api.post<{ scenarioId: string }>( `/api/scenarios/${copyFrom.id}/copy`, { name } ); setCopyFrom(null); await loadPlans(); openScenario(scenarioId); }} /> )}
); } // Breiter Dialog fuer den Analyse-Bereich (Grafiken). function ChartsDialog({ title, children, onClose, }: { title: string; children: React.ReactNode; onClose: () => void; }) { return (
e.stopPropagation()} className="flex w-full max-w-5xl flex-col gap-4 rounded-2xl border border-border bg-surface p-6 shadow-xl" >

{title}

{children}
); } // Rekursiver Szenario-Baum: Kinder werden eingerueckt, damit Sub-Szenarien sichtbar sind. function ScenarioTree({ scenarios, parentId, depth, selectedId, onSelect, onCopy, onDelete, }: { scenarios: ScenarioMeta[]; parentId: string | null; depth: number; selectedId: string | null; onSelect: (id: string) => void; onCopy: (s: ScenarioMeta) => void; onDelete: (s: ScenarioMeta) => void; }) { const level = scenarios.filter((s) => (s.parentScenarioId ?? null) === parentId); if (level.length === 0) return null; return ( <> {level.map((s) => (
{!s.isBase && ( )}
))} ); } // Startansicht: Begruessung + Plan-Kacheln (Klick oeffnet das Basisszenario). function DashboardHome({ username, plans, onSelect, onCreate, onDelete, }: { username: string; plans: PlanListItem[]; onSelect: (scenarioId: string) => void; onCreate: () => void; onDelete: (id: string) => void; }) { return (

Willkommen, {username}

Waehlen Sie einen Plan oder erstellen Sie einen neuen. Jeder Plan enthaelt ein Basisszenario und beliebig viele Varianten davon.

{plans.map((p) => { const base = p.scenarios.find((s) => s.isBase) ?? p.scenarios[0]; const others = p.scenarios.length - 1; return (
base && onSelect(base.id)} >
{p.name}
Basisszenario{others > 0 ? ` + ${others} Variante${others === 1 ? "" : "n"}` : ""}
); })}
); } function PlanDialog({ onCreate, onClose, }: { onCreate: (name: string, profile: ProfileDraft) => void; onClose: () => void; }) { const [name, setName] = useState("Meine Planung"); const [draft, setDraft] = useState(emptyProfileDraft); const [saving, setSaving] = useState(false); return (
e.stopPropagation()} className="flex w-full max-w-md flex-col gap-4 rounded-2xl border border-border bg-surface p-6 shadow-xl" >

Neuen Plan erstellen

Es wird automatisch ein Basisszenario angelegt. Weitere Szenarien entstehen spaeter als Kopien davon.

setName(e.target.value)} placeholder="Name des Plans" />
); } function CopyScenarioDialog({ source, onCreate, onClose, }: { source: ScenarioMeta; onCreate: (name: string) => void; onClose: () => void; }) { const [name, setName] = useState(`${source.name} – Variante`); const [saving, setSaving] = useState(false); return (
e.stopPropagation()} className="flex w-full max-w-sm flex-col gap-3 rounded-2xl border border-border bg-surface p-6 shadow-xl" >

Neues Szenario

Vollstaendige Kopie von {source.name}. Aenderungen darin werden anschliessend farblich hervorgehoben.

setName(e.target.value)} placeholder="Name des Szenarios" />
); }