add rounds and prepare for frontend
This commit is contained in:
parent
a8d502f2ee
commit
f723d90e65
9 changed files with 207 additions and 164 deletions
|
@ -7,6 +7,7 @@ import * as fields from './schema/fields';
|
|||
import * as matches from './schema/matches';
|
||||
import * as permissions from './schema/permissions';
|
||||
import * as roles from './schema/roles';
|
||||
import * as rounds from './schema/rounds';
|
||||
import * as schedulingmodes from './schema/schedulingmodes';
|
||||
import * as sessions from './schema/sessions';
|
||||
import * as teams from './schema/teams';
|
||||
|
@ -24,6 +25,7 @@ export const db = drizzle(client, {
|
|||
...matches,
|
||||
...permissions,
|
||||
...roles,
|
||||
...rounds,
|
||||
...schedulingmodes,
|
||||
...sessions,
|
||||
...teams,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as t from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../util';
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { eq, relations } from 'drizzle-orm';
|
||||
import { users } from './users';
|
||||
import { schedulingModes } from './schedulingmodes';
|
||||
import { matches } from './matches';
|
||||
import { breakperiods } from './breakperiods';
|
||||
import { fields } from './fields';
|
||||
import { teams } from './teams';
|
||||
import { rounds } from './rounds';
|
||||
import { db } from '..';
|
||||
|
||||
export const competitions = t.pgTable('competitions', {
|
||||
id: t.serial('id').primaryKey(),
|
||||
|
@ -14,21 +14,55 @@ export const competitions = t.pgTable('competitions', {
|
|||
description: t.text('description'),
|
||||
start_date: t.timestamp({ withTimezone: true, mode: 'date' }).notNull(),
|
||||
location: t.text('location'),
|
||||
current_scheduling_mode_id: t.integer('current_scheduling_mode_id').notNull(),
|
||||
owner: t.text('owner').notNull(),
|
||||
...timestamps
|
||||
});
|
||||
|
||||
export const competitionsRelations = relations(competitions, ({ one, many }) => ({
|
||||
scheduling_modes: many(schedulingModes),
|
||||
owner: one(users, {
|
||||
fields: [competitions.owner],
|
||||
references: [users.id]
|
||||
}),
|
||||
matches: many(matches),
|
||||
breakperiods: many(breakperiods),
|
||||
fields: many(fields),
|
||||
teams: many(teams)
|
||||
teams: many(teams),
|
||||
rounds: many(rounds)
|
||||
}));
|
||||
|
||||
export type Competition = typeof competitions.$inferSelect;
|
||||
|
||||
export async function getCompetitionWithAll(id: number) {
|
||||
return await db.query.competitions.findFirst({
|
||||
where: eq(competitions.id, id),
|
||||
with: {
|
||||
breakperiods: true,
|
||||
fields: true,
|
||||
rounds: {
|
||||
with: {
|
||||
matches: true,
|
||||
schedulingMode: true
|
||||
}
|
||||
},
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCompetitionsWithAll(skip: number = 0, take: number = 10) {
|
||||
return await db.query.competitions.findMany({
|
||||
orderBy: (competitions, { desc }) => [desc(competitions.start_date)],
|
||||
with: {
|
||||
breakperiods: true,
|
||||
fields: true,
|
||||
rounds: {
|
||||
with: {
|
||||
matches: true,
|
||||
schedulingMode: true
|
||||
}
|
||||
},
|
||||
teams: true
|
||||
},
|
||||
limit: take,
|
||||
offset: skip
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import * as t from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../util';
|
||||
import { competitions } from './competitions';
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { fields } from './fields';
|
||||
import { schedulingModes } from './schedulingmodes';
|
||||
import { rounds } from './rounds';
|
||||
|
||||
export const status = t.pgEnum('match_status', ['pending', 'running', 'finished', 'cancelled']);
|
||||
|
||||
export const matches = t.pgTable('match', {
|
||||
export const matches = t.pgTable('matches', {
|
||||
id: t.serial('id').primaryKey(),
|
||||
competition_id: t.integer('competition_id').notNull(),
|
||||
scheduling_mode_id: t.integer('scheduling_mode_id').notNull(),
|
||||
field_id: t.integer('field_id').notNull(),
|
||||
team1: t.text('team1').notNull(),
|
||||
team2: t.text('team2').notNull(),
|
||||
|
@ -20,21 +17,18 @@ export const matches = t.pgTable('match', {
|
|||
score2: t.integer('score2').notNull(),
|
||||
status: status('status').notNull(),
|
||||
match_number: t.integer('match_number').notNull(),
|
||||
round_id: t.integer('round').notNull(),
|
||||
...timestamps
|
||||
});
|
||||
|
||||
export const matchRelations = relations(matches, ({ one }) => ({
|
||||
competition: one(competitions, {
|
||||
fields: [matches.competition_id],
|
||||
references: [competitions.id]
|
||||
}),
|
||||
field: one(fields, {
|
||||
fields: [matches.field_id],
|
||||
references: [fields.id]
|
||||
}),
|
||||
scheduling_mode: one(schedulingModes, {
|
||||
fields: [matches.scheduling_mode_id],
|
||||
references: [schedulingModes.id]
|
||||
round: one(rounds, {
|
||||
fields: [matches.round_id],
|
||||
references: [rounds.id]
|
||||
})
|
||||
}));
|
||||
|
||||
|
|
24
src/lib/server/db/schema/rounds.ts
Normal file
24
src/lib/server/db/schema/rounds.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { relations } from 'drizzle-orm';
|
||||
import * as t from 'drizzle-orm/pg-core';
|
||||
import { matches } from './matches';
|
||||
import { competitions } from './competitions';
|
||||
import { schedulingModes } from './schedulingmodes';
|
||||
|
||||
export const rounds = t.pgTable('rounds', {
|
||||
id: t.serial('id').primaryKey(),
|
||||
nb_matches: t.integer('nb_matches').notNull(),
|
||||
competition_id: t.integer('competition_id').notNull(),
|
||||
scheduling_mode: t.integer('scheduling_mode').notNull()
|
||||
});
|
||||
|
||||
export const roundsRelations = relations(rounds, ({ many, one }) => ({
|
||||
competition: one(competitions, {
|
||||
fields: [rounds.competition_id],
|
||||
references: [competitions.id]
|
||||
}),
|
||||
matches: many(matches),
|
||||
schedulingMode: one(schedulingModes, {
|
||||
fields: [rounds.scheduling_mode],
|
||||
references: [schedulingModes.id]
|
||||
})
|
||||
}));
|
|
@ -1,24 +1,24 @@
|
|||
import * as t from 'drizzle-orm/pg-core';
|
||||
import { timestamps } from '../util';
|
||||
import { competitions } from './competitions';
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { rounds } from './rounds';
|
||||
|
||||
export const schedulingModes = t.pgTable('scheduling_modes', {
|
||||
id: t.serial('id').primaryKey(),
|
||||
competition_id: t.integer('competition_id').notNull(),
|
||||
name: t.text('name').notNull(),
|
||||
description: t.text('description'),
|
||||
algorithm: t.text('algorithm').notNull(),
|
||||
config: t.jsonb('config').notNull(),
|
||||
sequence_order: t.integer('sequence_order').notNull(),
|
||||
status: t.text('status').notNull(),
|
||||
round_id: t.integer('round_id').notNull(),
|
||||
...timestamps
|
||||
});
|
||||
|
||||
export const schedulingModesRelations = relations(schedulingModes, ({ one }) => ({
|
||||
competition: one(competitions, {
|
||||
fields: [schedulingModes.competition_id],
|
||||
references: [competitions.id]
|
||||
round: one(rounds, {
|
||||
fields: [schedulingModes.round_id],
|
||||
references: [rounds.id]
|
||||
})
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { db } from '$lib/server/db';
|
||||
import { getCompetitionsWithAll } from '@/server/db/schema/competitions';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
|
@ -14,17 +14,7 @@ export const load: PageServerLoad = async ({ locals }) => {
|
|||
|
||||
try {
|
||||
// Fetch competitions from the database
|
||||
const userCompetitions = db.query.competitions.findMany({
|
||||
orderBy: (competitions, { desc }) => [desc(competitions.start_date)],
|
||||
limit: 10,
|
||||
with: {
|
||||
breakperiods: true,
|
||||
fields: true,
|
||||
matches: true,
|
||||
scheduling_modes: true,
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
const userCompetitions = getCompetitionsWithAll();
|
||||
|
||||
return {
|
||||
competitions: await userCompetitions
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { db } from '@/server/db';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { competitions } from '@/server/db/schema/competitions';
|
||||
import { competitions, getCompetitionWithAll } from '@/server/db/schema/competitions';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { and } from 'drizzle-orm';
|
||||
|
@ -10,21 +10,16 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
|||
try {
|
||||
const id = Number.parseInt(params.id);
|
||||
|
||||
const competition = await db.query.competitions.findFirst({
|
||||
where: and(eq(competitions.id, id), eq(competitions.owner, locals.user.id)),
|
||||
with: {
|
||||
breakperiods: true,
|
||||
fields: true,
|
||||
matches: true,
|
||||
scheduling_modes: true,
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
const competition = await getCompetitionWithAll(id);
|
||||
|
||||
if (!competition) {
|
||||
throw error(404, 'Competition not found');
|
||||
}
|
||||
|
||||
if (competition.owner !== locals.user.id) {
|
||||
throw error(403, 'You are not authorized to view this competition');
|
||||
}
|
||||
|
||||
return {
|
||||
competition: competition
|
||||
};
|
||||
|
@ -65,16 +60,11 @@ export const actions = {
|
|||
competition_id: id,
|
||||
name: teamName
|
||||
});
|
||||
const comp = await db.query.competitions.findFirst({
|
||||
where: and(eq(competitions.id, id), eq(competitions.owner, event.locals.user.id)),
|
||||
with: {
|
||||
breakperiods: true,
|
||||
fields: true,
|
||||
matches: true,
|
||||
scheduling_modes: true,
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
const comp = await getCompetitionWithAll(id);
|
||||
|
||||
if (!comp || comp.owner !== event.locals.user.id) {
|
||||
throw error(403, 'You are not authorized to update this competition');
|
||||
}
|
||||
|
||||
return {
|
||||
comp
|
||||
|
@ -110,16 +100,11 @@ export const actions = {
|
|||
|
||||
// remove team to competition's teams array
|
||||
await db.delete(teams).where(and(eq(teams.id, teamId), eq(teams.competition_id, id)));
|
||||
const comp = await db.query.competitions.findFirst({
|
||||
where: and(eq(competitions.id, id), eq(competitions.owner, event.locals.user.id)),
|
||||
with: {
|
||||
breakperiods: true,
|
||||
fields: true,
|
||||
matches: true,
|
||||
scheduling_modes: true,
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
const comp = await getCompetitionWithAll(id);
|
||||
|
||||
if (!comp || comp.owner !== event.locals.user.id) {
|
||||
throw error(403, 'You are not authorized to update this competition');
|
||||
}
|
||||
|
||||
return {
|
||||
comp
|
||||
|
@ -151,16 +136,12 @@ export const actions = {
|
|||
// update competition's description
|
||||
await db.update(competitions).set({ description }).where(eq(competitions.id, id));
|
||||
|
||||
const comp = await db.query.competitions.findFirst({
|
||||
where: and(eq(competitions.id, id), eq(competitions.owner, event.locals.user.id)),
|
||||
with: {
|
||||
breakperiods: true,
|
||||
fields: true,
|
||||
matches: true,
|
||||
scheduling_modes: true,
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
const comp = await getCompetitionWithAll(id);
|
||||
|
||||
if (!comp || comp.owner !== event.locals.user.id) {
|
||||
throw error(403, 'You are not authorized to update this competition');
|
||||
}
|
||||
|
||||
return {
|
||||
comp
|
||||
};
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
import Input from '@/components/ui/input/input.svelte';
|
||||
import { enhance } from '$app/forms';
|
||||
import Textarea from '@/components/ui/textarea/textarea.svelte';
|
||||
import Tabs from '@/components/ui/tabs/tabs.svelte';
|
||||
import TabsList from '@/components/ui/tabs/tabs-list.svelte';
|
||||
import TabsTrigger from '@/components/ui/tabs/tabs-trigger.svelte';
|
||||
import TabsContent from '@/components/ui/tabs/tabs-content.svelte';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
|
@ -53,97 +57,112 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 md:grid-cols-3">
|
||||
<Card class=" md:col-span-2">
|
||||
<CardHeader>
|
||||
<h2 class=" text-xl font-semibold">Description</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if showInputDescription}
|
||||
<form method="post" action="?/updateDescription" class="flex flex-col gap-4" use:enhance>
|
||||
<Textarea name="description" placeholder="Description" bind:value={description} />
|
||||
<div class="flex gap-2">
|
||||
<Button formaction="?/updateDescription" type="submit">Save</Button>
|
||||
<Button onclick={() => clearAndCloseDescription()}>Cancel</Button>
|
||||
</div>
|
||||
</form>
|
||||
{:else if data.competition.description}
|
||||
<p class="text-gray-700 dark:text-gray-300">{data.competition.description}</p>
|
||||
{:else}
|
||||
<p class="text-gray-500 italic">No description available</p>
|
||||
{/if}
|
||||
</CardContent>
|
||||
{#if !showInputDescription}
|
||||
<CardFooter class="flex gap-2">
|
||||
<Button onclick={() => (showInputDescription = true)}>Edit</Button>
|
||||
</CardFooter>
|
||||
{/if}
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 class="text-xl font-semibold">Details</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="flex items-center gap-2">
|
||||
<Clock class="h-5 w-5 text-blue-500" />
|
||||
<span>Created: {formatDate(new Date(data.competition.created_at))}</span>
|
||||
</div>
|
||||
{#if data.competition.updated_at}
|
||||
<div class="flex items-center gap-2">
|
||||
<Clock class="h-5 w-5 text-green-500" />
|
||||
<span>Updated: {formatDate(new Date(data.competition.updated_at))}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</CardContent>
|
||||
<CardFooter></CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="mt-6">
|
||||
<CardHeader>
|
||||
<h2 class="text-xl font-semibold">Teams</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if !data.competition.teams || data.competition.teams.length === 0}
|
||||
<p class="text-gray-500 italic">No teams have been added yet</p>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-4">
|
||||
{#each data.competition.teams as team}
|
||||
<div class="">
|
||||
<Tabs value="general">
|
||||
<TabsList>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="rounds">Rounds</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="general">
|
||||
<div class="grid gap-6 md:grid-cols-3">
|
||||
<Card class=" md:col-span-2">
|
||||
<CardHeader>
|
||||
<h2 class=" text-xl font-semibold">Description</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if showInputDescription}
|
||||
<form
|
||||
method="post"
|
||||
action="?/deleteTeam"
|
||||
class="flex items-center justify-between gap-2"
|
||||
action="?/updateDescription"
|
||||
class="flex flex-col gap-4"
|
||||
use:enhance
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<Users class="h-5 w-5 text-indigo-500" />
|
||||
<input type="hidden" name="id" value={team.id} />
|
||||
<span>{team.name}</span>
|
||||
<Textarea name="description" placeholder="Description" bind:value={description} />
|
||||
<div class="flex gap-2">
|
||||
<Button formaction="?/updateDescription" type="submit">Save</Button>
|
||||
<Button onclick={() => clearAndCloseDescription()}>Cancel</Button>
|
||||
</div>
|
||||
<Button formaction="?/deleteTeam" variant="destructive" type="submit"
|
||||
>Delete<Trash2Icon /></Button
|
||||
>
|
||||
</form>
|
||||
{:else if data.competition.description}
|
||||
<p class="text-gray-700 dark:text-gray-300">{data.competition.description}</p>
|
||||
{:else}
|
||||
<p class="text-gray-500 italic">No description available</p>
|
||||
{/if}
|
||||
</CardContent>
|
||||
{#if !showInputDescription}
|
||||
<CardFooter class="flex gap-2">
|
||||
<Button onclick={() => (showInputDescription = true)}>Edit</Button>
|
||||
</CardFooter>
|
||||
{/if}
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 class="text-xl font-semibold">Details</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="flex items-center gap-2">
|
||||
<Clock class="h-5 w-5 text-blue-500" />
|
||||
<span>Created: {formatDate(new Date(data.competition.created_at))}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showInputTeam}
|
||||
<form method="post" action="?/addTeam" class="mt-4 flex gap-4" use:enhance>
|
||||
<Input name="team_name" placeholder="Team Name" />
|
||||
<Button formaction="?/addTeam" type="submit">Save</Button>
|
||||
</form>
|
||||
{/if}
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button
|
||||
disabled={showInputTeam}
|
||||
onclick={() => {
|
||||
showInputTeam = true;
|
||||
}}>Add</Button
|
||||
>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
{#if data.competition.updated_at}
|
||||
<div class="flex items-center gap-2">
|
||||
<Clock class="h-5 w-5 text-green-500" />
|
||||
<span>Updated: {formatDate(new Date(data.competition.updated_at))}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</CardContent>
|
||||
<CardFooter></CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="mt-6">
|
||||
<CardHeader>
|
||||
<h2 class="text-xl font-semibold">Teams</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{#if !data.competition.teams || data.competition.teams.length === 0}
|
||||
<p class="text-gray-500 italic">No teams have been added yet</p>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-4">
|
||||
{#each data.competition.teams as team}
|
||||
<div class="">
|
||||
<form
|
||||
method="post"
|
||||
action="?/deleteTeam"
|
||||
class="flex items-center justify-between gap-2"
|
||||
use:enhance
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<Users class="h-5 w-5 text-indigo-500" />
|
||||
<input type="hidden" name="id" value={team.id} />
|
||||
<span>{team.name}</span>
|
||||
</div>
|
||||
<Button formaction="?/deleteTeam" variant="destructive" type="submit"
|
||||
>Delete<Trash2Icon /></Button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if showInputTeam}
|
||||
<form method="post" action="?/addTeam" class="mt-4 flex gap-4" use:enhance>
|
||||
<Input name="team_name" placeholder="Team Name" />
|
||||
<Button formaction="?/addTeam" type="submit">Save</Button>
|
||||
</form>
|
||||
{/if}
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button
|
||||
disabled={showInputTeam}
|
||||
onclick={() => {
|
||||
showInputTeam = true;
|
||||
}}>Add</Button
|
||||
>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="rounds"></TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
|
|
@ -45,7 +45,6 @@ export const actions = {
|
|||
start_date: startDate,
|
||||
location,
|
||||
owner: event.locals.user.id,
|
||||
current_scheduling_mode_id: -1, // This will be updated after creating the scheduling mode
|
||||
created_at: new Date(),
|
||||
updated_at: new Date()
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue