Add refresh functionality and duplicate fight feature in FightEditRow component

This commit is contained in:
2025-06-29 11:23:51 +02:00
parent e60cebc9a3
commit e5e3c15b07
6 changed files with 98 additions and 96 deletions

View File

@ -296,6 +296,7 @@
bind:groups={data.groups} bind:groups={data.groups}
event={data.event} event={data.event}
onupdate={(update) => (data.fights = data.fights.map((v) => (v.id === update.id ? update : v)))} onupdate={(update) => (data.fights = data.fights.map((v) => (v.id === update.id ? update : v)))}
{refresh}
></FightEditRow> ></FightEditRow>
</TableCell> </TableCell>
</TableRow> </TableRow>

View File

@ -1,15 +1,24 @@
<script lang="ts"> <script lang="ts">
import type { EventFight, EventFightEdit, ResponseGroups, SWEvent } from "@type/event"; import type { EventFight, EventFightEdit, ResponseGroups, SWEvent } from "@type/event";
import { Button } from "@components/ui/button"; import { Button } from "@components/ui/button";
import { EditIcon, MenuIcon, GroupIcon } from "lucide-svelte"; import { EditIcon, CopyIcon } from "lucide-svelte";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@components/ui/dialog"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@components/ui/dialog";
import FightEdit from "@components/moderator/components/FightEdit.svelte"; import FightEdit from "@components/moderator/components/FightEdit.svelte";
import type { Team } from "@components/types/team"; import type { Team } from "@components/types/team";
import { fightRepo } from "@components/repo/fight"; import { fightRepo } from "@components/repo/fight";
import { eventRepo } from "@components/repo/event";
let { fight, teams, groups = $bindable(), event, onupdate }: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void } = $props(); let {
fight,
teams,
groups = $bindable(),
event,
onupdate,
refresh,
}: { fight: EventFight; teams: Team[]; groups: ResponseGroups[]; event: SWEvent; onupdate: (update: EventFight) => void; refresh: () => void } = $props();
let editOpen = $state(false); let editOpen = $state(false);
let duplicateOpen = $state(false);
async function handleSave(fightData: EventFightEdit) { async function handleSave(fightData: EventFightEdit) {
let f = await $fightRepo.updateFight(event.id, fight.id, { let f = await $fightRepo.updateFight(event.id, fight.id, {
@ -23,6 +32,18 @@
editOpen = false; editOpen = false;
} }
async function handlyCopy(fightData: EventFightEdit) {
await $eventRepo.createFight(event.id.toString(), {
...fightData,
blueTeam: fightData.blueTeam.id,
redTeam: fightData.redTeam.id,
});
refresh();
duplicateOpen = false;
}
</script> </script>
<div> <div>
@ -46,4 +67,24 @@
</FightEdit> </FightEdit>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Dialog bind:open={duplicateOpen}>
<DialogTrigger>
<Button variant="ghost" size="icon">
<CopyIcon />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Fight duplizieren</DialogTitle>
<DialogDescription>Hier kannst du die Daten des duplizierten Fights ändern</DialogDescription>
</DialogHeader>
<FightEdit {fight} {teams} bind:groups {event} onSave={handlyCopy}>
{#snippet actions(dirty, submit)}
<DialogFooter>
<Button onclick={submit}>Speichern</Button>
</DialogFooter>
{/snippet}
</FightEdit>
</DialogContent>
</Dialog>
</div> </div>

View File

@ -73,7 +73,7 @@
<CommandEmpty>No Players found :(</CommandEmpty> <CommandEmpty>No Players found :(</CommandEmpty>
<CommandGroup heading="Players"> <CommandGroup heading="Players">
{#each $players {#each $players
.filter((v) => v.name.includes(playerSearch)) .filter((v) => v.name.toLowerCase().includes(playerSearch.toLowerCase()))
.filter((v, i) => i < 50) .filter((v, i) => i < 50)
.filter((v) => !referees.some((k) => k.uuid === v.uuid)) as player (player.uuid)} .filter((v) => !referees.some((k) => k.uuid === v.uuid)) as player (player.uuid)}
<CommandItem value={player.name} onSelect={() => addReferee(player.uuid)} keywords={[player.uuid]}>{player.name}</CommandItem> <CommandItem value={player.name} onSelect={() => addReferee(player.uuid)} keywords={[player.uuid]}>{player.name}</CommandItem>

View File

@ -24,14 +24,15 @@
import { ScrollArea } from "$lib/components/ui/scroll-area"; import { ScrollArea } from "$lib/components/ui/scroll-area";
import { CalendarIcon } from "lucide-svelte"; import { CalendarIcon } from "lucide-svelte";
import { cn } from "@components/utils"; import { cn } from "@components/utils";
import type {ZonedDateTime} from "@internationalized/date"; import { fromDate, type ZonedDateTime } from "@internationalized/date";
import Input from "../input/input.svelte";
let { let {
value = $bindable(), value = $bindable(),
onChange onChange,
}: { }: {
value: ZonedDateTime value: ZonedDateTime;
onChange?: ((date: ZonedDateTime | undefined) => void) | undefined onChange?: ((date: ZonedDateTime | undefined) => void) | undefined;
} = $props(); } = $props();
let isOpen = $state(false); let isOpen = $state(false);
@ -63,13 +64,7 @@
<Popover bind:open={isOpen}> <Popover bind:open={isOpen}>
<PopoverTrigger> <PopoverTrigger>
<Button <Button variant="outline" class={cn("w-full justify-start text-left font-normal", !value && "text-muted-foreground")}>
variant="outline"
class={cn(
"w-full justify-start text-left font-normal",
!value && "text-muted-foreground"
)}
>
<CalendarIcon class="mr-2 h-4 w-4" /> <CalendarIcon class="mr-2 h-4 w-4" />
{#if value} {#if value}
{new Intl.DateTimeFormat("de-DE", { {new Intl.DateTimeFormat("de-DE", {
@ -86,23 +81,14 @@
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="w-auto p-0"> <PopoverContent class="w-auto p-0">
<Input type="datetime-local" value={value.toDate().toISOString().slice(0, 16)} onchange={(e) => handleDateSelect(fromDate(e.target.valueAsDate, "Europe/Berlin"))} />
<div class="sm:flex"> <div class="sm:flex">
<Calendar <Calendar mode="single" bind:value onValueChange={(date) => handleDateSelect(date)} initialFocus />
mode="single"
bind:value
onValueChange={(date) => handleDateSelect(date)}
initialFocus
/>
<div class="flex flex-col sm:flex-row sm:h-[300px] divide-y sm:divide-y-0 sm:divide-x"> <div class="flex flex-col sm:flex-row sm:h-[300px] divide-y sm:divide-y-0 sm:divide-x">
<ScrollArea class="w-64 sm:w-auto"> <ScrollArea class="w-64 sm:w-auto">
<div class="flex sm:flex-col p-2"> <div class="flex sm:flex-col p-2">
{#each [...hours].reverse() as hour} {#each [...hours].reverse() as hour}
<Button <Button size="icon" variant={value && value.hour === hour ? "default" : "ghost"} class="sm:w-full shrink-0 aspect-square" onclick={() => handleTimeChange("hour", hour)}>
size="icon"
variant={value && value.hour === hour ? "default" : "ghost"}
class="sm:w-full shrink-0 aspect-square"
onclick={() => handleTimeChange("hour", hour)}
>
{hour} {hour}
</Button> </Button>
{/each} {/each}
@ -118,7 +104,7 @@
class="sm:w-full shrink-0 aspect-square" class="sm:w-full shrink-0 aspect-square"
onclick={() => handleTimeChange("minute", minute)} onclick={() => handleTimeChange("minute", minute)}
> >
{minute.toString().padStart(2, '0')} {minute.toString().padStart(2, "0")}
</Button> </Button>
{/each} {/each}
</div> </div>

View File

@ -1,27 +1,4 @@
import Root from "./input.svelte"; import Root from "./input.svelte";
export type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
mousemove: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
wheel: FormInputEvent<WheelEvent>;
};
export { export {
Root, Root,
// //

View File

@ -1,42 +1,39 @@
<script lang="ts"> <script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements"; import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
import type { InputEvents } from "./index.js"; import { cn } from "@components/utils";
import { cn } from "$lib/components/utils.js"; import { type WithElementRef } from "bits-ui";
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
type $$Props = HTMLInputAttributes; type Props = WithElementRef<Omit<HTMLInputAttributes, "type"> & ({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })>;
type $$Events = InputEvents; let { ref = $bindable(null), value = $bindable(), type, files = $bindable(), class: className, ...restProps }: Props = $props();
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
// Workaround for https://github.com/sveltejs/svelte/issues/9305
// Fixed in Svelte 5, but not backported to 4.x.
export let readonly: $$Props["readonly"] = undefined;
</script> </script>
{#if type === "file"}
<input <input
bind:this={ref}
data-slot="input"
class={cn( class={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className className
)} )}
type="file"
bind:files
bind:value bind:value
{readonly} {...restProps}
on:blur
on:change
on:click
on:focus
on:focusin
on:focusout
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:mousemove
on:paste
on:input
on:wheel|passive
{...$$restProps}
/> />
{:else}
<input
bind:this={ref}
data-slot="input"
class={cn(
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{type}
bind:value
{...restProps}
/>
{/if}