130 lines
4.7 KiB
Svelte
130 lines
4.7 KiB
Svelte
<!--
|
|
- This file is a part of the SteamWar software.
|
|
-
|
|
- Copyright (C) 2025 SteamWar.de-Serverteam
|
|
-
|
|
- This program is free software: you can redistribute it and/or modify
|
|
- it under the terms of the GNU Affero General Public License as published by
|
|
- the Free Software Foundation, either version 3 of the License, or
|
|
- (at your option) any later version.
|
|
-
|
|
- This program is distributed in the hope that it will be useful,
|
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
- GNU Affero General Public License for more details.
|
|
-
|
|
- You should have received a copy of the GNU Affero General Public License
|
|
- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
-->
|
|
|
|
<script lang="ts">
|
|
import { Calendar } from "@components/ui/calendar";
|
|
import { Button } from "@components/ui/button";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "$lib/components/ui/popover";
|
|
import { ScrollArea } from "$lib/components/ui/scroll-area";
|
|
import { CalendarIcon } from "lucide-svelte";
|
|
import { cn } from "@components/utils";
|
|
import type {ZonedDateTime} from "@internationalized/date";
|
|
|
|
let {
|
|
value = $bindable(),
|
|
onChange
|
|
}: {
|
|
value: ZonedDateTime
|
|
onChange?: ((date: ZonedDateTime | undefined) => void) | undefined
|
|
} = $props();
|
|
|
|
let isOpen = $state(false);
|
|
|
|
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
|
|
function handleDateSelect(selectedDate: ZonedDateTime) {
|
|
if (selectedDate) {
|
|
value = selectedDate;
|
|
if (onChange) {
|
|
onChange(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleTimeChange(type: "hour" | "minute", change: number) {
|
|
if (change !== undefined) {
|
|
if (type === "hour") {
|
|
value = value.set({ hour: change });
|
|
} else if (type === "minute") {
|
|
value = value.set({ minute: change });
|
|
}
|
|
if (onChange) {
|
|
onChange(value);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<Popover bind:open={isOpen}>
|
|
<PopoverTrigger>
|
|
<Button
|
|
variant="outline"
|
|
class={cn(
|
|
"w-full justify-start text-left font-normal",
|
|
!value && "text-muted-foreground"
|
|
)}
|
|
>
|
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
|
{#if value}
|
|
{new Intl.DateTimeFormat("de-DE", {
|
|
day: "2-digit",
|
|
month: "2-digit",
|
|
year: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
}).format(value.toDate())}
|
|
{:else}
|
|
<span>DD.MM.YYYY, hh:mm</span>
|
|
{/if}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
|
|
<PopoverContent class="w-auto p-0">
|
|
<div class="sm:flex">
|
|
<Calendar
|
|
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">
|
|
<ScrollArea class="w-64 sm:w-auto">
|
|
<div class="flex sm:flex-col p-2">
|
|
{#each [...hours].reverse() as hour}
|
|
<Button
|
|
size="icon"
|
|
variant={value && value.hour === hour ? "default" : "ghost"}
|
|
class="sm:w-full shrink-0 aspect-square"
|
|
onclick={() => handleTimeChange("hour", hour)}
|
|
>
|
|
{hour}
|
|
</Button>
|
|
{/each}
|
|
</div>
|
|
</ScrollArea>
|
|
|
|
<ScrollArea class="w-64 sm:w-auto">
|
|
<div class="flex sm:flex-col p-2">
|
|
{#each Array.from({ length: 60 }, (_, i) => i) as minute}
|
|
<Button
|
|
size="icon"
|
|
variant={value && value.minute === minute ? "default" : "ghost"}
|
|
class="sm:w-full shrink-0 aspect-square"
|
|
onclick={() => handleTimeChange("minute", minute)}
|
|
>
|
|
{minute.toString().padStart(2, '0')}
|
|
</Button>
|
|
{/each}
|
|
</div>
|
|
</ScrollArea>
|
|
</div>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|