feat: Enhance event management with FightEdit and GroupEdit components, including improved data handling and new functionalities
This commit is contained in:
@ -1,45 +1,36 @@
|
||||
<script lang="ts">
|
||||
import type { EventFight } from "@type/event";
|
||||
import type { EventFight, EventFightEdit, ResponseGroups, UpdateEventGroup, GroupUpdateEdit, SWEvent } from "@type/event";
|
||||
import { fromAbsolute, now, ZonedDateTime } from "@internationalized/date";
|
||||
import { Label } from "@components/ui/label";
|
||||
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
||||
import { gamemodes, maps } from "@components/stores/stores";
|
||||
import { CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Command } from "@components/ui/command";
|
||||
import { ChevronsUpDown, Check } from "lucide-svelte";
|
||||
import { ChevronsUpDown, Check, ChevronsUpDownIcon, PlusIcon, CheckIcon, MinusIcon } from "lucide-svelte";
|
||||
import { Button } from "@components/ui/button";
|
||||
import { cn } from "@components/utils";
|
||||
import type { Team } from "@components/types/team";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
|
||||
import type { Snippet } from "svelte";
|
||||
import { Input } from "@components/ui/input";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
||||
import { eventRepo } from "@components/repo/event";
|
||||
import GroupEdit from "./GroupEdit.svelte";
|
||||
|
||||
const {
|
||||
fight,
|
||||
teams,
|
||||
event,
|
||||
actions,
|
||||
onSave,
|
||||
groups,
|
||||
}: {
|
||||
fight: EventFight | null;
|
||||
teams: Team[];
|
||||
event: SWEvent;
|
||||
groups: ResponseGroups[];
|
||||
actions: Snippet<[boolean, () => void]>;
|
||||
onSave: (fight: {
|
||||
spielmodus: string;
|
||||
map: string;
|
||||
blueTeam: {
|
||||
id: number;
|
||||
name: string;
|
||||
kuerzel: string;
|
||||
color: string;
|
||||
};
|
||||
redTeam: {
|
||||
id: number;
|
||||
name: string;
|
||||
kuerzel: string;
|
||||
color: string;
|
||||
};
|
||||
start: number;
|
||||
ergebnis: number;
|
||||
}) => void;
|
||||
onSave: (fight: EventFightEdit) => void;
|
||||
} = $props();
|
||||
|
||||
let fightModus = $state(fight?.spielmodus);
|
||||
@ -48,33 +39,63 @@
|
||||
let fightRedTeam = $state(fight?.redTeam);
|
||||
let fightStart = $state(fight?.start ? fromAbsolute(fight.start, "Europe/Berlin") : now("Europe/Berlin"));
|
||||
let fightErgebnis = $state(fight?.ergebnis ?? 0);
|
||||
let fightSpectatePort = $state(fight?.spectatePort?.toString() ?? null);
|
||||
let fightGroup = $state(fight?.group?.id ?? null);
|
||||
|
||||
let selectedGroup = $derived(groups.find((group) => group.id === fightGroup));
|
||||
|
||||
let mapsStore = $derived(maps(fightModus ?? "null"));
|
||||
let gamemodeSelectOpen = $state(false);
|
||||
let mapSelectOpen = $state(false);
|
||||
let blueTeamSelectOpen = $state(false);
|
||||
let redTeamSelectOpen = $state(false);
|
||||
|
||||
let createOpen = $state(false);
|
||||
let groupSelectOpen = $state(false);
|
||||
|
||||
let dirty = $derived(
|
||||
fightModus !== fight?.spielmodus ||
|
||||
fightMap !== fight?.map ||
|
||||
fightBlueTeam !== fight?.blueTeam ||
|
||||
fightRedTeam !== fight?.redTeam ||
|
||||
fightBlueTeam?.id !== fight?.blueTeam?.id ||
|
||||
fightRedTeam?.id !== fight?.redTeam?.id ||
|
||||
fightStart.toDate().getTime() !== fight?.start ||
|
||||
fightErgebnis !== fight?.ergebnis
|
||||
fightErgebnis !== fight?.ergebnis ||
|
||||
fightSpectatePort !== (fight?.spectatePort?.toString() ?? null) ||
|
||||
fightGroup !== (fight?.group?.id ?? null)
|
||||
);
|
||||
|
||||
function submit() {
|
||||
onSave({
|
||||
let loading = $state(false);
|
||||
|
||||
async function submit() {
|
||||
loading = true;
|
||||
try {
|
||||
await onSave({
|
||||
spielmodus: fightModus!,
|
||||
map: fightMap!,
|
||||
blueTeam: fightBlueTeam!,
|
||||
redTeam: fightRedTeam!,
|
||||
start: fightStart?.toDate().getTime(),
|
||||
ergebnis: fightErgebnis,
|
||||
spectatePort: fightSpectatePort ? +fightSpectatePort : null,
|
||||
group: fightGroup,
|
||||
});
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGroupSave(group: GroupUpdateEdit) {
|
||||
let g = await $eventRepo.createGroup(event.id.toString(), group);
|
||||
groups.push(g);
|
||||
fightGroup = g.id;
|
||||
createOpen = false;
|
||||
groupSelectOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="fight-modus">Modus</Label>
|
||||
<Popover>
|
||||
<Popover bind:open={gamemodeSelectOpen}>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
||||
@ -94,6 +115,7 @@
|
||||
value={modus}
|
||||
onSelect={() => {
|
||||
fightModus = modus;
|
||||
gamemodeSelectOpen = false;
|
||||
}}
|
||||
>
|
||||
<Check class={cn("mr-2 size-4", modus !== fightModus && "text-transparent")} />
|
||||
@ -106,7 +128,7 @@
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Label for="fight-map">Map</Label>
|
||||
<Popover>
|
||||
<Popover bind:open={mapSelectOpen}>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
||||
@ -126,6 +148,7 @@
|
||||
value={map}
|
||||
onSelect={() => {
|
||||
fightMap = map;
|
||||
mapSelectOpen = false;
|
||||
}}
|
||||
>
|
||||
<Check class={cn("mr-2 size-4", map !== fightMap && "text-transparent")} />
|
||||
@ -138,7 +161,7 @@
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Label for="fight-blue-team">Blue Team</Label>
|
||||
<Popover>
|
||||
<Popover bind:open={blueTeamSelectOpen}>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
||||
@ -158,6 +181,7 @@
|
||||
value={team.name}
|
||||
onSelect={() => {
|
||||
fightBlueTeam = team;
|
||||
blueTeamSelectOpen = false;
|
||||
}}
|
||||
>
|
||||
<Check class={cn("mr-2 size-4", team !== fightBlueTeam && "text-transparent")} />
|
||||
@ -170,7 +194,7 @@
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<Label for="fight-red-team">Red Team</Label>
|
||||
<Popover>
|
||||
<Popover bind:open={redTeamSelectOpen}>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
||||
@ -190,6 +214,7 @@
|
||||
value={team.name}
|
||||
onSelect={() => {
|
||||
fightRedTeam = team;
|
||||
redTeamSelectOpen = false;
|
||||
}}
|
||||
>
|
||||
<Check class={cn("mr-2 size-4", team !== fightRedTeam && "text-transparent")} />
|
||||
@ -203,6 +228,7 @@
|
||||
</Popover>
|
||||
<Label>Start</Label>
|
||||
<DateTimePicker bind:value={fightStart} />
|
||||
{#if fight !== null}
|
||||
<Label for="fight-ergebnis">Ergebnis</Label>
|
||||
<Select type="single" value={fightErgebnis?.toString()} onValueChange={(v) => (fightErgebnis = +v)}>
|
||||
<SelectTrigger>
|
||||
@ -214,6 +240,76 @@
|
||||
<SelectItem value={"2"}>{fightRedTeam?.name ?? "Team Blau"} gewinnt</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/if}
|
||||
|
||||
<Label for="fight-group">Gruppe</Label>
|
||||
<Dialog bind:open={createOpen}>
|
||||
<Popover bind:open={groupSelectOpen}>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button id="fight-group" variant="outline" class="justify-between" {...props} role="combobox">
|
||||
{selectedGroup?.name || "Keine Gruppe"}
|
||||
<ChevronsUpDownIcon class="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Gruppe suchen..." />
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
<CommandItem value={"new"} onSelect={() => (createOpen = true)}>
|
||||
<PlusIcon class={"mr-2 size-4"} />
|
||||
Neue Gruppe
|
||||
</CommandItem>
|
||||
<CommandItem
|
||||
value={"none"}
|
||||
onSelect={() => {
|
||||
fightGroup = null;
|
||||
groupSelectOpen = false;
|
||||
}}
|
||||
>
|
||||
{#if fightGroup === null}
|
||||
<CheckIcon class={"mr-2 size-4"} />
|
||||
{:else}
|
||||
<MinusIcon class={"mr-2 size-4"} />
|
||||
{/if}
|
||||
Keine Gruppe
|
||||
</CommandItem>
|
||||
|
||||
{#each groups as group}
|
||||
<CommandItem
|
||||
value={group.id.toString()}
|
||||
onSelect={() => {
|
||||
fightGroup = group.id;
|
||||
groupSelectOpen = false;
|
||||
}}
|
||||
>
|
||||
<CheckIcon class={cn("mr-2 size-4", fightGroup !== group.id && "text-transparent")} />
|
||||
{group.name}
|
||||
</CommandItem>
|
||||
{/each}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Neue Gruppe erstellen</DialogTitle>
|
||||
<DialogDescription>Hier kannst du eine neue Gruppe erstellen</DialogDescription>
|
||||
</DialogHeader>
|
||||
<GroupEdit group={null} onSave={handleGroupSave}>
|
||||
{#snippet actions(dirty, submit)}
|
||||
<DialogFooter>
|
||||
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
||||
</DialogFooter>
|
||||
{/snippet}
|
||||
</GroupEdit>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Label for="spectate-port">Spectate Port</Label>
|
||||
<Input id="spectate-port" bind:value={fightSpectatePort} type="number" placeholder="2001" />
|
||||
</div>
|
||||
|
||||
{@render actions(dirty, submit)}
|
||||
{@render actions(dirty && !loading, submit)}
|
||||
|
||||
78
src/components/moderator/components/GroupEdit.svelte
Normal file
78
src/components/moderator/components/GroupEdit.svelte
Normal file
@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import type { ResponseGroups, GroupUpdateEdit } from "@type/event";
|
||||
import { Label } from "@components/ui/label";
|
||||
import { Input } from "@components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
|
||||
|
||||
const {
|
||||
group,
|
||||
actions,
|
||||
onSave,
|
||||
}: {
|
||||
group: ResponseGroups | null;
|
||||
actions: Snippet<[boolean, () => void]>;
|
||||
onSave: (groupData: GroupUpdateEdit) => void;
|
||||
} = $props();
|
||||
|
||||
let groupName = $state(group?.name ?? "");
|
||||
let groupType = $state(group?.type ?? "GROUP_STAGE");
|
||||
let pointsPerWin = $state(group?.pointsPerWin ?? 3);
|
||||
let pointsPerLoss = $state(group?.pointsPerLoss ?? 0);
|
||||
let pointsPerDraw = $state(group?.pointsPerDraw ?? 1);
|
||||
|
||||
let canSave = $derived(groupName.length > 0 && (groupType === "GROUP_STAGE" || groupType === "ELIMINATION_STAGE") && pointsPerWin !== null && pointsPerLoss !== null && pointsPerDraw !== null);
|
||||
|
||||
let dirty = $derived(
|
||||
groupName !== (group ? group.name : "") ||
|
||||
groupType !== (group ? group.type : "GROUP_STAGE") ||
|
||||
pointsPerWin !== (group ? group.pointsPerWin : 3) ||
|
||||
pointsPerLoss !== (group ? group.pointsPerLoss : 0) ||
|
||||
pointsPerDraw !== (group ? group.pointsPerDraw : 1)
|
||||
);
|
||||
|
||||
function submit() {
|
||||
onSave({
|
||||
name: groupName,
|
||||
type: groupType,
|
||||
pointsPerWin: pointsPerWin,
|
||||
pointsPerLoss: pointsPerLoss,
|
||||
pointsPerDraw: pointsPerDraw,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="group-name">Name</Label>
|
||||
<Input id="group-name" bind:value={groupName} placeholder="z.B. Gruppenphase A" />
|
||||
|
||||
<Label for="group-type">Typ</Label>
|
||||
<Select
|
||||
value={groupType}
|
||||
type="single"
|
||||
onValueChange={(v) => {
|
||||
if (v) groupType = v as "GROUP_STAGE" | "ELIMINATION_STAGE";
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id="group-type" placeholder="Wähle einen Gruppentyp">
|
||||
{groupType === "GROUP_STAGE" ? "Gruppenphase" : "Eliminierungsphase"}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="GROUP_STAGE">Gruppenphase</SelectItem>
|
||||
<SelectItem value="ELIMINATION_STAGE">Eliminierungsphase</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{#if groupType === "GROUP_STAGE" && group !== null}
|
||||
<Label for="points-win">Punkte pro Sieg</Label>
|
||||
<Input id="points-win" type="number" bind:value={pointsPerWin} placeholder="3" />
|
||||
|
||||
<Label for="points-loss">Punkte pro Niederlage</Label>
|
||||
<Input id="points-loss" type="number" bind:value={pointsPerLoss} placeholder="0" />
|
||||
|
||||
<Label for="points-draw">Punkte pro Unentschieden</Label>
|
||||
<Input id="points-draw" type="number" bind:value={pointsPerDraw} placeholder="1" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{@render actions(group === null ? canSave : dirty, submit)}
|
||||
@ -22,25 +22,29 @@
|
||||
|
||||
import GroupEditRow from "./GroupEditRow.svelte";
|
||||
|
||||
import type { ExtendedEvent } from "@type/event";
|
||||
import type { EventFightEdit, ExtendedEvent } from "@type/event";
|
||||
import { createSvelteTable, FlexRender } from "@components/ui/data-table";
|
||||
import { type ColumnFiltersState, getCoreRowModel, getFilteredRowModel, getGroupedRowModel, getSortedRowModel, type RowSelectionState, type SortingState } from "@tanstack/table-core";
|
||||
import { columns } from "./columns";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
|
||||
import { Checkbox } from "@components/ui/checkbox";
|
||||
import { Menubar, MenubarContent, MenubarItem, MenubarGroup, MenubarGroupHeading, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@components/ui/menubar";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
||||
import FightEdit from "@components/moderator/components/FightEdit.svelte";
|
||||
import { Button } from "@components/ui/button";
|
||||
import { MenuIcon } from "lucide-svelte";
|
||||
import { eventRepo } from "@components/repo/event";
|
||||
|
||||
let { data = $bindable() }: { data: ExtendedEvent } = $props();
|
||||
|
||||
let fights = $state(data.fights);
|
||||
|
||||
let sorting = $state<SortingState>([]);
|
||||
let columnFilters = $state<ColumnFiltersState>([]);
|
||||
let selection = $state<RowSelectionState>({});
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data.fights;
|
||||
return fights;
|
||||
},
|
||||
initialState: {
|
||||
columnOrder: ["auswahl", "begegnung", "group"],
|
||||
@ -88,9 +92,23 @@
|
||||
groupedColumnMode: "remove",
|
||||
getRowId: (row) => row.id.toString(),
|
||||
});
|
||||
|
||||
let createOpen = $state(false);
|
||||
|
||||
async function handleSave(fight: EventFightEdit) {
|
||||
await $eventRepo.createFight(data.event.id.toString(), {
|
||||
...fight,
|
||||
blueTeam: fight.blueTeam.id,
|
||||
redTeam: fight.redTeam.id,
|
||||
});
|
||||
|
||||
fights = await $eventRepo.listFights(data.event.id.toString());
|
||||
createOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-fit">
|
||||
<Dialog bind:open={createOpen}>
|
||||
<Menubar>
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
|
||||
@ -103,7 +121,7 @@
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger>Erstellen</MenubarTrigger>
|
||||
<MenubarContent>
|
||||
<MenubarItem disabled>Fight Erstellen</MenubarItem>
|
||||
<MenubarItem onclick={() => (createOpen = true)}>Fight Erstellen</MenubarItem>
|
||||
<MenubarGroup>
|
||||
<MenubarGroupHeading>Generatoren</MenubarGroupHeading>
|
||||
<MenubarItem disabled>Gruppenphase</MenubarItem>
|
||||
@ -112,6 +130,20 @@
|
||||
</MenubarContent>
|
||||
</MenubarMenu>
|
||||
</Menubar>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Fight Erstellen</DialogTitle>
|
||||
<DialogDescription>Hier kannst du einen neuen Fight erstellen</DialogDescription>
|
||||
</DialogHeader>
|
||||
<FightEdit fight={null} teams={data.teams} event={data.event} groups={data.groups} onSave={handleSave}>
|
||||
{#snippet actions(dirty, submit)}
|
||||
<DialogFooter>
|
||||
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
||||
</DialogFooter>
|
||||
{/snippet}
|
||||
</FightEdit>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
@ -131,7 +163,7 @@
|
||||
<TableBody>
|
||||
{#each table.getRowModel().rows as groupRow (groupRow.id)}
|
||||
{#if groupRow.getIsGrouped()}
|
||||
<TableRow class="bg-muted font-bold">
|
||||
<TableRow class="font-bold">
|
||||
<TableCell colspan={columns.length - 1}>
|
||||
<Checkbox
|
||||
checked={groupRow.getIsSelected()}
|
||||
@ -155,7 +187,13 @@
|
||||
</TableCell>
|
||||
{/each}
|
||||
<TableCell class="text-right">
|
||||
<FightEditRow fight={row.original} teams={data.teams}></FightEditRow>
|
||||
<FightEditRow
|
||||
fight={row.original}
|
||||
teams={data.teams}
|
||||
groups={data.groups}
|
||||
event={data.event}
|
||||
onupdate={(update) => (fights = fights.map((v) => (v.id === update.id ? update : v)))}
|
||||
></FightEditRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
import RefereesList from "@components/moderator/pages/event/RefereesList.svelte";
|
||||
import TeamTable from "@components/moderator/pages/event/TeamTable.svelte";
|
||||
|
||||
let { event }: { event: ExtendedEvent } = $props();
|
||||
let { event = $bindable() }: { event: ExtendedEvent } = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col m-4 p-4 rounded-md border gap-4">
|
||||
|
||||
@ -1,21 +1,32 @@
|
||||
<script lang="ts">
|
||||
import type { EventFight } from "@type/event";
|
||||
import type { EventFight, EventFightEdit, ResponseGroups, SWEvent } from "@type/event";
|
||||
import { Button } from "@components/ui/button";
|
||||
import { EditIcon, MenuIcon } from "lucide-svelte";
|
||||
import { EditIcon, MenuIcon, GroupIcon } from "lucide-svelte";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@components/ui/dialog";
|
||||
import FightEdit from "@components/moderator/components/FightEdit.svelte";
|
||||
import type { Team } from "@components/types/team";
|
||||
import { fightRepo } from "@components/repo/fight";
|
||||
|
||||
const { fight, teams }: { fight: EventFight; teams: Team[] } = $props();
|
||||
let { fight, teams, groups, event, onupdate }: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void } = $props();
|
||||
|
||||
function handleSave(fightData) {
|
||||
// Handle the save action here
|
||||
console.log("Fight data saved:", fightData);
|
||||
let editOpen = $state(false);
|
||||
|
||||
async function handleSave(fightData: EventFightEdit) {
|
||||
let f = await $fightRepo.updateFight(event.id, fight.id, {
|
||||
...fightData,
|
||||
blueTeam: fightData.blueTeam.id,
|
||||
redTeam: fightData.redTeam.id,
|
||||
group: fightData.group ?? -1,
|
||||
});
|
||||
|
||||
onupdate(f);
|
||||
|
||||
editOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Dialog>
|
||||
<Dialog bind:open={editOpen}>
|
||||
<DialogTrigger>
|
||||
<Button variant="ghost" size="icon">
|
||||
<EditIcon />
|
||||
@ -26,7 +37,7 @@
|
||||
<DialogTitle>Fight bearbeiten</DialogTitle>
|
||||
<DialogDescription>Hier kannst du die Daten des Kampfes bearbeiten.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<FightEdit {fight} {teams} onSave={handleSave}>
|
||||
<FightEdit {fight} {teams} {groups} {event} onSave={handleSave}>
|
||||
{#snippet actions(dirty, submit)}
|
||||
<DialogFooter>
|
||||
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
||||
@ -35,7 +46,4 @@
|
||||
</FightEdit>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button variant="ghost" size="icon">
|
||||
<MenuIcon />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -77,4 +77,28 @@ export const columns: ColumnDef<EventFight> = [
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Spielmodus",
|
||||
accessorKey: "spielmodus",
|
||||
},
|
||||
{
|
||||
header: "Map",
|
||||
accessorKey: "map",
|
||||
},
|
||||
{
|
||||
header: "Ergebnis",
|
||||
accessorKey: "ergebnis",
|
||||
cell: ({ row }) => {
|
||||
const fight = row.original;
|
||||
if (fight.ergebnis === 0 && fight.start > Date.now()) {
|
||||
return "Noch nicht gespielt";
|
||||
} else if (fight.ergebnis === 1) {
|
||||
return fight.blueTeam.name + " hat gewonnen";
|
||||
} else if (fight.ergebnis === 2) {
|
||||
return fight.redTeam.name + " hat gewonnen";
|
||||
} else {
|
||||
return "Unentschieden";
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -128,6 +128,7 @@ export class EventRepo {
|
||||
.then((value) => z.array(EventFightSchema).parse(value));
|
||||
}
|
||||
public async createFight(eventId: string, fight: any): Promise<EventFight> {
|
||||
delete fight.ergebnis;
|
||||
return await fetchWithToken(this.token, `/events/${eventId}/fights`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(fight),
|
||||
@ -153,7 +154,10 @@ export class EventRepo {
|
||||
CreateEventGroupSchema.parse(group);
|
||||
return await fetchWithToken(this.token, `/events/${eventId}/groups`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(group),
|
||||
body: JSON.stringify({
|
||||
name: group.name,
|
||||
type: group.type,
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
.then((value) => value.json())
|
||||
|
||||
@ -17,12 +17,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {EventFight} from "@type/event.js";
|
||||
import {fetchWithToken, tokenStore} from "./repo";
|
||||
import {z} from "zod";
|
||||
import {EventFightSchema} from "@type/event.js";
|
||||
import type {Dayjs} from "dayjs";
|
||||
import {derived} from "svelte/store";
|
||||
import type { EventFight } from "@type/event.js";
|
||||
import { fetchWithToken, tokenStore } from "./repo";
|
||||
import { z } from "zod";
|
||||
import { EventFightSchema } from "@type/event.js";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import { derived } from "svelte/store";
|
||||
|
||||
export interface CreateFight {
|
||||
spielmodus: string;
|
||||
@ -39,23 +39,22 @@ export interface UpdateFight {
|
||||
map: string | null;
|
||||
blueTeam: number | null;
|
||||
redTeam: number | null;
|
||||
start: Dayjs | null;
|
||||
start: number | null;
|
||||
spectatePort: number | null;
|
||||
group: string | null;
|
||||
group: number | null;
|
||||
}
|
||||
|
||||
export class FightRepo {
|
||||
constructor(private token: string) {
|
||||
}
|
||||
constructor(private token: string) {}
|
||||
|
||||
public async listFights(eventId: number): Promise<EventFight[]> {
|
||||
return await fetchWithToken(this.token, `/events/${eventId}/fights`)
|
||||
.then(value => value.json())
|
||||
.then(value => z.array(EventFightSchema).parse(value));
|
||||
.then((value) => value.json())
|
||||
.then((value) => z.array(EventFightSchema).parse(value));
|
||||
}
|
||||
|
||||
public async createFight(eventId: number, fight: CreateFight): Promise<EventFight> {
|
||||
return await fetchWithToken(this.token, "/fights", {
|
||||
return await fetchWithToken(this.token, `/events/${eventId}/fights`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
event: eventId,
|
||||
@ -67,28 +66,25 @@ export class FightRepo {
|
||||
spectatePort: fight.spectatePort,
|
||||
group: fight.group,
|
||||
}),
|
||||
}).then(value => value.json())
|
||||
})
|
||||
.then((value) => value.json())
|
||||
.then(EventFightSchema.parse);
|
||||
}
|
||||
|
||||
public async updateFight(fightId: number, fight: UpdateFight): Promise<EventFight> {
|
||||
return await fetchWithToken(this.token, `/fights/${fightId}`, {
|
||||
public async updateFight(eventId: number, fightId: number, fight: UpdateFight): Promise<EventFight> {
|
||||
return await fetchWithToken(this.token, `/events/${eventId}/fights/${fightId}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
spielmodus: fight.spielmodus,
|
||||
map: fight.map,
|
||||
blueTeam: fight.blueTeam,
|
||||
redTeam: fight.redTeam,
|
||||
...fight,
|
||||
start: fight.start?.valueOf(),
|
||||
spectatePort: fight.spectatePort,
|
||||
group: fight.group,
|
||||
}),
|
||||
}).then(value => value.json())
|
||||
})
|
||||
.then((value) => value.json())
|
||||
.then(EventFightSchema.parse);
|
||||
}
|
||||
|
||||
public async deleteFight(fightId: number): Promise<void> {
|
||||
const res = await fetchWithToken(this.token, `/fights/${fightId}`, {
|
||||
public async deleteFight(eventId: number, fightId: number): Promise<void> {
|
||||
const res = await fetchWithToken(this.token, `/events/${eventId}/fights/${fightId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
|
||||
@ -45,6 +45,15 @@ export const EventFightSchema = z.object({
|
||||
|
||||
export type EventFight = z.infer<typeof EventFightSchema>;
|
||||
|
||||
export const EventFightEditSchema = EventFightSchema.omit({
|
||||
id: true,
|
||||
group: true,
|
||||
}).extend({
|
||||
group: z.number().nullable(),
|
||||
});
|
||||
|
||||
export type EventFightEdit = z.infer<typeof EventFightEditSchema>;
|
||||
|
||||
export type ResponseGroups = z.infer<typeof ResponseGroupsSchema>;
|
||||
|
||||
export const ResponseRelationSchema = z.object({
|
||||
@ -111,6 +120,12 @@ export const UpdateEventGroupSchema = z.object({
|
||||
});
|
||||
export type UpdateEventGroup = z.infer<typeof UpdateEventGroupSchema>;
|
||||
|
||||
export const GroupEditSchema = ResponseGroupsSchema.omit({
|
||||
id: true,
|
||||
points: true,
|
||||
});
|
||||
export type GroupUpdateEdit = z.infer<typeof GroupEditSchema>;
|
||||
|
||||
export const CreateEventRelationSchema = z.object({
|
||||
fightId: z.number(),
|
||||
team: z.enum(["RED", "BLUE"]),
|
||||
|
||||
Reference in New Issue
Block a user