Skip to content

Commit c706cef

Browse files
committed
added components
1 parent d8cb714 commit c706cef

3 files changed

Lines changed: 73 additions & 211 deletions

File tree

app/(admin)/overview/page.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ const PlatformVolumeChart = dynamic(() => import('@/components/charts/PlatformVo
1616
import { Skeleton } from '@/components/ui';
1717
import { StatCard } from '@/components/shared';
1818

19-
const PlatformVolumeChart = dynamic(() => import('@/components/charts/PlatformVolumeChart'), {
20-
ssr: false,
21-
loading: () => <Skeleton className="h-[300px] w-full rounded-xl" />,
22-
});
19+
2320

2421
// Memoised so future additions of state to the parent won't re-render the chart.
2522
const AdminChartSection = memo(function AdminChartSection() {

components/charts/PlatformVolumeChart.tsx

Lines changed: 31 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,42 @@
11
"use client";
22

33
import React from 'react';
4+
import { useQuery } from '@tanstack/react-query';
5+
import axios from 'axios';
6+
import { useTheme } from 'next-themes';
47
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip } from 'recharts';
8+
import { Skeleton } from '@/components/ui';
59

6-
const mockChartData = [
7-
{ name: 'Mon', volume: 45000, fee: 450 },
8-
{ name: 'Tue', volume: 52000, fee: 520 },
9-
{ name: 'Wed', volume: 38000, fee: 380 },
10-
{ name: 'Thu', volume: 61000, fee: 610 },
11-
{ name: 'Fri', volume: 59000, fee: 590 },
12-
{ name: 'Sat', volume: 72000, fee: 720 },
13-
{ name: 'Sun', volume: 68000, fee: 680 },
14-
];
10+
interface ChartDataItem {
11+
name: string;
12+
volume: number;
13+
fee: number;
14+
}
1515

1616
export default function PlatformVolumeChart({ height = 300 }: { height?: number }) {
17-
return (
18-
<div style={{ height, width: '100%' }}>
19-
<ResponsiveContainer width="100%" height="100%">
20-
<BarChart data={mockChartData}>
21-
<XAxis
22-
dataKey="name"
23-
stroke="hsl(var(--muted-foreground))"
24-
fontSize={12}
25-
tickLine={false}
26-
axisLine={false}
27-
/>
28-
<YAxis
29-
yAxisId="left"
30-
stroke="hsl(var(--muted-foreground))"
31-
fontSize={12}
32-
tickLine={false}
33-
axisLine={false}
34-
tickFormatter={(value) => `$${value/1000}k`}
35-
/>
36-
<Tooltip
37-
contentStyle={{ backgroundColor: 'hsl(var(--card))', borderColor: 'hsl(var(--border))', color: 'hsl(var(--foreground))' }}
38-
cursor={{ fill: 'hsl(var(--accent))' }}
39-
/>
40-
<Bar yAxisId="left" dataKey="volume" fill="hsl(var(--border))" radius={[4, 4, 0, 0]} />
41-
<Bar yAxisId="left" dataKey="fee" fill="hsl(var(--primary))" radius={[4, 4, 0, 0]} />
42-
import { useTheme } from "next-themes";
43-
import {
44-
ResponsiveContainer,
45-
BarChart,
46-
Bar,
47-
XAxis,
48-
YAxis,
49-
Tooltip,
50-
} from "recharts";
17+
const { resolvedTheme } = useTheme();
18+
const isDark = resolvedTheme === 'dark';
5119

52-
const mockChartData = [
53-
{ name: "Mon", volume: 45000, fee: 450 },
54-
{ name: "Tue", volume: 52000, fee: 520 },
55-
{ name: "Wed", volume: 38000, fee: 380 },
56-
{ name: "Thu", volume: 61000, fee: 610 },
57-
{ name: "Fri", volume: 59000, fee: 590 },
58-
{ name: "Sat", volume: 72000, fee: 720 },
59-
{ name: "Sun", volume: 68000, fee: 680 },
60-
];
20+
const { data, isLoading, isError } = useQuery<ChartDataItem[]>(
21+
['platform-volume'],
22+
async () => {
23+
const response = await axios.get<ChartDataItem[]>('/api/platform-volume');
24+
return response.data;
25+
}
26+
);
6127

62-
interface PlatformVolumeChartProps {
63-
height?: number;
64-
}
28+
if (isLoading) {
29+
return <Skeleton className="h-[300px] w-full rounded-xl" />;
30+
}
6531

66-
export default function PlatformVolumeChart({
67-
height = 300,
68-
}: PlatformVolumeChartProps) {
69-
const { resolvedTheme } = useTheme();
70-
const isDark = resolvedTheme === "dark";
32+
if (isError || !data) {
33+
return <p className="text-destructive">Failed to load platform volume data.</p>;
34+
}
7135

7236
return (
7337
<div className="w-full" style={{ height }}>
7438
<ResponsiveContainer width="100%" height="100%">
75-
<BarChart data={mockChartData}>
39+
<BarChart data={data}>
7640
<XAxis
7741
dataKey="name"
7842
stroke="var(--muted-foreground)"
@@ -90,24 +54,14 @@ export default function PlatformVolumeChart({
9054
/>
9155
<Tooltip
9256
contentStyle={{
93-
backgroundColor: isDark ? "var(--card)" : "var(--card)",
94-
borderColor: isDark ? "var(--border)" : "var(--border)",
95-
color: isDark ? "var(--foreground)" : "var(--foreground)",
57+
backgroundColor: isDark ? 'var(--card)' : 'var(--card)',
58+
borderColor: isDark ? 'var(--border)' : 'var(--border)',
59+
color: isDark ? 'var(--foreground)' : 'var(--foreground)',
9660
}}
97-
cursor={{ fill: "var(--accent)" }}
98-
/>
99-
<Bar
100-
yAxisId="left"
101-
dataKey="volume"
102-
fill="var(--border)"
103-
radius={[4, 4, 0, 0]}
104-
/>
105-
<Bar
106-
yAxisId="left"
107-
dataKey="fee"
108-
fill="var(--primary)"
109-
radius={[4, 4, 0, 0]}
61+
cursor={{ fill: 'var(--accent)' }}
11062
/>
63+
<Bar yAxisId="left" dataKey="volume" fill="var(--border)" radius={[4, 4, 0, 0]} />
64+
<Bar yAxisId="left" dataKey="fee" fill="var(--primary)" radius={[4, 4, 0, 0]} />
11165
</BarChart>
11266
</ResponsiveContainer>
11367
</div>

components/charts/RevenueChart.tsx

Lines changed: 41 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,18 @@
11
"use client";
22

3-
import React from 'react';
4-
import { useState, useEffect } from "react";
5-
import { cn } from "@/lib/utils";
6-
import { useTheme } from "next-themes";
7-
import {
8-
ResponsiveContainer,
9-
AreaChart,
10-
Area,
11-
XAxis,
12-
YAxis,
13-
Tooltip,
14-
CartesianGrid,
15-
} from 'recharts';
3+
import React, { useState, useEffect } from 'react';
4+
import { useQuery } from '@tanstack/react-query';
5+
import axios from 'axios';
6+
import { cn } from '@/lib/utils';
7+
import { useTheme } from 'next-themes';
8+
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts';
9+
import { Skeleton } from '@/components/ui';
1610

17-
const mockChartData = [
18-
{ name: 'Mon', total: 1200, volume: 8400 },
19-
{ name: 'Tue', total: 2100, volume: 14700 },
20-
{ name: 'Wed', total: 1800, volume: 12600 },
21-
{ name: 'Thu', total: 3200, volume: 22400 },
22-
{ name: 'Fri', total: 2800, volume: 19600 },
23-
{ name: 'Sat', total: 4100, volume: 28700 },
24-
{ name: 'Sun', total: 3800, volume: 26600 },
25-
];
26-
27-
interface TooltipProps { active?: boolean; payload?: { value: number }[]; label?: string; }
28-
29-
export const ChartTooltip = ({ active, payload, label }: TooltipProps) => {
30-
if (active && payload && payload.length) {
31-
return (
32-
<div className="bg-white border border-slate-200 rounded-xl p-3 shadow-lg text-sm">
33-
<p className="font-semibold text-slate-700 mb-1">{label}</p>
34-
<p className="text-amber-600 font-bold">${payload[0]?.value?.toLocaleString()}</p>
35-
} from "recharts";
36-
37-
const mockChartData = [
38-
{ name: "Mon", total: 1200, volume: 8400 },
39-
{ name: "Tue", total: 2100, volume: 14700 },
40-
{ name: "Wed", total: 1800, volume: 12600 },
41-
{ name: "Thu", total: 3200, volume: 22400 },
42-
{ name: "Fri", total: 2800, volume: 19600 },
43-
{ name: "Sat", total: 4100, volume: 28700 },
44-
{ name: "Sun", total: 3800, volume: 26600 },
45-
];
11+
interface ChartDataItem {
12+
name: string;
13+
total: number;
14+
volume: number;
15+
}
4616

4717
interface ChartTooltipProps {
4818
active?: boolean;
@@ -52,119 +22,60 @@ interface ChartTooltipProps {
5222

5323
const ChartTooltip = ({ active, payload, label }: ChartTooltipProps) => {
5424
const { resolvedTheme } = useTheme();
55-
const isDark = resolvedTheme === "dark";
56-
25+
const isDark = resolvedTheme === 'dark';
5726
if (active && payload && payload.length) {
5827
return (
59-
<div
60-
className="border rounded-xl p-3 shadow-lg text-sm"
61-
style={{
62-
backgroundColor: isDark ? "var(--card)" : "var(--card)",
63-
borderColor: isDark ? "var(--border)" : "var(--border)",
64-
}}
65-
>
66-
<p className="font-semibold mb-1" style={{ color: isDark ? "var(--foreground)" : "var(--foreground)" }}>{label}</p>
67-
<p className="font-bold" style={{ color: isDark ? "var(--primary)" : "var(--primary)" }}>
68-
${payload[0]?.value?.toLocaleString()}
69-
</p>
28+
<div className="border rounded-xl p-3 shadow-lg text-sm" style={{ backgroundColor: isDark ? 'var(--card)' : 'var(--card)', borderColor: isDark ? 'var(--border)' : 'var(--border)' }}>
29+
<p className="font-semibold mb-1" style={{ color: isDark ? 'var(--foreground)' : 'var(--foreground)' }}>{label}</p>
30+
<p className="font-bold" style={{ color: isDark ? 'var(--primary)' : 'var(--primary)' }}>${payload[0]?.value?.toLocaleString()}</p>
7031
</div>
7132
);
7233
}
7334
return null;
7435
};
7536

7637
export default function RevenueChart({ height = 260 }: { height?: number }) {
77-
return (
78-
<div style={{ height, width: '100%' }}>
79-
<ResponsiveContainer width="100%" height="100%">
80-
<AreaChart data={mockChartData} margin={{ top: 4, right: 4, bottom: 0, left: -16 }}>
81-
<defs>
82-
<linearGradient id="colorRevenue" x1="0" y1="0" x2="0" y2="1">
83-
<stop offset="5%" stopColor="#F0A500" stopOpacity={0.25} />
84-
<stop offset="95%" stopColor="#F0A500" stopOpacity={0} />
85-
</linearGradient>
86-
</defs>
87-
<CartesianGrid strokeDasharray="3 3" stroke="#F1F5F9" vertical={false} />
88-
<XAxis
89-
dataKey="name"
90-
stroke="#CBD5E1"
91-
fontSize={11}
92-
tickLine={false}
93-
axisLine={false}
94-
tick={{ fill: '#94A3B8' }}
95-
/>
96-
<YAxis
97-
stroke="#CBD5E1"
98-
interface RevenueChartProps {
99-
height?: number;
100-
}
101-
102-
export default function RevenueChart({ height = 260 }: RevenueChartProps) {
103-
const [isMobile, setIsMobile] = useState(false);
10438
const { resolvedTheme } = useTheme();
105-
const isDark = resolvedTheme === "dark";
39+
const isDark = resolvedTheme === 'dark';
40+
const { data, isLoading, isError } = useQuery<ChartDataItem[]>(
41+
['revenue'],
42+
async () => {
43+
const res = await axios.get<ChartDataItem[]>('/api/revenue');
44+
return res.data;
45+
}
46+
);
10647

48+
const [isMobile, setIsMobile] = useState(false);
10749
useEffect(() => {
108-
const mq = window.matchMedia("(max-width: 639px)");
50+
const mq = window.matchMedia('(max-width: 639px)');
10951
setIsMobile(mq.matches);
11052
const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);
111-
mq.addEventListener("change", handler);
112-
return () => mq.removeEventListener("change", handler);
53+
mq.addEventListener('change', handler);
54+
return () => mq.removeEventListener('change', handler);
11355
}, []);
11456

57+
if (isLoading) {
58+
return <Skeleton className="h-[260px] w-full rounded-xl" />;
59+
}
60+
if (isError || !data) {
61+
return <p className="text-destructive">Failed to load revenue data.</p>;
62+
}
63+
11564
return (
116-
<div className={cn("w-full", height ? `h-[${height}px]` : "h-64")} style={{ height }}>
65+
<div className={cn('w-full', height ? `h-[${height}px]` : 'h-64')} style={{ height }}>
11766
<ResponsiveContainer width="100%" height="100%">
118-
<AreaChart
119-
data={mockChartData}
120-
margin={{ top: 4, right: 4, bottom: 0, left: isMobile ? 0 : -16 }}
121-
>
67+
<AreaChart data={data} margin={{ top: 4, right: 4, bottom: 0, left: isMobile ? 0 : -16 }}>
12268
<defs>
12369
<linearGradient id="colorRevenue" x1="0" y1="0" x2="0" y2="1">
12470
<stop offset="5%" stopColor="var(--primary)" stopOpacity={isDark ? 0.4 : 0.25} />
12571
<stop offset="95%" stopColor="var(--primary)" stopOpacity={isDark ? 0.05 : 0} />
12672
</linearGradient>
12773
</defs>
128-
<CartesianGrid
129-
strokeDasharray="3 3"
130-
stroke="var(--border)"
131-
vertical={false}
132-
/>
133-
<XAxis
134-
dataKey="name"
135-
stroke="var(--muted-foreground)"
136-
fontSize={11}
137-
tickLine={false}
138-
axisLine={false}
139-
tick={{ fill: "var(--muted-foreground)" }}
140-
/>
141-
<YAxis
142-
stroke="var(--muted-foreground)"
143-
fontSize={11}
144-
tickLine={false}
145-
axisLine={false}
146-
tickFormatter={(v) => `$${v}`}
147-
tick={{ fill: '#94A3B8' }}
148-
tick={{ fill: "var(--muted-foreground)" }}
149-
/>
74+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" vertical={false} />
75+
<XAxis dataKey="name" stroke="var(--muted-foreground)" fontSize={11} tickLine={false} axisLine={false} tick={{ fill: 'var(--muted-foreground)' }} />
76+
<YAxis fontSize={11} tickLine={false} axisLine={false} tickFormatter={(v) => `$${v}`} tick={{ fill: 'var(--muted-foreground)' }} domain={['auto', 'auto']} />
15077
<Tooltip content={<ChartTooltip />} />
151-
<Area
152-
type="monotone"
153-
dataKey="total"
154-
stroke="#F0A500"
155-
stroke="var(--primary)"
156-
strokeWidth={2.5}
157-
fillOpacity={1}
158-
fill="url(#colorRevenue)"
159-
dot={false}
160-
activeDot={{ r: 5, fill: '#F0A500', stroke: '#fff', strokeWidth: 2 }}
161-
activeDot={{
162-
r: 5,
163-
fill: "var(--primary)",
164-
stroke: "var(--card)",
165-
strokeWidth: 2,
166-
}}
167-
/>
78+
<Area type="monotone" dataKey="total" stroke="var(--primary)" strokeWidth={2.5} fillOpacity={1} fill="url(#colorRevenue)" dot={false} activeDot={{ r: 5, fill: 'var(--primary)', stroke: 'var(--card)', strokeWidth: 2 }} />
16879
</AreaChart>
16980
</ResponsiveContainer>
17081
</div>

0 commit comments

Comments
 (0)