314 lines
13 KiB
Svelte
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>
|