feat: Add FightEdit and GroupEdit components for enhanced event management
All checks were successful
SteamWarCI Build successful
All checks were successful
SteamWarCI Build successful
This commit is contained in:
219
src/components/moderator/components/FightEdit.svelte
Normal file
219
src/components/moderator/components/FightEdit.svelte
Normal 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)}
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
41
src/components/moderator/pages/event/FightEditRow.svelte
Normal file
41
src/components/moderator/pages/event/FightEditRow.svelte
Normal 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>
|
||||
16
src/components/moderator/pages/event/GroupEditRow.svelte
Normal file
16
src/components/moderator/pages/event/GroupEditRow.svelte
Normal 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>
|
||||
@ -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("");
|
||||
|
||||
Reference in New Issue
Block a user