83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
CartesianGrid,
|
|
Legend,
|
|
Line,
|
|
LineChart,
|
|
ResponsiveContainer,
|
|
Tooltip,
|
|
XAxis,
|
|
YAxis,
|
|
} from "recharts";
|
|
import { formatChf } from "@/lib/format";
|
|
import type { PlanComputed } from "@/lib/calculations";
|
|
|
|
export interface TimelineSeries {
|
|
label: string;
|
|
color: string;
|
|
computed: PlanComputed;
|
|
}
|
|
|
|
// Liniendiagramm: Endvermoegen (nominal + real) je Lebensphase. Unterstuetzt mehrere
|
|
// ueberlagerte Plaene fuer den Szenario-Vergleich.
|
|
export function WealthChart({ series }: { series: TimelineSeries[] }) {
|
|
if (series.length === 0 || series[0].computed.phases.length === 0) {
|
|
return <p className="text-sm text-zinc-500">Noch keine Phasen vorhanden.</p>;
|
|
}
|
|
|
|
// Datenpunkte je Phasen-Index; X-Achse = Phasenname des Hauptplans.
|
|
const maxLen = Math.max(...series.map((s) => s.computed.phases.length));
|
|
const data = Array.from({ length: maxLen }, (_, i) => {
|
|
const row: Record<string, number | string> = {
|
|
phase: series[0].computed.phases[i]?.name ?? `Phase ${i + 1}`,
|
|
};
|
|
for (const s of series) {
|
|
const p = s.computed.phases[i];
|
|
if (p) {
|
|
row[`${s.label} (nominal)`] = Math.round(p.endWealthNominal);
|
|
row[`${s.label} (real)`] = Math.round(p.endWealthReal);
|
|
}
|
|
}
|
|
return row;
|
|
});
|
|
|
|
return (
|
|
<div className="h-80 w-full">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<LineChart data={data} margin={{ top: 8, right: 16, left: 8, bottom: 8 }}>
|
|
<CartesianGrid strokeDasharray="3 3" className="stroke-zinc-200 dark:stroke-zinc-700" />
|
|
<XAxis dataKey="phase" tick={{ fontSize: 11 }} />
|
|
<YAxis
|
|
tick={{ fontSize: 11 }}
|
|
tickFormatter={(v) => Intl.NumberFormat("de-CH", { notation: "compact" }).format(v)}
|
|
/>
|
|
<Tooltip formatter={(v) => (typeof v === "number" ? formatChf(v) : v)} />
|
|
<Legend wrapperStyle={{ fontSize: 12 }} />
|
|
{series.map((s) => (
|
|
<Line
|
|
key={`${s.label}-nominal`}
|
|
type="monotone"
|
|
dataKey={`${s.label} (nominal)`}
|
|
stroke={s.color}
|
|
strokeWidth={2}
|
|
dot={false}
|
|
/>
|
|
))}
|
|
{series.map((s) => (
|
|
<Line
|
|
key={`${s.label}-real`}
|
|
type="monotone"
|
|
dataKey={`${s.label} (real)`}
|
|
stroke={s.color}
|
|
strokeWidth={2}
|
|
strokeDasharray="5 3"
|
|
dot={false}
|
|
/>
|
|
))}
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</div>
|
|
);
|
|
}
|