Add scoreboard and teams
This commit is contained in:
parent
dd54f383e1
commit
027f87167c
@ -38,9 +38,14 @@ model Submission {
|
||||
|
||||
model Problem {
|
||||
id Int @id @default(autoincrement())
|
||||
friendlyName String
|
||||
friendlyName String @unique
|
||||
sampleInput String
|
||||
sampleOutput String
|
||||
realInput String
|
||||
realOutput String
|
||||
}
|
||||
|
||||
model Team {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
<li><a href="/admin" class="nav-link px-2">Home</a></li>
|
||||
<li><a href="/admin/reviews" class="nav-link px-2">Reviews</a></li>
|
||||
<li><a href="/admin/problems" class="nav-link px-2">Problems</a></li>
|
||||
<li><a href="/admin/scoreboard" class="nav-link px-2">Scoreboard</a></li>
|
||||
<li><a href="/admin/teams" class="nav-link px-2">Teams</a></li>
|
||||
<li><a href="/logout" class="nav-link px-2" data-sveltekit-preload-data="off">Logout</a></li>
|
||||
</ul>
|
||||
</header>
|
||||
|
17
web/src/routes/admin/scoreboard/+page.server.ts
Normal file
17
web/src/routes/admin/scoreboard/+page.server.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { db } from '$lib/server/prisma';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async () => {
|
||||
const timestamp = new Date();
|
||||
const problems = await db.problem.findMany();
|
||||
const teams = await db.team.findMany();
|
||||
return {
|
||||
timestamp: timestamp,
|
||||
problems: problems.map((row) => {
|
||||
return { friendlyName: row.friendlyName };
|
||||
}),
|
||||
teams: teams.map((row) => {
|
||||
return { name: row.name };
|
||||
})
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
62
web/src/routes/admin/scoreboard/+page.svelte
Normal file
62
web/src/routes/admin/scoreboard/+page.svelte
Normal file
@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let updateInterval: number;
|
||||
let updating = false;
|
||||
|
||||
onMount(() => {
|
||||
updateInterval = setInterval(async () => {
|
||||
updating = true;
|
||||
await invalidateAll();
|
||||
updating = false;
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
clearInterval(updateInterval);
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Admin Scoreboard</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">Admin Scoreboard</h1>
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="text-end">
|
||||
{#if updating}
|
||||
<div class="spinner-border spinner-border-sm text-secondary" />
|
||||
{/if}
|
||||
<strong>Last Updated: </strong>{data.timestamp.toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Team Name</th>
|
||||
{#each data.problems as problem}
|
||||
<th>{problem.friendlyName}</th>
|
||||
{/each}
|
||||
<th>Total Correct</th>
|
||||
<th>Total Points</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data.teams as team}
|
||||
<tr>
|
||||
<td>{team.name}</td>
|
||||
{#each data.problems as _}
|
||||
<td>-/-</td>
|
||||
{/each}
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
50
web/src/routes/admin/teams/+page.server.ts
Normal file
50
web/src/routes/admin/teams/+page.server.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { db } from '$lib/server/prisma';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async () => {
|
||||
const teams = await db.team.findMany();
|
||||
teams.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return {
|
||||
teams: teams.map((row) => {
|
||||
return { id: row.id, name: row.name };
|
||||
})
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
||||
|
||||
export const actions = {
|
||||
add: async ({ request }) => {
|
||||
const data = await request.formData();
|
||||
const name = data.get('name');
|
||||
if (!name) {
|
||||
return { success: false };
|
||||
}
|
||||
try {
|
||||
await db.team.create({ data: { name: name.toString() } });
|
||||
} catch {
|
||||
return { success: false };
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
delete: async ({ request }) => {
|
||||
const data = await request.formData();
|
||||
const teamId = data.get('teamId');
|
||||
if (!teamId) {
|
||||
return { success: false };
|
||||
}
|
||||
const teamIdNum = parseInt(teamId.toString());
|
||||
try {
|
||||
await db.team.delete({ where: { id: teamIdNum } });
|
||||
} catch {
|
||||
return { success: false };
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
} satisfies Actions;
|
97
web/src/routes/admin/teams/+page.svelte
Normal file
97
web/src/routes/admin/teams/+page.svelte
Normal file
@ -0,0 +1,97 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import type { Actions, PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
export let form: Actions;
|
||||
|
||||
let adding = false;
|
||||
let deleting = false;
|
||||
|
||||
$: if (form && form.success) {
|
||||
adding = false;
|
||||
deleting = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Teams</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">Teams</h1>
|
||||
|
||||
{#if form && !form.success}
|
||||
<div class="alert alert-danger">Invalid action</div>
|
||||
{/if}
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="text-end">
|
||||
{#if !deleting}
|
||||
<button
|
||||
on:click={() => {
|
||||
deleting = true;
|
||||
}}
|
||||
type="button"
|
||||
class="btn btn-outline-danger">Delete</button
|
||||
>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => {
|
||||
deleting = false;
|
||||
}}
|
||||
type="button"
|
||||
class="btn btn-outline-danger">Cancel</button
|
||||
>
|
||||
{/if}
|
||||
{#if !adding}
|
||||
<button
|
||||
on:click={() => {
|
||||
adding = true;
|
||||
}}
|
||||
type="button"
|
||||
class="btn btn-outline-success">Add</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if adding}
|
||||
<form class="mb-3" method="POST" action="?/add" use:enhance>
|
||||
<h5>Name</h5>
|
||||
<input id="name" name="name" class="form-control" />
|
||||
<div class="mt-3 row">
|
||||
<div class="text-end">
|
||||
<button
|
||||
on:click={() => {
|
||||
adding = false;
|
||||
}}
|
||||
type="button"
|
||||
class="btn btn-outline-secondary">Cancel</button
|
||||
>
|
||||
<button type="submit" class="btn btn-success">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
<div class="list-group">
|
||||
{#each data.teams as team}
|
||||
<div class="list-group-item">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
{team.name}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
{#if deleting}
|
||||
<form method="POST" action="?/delete" use:enhance>
|
||||
<div class="text-end">
|
||||
<input type="hidden" value={team.id} name="teamId" />
|
||||
<button type="submit" class="btn btn-danger">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user