@@ -14,15 +14,15 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
1414import { useToast } from "@/hooks/use-toast" ;
1515import MarkdownViewer from '@/components/shared/markdown-viewer' ;
1616import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from '@/components/ui/table' ;
17- import { ResponsiveContainer , LineChart as RechartsLineChart , CartesianGrid , XAxis , YAxis , Tooltip , Legend , Line } from 'recharts' ;
1817import { Avatar , AvatarFallback , AvatarImage } from '@/components/ui/avatar' ;
1918import { HoverCard , HoverCardContent , HoverCardTrigger } from '@/components/ui/hover-card' ;
2019import { UserProfileCard } from '@/components/shared/user-profile-card' ;
2120import { getInitials } from '@/lib/utils' ;
21+ import EchartsTrendChart from '@/components/charts/echarts-trend-chart' ;
2222
2323const fetcher = ( url : string ) => api . get ( url ) . then ( res => res . data . data ) ;
2424
25- // --- ContestList (no changes) ---
25+ // --- ContestList ---
2626function ContestList ( ) {
2727 const { data : contests , error, isLoading } = useSWR < Record < string , Contest > > ( '/contests' , fetcher ) ;
2828
@@ -125,52 +125,21 @@ function ContestProblems({ contestId }: { contestId: string }) {
125125 ) ;
126126}
127127
128- const COLORS = [ '#8884d8' , '#82ca9d' , '#ffc658' , '#ff8042' , '#0088FE' , '#00C49F' , '#FFBB28' , '#FF8042' ] ;
129-
130128function ContestTrend ( { contestId } : { contestId : string } ) {
131129 const { data : trendData , error, isLoading } = useSWR < TrendEntry [ ] > ( `/contests/${ contestId } /trend` , fetcher , { refreshInterval : 30000 } ) ;
132130
133131 if ( isLoading ) return < Skeleton className = "h-96 w-full" /> ;
134132 if ( error ) return < div > Failed to load trend data.</ div > ;
135133 if ( ! trendData || trendData . length === 0 ) return < div > No trend data available yet.</ div > ;
136134
137- const allTimePoints = new Set < number > ( ) ;
138- trendData . forEach ( user => {
139- user . history . forEach ( point => {
140- allTimePoints . add ( new Date ( point . time ) . getTime ( ) ) ;
141- } ) ;
142- } ) ;
143-
144- const sortedTimePoints = Array . from ( allTimePoints ) . sort ( ) ;
145-
146- const chartData = sortedTimePoints . map ( time => {
147- const dataPoint : { [ key : string ] : any } = { time : format ( new Date ( time ) , 'HH:mm:ss' ) } ;
148- trendData . forEach ( user => {
149- const lastPoint = [ ...user . history ] . reverse ( ) . find ( p => new Date ( p . time ) . getTime ( ) <= time ) ;
150- dataPoint [ user . nickname ] = lastPoint ? lastPoint . score : 0 ;
151- } ) ;
152- return dataPoint ;
153- } ) ;
154-
155135 return (
156136 < Card >
157137 < CardHeader >
158138 < CardTitle > Score Trend</ CardTitle >
159139 < CardDescription > Score progression of top users over time.</ CardDescription >
160140 </ CardHeader >
161141 < CardContent className = "h-96 w-full" >
162- < ResponsiveContainer >
163- < RechartsLineChart data = { chartData } margin = { { top : 5 , right : 20 , left : - 10 , bottom : 5 } } >
164- < CartesianGrid strokeDasharray = "3 3" />
165- < XAxis dataKey = "time" />
166- < YAxis />
167- < Tooltip />
168- < Legend />
169- { trendData . map ( ( user , index ) => (
170- < Line key = { user . user_id } type = "stepAfter" dataKey = { user . nickname } stroke = { COLORS [ index % COLORS . length ] } strokeWidth = { 2 } dot = { false } />
171- ) ) }
172- </ RechartsLineChart >
173- </ ResponsiveContainer >
142+ < EchartsTrendChart trendData = { trendData } />
174143 </ CardContent >
175144 </ Card >
176145 ) ;
@@ -220,7 +189,7 @@ function LeaderboardRow({ entry, rank, problemIds }: { entry: LeaderboardEntry,
220189}
221190
222191
223- // --- UPDATED ContestLeaderboard component ---
192+ // --- ContestLeaderboard component ---
224193function ContestLeaderboard ( { contestId } : { contestId : string } ) {
225194 // Fetch contest details to get the problem IDs in order
226195 const { data : contest , error : contestError , isLoading : isContestLoading } = useSWR < Contest > ( `/contests/${ contestId } ` , fetcher ) ;
@@ -292,7 +261,7 @@ function ContestDetailView({ contestId, view }: { contestId: string, view: strin
292261 toast ( { variant : "destructive" , title : "Registration Failed" , description : error . response ?. data ?. message || "An unexpected error occurred." } ) ;
293262 }
294263 } ;
295-
264+
296265 const now = new Date ( ) ;
297266 const canRegister = contest && now >= new Date ( contest . starttime ) && now <= new Date ( contest . endtime ) ;
298267
@@ -313,20 +282,24 @@ function ContestDetailView({ contestId, view }: { contestId: string, view: strin
313282 ) }
314283 </ div >
315284 < Tabs value = { view } className = "w-full" >
316- < TabsList className = "grid w-full grid-cols-3 " >
285+ < TabsList className = "grid w-full grid-cols-2 " >
317286 < TabsTrigger value = "problems" asChild >
318287 < Link href = { `/contests?id=${ contestId } &view=problems` } > Problems</ Link >
319288 </ TabsTrigger >
320289 < TabsTrigger value = "leaderboard" asChild >
321290 < Link href = { `/contests?id=${ contestId } &view=leaderboard` } > Leaderboard</ Link >
322291 </ TabsTrigger >
323- < TabsTrigger value = "trend" asChild >
324- < Link href = { `/contests?id=${ contestId } &view=trend` } > Trend</ Link >
325- </ TabsTrigger >
326292 </ TabsList >
327293 </ Tabs >
328294 < div className = "mt-6" >
329- { view === 'leaderboard' ? < ContestLeaderboard contestId = { contestId } /> : view === 'trend' ? < ContestTrend contestId = { contestId } /> : < ContestProblems contestId = { contestId } /> }
295+ { view === 'leaderboard' ? (
296+ < div className = "space-y-6" >
297+ < ContestTrend contestId = { contestId } />
298+ < ContestLeaderboard contestId = { contestId } />
299+ </ div >
300+ ) : (
301+ < ContestProblems contestId = { contestId } />
302+ ) }
330303 </ div >
331304 </ div >
332305 ) ;
0 commit comments