Sidebar: Szenario-Baum standardmaessig eingeklappt, gleichmaessig eingerueckt
Deploy App / deploy (push) Successful in 1m5s
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:
+68
-58
@@ -4,6 +4,8 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
BarChart3,
|
||||
BookOpen,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Copy,
|
||||
Dices,
|
||||
FileText,
|
||||
@@ -104,6 +106,8 @@ function AppShellInner({ username }: { username: string }) {
|
||||
const [showActuals, setShowActuals] = useState(false);
|
||||
// Plan-Ebene: Dashboard / Szenarien-Liste / Analysen. Null = kein Plan-View aktiv.
|
||||
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 [showSystemParams, setShowSystemParams] = useState(false);
|
||||
const [showPlanTraces, setShowPlanTraces] = useState(false);
|
||||
@@ -378,18 +382,40 @@ function AppShellInner({ username }: { username: string }) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Unter-Menüpunkte, leicht eingezogen. */}
|
||||
{subItem("scenarios", "Szenarien", Layers)}
|
||||
{/* Der Szenario-Baum bleibt darunter -- ein Klick führt direkt in die Matrix. */}
|
||||
<ScenarioTree
|
||||
scenarios={p.scenarios}
|
||||
parentId={null}
|
||||
depth={1}
|
||||
selectedId={selectedScenarioId}
|
||||
onSelect={openScenario}
|
||||
onCopy={setCopyFrom}
|
||||
onDelete={handleDeleteScenario}
|
||||
/>
|
||||
{/* «Szenarien»: Chevron klappt den Baum auf/zu (Standard: zu), das Label führt
|
||||
in die Szenario-Liste. */}
|
||||
<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
|
||||
scenarios={p.scenarios}
|
||||
selectedId={selectedScenarioId}
|
||||
onSelect={openScenario}
|
||||
onCopy={setCopyFrom}
|
||||
onDelete={handleDeleteScenario}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openActualsForPlan(p.id)}
|
||||
@@ -863,70 +889,54 @@ function ChartsDialog({
|
||||
// gibt es kein Hover.
|
||||
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;
|
||||
// Flache, gleichmässig eingerückte Liste: Basisszenario zuoberst, danach die Varianten.
|
||||
// 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 (
|
||||
<>
|
||||
{level.map((s) => (
|
||||
<div key={s.id}>
|
||||
<div
|
||||
className={`group flex items-center gap-1.5 rounded-lg py-1.5 pr-1.5 text-sm transition-colors ${
|
||||
selectedId === s.id ? "bg-accent-soft font-medium text-accent-soft-fg" : "text-muted hover:bg-surface-2"
|
||||
}`}
|
||||
style={{ paddingLeft: `${12 + depth * 14}px` }}
|
||||
{ordered.map((s) => (
|
||||
<div
|
||||
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"
|
||||
}`}
|
||||
>
|
||||
<button type="button" onClick={() => onSelect(s.id)} className="flex min-w-0 flex-1 items-center gap-1.5 text-left">
|
||||
{s.isBase ? <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>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Kopie erstellen"
|
||||
title="Neues Szenario aus diesem"
|
||||
onClick={() => onCopy(s)}
|
||||
className="rounded p-0.5 text-faint opacity-60 transition-opacity hover:bg-accent-soft hover:text-accent group-hover:opacity-100"
|
||||
>
|
||||
<button type="button" onClick={() => onSelect(s.id)} className="flex min-w-0 flex-1 items-center gap-1.5 text-left">
|
||||
{s.isBase ? (
|
||||
<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>
|
||||
</button>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{!s.isBase && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Kopie erstellen"
|
||||
title="Neues Szenario aus diesem"
|
||||
onClick={() => onCopy(s)}
|
||||
className="rounded p-0.5 text-faint opacity-60 transition-opacity hover:bg-accent-soft hover:text-accent group-hover:opacity-100"
|
||||
aria-label="Szenario löschen"
|
||||
onClick={() => onDelete(s)}
|
||||
className="rounded p-0.5 text-faint opacity-60 transition-opacity hover:bg-danger-soft hover:text-danger group-hover:opacity-100"
|
||||
>
|
||||
<Copy className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{!s.isBase && (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Szenario löschen"
|
||||
onClick={() => onDelete(s)}
|
||||
className="rounded p-0.5 text-faint opacity-60 transition-opacity hover:bg-danger-soft hover:text-danger group-hover:opacity-100"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<ScenarioTree
|
||||
scenarios={scenarios}
|
||||
parentId={s.id}
|
||||
depth={depth + 1}
|
||||
selectedId={selectedId}
|
||||
onSelect={onSelect}
|
||||
onCopy={onCopy}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user