Refactor player handling: replace player arrays with IDs, implement PlayerSelector component

This commit is contained in:
2025-12-02 22:23:55 +01:00
parent 7ec678ae7d
commit 5f5988e270
14 changed files with 332 additions and 304 deletions

View File

@@ -20,7 +20,7 @@
<script lang="ts">
import {Input, Label, Select} from "flowbite-svelte";
import TypeAheadSearch from "./TypeAheadSearch.svelte";
import {gamemodes, groups, maps, players} from "@stores/stores.ts";
import {gamemodes, groups, maps} from "@stores/stores.ts";
import type {Team} from "@type/team.ts";
interface Props {

View File

@@ -22,17 +22,33 @@
import {Button, Card, Checkbox, Input, Label, Navbar, NavBrand, Radio, Spinner} from "flowbite-svelte";
import {ArrowLeftOutline} from "flowbite-svelte-icons";
import {players} from "@stores/stores.ts";
import {capitalize} from "../util.ts";
import {permsRepo} from "@repo/perms.ts";
import {me} from "@stores/me.ts";
import SWButton from "@components/styled/SWButton.svelte";
import SWModal from "@components/styled/SWModal.svelte";
import {userRepo} from "@repo/user.ts";
import {dataRepo} from "@repo/data.ts";
import type {Player} from "@type/data";
let search = $state("");
let playersList: Player[] = $state([]);
let debounceTimer: NodeJS.Timeout;
function fetchPlayers(searchTerm: string) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
const res = await $dataRepo.queryPlayers(searchTerm || undefined, undefined, undefined, 100, 0, undefined, undefined);
playersList = res.players;
}, 300);
}
$effect(() => {
fetchPlayers(search);
});
let selectedPlayer: string | null = $state(null);
let selectedPlayerName: string = $state("");
let playerPerms = $state(loadPlayer(selectedPlayer));
let prefixEdit = $state("PREFIX_NONE");
@@ -99,9 +115,7 @@
resetPasswordRepeat = "";
resetPasswordModal = false;
}
let lowerCaseSearch = $derived(search.toLowerCase());
let filteredPlayers = $derived($players.filter(value => value.name.toLowerCase().includes(lowerCaseSearch)));
let player = $derived($players.find(value => value.uuid === selectedPlayer));
run(() => {
playerPerms = loadPlayer(selectedPlayer);
});
@@ -126,12 +140,15 @@
<Label for="user_search" class="mb-2">Search Users...</Label>
<Input type="text" id="user_search" placeholder="Name..." bind:value={search}/>
</div>
{#if filteredPlayers.length < 100}
{#if playersList.length < 100}
<ul class="flex-1 overflow-scroll">
{#each filteredPlayers as player (player.uuid)}
{#each playersList as player (player.uuid)}
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
class:text-orange-500={player.uuid === selectedPlayer}
onclick={preventDefault(() => selectedPlayer = player.uuid)}>
onclick={preventDefault(() => {
selectedPlayer = player.uuid;
selectedPlayerName = player.name;
})}>
{player.name}
</li>
{/each}
@@ -140,7 +157,7 @@
</Card>
<Card class="!max-w-full" style="grid-column: 2/4">
{#if selectedPlayer}
<h1 class="text-3xl">{player.name}</h1>
<h1 class="text-3xl">{selectedPlayerName}</h1>
{#await permsFuture}
<Spinner></Spinner>
{:then perms}

View File

@@ -36,8 +36,9 @@
} from "flowbite-svelte-icons";
import FightCard from "./FightCard.svelte";
import CreateFightModal from "./modals/CreateFightModal.svelte";
import {groups, players} from "@stores/stores.ts";
import {groups} from "@stores/stores.ts";
import TypeAheadSearch from "../../components/TypeAheadSearch.svelte";
import PlayerSelector from "@components/ui/PlayerSelector.svelte";
import {fightRepo, type UpdateFight} from "@repo/fight.ts";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
@@ -97,12 +98,6 @@
}
let spectatePortOpen = $state(false);
let selectPlayers = $derived($players.map(player => {
return {
name: player.name,
value: player.uuid
};
}).sort((a, b) => a.name.localeCompare(b.name)));
let spectatePort = $state("");
async function updateSpectatePort() {
@@ -262,12 +257,12 @@
<TypeAheadSearch items={selectPlayers} bind:selected={spectatePort}></TypeAheadSearch>
</div>
{#snippet footer()}
<Button class="ml-auto" onclick={updateSpectatePort}>Change</Button>
<Button onclick={() => spectatePortOpen = false} color="alternative">Cancel</Button>
{/snippet}
</Modal>
<Modal bind:open={spectatePortOpen} title="Change Kampfleiter" size="sm">
<div class="m-2">
<Label for="fight-kampf">Kampfleiter</Label>
<PlayerSelector bind:value={spectatePort} placeholder="Search player..." />
</div>
{#snippet footer()}
<Modal bind:open={groupChangeOpen} title="Change Group" size="sm">
<div class="m-2">

View File

@@ -23,8 +23,7 @@
import {PlusOutline} from "flowbite-svelte-icons";
import SWModal from "@components/styled/SWModal.svelte";
import SWButton from "@components/styled/SWButton.svelte";
import TypeAheadSearch from "@components/admin/components/TypeAheadSearch.svelte";
import {players} from "@stores/stores.ts";
import PlayerSelector from "@components/ui/PlayerSelector.svelte";
import {eventRepo} from "@repo/event.ts";
interface Props {
@@ -102,8 +101,7 @@
<SWModal title="Schiedsrichter hinzufügen" bind:open={showAdd}>
<div class="flex flex-grow justify-center h-80">
<div>
<TypeAheadSearch bind:searchValue bind:selected={selectedPlayer}
items={$players.map(v => ({ name: v.name, value: v.uuid }))}/>
<PlayerSelector bind:value={selectedPlayer} placeholder="Search player..." />
</div>
</div>
{#snippet footer()}