345 lines
15 KiB
Svelte
345 lines
15 KiB
Svelte
<script lang="ts">
|
|
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, 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 = $bindable(),
|
|
}: {
|
|
fight: EventFight | null;
|
|
teams: Team[];
|
|
event: SWEvent;
|
|
groups: ResponseGroups[];
|
|
actions: Snippet<[boolean, () => void]>;
|
|
onSave: (fight: EventFightEdit) => void;
|
|
} = $props();
|
|
|
|
let fightModus = $state(fight?.spielmodus);
|
|
let fightMap = $state(fight?.map);
|
|
let fightBlueTeam = $state(fight?.blueTeam);
|
|
let fightRedTeam = $state(fight?.redTeam);
|
|
let fightStart = $state(fight?.start ? fromAbsolute(fight.start, "Europe/Berlin") : fromAbsolute(event.start, "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?.id !== fight?.blueTeam?.id ||
|
|
fightRedTeam?.id !== fight?.redTeam?.id ||
|
|
fightStart.toDate().getTime() !== fight?.start ||
|
|
fightErgebnis !== fight?.ergebnis ||
|
|
fightSpectatePort !== (fight?.spectatePort?.toString() ?? null) ||
|
|
fightGroup !== (fight?.group?.id ?? null)
|
|
);
|
|
|
|
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 bind:open={gamemodeSelectOpen}>
|
|
<PopoverTrigger>
|
|
{#snippet child({ props })}
|
|
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
|
{$gamemodes.find((value) => value === fightModus) || fightModus || "Select a modus type..."}
|
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
{/snippet}
|
|
</PopoverTrigger>
|
|
<PopoverContent class="p-0">
|
|
<Command>
|
|
<CommandInput placeholder="Search Fight Modus..." />
|
|
<CommandList>
|
|
<CommandEmpty>No fight modus found.</CommandEmpty>
|
|
<CommandGroup>
|
|
{#each $gamemodes as modus}
|
|
<CommandItem
|
|
value={modus}
|
|
onSelect={() => {
|
|
fightModus = modus;
|
|
gamemodeSelectOpen = false;
|
|
}}
|
|
>
|
|
<Check class={cn("mr-2 size-4", modus !== fightModus && "text-transparent")} />
|
|
{modus}
|
|
</CommandItem>
|
|
{/each}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<Label for="fight-map">Map</Label>
|
|
<Popover bind:open={mapSelectOpen}>
|
|
<PopoverTrigger>
|
|
{#snippet child({ props })}
|
|
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
|
{$mapsStore.find((value) => value === fightMap) || fightMap || "Select a map..."}
|
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
{/snippet}
|
|
</PopoverTrigger>
|
|
<PopoverContent class="p-0">
|
|
<Command>
|
|
<CommandInput placeholder="Search Maps..." />
|
|
<CommandList>
|
|
<CommandEmpty>No map found.</CommandEmpty>
|
|
<CommandGroup>
|
|
{#each $mapsStore as map}
|
|
<CommandItem
|
|
value={map}
|
|
onSelect={() => {
|
|
fightMap = map;
|
|
mapSelectOpen = false;
|
|
}}
|
|
>
|
|
<Check class={cn("mr-2 size-4", map !== fightMap && "text-transparent")} />
|
|
{map}
|
|
</CommandItem>
|
|
{/each}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<Label for="fight-blue-team">Blue Team</Label>
|
|
<Popover bind:open={blueTeamSelectOpen}>
|
|
<PopoverTrigger>
|
|
{#snippet child({ props })}
|
|
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
|
{teams.find((value) => value.id === fightBlueTeam?.id)?.name || fightBlueTeam?.name || "Select a team..."}
|
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
{/snippet}
|
|
</PopoverTrigger>
|
|
<PopoverContent class="p-0">
|
|
<Command>
|
|
<CommandInput placeholder="Search Teams..." />
|
|
<CommandList>
|
|
<CommandEmpty>No team found.</CommandEmpty>
|
|
<CommandItem
|
|
value={"-1"}
|
|
onSelect={() => {
|
|
fightBlueTeam = {
|
|
id: -1,
|
|
name: "?",
|
|
color: "7",
|
|
kuerzel: "?",
|
|
};
|
|
blueTeamSelectOpen = false;
|
|
}}
|
|
keywords={["?"]}>???</CommandItem
|
|
>
|
|
<CommandGroup heading="Teams">
|
|
{#each teams as team}
|
|
<CommandItem
|
|
value={team.name}
|
|
onSelect={() => {
|
|
fightBlueTeam = team;
|
|
blueTeamSelectOpen = false;
|
|
}}
|
|
>
|
|
<Check class={cn("mr-2 size-4", team !== fightBlueTeam && "text-transparent")} />
|
|
{team.name}
|
|
</CommandItem>
|
|
{/each}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<Label for="fight-red-team">Red Team</Label>
|
|
<Popover bind:open={redTeamSelectOpen}>
|
|
<PopoverTrigger>
|
|
{#snippet child({ props })}
|
|
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
|
{teams.find((value) => value.id === fightRedTeam?.id)?.name || fightRedTeam?.name || "Select a team..."}
|
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
|
</Button>
|
|
{/snippet}
|
|
</PopoverTrigger>
|
|
<PopoverContent class="p-0">
|
|
<Command>
|
|
<CommandInput placeholder="Search Teams..." />
|
|
<CommandList>
|
|
<CommandEmpty>No team found.</CommandEmpty>
|
|
<CommandItem
|
|
value={"-1"}
|
|
onSelect={() => {
|
|
fightRedTeam = {
|
|
id: -1,
|
|
name: "?",
|
|
color: "7",
|
|
kuerzel: "?",
|
|
};
|
|
redTeamSelectOpen = false;
|
|
}}
|
|
keywords={["?"]}>???</CommandItem
|
|
>
|
|
<CommandGroup heading="Teams">
|
|
{#each teams as team}
|
|
<CommandItem
|
|
value={team.name}
|
|
onSelect={() => {
|
|
fightRedTeam = team;
|
|
redTeamSelectOpen = false;
|
|
}}
|
|
>
|
|
<Check class={cn("mr-2 size-4", team !== fightRedTeam && "text-transparent")} />
|
|
{team.name}
|
|
</CommandItem>
|
|
{/each}
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</PopoverContent>
|
|
</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>
|
|
{fightErgebnis === 0 ? "Unentschieden" : (fightErgebnis === 1 ? fightBlueTeam?.name : fightRedTeam?.name) + " gewinnt"}
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value={"0"}>Unentschieden</SelectItem>
|
|
<SelectItem value={"1"}>{fightBlueTeam?.name ?? "Team Blau"} gewinnt</SelectItem>
|
|
<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>
|
|
|
|
<CommandGroup heading="Gruppen">
|
|
<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>
|
|
</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 && !loading, submit)}
|