UI-Umbau: Analyse-Bereich, Planstart, Zeitachse mit Phasen
Deploy App / deploy (push) Successful in 1m44s

Sieben Anpassungen aus dem Feedback:

- Grafiken liegen neu im eigenen Bereich "Grafiken" (Dialog, Button oben) statt
  unter der Matrix. Die Matrix bleibt die ruhige Hauptansicht.
- Kennzahl "Geschaetzter Nachlass" entfernt -- sie war rechnerisch identisch mit
  dem nominalen Endvermoegen und suggerierte eine Zusatzinformation, die es nicht gab.
- Monte-Carlo-Button nach oben zu den Szenario-Aktionen verschoben.
- Neues Profilfeld "Planstart (Jahr)" (Scenario.startYear + Migration). Rein fuer
  die Darstellung -- die Berechnung rechnet unveraendert in relativen Jahren.
  Bestehende Szenarien werden auf das laufende Jahr gesetzt.
- Zeitachse zeigt die Lebensphasen als Segmente (Breite = Dauer, Einfaerbung nach
  Phasentyp) inkl. Jahresspanne, plus Jahres-Beschriftung an den Enden.
- Lebensphase bearbeiten neu als Popup statt Panel unter der Tabelle -- konsistent
  zu allen anderen Eingaben; Speichern schliesst.
- Vermoegensverlauf ueber ALLE Jahre statt nur ueber die Phasengrenzen. Dafuer
  fuehrt computePlan das Vermoegen neu pro Jahr mit (YearPoint.wealthNominal/
  wealthReal) -- dieselbe Groesse, die schon die Ruin-Erkennung berechnet. Damit
  werden Verlaeufe INNERHALB einer Phase sichtbar (z. B. Kapitalverzehr).

Zwei Tests fuer das Jahres-Vermoegen (58 -> 60). Spezifikation auf v0.9.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 13:40:31 +02:00
parent a5d4868c58
commit d203e504c0
16 changed files with 266 additions and 84 deletions
+73 -8
View File
@@ -2,7 +2,9 @@
import { useCallback, useEffect, useState } from "react";
import {
BarChart3,
Copy,
Dices,
FileText,
FolderKanban,
GitBranch,
@@ -15,6 +17,7 @@ import {
} from "lucide-react";
import { PlanView } from "@/components/PlanView";
import { Dashboard } from "@/components/Dashboard";
import { MonteCarloDialog } from "@/components/MonteCarloDialog";
import { SpecView } from "@/components/SpecView";
import { ProfileMenu } from "@/components/ProfileMenu";
import { PlanProfileFields, emptyProfileDraft, type ProfileDraft } from "@/components/PlanProfileFields";
@@ -39,6 +42,8 @@ export function AppShell({ username }: { username: string }) {
const [showNewPlan, setShowNewPlan] = useState(false);
const [copyFrom, setCopyFrom] = useState<ScenarioMeta | null>(null);
const [showSpec, setShowSpec] = useState(false);
const [showCharts, setShowCharts] = useState(false);
const [showMonteCarlo, setShowMonteCarlo] = useState(false);
const loadPlans = useCallback(async () => {
const data = await api.get<{ plans: PlanListItem[] }>("/api/plans");
@@ -267,6 +272,26 @@ export function AppShell({ username }: { username: string }) {
Szenario loeschen
</button>
)}
{detail.plan.phases.length > 0 && (
<>
<button
type="button"
onClick={() => setShowCharts(true)}
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-sm font-medium text-muted hover:bg-surface-2"
>
<BarChart3 className="h-4 w-4" />
Grafiken
</button>
<button
type="button"
onClick={() => setShowMonteCarlo(true)}
className="flex items-center gap-1.5 rounded-lg border border-border px-3 py-1.5 text-sm font-medium text-muted hover:bg-surface-2"
>
<Dices className="h-4 w-4" />
Monte-Carlo-Simulation
</button>
</>
)}
{diff && detail.base && (
<span className="ml-auto flex items-center gap-1.5 rounded-lg border border-diff bg-diff-soft px-3 py-1.5 text-xs font-medium text-diff">
<GitBranch className="h-3.5 w-3.5" />
@@ -278,14 +303,6 @@ export function AppShell({ username }: { username: string }) {
</div>
<PlanView plan={detail.plan} computed={detail.computed} diff={diff} onChanged={refreshCurrent} />
{detail.plan.phases.length > 0 && (
<Dashboard
plan={detail.plan}
computed={detail.computed}
siblings={(activePlan?.scenarios ?? []).filter((s) => s.id !== detail.meta.id)}
/>
)}
</div>
)}
</main>
@@ -306,6 +323,24 @@ export function AppShell({ username }: { username: string }) {
/>
)}
{showCharts && detail && (
<ChartsDialog onClose={() => setShowCharts(false)} title={`Grafiken · ${detail.meta.name}`}>
<Dashboard
plan={detail.plan}
computed={detail.computed}
siblings={(activePlan?.scenarios ?? []).filter((s) => s.id !== detail.meta.id)}
/>
</ChartsDialog>
)}
{showMonteCarlo && detail && (
<MonteCarloDialog
plan={detail.plan}
computed={detail.computed}
onClose={() => setShowMonteCarlo(false)}
/>
)}
{copyFrom && (
<CopyScenarioDialog
source={copyFrom}
@@ -325,6 +360,36 @@ export function AppShell({ username }: { username: string }) {
);
}
// Breiter Dialog fuer den Analyse-Bereich (Grafiken).
function ChartsDialog({
title,
children,
onClose,
}: {
title: string;
children: React.ReactNode;
onClose: () => void;
}) {
return (
<div className="fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/40 px-4 py-8" onClick={onClose}>
<div
onClick={(e) => e.stopPropagation()}
className="flex w-full max-w-5xl flex-col gap-4 rounded-2xl border border-border bg-surface p-6 shadow-xl"
>
<div className="flex items-center justify-between">
<h2 className="flex items-center gap-2 text-base font-semibold text-fg">
<BarChart3 className="h-5 w-5 text-accent" /> {title}
</h2>
<button type="button" onClick={onClose} aria-label="Schliessen" className="rounded-md p-1 text-faint hover:bg-surface-2">
<X className="h-4 w-4" />
</button>
</div>
{children}
</div>
</div>
);
}
// Rekursiver Szenario-Baum: Kinder werden eingerueckt, damit Sub-Szenarien sichtbar sind.
function ScenarioTree({
scenarios,