starting to have a bracket system that is previewable

This commit is contained in:
unurled 2025-07-14 20:48:29 +02:00
parent 62127cc5e4
commit 82ecf80068
Signed by: unurled
GPG key ID: EFC5F5E709B47DDD
82 changed files with 3461 additions and 637 deletions

View file

@ -0,0 +1,120 @@
<script lang="ts">
import Button from '$ui/button/button.svelte';
import CardContent from '$ui/card/card-content.svelte';
import CardHeader from '$ui/card/card-header.svelte';
import Card from '$ui/card/card.svelte';
import Input from '$ui/input/input.svelte';
import Label from '$ui/label/label.svelte';
import RadioGroupItem from '$ui/radio-group/radio-group-item.svelte';
import RadioGroup from '$ui/radio-group/radio-group.svelte';
import type { Bracket } from '@/lib/server/db/schema/brackets';
import type { Round } from '@/lib/server/db/schema/rounds';
import { cn, instanceOf } from '@/lib/utils';
import { SchedulingMode } from '@/types';
import { ArrowRightIcon, CircleQuestionMarkIcon } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import { toast } from 'svelte-sonner';
import { Tooltip, TooltipProvider } from '../ui/tooltip';
import TooltipContent from '../ui/tooltip/tooltip-content.svelte';
import TooltipTrigger from '../ui/tooltip/tooltip-trigger.svelte';
let {
competitionId,
brackets = $bindable(),
showAddBracket = $bindable(true)
}: { competitionId: string; brackets: Bracket[]; showAddBracket: boolean } = $props();
let schedulingMode: SchedulingMode = $state(SchedulingMode.single);
let name = $state('');
let nameInvalid = $state(false);
let size: number | undefined = $state();
let sizeInvalid = $state(false);
let buttonDisabled = $state(false);
async function submit() {
if (name.length === 0) {
nameInvalid = true;
toast.error('Name is required');
return;
}
if (!size || size < 0) {
sizeInvalid = true;
toast.error('Number of matches must be greater than 0');
return;
}
nameInvalid = false;
// loading/disable button
buttonDisabled = true;
const response = await fetch(`/api/competitions/${competitionId}`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify({ scheduling_mode: schedulingMode, name: name, size: size })
});
// update rounds
const data = await response.json();
buttonDisabled = false;
if (Array.isArray(data) && data.length > 0 && instanceOf<Bracket>(data[0], 'id')) {
brackets = [...brackets, data[0]];
showAddBracket = false;
} else {
throw new Error('Invalid bracket');
}
}
</script>
<div class="flex flex-col gap-4">
<div class="grid grid-cols-2 grid-rows-2 gap-4">
<Label for="name" class="text-start">Name<span class="text-red-500">*</span></Label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Label for="size" class="text-start"
>Size<span class="text-red-500">*</span><CircleQuestionMarkIcon /></Label
>
</TooltipTrigger>
<TooltipContent>Number of participants at the beginning of the stage.</TooltipContent>
</Tooltip>
</TooltipProvider>
<Input
aria-invalid={nameInvalid}
name="name"
type="text"
bind:value={name}
placeholder="Name"
/>
<Input
aria-invalid={sizeInvalid}
name="size"
type="number"
bind:value={size}
placeholder="Number of matches"
/>
</div>
<RadioGroup
required
bind:value={() => schedulingMode, (t: SchedulingMode) => {}}
name="scheduling_mode"
>
{#each Object.values(SchedulingMode) as mode}
<button
class="flex h-full w-full items-center gap-4 rounded-xl"
onclick={() => (schedulingMode = mode)}
>
<Card class={cn('w-full', schedulingMode === mode && ' border-green-500')}>
<CardHeader class="flex justify-between">
<Label class="text-start">{$_(mode)}</Label>
</CardHeader>
<CardContent>
<RadioGroupItem hidden value={mode} />
<Label class="text-start">{$_(`${mode}.description`)}</Label>
</CardContent>
</Card>
</button>
{/each}
</RadioGroup>
<div class="flex w-full justify-end">
<Button disabled={buttonDisabled} onclick={submit}>Add Round<ArrowRightIcon /></Button>
</div>
</div>