Sidebar: Szenario-Baum standardmaessig eingeklappt, gleichmaessig eingerueckt
Deploy App / deploy (push) Successful in 1m5s

"Szenarien" bekommt ein Chevron: der Baum ist per Default zu und wird
aktiv aufgeklappt; das Label fuehrt weiterhin in die Szenario-Liste.

Der Baum wird flach dargestellt -- alle Szenarien gleich eingerueckt,
Basisszenario zuoberst. Die Herkunfts-Verschachtelung zeigt weiterhin
die Szenario-Liste (Spalte "aus ...").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-20 22:28:48 +02:00
parent fb70781e5b
commit 74d1577bd8
+40 -30
View File
@@ -4,6 +4,8 @@ import { useCallback, useEffect, useMemo, useState } from "react";
import { import {
BarChart3, BarChart3,
BookOpen, BookOpen,
ChevronDown,
ChevronRight,
Copy, Copy,
Dices, Dices,
FileText, FileText,
@@ -104,6 +106,8 @@ function AppShellInner({ username }: { username: string }) {
const [showActuals, setShowActuals] = useState(false); const [showActuals, setShowActuals] = useState(false);
// Plan-Ebene: Dashboard / Szenarien-Liste / Analysen. Null = kein Plan-View aktiv. // Plan-Ebene: Dashboard / Szenarien-Liste / Analysen. Null = kein Plan-View aktiv.
const [planNav, setPlanNav] = useState<{ planId: string; tab: "dashboard" | "scenarios" | "analyses" } | null>(null); const [planNav, setPlanNav] = useState<{ planId: string; tab: "dashboard" | "scenarios" | "analyses" } | null>(null);
// Welche Szenario-Bäume in der Seitenleiste aufgeklappt sind. Standard: eingeklappt.
const [expandedTrees, setExpandedTrees] = useState<Record<string, boolean>>({});
const [savedAnalysisId, setSavedAnalysisId] = useState<string | null>(null); const [savedAnalysisId, setSavedAnalysisId] = useState<string | null>(null);
const [showSystemParams, setShowSystemParams] = useState(false); const [showSystemParams, setShowSystemParams] = useState(false);
const [showPlanTraces, setShowPlanTraces] = useState(false); const [showPlanTraces, setShowPlanTraces] = useState(false);
@@ -378,18 +382,40 @@ function AppShellInner({ username }: { username: string }) {
</button> </button>
</div> </div>
{/* Unter-Menüpunkte, leicht eingezogen. */} {/* «Szenarien»: Chevron klappt den Baum auf/zu (Standard: zu), das Label führt
{subItem("scenarios", "Szenarien", Layers)} in die Szenario-Liste. */}
{/* Der Szenario-Baum bleibt darunter -- ein Klick führt direkt in die Matrix. */} <div
className={`group flex w-full items-center gap-1 rounded-lg py-1.5 pr-3 pl-1 text-xs font-medium transition-colors ${
navHere && planNav?.tab === "scenarios" ? "bg-accent-soft text-accent-soft-fg" : "text-muted hover:bg-surface-2"
}`}
>
<button
type="button"
aria-label={expandedTrees[p.id] ? "Szenarien einklappen" : "Szenarien aufklappen"}
onClick={() => setExpandedTrees((prev) => ({ ...prev, [p.id]: !prev[p.id] }))}
className="rounded p-0.5 text-faint hover:text-fg"
>
{expandedTrees[p.id] ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
</button>
<button
type="button"
onClick={() => openPlanTab(p.id, "scenarios")}
className="flex min-w-0 flex-1 items-center gap-2 text-left"
>
<Layers className="h-3.5 w-3.5 shrink-0" />
Szenarien
</button>
</div>
{/* Der Baum erscheint nur aufgeklappt -- alle Szenarien gleich eingerückt. */}
{expandedTrees[p.id] && (
<ScenarioTree <ScenarioTree
scenarios={p.scenarios} scenarios={p.scenarios}
parentId={null}
depth={1}
selectedId={selectedScenarioId} selectedId={selectedScenarioId}
onSelect={openScenario} onSelect={openScenario}
onCopy={setCopyFrom} onCopy={setCopyFrom}
onDelete={handleDeleteScenario} onDelete={handleDeleteScenario}
/> />
)}
<button <button
type="button" type="button"
onClick={() => openActualsForPlan(p.id)} onClick={() => openActualsForPlan(p.id)}
@@ -863,39 +889,33 @@ function ChartsDialog({
// gibt es kein Hover. // gibt es kein Hover.
function ScenarioTree({ function ScenarioTree({
scenarios, scenarios,
parentId,
depth,
selectedId, selectedId,
onSelect, onSelect,
onCopy, onCopy,
onDelete, onDelete,
}: { }: {
scenarios: ScenarioMeta[]; scenarios: ScenarioMeta[];
parentId: string | null;
depth: number;
selectedId: string | null; selectedId: string | null;
onSelect: (id: string) => void; onSelect: (id: string) => void;
onCopy: (s: ScenarioMeta) => void; onCopy: (s: ScenarioMeta) => void;
onDelete: (s: ScenarioMeta) => void; onDelete: (s: ScenarioMeta) => void;
}) { }) {
const level = scenarios.filter((s) => (s.parentScenarioId ?? null) === parentId); // Flache, gleichmässig eingerückte Liste: Basisszenario zuoberst, danach die Varianten.
if (level.length === 0) return null; // Die Herkunfts-Verschachtelung wird in der Szenario-Liste (Spalte «aus …») gezeigt, nicht
// mehr durch die Einrückung in der Seitenleiste.
const ordered = [...scenarios].sort((a, b) => (a.isBase === b.isBase ? 0 : a.isBase ? -1 : 1));
if (ordered.length === 0) return null;
return ( return (
<> <>
{level.map((s) => ( {ordered.map((s) => (
<div key={s.id}>
<div <div
className={`group flex items-center gap-1.5 rounded-lg py-1.5 pr-1.5 text-sm transition-colors ${ key={s.id}
className={`group flex items-center gap-1.5 rounded-lg py-1.5 pr-1.5 pl-9 text-sm transition-colors ${
selectedId === s.id ? "bg-accent-soft font-medium text-accent-soft-fg" : "text-muted hover:bg-surface-2" selectedId === s.id ? "bg-accent-soft font-medium text-accent-soft-fg" : "text-muted hover:bg-surface-2"
}`} }`}
style={{ paddingLeft: `${12 + depth * 14}px` }}
> >
<button type="button" onClick={() => onSelect(s.id)} className="flex min-w-0 flex-1 items-center gap-1.5 text-left"> <button type="button" onClick={() => onSelect(s.id)} className="flex min-w-0 flex-1 items-center gap-1.5 text-left">
{s.isBase ? ( {s.isBase ? <PiggyBank className="h-3.5 w-3.5 shrink-0" /> : <GitBranch className="h-3.5 w-3.5 shrink-0" />}
<PiggyBank className="h-3.5 w-3.5 shrink-0" />
) : (
<GitBranch className="h-3.5 w-3.5 shrink-0" />
)}
<span className="min-w-0 flex-1 truncate" title={s.name}>{s.name}</span> <span className="min-w-0 flex-1 truncate" title={s.name}>{s.name}</span>
</button> </button>
<button <button
@@ -918,16 +938,6 @@ function ScenarioTree({
</button> </button>
)} )}
</div> </div>
<ScenarioTree
scenarios={scenarios}
parentId={s.id}
depth={depth + 1}
selectedId={selectedId}
onSelect={onSelect}
onCopy={onCopy}
onDelete={onDelete}
/>
</div>
))} ))}
</> </>
); );