result style
This commit is contained in:
parent
ffb8e8d506
commit
0f6cd57f3d
@ -541,180 +541,206 @@ export default function FireCalculatorForm() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{result && (
|
{result && (
|
||||||
<>
|
<div className="mb-8 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<Card className="mb-8">
|
{result.error ? (
|
||||||
<CardHeader>
|
<Card className="col-span-full">
|
||||||
<CardTitle>Results</CardTitle>
|
<CardContent className="pt-6">
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{result.error ? (
|
|
||||||
<p className="text-destructive">{result.error}</p>
|
<p className="text-destructive">{result.error}</p>
|
||||||
) : (
|
</CardContent>
|
||||||
<div className="space-y-4">
|
</Card>
|
||||||
<div>
|
) : (
|
||||||
<Label>
|
<>
|
||||||
FIRE Number (Required Capital at Retirement - Strategy:{" "}
|
<Card>
|
||||||
{form.getValues().retirementStrategy})
|
<CardHeader>
|
||||||
</Label>
|
<CardTitle>FIRE Number</CardTitle>
|
||||||
<p className="text-2xl font-bold">
|
<CardDescription className="text-xs">
|
||||||
|
Required capital at retirement using{" "}
|
||||||
|
{form.getValues().retirementStrategy}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-3xl font-bold">
|
||||||
{formatNumber(result.fireNumber)}
|
{formatNumber(result.fireNumber)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</CardContent>
|
||||||
<Label>Estimated Retirement Age</Label>
|
</Card>
|
||||||
<p className="text-2xl font-bold">
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Retirement Age</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
Estimated age when you can retire
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-3xl font-bold">
|
||||||
{result.retirementAge ?? "N/A"}
|
{result.retirementAge ?? "N/A"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{result.inflationAdjustedAllowance && (
|
</CardContent>
|
||||||
<div>
|
</Card>
|
||||||
<Label>
|
|
||||||
Monthly Allowance at Retirement (Inflation Adjusted)
|
{result.inflationAdjustedAllowance && (
|
||||||
</Label>
|
<Card>
|
||||||
<p className="text-2xl font-bold">
|
<CardHeader>
|
||||||
|
<CardTitle>Monthly Allowance</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
At retirement (inflation adjusted)
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-3xl font-bold">
|
||||||
{formatNumber(result.inflationAdjustedAllowance)}
|
{formatNumber(result.inflationAdjustedAllowance)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</CardContent>
|
||||||
{result.retirementYears && (
|
</Card>
|
||||||
<div>
|
)}
|
||||||
<Label>Retirement Duration (Years)</Label>
|
|
||||||
<p className="text-2xl font-bold">
|
{result.retirementYears && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Retirement Duration</CardTitle>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
Years in retirement
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-3xl font-bold">
|
||||||
{result.retirementYears}
|
{result.retirementYears}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</>
|
||||||
</Card>
|
|
||||||
|
|
||||||
{result.yearlyData && result.yearlyData.length > 0 && (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Financial Projection</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Projected balance growth and FIRE number threshold
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<ChartContainer
|
|
||||||
className="h-80"
|
|
||||||
config={{
|
|
||||||
balance: {
|
|
||||||
label: "Balance",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
fireNumber: {
|
|
||||||
label: "FIRE Number",
|
|
||||||
color: "var(--chart-3)",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AreaChart
|
|
||||||
data={result.yearlyData}
|
|
||||||
margin={{ top: 20, right: 30, left: 20, bottom: 20 }}
|
|
||||||
>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis
|
|
||||||
dataKey="year"
|
|
||||||
label={{
|
|
||||||
value: "Year",
|
|
||||||
position: "insideBottom",
|
|
||||||
offset: -10,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<YAxis
|
|
||||||
tickFormatter={(value: number) => {
|
|
||||||
if (value >= 1000000) {
|
|
||||||
return `${(value / 1000000).toFixed(1)}M`;
|
|
||||||
} else if (value >= 1000) {
|
|
||||||
return `${(value / 1000).toFixed(0)}K`;
|
|
||||||
}
|
|
||||||
return value.toString();
|
|
||||||
}}
|
|
||||||
width={80}
|
|
||||||
/>
|
|
||||||
<ChartTooltip
|
|
||||||
content={({ active, payload }) => {
|
|
||||||
if (active && payload?.[0]?.payload) {
|
|
||||||
const data = payload[0]
|
|
||||||
.payload as (typeof result.yearlyData)[0];
|
|
||||||
return (
|
|
||||||
<div className="bg-background border p-2 shadow-sm">
|
|
||||||
<p className="font-medium">{`Year: ${data.year.toString()} (Age: ${data.age.toString()})`}</p>
|
|
||||||
<p className="text-primary">{`Balance: ${formatNumber(data.balance)}`}</p>
|
|
||||||
{result.fireNumber !== null && (
|
|
||||||
<p className="text-destructive">{`Target FIRE Number: ${formatNumber(result.fireNumber)}`}</p>
|
|
||||||
)}
|
|
||||||
<p>{`Phase: ${data.phase === "accumulation" ? "Accumulation" : "Retirement"}`}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<defs>
|
|
||||||
<linearGradient
|
|
||||||
id="fillBalance"
|
|
||||||
x1="0"
|
|
||||||
y1="0"
|
|
||||||
x2="0"
|
|
||||||
y2="1"
|
|
||||||
>
|
|
||||||
<stop
|
|
||||||
offset="5%"
|
|
||||||
stopColor="var(--chart-1)"
|
|
||||||
stopOpacity={0.8}
|
|
||||||
/>
|
|
||||||
<stop
|
|
||||||
offset="95%"
|
|
||||||
stopColor="var(--chart-1)"
|
|
||||||
stopOpacity={0.1}
|
|
||||||
/>
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<Area
|
|
||||||
type="monotone"
|
|
||||||
dataKey="balance"
|
|
||||||
name="balance"
|
|
||||||
stroke="var(--chart-1)"
|
|
||||||
fill="url(#fillBalance)"
|
|
||||||
fillOpacity={0.4}
|
|
||||||
activeDot={{ r: 6 }}
|
|
||||||
/>
|
|
||||||
{result.fireNumber && (
|
|
||||||
<ReferenceLine
|
|
||||||
y={result.fireNumber}
|
|
||||||
stroke="var(--chart-3)"
|
|
||||||
strokeWidth={2}
|
|
||||||
strokeDasharray="5 5"
|
|
||||||
label={{
|
|
||||||
value: "FIRE Number",
|
|
||||||
position: "insideBottomRight",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{result.retirementAge && (
|
|
||||||
<ReferenceLine
|
|
||||||
x={
|
|
||||||
currentYear +
|
|
||||||
(result.retirementAge - form.getValues().currentAge)
|
|
||||||
}
|
|
||||||
stroke="var(--chart-2)"
|
|
||||||
strokeWidth={2}
|
|
||||||
label={{
|
|
||||||
value: "Retirement",
|
|
||||||
position: "insideTopRight",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{result?.yearlyData && result.yearlyData.length > 0 && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Financial Projection</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Projected balance growth and FIRE number threshold
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<ChartContainer
|
||||||
|
className="h-80"
|
||||||
|
config={{
|
||||||
|
balance: {
|
||||||
|
label: "Balance",
|
||||||
|
color: "var(--chart-1)",
|
||||||
|
},
|
||||||
|
fireNumber: {
|
||||||
|
label: "FIRE Number",
|
||||||
|
color: "var(--chart-3)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AreaChart
|
||||||
|
data={result.yearlyData}
|
||||||
|
margin={{ top: 20, right: 30, left: 20, bottom: 20 }}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis
|
||||||
|
dataKey="year"
|
||||||
|
label={{
|
||||||
|
value: "Year",
|
||||||
|
position: "insideBottom",
|
||||||
|
offset: -10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
tickFormatter={(value: number) => {
|
||||||
|
if (value >= 1000000) {
|
||||||
|
return `${(value / 1000000).toFixed(1)}M`;
|
||||||
|
} else if (value >= 1000) {
|
||||||
|
return `${(value / 1000).toFixed(0)}K`;
|
||||||
|
}
|
||||||
|
return value.toString();
|
||||||
|
}}
|
||||||
|
width={80}
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
content={({ active, payload }) => {
|
||||||
|
if (active && payload?.[0]?.payload) {
|
||||||
|
const data = payload[0]
|
||||||
|
.payload as (typeof result.yearlyData)[0];
|
||||||
|
return (
|
||||||
|
<div className="bg-background border p-2 shadow-sm">
|
||||||
|
<p className="font-medium">{`Year: ${data.year.toString()} (Age: ${data.age.toString()})`}</p>
|
||||||
|
<p className="text-primary">{`Balance: ${formatNumber(data.balance)}`}</p>
|
||||||
|
{result.fireNumber !== null && (
|
||||||
|
<p className="text-destructive">{`Target FIRE Number: ${formatNumber(result.fireNumber)}`}</p>
|
||||||
|
)}
|
||||||
|
<p>{`Phase: ${data.phase === "accumulation" ? "Accumulation" : "Retirement"}`}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="fillBalance" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--chart-1)"
|
||||||
|
stopOpacity={0.8}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--chart-1)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="balance"
|
||||||
|
name="balance"
|
||||||
|
stroke="var(--chart-1)"
|
||||||
|
fill="url(#fillBalance)"
|
||||||
|
fillOpacity={0.4}
|
||||||
|
activeDot={{ r: 6 }}
|
||||||
|
/>
|
||||||
|
{result.fireNumber && (
|
||||||
|
<ReferenceLine
|
||||||
|
y={result.fireNumber}
|
||||||
|
stroke="var(--chart-3)"
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray="5 5"
|
||||||
|
label={{
|
||||||
|
value: "FIRE Number",
|
||||||
|
position: "insideBottomRight",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{result.retirementAge && (
|
||||||
|
<ReferenceLine
|
||||||
|
x={
|
||||||
|
currentYear +
|
||||||
|
(result.retirementAge - form.getValues().currentAge)
|
||||||
|
}
|
||||||
|
stroke="var(--chart-2)"
|
||||||
|
strokeWidth={2}
|
||||||
|
label={{
|
||||||
|
value: "Retirement",
|
||||||
|
position: "insideTopRight",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user