Phasen-Kopf: Verteil-Prozent bei der Quote, Start->End-Alter je Person, Vermoegen Start->Ende
Deploy App / deploy (push) Successful in 1m0s

- Sparquote/Verzehr zeigt in Klammern den bereits verteilten/gedeckten Prozentsatz,
  z. B. "Sparquote 10'000 (100% verteilt)".
- Statt einer Alterszeile je Phase nun pro Person "Name Startalter -> Endalter".
- Neue Zeile "Vermoegen Startvermoegen -> Endvermoegen" (nominal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 14:56:45 +02:00
parent bbe5acee85
commit b45af8f264
+24 -5
View File
@@ -308,6 +308,7 @@ export function PlanView({
<PhaseHeader <PhaseHeader
key={col.phase.id} key={col.phase.id}
phase={col.phase} phase={col.phase}
personLabel={personLabel}
onClick={() => setSelected({ type: "phase", phaseId: col.phase.id })} onClick={() => setSelected({ type: "phase", phaseId: col.phase.id })}
active={selected?.type === "phase" && selected.phaseId === col.phase.id} active={selected?.type === "phase" && selected.phaseId === col.phase.id}
/> />
@@ -596,9 +597,21 @@ function phaseCellContent(ce: ReturnType<PhaseComputed["elements"]["find"]> | un
return ce.summary || ""; return ce.summary || "";
} }
function PhaseHeader({ phase, onClick, active }: { phase: PhaseComputed; onClick: () => void; active: boolean }) { function PhaseHeader({
phase,
personLabel,
onClick,
active,
}: {
phase: PhaseComputed;
personLabel: (role: string) => string;
onClick: () => void;
active: boolean;
}) {
const quotaLabel = phase.isConsumption ? "Verzehr" : "Sparquote"; const quotaLabel = phase.isConsumption ? "Verzehr" : "Sparquote";
const quotaRemaining = Math.max(0, phase.quotaRemaining); const quotaTarget = Math.abs(phase.quota);
const quotaPct = quotaTarget > 0 ? Math.round((phase.quotaAllocated / quotaTarget) * 100) : 100;
const distributedWord = phase.isConsumption ? "gedeckt" : "verteilt";
return ( return (
<th <th
onClick={onClick} onClick={onClick}
@@ -619,14 +632,20 @@ function PhaseHeader({ phase, onClick, active }: { phase: PhaseComputed; onClick
{phase.type === "ERWERB" ? "Erwerb" : phase.type === "PENSION" ? "Pension" : "Misch"} {phase.type === "ERWERB" ? "Erwerb" : phase.type === "PENSION" ? "Pension" : "Misch"}
</span> </span>
<span className="text-[10px] text-faint">{phase.durationYears} J.</span> <span className="text-[10px] text-faint">{phase.durationYears} J.</span>
<span className="text-[10px] text-faint">Alter {phase.persons.map((p) => p.startAge).join("/")}</span>
</div> </div>
<div className="mt-1 space-y-0.5 text-[10px] leading-tight text-muted"> <div className="mt-1 space-y-0.5 text-[10px] leading-tight text-muted">
{phase.persons.map((p) => (
<div key={p.role} className="text-faint">
{personLabel(p.role)} {p.startAge} {p.endAge}
</div>
))}
<div>Einkommen {formatChf(phase.incomeTotal)}</div> <div>Einkommen {formatChf(phase.incomeTotal)}</div>
<div>Ausgaben {formatChf(phase.expenseTotal)}</div> <div>Ausgaben {formatChf(phase.expenseTotal)}</div>
<div className={phase.quotaComplete ? "text-success" : "text-danger"}> <div className={phase.quotaComplete ? "text-success" : "text-danger"}>
{quotaLabel} {formatChf(Math.abs(phase.quota))} {quotaLabel} {formatChf(quotaTarget)} ({quotaPct}% {distributedWord})
{!phase.quotaComplete && <span> · offen {formatChf(quotaRemaining)}</span>} </div>
<div>
Vermoegen {formatChf(phase.startWealthNominal)} {formatChf(phase.endWealthNominal)}
</div> </div>
<div className={phase.availableCapitalComplete ? "" : "text-danger"}> <div className={phase.availableCapitalComplete ? "" : "text-danger"}>
Kapital {phase.availableCapital === null ? "n.a." : formatChf(phase.availableCapital)} Kapital {phase.availableCapital === null ? "n.a." : formatChf(phase.availableCapital)}