Refactor player handling: replace player arrays with IDs, implement PlayerSelector component
This commit is contained in:
122
src/components/ui/PlayerSelector.svelte
Normal file
122
src/components/ui/PlayerSelector.svelte
Normal file
@@ -0,0 +1,122 @@
|
||||
<script lang="ts">
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
|
||||
import { Button } from "@components/ui/button";
|
||||
import { Check, ChevronsUpDown } from "lucide-svelte";
|
||||
import { cn } from "@components/utils";
|
||||
import { dataRepo } from "@repo/data";
|
||||
import type { Player } from "@type/data";
|
||||
|
||||
let {
|
||||
value = $bindable(null),
|
||||
multiple = false,
|
||||
placeholder = "Select player...",
|
||||
onSelect,
|
||||
}: {
|
||||
value?: number | number[] | null;
|
||||
multiple?: boolean;
|
||||
placeholder?: string;
|
||||
onSelect?: (player: Player) => void;
|
||||
} = $props();
|
||||
|
||||
let open = $state(false);
|
||||
let search = $state("");
|
||||
let players: Player[] = $state([]);
|
||||
let loading = $state(false);
|
||||
|
||||
let debounceTimer: NodeJS.Timeout;
|
||||
|
||||
function fetchPlayers(searchTerm: string) {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(async () => {
|
||||
loading = true;
|
||||
try {
|
||||
const res = await $dataRepo.queryPlayers(searchTerm || undefined, undefined, undefined, 50, 0, false, true);
|
||||
players = res.entries;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
fetchPlayers(search);
|
||||
});
|
||||
|
||||
function handleSelect(player: Player) {
|
||||
if (onSelect) {
|
||||
onSelect(player);
|
||||
}
|
||||
|
||||
if (multiple) {
|
||||
if (Array.isArray(value)) {
|
||||
if (value.includes(player.id!)) {
|
||||
value = value.filter((v) => v !== player.id);
|
||||
} else {
|
||||
value = [...value, player.id!];
|
||||
}
|
||||
} else {
|
||||
value = [player.id!];
|
||||
}
|
||||
} else {
|
||||
if (value === player.id) {
|
||||
value = null; // Deselect
|
||||
} else {
|
||||
value = player.id;
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isSelected(id: number) {
|
||||
if (multiple) {
|
||||
return Array.isArray(value) && value.includes(id);
|
||||
}
|
||||
return value === id;
|
||||
}
|
||||
|
||||
let triggerLabel = $derived.by(() => {
|
||||
if (multiple) {
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
return `${placeholder} (${value.length})`;
|
||||
}
|
||||
return placeholder;
|
||||
} else {
|
||||
// We might need to fetch the selected player's name if it's not in the current list
|
||||
// For now, let's just show the placeholder or "Selected"
|
||||
// Ideally we would have a way to resolve the name from the UUID if it's not in `players`
|
||||
// But `players` only contains search results.
|
||||
// If we want to show the name, we might need to fetch it or pass it in.
|
||||
// Given the context of AuditLog, it shows "Spieler Filter (count)".
|
||||
// Given RefereesList, it's a button "Hinzufügen".
|
||||
return placeholder;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Popover bind:open>
|
||||
<PopoverTrigger>
|
||||
{#snippet child({ props })}
|
||||
<Button variant="outline" class={cn("justify-between", Array.isArray(value) && !value?.length && "text-muted-foreground")} {...props} role="combobox" aria-expanded={open}>
|
||||
{triggerLabel}
|
||||
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-0">
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput bind:value={search} placeholder="Search players..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>No players found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{#each players as player (player.uuid)}
|
||||
<CommandItem value={player.id?.toString()} onSelect={() => handleSelect(player)}>
|
||||
<Check class={cn("mr-2 size-4", isSelected(player.id!) ? "opacity-100" : "opacity-0")} />
|
||||
{player.name}
|
||||
</CommandItem>
|
||||
{/each}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
Reference in New Issue
Block a user