UI-Umbau: Analyse-Bereich, Planstart, Zeitachse mit Phasen
Deploy App / deploy (push) Successful in 1m44s
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user