Skip to content

Commit 262196b

Browse files
committed
2 parents 605dff4 + 3ff07bf commit 262196b

File tree

10 files changed

+357
-382
lines changed

10 files changed

+357
-382
lines changed

src/app/admin/page.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import TotalParticipants from "@/components/Dashboard/TotalParticipants";
88
import TotalTeams from "@/components/Dashboard/TotalTeams";
99
import { SuspenseWrapper } from "@/components/SuspenseWrapper";
1010

11+
export const dynamic = "force-dynamic";
12+
13+
export const revalidate = 0;
1114
export const metadata: Metadata = {
1215
title: "Hack the Change - Admin",
1316
description: "Hack the Change Admin Portal",
@@ -40,7 +43,9 @@ export default function page() {
4043
<NumFoodTickets />
4144
</SuspenseWrapper>
4245
</div>
43-
<TeamRankings />
46+
<SuspenseWrapper>
47+
<TeamRankings />
48+
</SuspenseWrapper>
4449
</div>
4550
</CheckUserLoggedIn>
4651
</div>

src/app/judging/JudgingDashboard.tsx

+15-185
Original file line numberDiff line numberDiff line change
@@ -1,191 +1,21 @@
1-
import { generateClient } from "aws-amplify/data";
2-
import Image from "next/image";
3-
import { useState } from "react";
1+
import Greetings from "@/components/Dashboard/Greetings";
2+
import client from "@/components/_Amplify/AmplifyBackendClient";
43

5-
import { type Schema } from "@/amplify/data/resource";
6-
import LoadingRing from "@/components/LoadingRing";
7-
import { useUser } from "@/components/contexts/UserContext";
8-
import ModalPopup from "@/components/judging/ModalPopup";
9-
import ScoresTable from "@/components/judging/ScoresTable";
10-
import StatsPanel from "@/components/judging/StatsPanel";
11-
import { useQuery } from "@tanstack/react-query";
4+
import JudgingTable from "./JudgingTable";
125

13-
const pink_underlines = "/svgs/judging/pink_underline.svg";
14-
15-
const LOADING_SCREEN_STYLES =
16-
"flex h-screen w-full items-center justify-center bg-pastel-pink";
17-
18-
const JUDGE_DASHBOARD_PAGE_STYLES =
19-
"flex justify-center text-blackish h-screen";
20-
const JUDGE_DASHBOARD_CONTENT_STYLES = "w-full max-w-[1500px] p-6";
21-
22-
const JUDGE_DASHBOARD_HELLO_TILE_STYLES =
23-
"mb-6 flex rounded-lg bg-white p-8 pb-6 text-4xl font-semibold drop-shadow-md";
24-
25-
const SUBHEADER_TEXT_STYLES = "mb-4 text-xl font-semibold";
26-
27-
const client = generateClient<Schema>();
28-
29-
const JudgingDashboard = () => {
30-
const [selectedTeam, setSelectedTeamId] = useState("");
31-
32-
const { currentUser } = useUser();
33-
34-
const { data: roomData, isFetching: roomIsFetching } = useQuery({
35-
queryKey: ["RoomForJudge", currentUser.JUDGE_roomId],
36-
queryFn: async () => {
37-
const { data, errors } = await client.models.Room.get({
38-
id: currentUser.JUDGE_roomId,
39-
});
40-
console.log(currentUser);
41-
console.log(data);
42-
if (errors) throw Error(errors[0].message);
43-
44-
return data;
45-
},
46-
});
47-
48-
const { data: hackathonData, isFetching: hackathonIsFetching } = useQuery({
49-
queryKey: ["Hackathon"],
50-
queryFn: async () => {
51-
const { data, errors } = await client.models.Hackathon.list();
52-
if (errors) throw Error(errors[0].message);
53-
54-
return data[0];
55-
},
56-
});
57-
58-
const { data: teamsForRoomData, isFetching: teamsForRoomIsFetching } =
59-
useQuery({
60-
queryKey: ["TeamsForRoom", roomData?.id],
61-
queryFn: async () => {
62-
const teamRooms = (await roomData?.teamRoom())?.data;
63-
if (!teamRooms) return [];
64-
const teams = await Promise.all(
65-
teamRooms.map(async (teamRoom) => (await teamRoom.team()).data),
66-
);
67-
if (!teams) return [];
68-
69-
return teams;
70-
},
71-
});
72-
73-
if (!hackathonData || !teamsForRoomData || !roomData) return;
74-
75-
const tableHeaders = [
76-
{ columnHeader: "Team Name", className: "w-1/3 rounded-tl-lg" },
77-
...hackathonData.scoringComponents.map((component) => ({
78-
columnHeader: component.friendlyName,
79-
className: "w-fit",
80-
})),
81-
...hackathonData.scoringSidepots.map((component) => ({
82-
columnHeader: (
83-
<div className="flex flex-col">
84-
<p>Sidepot:</p>
85-
{component.friendlyName}
86-
</div>
87-
),
88-
className: "w-fit bg-pastel-pink",
89-
})),
90-
];
91-
92-
const panelData = [
93-
{
94-
icon: "/svgs/judging/team_icon.svg",
95-
alt: "Teams assigned icon",
96-
stat: teamsForRoomData.length,
97-
text: `Teams Assigned to ${roomData.name}`,
98-
},
99-
{
100-
icon: "/svgs/judging/teams_left.svg",
101-
alt: "Teams left icon",
102-
stat: teamsForRoomData.filter(
103-
async (team) =>
104-
(await team?.scores())?.data.filter(
105-
(score) => score.judgeId === currentUser.username,
106-
).length === 0,
107-
).length,
108-
text: "Teams Left To Score",
109-
},
110-
];
111-
112-
const isFetching =
113-
roomIsFetching || hackathonIsFetching || teamsForRoomIsFetching;
114-
115-
const handleCreateScoreClick = (teamId: string) => {
116-
setSelectedTeamId(teamId);
117-
};
118-
119-
const handleEditScoreClick = (teamId: string) => {
120-
setSelectedTeamId(teamId);
121-
};
122-
123-
const closeModal = () => {
124-
setSelectedTeamId("");
6+
export default async function JudgingDashboard() {
7+
const { data: hackathons } = await client.models.Hackathon.list();
8+
const hackathonData = {
9+
scoringComponents: hackathons[0].scoringComponents,
10+
scoringSidepots: hackathons[0].scoringSidepots,
11+
id: hackathons[0].id,
12512
};
12613

12714
return (
128-
<>
129-
{isFetching ? (
130-
<div className={LOADING_SCREEN_STYLES}>
131-
<LoadingRing />
132-
</div>
133-
) : (
134-
<div className={JUDGE_DASHBOARD_PAGE_STYLES}>
135-
<div className={JUDGE_DASHBOARD_CONTENT_STYLES}>
136-
<div className={JUDGE_DASHBOARD_HELLO_TILE_STYLES}>
137-
<h1>Hello,</h1>
138-
<div className="ml-2">
139-
<h1 className="text-dark-pink">
140-
<i>Judge!</i>
141-
</h1>
142-
<div className="mt-2 flex justify-center">
143-
<Image
144-
src={pink_underlines}
145-
height={100}
146-
width={80}
147-
alt="Pink underlines"
148-
/>
149-
</div>
150-
</div>
151-
</div>
152-
<h2 className={SUBHEADER_TEXT_STYLES}>Assigned Teams</h2>
153-
<div className="mb-4 flex">
154-
<div className="mr-4 flex w-1/4 flex-col space-y-4">
155-
{panelData.map((item, index) => (
156-
<div key={index} className="h-1/2">
157-
<StatsPanel
158-
icon={item.icon}
159-
alt={item.alt}
160-
stat={item.stat}
161-
subheader={item.text}
162-
/>
163-
</div>
164-
))}
165-
</div>
166-
<div className="w-3/4">
167-
<ScoresTable
168-
tableHeaders={tableHeaders}
169-
tableData={teamsForRoomData as Schema["Team"]["type"][]}
170-
onCreateScoreClick={handleCreateScoreClick}
171-
onEditScoreClick={handleEditScoreClick}
172-
colorScheme="pink"
173-
entriesPerPage={150}
174-
/>
175-
</div>
176-
</div>
177-
</div>
178-
{selectedTeam !== "" && (
179-
<ModalPopup
180-
hackathon={hackathonData}
181-
onClose={closeModal}
182-
teamId={selectedTeam}
183-
/>
184-
)}
185-
</div>
186-
)}
187-
</>
15+
<div className="flex w-full flex-1 flex-col items-center p-6 text-blackish">
16+
<Greetings accentColor="text-dark-pink" />
17+
<h2 className="flex w-full py-4 text-xl font-semibold">Assigned Teams</h2>
18+
<JudgingTable hackathonData={hackathonData} />
19+
</div>
18820
);
189-
};
190-
191-
export default JudgingDashboard;
21+
}

src/app/judging/JudgingTable.tsx

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import Skeleton from "react-loading-skeleton";
5+
6+
import type { Schema } from "@/amplify/data/resource";
7+
import LoadingRing from "@/components/LoadingRing";
8+
import { useUser } from "@/components/contexts/UserContext";
9+
import ModalPopup from "@/components/judging/ModalPopup";
10+
import ScoresTable from "@/components/judging/ScoresTable";
11+
import StatsPanel from "@/components/judging/StatsPanel";
12+
import { useQuery } from "@tanstack/react-query";
13+
14+
import { client } from "../QueryProvider";
15+
16+
export default function JudgingTable({
17+
hackathonData,
18+
}: {
19+
hackathonData: Pick<
20+
Schema["Hackathon"]["type"],
21+
"id" | "scoringComponents" | "scoringSidepots"
22+
>;
23+
}) {
24+
const [selectedTeam, setSelectedTeamId] = useState("");
25+
26+
const { currentUser } = useUser();
27+
const { data: roomData, isFetching: roomIsFetching } = useQuery({
28+
queryKey: ["RoomForJudge", currentUser.JUDGE_roomId],
29+
queryFn: async () => {
30+
const { data, errors } = await client.models.Room.get({
31+
id: currentUser.JUDGE_roomId,
32+
});
33+
if (errors) throw Error(errors[0].message);
34+
35+
return data;
36+
},
37+
});
38+
const { data: teamsForRoomData, isFetching: teamsForRoomIsFetching } =
39+
useQuery({
40+
queryKey: ["TeamsForRoom", roomData?.id],
41+
queryFn: async () => {
42+
const teamRooms = (await roomData?.teamRoom())?.data;
43+
if (!teamRooms) return [];
44+
const teams = await Promise.all(
45+
teamRooms.map(async (teamRoom) => (await teamRoom.team()).data),
46+
);
47+
if (!teams) return [];
48+
49+
return teams;
50+
},
51+
});
52+
const isFetching = roomIsFetching || teamsForRoomIsFetching;
53+
if (isFetching || !roomData || !teamsForRoomData) {
54+
return (
55+
<div className="flex flex-1">
56+
<Skeleton className="h-full" containerClassName="flex-1" />
57+
</div>
58+
);
59+
}
60+
61+
const panelData = [
62+
{
63+
icon: "/svgs/judging/team_icon.svg",
64+
alt: "Teams assigned icon",
65+
stat: teamsForRoomData.length,
66+
text: `Teams Assigned to ${roomData.name}`,
67+
},
68+
{
69+
icon: "/svgs/judging/teams_left.svg",
70+
alt: "Teams left icon",
71+
stat: teamsForRoomData.filter(
72+
async (team) =>
73+
(await team?.scores())?.data.filter(
74+
(score) => score.judgeId === currentUser.username,
75+
).length === 0,
76+
).length,
77+
text: "Teams Left To Score",
78+
},
79+
];
80+
const handleCreateScoreClick = (teamId: string) => {
81+
setSelectedTeamId(teamId);
82+
};
83+
const handleEditScoreClick = (teamId: string) => {
84+
setSelectedTeamId(teamId);
85+
};
86+
87+
const closeModal = () => {
88+
setSelectedTeamId("");
89+
};
90+
91+
return isFetching ? (
92+
<div className="flex size-full flex-1 items-center justify-center bg-pastel-pink">
93+
<LoadingRing />
94+
</div>
95+
) : (
96+
<>
97+
<div className="flex w-full flex-col justify-center gap-4 py-6 xl:flex-row">
98+
<div className=" flex w-full flex-row gap-4 xl:w-1/4 xl:flex-col">
99+
{panelData.map((item, index) => (
100+
<StatsPanel
101+
key={index}
102+
icon={item.icon}
103+
alt={item.alt}
104+
stat={item.stat}
105+
subheader={item.text}
106+
/>
107+
))}
108+
</div>
109+
<ScoresTable
110+
tableData={teamsForRoomData as Schema["Team"]["type"][]}
111+
onCreateScoreClick={handleCreateScoreClick}
112+
onEditScoreClick={handleEditScoreClick}
113+
colorScheme="pink"
114+
entriesPerPage={150}
115+
hackathonData={hackathonData}
116+
/>
117+
</div>
118+
{selectedTeam !== "" && (
119+
<ModalPopup
120+
hackathon={hackathonData}
121+
onClose={closeModal}
122+
teamId={selectedTeam}
123+
/>
124+
)}
125+
</>
126+
);
127+
}

src/app/judging/page.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
1-
"use client";
1+
import type { Metadata } from "next";
22

33
import JudgingDashboard from "./JudgingDashboard";
44

5+
export const dynamic = "force-dynamic";
6+
7+
export const revalidate = 0;
8+
export const metadata: Metadata = {
9+
title: "Hack the Change - Judging",
10+
description: "Hack the Change Juding Portal",
11+
icons: [
12+
{
13+
rel: "icon",
14+
type: "image/ico",
15+
sizes: "32x32",
16+
url: "/favicon.ico",
17+
},
18+
],
19+
};
520
export default function Judging() {
621
return (
7-
<main className="w-full bg-dashboard-grey">
22+
<main className="flex w-full flex-1 flex-col gap-4 bg-dashboard-grey">
823
<JudgingDashboard />
924
</main>
1025
);

0 commit comments

Comments
 (0)