feat: Refactor event management components and introduce EventModel for better state handling
All checks were successful
SteamWarCI Build successful
All checks were successful
SteamWarCI Build successful
This commit is contained in:
@ -18,19 +18,19 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {t} from "astro-i18n";
|
import { t } from "astro-i18n";
|
||||||
import type {Player} from "@type/data.ts";
|
import type { Player } from "@type/data.ts";
|
||||||
import {l} from "@utils/util.ts";
|
import { l } from "@utils/util.ts";
|
||||||
import Statistics from "./Statistics.svelte";
|
import Statistics from "./Statistics.svelte";
|
||||||
import {authV2Repo} from "@repo/authv2.ts";
|
import { authV2Repo } from "@repo/authv2.ts";
|
||||||
import Card from "@components/Card.svelte";
|
import Card from "@components/Card.svelte";
|
||||||
import {navigate} from "astro:transitions/client";
|
import { navigate } from "astro:transitions/client";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: Player;
|
user: Player;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { user }: Props = $props();
|
let { user }: Props = $props();
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
await $authV2Repo.logout();
|
await $authV2Repo.logout();
|
||||||
@ -43,19 +43,25 @@
|
|||||||
<Card>
|
<Card>
|
||||||
<figure>
|
<figure>
|
||||||
<figcaption class="text-center mb-4 text-2xl">{user.name}</figcaption>
|
<figcaption class="text-center mb-4 text-2xl">{user.name}</figcaption>
|
||||||
<img src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${user.uuid}`} class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl" alt={user.name + "s bust"} width="150" height="150" />
|
<img
|
||||||
|
src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${user.uuid}`}
|
||||||
|
class="transition duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl"
|
||||||
|
alt={user.name + "s bust"}
|
||||||
|
width="150"
|
||||||
|
height="150"
|
||||||
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
</Card>
|
</Card>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
<button class="btn mt-2" onclick={logout}>{t("dashboard.buttons.logout")}</button>
|
<button class="btn mt-2" onclick={logout}>{t("dashboard.buttons.logout")}</button>
|
||||||
{#if user.perms.includes("MODERATION")}
|
{#if user.perms.includes("MODERATION")}
|
||||||
<a class="btn w-fit mt-2" href="/admin" data-astro-reload>{t("dashboard.buttons.admin")}</a>
|
<a class="btn w-fit mt-2" href="/admin/new" data-astro-reload>{t("dashboard.buttons.admin")}</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-4xl font-bold">{t("dashboard.title", {name: user.name})}</h1>
|
<h1 class="text-4xl font-bold">{t("dashboard.title", { name: user.name })}</h1>
|
||||||
<p>{t("dashboard.rank", {rank: t("home.prefix." + (user.prefix || "User"))})}</p>
|
<p>{t("dashboard.rank", { rank: t("home.prefix." + (user.prefix || "User")) })}</p>
|
||||||
<Statistics {user} />
|
<Statistics {user} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,23 +1,22 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { EventFight, EventFightEdit, ResponseGroups, UpdateEventGroup, GroupUpdateEdit, SWEvent } from "@type/event";
|
import GroupSelector from "./GroupSelector.svelte";
|
||||||
import { fromAbsolute, now, ZonedDateTime } from "@internationalized/date";
|
|
||||||
|
import type { EventFight, EventFightEdit, ResponseGroups, SWEvent } from "@type/event";
|
||||||
|
import { fromAbsolute } from "@internationalized/date";
|
||||||
import { Label } from "@components/ui/label";
|
import { Label } from "@components/ui/label";
|
||||||
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
|
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
||||||
import { gamemodes, maps } from "@components/stores/stores";
|
import { gamemodes, maps } from "@components/stores/stores";
|
||||||
import { CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Command } from "@components/ui/command";
|
import { CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Command } from "@components/ui/command";
|
||||||
import { ChevronsUpDown, Check, ChevronsUpDownIcon, PlusIcon, CheckIcon, MinusIcon } from "lucide-svelte";
|
import { ChevronsUpDown, Check } from "lucide-svelte";
|
||||||
import { Button } from "@components/ui/button";
|
import { Button } from "@components/ui/button";
|
||||||
import { cn } from "@components/utils";
|
import { cn } from "@components/utils";
|
||||||
import type { Team } from "@components/types/team";
|
import type { Team } from "@components/types/team";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger } from "@components/ui/select";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import { Input } from "@components/ui/input";
|
import { Input } from "@components/ui/input";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
|
||||||
import { eventRepo } from "@components/repo/event";
|
|
||||||
import GroupEdit from "./GroupEdit.svelte";
|
|
||||||
|
|
||||||
const {
|
let {
|
||||||
fight,
|
fight,
|
||||||
teams,
|
teams,
|
||||||
event,
|
event,
|
||||||
@ -83,14 +82,6 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleGroupSave(group: GroupUpdateEdit) {
|
|
||||||
let g = await $eventRepo.createGroup(event.id.toString(), group);
|
|
||||||
groups.push(g);
|
|
||||||
fightGroup = g.id;
|
|
||||||
createOpen = false;
|
|
||||||
groupSelectOpen = false;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@ -175,19 +166,34 @@
|
|||||||
<CommandInput placeholder="Search Teams..." />
|
<CommandInput placeholder="Search Teams..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No team found.</CommandEmpty>
|
<CommandEmpty>No team found.</CommandEmpty>
|
||||||
<CommandItem
|
<CommandGroup>
|
||||||
value={"-1"}
|
<CommandItem
|
||||||
onSelect={() => {
|
value={"-1"}
|
||||||
fightBlueTeam = {
|
onSelect={() => {
|
||||||
id: -1,
|
fightBlueTeam = {
|
||||||
name: "?",
|
id: -1,
|
||||||
color: "7",
|
name: "?",
|
||||||
kuerzel: "?",
|
color: "7",
|
||||||
};
|
kuerzel: "?",
|
||||||
blueTeamSelectOpen = false;
|
};
|
||||||
}}
|
blueTeamSelectOpen = false;
|
||||||
keywords={["?"]}>???</CommandItem
|
}}
|
||||||
>
|
keywords={["?"]}>???</CommandItem
|
||||||
|
>
|
||||||
|
<CommandItem
|
||||||
|
value={"0"}
|
||||||
|
onSelect={() => {
|
||||||
|
fightBlueTeam = {
|
||||||
|
id: 0,
|
||||||
|
name: "Public",
|
||||||
|
color: "7",
|
||||||
|
kuerzel: "PUB",
|
||||||
|
};
|
||||||
|
blueTeamSelectOpen = false;
|
||||||
|
}}
|
||||||
|
keywords={["PUB", "Public"]}>PUB</CommandItem
|
||||||
|
>
|
||||||
|
</CommandGroup>
|
||||||
<CommandGroup heading="Teams">
|
<CommandGroup heading="Teams">
|
||||||
{#each teams as team}
|
{#each teams as team}
|
||||||
<CommandItem
|
<CommandItem
|
||||||
@ -197,7 +203,7 @@
|
|||||||
blueTeamSelectOpen = false;
|
blueTeamSelectOpen = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Check class={cn("mr-2 size-4", team !== fightBlueTeam && "text-transparent")} />
|
<Check class={cn("mr-2 size-4", team.id !== fightBlueTeam?.id && "text-transparent")} />
|
||||||
{team.name}
|
{team.name}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
{/each}
|
{/each}
|
||||||
@ -221,19 +227,34 @@
|
|||||||
<CommandInput placeholder="Search Teams..." />
|
<CommandInput placeholder="Search Teams..." />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No team found.</CommandEmpty>
|
<CommandEmpty>No team found.</CommandEmpty>
|
||||||
<CommandItem
|
<CommandGroup>
|
||||||
value={"-1"}
|
<CommandItem
|
||||||
onSelect={() => {
|
value={"-1"}
|
||||||
fightRedTeam = {
|
onSelect={() => {
|
||||||
id: -1,
|
fightRedTeam = {
|
||||||
name: "?",
|
id: -1,
|
||||||
color: "7",
|
name: "?",
|
||||||
kuerzel: "?",
|
color: "7",
|
||||||
};
|
kuerzel: "?",
|
||||||
redTeamSelectOpen = false;
|
};
|
||||||
}}
|
redTeamSelectOpen = false;
|
||||||
keywords={["?"]}>???</CommandItem
|
}}
|
||||||
>
|
keywords={["?"]}>???</CommandItem
|
||||||
|
>
|
||||||
|
<CommandItem
|
||||||
|
value={"0"}
|
||||||
|
onSelect={() => {
|
||||||
|
fightRedTeam = {
|
||||||
|
id: 0,
|
||||||
|
name: "Public",
|
||||||
|
color: "7",
|
||||||
|
kuerzel: "PUB",
|
||||||
|
};
|
||||||
|
redTeamSelectOpen = false;
|
||||||
|
}}
|
||||||
|
keywords={["PUB", "Public"]}>PUB</CommandItem
|
||||||
|
>
|
||||||
|
</CommandGroup>
|
||||||
<CommandGroup heading="Teams">
|
<CommandGroup heading="Teams">
|
||||||
{#each teams as team}
|
{#each teams as team}
|
||||||
<CommandItem
|
<CommandItem
|
||||||
@ -243,7 +264,7 @@
|
|||||||
redTeamSelectOpen = false;
|
redTeamSelectOpen = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Check class={cn("mr-2 size-4", team !== fightRedTeam && "text-transparent")} />
|
<Check class={cn("mr-2 size-4", team.id !== fightRedTeam?.id && "text-transparent")} />
|
||||||
{team.name}
|
{team.name}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
{/each}
|
{/each}
|
||||||
@ -269,74 +290,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Label for="fight-group">Gruppe</Label>
|
<Label for="fight-group">Gruppe</Label>
|
||||||
<Dialog bind:open={createOpen}>
|
<GroupSelector {event} bind:value={fightGroup} bind:groups></GroupSelector>
|
||||||
<Popover bind:open={groupSelectOpen}>
|
|
||||||
<PopoverTrigger>
|
|
||||||
{#snippet child({ props })}
|
|
||||||
<Button id="fight-group" variant="outline" class="justify-between" {...props} role="combobox">
|
|
||||||
{selectedGroup?.name || "Keine Gruppe"}
|
|
||||||
<ChevronsUpDownIcon class="ml-2 size-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
{/snippet}
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent class="p-0">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="Gruppe suchen..." />
|
|
||||||
<CommandList>
|
|
||||||
<CommandGroup>
|
|
||||||
<CommandItem value={"new"} onSelect={() => (createOpen = true)}>
|
|
||||||
<PlusIcon class={"mr-2 size-4"} />
|
|
||||||
Neue Gruppe
|
|
||||||
</CommandItem>
|
|
||||||
|
|
||||||
<CommandGroup heading="Gruppen">
|
|
||||||
<CommandItem
|
|
||||||
value={"none"}
|
|
||||||
onSelect={() => {
|
|
||||||
fightGroup = null;
|
|
||||||
groupSelectOpen = false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{#if fightGroup === null}
|
|
||||||
<CheckIcon class={"mr-2 size-4"} />
|
|
||||||
{:else}
|
|
||||||
<MinusIcon class={"mr-2 size-4"} />
|
|
||||||
{/if}
|
|
||||||
Keine Gruppe
|
|
||||||
</CommandItem>
|
|
||||||
|
|
||||||
{#each groups as group}
|
|
||||||
<CommandItem
|
|
||||||
value={group.id.toString()}
|
|
||||||
onSelect={() => {
|
|
||||||
fightGroup = group.id;
|
|
||||||
groupSelectOpen = false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CheckIcon class={cn("mr-2 size-4", fightGroup !== group.id && "text-transparent")} />
|
|
||||||
{group.name}
|
|
||||||
</CommandItem>
|
|
||||||
{/each}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Neue Gruppe erstellen</DialogTitle>
|
|
||||||
<DialogDescription>Hier kannst du eine neue Gruppe erstellen</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<GroupEdit group={null} onSave={handleGroupSave}>
|
|
||||||
{#snippet actions(dirty, submit)}
|
|
||||||
<DialogFooter>
|
|
||||||
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
{/snippet}
|
|
||||||
</GroupEdit>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
<Label for="spectate-port">Spectate Port</Label>
|
<Label for="spectate-port">Spectate Port</Label>
|
||||||
<Input id="spectate-port" bind:value={fightSpectatePort} type="number" placeholder="2001" />
|
<Input id="spectate-port" bind:value={fightSpectatePort} type="number" placeholder="2001" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
103
src/components/moderator/components/GroupSelector.svelte
Normal file
103
src/components/moderator/components/GroupSelector.svelte
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { GroupUpdateEdit, ResponseGroups, SWEvent } from "@type/event";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
||||||
|
import { CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Command } from "@components/ui/command";
|
||||||
|
import { ChevronsUpDownIcon, PlusIcon, CheckIcon, MinusIcon } from "lucide-svelte";
|
||||||
|
import { Button } from "@components/ui/button";
|
||||||
|
import { cn } from "@components/utils";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@components/ui/dialog";
|
||||||
|
import GroupEdit from "./GroupEdit.svelte";
|
||||||
|
import { eventRepo } from "@components/repo/event";
|
||||||
|
|
||||||
|
let {
|
||||||
|
event,
|
||||||
|
groups = $bindable(),
|
||||||
|
value = $bindable(),
|
||||||
|
}: {
|
||||||
|
event: SWEvent;
|
||||||
|
groups: ResponseGroups[];
|
||||||
|
value: number | null;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let selectedGroup = $derived(groups.find((group) => group.id === value));
|
||||||
|
|
||||||
|
let createOpen = $state(false);
|
||||||
|
let groupSelectOpen = $state(false);
|
||||||
|
|
||||||
|
async function handleGroupSave(group: GroupUpdateEdit) {
|
||||||
|
let g = await $eventRepo.createGroup(event.id.toString(), group);
|
||||||
|
groups.push(g);
|
||||||
|
value = g.id;
|
||||||
|
createOpen = false;
|
||||||
|
groupSelectOpen = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog bind:open={createOpen}>
|
||||||
|
<Popover bind:open={groupSelectOpen}>
|
||||||
|
<PopoverTrigger>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<Button id="fight-group" variant="outline" class="justify-between" {...props} role="combobox">
|
||||||
|
{selectedGroup?.name || "Keine Gruppe"}
|
||||||
|
<ChevronsUpDownIcon class="ml-2 size-4 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent class="p-0">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Gruppe suchen..." />
|
||||||
|
<CommandList>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem value={"new"} onSelect={() => (createOpen = true)}>
|
||||||
|
<PlusIcon class={"mr-2 size-4"} />
|
||||||
|
Neue Gruppe
|
||||||
|
</CommandItem>
|
||||||
|
|
||||||
|
<CommandGroup heading="Gruppen">
|
||||||
|
<CommandItem
|
||||||
|
value={"none"}
|
||||||
|
onSelect={() => {
|
||||||
|
value = null;
|
||||||
|
groupSelectOpen = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if value === null}
|
||||||
|
<CheckIcon class={"mr-2 size-4"} />
|
||||||
|
{:else}
|
||||||
|
<MinusIcon class={"mr-2 size-4"} />
|
||||||
|
{/if}
|
||||||
|
Keine Gruppe
|
||||||
|
</CommandItem>
|
||||||
|
|
||||||
|
{#each groups as group}
|
||||||
|
<CommandItem
|
||||||
|
value={group.id.toString()}
|
||||||
|
onSelect={() => {
|
||||||
|
value = group.id;
|
||||||
|
groupSelectOpen = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckIcon class={cn("mr-2 size-4", value !== group.id && "text-transparent")} />
|
||||||
|
{group.name}
|
||||||
|
</CommandItem>
|
||||||
|
{/each}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Neue Gruppe erstellen</DialogTitle>
|
||||||
|
<DialogDescription>Hier kannst du eine neue Gruppe erstellen</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<GroupEdit group={null} onSave={handleGroupSave}>
|
||||||
|
{#snippet actions(dirty, submit)}
|
||||||
|
<DialogFooter>
|
||||||
|
<Button disabled={!dirty} onclick={submit}>Speichern</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
{/snippet}
|
||||||
|
</GroupEdit>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
@ -18,8 +18,11 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {eventRepo} from "@repo/event.ts";
|
import { eventRepo } from "@repo/event.ts";
|
||||||
import EventView from "@components/moderator/pages/event/EventView.svelte";
|
import EventView from "@components/moderator/pages/event/EventView.svelte";
|
||||||
|
import type { ExtendedEvent } from "@components/types/event";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { EventModel } from "./eventmodel.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
params: { id: number };
|
params: { id: number };
|
||||||
@ -28,11 +31,21 @@
|
|||||||
let { params }: Props = $props();
|
let { params }: Props = $props();
|
||||||
|
|
||||||
let id = params.id;
|
let id = params.id;
|
||||||
let event = $eventRepo.getEvent(id.toString());
|
let data: EventModel | undefined = $state(undefined);
|
||||||
|
let loaded = $state(false);
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
data = new EventModel(await $eventRepo.getEvent(id.toString()));
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#await event}
|
{#if loaded}
|
||||||
|
<EventView bind:event={data!!} {refresh} />
|
||||||
|
{:else}
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
{:then data}
|
{/if}
|
||||||
<EventView event={data} />
|
|
||||||
{/await}
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FightEditRow from "./FightEditRow.svelte";
|
import FightEditRow from "./FightEditRow.svelte";
|
||||||
|
|
||||||
import type { EventFightEdit, ExtendedEvent } from "@type/event";
|
import type { EventFight, 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";
|
||||||
@ -36,11 +36,12 @@
|
|||||||
import type { ResponseGroups } from "@type/event";
|
import type { ResponseGroups } from "@type/event";
|
||||||
import { EditIcon, GroupIcon, LinkIcon } from "lucide-svelte";
|
import { EditIcon, GroupIcon, LinkIcon } from "lucide-svelte";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@components/ui/dropdown-menu";
|
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 { Team } from "@components/types/team";
|
||||||
|
import type { EventModel } from "./eventmodel.svelte";
|
||||||
|
|
||||||
let { data = $bindable() }: { data: ExtendedEvent } = $props();
|
let { data = $bindable(), refresh }: { data: EventModel; refresh: () => void } = $props();
|
||||||
|
|
||||||
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>([]);
|
||||||
@ -48,7 +49,7 @@
|
|||||||
|
|
||||||
const table = createSvelteTable({
|
const table = createSvelteTable({
|
||||||
get data() {
|
get data() {
|
||||||
return fights;
|
return data.fights;
|
||||||
},
|
},
|
||||||
initialState: {
|
initialState: {
|
||||||
columnOrder: ["auswahl", "begegnung", "group"],
|
columnOrder: ["auswahl", "begegnung", "group"],
|
||||||
@ -103,6 +104,9 @@
|
|||||||
let groupResultsOpen = $state(false);
|
let groupResultsOpen = $state(false);
|
||||||
let selectedGroupForResults: ResponseGroups | null = $state(null);
|
let selectedGroupForResults: ResponseGroups | null = $state(null);
|
||||||
|
|
||||||
|
let groupChangeOpen = $state(false);
|
||||||
|
let groupChangeSelected: number | 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(), {
|
||||||
...fight,
|
...fight,
|
||||||
@ -110,7 +114,7 @@
|
|||||||
redTeam: fight.redTeam.id,
|
redTeam: fight.redTeam.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
reload();
|
refresh();
|
||||||
createOpen = false;
|
createOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,10 +127,6 @@
|
|||||||
selectedGroupForResults = group;
|
selectedGroupForResults = group;
|
||||||
groupResultsOpen = true;
|
groupResultsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reload() {
|
|
||||||
fights = await $eventRepo.listFights(data.event.id.toString());
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dialog bind:open={createOpen}>
|
<Dialog bind:open={createOpen}>
|
||||||
@ -146,19 +146,53 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{#if selectedGroup}
|
{#if selectedGroup}
|
||||||
<GroupEditDialog bind:open={editGroupOpen} group={selectedGroup} event={data.event} bind:groups />
|
<GroupEditDialog bind:open={editGroupOpen} group={selectedGroup} event={data.event} bind:groups={data.groups} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if selectedGroupForResults}
|
{#if selectedGroupForResults}
|
||||||
<GroupResultsDialog bind:open={groupResultsOpen} group={selectedGroupForResults} teams={data.teams} {fights} />
|
<GroupResultsDialog bind:open={groupResultsOpen} group={selectedGroupForResults} teams={data.teams} fights={data.fights} />
|
||||||
{/if}
|
{/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">
|
<div class="flex items-center justify-between">
|
||||||
<Menubar>
|
<Menubar>
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
|
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
|
||||||
<MenubarContent>
|
<MenubarContent>
|
||||||
<MenubarItem disabled>Gruppe Ändern</MenubarItem>
|
<MenubarItem onclick={() => (groupChangeOpen = true)}>Gruppe Ändern</MenubarItem>
|
||||||
<MenubarItem disabled>Startzeit Verschieben</MenubarItem>
|
<MenubarItem disabled>Startzeit Verschieben</MenubarItem>
|
||||||
<MenubarItem disabled>Spectate Port Ändern</MenubarItem>
|
<MenubarItem disabled>Spectate Port Ändern</MenubarItem>
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
@ -175,9 +209,9 @@
|
|||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger disabled={!groups.length}>Gruppen</MenubarTrigger>
|
<MenubarTrigger disabled={!data.groups.length}>Gruppen</MenubarTrigger>
|
||||||
<MenubarContent>
|
<MenubarContent>
|
||||||
{#each groups as group (group.id)}
|
{#each data.groups as group (group.id)}
|
||||||
<MenubarSub>
|
<MenubarSub>
|
||||||
<MenubarSubTrigger>
|
<MenubarSubTrigger>
|
||||||
{group.name}
|
{group.name}
|
||||||
@ -191,7 +225,7 @@
|
|||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
</Menubar>
|
</Menubar>
|
||||||
<Button variant="outline" class="ml-4" onclick={reload}>Neu laden</Button>
|
<Button variant="outline" class="ml-4" onclick={refresh}>Neu laden</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table>
|
<Table>
|
||||||
@ -205,13 +239,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
{/each}
|
{/each}
|
||||||
|
<TableHead></TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{/each}
|
{/each}
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<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"))}
|
{@const group = data.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
|
||||||
@ -256,7 +291,12 @@
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
{/each}
|
{/each}
|
||||||
<TableCell class="text-right">
|
<TableCell class="text-right">
|
||||||
<FightEditRow fight={row.original} teams={data.teams} bind:groups event={data.event} onupdate={(update) => (fights = fights.map((v) => (v.id === update.id ? update : v)))}
|
<FightEditRow
|
||||||
|
fight={row.original}
|
||||||
|
teams={data.teams}
|
||||||
|
bind:groups={data.groups}
|
||||||
|
event={data.event}
|
||||||
|
onupdate={(update) => (data.fights = data.fights.map((v) => (v.id === update.id ? update : v)))}
|
||||||
></FightEditRow>
|
></FightEditRow>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@ -18,13 +18,13 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ExtendedEvent } from "@type/event.ts";
|
|
||||||
import EventEdit from "@components/moderator/pages/event/EventEdit.svelte";
|
import EventEdit from "@components/moderator/pages/event/EventEdit.svelte";
|
||||||
import EventFightList from "@components/moderator/pages/event/EventFightList.svelte";
|
import EventFightList from "@components/moderator/pages/event/EventFightList.svelte";
|
||||||
import RefereesList from "@components/moderator/pages/event/RefereesList.svelte";
|
import RefereesList from "@components/moderator/pages/event/RefereesList.svelte";
|
||||||
import TeamTable from "@components/moderator/pages/event/TeamTable.svelte";
|
import TeamTable from "@components/moderator/pages/event/TeamTable.svelte";
|
||||||
|
import type { EventModel } from "./eventmodel.svelte";
|
||||||
|
|
||||||
let { event = $bindable() }: { event: ExtendedEvent } = $props();
|
let { event = $bindable(), refresh }: { event: EventModel; refresh: () => void } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col m-4 p-4 rounded-md border gap-4">
|
<div class="flex flex-col m-4 p-4 rounded-md border gap-4">
|
||||||
@ -42,5 +42,5 @@
|
|||||||
<RefereesList {event} />
|
<RefereesList {event} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EventFightList bind:data={event} />
|
<EventFightList bind:data={event} {refresh} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -25,21 +25,19 @@
|
|||||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
|
||||||
import { teams } from "@components/stores/stores";
|
import { teams } from "@components/stores/stores";
|
||||||
|
import type { Team } from "@components/types/team";
|
||||||
|
import type { EventModel } from "./eventmodel.svelte";
|
||||||
|
|
||||||
const { event = $bindable() }: { event: ExtendedEvent } = $props();
|
let { event = $bindable() }: { event: EventModel } = $props();
|
||||||
|
|
||||||
let team = $state(event.teams);
|
|
||||||
|
|
||||||
async function addTeam(value: number) {
|
async function addTeam(value: number) {
|
||||||
await $eventRepo.updateTeams(event.event.id.toString(), [value]);
|
await $eventRepo.updateTeams(event.event.id.toString(), [value]);
|
||||||
team = await $eventRepo.listTeams(event.event.id.toString());
|
event.teams = await $eventRepo.listTeams(event.event.id.toString());
|
||||||
event.teams = team;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeTeam(value: number) {
|
async function removeTeam(value: number) {
|
||||||
await $eventRepo.deleteTeams(event.event.id.toString(), [value]);
|
await $eventRepo.deleteTeams(event.event.id.toString(), [value]);
|
||||||
team = await $eventRepo.listTeams(event.event.id.toString());
|
event.teams = await $eventRepo.listTeams(event.event.id.toString());
|
||||||
event.teams = team;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let teamSearch = $state("");
|
let teamSearch = $state("");
|
||||||
@ -54,7 +52,7 @@
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{#each team as t (t.id)}
|
{#each event.teams as t (t.id)}
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>{t.kuerzel}</TableCell>
|
<TableCell>{t.kuerzel}</TableCell>
|
||||||
<TableCell>{t.name}</TableCell>
|
<TableCell>{t.name}</TableCell>
|
||||||
@ -63,7 +61,7 @@
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{/each}
|
{/each}
|
||||||
{#if team.length === 0}
|
{#if event.teams.length === 0}
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell class="text-center col-span-3">No teams available</TableCell>
|
<TableCell class="text-center col-span-3">No teams available</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -83,7 +81,7 @@
|
|||||||
<CommandGroup heading="Teams">
|
<CommandGroup heading="Teams">
|
||||||
{#each $teams
|
{#each $teams
|
||||||
.filter((v) => v.name.includes(teamSearch))
|
.filter((v) => v.name.includes(teamSearch))
|
||||||
.filter((v) => !team.some((k) => k.id === v.id))
|
.filter((v) => !event.teams.some((k) => k.id === v.id))
|
||||||
.filter((v, i) => i < 50) as t (t.id)}
|
.filter((v, i) => i < 50) as t (t.id)}
|
||||||
<CommandItem value={t.id.toString()} onSelect={() => addTeam(t.id)} keywords={[t.name, t.kuerzel]}>{t.name}</CommandItem>
|
<CommandItem value={t.id.toString()} onSelect={() => addTeam(t.id)} keywords={[t.name, t.kuerzel]}>{t.name}</CommandItem>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
21
src/components/moderator/pages/event/eventmodel.svelte.ts
Normal file
21
src/components/moderator/pages/event/eventmodel.svelte.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import type { ResponseUser } from "@components/repo/event";
|
||||||
|
import type { EventFight, ExtendedEvent, ResponseGroups, ResponseRelation, SWEvent } from "@components/types/event";
|
||||||
|
import type { Team } from "@components/types/team";
|
||||||
|
|
||||||
|
export class EventModel {
|
||||||
|
public event: SWEvent = $state({} as SWEvent);
|
||||||
|
public teams: Array<Team> = $state([]);
|
||||||
|
public groups: Array<ResponseGroups> = $state([]);
|
||||||
|
public fights: Array<EventFight> = $state([]);
|
||||||
|
public referees: Array<ResponseUser> = $state([]);
|
||||||
|
public relations: Array<ResponseRelation> = $state([]);
|
||||||
|
|
||||||
|
constructor(data: ExtendedEvent) {
|
||||||
|
this.event = data.event;
|
||||||
|
this.teams = data.teams;
|
||||||
|
this.groups = data.groups;
|
||||||
|
this.fights = data.fights;
|
||||||
|
this.referees = data.referees;
|
||||||
|
this.relations = data.relations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,7 +38,9 @@ export class OpenEditPage {
|
|||||||
}
|
}
|
||||||
contentToSave += this.content;
|
contentToSave += this.content;
|
||||||
const encodedContent = btoa(new TextEncoder().encode(contentToSave).reduce((data, byte) => data + String.fromCharCode(byte), ""));
|
const encodedContent = btoa(new TextEncoder().encode(contentToSave).reduce((data, byte) => data + String.fromCharCode(byte), ""));
|
||||||
await get(pageRepo).updatePage(this.pageId, this.sha, encodedContent, this.manager.branch);
|
|
||||||
|
console.log(encodedContent);
|
||||||
|
//await get(pageRepo).updatePage(this.pageId, this.sha, encodedContent, this.manager.branch);
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
this.manager.reloadImages();
|
this.manager.reloadImages();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user