Add contests
This commit is contained in:
parent
cb1e3060fd
commit
03f55baf1c
@ -48,10 +48,19 @@ model Problem {
|
||||
realInput String
|
||||
realOutput String
|
||||
Submission Submission[]
|
||||
contests Contest[] @relation("ProblemContestRelation")
|
||||
}
|
||||
|
||||
model Team {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
Submission Submission[]
|
||||
contests Contest[] @relation("TeamContestRelation")
|
||||
}
|
||||
|
||||
model Contest {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
teams Team[] @relation("TeamContestRelation")
|
||||
problems Problem[] @relation("ProblemContestRelation")
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
<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="/admin/contests" class="nav-link px-2">Contests</a></li>
|
||||
<li><a href="/logout" class="nav-link px-2" data-sveltekit-preload-data="off">Logout</a></li>
|
||||
</ul>
|
||||
</header>
|
||||
|
11
web/src/routes/admin/contests/+page.server.ts
Normal file
11
web/src/routes/admin/contests/+page.server.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { db } from '$lib/server/prisma';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async () => {
|
||||
const contests = await db.contest.findMany();
|
||||
return {
|
||||
contests: contests.map((contest) => {
|
||||
return { id: contest.id, name: contest.name };
|
||||
})
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
26
web/src/routes/admin/contests/+page.svelte
Normal file
26
web/src/routes/admin/contests/+page.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Contests</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">Contests</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="text-end">
|
||||
<a href="/admin/contests/create" class="btn btn-outline-success">Create</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 list-group">
|
||||
{#each data.contests as contest}
|
||||
<a
|
||||
href={'/admin/contests/' + contest.id.toString()}
|
||||
class="list-group-item list-group-item-action">{contest.name}</a
|
||||
>
|
||||
{/each}
|
||||
</div>
|
26
web/src/routes/admin/contests/[contestId]/+page.server.ts
Normal file
26
web/src/routes/admin/contests/[contestId]/+page.server.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { db } from '$lib/server/prisma';
|
||||
|
||||
export const load = (async ({ params }) => {
|
||||
const contestId = parseInt(params.contestId);
|
||||
if (isNaN(contestId)) {
|
||||
throw error(400, 'Invalid request');
|
||||
}
|
||||
const contest = await db.contest.findUnique({
|
||||
where: { id: contestId },
|
||||
include: { problems: true, teams: true }
|
||||
});
|
||||
if (!contest) {
|
||||
throw redirect(302, '/admin/contests');
|
||||
}
|
||||
return {
|
||||
name: contest.name,
|
||||
problems: contest.problems.map((problem) => {
|
||||
return { name: problem.friendlyName };
|
||||
}),
|
||||
teams: contest.teams.map((team) => {
|
||||
return { name: team.name };
|
||||
})
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
36
web/src/routes/admin/contests/[contestId]/+page.svelte
Normal file
36
web/src/routes/admin/contests/[contestId]/+page.svelte
Normal file
@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{data.name}</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">{data.name}</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<a href="/admin/contests" class="btn btn-outline-primary">All Contests</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 row">
|
||||
<div class="col-6">
|
||||
<h4>Teams</h4>
|
||||
<div class="list-group">
|
||||
{#each data.teams as team}
|
||||
<div class="list-group-item">{team.name}</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h4>Problems</h4>
|
||||
<div class="list-group">
|
||||
{#each data.problems as problem}
|
||||
<div class="list-group-item">{problem.name}</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
47
web/src/routes/admin/contests/create/+page.server.ts
Normal file
47
web/src/routes/admin/contests/create/+page.server.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { db } from '$lib/server/prisma';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async () => {
|
||||
const teams = await db.team.findMany();
|
||||
const problems = await db.problem.findMany();
|
||||
return {
|
||||
teams: teams.map((row) => {
|
||||
return { id: row.id, name: row.name };
|
||||
}),
|
||||
problems: problems.map((row) => {
|
||||
return { id: row.id, name: row.friendlyName };
|
||||
})
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
||||
|
||||
export const actions = {
|
||||
create: async ({ request }) => {
|
||||
const data = await request.formData();
|
||||
const name = data.get('name');
|
||||
const problems = (await db.problem.findMany()).filter((problem) => {
|
||||
return data.get('problem_' + problem.id) !== null;
|
||||
});
|
||||
const teams = (await db.team.findMany()).filter((team) => {
|
||||
return data.get('team_' + team.id) !== null;
|
||||
});
|
||||
if (!name) {
|
||||
return { success: false };
|
||||
}
|
||||
await db.contest.create({
|
||||
data: {
|
||||
name: name.toString(),
|
||||
teams: {
|
||||
connect: teams.map((team) => {
|
||||
return { id: team.id };
|
||||
})
|
||||
},
|
||||
problems: {
|
||||
connect: problems.map((problem) => {
|
||||
return { id: problem.id };
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
} satisfies Actions;
|
115
web/src/routes/admin/contests/create/+page.svelte
Normal file
115
web/src/routes/admin/contests/create/+page.svelte
Normal file
@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { Actions, PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
export let form: Actions;
|
||||
|
||||
$: if (form && form.success) {
|
||||
goto('/admin/contests');
|
||||
}
|
||||
|
||||
function selectTeamsAll() {
|
||||
document.querySelectorAll<HTMLInputElement>('.team-checkbox').forEach((elem) => {
|
||||
elem.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
function selectTeamsNone() {
|
||||
document.querySelectorAll<HTMLInputElement>('.team-checkbox').forEach((elem) => {
|
||||
elem.checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
function selectProblemsAll() {
|
||||
document.querySelectorAll<HTMLInputElement>('.problem-checkbox').forEach((elem) => {
|
||||
elem.checked = true;
|
||||
});
|
||||
}
|
||||
|
||||
function selectProblemsNone() {
|
||||
document.querySelectorAll<HTMLInputElement>('.problem-checkbox').forEach((elem) => {
|
||||
elem.checked = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Create Contest</title>
|
||||
</svelte:head>
|
||||
|
||||
<h1 style="text-align:center" class="mb-4">Create Contest</h1>
|
||||
|
||||
<a href="/admin/contests" class="mb-3 btn btn-outline-secondary">Cancel</a>
|
||||
|
||||
{#if form && !form.success}
|
||||
<div class="alert alert-danger">Invalid entry</div>
|
||||
{/if}
|
||||
|
||||
<form method="POST" action="?/create" use:enhance>
|
||||
<h4>Name</h4>
|
||||
<input name="name" class="form-control" />
|
||||
|
||||
<div class="mt-3 row">
|
||||
<div class="col-6">
|
||||
<h4>Teams</h4>
|
||||
<div class="row mb-2">
|
||||
<div>
|
||||
<button on:click={selectTeamsAll} type="button" class="btn btn-outline-secondary btn-sm"
|
||||
>Select All</button
|
||||
>
|
||||
<button on:click={selectTeamsNone} type="button" class="btn btn-outline-secondary btn-sm"
|
||||
>Select None</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{#each data.teams as team}
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="team-checkbox form-check-input"
|
||||
type="checkbox"
|
||||
value={team.id}
|
||||
id={'team_' + team.id}
|
||||
name={'team_' + team.id}
|
||||
/>
|
||||
<label class="form-check-label" for={'team_' + team.id}>{team.name}</label>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<h4>Problems</h4>
|
||||
<div class="row mb-2">
|
||||
<div>
|
||||
<button
|
||||
on:click={selectProblemsAll}
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-sm">Select All</button
|
||||
>
|
||||
<button
|
||||
on:click={selectProblemsNone}
|
||||
type="button"
|
||||
class="btn btn-outline-secondary btn-sm">Select None</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{#each data.problems as problem}
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="problem-checkbox form-check-input"
|
||||
type="checkbox"
|
||||
value={problem.id}
|
||||
id={'problem_' + problem.id}
|
||||
name={'problem_' + problem.id}
|
||||
/>
|
||||
<label class="form-check-label" for={'problem_' + problem.id}>{problem.name}</label>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
Loading…
Reference in New Issue
Block a user