add round creation
This commit is contained in:
parent
9cc0ac3089
commit
764d0c0526
25 changed files with 356 additions and 27 deletions
|
@ -30,6 +30,7 @@
|
|||
"bits-ui": "^2.8.6",
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"formsnap": "^2.0.1",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"oxlint": "^1.5.0",
|
||||
"prettier": "^3.4.2",
|
||||
|
@ -38,6 +39,7 @@
|
|||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"sveltekit-superforms": "^2.26.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
|
|
8
src/app.d.ts
vendored
8
src/app.d.ts
vendored
|
@ -30,3 +30,11 @@ export interface NavItem {
|
|||
isActive?: boolean;
|
||||
requireAdmin?: boolean;
|
||||
}
|
||||
|
||||
export enum SchedulingMode {
|
||||
single = 'signle',
|
||||
double = 'double',
|
||||
swiss = 'swiss',
|
||||
round_robin = 'round_robin',
|
||||
double_round_robin = 'double_round_robin'
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import { cn } from '$lib/utils';
|
||||
import { getContext } from 'svelte';
|
||||
import Nav from '$lib/components/nav.svelte';
|
||||
import type { User } from '@/server/db/schema/users';
|
||||
import type { User } from '$lib/server/db/schema/users';
|
||||
|
||||
let {
|
||||
breadcrumbs,
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
import { Button } from '$lib/components/ui/button';
|
||||
import type { NavItem } from '../../app';
|
||||
import { Home, Trophy, Calendar, UserCircle, Settings } from 'lucide-svelte';
|
||||
import type { User } from '@/server/db/schema/users';
|
||||
import { type Role } from '@/server/db/schema/roles';
|
||||
import type { User } from '$lib/server/db/schema/users';
|
||||
import { type Role } from '$lib/server/db/schema/roles';
|
||||
import { page } from '$app/state';
|
||||
|
||||
let { className }: { className?: string } = $props();
|
||||
|
|
41
src/lib/components/rounds/create-round.svelte
Normal file
41
src/lib/components/rounds/create-round.svelte
Normal file
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import { superForm, type Infer, type SuperValidated } from 'sveltekit-superforms';
|
||||
import { FormControl } from '../ui/form';
|
||||
import FormDescription from '../ui/form/form-description.svelte';
|
||||
import FormFieldErrors from '../ui/form/form-field-errors.svelte';
|
||||
import FormField from '../ui/form/form-field.svelte';
|
||||
import FormLabel from '../ui/form/form-label.svelte';
|
||||
import { formSchema, type FormSchema } from './create-schema';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import RadioGroup from '../ui/radio-group/radio-group.svelte';
|
||||
import RadioGroupItem from '../ui/radio-group/radio-group-item.svelte';
|
||||
import { SchedulingMode } from '@/app';
|
||||
|
||||
let { data }: { data: { form: SuperValidated<Infer<FormSchema>> } } = $props();
|
||||
|
||||
const form = superForm(data.form, {
|
||||
validators: zodClient(formSchema)
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
</script>
|
||||
|
||||
<form method="post" use:enhance>
|
||||
<FormField {form} name="scheduling_mode">
|
||||
<RadioGroup required bind:value={$formData.scheduling_mode} name="scheduling_mode">
|
||||
{#each Object.values(SchedulingMode) as mode}
|
||||
<div class="flex items-center gap-4">
|
||||
<FormControl>
|
||||
{#snippet children({ props })}
|
||||
<RadioGroupItem value={mode} {...props} />
|
||||
<FormLabel>{mode}</FormLabel>
|
||||
{/snippet}
|
||||
</FormControl>
|
||||
</div>
|
||||
{/each}
|
||||
</RadioGroup>
|
||||
|
||||
<FormDescription />
|
||||
<FormFieldErrors />
|
||||
</FormField>
|
||||
</form>
|
7
src/lib/components/rounds/create-schema.ts
Normal file
7
src/lib/components/rounds/create-schema.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import z from 'zod';
|
||||
|
||||
export const formSchema = z.object({
|
||||
scheduling_mode: z.nativeEnum(SchedulingMode)
|
||||
});
|
||||
|
||||
export type FormSchema = typeof formSchema;
|
10
src/lib/components/rounds/rounds.svelte
Normal file
10
src/lib/components/rounds/rounds.svelte
Normal file
|
@ -0,0 +1,10 @@
|
|||
<script lang="ts">
|
||||
import CreateRound from './create-round.svelte';
|
||||
|
||||
let { rounds, createForm } = $props();
|
||||
</script>
|
||||
|
||||
{JSON.stringify(rounds)}
|
||||
<div></div>
|
||||
|
||||
<CreateRound data={{ form: createForm }} />
|
7
src/lib/components/ui/form/form-button.svelte
Normal file
7
src/lib/components/ui/form/form-button.svelte
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
import * as Button from "$lib/components/ui/button/index.js";
|
||||
|
||||
let { ref = $bindable(null), ...restProps }: Button.Props = $props();
|
||||
</script>
|
||||
|
||||
<Button.Root bind:ref type="submit" {...restProps} />
|
17
src/lib/components/ui/form/form-description.svelte
Normal file
17
src/lib/components/ui/form/form-description.svelte
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.DescriptionProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Description
|
||||
bind:ref
|
||||
data-slot="form-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
24
src/lib/components/ui/form/form-element-field.svelte
Normal file
24
src/lib/components/ui/form/form-element-field.svelte
Normal file
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { FormPathLeaves } from "sveltekit-superforms";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
form,
|
||||
name,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> &
|
||||
FormPrimitive.ElementFieldProps<T, U> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.ElementField {form} {name}>
|
||||
{#snippet children({ constraints, errors, tainted, value })}
|
||||
<div bind:this={ref} class={cn("space-y-2", className)} {...restProps}>
|
||||
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FormPrimitive.ElementField>
|
30
src/lib/components/ui/form/form-field-errors.svelte
Normal file
30
src/lib/components/ui/form/form-field-errors.svelte
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
errorClasses,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.FieldErrorsProps> & {
|
||||
errorClasses?: string | undefined | null;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.FieldErrors
|
||||
bind:ref
|
||||
class={cn("text-destructive text-sm font-medium", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ errors, errorProps })}
|
||||
{#if childrenProp}
|
||||
{@render childrenProp({ errors, errorProps })}
|
||||
{:else}
|
||||
{#each errors as error (error)}
|
||||
<div {...errorProps} class={cn(errorClasses)}>{error}</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{/snippet}
|
||||
</FormPrimitive.FieldErrors>
|
29
src/lib/components/ui/form/form-field.svelte
Normal file
29
src/lib/components/ui/form/form-field.svelte
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { FormPath } from "sveltekit-superforms";
|
||||
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
form,
|
||||
name,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
}: FormPrimitive.FieldProps<T, U> &
|
||||
WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Field {form} {name}>
|
||||
{#snippet children({ constraints, errors, tainted, value })}
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="form-item"
|
||||
class={cn("space-y-2", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
|
||||
</div>
|
||||
{/snippet}
|
||||
</FormPrimitive.Field>
|
15
src/lib/components/ui/form/form-fieldset.svelte
Normal file
15
src/lib/components/ui/form/form-fieldset.svelte
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import type { FormPath } from "sveltekit-superforms";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
form,
|
||||
name,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.FieldsetProps<T, U>> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Fieldset bind:ref {form} {name} class={cn("space-y-2", className)} {...restProps} />
|
24
src/lib/components/ui/form/form-label.svelte
Normal file
24
src/lib/components/ui/form/form-label.svelte
Normal file
|
@ -0,0 +1,24 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { Label } from "$lib/components/ui/label/index.js";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
children,
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.LabelProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Label {...restProps} bind:ref>
|
||||
{#snippet child({ props })}
|
||||
<Label
|
||||
{...props}
|
||||
data-slot="form-label"
|
||||
class={cn("data-[fs-error]:text-destructive", className)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</Label>
|
||||
{/snippet}
|
||||
</FormPrimitive.Label>
|
16
src/lib/components/ui/form/form-legend.svelte
Normal file
16
src/lib/components/ui/form/form-legend.svelte
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
import * as FormPrimitive from "formsnap";
|
||||
import { cn, type WithoutChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChild<FormPrimitive.LegendProps> = $props();
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Legend
|
||||
bind:ref
|
||||
class={cn("data-[fs-error]:text-destructive text-sm font-medium leading-none", className)}
|
||||
{...restProps}
|
||||
/>
|
33
src/lib/components/ui/form/index.ts
Normal file
33
src/lib/components/ui/form/index.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import * as FormPrimitive from "formsnap";
|
||||
import Description from "./form-description.svelte";
|
||||
import Label from "./form-label.svelte";
|
||||
import FieldErrors from "./form-field-errors.svelte";
|
||||
import Field from "./form-field.svelte";
|
||||
import Fieldset from "./form-fieldset.svelte";
|
||||
import Legend from "./form-legend.svelte";
|
||||
import ElementField from "./form-element-field.svelte";
|
||||
import Button from "./form-button.svelte";
|
||||
|
||||
const Control = FormPrimitive.Control;
|
||||
|
||||
export {
|
||||
Field,
|
||||
Control,
|
||||
Label,
|
||||
Button,
|
||||
FieldErrors,
|
||||
Description,
|
||||
Fieldset,
|
||||
Legend,
|
||||
ElementField,
|
||||
//
|
||||
Field as FormField,
|
||||
Control as FormControl,
|
||||
Description as FormDescription,
|
||||
Label as FormLabel,
|
||||
FieldErrors as FormFieldErrors,
|
||||
Fieldset as FormFieldset,
|
||||
Legend as FormLegend,
|
||||
ElementField as FormElementField,
|
||||
Button as FormButton,
|
||||
};
|
10
src/lib/components/ui/radio-group/index.ts
Normal file
10
src/lib/components/ui/radio-group/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import Root from "./radio-group.svelte";
|
||||
import Item from "./radio-group-item.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Item,
|
||||
//
|
||||
Root as RadioGroup,
|
||||
Item as RadioGroupItem,
|
||||
};
|
31
src/lib/components/ui/radio-group/radio-group-item.svelte
Normal file
31
src/lib/components/ui/radio-group/radio-group-item.svelte
Normal file
|
@ -0,0 +1,31 @@
|
|||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="radio-group-item"
|
||||
class={cn(
|
||||
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 shadow-xs aspect-square size-4 shrink-0 rounded-full border outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<div data-slot="radio-group-indicator" class="relative flex items-center justify-center">
|
||||
{#if checked}
|
||||
<CircleIcon
|
||||
class="fill-primary absolute left-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</RadioGroupPrimitive.Item>
|
19
src/lib/components/ui/radio-group/radio-group.svelte
Normal file
19
src/lib/components/ui/radio-group/radio-group.svelte
Normal file
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value = $bindable(""),
|
||||
...restProps
|
||||
}: RadioGroupPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Root
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="radio-group"
|
||||
class={cn("grid gap-3", className)}
|
||||
{...restProps}
|
||||
/>
|
|
@ -3,7 +3,7 @@
|
|||
import '../app.css';
|
||||
import { setContext } from 'svelte';
|
||||
import type { PageProps } from './$types';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import { Toaster } from '$lib/components/ui/sonner';
|
||||
|
||||
let {
|
||||
children,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { getCompetitionsWithAll } from '@/server/db/schema/competitions';
|
||||
import { getCompetitionsWithAll } from '$lib/server/db/schema/competitions';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ locals }) => {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import type { PageProps } from './$types.js';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { formatDate } from '@/utils';
|
||||
import { formatDate } from '$lib/utils';
|
||||
|
||||
const breadcrumbs: BreadcrumbItem[] = [
|
||||
{
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { db } from '@/server/db';
|
||||
import { db } from '$lib/server/db';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { competitions, getCompetitionWithAll } from '@/server/db/schema/competitions';
|
||||
import { competitions, getCompetitionWithAll } from '$lib/server/db/schema/competitions';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { and } from 'drizzle-orm';
|
||||
import { teams } from '@/server/db/schema/teams';
|
||||
import { teams } from '$lib/server/db/schema/teams';
|
||||
import { superValidate } from 'sveltekit-superforms';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { formSchema } from '$lib/components/rounds/create-schema';
|
||||
|
||||
export const load: PageServerLoad = async ({ params, locals }) => {
|
||||
try {
|
||||
|
@ -21,7 +24,8 @@ export const load: PageServerLoad = async ({ params, locals }) => {
|
|||
}
|
||||
|
||||
return {
|
||||
competition: competition
|
||||
competition: competition,
|
||||
createForm: await superValidate(zod(formSchema))
|
||||
};
|
||||
} catch (e) {
|
||||
return redirect(302, '/');
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
<script lang="ts">
|
||||
import Badge from '@/components/ui/badge/badge.svelte';
|
||||
import Badge from '$lib/components/ui/badge/badge.svelte';
|
||||
import type { PageProps } from './$types';
|
||||
import { CalendarDays, MapPin, Users, Trophy, Clock, Trash2Icon } from 'lucide-svelte';
|
||||
import { formatDate } from '@/utils';
|
||||
import Button from '@/components/ui/button/button.svelte';
|
||||
import Card from '@/components/ui/card/card.svelte';
|
||||
import CardHeader from '@/components/ui/card/card-header.svelte';
|
||||
import CardContent from '@/components/ui/card/card-content.svelte';
|
||||
import CardFooter from '@/components/ui/card/card-footer.svelte';
|
||||
import Input from '@/components/ui/input/input.svelte';
|
||||
import { formatDate } from '$lib/utils';
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import Card from '$lib/components/ui/card/card.svelte';
|
||||
import CardHeader from '$lib/components/ui/card/card-header.svelte';
|
||||
import CardContent from '$lib/components/ui/card/card-content.svelte';
|
||||
import CardFooter from '$lib/components/ui/card/card-footer.svelte';
|
||||
import Input from '$lib/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';
|
||||
import Textarea from '$lib/components/ui/textarea/textarea.svelte';
|
||||
import Tabs from '$lib/components/ui/tabs/tabs.svelte';
|
||||
import TabsList from '$lib/components/ui/tabs/tabs-list.svelte';
|
||||
import TabsTrigger from '$lib/components/ui/tabs/tabs-trigger.svelte';
|
||||
import TabsContent from '$lib/components/ui/tabs/tabs-content.svelte';
|
||||
import { Svelvet } from 'svelvet';
|
||||
import Rounds from '$lib/components/rounds/rounds.svelte';
|
||||
|
||||
let { data }: PageProps = $props();
|
||||
|
||||
|
@ -58,7 +59,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs value="general">
|
||||
<Tabs value="rounds">
|
||||
<TabsList>
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="rounds">Rounds</TabsTrigger>
|
||||
|
@ -165,7 +166,9 @@
|
|||
</TabsContent>
|
||||
|
||||
<TabsContent value="rounds">
|
||||
<Svelvet id="rounds-canvas" TD minimap controls locked></Svelvet>
|
||||
{#if !data.competition.rounds || data.competition.rounds.length === 0}
|
||||
<Rounds createForm={data.createForm} rounds={data.competition.rounds} />
|
||||
{/if}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
|
|
@ -9,8 +9,7 @@ const config = {
|
|||
kit: {
|
||||
adapter: adapter(),
|
||||
alias: {
|
||||
'@/*': './src/lib/*',
|
||||
'~': './src/'
|
||||
'@/*': './src/*'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue