From 890b5d07db3729c2625877d791c30a545260777e Mon Sep 17 00:00:00 2001 From: JihengLi Date: Wed, 4 Sep 2024 12:52:54 -0500 Subject: [PATCH 1/9] change judging and organizer logic and ui --- .../Organizer/ScheduleTab/ScheduleTab.tsx | 4 +- components/judges/JudgeDash.tsx | 90 ++++++++++--------- components/judges/schedule.tsx | 50 +++-------- 3 files changed, 65 insertions(+), 79 deletions(-) diff --git a/components/Organizer/ScheduleTab/ScheduleTab.tsx b/components/Organizer/ScheduleTab/ScheduleTab.tsx index e54de269..92c527a5 100644 --- a/components/Organizer/ScheduleTab/ScheduleTab.tsx +++ b/components/Organizer/ScheduleTab/ScheduleTab.tsx @@ -67,14 +67,14 @@ const ScheduleTab = () => { let judgingTimes = generateTimes( new Date(hackathonSettings?.JUDGING_START as string), new Date(hackathonSettings?.JUDGING_END as string), - 10 + 5 ); setPotentialSchedule(matchTeams(teams, judges, judgingTimes, timesJudged)); }; useEffect(() => { if (!teamsData || !judgesData) return; - setMaxTimesJudged(Math.floor((judgesData?.length * 12) / teamsData?.length)); + setMaxTimesJudged(Math.floor((judgesData?.length * 24) / teamsData?.length)); }, [teamsData, judgesData]); useEffect(() => { diff --git a/components/judges/JudgeDash.tsx b/components/judges/JudgeDash.tsx index 423df3fb..6b4f7976 100644 --- a/components/judges/JudgeDash.tsx +++ b/components/judges/JudgeDash.tsx @@ -45,6 +45,7 @@ async function handleSubmit( mutate('/api/teams'); mutate('/api/judging-form'); handleSubmitSuccess(`Successfully ${isNewForm ? 'submitted' : 'updated'}!`); + if (isNewForm) window.location.reload(); setIsNewForm(false); } else { handleSubmitFailure(await res.text()); @@ -54,8 +55,8 @@ async function handleSubmit( export default function JudgeDash() { const { data: session, status } = useSession(); const [teamID, setTeamID] = useState(''); - const [currentScheduleItem, setCurrentScheduleItem] = useState(undefined); - const [nextScheduleItem, setNextScheduleItem] = useState(undefined); + // const [currentScheduleItem, setCurrentScheduleItem] = useState(undefined); + // const [nextScheduleItem, setNextScheduleItem] = useState(undefined); const [isNewForm, setIsNewForm] = useState(false); const [nextIndex, setNextIndex] = useState(-1); const { mutate } = useSWRConfig(); @@ -132,51 +133,60 @@ export default function JudgeDash() { return (await res.json()) as JudgingSessionData[]; }); - // Initialize state if data was just received + // // Initialize state if data was just received + // useEffect(() => { + // if (nextIndex === -1 && scheduleData) { + // const now = Date.now(); + // let index = scheduleData.findIndex(el => now < new Date(el.time as string).getTime()); + // if (index === -1) index = scheduleData.length; + // let currentlyGoingTime = new Date(scheduleData[index - 1]?.time as string).getTime() + judgingLength; + // setNextScheduleItem(scheduleData[index]); + // setCurrentScheduleItem(now < currentlyGoingTime ? scheduleData[index - 1] : undefined); + // setNextIndex(index); + // } + // }, [scheduleData, nextIndex, judgingLength]); + + // // Loop to manage current schedule state + // useEffect(() => { + // const interval = setInterval(() => { + // const now = Date.now(); + // if (scheduleData && nextIndex > -1) { + // // Data has been received and state is initialized + // let time2 = new Date(scheduleData[scheduleData.length - 1]?.time as string).getTime() + judgingLength; + // if (now <= time2) { + // // Not yet done judging + // let time3 = new Date((currentScheduleItem?.time as string) || 0).getTime() + judgingLength; + + // if ( + // nextIndex < scheduleData.length && + // now >= new Date((nextScheduleItem?.time as string) || 0).getTime() + // ) { + // // Next event should be current + // setNextScheduleItem(scheduleData[nextIndex + 1]); + // setCurrentScheduleItem(scheduleData[nextIndex]); + // setNextIndex(nextIndex + 1); + // } else if (now > time3) { + // // Next event has not yet arrived but current event is over + // setCurrentScheduleItem(undefined); + // } + // } else { + // // Done judging + // setCurrentScheduleItem(undefined); + // } + // } + // }, 1000); + // return () => clearInterval(interval); + // }); + useEffect(() => { if (nextIndex === -1 && scheduleData) { - const now = Date.now(); - let index = scheduleData.findIndex(el => now < new Date(el.time as string).getTime()); + const today = new Date().setHours(0, 0, 0, 0); + let index = scheduleData.findIndex(el => today < new Date(el.time as string).getTime()); if (index === -1) index = scheduleData.length; - let currentlyGoingTime = new Date(scheduleData[index - 1]?.time as string).getTime() + judgingLength; - setNextScheduleItem(scheduleData[index]); - setCurrentScheduleItem(now < currentlyGoingTime ? scheduleData[index - 1] : undefined); setNextIndex(index); } }, [scheduleData, nextIndex, judgingLength]); - // Loop to manage current schedule state - useEffect(() => { - const interval = setInterval(() => { - const now = Date.now(); - if (scheduleData && nextIndex > -1) { - // Data has been received and state is initialized - let time2 = new Date(scheduleData[scheduleData.length - 1]?.time as string).getTime() + judgingLength; - if (now <= time2) { - // Not yet done judging - let time3 = new Date((currentScheduleItem?.time as string) || 0).getTime() + judgingLength; - - if ( - nextIndex < scheduleData.length && - now >= new Date((nextScheduleItem?.time as string) || 0).getTime() - ) { - // Next event should be current - setNextScheduleItem(scheduleData[nextIndex + 1]); - setCurrentScheduleItem(scheduleData[nextIndex]); - setNextIndex(nextIndex + 1); - } else if (now > time3) { - // Next event has not yet arrived but current event is over - setCurrentScheduleItem(undefined); - } - } else { - // Done judging - setCurrentScheduleItem(undefined); - } - } - }, 1000); - return () => clearInterval(interval); - }); - const handleTeamChange: Dispatch> = e => { setTeamID(e); setTimeout(() => { diff --git a/components/judges/schedule.tsx b/components/judges/schedule.tsx index a78b5e0f..ced061f5 100644 --- a/components/judges/schedule.tsx +++ b/components/judges/schedule.tsx @@ -103,7 +103,7 @@ export default function OrganizerSchedule(props: ScheduleProps) { sessionTimeStart = sessionTimeStart || new Date(); sessionTimeEnd = sessionTimeEnd || new Date(); - const sessionTimes = generateTimes(sessionTimeStart, sessionTimeEnd, 10); + const sessionTimes = generateTimes(sessionTimeStart, sessionTimeEnd, 5); // Reorganize data to be fed into table const tableData = useMemo(() => { @@ -183,17 +183,10 @@ export default function OrganizerSchedule(props: ScheduleProps) { } export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps) { - const [showPast, setShowPast] = useState(false); + const [isJudged, setIsJudged] = useState(false); const { accentColor, baseTheme } = useContext(ThemeContext); const columns = [ - { - title: 'Time', - dataIndex: 'time', - key: 'time', - width: '10%', - render: (date: string) => DateTime.fromISO(date).toLocaleString(DateTime.TIME_SIMPLE), - }, { title: 'Table', dataIndex: 'table', @@ -205,7 +198,7 @@ export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps title: 'Project', dataIndex: 'project', key: 'project', - width: '25%', + width: '40%', render: ({ name, link }: { name: string; link: URL }) => ( <> {name} @@ -223,27 +216,9 @@ export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps title: 'Team Members', dataIndex: 'teamMembers', key: 'teamMembers', - width: '25%', + width: '40%', render: (members: User[]) => members.map(member => {member.name}), }, - { - title: 'Judge', - dataIndex: 'judge', - key: 'judge', - width: '10%', - render: (judge: User) => {judge.name}, - }, - { - title: 'Action', - dataIndex: 'teamId', - key: 'teamId', - width: '10%', - render: (teamId: any) => ( - - ), - }, { title: 'Judgement State', dataIndex: 'scores', @@ -252,17 +227,17 @@ export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps render: (scores: []) => {scores.length ? 'Judged' : 'Without Judgement'}, }, ]; - const dataSource = data.slice(showPast ? 0 : cutoffIndex).map(item => { - return { - time: item.time, + + const dataSource = data + .slice(cutoffIndex) + .filter(item => (isJudged ? item.team.scores.length : !item.team.scores.length)) + .map(item => ({ table: item.team.locationNum, project: { name: item.team.name, link: new URL(item.team.devpost) }, teamMembers: item.team.members, - judge: item.judge, teamId: item.team._id, scores: item.team.scores, - }; - }); + })); const handleRowClick = (record: any) => { handleChange(record.teamId); @@ -286,8 +261,9 @@ export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps - setShowPast(!showPast)} /> - Show Past Sessions + From addb754b0751dad4fabfa056e6b5703e403b13d4 Mon Sep 17 00:00:00 2001 From: JihengLi Date: Wed, 4 Sep 2024 19:23:48 -0500 Subject: [PATCH 2/9] remove all groups from dropdown in judging page --- components/Organizer/ScheduleTab/ScheduleTab.tsx | 1 + components/judges/TeamSelect.tsx | 3 ++- components/judges/schedule.tsx | 10 ++++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/components/Organizer/ScheduleTab/ScheduleTab.tsx b/components/Organizer/ScheduleTab/ScheduleTab.tsx index 92c527a5..e3f49714 100644 --- a/components/Organizer/ScheduleTab/ScheduleTab.tsx +++ b/components/Organizer/ScheduleTab/ScheduleTab.tsx @@ -64,6 +64,7 @@ const ScheduleTab = () => { } // Set that potential schedules as newly generated schedules + // 5 represents the amount of time a judge judges one group let judgingTimes = generateTimes( new Date(hackathonSettings?.JUDGING_START as string), new Date(hackathonSettings?.JUDGING_END as string), diff --git a/components/judges/TeamSelect.tsx b/components/judges/TeamSelect.tsx index 4dca5d2a..fdaf7c94 100644 --- a/components/judges/TeamSelect.tsx +++ b/components/judges/TeamSelect.tsx @@ -63,13 +63,14 @@ export default function TeamSelect(props: TeamSelectProps) { ))} )} + {/* // we should not allow the judge to see and update the score of others {teamsData.map(team => ( ))} - + */} ); diff --git a/components/judges/schedule.tsx b/components/judges/schedule.tsx index ced061f5..c27ff3f2 100644 --- a/components/judges/schedule.tsx +++ b/components/judges/schedule.tsx @@ -103,6 +103,7 @@ export default function OrganizerSchedule(props: ScheduleProps) { sessionTimeStart = sessionTimeStart || new Date(); sessionTimeEnd = sessionTimeEnd || new Date(); + // 5 represents the amount of time a judge judges one group const sessionTimes = generateTimes(sessionTimeStart, sessionTimeEnd, 5); // Reorganize data to be fed into table @@ -229,7 +230,6 @@ export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps ]; const dataSource = data - .slice(cutoffIndex) .filter(item => (isJudged ? item.team.scores.length : !item.team.scores.length)) .map(item => ({ table: item.team.locationNum, @@ -248,7 +248,13 @@ export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps locale={{ emptyText: (
-

Stay tuned! You will see your teams that you will judge soon!

+

+ {data.length == 0 + ? 'Stay tuned! You will see your teams that you will judge soon!' + : isJudged + ? "You haven't started judging yet." + : "Hurraaaaarrgh! You're off duty!"} +

), }} From 5ee47d4b4336b82837db8d119f5bed6c42f778cb Mon Sep 17 00:00:00 2001 From: JihengLi Date: Wed, 4 Sep 2024 19:25:50 -0500 Subject: [PATCH 3/9] remove time constraint of judging --- components/judges/JudgeDash.tsx | 20 +++++++++----------- components/judges/schedule.tsx | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/components/judges/JudgeDash.tsx b/components/judges/JudgeDash.tsx index 6b4f7976..6f3fb68e 100644 --- a/components/judges/JudgeDash.tsx +++ b/components/judges/JudgeDash.tsx @@ -178,14 +178,14 @@ export default function JudgeDash() { // return () => clearInterval(interval); // }); - useEffect(() => { - if (nextIndex === -1 && scheduleData) { - const today = new Date().setHours(0, 0, 0, 0); - let index = scheduleData.findIndex(el => today < new Date(el.time as string).getTime()); - if (index === -1) index = scheduleData.length; - setNextIndex(index); - } - }, [scheduleData, nextIndex, judgingLength]); + // useEffect(() => { + // if (nextIndex === -1 && scheduleData) { + // const today = new Date().setHours(0, 0, 0, 0); + // let index = scheduleData.findIndex(el => today < new Date(el.time as string).getTime()); + // if (index === -1) index = scheduleData.length; + // setNextIndex(index); + // } + // }, [scheduleData, nextIndex, judgingLength]); const handleTeamChange: Dispatch> = e => { setTeamID(e); @@ -214,9 +214,7 @@ export default function JudgeDash() { - {scheduleData && ( - - )} + {scheduleData && }

{teamsData && } diff --git a/components/judges/schedule.tsx b/components/judges/schedule.tsx index c27ff3f2..9ae4b3d6 100644 --- a/components/judges/schedule.tsx +++ b/components/judges/schedule.tsx @@ -183,7 +183,7 @@ export default function OrganizerSchedule(props: ScheduleProps) { ); } -export function JudgeSchedule({ data, cutoffIndex, handleChange }: ScheduleProps) { +export function JudgeSchedule({ data, handleChange }: ScheduleProps) { const [isJudged, setIsJudged] = useState(false); const { accentColor, baseTheme } = useContext(ThemeContext); From 3a35c6ef6189024492878b68d1d92c7971c785e5 Mon Sep 17 00:00:00 2001 From: JihengLi Date: Sat, 7 Sep 2024 02:20:29 -0500 Subject: [PATCH 4/9] debug the judging dashboard, change the get judgingsession method, add have judged property to judging session dto --- components/Organizer/ScheduleTab/ScheduleTab.tsx | 1 + components/judges/schedule.tsx | 10 +++++----- pages/_app.tsx | 3 ++- pages/api/judging-sessions.ts | 14 ++++++++++++-- types/database.ts | 1 + 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/components/Organizer/ScheduleTab/ScheduleTab.tsx b/components/Organizer/ScheduleTab/ScheduleTab.tsx index e3f49714..8e4d0a25 100644 --- a/components/Organizer/ScheduleTab/ScheduleTab.tsx +++ b/components/Organizer/ScheduleTab/ScheduleTab.tsx @@ -73,6 +73,7 @@ const ScheduleTab = () => { setPotentialSchedule(matchTeams(teams, judges, judgingTimes, timesJudged)); }; + // bug1: do not use the default 2 hours constant. useEffect(() => { if (!teamsData || !judgesData) return; setMaxTimesJudged(Math.floor((judgesData?.length * 24) / teamsData?.length)); diff --git a/components/judges/schedule.tsx b/components/judges/schedule.tsx index 9ae4b3d6..13c372f7 100644 --- a/components/judges/schedule.tsx +++ b/components/judges/schedule.tsx @@ -222,21 +222,21 @@ export function JudgeSchedule({ data, handleChange }: ScheduleProps) { }, { title: 'Judgement State', - dataIndex: 'scores', - key: 'scores', + dataIndex: 'haveJudged', + key: 'haveJudged', width: '10%', - render: (scores: []) => {scores.length ? 'Judged' : 'Without Judgement'}, + render: (haveJudged: []) => {haveJudged ? 'Judged' : 'Without Judgement'}, }, ]; const dataSource = data - .filter(item => (isJudged ? item.team.scores.length : !item.team.scores.length)) + .filter(item => (isJudged ? item.haveJudged : !item.haveJudged)) .map(item => ({ table: item.team.locationNum, project: { name: item.team.name, link: new URL(item.team.devpost) }, teamMembers: item.team.members, teamId: item.team._id, - scores: item.team.scores, + haveJudged: item.haveJudged, })); const handleRowClick = (record: any) => { diff --git a/pages/_app.tsx b/pages/_app.tsx index c67c7a0b..8c9b87cd 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -19,7 +19,8 @@ function MyApp({ Component, pageProps }: AppProps) { return ( <> - + + {pageProps.title} diff --git a/pages/api/judging-sessions.ts b/pages/api/judging-sessions.ts index 4b445b94..d6fb0179 100644 --- a/pages/api/judging-sessions.ts +++ b/pages/api/judging-sessions.ts @@ -3,6 +3,9 @@ import { getSession } from 'next-auth/react'; import JudgingSession from '../../models/JudgingSession'; import Team from '../../models/team'; import User from '../../models/user'; +import Scores from '../../models/scores'; +import { ObjectId } from 'mongodb'; +import { ScoreData } from '../../types/database'; /** * gets a judging schedule for a team @@ -37,13 +40,20 @@ async function getOrganizerSchedule(res: NextApiResponse) { * @param userID ID of the judge * @returns response containing judging schedule for a judge */ -async function getJudgeSchedule(res: NextApiResponse, userID: string) { +async function getJudgeSchedule(res: NextApiResponse, userID: ObjectId) { Team; User; // Don't remove or the import will get optimized out and the populate will fail const data = await JudgingSession.find({ judge: userID }) .populate('team judge') .populate({ path: 'team', populate: { path: 'members' } }) .lean(); + + // whether the team in the session is judged + const teamsJudged = await Scores.find({ judge: userID }).select('team'); + const teamsJudgedIDs = teamsJudged.map(teamItem => teamItem.team.toString()); + data.forEach(judgingSession => { + judgingSession.haveJudged = teamsJudgedIDs.includes(judgingSession.team._id.toString()); + }); return res.status(200).send(data); } @@ -62,7 +72,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< case 'ORGANIZER': return getOrganizerSchedule(res); case 'JUDGE': - return getJudgeSchedule(res, session?.userID as string); + return getJudgeSchedule(res, session?.userID as ObjectId); default: return res.status(403).send('Forbidden'); } diff --git a/types/database.ts b/types/database.ts index f2ef8e0f..d6a0b29f 100644 --- a/types/database.ts +++ b/types/database.ts @@ -123,6 +123,7 @@ export interface JudgingSessionData { _id?: mongoose.Schema.Types.ObjectId; team: TeamData; judge: UserData; + haveJudged?: Boolean; time: String; } From ec680512bab03c0e4b9184e782689d2855b1f4e9 Mon Sep 17 00:00:00 2001 From: JihengLi Date: Sat, 7 Sep 2024 02:26:00 -0500 Subject: [PATCH 5/9] typo --- components/judges/schedule.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/judges/schedule.tsx b/components/judges/schedule.tsx index 13c372f7..e2a30b68 100644 --- a/components/judges/schedule.tsx +++ b/components/judges/schedule.tsx @@ -268,7 +268,7 @@ export function JudgeSchedule({ data, handleChange }: ScheduleProps) { From 17c11db31bb29a4d76d06e179cc2df7688f8d865 Mon Sep 17 00:00:00 2001 From: JihengLi Date: Sun, 8 Sep 2024 00:15:40 -0500 Subject: [PATCH 6/9] let organizers to set time for one judge to score one team, changed the corresponding backend and database --- .../Organizer/ScheduleTab/ScheduleTab.tsx | 35 ++++++++++++------- .../SettingsTab/HackathonSettings.tsx | 33 ++++++++++++++--- components/judges/schedule.tsx | 7 ++-- models/hackathon.ts | 2 ++ pages/api/hackathon-settings.ts | 12 ++++++- pages/api/judging-sessions.ts | 2 +- types/database.ts | 2 ++ 7 files changed, 70 insertions(+), 23 deletions(-) diff --git a/components/Organizer/ScheduleTab/ScheduleTab.tsx b/components/Organizer/ScheduleTab/ScheduleTab.tsx index 8e4d0a25..e5ec1d44 100644 --- a/components/Organizer/ScheduleTab/ScheduleTab.tsx +++ b/components/Organizer/ScheduleTab/ScheduleTab.tsx @@ -13,9 +13,26 @@ const ScheduleTab = () => { const [timesJudged, setTimesJudged] = useState(1); const [maxTimesJudged, setMaxTimesJudged] = useState(0); const [potentialSchedule, setPotentialSchedule] = useState(undefined); + const [hackathonSettings, setHackathonSettings] = useState(undefined); const { baseTheme } = useContext(ThemeContext); + // Get hackathon settings + useEffect(() => { + const fetchHackathonSettings = async () => { + const res = await fetch('/api/hackathon-settings'); + if (res.ok) { + const settings = await res.json(); + setHackathonSettings(settings as HackathonSettingsData); + } + }; + fetchHackathonSettings(); + }, []); + + const TimeForJudgeToScoreOneTeam = hackathonSettings?.JUDGING_TIME_PER_TEAM as number; + const JudgingDuration = hackathonSettings?.JUDGING_DURATION as number; + const MaximumNumberOfTeamsOneJudgeCanFinishInJudgingTime = JudgingDuration / TimeForJudgeToScoreOneTeam; + // Get judging sessions const { data: judgingSessions, error: judgingSessionsError } = useCustomSWR({ url: '/api/judging-sessions', @@ -37,13 +54,6 @@ const ScheduleTab = () => { errorMessage: 'Failed to get list of teams.', }); - // Get hackathon settings - const { data: hackathonSettings, error: hackathonError } = useCustomSWR({ - url: '/api/hackathon-settings', - method: RequestType.GET, - errorMessage: 'Failed to get hackathon times.', - }); - // Confirm potential schedule const handleConfirmPotentialSchedules = (potentialSchedule: JudgingSessionData[] | undefined) => { // Exit early if we don't have data yet @@ -64,20 +74,20 @@ const ScheduleTab = () => { } // Set that potential schedules as newly generated schedules - // 5 represents the amount of time a judge judges one group let judgingTimes = generateTimes( new Date(hackathonSettings?.JUDGING_START as string), new Date(hackathonSettings?.JUDGING_END as string), - 5 + TimeForJudgeToScoreOneTeam ); setPotentialSchedule(matchTeams(teams, judges, judgingTimes, timesJudged)); }; - // bug1: do not use the default 2 hours constant. useEffect(() => { if (!teamsData || !judgesData) return; - setMaxTimesJudged(Math.floor((judgesData?.length * 24) / teamsData?.length)); - }, [teamsData, judgesData]); + setMaxTimesJudged( + Math.floor((judgesData?.length * MaximumNumberOfTeamsOneJudgeCanFinishInJudgingTime) / teamsData?.length) + ); + }, [teamsData, judgesData, hackathonSettings]); useEffect(() => { // Exit early if we don't have data yet @@ -164,6 +174,7 @@ const ScheduleTab = () => { handleChange={function (value: SetStateAction): void { throw new Error('Function not implemented.'); }} + TimeForJudgeToScoreOneTeam={hackathonSettings?.JUDGING_TIME_PER_TEAM as number} sessionTimeStart={new Date(hackathonSettings?.JUDGING_START as string)} sessionTimeEnd={new Date(hackathonSettings?.JUDGING_END as string)} /> diff --git a/components/Organizer/SettingsTab/HackathonSettings.tsx b/components/Organizer/SettingsTab/HackathonSettings.tsx index 590d146b..91c396ea 100644 --- a/components/Organizer/SettingsTab/HackathonSettings.tsx +++ b/components/Organizer/SettingsTab/HackathonSettings.tsx @@ -1,4 +1,4 @@ -import { Button, DatePicker, Select, Space } from 'antd'; +import { Button, DatePicker, Input, Select, Space } from 'antd'; import dayjs from 'dayjs'; import React, { useContext, useEffect, useState } from 'react'; import { handleSubmitFailure, handleSubmitSuccess } from '../../../lib/helpers'; @@ -89,6 +89,7 @@ const HackathonSettings = () => { setHackathonSetting(settings as HackathonSettingsData); setStatusMessage('Successfully saved to database!'); handleSubmitSuccess('Successfully saved to database!'); + window.location.reload(); } else { setStatusMessage('Failed to save to database!'); handleSubmitFailure('Failed to save to database!'); @@ -114,14 +115,16 @@ const HackathonSettings = () => { }; const handleJudgingChange = (_: any, dateStrings: [string, string]) => { - const newJudgingStart = dayjs(dateStrings[0], { utc: true }).format('MM/DD/YYYY hh:mm A'); - const newJudgingEnd = dayjs(dateStrings[1], { utc: true }).format('MM/DD/YYYY hh:mm A'); + const newJudgingStart = dayjs(dateStrings[0], { utc: true }); + const newJudgingEnd = dayjs(dateStrings[1], { utc: true }); + const judgingDuration = newJudgingEnd.diff(newJudgingStart, 'minute'); setHackathonSetting(prev => { if (prev) return { ...prev, - JUDGING_START: newJudgingStart, - JUDGING_END: newJudgingEnd, + JUDGING_START: newJudgingStart.format('MM/DD/YYYY hh:mm A'), + JUDGING_END: newJudgingEnd.format('MM/DD/YYYY hh:mm A'), + JUDGING_DURATION: judgingDuration, }; return undefined; }); @@ -161,6 +164,26 @@ const HackathonSettings = () => { defaultValue={[dayjs(hackathonSetting?.JUDGING_START), dayjs(hackathonSetting?.JUDGING_END)]} /> +
+ +
Time for Judge to Score One Team (minutes):
+ { + setHackathonSetting(prev => { + if (prev) { + return { + ...prev, + JUDGING_TIME_PER_TEAM: e.target.value, + }; + } + return prev; + }); + }} + placeholder="Enter time in minutes" + style={{ width: 138 }} + /> +

diff --git a/components/judges/schedule.tsx b/components/judges/schedule.tsx index e2a30b68..66313bfc 100644 --- a/components/judges/schedule.tsx +++ b/components/judges/schedule.tsx @@ -8,7 +8,7 @@ import { ThemeContext, getAccentColor } from '../../theme/themeProvider'; interface ScheduleProps { data: JudgingSessionData[]; - cutoffIndex?: number; + TimeForJudgeToScoreOneTeam?: number; handleChange: (teamId: string) => void; sessionTimeStart?: Date; sessionTimeEnd?: Date; @@ -67,7 +67,7 @@ export function generateTimes(start: Date, end: Date, interval: number) { } export default function OrganizerSchedule(props: ScheduleProps) { - let { data, sessionTimeStart, sessionTimeEnd } = props; + let { data, TimeForJudgeToScoreOneTeam, sessionTimeStart, sessionTimeEnd } = props; const teams = useMemo( () => [...new Set(data.filter(x => x.team !== null && x.team.name !== null).map(x => x.team.name))], @@ -103,8 +103,7 @@ export default function OrganizerSchedule(props: ScheduleProps) { sessionTimeStart = sessionTimeStart || new Date(); sessionTimeEnd = sessionTimeEnd || new Date(); - // 5 represents the amount of time a judge judges one group - const sessionTimes = generateTimes(sessionTimeStart, sessionTimeEnd, 5); + const sessionTimes = generateTimes(sessionTimeStart, sessionTimeEnd, TimeForJudgeToScoreOneTeam as number); // Reorganize data to be fed into table const tableData = useMemo(() => { diff --git a/models/hackathon.ts b/models/hackathon.ts index 7c6685fc..5110a0f9 100644 --- a/models/hackathon.ts +++ b/models/hackathon.ts @@ -5,6 +5,8 @@ const HackathonSchema = new Schema({ HACKATHON_END: { type: String, required: true }, // MM/DD/YYYY hh:mm A JUDGING_START: { type: String, required: true }, // MM/DD/YYYY hh:mm A JUDGING_END: { type: String, required: true }, // MM/DD/YYYY hh:mm A + JUDGING_DURATION: { type: Number, required: true }, + JUDGING_TIME_PER_TEAM: { type: Number, required: true }, ON_CALL_DEV: { type: String, required: true }, }); diff --git a/pages/api/hackathon-settings.ts b/pages/api/hackathon-settings.ts index ce82884d..cb88d400 100644 --- a/pages/api/hackathon-settings.ts +++ b/pages/api/hackathon-settings.ts @@ -21,7 +21,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< try { if (session?.userType !== 'ORGANIZER') return res.status(403).send('Forbidden'); // extract variables from body - const { HACKATHON_START, HACKATHON_END, JUDGING_START, JUDGING_END, ON_CALL_DEV } = req.body; + const { + HACKATHON_START, + HACKATHON_END, + JUDGING_START, + JUDGING_END, + JUDGING_DURATION, + JUDGING_TIME_PER_TEAM, + ON_CALL_DEV, + } = req.body; // check if hackathon settings valid (must be correct format of date) const isValid = @@ -42,6 +50,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< HACKATHON_END, JUDGING_START, JUDGING_END, + JUDGING_DURATION, + JUDGING_TIME_PER_TEAM: parseInt(JUDGING_TIME_PER_TEAM, 10), ON_CALL_DEV, }, }, diff --git a/pages/api/judging-sessions.ts b/pages/api/judging-sessions.ts index d6fb0179..6f4f3463 100644 --- a/pages/api/judging-sessions.ts +++ b/pages/api/judging-sessions.ts @@ -48,7 +48,7 @@ async function getJudgeSchedule(res: NextApiResponse, userID: ObjectId) { .populate({ path: 'team', populate: { path: 'members' } }) .lean(); - // whether the team in the session is judged + // whether the team in the session has judged by the specific judge const teamsJudged = await Scores.find({ judge: userID }).select('team'); const teamsJudgedIDs = teamsJudged.map(teamItem => teamItem.team.toString()); data.forEach(judgingSession => { diff --git a/types/database.ts b/types/database.ts index d6a0b29f..88235588 100644 --- a/types/database.ts +++ b/types/database.ts @@ -133,5 +133,7 @@ export interface HackathonSettingsData { HACKATHON_END: string; // MM/DD/YYYY hh:mm A JUDGING_START: string; // MM/DD/YYYY hh:mm A JUDGING_END: string; // MM/DD/YYYY hh:mm A + JUDGING_DURATION: number; + JUDGING_TIME_PER_TEAM: string; ON_CALL_DEV: string; } From 25d1b983740f1d02fceab41b58a9665bb64944cb Mon Sep 17 00:00:00 2001 From: JihengLi Date: Sun, 8 Sep 2024 00:40:57 -0500 Subject: [PATCH 7/9] fix type error --- components/Organizer/ScheduleTab/ScheduleTab.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/Organizer/ScheduleTab/ScheduleTab.tsx b/components/Organizer/ScheduleTab/ScheduleTab.tsx index e5ec1d44..8937e734 100644 --- a/components/Organizer/ScheduleTab/ScheduleTab.tsx +++ b/components/Organizer/ScheduleTab/ScheduleTab.tsx @@ -29,7 +29,7 @@ const ScheduleTab = () => { fetchHackathonSettings(); }, []); - const TimeForJudgeToScoreOneTeam = hackathonSettings?.JUDGING_TIME_PER_TEAM as number; + const TimeForJudgeToScoreOneTeam = parseInt(hackathonSettings?.JUDGING_TIME_PER_TEAM as string, 10); const JudgingDuration = hackathonSettings?.JUDGING_DURATION as number; const MaximumNumberOfTeamsOneJudgeCanFinishInJudgingTime = JudgingDuration / TimeForJudgeToScoreOneTeam; @@ -174,7 +174,7 @@ const ScheduleTab = () => { handleChange={function (value: SetStateAction): void { throw new Error('Function not implemented.'); }} - TimeForJudgeToScoreOneTeam={hackathonSettings?.JUDGING_TIME_PER_TEAM as number} + TimeForJudgeToScoreOneTeam={TimeForJudgeToScoreOneTeam} sessionTimeStart={new Date(hackathonSettings?.JUDGING_START as string)} sessionTimeEnd={new Date(hackathonSettings?.JUDGING_END as string)} /> From 7bc82c85b4504022e773c5a3a379b597ec2993ca Mon Sep 17 00:00:00 2001 From: JihengLi Date: Mon, 9 Sep 2024 04:07:03 -0500 Subject: [PATCH 8/9] fix the bug: we should clear all the elements in judging session whenever we update the settings --- components/Organizer/ScheduleTab/ScheduleTab.tsx | 16 ++-------------- pages/api/hackathon-settings.ts | 2 ++ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/components/Organizer/ScheduleTab/ScheduleTab.tsx b/components/Organizer/ScheduleTab/ScheduleTab.tsx index 8937e734..01bb5ea8 100644 --- a/components/Organizer/ScheduleTab/ScheduleTab.tsx +++ b/components/Organizer/ScheduleTab/ScheduleTab.tsx @@ -92,20 +92,8 @@ const ScheduleTab = () => { useEffect(() => { // Exit early if we don't have data yet if (!judgingSessions) return; - - // Sort judging sessions by time - const time = new Date('2022-10-23T11:00:00').getTime(); - - // Set the data after filtering it by time - setPotentialSchedule( - judgingSessions.filter(judgingSession => { - let time = new Date(judgingSession.time as string); - return ( - new Date(hackathonSettings?.JUDGING_START as string) <= time && - time <= new Date(hackathonSettings?.JUDGING_END as string) - ); - }) - ); + // Set the data + setPotentialSchedule(judgingSessions); }, [judgingSessions, hackathonSettings]); // Combine all the loading, null, and error states diff --git a/pages/api/hackathon-settings.ts b/pages/api/hackathon-settings.ts index cb88d400..0379bc1a 100644 --- a/pages/api/hackathon-settings.ts +++ b/pages/api/hackathon-settings.ts @@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import dbConnect from '../../middleware/database'; import { getSession } from 'next-auth/react'; import hackathon from '../../models/hackathon'; +import JudgingSession from '../../models/JudgingSession'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({ req }); @@ -60,6 +61,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< } ); + await JudgingSession.remove(); // return the updated hackathon settings return res.status(200).json(updatedHackathonSettings); } catch (err) { From 9da4e7d79232401678e89e73521cae6bc778712a Mon Sep 17 00:00:00 2001 From: JihengLi Date: Thu, 12 Sep 2024 20:13:51 -0500 Subject: [PATCH 9/9] fix bugs in hacker page --- components/hacker/HackerDash.tsx | 2 +- components/hacker/JudgingSchedule.tsx | 8 ++-- components/judges/JudgeDash.tsx | 56 --------------------------- components/judges/TeamSelect.tsx | 8 ---- pages/api/leaderboard.ts | 5 ++- 5 files changed, 8 insertions(+), 71 deletions(-) diff --git a/components/hacker/HackerDash.tsx b/components/hacker/HackerDash.tsx index c6513a72..01706944 100644 --- a/components/hacker/HackerDash.tsx +++ b/components/hacker/HackerDash.tsx @@ -769,7 +769,7 @@ export default function HackerDash({ userApplicationStatus, setUserApplicationSt
- + {/* */} diff --git a/components/hacker/JudgingSchedule.tsx b/components/hacker/JudgingSchedule.tsx index 7b660e74..b0d0887c 100644 --- a/components/hacker/JudgingSchedule.tsx +++ b/components/hacker/JudgingSchedule.tsx @@ -34,9 +34,7 @@ const JudgingSchedule = ({ judgingSessionData }: JudgingScheduleProps) => { return (
Judging Schedule -
- You will be assigned a table and judge for judging. Please be at your table at the time indicated below. -
+
You will be assigned a table and judge for judging.
{judgingSessionData?.length === 0 ? (
Schedule will show up here when hacking ends!
) : ( @@ -44,7 +42,7 @@ const JudgingSchedule = ({ judgingSessionData }: JudgingScheduleProps) => { - + {/* */} @@ -52,7 +50,7 @@ const JudgingSchedule = ({ judgingSessionData }: JudgingScheduleProps) => { {judgingSessionData?.map(entry => ( - + {/* */} diff --git a/components/judges/JudgeDash.tsx b/components/judges/JudgeDash.tsx index 6f3fb68e..ac8009ff 100644 --- a/components/judges/JudgeDash.tsx +++ b/components/judges/JudgeDash.tsx @@ -55,8 +55,6 @@ async function handleSubmit( export default function JudgeDash() { const { data: session, status } = useSession(); const [teamID, setTeamID] = useState(''); - // const [currentScheduleItem, setCurrentScheduleItem] = useState(undefined); - // const [nextScheduleItem, setNextScheduleItem] = useState(undefined); const [isNewForm, setIsNewForm] = useState(false); const [nextIndex, setNextIndex] = useState(-1); const { mutate } = useSWRConfig(); @@ -133,60 +131,6 @@ export default function JudgeDash() { return (await res.json()) as JudgingSessionData[]; }); - // // Initialize state if data was just received - // useEffect(() => { - // if (nextIndex === -1 && scheduleData) { - // const now = Date.now(); - // let index = scheduleData.findIndex(el => now < new Date(el.time as string).getTime()); - // if (index === -1) index = scheduleData.length; - // let currentlyGoingTime = new Date(scheduleData[index - 1]?.time as string).getTime() + judgingLength; - // setNextScheduleItem(scheduleData[index]); - // setCurrentScheduleItem(now < currentlyGoingTime ? scheduleData[index - 1] : undefined); - // setNextIndex(index); - // } - // }, [scheduleData, nextIndex, judgingLength]); - - // // Loop to manage current schedule state - // useEffect(() => { - // const interval = setInterval(() => { - // const now = Date.now(); - // if (scheduleData && nextIndex > -1) { - // // Data has been received and state is initialized - // let time2 = new Date(scheduleData[scheduleData.length - 1]?.time as string).getTime() + judgingLength; - // if (now <= time2) { - // // Not yet done judging - // let time3 = new Date((currentScheduleItem?.time as string) || 0).getTime() + judgingLength; - - // if ( - // nextIndex < scheduleData.length && - // now >= new Date((nextScheduleItem?.time as string) || 0).getTime() - // ) { - // // Next event should be current - // setNextScheduleItem(scheduleData[nextIndex + 1]); - // setCurrentScheduleItem(scheduleData[nextIndex]); - // setNextIndex(nextIndex + 1); - // } else if (now > time3) { - // // Next event has not yet arrived but current event is over - // setCurrentScheduleItem(undefined); - // } - // } else { - // // Done judging - // setCurrentScheduleItem(undefined); - // } - // } - // }, 1000); - // return () => clearInterval(interval); - // }); - - // useEffect(() => { - // if (nextIndex === -1 && scheduleData) { - // const today = new Date().setHours(0, 0, 0, 0); - // let index = scheduleData.findIndex(el => today < new Date(el.time as string).getTime()); - // if (index === -1) index = scheduleData.length; - // setNextIndex(index); - // } - // }, [scheduleData, nextIndex, judgingLength]); - const handleTeamChange: Dispatch> = e => { setTeamID(e); setTimeout(() => { diff --git a/components/judges/TeamSelect.tsx b/components/judges/TeamSelect.tsx index fdaf7c94..cc662f35 100644 --- a/components/judges/TeamSelect.tsx +++ b/components/judges/TeamSelect.tsx @@ -63,14 +63,6 @@ export default function TeamSelect(props: TeamSelectProps) { ))} )} - {/* // we should not allow the judge to see and update the score of others - - {teamsData.map(team => ( - - ))} - */} ); diff --git a/pages/api/leaderboard.ts b/pages/api/leaderboard.ts index 0b3295e3..e640403f 100644 --- a/pages/api/leaderboard.ts +++ b/pages/api/leaderboard.ts @@ -12,7 +12,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await dbConnect(); switch (req.method) { case 'GET': - const users = await User.find({ nfcPoints: { $exists: true } }) + const users = await User.find({ + userType: 'HACKER', + nfcPoints: { $exists: true }, + }) .sort({ nfcPoints: -1 }) .limit(10) .populate('team')
TimeTimeTable Judge
{renderJudgingTime(entry.time.toString())}{renderJudgingTime(entry.time.toString())}{entry.team.locationNum} {entry.judge.name}