feat: Implement group management features with dialogs for editing and displaying group results, enhance event creation with a form, and update team and referee management UI
All checks were successful
SteamWarCI Build successful
All checks were successful
SteamWarCI Build successful
This commit is contained in:
@ -23,7 +23,7 @@
|
|||||||
event,
|
event,
|
||||||
actions,
|
actions,
|
||||||
onSave,
|
onSave,
|
||||||
groups,
|
groups = $bindable(),
|
||||||
}: {
|
}: {
|
||||||
fight: EventFight | null;
|
fight: EventFight | null;
|
||||||
teams: Team[];
|
teams: Team[];
|
||||||
@ -139,7 +139,7 @@
|
|||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="p-0">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="Search Maps..." />
|
<CommandInput placeholder="Search Teams..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No map found.</CommandEmpty>
|
<CommandEmpty>No map found.</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
@ -165,17 +165,30 @@
|
|||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
||||||
{teams.find((value) => value === fightBlueTeam) || fightBlueTeam?.name || "Select a team..."}
|
{teams.find((value) => value.id === fightBlueTeam?.id)?.name || fightBlueTeam?.name || "Select a team..."}
|
||||||
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="p-0">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="Search Maps..." />
|
<CommandInput placeholder="Search Teams..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No map found.</CommandEmpty>
|
<CommandEmpty>No team found.</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandItem
|
||||||
|
value={"-1"}
|
||||||
|
onSelect={() => {
|
||||||
|
fightBlueTeam = {
|
||||||
|
id: -1,
|
||||||
|
name: "?",
|
||||||
|
color: "7",
|
||||||
|
kuerzel: "?",
|
||||||
|
};
|
||||||
|
blueTeamSelectOpen = false;
|
||||||
|
}}
|
||||||
|
keywords={["?"]}>???</CommandItem
|
||||||
|
>
|
||||||
|
<CommandGroup heading="Teams">
|
||||||
{#each teams as team}
|
{#each teams as team}
|
||||||
<CommandItem
|
<CommandItem
|
||||||
value={team.name}
|
value={team.name}
|
||||||
@ -198,17 +211,30 @@
|
|||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
{#snippet child({ props })}
|
{#snippet child({ props })}
|
||||||
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
<Button variant="outline" class="justify-between" {...props} role="combobox">
|
||||||
{teams.find((value) => value === fightRedTeam) || fightRedTeam?.name || "Select a team..."}
|
{teams.find((value) => value.id === fightRedTeam?.id)?.name || fightRedTeam?.name || "Select a team..."}
|
||||||
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="p-0">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput placeholder="Search Maps..." />
|
<CommandInput placeholder="Search Teams..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No map found.</CommandEmpty>
|
<CommandEmpty>No team found.</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandItem
|
||||||
|
value={"-1"}
|
||||||
|
onSelect={() => {
|
||||||
|
fightRedTeam = {
|
||||||
|
id: -1,
|
||||||
|
name: "?",
|
||||||
|
color: "7",
|
||||||
|
kuerzel: "?",
|
||||||
|
};
|
||||||
|
redTeamSelectOpen = false;
|
||||||
|
}}
|
||||||
|
keywords={["?"]}>???</CommandItem
|
||||||
|
>
|
||||||
|
<CommandGroup heading="Teams">
|
||||||
{#each teams as team}
|
{#each teams as team}
|
||||||
<CommandItem
|
<CommandItem
|
||||||
value={team.name}
|
value={team.name}
|
||||||
@ -262,6 +288,8 @@
|
|||||||
<PlusIcon class={"mr-2 size-4"} />
|
<PlusIcon class={"mr-2 size-4"} />
|
||||||
Neue Gruppe
|
Neue Gruppe
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
|
||||||
|
<CommandGroup heading="Gruppen">
|
||||||
<CommandItem
|
<CommandItem
|
||||||
value={"none"}
|
value={"none"}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
@ -290,6 +318,7 @@
|
|||||||
</CommandItem>
|
</CommandItem>
|
||||||
{/each}
|
{/each}
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|||||||
@ -20,23 +20,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FightEditRow from "./FightEditRow.svelte";
|
import FightEditRow from "./FightEditRow.svelte";
|
||||||
|
|
||||||
import GroupEditRow from "./GroupEditRow.svelte";
|
|
||||||
|
|
||||||
import type { EventFightEdit, ExtendedEvent } from "@type/event";
|
import type { EventFightEdit, ExtendedEvent } from "@type/event";
|
||||||
import { createSvelteTable, FlexRender } from "@components/ui/data-table";
|
import { createSvelteTable, FlexRender } from "@components/ui/data-table";
|
||||||
import { type ColumnFiltersState, getCoreRowModel, getFilteredRowModel, getGroupedRowModel, getSortedRowModel, type RowSelectionState, type SortingState } from "@tanstack/table-core";
|
import { type ColumnFiltersState, getCoreRowModel, getFilteredRowModel, getGroupedRowModel, getSortedRowModel, type RowSelectionState, type SortingState } from "@tanstack/table-core";
|
||||||
import { columns } from "./columns";
|
import { columns } from "./columns";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
|
||||||
import { Checkbox } from "@components/ui/checkbox";
|
import { Checkbox } from "@components/ui/checkbox";
|
||||||
import { Menubar, MenubarContent, MenubarItem, MenubarGroup, MenubarGroupHeading, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@components/ui/menubar";
|
import { Menubar, MenubarContent, MenubarItem, MenubarGroup, MenubarGroupHeading, MenubarMenu, MenubarTrigger, MenubarSub, MenubarSubTrigger, MenubarSubContent } from "@components/ui/menubar";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
||||||
import FightEdit from "@components/moderator/components/FightEdit.svelte";
|
import FightEdit from "@components/moderator/components/FightEdit.svelte";
|
||||||
import { Button } from "@components/ui/button";
|
import { Button } from "@components/ui/button";
|
||||||
import { eventRepo } from "@components/repo/event";
|
import { eventRepo } from "@components/repo/event";
|
||||||
|
import GroupEditDialog from "./GroupEditDialog.svelte";
|
||||||
|
import GroupResultsDialog from "./GroupResultsDialog.svelte";
|
||||||
|
import type { ResponseGroups } from "@type/event";
|
||||||
|
|
||||||
let { data = $bindable() }: { data: ExtendedEvent } = $props();
|
let { data = $bindable() }: { data: ExtendedEvent } = $props();
|
||||||
|
|
||||||
let fights = $state(data.fights);
|
let fights = $state(data.fights);
|
||||||
|
let groups = $state(data.groups);
|
||||||
|
|
||||||
let sorting = $state<SortingState>([]);
|
let sorting = $state<SortingState>([]);
|
||||||
let columnFilters = $state<ColumnFiltersState>([]);
|
let columnFilters = $state<ColumnFiltersState>([]);
|
||||||
@ -94,6 +96,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
let createOpen = $state(false);
|
let createOpen = $state(false);
|
||||||
|
let editGroupOpen = $state(false);
|
||||||
|
let selectedGroup: ResponseGroups | null = $state(null);
|
||||||
|
let groupResultsOpen = $state(false);
|
||||||
|
let selectedGroupForResults: ResponseGroups | null = $state(null);
|
||||||
|
|
||||||
async function handleSave(fight: EventFightEdit) {
|
async function handleSave(fight: EventFightEdit) {
|
||||||
await $eventRepo.createFight(data.event.id.toString(), {
|
await $eventRepo.createFight(data.event.id.toString(), {
|
||||||
@ -102,13 +108,50 @@
|
|||||||
redTeam: fight.redTeam.id,
|
redTeam: fight.redTeam.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
fights = await $eventRepo.listFights(data.event.id.toString());
|
reload();
|
||||||
createOpen = false;
|
createOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openGroupEditDialog(group: ResponseGroups) {
|
||||||
|
selectedGroup = group;
|
||||||
|
editGroupOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openGroupResultsDialog(group: ResponseGroups) {
|
||||||
|
selectedGroupForResults = group;
|
||||||
|
groupResultsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reload() {
|
||||||
|
fights = await $eventRepo.listFights(data.event.id.toString());
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-fit">
|
<Dialog bind:open={createOpen}>
|
||||||
<Dialog bind:open={createOpen}>
|
<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>
|
||||||
|
|
||||||
|
{#if selectedGroup}
|
||||||
|
<GroupEditDialog bind:open={editGroupOpen} group={selectedGroup} event={data.event} bind:groups />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if selectedGroupForResults}
|
||||||
|
<GroupResultsDialog bind:open={groupResultsOpen} group={selectedGroupForResults} teams={data.teams} {fights} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<Menubar>
|
<Menubar>
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
|
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
|
||||||
@ -129,21 +172,24 @@
|
|||||||
</MenubarGroup>
|
</MenubarGroup>
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
|
<MenubarMenu>
|
||||||
|
<MenubarTrigger disabled={!groups.length}>Gruppen</MenubarTrigger>
|
||||||
|
<MenubarContent>
|
||||||
|
{#each groups as group (group.id)}
|
||||||
|
<MenubarSub>
|
||||||
|
<MenubarSubTrigger>
|
||||||
|
{group.name}
|
||||||
|
</MenubarSubTrigger>
|
||||||
|
<MenubarSubContent>
|
||||||
|
<MenubarItem onclick={() => openGroupEditDialog(group)}>Bearbeiten</MenubarItem>
|
||||||
|
<MenubarItem onclick={() => openGroupResultsDialog(group)}>Gruppen Ergebnisse</MenubarItem>
|
||||||
|
</MenubarSubContent>
|
||||||
|
</MenubarSub>
|
||||||
|
{/each}
|
||||||
|
</MenubarContent>
|
||||||
|
</MenubarMenu>
|
||||||
</Menubar>
|
</Menubar>
|
||||||
<DialogContent>
|
<Button variant="outline" class="ml-4" onclick={reload}>Neu laden</Button>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<Table>
|
<Table>
|
||||||
@ -163,6 +209,7 @@
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{#each table.getRowModel().rows as groupRow (groupRow.id)}
|
{#each table.getRowModel().rows as groupRow (groupRow.id)}
|
||||||
{#if groupRow.getIsGrouped()}
|
{#if groupRow.getIsGrouped()}
|
||||||
|
{@const group = groups.find((g) => g.id === groupRow.getValue("group"))}
|
||||||
<TableRow class="font-bold">
|
<TableRow class="font-bold">
|
||||||
<TableCell colspan={columns.length - 1}>
|
<TableCell colspan={columns.length - 1}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -171,13 +218,9 @@
|
|||||||
onCheckedChange={() => groupRow.toggleSelected()}
|
onCheckedChange={() => groupRow.toggleSelected()}
|
||||||
class="mr-4"
|
class="mr-4"
|
||||||
/>
|
/>
|
||||||
{groupRow.getValue("group") ?? "Keine Gruppe"}
|
{group?.name ?? "Keine Gruppe"}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{#if groupRow.original.group != null}
|
<TableCell class="text-right"></TableCell>
|
||||||
<TableCell class="text-right">
|
|
||||||
<GroupEditRow group={groupRow.original.group}></GroupEditRow>
|
|
||||||
</TableCell>
|
|
||||||
{/if}
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{#each groupRow.subRows as row (row.id)}
|
{#each groupRow.subRows as row (row.id)}
|
||||||
<TableRow data-state={row.getIsSelected() && "selected"}>
|
<TableRow data-state={row.getIsSelected() && "selected"}>
|
||||||
@ -187,12 +230,7 @@
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
{/each}
|
{/each}
|
||||||
<TableCell class="text-right">
|
<TableCell class="text-right">
|
||||||
<FightEditRow
|
<FightEditRow fight={row.original} teams={data.teams} bind:groups event={data.event} onupdate={(update) => (fights = fights.map((v) => (v.id === update.id ? update : v)))}
|
||||||
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>
|
></FightEditRow>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
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";
|
||||||
|
|
||||||
let { fight, teams, groups, event, onupdate }: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void } = $props();
|
let { fight, teams, groups = $bindable(), event, onupdate }: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void } = $props();
|
||||||
|
|
||||||
let editOpen = $state(false);
|
let editOpen = $state(false);
|
||||||
|
|
||||||
@ -37,7 +37,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} {groups} {event} onSave={handleSave}>
|
<FightEdit {fight} {teams} bind:groups {event} 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>
|
||||||
|
|||||||
45
src/components/moderator/pages/event/GroupEditDialog.svelte
Normal file
45
src/components/moderator/pages/event/GroupEditDialog.svelte
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { GroupUpdateEdit, ResponseGroups, SWEvent } from "@type/event";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
||||||
|
import GroupEdit from "@components/moderator/components/GroupEdit.svelte";
|
||||||
|
import { Button } from "@components/ui/button";
|
||||||
|
import { eventRepo } from "@repo/event";
|
||||||
|
|
||||||
|
let { group, groups = $bindable(), open = $bindable(), event }: { group: ResponseGroups; groups: ResponseGroups[]; open?: boolean; event: SWEvent } = $props();
|
||||||
|
|
||||||
|
async function handleSave(groupData: GroupUpdateEdit) {
|
||||||
|
if (!group) return;
|
||||||
|
const updatedGroup = await $eventRepo.updateGroup(event.id.toString(), group.id.toString(), groupData);
|
||||||
|
groups = groups.map((g) => (g.id === updatedGroup.id ? updatedGroup : g));
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete() {
|
||||||
|
if (!group) return;
|
||||||
|
await $eventRepo.deleteGroup(event.id.toString(), group.id.toString());
|
||||||
|
groups = groups.filter((g) => g.id !== group.id);
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if group}
|
||||||
|
<Dialog bind:open>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Gruppe Bearbeiten: {group.name}</DialogTitle>
|
||||||
|
<DialogDescription>Hier kannst du die Gruppendetails bearbeiten.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<GroupEdit {group} onSave={handleSave}>
|
||||||
|
{#snippet actions(dirty, submit)}
|
||||||
|
<DialogFooter class="flex justify-between">
|
||||||
|
<Button variant="destructive" onclick={handleDelete}>Löschen</Button>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button variant="outline" onclick={() => (open = false)}>Abbrechen</Button>
|
||||||
|
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
||||||
|
</div>
|
||||||
|
</DialogFooter>
|
||||||
|
{/snippet}
|
||||||
|
</GroupEdit>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
{/if}
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<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>
|
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { EventFight, ExtendedEvent, ResponseGroups, ResponseTeam } from "@type/event";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
|
||||||
|
import { Button } from "@components/ui/button";
|
||||||
|
import type { Team } from "@components/types/team";
|
||||||
|
|
||||||
|
let { open = $bindable(), group, teams, fights }: { open?: boolean; group: ResponseGroups; teams: Team[]; fights: EventFight[] } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog bind:open>
|
||||||
|
<DialogContent class="sm:max-w-[600px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Ergebnisse: {group?.name}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Punkte: Sieg: {group?.pointsPerWin}, Unentschieden: {group?.pointsPerDraw}, Niederlage: {group?.pointsPerLoss}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
{#if group.points !== null}
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Team</TableHead>
|
||||||
|
<TableHead class="text-right">Spiele</TableHead>
|
||||||
|
<TableHead class="text-right">Punkte</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{#each Object.entries(group.points).toSorted((a, b) => b[1] - a[1]) as [teamIdString, points] (teamIdString)}
|
||||||
|
{@const teamId = Number(teamIdString)}
|
||||||
|
{@const team = teams.find((t) => t.id === teamId) as ResponseTeam}
|
||||||
|
{@const playedGames = fights.filter((f) => f.start > Date.now() && f.group?.id === group.id && (f.blueTeam.id === teamId || f.redTeam.id === teamId)).length}
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{team.name} ({team.kuerzel})</TableCell>
|
||||||
|
<TableCell class="text-right">{playedGames}</TableCell>
|
||||||
|
<TableCell class="text-right font-bold">{points}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
{/each}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
{:else}
|
||||||
|
<p class="text-center py-4">Noch keine Ergebnisse für diese Gruppe vorhanden oder keine Spiele zugeordnet.</p>
|
||||||
|
{/if}
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onclick={() => (open = false)}>Schließen</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>{referee.name}</TableCell>
|
<TableCell>{referee.name}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button onclick={() => removeReferee(referee.uuid)}>Remove</Button>
|
<Button onclick={() => removeReferee(referee.uuid)} variant="outline" size="sm">{referee.name} entfernen</Button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{/each}
|
{/each}
|
||||||
@ -63,7 +63,7 @@
|
|||||||
<Popover>
|
<Popover>
|
||||||
<TableCaption>
|
<TableCaption>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button>Add</Button>
|
<Button>Hinzufügen</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
</TableCaption>
|
</TableCaption>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="p-0">
|
||||||
|
|||||||
@ -59,7 +59,7 @@
|
|||||||
<TableCell>{t.kuerzel}</TableCell>
|
<TableCell>{t.kuerzel}</TableCell>
|
||||||
<TableCell>{t.name}</TableCell>
|
<TableCell>{t.name}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button onclick={() => removeTeam(t.id)}>Remove</Button>
|
<Button onclick={() => removeTeam(t.id)} variant="outline" size="sm">{t.name} abmelden</Button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{/each}
|
{/each}
|
||||||
@ -72,7 +72,7 @@
|
|||||||
<Popover>
|
<Popover>
|
||||||
<TableCaption>
|
<TableCaption>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<Button>Add Team</Button>
|
<Button>Team Anmelden</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
</TableCaption>
|
</TableCaption>
|
||||||
<PopoverContent class="p-0">
|
<PopoverContent class="p-0">
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export const columns: ColumnDef<EventFight> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "Gruppe",
|
header: "Gruppe",
|
||||||
accessorKey: "group.name",
|
accessorKey: "group.id",
|
||||||
id: "group",
|
id: "group",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -20,12 +20,136 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { eventRepo } from "@repo/event.ts";
|
import { eventRepo } from "@repo/event.ts";
|
||||||
import EventCard from "@components/moderator/components/EventCard.svelte";
|
import EventCard from "@components/moderator/components/EventCard.svelte";
|
||||||
|
import { Button } from "@components/ui/button/index.js";
|
||||||
|
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@components/ui/dialog/index.js";
|
||||||
|
import { Input } from "@components/ui/input/index.js";
|
||||||
|
import { Label } from "@components/ui/label/index.js";
|
||||||
|
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
|
||||||
|
import { PlusIcon } from "lucide-svelte";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import { fromAbsolute, now, ZonedDateTime } from "@internationalized/date";
|
||||||
|
|
||||||
let eventsFuture = $state($eventRepo.listEvents());
|
let eventsFuture = $state($eventRepo.listEvents());
|
||||||
let millis = Date.now();
|
let millis = Date.now();
|
||||||
|
|
||||||
|
let createOpen = $state(false);
|
||||||
|
let newEventName = $state("");
|
||||||
|
let newEventStart: ZonedDateTime = $state(now("Europe/Berlin"));
|
||||||
|
let newEventEnd: ZonedDateTime = $state(
|
||||||
|
now("Europe/Berlin").add({
|
||||||
|
days: 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let isSubmitting = $state(false);
|
||||||
|
let errorMsg = $state("");
|
||||||
|
|
||||||
|
function resetFormFields() {
|
||||||
|
newEventName = "";
|
||||||
|
newEventStart = now("Europe/Berlin");
|
||||||
|
newEventEnd = now("Europe/Berlin").add({
|
||||||
|
days: 1,
|
||||||
|
});
|
||||||
|
errorMsg = "";
|
||||||
|
isSubmitting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (createOpen) {
|
||||||
|
resetFormFields();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const canSubmit = $derived(
|
||||||
|
newEventName.trim() !== "" &&
|
||||||
|
newEventStart &&
|
||||||
|
newEventEnd &&
|
||||||
|
dayjs(newEventStart.toDate()).isValid() &&
|
||||||
|
dayjs(newEventEnd.toDate()).isValid() &&
|
||||||
|
newEventStart.toDate() < newEventEnd.toDate() &&
|
||||||
|
!isSubmitting
|
||||||
|
);
|
||||||
|
|
||||||
|
async function submitCreateEvent() {
|
||||||
|
if (!canSubmit) return;
|
||||||
|
|
||||||
|
isSubmitting = true;
|
||||||
|
errorMsg = "";
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: newEventName.trim(),
|
||||||
|
start: dayjs(newEventStart.toDate()),
|
||||||
|
end: dayjs(newEventEnd.toDate()),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $eventRepo.createEvent(payload);
|
||||||
|
eventsFuture = $eventRepo.listEvents(); // Refresh the list
|
||||||
|
createOpen = false;
|
||||||
|
} catch (e: any) {
|
||||||
|
errorMsg = e.message || "Failed to create event. Please try again.";
|
||||||
|
console.error("Failed to create event:", e);
|
||||||
|
} finally {
|
||||||
|
isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h1 class="text-3xl font-semibold">Events</h1>
|
||||||
|
<Dialog bind:open={createOpen}>
|
||||||
|
<DialogTrigger>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<Button variant="outline" {...props}>
|
||||||
|
<PlusIcon class="mr-2" />
|
||||||
|
Create Event
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent class="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create New Event</DialogTitle>
|
||||||
|
<DialogDescription>Fill in the details for the new event. Click create when you're done.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div class="grid gap-4 py-4">
|
||||||
|
<div class="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label for="eventName" class="text-right">Name</Label>
|
||||||
|
<Input id="eventName" bind:value={newEventName} class="col-span-3" placeholder="Event Name" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label for="eventStart" class="text-right">Start</Label>
|
||||||
|
<div class="col-span-3">
|
||||||
|
<DateTimePicker bind:value={newEventStart} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-4 items-center gap-4">
|
||||||
|
<Label for="eventEnd" class="text-right">End</Label>
|
||||||
|
<div class="col-span-3">
|
||||||
|
<DateTimePicker bind:value={newEventEnd} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if errorMsg}
|
||||||
|
<p class="col-span-4 text-sm text-red-600 dark:text-red-500 text-center">{errorMsg}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<Button variant="outline" {...props}>Cancel</Button>
|
||||||
|
{/snippet}
|
||||||
|
</DialogClose>
|
||||||
|
<Button onclick={submitCreateEvent} disabled={!canSubmit}>
|
||||||
|
{#if isSubmitting}
|
||||||
|
Creating...
|
||||||
|
{:else}
|
||||||
|
Create Event
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#await eventsFuture}
|
{#await eventsFuture}
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
{:then events}
|
{:then events}
|
||||||
@ -45,7 +169,5 @@
|
|||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:catch e}
|
|
||||||
|
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user