1- import { useEffect , useState } from "react" ;
2-
3- const groupNames : String [ ] = [ "" , "Group A" , "Group B" , "Group C" ] ;
4-
5- export default function List ( { data = [ ] } ) {
6- // Mock data for development (with four teams per group)
7- const mockData = [
8- // Group A teams
9- {
10- team_name : "Drone Rangers" ,
11- honor_points : 350 ,
12- regular_points : 420 ,
13- total_time_seconds : 154 ,
14- group : 1 ,
15- } ,
16- {
17- team_name : "RoboPilots" ,
18- honor_points : 290 ,
19- regular_points : 365 ,
20- total_time_seconds : 234 ,
21- group : 1 ,
22- } ,
23- {
24- team_name : "Airborne Coders" ,
25- honor_points : 210 ,
26- regular_points : 280 ,
27- total_time_seconds : 163 ,
28- group : 1 ,
29- } ,
30- {
31- team_name : "Flying Algorithms" ,
32- honor_points : 180 ,
33- regular_points : 240 ,
34- total_time_seconds : 274 ,
35- group : 1 ,
36- } ,
37-
38- // Group B teams
39- {
40- team_name : "Sky Navigators" ,
41- honor_points : 320 ,
42- regular_points : 380 ,
43- total_time_seconds : 198 ,
44- group : 2 ,
45- } ,
46- {
47- team_name : "Circuit Flyers" ,
48- honor_points : 250 ,
49- regular_points : 310 ,
50- total_time_seconds : 193 ,
51- group : 2 ,
52- } ,
53- {
54- team_name : "Byte Squadron" ,
55- honor_points : 220 ,
56- regular_points : 290 ,
57- total_time_seconds : 198 ,
58- group : 2 ,
59- } ,
60- {
61- team_name : "Quantum Wings" ,
62- honor_points : 190 ,
63- regular_points : 250 ,
64- total_time_seconds : 183 ,
65- group : 2 ,
66- } ,
67-
68- // Group C teams
69- {
70- team_name : "Aerial Mechanics" ,
71- honor_points : 270 ,
72- regular_points : 340 ,
73- total_time_seconds : 348 ,
74- group : 3 ,
75- } ,
76- {
77- team_name : "PropCoders" ,
78- honor_points : 240 ,
79- regular_points : 300 ,
80- total_time_seconds : 314 ,
81- group : 3 ,
82- } ,
83- {
84- team_name : "Binary Flyers" ,
85- honor_points : 200 ,
86- regular_points : 270 ,
87- total_time_seconds : 109 ,
88- group : 3 ,
89- } ,
90- {
91- team_name : "Aerodynamic Devs" ,
92- honor_points : 170 ,
93- regular_points : 230 ,
94- total_time_seconds : 54 ,
95- group : 3 ,
96- } ,
97- ] ;
98-
99- // TODO: Fetch real data from api/match/group
100-
101- // Use provided data or fallback to mock data
102- const rawData = data . length > 0 ? data : mockData ;
103-
104- // Get unique groups
105- const groupIds = [ ...new Set ( rawData . map ( ( item ) => item . group ) ) ] ;
1+ "use client" ;
1062
107- function processData ( rawData : Array < any > ) {
108- let teamsData : Array < any > = [ ] ;
109-
110- // For each group
111- for ( let i = 0 ; i < groupIds . length ; i ++ ) {
112- // Get all the teams in that group
113- let groupData = rawData . filter ( ( team ) => {
114- return team . group === groupIds [ i ] ;
115- } ) ;
116-
117- // Sort them descending by points
118- groupData . sort ( ( teamA , teamB ) => {
119- return teamB . regular_points - teamA . regular_points ;
120- // TODO: Implement sorting by honor points in case of draw, then by time to finish
121- } ) ;
3+ import { useEffect , useState } from "react" ;
1224
123- // Add each of those teams into the teamData with calculated fields
124- for ( let j = 0 ; j < groupData . length ; j ++ ) {
125- let team = groupData [ j ] ;
126- team . group = groupNames [ team . group ] ;
127- team . group_rank = j + 1 ; // TODO: Make resistant to ties
128- team . qualified = team . group_rank <= 2 ; // TODO: Make resistant to ties
129- team . mission_time =
130- "" +
131- Math . floor ( team . total_time_seconds / 60 ) +
132- ":" +
133- ( team . total_time_seconds % 60 ) ;
134- teamsData . push ( team ) ;
5+ const API_BASE = process . env . NEXT_PUBLIC_BACKEND_URL ;
6+
7+ type TeamResult = {
8+ team_name : string ;
9+ honor_point : number ;
10+ point : number ;
11+ completed_time_second : number ;
12+ group : number ;
13+ group_rank : number ;
14+ qualified : boolean ;
15+ } ;
16+
17+ export default function List ( ) {
18+ const [ groups , setGroups ] = useState < number [ ] > ( [ ] ) ;
19+ const [ selectedGroup , setSelectedGroup ] = useState < number | null > ( null ) ;
20+ const [ rawData , setRawData ] = useState < TeamResult [ ] > ( [ ] ) ;
21+ const [ loading , setLoading ] = useState < boolean > ( false ) ;
22+
23+ // Fetch group list on first load
24+ useEffect ( ( ) => {
25+ async function fetchGroups ( ) {
26+ try {
27+ const res = await fetch ( `${ API_BASE } /details/groups/` ) ;
28+ const data : number [ ] = await res . json ( ) ;
29+ setGroups ( data ) ;
30+ if ( data . length > 0 ) {
31+ setSelectedGroup ( data [ 0 ] ) ;
32+ }
33+ } catch ( err ) {
34+ console . error ( "Failed to fetch groups:" , err ) ;
13535 }
13636 }
137- return teamsData ;
138- }
139-
140- // Add calculated fields group_rank and qualified to the data
141- const teamsData = processData ( rawData ) ;
14237
143- // Get unique groups for dropdown
144- const groups = [ ...new Set ( teamsData . map ( ( item ) => item . group ) ) ] ;
145-
146- // Initialize selectedGroup with the first group instead of "All Groups"
147- const [ selectedGroup , setSelectedGroup ] = useState ( groups [ 0 ] || 1 ) ;
38+ fetchGroups ( ) ;
39+ } , [ ] ) ;
40+
41+ // Fetch leaderboard for selected group
42+ useEffect ( ( ) => {
43+ if ( selectedGroup === null ) return ;
44+
45+ async function fetchLeaderboard ( ) {
46+ setLoading ( true ) ;
47+ try {
48+ const res = await fetch (
49+ `${ API_BASE } /details/group_leaderboard/${ selectedGroup } /` ,
50+ ) ;
51+ const data = await res . json ( ) ;
52+ setRawData ( data ) ;
53+ } catch ( err ) {
54+ console . error ( "Failed to fetch leaderboard:" , err ) ;
55+ } finally {
56+ setLoading ( false ) ;
57+ }
58+ }
14859
149- // Filter data for the selected group only
150- const displayData = teamsData . filter ( ( item ) => item . group === selectedGroup ) ;
60+ fetchLeaderboard ( ) ;
61+ } , [ selectedGroup ] ) ;
15162
152- const handleGroupChange = ( e : any ) => {
153- setSelectedGroup ( e . target . value ) ;
63+ const handleGroupChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
64+ const newGroup = parseInt ( e . target . value , 10 ) ;
65+ setSelectedGroup ( newGroup ) ;
15466 } ;
15567
15668 return (
@@ -167,58 +79,73 @@ export default function List({ data = [] }) {
16779 </ label >
16880 < select
16981 id = "groupSelect"
170- value = { selectedGroup }
82+ value = { selectedGroup ?? "" }
17183 onChange = { handleGroupChange }
17284 className = "body-sm rounded-md border border-gray-300 bg-white px-4 py-2 pt-3 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary"
17385 >
174- { groups . map ( ( group ) => (
175- < option key = { group } value = { group } >
176- { group }
86+ < option value = "" disabled >
87+ -- Select --
88+ </ option >
89+ { groups . map ( ( groupId ) => (
90+ < option key = { groupId } value = { groupId } >
91+ Group { groupId }
17792 </ option >
17893 ) ) }
17994 </ select >
18095 </ div >
18196 </ div >
18297
183- < table className = "min-w-full bg-white" >
184- < thead >
185- < tr className = "title bg-primary text-center font-bold text-white" >
186- < th className = "px-6 py-4 md:text-lg" > Group Rank</ th >
187- < th className = "px-6 py-4 md:text-lg" > Team</ th >
188- < th className = "px-6 py-4 md:text-lg" > Honour Points</ th >
189- < th className = "px-6 py-4 md:text-lg" > Regular Points</ th >
190- < th className = "px-6 py-4 md:text-lg" > Mission Time</ th >
191- < th className = "px-6 py-4 md:text-lg" > Status</ th >
192- </ tr >
193- </ thead >
194- < tbody className = "body-sm" >
195- { displayData . map ( ( row , index ) => (
196- < tr
197- key = { index }
198- className = { `border-b border-gray-200 text-center hover:bg-gray-100 ${
199- row . qualified ? "bg-green-50" : ""
200- } `}
201- >
202- < td className = "body-lg px-6 py-4 font-bold italic" >
203- { row . group_rank }
204- </ td >
205- < td className = "px-6 py-4" > { row . team_name } </ td >
206- < td className = "px-6 py-4" > { row . honor_points } </ td >
207- < td className = "px-6 py-4" > { row . regular_points } </ td >
208- < td className = "px-6 py-4" > { row . mission_time } </ td >
209- < td className = "px-6 py-4" >
210- { row . qualified ? (
211- < span className = "font-medium text-green-600" >
212- Qualified
213- </ span >
214- ) : (
215- < span className = "text-gray-500" > Not Qualified</ span >
216- ) }
217- </ td >
98+ { loading ? (
99+ < p className = "py-8 text-center" > Loading leaderboard...</ p >
100+ ) : (
101+ < table className = "min-w-full bg-white" >
102+ < thead >
103+ < tr className = "title bg-primary text-center text-xs font-bold text-white sm:text-sm md:text-base" >
104+ < th className = "md:text-l px-6 py-4" > Group Rank</ th >
105+ < th className = "px-6 py-4 md:text-lg" > Team</ th >
106+ < th className = "px-6 py-4 md:text-lg" > Honour Points</ th >
107+ < th className = "px-6 py-4 md:text-lg" > Regular Points</ th >
108+ < th className = "px-6 py-4 md:text-lg" > Mission Time</ th >
109+ < th className = "px-6 py-4 md:text-lg" > Status</ th >
218110 </ tr >
219- ) ) }
220- </ tbody >
221- </ table >
111+ </ thead >
112+ < tbody className = "body-sm" >
113+ { rawData . length === 0 ? (
114+ < tr >
115+ < td colSpan = { 6 } className = "py-6 text-center text-gray-400" >
116+ No results yet
117+ </ td >
118+ </ tr >
119+ ) : (
120+ rawData . map ( ( row , index ) => (
121+ < tr
122+ key = { index }
123+ className = { `border-b border-gray-200 text-center hover:bg-gray-100 ${
124+ row . qualified ? "bg-green-50" : ""
125+ } `}
126+ >
127+ < td className = "body-lg px-6 py-4 font-bold italic" >
128+ { row . group_rank }
129+ </ td >
130+ < td className = "px-6 py-4" > { row . team_name } </ td >
131+ < td className = "px-6 py-4" > { row . honor_point } </ td >
132+ < td className = "px-6 py-4" > { row . point } </ td >
133+ < td className = "px-6 py-4" > { row . completed_time_second } </ td >
134+ < td className = "px-6 py-4" >
135+ { row . qualified ? (
136+ < span className = "font-medium text-green-600" >
137+ Qualified
138+ </ span >
139+ ) : (
140+ < span className = "text-gray-500" > Not Qualified</ span >
141+ ) }
142+ </ td >
143+ </ tr >
144+ ) )
145+ ) }
146+ </ tbody >
147+ </ table >
148+ ) }
222149 </ div >
223150 </ div >
224151 ) ;
0 commit comments