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
4717interface ChartTooltipProps {
4818 active ?: boolean ;
@@ -52,119 +22,60 @@ interface ChartTooltipProps {
5222
5323const 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
7637export 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