Add refresh functionality and duplicate fight feature in FightEditRow component
This commit is contained in:
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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,
|
||||||
//
|
//
|
||||||
|
|||||||
@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user