diff --git a/web/src/routes/admin/scoreboard/+layout.server.ts b/web/src/routes/admin/scoreboard/+layout.server.ts new file mode 100644 index 0000000..fe14bcc --- /dev/null +++ b/web/src/routes/admin/scoreboard/+layout.server.ts @@ -0,0 +1,7 @@ +import { db } from '$lib/server/prisma'; +import type { LayoutServerLoad } from './$types'; + +export const load = (async () => { + const contests = await db.contest.findMany({ select: { id: true, name: true } }); + return { contests }; +}) satisfies LayoutServerLoad; diff --git a/web/src/routes/admin/scoreboard/+layout.svelte b/web/src/routes/admin/scoreboard/+layout.svelte new file mode 100644 index 0000000..e7c5e30 --- /dev/null +++ b/web/src/routes/admin/scoreboard/+layout.svelte @@ -0,0 +1,41 @@ + + + + Admin Scoreboards + + +

Admin Scoreboards

+ +
+ +
+ + diff --git a/web/src/routes/admin/scoreboard/+page.server.ts b/web/src/routes/admin/scoreboard/+page.server.ts deleted file mode 100644 index 006f36d..0000000 --- a/web/src/routes/admin/scoreboard/+page.server.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { db } from '$lib/server/prisma'; -import type { PageServerLoad } from './$types'; - -export const load = (async () => { - const timestamp = new Date(); - const contests = await db.contest.findMany({ - include: { problems: true, teams: { include: { submissions: true } } } - }); - const data = { - timestamp: timestamp, - contests: contests.map((contest) => { - return { - name: contest.name, - problems: contest.problems.map((problem) => { - return { id: problem.id, friendlyName: problem.friendlyName }; - }), - teams: contest.teams - .map((team) => { - return { - name: team.name, - solves: team.submissions.filter((submission) => { - return submission.contestId === contest.id && submission.state === 'Correct'; - }).length, - time: (() => { - const correctSubmissions = team.submissions.filter((submission) => { - return submission.contestId === contest.id && submission.state === 'Correct'; - }); - const penaltyTime = - team.submissions.filter((submission) => { - return ( - submission.contestId === contest.id && - submission.state === 'Incorrect' && - correctSubmissions.find((correct) => { - return correct.problemId === submission.problemId; - }) - ); - }).length * 10; - let time = penaltyTime; - correctSubmissions.forEach((correctSubmission) => { - const gradedAt = correctSubmission.gradedAt!.valueOf(); - const min = (gradedAt - contest.startTime!.valueOf()) / 60000; - time += min; - }); - return time; - })(), - problems: contest.problems.map((problem) => { - return { - id: problem.id, - attempts: team.submissions.filter((submission) => { - return ( - submission.contestId === contest.id && - submission.problemId === problem.id && - (submission.state === 'Correct' || submission.state === 'Incorrect') - ); - }).length, - graphic: team.submissions.find((submission) => { - return ( - submission.contestId === contest.id && - submission.problemId === problem.id && - (submission.state === 'Correct' || submission.state === 'Incorrect') - ); - }) - ? team.submissions.find((submission) => { - return ( - submission.problemId === problem.id && submission.state === 'Correct' - ); - }) - ? 'correct' - : 'incorrect' - : null, - min: (() => { - const correctSubmission = team.submissions.find((submission) => { - return ( - submission.contestId === contest.id && - submission.problemId === problem.id && - submission.state === 'Correct' - ); - }); - if (correctSubmission) { - const gradedAt = correctSubmission.gradedAt!.valueOf(); - return (gradedAt - contest.startTime!.valueOf()) / 60000; - } - return undefined; - })() - }; - }) - }; - }) - .sort((a, b) => { - if (a.solves > b.solves) { - return -1; - } else if (a.solves < b.solves) { - return 1; - } else { - if (a.time < b.time) { - return -1; - } else if (a.time > b.time) { - return 1; - } else { - return 0; - } - } - }) - }; - }) - }; - return data; -}) satisfies PageServerLoad; diff --git a/web/src/routes/admin/scoreboard/+page.svelte b/web/src/routes/admin/scoreboard/+page.svelte index 6281b20..e800732 100644 --- a/web/src/routes/admin/scoreboard/+page.svelte +++ b/web/src/routes/admin/scoreboard/+page.svelte @@ -1,109 +1,4 @@ - - - Admin Scoreboards - - -

Admin Scoreboards

- -
- {#if updating} -
- {/if} - Last Updated: {data.timestamp.toLocaleTimeString()} -
- -{#each data.contests as contest} -

{contest.name}

-
-
-
- - - - - - - - - {#each contest.problems as problem} - - {/each} - - - - {#each contest.teams as team, i} - - - - - - {#each contest.problems as problem} - - {/each} - - {/each} - -
PlaceTeam NameSolvesTime{problem.friendlyName}
{i + 1}{team.name}{team.solves}{team.time.toFixed(0)} -
-
- {#if team.problems.find((p) => { - return p.id === problem.id; - })?.graphic !== null} - { - return p.id === problem.id; - })?.graphic === 'correct' - ? '/correct.png' - : '/incorrect.png'} - alt="check or X" - width="30px" - /> - {/if} -
-
- {#if team.problems.find((p) => { - return p.id === problem.id; - })?.attempts !== 0} - {team.problems.find((p) => { - return p.id === problem.id; - })?.attempts} - {team.problems.find((p) => { - return p.id === problem.id; - })?.attempts === 1 - ? 'Attempt' - : 'Attempts'}
{#if team.problems.find((p) => { - return p.id === problem.id; - })?.min}{team.problems - .find((p) => { - return p.id === problem.id; - }) - ?.min?.toFixed(0)} min{/if} - {/if} -
-
-
-{/each} diff --git a/web/src/routes/admin/scoreboard/[contestId]/+page.server.ts b/web/src/routes/admin/scoreboard/[contestId]/+page.server.ts new file mode 100644 index 0000000..7638862 --- /dev/null +++ b/web/src/routes/admin/scoreboard/[contestId]/+page.server.ts @@ -0,0 +1,109 @@ +import { db } from '$lib/server/prisma'; +import { redirect } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +export const load = (async ({ params }) => { + const timestamp = new Date(); + const contest = await db.contest.findUnique({ + where: { id: parseInt(params.contestId) }, + include: { problems: true, teams: { include: { submissions: true } } } + }); + if (contest === null) { + throw redirect(302, '/admin/scoreboard'); + } + const data = { + timestamp: timestamp, + contest: { + name: contest.name, + problems: contest.problems.map((problem) => { + return { id: problem.id, friendlyName: problem.friendlyName }; + }), + teams: contest.teams + .map((team) => { + return { + name: team.name, + solves: team.submissions.filter((submission) => { + return submission.contestId === contest.id && submission.state === 'Correct'; + }).length, + time: (() => { + const correctSubmissions = team.submissions.filter((submission) => { + return submission.contestId === contest.id && submission.state === 'Correct'; + }); + const penaltyTime = + team.submissions.filter((submission) => { + return ( + submission.contestId === contest.id && + submission.state === 'Incorrect' && + correctSubmissions.find((correct) => { + return correct.problemId === submission.problemId; + }) + ); + }).length * 10; + let time = penaltyTime; + correctSubmissions.forEach((correctSubmission) => { + const gradedAt = correctSubmission.gradedAt!.valueOf(); + const min = (gradedAt - contest.startTime!.valueOf()) / 60000; + time += min; + }); + return time; + })(), + problems: contest.problems.map((problem) => { + return { + id: problem.id, + attempts: team.submissions.filter((submission) => { + return ( + submission.contestId === contest.id && + submission.problemId === problem.id && + (submission.state === 'Correct' || submission.state === 'Incorrect') + ); + }).length, + graphic: team.submissions.find((submission) => { + return ( + submission.contestId === contest.id && + submission.problemId === problem.id && + (submission.state === 'Correct' || submission.state === 'Incorrect') + ); + }) + ? team.submissions.find((submission) => { + return submission.problemId === problem.id && submission.state === 'Correct'; + }) + ? 'correct' + : 'incorrect' + : null, + min: (() => { + const correctSubmission = team.submissions.find((submission) => { + return ( + submission.contestId === contest.id && + submission.problemId === problem.id && + submission.state === 'Correct' + ); + }); + if (correctSubmission) { + const gradedAt = correctSubmission.gradedAt!.valueOf(); + return (gradedAt - contest.startTime!.valueOf()) / 60000; + } + return undefined; + })() + }; + }) + }; + }) + .sort((a, b) => { + if (a.solves > b.solves) { + return -1; + } else if (a.solves < b.solves) { + return 1; + } else { + if (a.time < b.time) { + return -1; + } else if (a.time > b.time) { + return 1; + } else { + return 0; + } + } + }) + } + }; + return data; +}) satisfies PageServerLoad; diff --git a/web/src/routes/admin/scoreboard/[contestId]/+page.svelte b/web/src/routes/admin/scoreboard/[contestId]/+page.svelte new file mode 100644 index 0000000..8319497 --- /dev/null +++ b/web/src/routes/admin/scoreboard/[contestId]/+page.svelte @@ -0,0 +1,109 @@ + + + + Admin Scoreboards + + +
+ {#if updating} +
+ {/if} + Last Updated: {data.timestamp.toLocaleTimeString()} +
+ +

{data.contest.name}

+
+
+
+ + + + + + + + + {#each data.contest.problems as problem} + + {/each} + + + + {#each data.contest.teams as team, i} + + + + + + {#each data.contest.problems as problem} + + {/each} + + {/each} + +
PlaceTeam NameSolvesTime{problem.friendlyName}
{i + 1}{team.name}{team.solves}{team.time.toFixed(0)} +
+
+ {#if team.problems.find((p) => { + return p.id === problem.id; + })?.graphic !== null} + { + return p.id === problem.id; + })?.graphic === 'correct' + ? '/correct.png' + : '/incorrect.png'} + alt="check or X" + width="30px" + /> + {/if} +
+
+ {#if team.problems.find((p) => { + return p.id === problem.id; + })?.attempts !== 0} + {team.problems.find((p) => { + return p.id === problem.id; + })?.attempts} + {team.problems.find((p) => { + return p.id === problem.id; + })?.attempts === 1 + ? 'Attempt' + : 'Attempts'}
{#if team.problems.find((p) => { + return p.id === problem.id; + })?.min}{team.problems + .find((p) => { + return p.id === problem.id; + }) + ?.min?.toFixed(0)} min{/if} + {/if} +
+
+
diff --git a/web/src/routes/admin/scoreboard/stores.ts b/web/src/routes/admin/scoreboard/stores.ts new file mode 100644 index 0000000..0a7e44b --- /dev/null +++ b/web/src/routes/admin/scoreboard/stores.ts @@ -0,0 +1,3 @@ +import { writable } from 'svelte/store'; + +export const selectedScoreboard = writable(null);