Add scoreboard and teams
This commit is contained in:
parent
dd54f383e1
commit
027f87167c
@ -38,9 +38,14 @@ model Submission {
|
|||||||
|
|
||||||
model Problem {
|
model Problem {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
friendlyName String
|
friendlyName String @unique
|
||||||
sampleInput String
|
sampleInput String
|
||||||
sampleOutput String
|
sampleOutput String
|
||||||
realInput String
|
realInput String
|
||||||
realOutput 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" 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/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/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>
|
<li><a href="/logout" class="nav-link px-2" data-sveltekit-preload-data="off">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</header>
|
</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