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
Deploy App / deploy (push) Successful in 1m57s
This commit is contained in:
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user