Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions src/app/judging/JudgingDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@ export default async function JudgingDashboard() {
};

return (
<>
<div className={"flex h-screen justify-center text-blackish"}>
<div className={"w-full max-w-[1500px] p-6"}>
<Greetings accentColor="text-dark-pink" />
<h2 className={"py-4 text-xl font-semibold"}>Assigned Teams</h2>
<JudgingTable hackathonData={hackathonData} />
</div>
</div>
</>
<div className="flex w-full flex-1 flex-col items-center p-6 text-blackish">
<Greetings accentColor="text-dark-pink" />
<h2 className="flex w-full py-4 text-xl font-semibold">Assigned Teams</h2>
<JudgingTable hackathonData={hackathonData} />
</div>
);
}
122 changes: 48 additions & 74 deletions src/app/judging/JudgingTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useMemo, useState } from "react";
import { useState } from "react";
import Skeleton from "react-loading-skeleton";

import type { Schema } from "@/amplify/data/resource";
import LoadingRing from "@/components/LoadingRing";
Expand Down Expand Up @@ -48,36 +49,34 @@ export default function JudgingTable({
return teams;
},
});
if (
roomIsFetching ||
teamsForRoomIsFetching ||
!roomData ||
!teamsForRoomData
) {
return <div>Loading...</div>;
const isFetching = roomIsFetching || teamsForRoomIsFetching;
if (isFetching || !roomData || !teamsForRoomData) {
return (
<div className="flex flex-1">
<Skeleton className="h-full" containerClassName="flex-1" />
</div>
);
}

const panelData = useMemo(() => {
return [
{
icon: "/svgs/judging/team_icon.svg",
alt: "Teams assigned icon",
stat: teamsForRoomData.length,
text: `Teams Assigned to ${roomData.name}`,
},
{
icon: "/svgs/judging/teams_left.svg",
alt: "Teams left icon",
stat: teamsForRoomData.filter(
async (team) =>
(await team?.scores())?.data.filter(
(score) => score.judgeId === currentUser.username,
).length === 0,
).length,
text: "Teams Left To Score",
},
];
}, [roomData, teamsForRoomData]);
const panelData = [
{
icon: "/svgs/judging/team_icon.svg",
alt: "Teams assigned icon",
stat: teamsForRoomData.length,
text: `Teams Assigned to ${roomData.name}`,
},
{
icon: "/svgs/judging/teams_left.svg",
alt: "Teams left icon",
stat: teamsForRoomData.filter(
async (team) =>
(await team?.scores())?.data.filter(
(score) => score.judgeId === currentUser.username,
).length === 0,
).length,
text: "Teams Left To Score",
},
];
const handleCreateScoreClick = (teamId: string) => {
setSelectedTeamId(teamId);
};
Expand All @@ -89,57 +88,32 @@ export default function JudgingTable({
setSelectedTeamId("");
};

const isFetching = roomIsFetching || teamsForRoomIsFetching;

const tableHeaders = [
{ columnHeader: "Team Name", className: "w-1/3 rounded-tl-lg" },
...hackathonData.scoringComponents.map((component) => ({
columnHeader: component.friendlyName,
className: "w-fit",
})),
...hackathonData.scoringSidepots.map((component) => ({
columnHeader: (
<div className="flex flex-col">
<p>Sidepot:</p>
{component.friendlyName}
</div>
),
className: "w-fit bg-pastel-pink",
})),
];
return isFetching ? (
<div
className={
"flex h-screen w-full items-center justify-center bg-pastel-pink"
}
>
<div className="flex size-full flex-1 items-center justify-center bg-pastel-pink">
<LoadingRing />
</div>
) : (
<div className={"flex h-screen justify-center text-blackish"}>
<div className="mb-4 flex w-full max-w-[1500px] p-6">
<div className="mr-4 flex w-1/4 flex-col space-y-4">
<>
<div className="flex w-full flex-col justify-center gap-4 py-6 xl:flex-row">
<div className=" flex w-full flex-row gap-4 xl:w-1/4 xl:flex-col">
{panelData.map((item, index) => (
<div key={index} className="h-1/2">
<StatsPanel
icon={item.icon}
alt={item.alt}
stat={item.stat}
subheader={item.text}
/>
</div>
<StatsPanel
key={index}
icon={item.icon}
alt={item.alt}
stat={item.stat}
subheader={item.text}
/>
))}
</div>
<div className="w-3/4">
<ScoresTable
tableHeaders={tableHeaders}
tableData={teamsForRoomData as Schema["Team"]["type"][]}
onCreateScoreClick={handleCreateScoreClick}
onEditScoreClick={handleEditScoreClick}
colorScheme="pink"
entriesPerPage={150}
/>
</div>
<ScoresTable
tableData={teamsForRoomData as Schema["Team"]["type"][]}
onCreateScoreClick={handleCreateScoreClick}
onEditScoreClick={handleEditScoreClick}
colorScheme="pink"
entriesPerPage={150}
hackathonData={hackathonData}
/>
</div>
{selectedTeam !== "" && (
<ModalPopup
Expand All @@ -148,6 +122,6 @@ export default function JudgingTable({
teamId={selectedTeam}
/>
)}
</div>
</>
);
}
2 changes: 1 addition & 1 deletion src/app/judging/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const metadata: Metadata = {
};
export default function Judging() {
return (
<main className="w-full bg-dashboard-grey">
<main className="flex w-full flex-1 flex-col gap-4 bg-dashboard-grey">
<JudgingDashboard />
</main>
);
Expand Down
120 changes: 64 additions & 56 deletions src/components/judging/ScoresTable.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { generateClient } from "aws-amplify/api";
import Image from "next/image";
import { useState } from "react";
import { twMerge } from "tailwind-merge";

import { type Schema } from "@/amplify/data/resource";
import { useQuery } from "@tanstack/react-query";

import Card from "../Dashboard/Card";
import { useUser } from "../contexts/UserContext";
import { type ScoreObject } from "./ModalPopup";

const edit_icon = "/svgs/judging/edit_icon.svg";
const filter_icon = "/svgs/judging/filter_arrows.svg";

const JUDGE_TABLE_SECTION_STYLES =
"h-full rounded-lg bg-white p-6 drop-shadow-md";

const JUDGE_TABLE_CONTENT_STYLES =
"w-full border-separate border-spacing-x-0.5";
const JUDGE_TABLE_HEADER_CELL_STLYES = "text-white text-xl font-medium py-4";
const JUDGE_TABLE_CELL_STYLES = "text-center text-lg py-4";
const SCORE_BUTTON_STYLES =
"rounded-full border-2 px-2 py-1 text-sm font-medium";
Expand All @@ -39,33 +35,50 @@ const COLOR_SCHEMES = {
const client = generateClient<Schema>();

interface JudgingTableProps {
tableHeaders: Array<{
columnHeader: string | JSX.Element;
className: string;
}>;
tableData: Schema["Team"]["type"][];
onCreateScoreClick: (teamName: string) => void;
onEditScoreClick: (teamName: string) => void;
colorScheme: "pink" | "purple";
entriesPerPage: number;
hackathonData: Pick<
Schema["Hackathon"]["type"],
"scoringComponents" | "scoringSidepots"
>;
}

const JudgingTable = (props: JudgingTableProps) => {
export default function JudgingTable(props: JudgingTableProps) {
const { currentUser } = useUser();

const {
tableHeaders,
tableData,
onCreateScoreClick,
onEditScoreClick,
colorScheme,
entriesPerPage,
hackathonData,
} = props;

const [currentPage, setCurrentPage] = useState(1);
const [sortedData, setSortedData] = useState(tableData);
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
const entries_per_page = entriesPerPage;

const tableHeaders = [
{ columnHeader: "Team Name", className: "px-6 rounded-tl-lg" },
...hackathonData.scoringComponents.map((component) => ({
columnHeader: component.friendlyName,
className: "w-fit",
})),
...hackathonData.scoringSidepots.map((component) => ({
columnHeader: (
<div className="flex flex-col">
<p>Sidepot:</p>
{component.friendlyName}
</div>
),
className: "w-fit bg-pastel-pink",
})),
];
const handleNextPage = () => {
setCurrentPage((prevPage) =>
Math.min(prevPage + 1, Math.ceil(tableData.length / entries_per_page)),
Expand Down Expand Up @@ -101,22 +114,26 @@ const JudgingTable = (props: JudgingTableProps) => {
const colorStyles = COLOR_SCHEMES[colorScheme];

return (
<div className={JUDGE_TABLE_SECTION_STYLES}>
<div>
<table className={JUDGE_TABLE_CONTENT_STYLES}>
<Card className="items-start gap-3">
<div className="w-full overflow-auto">
<table className="w-full border-separate border-spacing-x-0.5">
<thead>
<tr>
{tableHeaders.map((header, index) => (
<th
key={index}
className={`${JUDGE_TABLE_HEADER_CELL_STLYES} ${header.className} ${colorStyles.headerCellBg}`}
className={twMerge(
"p-4 text-xl font-medium capitalize text-white",
header.className,
colorStyles.headerCellBg,
)}
>
{header.columnHeader}
</th>
))}
<th
className={`w-1/5 rounded-tr-lg ${colorStyles.headerCellBg}`}
></th>
className={` rounded-tr-lg p-12 ${colorStyles.headerCellBg}`}
/>
</tr>
</thead>
<tbody>
Expand All @@ -130,7 +147,6 @@ const JudgingTable = (props: JudgingTableProps) => {
teamId: team.id,
});
if (errors) throw Error(errors[0].message);
console.log(data);
return data;
} catch (error) {
console.error(error);
Expand Down Expand Up @@ -187,44 +203,36 @@ const JudgingTable = (props: JudgingTableProps) => {
})}
</tbody>
</table>
<div className="mt-6 flex justify-between">
<button className="flex items-center" onClick={handleSortClick}>
<Image
src={filter_icon}
height={20}
width={20}
alt="Filter icon"
className="mr-2"
/>
<p>
Sort{" "}
{sortDirection === "asc"
? "Alphabetically"
: "Reverse Alphabetically"}
</p>
</div>
<div className="flex w-full justify-between">
<button className="flex items-center gap-2" onClick={handleSortClick}>
<Image src={filter_icon} height={20} width={20} alt="Filter icon" />
<p>
Sort{" "}
{sortDirection === "asc"
? "Alphabetically"
: "Reverse Alphabetically"}
</p>
</button>
<div>
<button
className={`${PAGINATION_BUTTON_STYLES} ${colorStyles.paginationButtonStyles}`}
onClick={handlePreviousPage}
disabled={currentPage === 1}
>
&lt;
</button>
<button
className={`${PAGINATION_BUTTON_STYLES} ${colorStyles.paginationButtonStyles}`}
onClick={handleNextPage}
disabled={
currentPage === Math.ceil(sortedData.length / entries_per_page)
}
>
&gt;
</button>
<div>
<button
className={`${PAGINATION_BUTTON_STYLES} ${colorStyles.paginationButtonStyles}`}
onClick={handlePreviousPage}
disabled={currentPage === 1}
>
&lt;
</button>
<button
className={`${PAGINATION_BUTTON_STYLES} ${colorStyles.paginationButtonStyles}`}
onClick={handleNextPage}
disabled={
currentPage === Math.ceil(sortedData.length / entries_per_page)
}
>
&gt;
</button>
</div>
</div>
</div>
</div>
</Card>
);
};

export default JudgingTable;
}
Loading
Loading