Skip to content

Commit a2f79c6

Browse files
AlenAlen
authored andcommitted
feat: add net worth history chart
1 parent 900d5dd commit a2f79c6

2 files changed

Lines changed: 162 additions & 2 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import Decimal from "decimal.js";
2+
import {
3+
Bar,
4+
CartesianGrid,
5+
ComposedChart,
6+
Legend,
7+
Line,
8+
XAxis,
9+
YAxis,
10+
} from "recharts";
11+
import {
12+
Card,
13+
CardContent,
14+
CardDescription,
15+
CardHeader,
16+
CardTitle,
17+
} from "~/components/ui/card";
18+
import {
19+
ChartConfig,
20+
ChartContainer,
21+
ChartTooltip,
22+
ChartTooltipContent,
23+
} from "~/components/ui/chart";
24+
import { APP_CURRENCY } from "~/constants";
25+
import { calculateZeroInclusiveYAxisDomain } from "~/utils/chart";
26+
import { formatDate } from "~/utils/date";
27+
import { formatNumber } from "~/utils/number";
28+
29+
type ChartDataItem = {
30+
id: string;
31+
timestamp: Date;
32+
netValue: Decimal;
33+
totalAssets: Decimal;
34+
totalDebts: Decimal;
35+
};
36+
37+
const netWorthChartConfig: ChartConfig = {
38+
netWorth: {
39+
label: "Net worth",
40+
color: "var(--chart-3)",
41+
},
42+
totalAssets: {
43+
label: "Assets",
44+
color: "var(--chart-1)",
45+
},
46+
totalDebts: {
47+
label: "Debts",
48+
color: "var(--chart-2)",
49+
},
50+
};
51+
52+
export function Chart({ data }: { data: ChartDataItem[] }) {
53+
const chartData = data.toReversed().map((nw) => ({
54+
month: formatDate({
55+
date: nw.timestamp,
56+
options: {
57+
dateStyle: undefined,
58+
month: "short",
59+
},
60+
}),
61+
netWorth: nw.netValue.toNumber(),
62+
totalAssets: nw.totalAssets.toNumber(),
63+
totalDebts: nw.totalDebts.toNumber(),
64+
}));
65+
console.log(data, chartData);
66+
67+
return (
68+
<Card>
69+
<CardHeader>
70+
<CardTitle>Net worth history</CardTitle>
71+
<CardDescription>All time</CardDescription>
72+
</CardHeader>
73+
<CardContent>
74+
<ChartContainer
75+
config={netWorthChartConfig}
76+
className="h-[15rem] w-full"
77+
>
78+
<ComposedChart accessibilityLayer data={chartData}>
79+
<CartesianGrid vertical={false} />
80+
<XAxis
81+
dataKey="month"
82+
tickLine={false}
83+
axisLine={false}
84+
tickMargin={8}
85+
/>
86+
87+
<YAxis
88+
tickLine={false}
89+
axisLine={false}
90+
tickMargin={8}
91+
domain={calculateZeroInclusiveYAxisDomain}
92+
tickFormatter={(value: number) =>
93+
formatNumber({
94+
value,
95+
options: {
96+
style: "currency",
97+
currency: APP_CURRENCY,
98+
maximumFractionDigits: 0,
99+
},
100+
})
101+
}
102+
/>
103+
104+
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
105+
106+
<Bar
107+
dataKey="totalAssets"
108+
fill="var(--color-totalAssets)"
109+
barSize={30}
110+
radius={4}
111+
name="Assets"
112+
/>
113+
114+
<Bar
115+
dataKey="totalDebts"
116+
fill="var(--color-totalDebts)"
117+
barSize={30}
118+
radius={4}
119+
name="Debts"
120+
/>
121+
122+
<Line
123+
dataKey="netWorth"
124+
type="monotone"
125+
stroke="var(--color-netWorth)"
126+
strokeWidth={3}
127+
dot={false}
128+
name="Net worth"
129+
/>
130+
131+
<Legend />
132+
</ComposedChart>
133+
</ChartContainer>
134+
</CardContent>
135+
</Card>
136+
);
137+
}

src/app/dashboard/performance/net-worth-history/page.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ import { TableSkeleton } from "~/components/table-skeleton";
1616
import { DataTable } from "~/components/ui/data-table";
1717
import { DataTableColumns } from "~/components/ui/data-table-columns";
1818
import { useTable } from "~/hooks/useTable";
19+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
20+
import { useState } from "react";
21+
import { Chart } from "./Chart";
22+
23+
type Tab = "TABLE" | "CHART";
1924

2025
export default function NwHistoryPage() {
2126
const { data = [], isPending } = api.netWorth.getAll.useQuery();
27+
const [tab, setTab] = useState<Tab>("TABLE");
2228

2329
const table = useTable({
2430
data,
@@ -48,9 +54,26 @@ export default function NwHistoryPage() {
4854
</header>
4955

5056
<div className="mx-auto flex w-full max-w-screen-md flex-col gap-2 p-5">
51-
<DataTableColumns table={table} className="self-end" />
57+
<Tabs value={tab} onValueChange={(value) => setTab(value as Tab)}>
58+
<div className="flex justify-between">
59+
<TabsList>
60+
<TabsTrigger value={"TABLE" satisfies Tab}>Table</TabsTrigger>
61+
<TabsTrigger value={"CHART" satisfies Tab}>Chart</TabsTrigger>
62+
</TabsList>
63+
64+
{tab === "TABLE" && (
65+
<DataTableColumns table={table} className="self-end" />
66+
)}
67+
</div>
68+
69+
<TabsContent value={"TABLE" satisfies Tab}>
70+
{isPending ? <TableSkeleton /> : <DataTable table={table} />}
71+
</TabsContent>
5272

53-
{isPending ? <TableSkeleton /> : <DataTable table={table} />}
73+
<TabsContent value={"CHART" satisfies Tab} className="mt-5">
74+
<Chart data={data} />
75+
</TabsContent>
76+
</Tabs>
5477
</div>
5578
</>
5679
);

0 commit comments

Comments
 (0)