Skip to content

Commit 35f2b50

Browse files
authored
Merge pull request #60 from codersforcauses/connect-to-backend
59-connect-leaderboard-and-matchdetails-to-backend
2 parents a56e38d + b115e91 commit 35f2b50

10 files changed

Lines changed: 465 additions & 315 deletions

File tree

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
export type MatchDetailsData = {
22
teamName: string;
3-
opponent: string;
43
whitePins: number;
54
penaltyPins: number;
65
yellowCard: number;
76
redCard: number;
8-
p1_postion: number;
9-
p2_postion: number;
7+
p1_position: number;
8+
p2_position: number;
109
honorPoint: number;
11-
regularPoint: number;
10+
point: number;
1211
completedTime: number;
12+
13+
matchId: number;
14+
matchName: string;
15+
group: number;
16+
stage: number;
1317
};

client/src/components/ui/Leaderboard/List.tsx

Lines changed: 115 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,68 @@
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

Comments
 (0)