feat: Add FightEdit and GroupEdit components for enhanced event management
All checks were successful
SteamWarCI Build successful

This commit is contained in:
2025-05-10 22:22:12 +02:00
parent 7d67ad0950
commit 1da279bb24
7 changed files with 308 additions and 7 deletions

View File

@ -0,0 +1,219 @@
<script lang="ts">
import type { EventFight } 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 { 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";
const {
fight,
teams,
actions,
onSave,
}: {
fight: EventFight | null;
teams: Team[];
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;
} = $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") : now("Europe/Berlin"));
let fightErgebnis = $state(fight?.ergebnis ?? 0);
let mapsStore = $derived(maps(fightModus ?? "null"));
let dirty = $derived(
fightModus !== fight?.spielmodus ||
fightMap !== fight?.map ||
fightBlueTeam !== fight?.blueTeam ||
fightRedTeam !== fight?.redTeam ||
fightStart.toDate().getTime() !== fight?.start ||
fightErgebnis !== fight?.ergebnis
);
function submit() {
onSave({
spielmodus: fightModus!,
map: fightMap!,
blueTeam: fightBlueTeam!,
redTeam: fightRedTeam!,
start: fightStart?.toDate().getTime(),
ergebnis: fightErgebnis,
});
}
</script>
<div class="flex flex-col gap-2">
<Label for="fight-modus">Modus</Label>
<Popover>
<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;
}}
>
<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>
<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;
}}
>
<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>
<PopoverTrigger>
{#snippet child({ props })}
<Button variant="outline" class="justify-between" {...props} role="combobox">
{teams.find((value) => value === fightBlueTeam) || 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 Maps..." />
<CommandList>
<CommandEmpty>No map found.</CommandEmpty>
<CommandGroup>
{#each teams as team}
<CommandItem
value={team.name}
onSelect={() => {
fightBlueTeam = team;
}}
>
<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>
<PopoverTrigger>
{#snippet child({ props })}
<Button variant="outline" class="justify-between" {...props} role="combobox">
{teams.find((value) => value === fightRedTeam) || 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 Maps..." />
<CommandList>
<CommandEmpty>No map found.</CommandEmpty>
<CommandGroup>
{#each teams as team}
<CommandItem
value={team.name}
onSelect={() => {
fightRedTeam = team;
}}
>
<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} />
<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>
</div>
{@render actions(dirty, submit)}

View File

@ -106,6 +106,15 @@
<CommandList>
<CommandEmpty>No schematic type found.</CommandEmpty>
<CommandGroup>
<CommandItem
value={"null"}
onSelect={() => {
eventSchematicType = null;
}}
>
<Check class={cn("mr-2 size-4", eventSchematicType !== null && "text-transparent")} />
Keinen
</CommandItem>
{#each $schemTypes as type}
<CommandItem
value={type.db}

View File

@ -18,6 +18,10 @@
-->
<script lang="ts">
import FightEditRow from "./FightEditRow.svelte";
import GroupEditRow from "./GroupEditRow.svelte";
import type { 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";
@ -25,8 +29,10 @@
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 { Button } from "@components/ui/button";
import { MenuIcon } from "lucide-svelte";
let { data }: { data: ExtendedEvent } = $props();
let { data = $bindable() }: { data: ExtendedEvent } = $props();
let sorting = $state<SortingState>([]);
let columnFilters = $state<ColumnFiltersState>([]);
@ -126,15 +132,20 @@
{#each table.getRowModel().rows as groupRow (groupRow.id)}
{#if groupRow.getIsGrouped()}
<TableRow class="bg-muted font-bold">
<TableCell colspan={columns.length}>
<TableCell colspan={columns.length - 1}>
<Checkbox
checked={groupRow.getIsSelected()}
indeterminate={groupRow.getIsSomeSelected() && !groupRow.getIsSelected()}
onCheckedChange={() => groupRow.toggleSelected()}
class="mr-4"
/>
Gruppe: {groupRow.getValue("group") ?? "Keine"}
{groupRow.getValue("group") ?? "Keine Gruppe"}
</TableCell>
{#if groupRow.original.group != null}
<TableCell class="text-right">
<GroupEditRow group={groupRow.original.group}></GroupEditRow>
</TableCell>
{/if}
</TableRow>
{#each groupRow.subRows as row (row.id)}
<TableRow data-state={row.getIsSelected() && "selected"}>
@ -143,6 +154,9 @@
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</TableCell>
{/each}
<TableCell class="text-right">
<FightEditRow fight={row.original} teams={data.teams}></FightEditRow>
</TableCell>
</TableRow>
{/each}
{:else}

View File

@ -24,7 +24,7 @@
import RefereesList from "@components/moderator/pages/event/RefereesList.svelte";
import TeamTable from "@components/moderator/pages/event/TeamTable.svelte";
const { event }: { event: ExtendedEvent } = $props();
let { event }: { event: ExtendedEvent } = $props();
</script>
<div class="flex flex-col m-4 p-4 rounded-md border gap-4">
@ -35,12 +35,12 @@
</div>
<div class="md:ml-4 md:pl-4 md:border-l md:w-1/3">
<h2 class="text-xl font-bold mb-4">Teams</h2>
<TeamTable {event} />
<TeamTable bind:event />
</div>
<div class="md:ml-4 md:pl-4 md:border-l md:w-1/3">
<h2 class="text-xl font-bold mb-4">Referees</h2>
<RefereesList {event} />
</div>
</div>
<EventFightList data={event} />
<EventFightList bind:data={event} />
</div>

View File

@ -0,0 +1,41 @@
<script lang="ts">
import type { EventFight } from "@type/event";
import { Button } from "@components/ui/button";
import { EditIcon, MenuIcon } 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";
const { fight, teams }: { fight: EventFight; teams: Team[] } = $props();
function handleSave(fightData) {
// Handle the save action here
console.log("Fight data saved:", fightData);
}
</script>
<div>
<Dialog>
<DialogTrigger>
<Button variant="ghost" size="icon">
<EditIcon />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Fight bearbeiten</DialogTitle>
<DialogDescription>Hier kannst du die Daten des Kampfes bearbeiten.</DialogDescription>
</DialogHeader>
<FightEdit {fight} {teams} onSave={handleSave}>
{#snippet actions(dirty, submit)}
<DialogFooter>
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
</DialogFooter>
{/snippet}
</FightEdit>
</DialogContent>
</Dialog>
<Button variant="ghost" size="icon">
<MenuIcon />
</Button>
</div>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import type { ResponseGroups } from "@type/event";
import { Button } from "@components/ui/button";
import { MenuIcon } from "lucide-svelte";
let { group }: { group: ResponseGroups } = $props();
</script>
<div>
<Button variant="ghost" size="icon">
<MenuIcon />
</Button>
<Button variant="ghost" size="icon">
<MenuIcon />
</Button>
</div>

View File

@ -26,18 +26,20 @@
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
import { teams } from "@components/stores/stores";
const { event }: { event: ExtendedEvent } = $props();
const { event = $bindable() }: { event: ExtendedEvent } = $props();
let team = $state(event.teams);
async function addTeam(value: number) {
await $eventRepo.updateTeams(event.event.id.toString(), [value]);
team = await $eventRepo.listTeams(event.event.id.toString());
event.teams = team;
}
async function removeTeam(value: number) {
await $eventRepo.deleteTeams(event.event.id.toString(), [value]);
team = await $eventRepo.listTeams(event.event.id.toString());
event.teams = team;
}
let teamSearch = $state("");