Build a Finance SaaS Platform -24 (Data Charts)

Install Package

1
npm i recharts

Add Area Variant Component

Add components/area-variant.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import { format } from "date-fns";
import {
Tooltip,
XAxis,
AreaChart,
Area,
ResponsiveContainer,
CartesianGrid
} from "recharts";
import { CustomTooltip } from "./custom-tooltips";

type Props = {
data: {
date: string;
income: number;
expenses: number;
}[];
};

export const AreaVariant = ( {data}: Props ) => {
return (
<ResponsiveContainer width="100%" height={350}>
<AreaChart data={ data }>
<CartesianGrid strokeDasharray="3 3" />
<defs>
<linearGradient id="income" x1="0" y1="0" x2="0" y2="1">
<stop offset="2%" stopColor="#3d82f6" stopOpacity={0.8} />
<stop offset="98%" stopColor="#3d82f6" stopOpacity={0} />
</linearGradient>
<linearGradient id="expenses" x1="0" y1="0" x2="0" y2="1">
<stop offset="2%" stopColor="#f43f5e" stopOpacity={0.8} />
<stop offset="98%" stopColor="#f43f5e" stopOpacity={0} />
</linearGradient>
</defs>
<XAxis
axisLine={false}
tickLine={false}
dataKey="date"
tickFormatter={(value) => format(value,"dd MMM")}
style={{fontSize: "12px" }}
tickMargin={16}
/>
<Tooltip content={<CustomTooltip />}/>
<Area
type="monotone"
dataKey="income"
stackId="income"
strokeWidth={2}
stroke="#3d82f6"
fill="url(#income)"
className="drop-shadow-sm"
/>
<Area
type="monotone"
dataKey="expenses"
stackId="expenses"
strokeWidth={2}
stroke="#f43f5e"
fill="url(#expenses)"
className="drop-shadow-sm"
/>
</AreaChart>
</ResponsiveContainer>
)
}

Add Line Variant Component

Add components/line-variant.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { format } from "date-fns";
import {
Tooltip,
XAxis,
ResponsiveContainer,
LineChart,
Line,
CartesianGrid
} from "recharts";
import { CustomTooltip } from "./custom-tooltips";

type Props = {
data: {
date: string;
income: number;
expenses: number;
}[];
};

export const LineVariant = ( {data}: Props ) => {
return (
<ResponsiveContainer width="100%" height={350}>
<LineChart data={ data }>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
axisLine={false}
tickLine={false}
dataKey="date"
tickFormatter={(value) => format(value,"dd MMM")}
style={{fontSize: "12px" }}
tickMargin={16}
/>
<Tooltip content={<CustomTooltip />}/>
<Line
dot={false}
dataKey="income"
stroke="#3d82f6"
strokeWidth={2}
className="drop-shadow-sm"
/>
<Line
dot={false}
dataKey="expenses"
stroke="#f43f5e"
strokeWidth={2}
className="drop-shadow-sm"
/>
</LineChart>
</ResponsiveContainer>
)
}

Add Bar Variant Component

Add components/bar-variant.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import { format } from "date-fns";
import {
Tooltip,
XAxis,
ResponsiveContainer,
BarChart,
Bar,
CartesianGrid
} from "recharts";
import { CustomTooltip } from "./custom-tooltips";

type Props = {
data: {
date: string;
income: number;
expenses: number;
}[];
};

export const BarVariant = ( {data}: Props ) => {
return (
<ResponsiveContainer width="100%" height={350}>
<BarChart data={ data }>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
axisLine={false}
tickLine={false}
dataKey="date"
tickFormatter={(value) => format(value,"dd MMM")}
style={{fontSize: "12px" }}
tickMargin={16}
/>
<Tooltip content={<CustomTooltip />}/>
<Bar
dataKey="income"
fill="#3d82f6"
className="drop-shadow-sm"
/>
<Bar
dataKey="expenses"
fill="#f43f5e"
className="drop-shadow-sm"
/>
</BarChart>
</ResponsiveContainer>
)
}

Add Chart Component

Add components/chart.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import { useState } from "react";
import { AreaChart, BarChart3, FileSearch, LineChart } from "lucide-react";

import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { AreaVariant } from "./area-variant";
import { BarVariant } from "./bar-variant";
import { LineVariant } from "./line-variant";

type Props = {
data?: {
date: string;
income: number;
expenses: number;
}[];
};

export const Chart = ( { data = []} : Props) => {
const [chartType, setChartType] = useState("area");

const onTypeChange = ( type: string) => {
// TODO: Add paywall
setChartType(type);
}
return(
<Card className="border-none drop-shadow-sm">
<CardHeader className="flex space-y-2 lg:space-y-0 lg:flex-row lg:items-center justify-between">
<CardTitle className="text-xl line-clamp-1">
Transactions
</CardTitle>
<Select
defaultValue={chartType}
onValueChange={onTypeChange}
>
<SelectTrigger className="lg:w-auto h-9 rounded-md px-3">
<SelectValue placeholder="Chart type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="area">
<div className="flex items-center">
<AreaChart className="size-4 mr-2 shrink-0" />
<p className="line-clamp-1">
Area chart
</p>
</div>
</SelectItem>
<SelectItem value="line">
<div className="flex items-center">
<LineChart className="size-4 mr-2 shrink-0" />
<p className="line-clamp-1">
Line chart
</p>
</div>
</SelectItem>
<SelectItem value="bar">
<div className="flex items-center">
<BarChart3 className="size-4 mr-2 shrink-0" />
<p className="line-clamp-1">
Bar chart
</p>
</div>
</SelectItem>
</SelectContent>
</Select>
</CardHeader>
<CardContent>
{data.length === 0 ? (
<div className="felx flex-col gap-y-4 items-center justify-center h-[350px] w-full">
<FileSearch className="size-6 text-muted-foreground" />
<p className="text-muted-foreground text-sm">
No data for this period
</p>
</div>
) : (
<>
{ chartType === "line" && <LineVariant data={data} /> }
{ chartType === "area" && <AreaVariant data={data} /> }
{ chartType === "bar" && <BarVariant data={data} /> }
</>
)}
</CardContent>
</Card>
)
}

Add Data Chart Component

ADd components/data-charts.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"use client";

import { useGetSummary } from "@/features/summary/api/use-get-summary";
import { Chart } from "./chart";

export const DataCharts = () => {
const { data, isLoading} = useGetSummary();

if( isLoading) {
return(
<div>
Loading...
</div>
)
}

return(
<div className="grid grid-cols-1 lg:grid-cols-6 gap-8">
<div className="col-span-1 lg:col-span-5 xl:col-span-6">
<Chart data={data?.days} />
</div>
</div>
);
}

Modify Summary Page

Modify app/(dashboard)/page.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"use client";

import { DataCharts } from "@/components/data-charts";
import { DataGrid } from "@/components/data-grid";

export default function DashboardPage() {

return (
<div className="max-w-screen-2xl mx-auto w-full pb-10 -mt-24">
<DataGrid />
<DataCharts />
</div>
);
}

Add Custom Tooltips

Add components/custom-tooltips.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { format } from "date-fns";
import { Separator } from "./ui/separator";
import { formatCurrency } from "@/lib/utils";

export const CustomTooltip = ( { active, payload } : any ) => {
if (!active) return null;

const date = payload[0].payload.date;
const income = payload[0].value;
const expenses = payload[1].value;

return(
<div className="rounded-sm bg-white shadow-sm border overflow-hidden">
<div className="text-sm p-2 px-3 bg-muted text-muted-foreground">
{format(date, "MMMM dd,yyyy")}
</div>
<Separator />
<div className="p-2 px-3 space-y-1">
<div className="flex items-center justify-between gap-x-4">
<div className="flex items-center gap-x-2">
<div className="size-1.5 bg-blue-500 rounded-full" />
<p className="text-sm text-muted-foreground">
Income
</p>
</div>
<p className="text-sm text-right font-medium">
{formatCurrency(income)}
</p>
</div>
<div className="flex items-center justify-between gap-x-4">
<div className="flex items-center gap-x-2">
<div className="size-1.5 bg-rose-500 rounded-full" />
<p className="text-sm text-muted-foreground">
Expenses
</p>
</div>
<p className="text-sm text-right font-medium">
{formatCurrency(expenses * -1)}
</p>
</div>
</div>
</div>
)
}

请我喝杯咖啡吧~

支付宝
微信