Rework core model: financial elements as plan-wide entities across phases; derived phase types (Erwerb/Pension/Misch) with retirement-capped durations and per-plan retirement age; AHV (gap years + couple ceiling), PK payout/annuity, 3a, real estate, other assets/debts; horizontal timeline with retirement markers; phase x element matrix with detail panel; savings/consumption quota + available-capital key figures with red status
Deploy App / deploy (push) Successful in 1m57s

This commit is contained in:
2026-07-13 07:55:58 +02:00
parent b3a3b565ac
commit b775ab77cb
27 changed files with 2407 additions and 2166 deletions
+7 -79
View File
@@ -10,8 +10,7 @@ import {
Trash2,
X,
} from "lucide-react";
import { PhaseCard, formatAges } from "@/components/PhaseCard";
import { TransitionPanel } from "@/components/TransitionPanel";
import { PlanView } from "@/components/PlanView";
import { Dashboard } from "@/components/Dashboard";
import { HouseholdSettings } from "@/components/HouseholdSettings";
import { ProfileMenu } from "@/components/ProfileMenu";
@@ -81,16 +80,6 @@ export function AppShell({
if (selectedPlanId) loadDetail(selectedPlanId);
}
async function handleAddPhase() {
if (!selectedPlanId || !detail) return;
await api.post(`/api/plans/${selectedPlanId}/phases`, {
name: detail.plan.phases.length === 0 ? "Erste Lebensphase" : `Neue Phase ${detail.plan.phases.length + 1}`,
durationYears: 10,
incomeMode: "HOUSEHOLD",
});
refreshCurrent();
}
async function handleDeletePlan(id: string) {
if (!confirm("Diesen Plan wirklich loeschen?")) return;
await api.delete(`/api/plans/${id}`);
@@ -232,16 +221,7 @@ export function AppShell({
{!loading && detail && selectedPlanId && (
<div className="flex flex-col gap-6">
{/* Plan-Kopf mit Aktionen */}
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={handleAddPhase}
className="flex items-center gap-1.5 rounded-lg border border-dashed border-indigo-300 bg-indigo-50/50 px-3 py-1.5 text-sm font-medium text-indigo-700 hover:bg-indigo-50 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300 dark:hover:bg-indigo-500/20"
>
<Plus className="h-4 w-4" />
Phase hinzufuegen
</button>
{detail.plan.phases.length > 0 && (
<button
type="button"
@@ -262,64 +242,12 @@ export function AppShell({
</button>
</div>
{/* Phasen mit Lebenslinie */}
<div className="flex flex-col">
{detail.plan.phases.map((phase, i) => {
const computedPhase = detail.computed.phases.find((c) => c.id === phase.id)!;
const nextPhase = detail.plan.phases[i + 1];
const startAges = computedPhase.ages.map((a) => a.startAge).join("·");
return (
<div key={phase.id} className="flex gap-3">
{/* Lebenslinie */}
<div className="hidden w-12 flex-col items-center sm:flex">
<div
title={`Alter zu Beginn: ${formatAges(computedPhase)}`}
className="flex h-9 w-12 items-center justify-center rounded-full border border-indigo-200 bg-indigo-50 text-[11px] font-semibold text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/15 dark:text-indigo-300"
>
{startAges}
</div>
<div className="w-px flex-1 bg-indigo-200 dark:bg-indigo-500/30" />
</div>
<div className="flex min-w-0 flex-1 flex-col gap-3 pb-3">
<PhaseCard
household={household}
phase={phase}
computed={computedPhase}
isFirst={i === 0}
isLast={i === detail.plan.phases.length - 1}
onChanged={refreshCurrent}
/>
{nextPhase && (
<TransitionPanel
phase={phase}
computed={computedPhase}
nextPhaseName={nextPhase.name}
onChanged={refreshCurrent}
/>
)}
</div>
</div>
);
})}
{detail.computed.phases.length > 0 && (
<div className="hidden w-12 flex-col items-center sm:flex">
<div
title="Alter am Ende der letzten Phase"
className="flex h-9 w-12 items-center justify-center rounded-full border border-zinc-300 bg-white text-[11px] font-semibold text-zinc-600 dark:border-zinc-600 dark:bg-zinc-800 dark:text-zinc-300"
>
{detail.computed.phases[detail.computed.phases.length - 1].ages
.map((a) => a.endAge)
.join("·")}
</div>
</div>
)}
</div>
{detail.plan.phases.length === 0 && (
<p className="text-sm text-zinc-500">
Dieser Plan hat noch keine Phasen. Fuegen Sie oben die erste Lebensphase hinzu.
</p>
)}
<PlanView
plan={detail.plan}
household={household}
computed={detail.computed}
onChanged={refreshCurrent}
/>
{detail.plan.phases.length > 0 && (
<Dashboard plan={detail.plan} computed={detail.computed} allPlans={plans} />