Files
Website/src/components/moderator/pages/event/EventFightList.svelte
2025-09-28 14:11:58 +02:00

314 lines
13 KiB
Svelte

<!--
- This file is a part of the SteamWar software.
-
- Copyright (C) 2025 SteamWar.de-Serverteam
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script lang="ts">
import FightEditRow from "./FightEditRow.svelte";
import type { EventFightEdit } 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";
import { columns } from "./columns";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
import { Checkbox } from "@components/ui/checkbox";
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 FightEdit from "@components/moderator/components/FightEdit.svelte";
import { Button } from "@components/ui/button";
import { eventRepo } from "@components/repo/event";
import GroupEditDialog from "./GroupEditDialog.svelte";
import GroupResultsDialog from "./GroupResultsDialog.svelte";
import type { ResponseGroups } from "@type/event";
import { EditIcon, GroupIcon, LinkIcon } from "lucide-svelte";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@components/ui/dropdown-menu";
import GroupSelector from "@components/moderator/components/GroupSelector.svelte";
import { fightRepo } from "@components/repo/fight";
import type { EventModel } from "./eventmodel.svelte";
let { data = $bindable(), refresh }: { data: EventModel; refresh: () => void } = $props();
let sorting = $state<SortingState>([]);
let columnFilters = $state<ColumnFiltersState>([]);
let selection = $state<RowSelectionState>({});
const table = createSvelteTable({
get data() {
return data.fights;
},
initialState: {
columnOrder: ["auswahl", "begegnung", "group"],
},
state: {
get sorting() {
return sorting;
},
get columnFilters() {
return columnFilters;
},
get grouping() {
return ["group"];
},
get rowSelection() {
return selection;
},
},
onSortingChange: (updater) => {
if (typeof updater === "function") {
sorting = updater(sorting);
} else {
sorting = updater;
}
},
onColumnFiltersChange: (updater) => {
if (typeof updater === "function") {
columnFilters = updater(columnFilters);
} else {
columnFilters = updater;
}
},
onRowSelectionChange: (updater) => {
if (typeof updater === "function") {
selection = updater(selection);
} else {
selection = updater;
}
},
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getGroupedRowModel: getGroupedRowModel(),
groupedColumnMode: "remove",
getRowId: (row) => row.id.toString(),
});
let createOpen = $state(false);
let editGroupOpen = $state(false);
let selectedGroup: ResponseGroups | null = $state(null);
let groupResultsOpen = $state(false);
let selectedGroupForResults: ResponseGroups | null = $state(null);
let groupChangeOpen = $state(false);
let groupChangeSelected: number | null = $state(null);
async function handleSave(fight: EventFightEdit) {
await $eventRepo.createFight(data.event.id.toString(), {
...fight,
blueTeam: fight.blueTeam.id,
redTeam: fight.redTeam.id,
});
refresh();
createOpen = false;
}
function openGroupEditDialog(group: ResponseGroups) {
selectedGroup = group;
editGroupOpen = true;
}
function openGroupResultsDialog(group: ResponseGroups) {
selectedGroupForResults = group;
groupResultsOpen = true;
}
</script>
<Dialog bind:open={createOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Fight Erstellen</DialogTitle>
<DialogDescription>Hier kannst du einen neuen Fight erstellen</DialogDescription>
</DialogHeader>
<FightEdit fight={null} {data} 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={data.groups} />
{/if}
{#if selectedGroupForResults}
<GroupResultsDialog bind:open={groupResultsOpen} group={selectedGroupForResults} teams={data.teams} fights={data.fights} />
{/if}
<Dialog bind:open={groupChangeOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Gruppe Ändern</DialogTitle>
<DialogDescription>Hier kannst du die Gruppe der ausgewählten Kämpfe ändern</DialogDescription>
</DialogHeader>
<GroupSelector event={data.event} bind:groups={data.groups} bind:value={groupChangeSelected} />
<DialogFooter>
<Button
onclick={async () => {
groupChangeOpen = false;
let group = data.groups.find((g) => g.id === groupChangeSelected);
if (group) {
let selectedGroups = table.getSelectedRowModel().rows.map((row) => row.original);
for (const g of selectedGroups) {
await $fightRepo.updateFight(data.event.id, g.id, {
group: group.id,
spielmodus: null,
map: null,
blueTeam: null,
redTeam: null,
start: null,
spectatePort: null,
});
}
refresh();
}
}}>Speichern</Button
>
</DialogFooter>
</DialogContent>
</Dialog>
<div class="flex items-center justify-between">
<Menubar>
<MenubarMenu>
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
<MenubarContent>
<MenubarItem onclick={() => (groupChangeOpen = true)}>Gruppe Ändern</MenubarItem>
<MenubarItem disabled>Startzeit Verschieben</MenubarItem>
<MenubarItem disabled>Spectate Port Ändern</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Erstellen</MenubarTrigger>
<MenubarContent>
<MenubarItem onclick={() => (createOpen = true)}>Fight Erstellen</MenubarItem>
<MenubarGroup>
<MenubarGroupHeading>Generatoren</MenubarGroupHeading>
<a href="#/event/{data.event.id}/generate">
<MenubarItem>Gruppenphase</MenubarItem>
</a>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger disabled={!data.groups.length}>Gruppen</MenubarTrigger>
<MenubarContent>
{#each data.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>
<Button variant="outline" class="ml-4" onclick={refresh}>Neu laden</Button>
</div>
<Table>
<TableHeader>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
<TableRow>
{#each headerGroup.headers as header (header.id)}
<TableHead>
{#if !header.isPlaceholder}
<FlexRender content={header.column.columnDef.header} context={header.getContext()} />
{/if}
</TableHead>
{/each}
<TableHead></TableHead>
</TableRow>
{/each}
</TableHeader>
<TableBody>
{#each table.getRowModel().rows as groupRow (groupRow.id)}
{#if groupRow.getIsGrouped()}
{@const group = data.groups.find((g) => g.id == groupRow.getValue("group"))}
<TableRow class="font-bold">
<TableCell colspan={columns.length - 1}>
<Checkbox
checked={groupRow.getIsSelected()}
indeterminate={groupRow.getIsSomeSelected() && !groupRow.getIsSelected()}
onCheckedChange={() => groupRow.toggleSelected()}
class="mr-4"
/>
{group?.name ?? "Keine Gruppe"}
</TableCell>
<TableCell class="text-right">
<Button variant="ghost" size="icon" onclick={() => openGroupEditDialog(group!)}>
<EditIcon />
</Button>
<Button variant="ghost" size="icon" onclick={() => openGroupResultsDialog(group!)}>
<GroupIcon />
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button variant="ghost" size="icon">
<LinkIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
onclick={() => navigator.clipboard.writeText(`<group-table data-event="${data.event.id}"${group ? ` data-group="${group?.id}"` : ""}> </group-table>`)}
>Punkte Tabelle</DropdownMenuItem
>
<DropdownMenuItem
onclick={() => navigator.clipboard.writeText(`<fight-table data-event="${data.event.id}"${group ? ` data-group="${group?.id}"` : ""}> </group-table>`)}
>Kampf Tabelle</DropdownMenuItem
>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
{#each groupRow.subRows as row (row.id)}
<TableRow data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<TableCell>
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</TableCell>
{/each}
<TableCell class="text-right">
<FightEditRow fight={row.original} {data} onupdate={(update) => (data._fights = data._fights.map((v) => (v.id === update.id ? update : v)))} {refresh}></FightEditRow>
</TableCell>
</TableRow>
{/each}
{:else}
<TableRow data-state={groupRow.getIsSelected() && "selected"}>
{#each groupRow.getVisibleCells() as cell (cell.id)}
<TableCell>
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</TableCell>
{/each}
</TableRow>
{/if}
{:else}
<TableRow>
<TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell>
</TableRow>
{/each}
</TableBody>
</Table>