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
+24 -24
View File
@@ -8,7 +8,6 @@ import {
ChevronDown,
ChevronRight,
CreditCard,
Dices,
Home,
Landmark,
PiggyBank,
@@ -33,7 +32,6 @@ import {
type CellContext,
} from "@/components/ElementDetail";
import { PhaseDetail } from "@/components/PhaseDetail";
import { MonteCarloDialog } from "@/components/MonteCarloDialog";
import { PlanProfileFields, type ProfileDraft } from "@/components/PlanProfileFields";
import { MoneyField } from "@/components/FormField";
import { api } from "@/lib/api-client";
@@ -123,7 +121,6 @@ export function PlanView({
const [editTransition, setEditTransition] = useState<{ elementId: string; fromPhaseId: string } | null>(null);
const [editPhaseCell, setEditPhaseCell] = useState<{ elementId: string; phaseId: string } | null>(null);
const [showCashInit, setShowCashInit] = useState(false);
const [showMonteCarlo, setShowMonteCarlo] = useState(false);
// fromPhaseId des Cash-Uebergangs, der gerade bearbeitet wird.
const [editCashTransition, setEditCashTransition] = useState<string | null>(null);
const [valueMode, setValueMode] = useState<ValueMode>("nominal");
@@ -321,7 +318,12 @@ export function PlanView({
<span className="text-[11px] text-faint">real = kaufkraftbereinigt (Planbeginn)</span>
</div>
<Timeline phases={computed.phases} persons={personAxes} ruinAge={computed.ruinAge} />
<Timeline
phases={computed.phases}
persons={personAxes}
ruinAge={computed.ruinAge}
startYear={plan.startYear}
/>
{/* Plan-Profil */}
<div
@@ -376,13 +378,6 @@ export function PlanView({
>
<Plus className="h-4 w-4" /> Lebensphase
</button>
<button
type="button"
onClick={() => setShowMonteCarlo(true)}
className="ml-auto 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 durchführen
</button>
</div>
)}
@@ -590,13 +585,18 @@ export function PlanView({
</div>
)}
{/* Detail-Panel. Der key erzwingt beim Wechsel von Zelle/Phase einen Neuaufbau,
damit der lokale Formular-Zustand nicht vom vorher geoeffneten Element uebrig bleibt. */}
{selected && (
<div key={`ph-${selected.phaseId}`} className="rounded-xl border border-accent bg-surface p-4 shadow-sm">
{renderDetail()}
</div>
)}
{/* Lebensphase bearbeiten -- als Popup, konsistent zu allen anderen Eingaben.
Der key erzwingt beim Wechsel der Phase einen Neuaufbau, damit der lokale
Formular-Zustand nicht von der vorher geoeffneten Phase uebrig bleibt. */}
{selected && (() => {
const phase = computed.phases.find((p) => p.id === selected.phaseId);
if (!phase) return null;
return (
<DialogShell key={`ph-${selected.phaseId}`} title={`Lebensphase: ${phase.name}`} onClose={() => setSelected(null)}>
{renderDetail()}
</DialogShell>
);
})()}
{showAdd && firstPhase && (
<AddElementDialog
@@ -640,11 +640,7 @@ export function PlanView({
/>
)}
{showMonteCarlo && (
<MonteCarloDialog plan={plan} computed={computed} onClose={() => setShowMonteCarlo(false)} />
)}
{editCashTransition && (() => {
{editCashTransition && (() => {
const fromPhase = computed.phases.find((p) => p.id === editCashTransition);
if (!fromPhase) return null;
const toIndex = computed.phases.findIndex((p) => p.id === fromPhase.id) + 1;
@@ -759,7 +755,10 @@ export function PlanView({
maxDurationYears={phase.maxDurationYears}
isLast={isLast}
laterPhaseCount={laterPhaseCount(phase)}
onSaved={onChanged}
onSaved={() => {
setSelected(null);
onChanged();
}}
onDeleted={() => {
setSelected(null);
onChanged();
@@ -1190,6 +1189,7 @@ function PlanSettingsDialog({ plan, onClose, onSaved }: { plan: PlanInput; onClo
const [draft, setDraft] = useState<ProfileDraft>({
householdType: plan.householdType,
inflationRateDefault: plan.inflationRateDefault,
startYear: plan.startYear ?? new Date().getFullYear(),
persons: plan.persons.map((p) => ({ role: p.role, name: p.name ?? "", age: p.age, retirementAge: p.retirementAge })),
});
const [saving, setSaving] = useState(false);