Refactor event handling and introduce TeamSelector component for improved fight management
All checks were successful
SteamWarCI Build successful
All checks were successful
SteamWarCI Build successful
This commit is contained in:
@ -30,6 +30,7 @@
|
|||||||
import Event from "@components/moderator/pages/event/Event.svelte";
|
import Event from "@components/moderator/pages/event/Event.svelte";
|
||||||
import Pages from "@components/moderator/pages/pages/Pages.svelte";
|
import Pages from "@components/moderator/pages/pages/Pages.svelte";
|
||||||
import Generator from "@components/moderator/pages/generators/Generator.svelte";
|
import Generator from "@components/moderator/pages/generators/Generator.svelte";
|
||||||
|
import { Tooltip } from "bits-ui";
|
||||||
|
|
||||||
const routes: RouteDefinition = {
|
const routes: RouteDefinition = {
|
||||||
"/": Dashboard,
|
"/": Dashboard,
|
||||||
@ -52,5 +53,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Router {routes} />
|
|
||||||
|
<Tooltip.Provider>
|
||||||
|
<Router {routes} />
|
||||||
|
</Tooltip.Provider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import GroupSelector from "./GroupSelector.svelte";
|
import GroupSelector from "./GroupSelector.svelte";
|
||||||
|
|
||||||
import type { EventFight, EventFightEdit, ResponseGroups, SWEvent } from "@type/event";
|
import type { EventFight, EventFightEdit, ResponseGroups, ResponseRelation, SWEvent } from "@type/event";
|
||||||
import { fromAbsolute } from "@internationalized/date";
|
import { fromAbsolute } from "@internationalized/date";
|
||||||
import { Label } from "@components/ui/label";
|
import { Label } from "@components/ui/label";
|
||||||
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
|
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
|
||||||
@ -11,46 +11,36 @@
|
|||||||
import { ChevronsUpDown, Check } from "lucide-svelte";
|
import { ChevronsUpDown, Check } from "lucide-svelte";
|
||||||
import { Button } from "@components/ui/button";
|
import { Button } from "@components/ui/button";
|
||||||
import { cn } from "@components/utils";
|
import { cn } from "@components/utils";
|
||||||
import type { Team } from "@components/types/team";
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import { Input } from "@components/ui/input";
|
import { Input } from "@components/ui/input";
|
||||||
|
import TeamSelector from "./TeamSelector.svelte";
|
||||||
|
import type { EventModel } from "../pages/event/eventmodel.svelte";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
fight,
|
fight,
|
||||||
teams,
|
|
||||||
event,
|
|
||||||
actions,
|
actions,
|
||||||
onSave,
|
onSave,
|
||||||
groups = $bindable(),
|
data,
|
||||||
}: {
|
}: {
|
||||||
fight: EventFight | null;
|
fight: EventFight | null;
|
||||||
teams: Team[];
|
|
||||||
event: SWEvent;
|
|
||||||
groups: ResponseGroups[];
|
|
||||||
actions: Snippet<[boolean, () => void]>;
|
actions: Snippet<[boolean, () => void]>;
|
||||||
onSave: (fight: EventFightEdit) => void;
|
onSave: (fight: EventFightEdit) => void;
|
||||||
|
data: EventModel;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let fightModus = $state(fight?.spielmodus);
|
let fightModus = $state(fight?.spielmodus);
|
||||||
let fightMap = $state(fight?.map);
|
let fightMap = $state(fight?.map);
|
||||||
let fightBlueTeam = $state(fight?.blueTeam);
|
let fightBlueTeam = $state(fight?.blueTeam);
|
||||||
let fightRedTeam = $state(fight?.redTeam);
|
let fightRedTeam = $state(fight?.redTeam);
|
||||||
let fightStart = $state(fight?.start ? fromAbsolute(fight.start, "Europe/Berlin") : fromAbsolute(event.start, "Europe/Berlin"));
|
let fightStart = $state(fight?.start ? fromAbsolute(fight.start, "Europe/Berlin") : fromAbsolute(data.event.start, "Europe/Berlin"));
|
||||||
let fightErgebnis = $state(fight?.ergebnis ?? 0);
|
let fightErgebnis = $state(fight?.ergebnis ?? 0);
|
||||||
let fightSpectatePort = $state(fight?.spectatePort?.toString() ?? null);
|
let fightSpectatePort = $state(fight?.spectatePort?.toString() ?? null);
|
||||||
let fightGroup = $state(fight?.group?.id ?? null);
|
let fightGroup = $state(fight?.group?.id ?? null);
|
||||||
|
|
||||||
let selectedGroup = $derived(groups.find((group) => group.id === fightGroup));
|
|
||||||
|
|
||||||
let mapsStore = $derived(maps(fightModus ?? "null"));
|
let mapsStore = $derived(maps(fightModus ?? "null"));
|
||||||
let gamemodeSelectOpen = $state(false);
|
let gamemodeSelectOpen = $state(false);
|
||||||
let mapSelectOpen = $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(
|
let dirty = $derived(
|
||||||
fightModus !== fight?.spielmodus ||
|
fightModus !== fight?.spielmodus ||
|
||||||
@ -151,128 +141,10 @@
|
|||||||
</Command>
|
</Command>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Label for="fight-blue-team">Blue Team</Label>
|
<Label>Blue Team</Label>
|
||||||
<Popover bind:open={blueTeamSelectOpen}>
|
<TeamSelector bind:selectedTeam={fightBlueTeam} {data} fightId={fight?.id} team="BLUE" />
|
||||||
<PopoverTrigger>
|
<Label>Red Team</Label>
|
||||||
{#snippet child({ props })}
|
<TeamSelector bind:selectedTeam={fightRedTeam} {data} fightId={fight?.id} team="RED" />
|
||||||
<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>
|
|
||||||
<CommandGroup>
|
|
||||||
<CommandItem
|
|
||||||
value={"-1"}
|
|
||||||
onSelect={() => {
|
|
||||||
fightBlueTeam = {
|
|
||||||
id: -1,
|
|
||||||
name: "?",
|
|
||||||
color: "7",
|
|
||||||
kuerzel: "?",
|
|
||||||
};
|
|
||||||
blueTeamSelectOpen = false;
|
|
||||||
}}
|
|
||||||
keywords={["?"]}>???</CommandItem
|
|
||||||
>
|
|
||||||
<CommandItem
|
|
||||||
value={"0"}
|
|
||||||
onSelect={() => {
|
|
||||||
fightBlueTeam = {
|
|
||||||
id: 0,
|
|
||||||
name: "Public",
|
|
||||||
color: "7",
|
|
||||||
kuerzel: "PUB",
|
|
||||||
};
|
|
||||||
blueTeamSelectOpen = false;
|
|
||||||
}}
|
|
||||||
keywords={["PUB", "Public"]}>PUB</CommandItem
|
|
||||||
>
|
|
||||||
</CommandGroup>
|
|
||||||
<CommandGroup heading="Teams">
|
|
||||||
{#each teams as team}
|
|
||||||
<CommandItem
|
|
||||||
value={team.name}
|
|
||||||
onSelect={() => {
|
|
||||||
fightBlueTeam = team;
|
|
||||||
blueTeamSelectOpen = false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Check class={cn("mr-2 size-4", team.id !== fightBlueTeam?.id && "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>
|
|
||||||
<CommandGroup>
|
|
||||||
<CommandItem
|
|
||||||
value={"-1"}
|
|
||||||
onSelect={() => {
|
|
||||||
fightRedTeam = {
|
|
||||||
id: -1,
|
|
||||||
name: "?",
|
|
||||||
color: "7",
|
|
||||||
kuerzel: "?",
|
|
||||||
};
|
|
||||||
redTeamSelectOpen = false;
|
|
||||||
}}
|
|
||||||
keywords={["?"]}>???</CommandItem
|
|
||||||
>
|
|
||||||
<CommandItem
|
|
||||||
value={"0"}
|
|
||||||
onSelect={() => {
|
|
||||||
fightRedTeam = {
|
|
||||||
id: 0,
|
|
||||||
name: "Public",
|
|
||||||
color: "7",
|
|
||||||
kuerzel: "PUB",
|
|
||||||
};
|
|
||||||
redTeamSelectOpen = false;
|
|
||||||
}}
|
|
||||||
keywords={["PUB", "Public"]}>PUB</CommandItem
|
|
||||||
>
|
|
||||||
</CommandGroup>
|
|
||||||
<CommandGroup heading="Teams">
|
|
||||||
{#each teams as team}
|
|
||||||
<CommandItem
|
|
||||||
value={team.name}
|
|
||||||
onSelect={() => {
|
|
||||||
fightRedTeam = team;
|
|
||||||
redTeamSelectOpen = false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Check class={cn("mr-2 size-4", team.id !== fightRedTeam?.id && "text-transparent")} />
|
|
||||||
{team.name}
|
|
||||||
</CommandItem>
|
|
||||||
{/each}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<Label>Start</Label>
|
<Label>Start</Label>
|
||||||
<DateTimePicker bind:value={fightStart} />
|
<DateTimePicker bind:value={fightStart} />
|
||||||
{#if fight !== null}
|
{#if fight !== null}
|
||||||
@ -290,7 +162,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Label for="fight-group">Gruppe</Label>
|
<Label for="fight-group">Gruppe</Label>
|
||||||
<GroupSelector {event} bind:value={fightGroup} bind:groups></GroupSelector>
|
<GroupSelector event={data.event} bind:value={fightGroup} bind:groups={data.groups}></GroupSelector>
|
||||||
<Label for="spectate-port">Spectate Port</Label>
|
<Label for="spectate-port">Spectate Port</Label>
|
||||||
<Input id="spectate-port" bind:value={fightSpectatePort} type="number" placeholder="2001" />
|
<Input id="spectate-port" bind:value={fightSpectatePort} type="number" placeholder="2001" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
253
src/components/moderator/components/TeamSelector.svelte
Normal file
253
src/components/moderator/components/TeamSelector.svelte
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { ResponseRelation } from "@components/types/event";
|
||||||
|
import type { Team } from "@components/types/team";
|
||||||
|
import { Button } from "@components/ui/button";
|
||||||
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/ui/tabs";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@components/ui/tooltip";
|
||||||
|
import { cn } from "@components/utils";
|
||||||
|
import { Check, ChevronsUpDown, GitPullRequestArrow, Plus } from "lucide-svelte";
|
||||||
|
import type { EventModel } from "../pages/event/eventmodel.svelte";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
|
||||||
|
import { Label } from "@components/ui/label";
|
||||||
|
import { eventRepo } from "@components/repo/event";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
selectedTeam: Team | undefined;
|
||||||
|
open?: boolean;
|
||||||
|
team: "BLUE" | "RED";
|
||||||
|
data: EventModel;
|
||||||
|
fightId?: number;
|
||||||
|
onSelect?: (team: Team) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { selectedTeam = $bindable(), data, team, open = $bindable(false), fightId, onSelect }: Props = $props();
|
||||||
|
|
||||||
|
const currentRelation = $derived(data.relations.find((r) => r.fight === fightId && r.team === team));
|
||||||
|
|
||||||
|
let fromType = $state<"FIGHT" | "GROUP">(currentRelation?.type ?? "FIGHT");
|
||||||
|
|
||||||
|
let fromFight = $state<string | undefined>(currentRelation?.fromFight?.id?.toString());
|
||||||
|
|
||||||
|
let fromFightData = $derived(data.fights.find((f) => f.id.toString() === fromFight));
|
||||||
|
|
||||||
|
let fromGroup = $state<string | undefined>(currentRelation?.fromGroup?.id?.toString());
|
||||||
|
|
||||||
|
let fromGroupData = $derived(data.groups.find((g) => g.id.toString() === fromGroup));
|
||||||
|
|
||||||
|
let fromPlace = $state<string | undefined>(currentRelation?.fromPlace?.toString());
|
||||||
|
|
||||||
|
let relationOpen = $state(false);
|
||||||
|
|
||||||
|
async function saveRelation() {
|
||||||
|
relationOpen = false;
|
||||||
|
if (currentRelation === undefined) {
|
||||||
|
await $eventRepo.createRelation(data.event.id, {
|
||||||
|
fightId: fightId!,
|
||||||
|
team,
|
||||||
|
fromType,
|
||||||
|
fromId: fromType === "FIGHT" ? parseInt(fromFight!) : parseInt(fromGroup!),
|
||||||
|
fromPlace: parseInt(fromPlace!),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await $eventRepo.updateRelation(data.event.id, currentRelation.id, {
|
||||||
|
from: {
|
||||||
|
fromType,
|
||||||
|
fromId: fromType === "FIGHT" ? parseInt(fromFight!) : parseInt(fromGroup!),
|
||||||
|
fromPlace: parseInt(fromPlace!),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
data.relations = await $eventRepo.listRelations(data.event.id);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearRelation() {
|
||||||
|
relationOpen = false;
|
||||||
|
if (currentRelation !== undefined) {
|
||||||
|
await $eventRepo.deleteRelation(data.event.id, currentRelation.id);
|
||||||
|
data.relations = await $eventRepo.listRelations(data.event.id);
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
fromType = currentRelation?.type ?? "FIGHT";
|
||||||
|
fromFight = currentRelation?.fromFight?.id.toString();
|
||||||
|
fromGroup = currentRelation?.fromGroup?.id.toString();
|
||||||
|
fromPlace = currentRelation?.fromPlace.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
let canSave = $derived(
|
||||||
|
(fromType !== currentRelation?.type ||
|
||||||
|
fromFight !== (currentRelation?.fromFight?.id.toString() ?? "") ||
|
||||||
|
fromGroup !== (currentRelation?.fromGroup?.id.toString() ?? "") ||
|
||||||
|
fromPlace !== (currentRelation?.fromPlace.toString() ?? "")) &&
|
||||||
|
((fromType === "FIGHT" && fromFight !== "" && fromPlace !== "") || (fromType === "GROUP" && fromGroup !== "" && fromPlace !== ""))
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Popover bind:open>
|
||||||
|
<PopoverTrigger>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<Button variant="outline" class="justify-between flex-1" {...props} role="combobox">
|
||||||
|
{#if selectedTeam?.id === -1}
|
||||||
|
???
|
||||||
|
{:else if selectedTeam?.id === 0}
|
||||||
|
PUB
|
||||||
|
{:else}
|
||||||
|
{data.teams.find((v) => v.id === selectedTeam?.id)?.name || selectedTeam?.name || "Select a team..."}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if currentRelation !== undefined}
|
||||||
|
({#if currentRelation.type === "FIGHT"}
|
||||||
|
{currentRelation.fromPlace === 0 ? "Gewinner" : "Verlierer"} von {currentRelation.fromFight?.blueTeam.name} vs {currentRelation.fromFight?.redTeam.name} ({new Date(
|
||||||
|
currentRelation.fromFight?.start ?? 0
|
||||||
|
).toLocaleTimeString("de-DE", {
|
||||||
|
timeStyle: "short",
|
||||||
|
})})
|
||||||
|
{:else}
|
||||||
|
{currentRelation.fromPlace + 1}. Platz von {currentRelation.fromGroup?.name}
|
||||||
|
{/if})
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
value={"-1"}
|
||||||
|
onSelect={() => {
|
||||||
|
selectedTeam = {
|
||||||
|
id: -1,
|
||||||
|
name: "?",
|
||||||
|
color: "7",
|
||||||
|
kuerzel: "?",
|
||||||
|
};
|
||||||
|
onSelect?.(selectedTeam);
|
||||||
|
open = false;
|
||||||
|
}}
|
||||||
|
keywords={["?"]}>???</CommandItem
|
||||||
|
>
|
||||||
|
<CommandItem
|
||||||
|
value={"0"}
|
||||||
|
onSelect={() => {
|
||||||
|
selectedTeam = {
|
||||||
|
id: 0,
|
||||||
|
name: "Public",
|
||||||
|
color: "7",
|
||||||
|
kuerzel: "PUB",
|
||||||
|
};
|
||||||
|
onSelect?.(selectedTeam);
|
||||||
|
open = false;
|
||||||
|
}}
|
||||||
|
keywords={["PUB", "Public"]}>PUB</CommandItem
|
||||||
|
>
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandGroup heading="Teams">
|
||||||
|
{#each data.teams as team}
|
||||||
|
<CommandItem
|
||||||
|
value={team.name}
|
||||||
|
onSelect={() => {
|
||||||
|
selectedTeam = team;
|
||||||
|
onSelect?.(selectedTeam);
|
||||||
|
open = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Check class={cn("mr-2 size-4", team.id !== selectedTeam?.id && "text-transparent")} />
|
||||||
|
{team.name}
|
||||||
|
</CommandItem>
|
||||||
|
{/each}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<Popover bind:open={relationOpen}>
|
||||||
|
<PopoverTrigger>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button {...props} size="icon" variant={currentRelation !== undefined ? "default" : "outline"} disabled={fightId === undefined}>
|
||||||
|
<GitPullRequestArrow />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Kampfverbindung</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
{/snippet}
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<Tabs bind:value={fromType}>
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="FIGHT">Kampf</TabsTrigger>
|
||||||
|
<TabsTrigger value="GROUP">Gruppe</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="FIGHT">
|
||||||
|
<Label>Kampf</Label>
|
||||||
|
<Select bind:value={fromFight} type="single" disabled={data.fights.length === 0}>
|
||||||
|
<SelectTrigger>
|
||||||
|
{fromFightData
|
||||||
|
? `${new Date(fromFightData.start).toLocaleString("de-DE", { timeStyle: "short" })}: ${fromFightData.blueTeam.kuerzel} vs. ${fromFightData.redTeam.kuerzel}`
|
||||||
|
: "Kampf auswählen..."}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{#each data.fights.filter((v) => v.id !== fightId) as fight (fight.id)}
|
||||||
|
<SelectItem value={fight.id.toString()}
|
||||||
|
>{new Date(fight.start).toLocaleString("de-DE", {
|
||||||
|
timeStyle: "short",
|
||||||
|
})}: {fight.blueTeam.kuerzel} vs. {fight.redTeam.kuerzel}</SelectItem
|
||||||
|
>
|
||||||
|
{/each}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>Team</Label>
|
||||||
|
<Select bind:value={fromPlace} type="single" disabled={data.fights.length === 0}>
|
||||||
|
<SelectTrigger>
|
||||||
|
{fromPlace ? (fromPlace === "0" ? "Gewinner" : "Verlierer") : "Platz auswählen..."}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value={"0"}>Gewinner</SelectItem>
|
||||||
|
<SelectItem value={"1"}>Verlierer</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="GROUP">
|
||||||
|
<Label>Gruppe</Label>
|
||||||
|
<Select bind:value={fromGroup} type="single" disabled={data.groups.length === 0}>
|
||||||
|
<SelectTrigger>
|
||||||
|
{fromGroupData ? fromGroupData.name : "Kampf auswählen..."}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{#each data.groups as group (group.id)}
|
||||||
|
<SelectItem value={group.id.toString()}>{group.name}</SelectItem>
|
||||||
|
{/each}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Label>Platz</Label>
|
||||||
|
<Select bind:value={fromPlace} type="single" disabled={data.fights.length === 0}>
|
||||||
|
<SelectTrigger>
|
||||||
|
{fromPlace ? `${parseInt(fromPlace) + 1}. Platz` : "Platz auswählen..."}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{#each Array(32) as _, i}
|
||||||
|
<SelectItem value={i.toString()}>{i + 1}. Platz</SelectItem>
|
||||||
|
{/each}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
<div class="flex justify-end gap-2 mt-2">
|
||||||
|
<Button onclick={clearRelation} variant="destructive">Löschen</Button>
|
||||||
|
<Button onclick={saveRelation} disabled={!canSave}>Übernehmen</Button>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
@ -134,7 +134,7 @@
|
|||||||
<DialogTitle>Fight Erstellen</DialogTitle>
|
<DialogTitle>Fight Erstellen</DialogTitle>
|
||||||
<DialogDescription>Hier kannst du einen neuen Fight erstellen</DialogDescription>
|
<DialogDescription>Hier kannst du einen neuen Fight erstellen</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<FightEdit fight={null} teams={data.teams} event={data.event} groups={data.groups} onSave={handleSave}>
|
<FightEdit fight={null} {data} onSave={handleSave}>
|
||||||
{#snippet actions(dirty, submit)}
|
{#snippet actions(dirty, submit)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
||||||
@ -291,14 +291,7 @@
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
{/each}
|
{/each}
|
||||||
<TableCell class="text-right">
|
<TableCell class="text-right">
|
||||||
<FightEditRow
|
<FightEditRow fight={row.original} {data} onupdate={(update) => (data._fights = data._fights.map((v) => (v.id === update.id ? update : v)))} {refresh}></FightEditRow>
|
||||||
fight={row.original}
|
|
||||||
teams={data.teams}
|
|
||||||
bind:groups={data.groups}
|
|
||||||
event={data.event}
|
|
||||||
onupdate={(update) => (data.fights = data.fights.map((v) => (v.id === update.id ? update : v)))}
|
|
||||||
{refresh}
|
|
||||||
></FightEditRow>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { EventFight, EventFightEdit, ResponseGroups, SWEvent } from "@type/event";
|
import type { EventFight, EventFightEdit, ResponseGroups, ResponseRelation, SWEvent } from "@type/event";
|
||||||
import { Button } from "@components/ui/button";
|
import { Button } from "@components/ui/button";
|
||||||
import { EditIcon, CopyIcon } from "lucide-svelte";
|
import { EditIcon, CopyIcon } from "lucide-svelte";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@components/ui/dialog";
|
||||||
@ -7,21 +7,15 @@
|
|||||||
import type { Team } from "@components/types/team";
|
import type { Team } from "@components/types/team";
|
||||||
import { fightRepo } from "@components/repo/fight";
|
import { fightRepo } from "@components/repo/fight";
|
||||||
import { eventRepo } from "@components/repo/event";
|
import { eventRepo } from "@components/repo/event";
|
||||||
|
import type { EventModel } from "./eventmodel.svelte";
|
||||||
|
|
||||||
let {
|
let { fight, onupdate, refresh, data }: { fight: EventFight; onupdate: (update: EventFight) => void; refresh: () => void; data: EventModel } = $props();
|
||||||
fight,
|
|
||||||
teams,
|
|
||||||
groups = $bindable(),
|
|
||||||
event,
|
|
||||||
onupdate,
|
|
||||||
refresh,
|
|
||||||
}: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void; refresh: () => void } = $props();
|
|
||||||
|
|
||||||
let editOpen = $state(false);
|
let editOpen = $state(false);
|
||||||
let duplicateOpen = $state(false);
|
let duplicateOpen = $state(false);
|
||||||
|
|
||||||
async function handleSave(fightData: EventFightEdit) {
|
async function handleSave(fightData: EventFightEdit) {
|
||||||
let f = await $fightRepo.updateFight(event.id, fight.id, {
|
let f = await $fightRepo.updateFight(data.event.id, fight.id, {
|
||||||
...fightData,
|
...fightData,
|
||||||
blueTeam: fightData.blueTeam.id,
|
blueTeam: fightData.blueTeam.id,
|
||||||
redTeam: fightData.redTeam.id,
|
redTeam: fightData.redTeam.id,
|
||||||
@ -34,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handlyCopy(fightData: EventFightEdit) {
|
async function handlyCopy(fightData: EventFightEdit) {
|
||||||
await $eventRepo.createFight(event.id.toString(), {
|
await $eventRepo.createFight(data.event.id.toString(), {
|
||||||
...fightData,
|
...fightData,
|
||||||
blueTeam: fightData.blueTeam.id,
|
blueTeam: fightData.blueTeam.id,
|
||||||
redTeam: fightData.redTeam.id,
|
redTeam: fightData.redTeam.id,
|
||||||
@ -58,7 +52,7 @@
|
|||||||
<DialogTitle>Fight bearbeiten</DialogTitle>
|
<DialogTitle>Fight bearbeiten</DialogTitle>
|
||||||
<DialogDescription>Hier kannst du die Daten des Kampfes bearbeiten.</DialogDescription>
|
<DialogDescription>Hier kannst du die Daten des Kampfes bearbeiten.</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<FightEdit {fight} {teams} bind:groups {event} onSave={handleSave}>
|
<FightEdit {fight} {data} onSave={handleSave}>
|
||||||
{#snippet actions(dirty, submit)}
|
{#snippet actions(dirty, submit)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
||||||
@ -78,7 +72,7 @@
|
|||||||
<DialogTitle>Fight duplizieren</DialogTitle>
|
<DialogTitle>Fight duplizieren</DialogTitle>
|
||||||
<DialogDescription>Hier kannst du die Daten des duplizierten Fights ändern</DialogDescription>
|
<DialogDescription>Hier kannst du die Daten des duplizierten Fights ändern</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<FightEdit {fight} {teams} bind:groups {event} onSave={handlyCopy}>
|
<FightEdit {fight} {data} onSave={handlyCopy}>
|
||||||
{#snippet actions(dirty, submit)}
|
{#snippet actions(dirty, submit)}
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button onclick={submit}>Speichern</Button>
|
<Button onclick={submit}>Speichern</Button>
|
||||||
|
|||||||
@ -1,21 +1,52 @@
|
|||||||
import type { ResponseUser } from "@components/repo/event";
|
import type { ResponseUser } from "@components/repo/event";
|
||||||
import type { EventFight, ExtendedEvent, ResponseGroups, ResponseRelation, SWEvent } from "@components/types/event";
|
import type { EventFight, ExtendedEvent, ResponseGroups, ResponseRelation, SWEvent } from "@components/types/event";
|
||||||
import type { Team } from "@components/types/team";
|
import type { Team } from "@components/types/team";
|
||||||
|
import { derived } from "svelte/store";
|
||||||
|
|
||||||
export class EventModel {
|
export class EventModel {
|
||||||
public event: SWEvent = $state({} as SWEvent);
|
public event: SWEvent = $state({} as SWEvent);
|
||||||
public teams: Array<Team> = $state([]);
|
public teams: Array<Team> = $state([]);
|
||||||
public groups: Array<ResponseGroups> = $state([]);
|
public groups: Array<ResponseGroups> = $state([]);
|
||||||
public fights: Array<EventFight> = $state([]);
|
public _fights: Array<EventFight> = $state([]);
|
||||||
public referees: Array<ResponseUser> = $state([]);
|
public referees: Array<ResponseUser> = $state([]);
|
||||||
public relations: Array<ResponseRelation> = $state([]);
|
public relations: Array<ResponseRelation> = $state([]);
|
||||||
|
|
||||||
|
public fights = $derived(this.remapFights(this._fights, this.relations));
|
||||||
|
|
||||||
constructor(data: ExtendedEvent) {
|
constructor(data: ExtendedEvent) {
|
||||||
this.event = data.event;
|
this.event = data.event;
|
||||||
|
this.relations = data.relations;
|
||||||
this.teams = data.teams;
|
this.teams = data.teams;
|
||||||
this.groups = data.groups;
|
this.groups = data.groups;
|
||||||
this.fights = data.fights;
|
this._fights = data.fights;
|
||||||
this.referees = data.referees;
|
this.referees = data.referees;
|
||||||
this.relations = data.relations;
|
}
|
||||||
|
|
||||||
|
private remapFights(v: Array<EventFight>, rels: Array<ResponseRelation>) {
|
||||||
|
return v.map((fight) => {
|
||||||
|
let f = JSON.parse(JSON.stringify(fight)) as EventFight;
|
||||||
|
let relations = rels.filter((relation) => relation.fight === f.id);
|
||||||
|
|
||||||
|
relations.forEach((relation) => {
|
||||||
|
let str = "";
|
||||||
|
if (relation.type === "FIGHT") {
|
||||||
|
str += `${relation.fromPlace === 0 ? "Gewinner" : "Verlierer"} von ${relation.fromFight?.blueTeam.name} vs ${relation.fromFight?.redTeam.name} (${new Date(
|
||||||
|
relation.fromFight?.start ?? 0
|
||||||
|
).toLocaleTimeString("de-DE", {
|
||||||
|
timeStyle: "short",
|
||||||
|
})})`;
|
||||||
|
} else {
|
||||||
|
str += `${relation.fromPlace + 1}. Platz von ${relation.fromGroup?.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relation.team === "BLUE") {
|
||||||
|
f.blueTeam.name += ` (${str})`;
|
||||||
|
} else {
|
||||||
|
f.redTeam.name += ` (${str})`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return f;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -186,12 +186,12 @@ export class EventRepo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
public async listRelations(eventId: string): Promise<ResponseRelation[]> {
|
public async listRelations(eventId: number): Promise<ResponseRelation[]> {
|
||||||
return await fetchWithToken(this.token, `/events/${eventId}/relations`)
|
return await fetchWithToken(this.token, `/events/${eventId}/relations`)
|
||||||
.then((value) => value.json())
|
.then((value) => value.json())
|
||||||
.then((value) => z.array(ResponseRelationSchema).parse(value));
|
.then((value) => z.array(ResponseRelationSchema).parse(value));
|
||||||
}
|
}
|
||||||
public async createRelation(eventId: string, relation: CreateEventRelation): Promise<ResponseRelation> {
|
public async createRelation(eventId: number, relation: CreateEventRelation): Promise<ResponseRelation> {
|
||||||
CreateEventRelationSchema.parse(relation);
|
CreateEventRelationSchema.parse(relation);
|
||||||
return await fetchWithToken(this.token, `/events/${eventId}/relations`, {
|
return await fetchWithToken(this.token, `/events/${eventId}/relations`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -206,7 +206,7 @@ export class EventRepo {
|
|||||||
.then((value) => value.json())
|
.then((value) => value.json())
|
||||||
.then(ResponseRelationSchema.parse);
|
.then(ResponseRelationSchema.parse);
|
||||||
}
|
}
|
||||||
public async updateRelation(eventId: string, relationId: string, relation: UpdateEventRelation): Promise<ResponseRelation> {
|
public async updateRelation(eventId: number, relationId: number, relation: UpdateEventRelation): Promise<ResponseRelation> {
|
||||||
UpdateEventRelationSchema.parse(relation);
|
UpdateEventRelationSchema.parse(relation);
|
||||||
return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
|
return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@ -216,7 +216,7 @@ export class EventRepo {
|
|||||||
.then((value) => value.json())
|
.then((value) => value.json())
|
||||||
.then(ResponseRelationSchema.parse);
|
.then(ResponseRelationSchema.parse);
|
||||||
}
|
}
|
||||||
public async deleteRelation(eventId: string, relationId: string): Promise<boolean> {
|
public async deleteRelation(eventId: number, relationId: number): Promise<boolean> {
|
||||||
const res = await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
|
const res = await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -60,10 +60,11 @@ export type ResponseGroups = z.infer<typeof ResponseGroupsSchema>;
|
|||||||
|
|
||||||
export const ResponseRelationSchema = z.object({
|
export const ResponseRelationSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
fight: EventFightSchema,
|
fight: z.number(),
|
||||||
|
team: z.enum(["RED", "BLUE"]),
|
||||||
type: z.enum(["FIGHT", "GROUP"]),
|
type: z.enum(["FIGHT", "GROUP"]),
|
||||||
fromFight: EventFightSchema.nullable(),
|
fromFight: EventFightSchema.optional(),
|
||||||
fromGroup: ResponseGroupsSchema.nullable(),
|
fromGroup: ResponseGroupsSchema.optional(),
|
||||||
fromPlace: z.number(),
|
fromPlace: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user