diff --git a/project.inlang/messages/de.json b/project.inlang/messages/de.json index 5e4bcec..305fd6f 100644 --- a/project.inlang/messages/de.json +++ b/project.inlang/messages/de.json @@ -944,6 +944,18 @@ "title": "Kalender", "selectDate": "Datum auswählen", "today": "Heute", + "zoomOptions": { + "title": "Kalender Zoom-Optionen", + "zoomIn": "Hinein-zoomen", + "zoomOut": "Heraus-zoomen" + }, + "viewOptions": { + "title": "Kalender Ansicht", + "day": "Tagesansicht", + "week": "Wochenansicht", + "week-workdays": "Wochenansicht (ohne Wochenende)" + }, + "calendarWeek": "KW {week}, {year}", "thisMonth": "Aktueller Monat", "addThreeMonths": "+3", "addSixMonths": "+6", diff --git a/project.inlang/messages/en.json b/project.inlang/messages/en.json index 6e87e59..d418e13 100644 --- a/project.inlang/messages/en.json +++ b/project.inlang/messages/en.json @@ -952,6 +952,18 @@ "title": "Calendar", "selectDate": "Select a date", "today": "Today", + "zoomOptions": { + "title": "Calendar zoom options", + "zoomIn": "zoom-in", + "zoomOut": "zoom-out" + }, + "viewOptions": { + "title": "Calendar view options", + "day": "Day", + "week": "Week", + "week-workdays": "Week (without weekend)" + }, + "calendarWeek": "CW {week}, {year}", "thisMonth": "Current month", "addThreeMonths": "+3", "addSixMonths": "+6", diff --git a/src/lib/assets/custom-icons/calendar-week-workdays.svelte b/src/lib/assets/custom-icons/calendar-week-workdays.svelte new file mode 100644 index 0000000..be2956b --- /dev/null +++ b/src/lib/assets/custom-icons/calendar-week-workdays.svelte @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/src/lib/assets/custom-icons/calendar-week.svelte b/src/lib/assets/custom-icons/calendar-week.svelte new file mode 100644 index 0000000..d644f19 --- /dev/null +++ b/src/lib/assets/custom-icons/calendar-week.svelte @@ -0,0 +1,32 @@ + + + + calendar-week + + + + + + + + + + + + + + diff --git a/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte b/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte index f629082..637bc27 100644 --- a/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte +++ b/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte @@ -13,6 +13,7 @@ import { toDisplayDateTime } from "$lib/utils/datetime"; import { getCurrentTranlslation } from "$lib/utils/localizations"; import { getLocalTimeZone } from "@internationalized/date"; + import { useQueryClient } from "@tanstack/svelte-query"; import { onMount } from "svelte"; let { @@ -23,6 +24,7 @@ closePopover: () => void; } = $props(); + const queryClient = useQueryClient(); const channels = $derived($channelsStore.channels); let appointment: TAppointment | undefined = $state(); @@ -57,6 +59,9 @@ onclick={() => { if (appointment) { closePopover(); + queryClient.invalidateQueries({ + queryKey: ["calendar"], + }); if (page.url.pathname === ROUTES.DASHBOARD.CALENDAR) { goto(resolve(ROUTES.DASHBOARD.CALENDAR), { state: { diff --git a/src/lib/components/ui/calendar-custom/day.svelte b/src/lib/components/ui/calendar-custom/day.svelte index 49dbe82..249105d 100644 --- a/src/lib/components/ui/calendar-custom/day.svelte +++ b/src/lib/components/ui/calendar-custom/day.svelte @@ -19,17 +19,20 @@ - {#if !outsideMonth} - - - {day.day} - - {#if isDateHighlighted && isDateHighlighted(day)} - - {/if} + + + {day.day} - {/if} + {#if isDateHighlighted && isDateHighlighted(day)} + + {/if} + diff --git a/src/lib/utils/datetime.ts b/src/lib/utils/datetime.ts index 643fa98..462a1e2 100644 --- a/src/lib/utils/datetime.ts +++ b/src/lib/utils/datetime.ts @@ -1,6 +1,7 @@ import { getLocale } from "$i18n/runtime"; import type { TCalendarSlot } from "$lib/types/calendar"; import { + CalendarDate, getLocalTimeZone, parseAbsoluteToLocal, toCalendarDateTime, @@ -228,3 +229,22 @@ export const getWeekStartsOn = (): 0 | 1 | 2 | 3 | 4 | 5 | 6 => { if (sundayStartLocales.has(locale)) return 0; return 1; }; + +export const getWeekDays = (day: CalendarDate, skipWeekend = false): CalendarDate[] => { + const jsDay = day.toDate("UTC").getDay(); + const weekStartsOn = getWeekStartsOn(); + let offset = jsDay - weekStartsOn; + if (offset < 0) offset += 7; + + const weekStart = day.subtract({ days: offset }); + const days = Array.from({ length: 7 }, (_, i) => weekStart.add({ days: i })); + + if (skipWeekend) { + return days.filter((d) => { + const dow = d.toDate("UTC").getDay(); + return dow !== 0 && dow !== 6; + }); + } + + return days; +}; diff --git a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte index 41afaeb..1799c0f 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte @@ -8,11 +8,14 @@ import { calendarStore } from "$lib/stores/calendar"; import { Button } from "$lib/components/ui/button"; import { m } from "$i18n/messages"; + import { cn } from "$lib/utils"; let { item, + scale, }: { item: TCalendarItem; + scale: number; } = $props(); let decrypted = $state(); @@ -101,13 +104,16 @@ {:else if decrypted === undefined} - {m["calendar.decrypting"]()} + + {m["calendar.decrypting"]()} + {:else if decrypted} diff --git a/src/routes/(pages)/dashboard/calendar/(components)/CalendarDay.svelte b/src/routes/(pages)/dashboard/calendar/(components)/CalendarDay.svelte index 7ab8379..c50e0a1 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/CalendarDay.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/CalendarDay.svelte @@ -1,49 +1,115 @@ - - - - - - {#each shownHours as hour (`hour-${hour}`)} - - - {Intl.DateTimeFormat(getLocale(), { - hour: "2-digit", - minute: "2-digit", - timeZone: getLocalTimeZone().toString(), - }).format(toCalendarDateTime(day).set({ hour }).toDate(getLocalTimeZone()))} - - - - - {/each} - - - {#if items === undefined} - - - {m["calendar.loading"]()} - + {toDisplayDateTime(day.toDate(getLocalTimeZone()), { weekday: "short" })} + + {/if} - - - + {#each processedItems as item, index (`${item.id}-${index}`)} {@const top = (item.startMinutes / 60) * hourSize + focusAdjustment - earliestStartHour * hourSize} @@ -119,7 +170,7 @@ )} > {#if ["booked", "reserved"].includes(item.status)} - + {:else if item.status === "available"} {/if} @@ -127,26 +178,4 @@ {/each} - - - {#if curTimeIndicator && toCalendarDate($clock).toString() === today(getLocalTimeZone()).toString() && latestEndHour * hourSize + hourSize / 2 > curTimeIndicator.hour * hourSize && earliestStartHour * hourSize - hourSize * 2 < curTimeIndicator.hour * hourSize} - {@const top = - focusAdjustment + - curTimeIndicator.hour * hourSize + - (curTimeIndicator.minute / 60) * hourSize - - earliestStartHour * hourSize} - - - {Intl.DateTimeFormat(getLocale(), { - hour: "2-digit", - minute: "2-digit", - timeZone: getLocalTimeZone().toString(), - }).format(toCalendarDateTime($clock).toDate(getLocalTimeZone()))} - - - - {/if} diff --git a/src/routes/(pages)/dashboard/calendar/(components)/CalendarFilters.svelte b/src/routes/(pages)/dashboard/calendar/(components)/CalendarFilters.svelte index ede8489..9d3ddd0 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/CalendarFilters.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/CalendarFilters.svelte @@ -1,6 +1,8 @@ - {Intl.DateTimeFormat(getLocale(), { - year: "numeric", - month: "long", - day: "numeric", - weekday: "short", - timeZone: getLocalTimeZone().toString(), - }).format(selectedDate.toDate(getLocalTimeZone()))} + {#if isWeekView} + {@const week = getISOWeek(selectedDate)} + {m["calendar.calendarWeek"](week)} + {:else} + {Intl.DateTimeFormat(getLocale(), { + year: "numeric", + month: "long", + day: "numeric", + weekday: "short", + timeZone: getLocalTimeZone().toString(), + }).format(selectedDate.toDate(getLocalTimeZone()))} + {/if} + import { m } from "$i18n/messages"; + import { getLocale } from "$i18n/runtime"; + import { Separator } from "$lib/components/ui/separator"; + import { Text } from "$lib/components/ui/typography"; + import { clock } from "$lib/stores/time"; + import { + CalendarDate, + getLocalTimeZone, + toCalendarDate, + toCalendarDateTime, + today, + } from "@internationalized/date"; + import Loader from "@lucide/svelte/icons/loader-2"; + + let { + day = $bindable(), + earliestStartHour, + latestEndHour, + scale = $bindable(), + isLoading, + }: { + day: CalendarDate; + earliestStartHour: number; + latestEndHour: number; + scale: number; + isLoading: boolean; + } = $props(); + + const hourSize = $derived(60 * scale); + const focusAdjustment = $derived(30 * scale); + const curTimeIndicator = $derived( + today(getLocalTimeZone()).toString() === day.toString() ? $clock : undefined, + ); + + + + + + + + + {#if isLoading} + + + {m["calendar.loading"]()} + + {/if} + + + {#if !isLoading && curTimeIndicator} + {@const isToday = toCalendarDate($clock).toString() === today(getLocalTimeZone()).toString()} + {@const isNotAfterHours = + latestEndHour * hourSize + hourSize / 2 > curTimeIndicator.hour * hourSize} + {@const isNotBeforeHours = + earliestStartHour * hourSize - hourSize * 2 < curTimeIndicator.hour * hourSize} + {#if isToday && isNotAfterHours && isNotBeforeHours} + {@const top = + focusAdjustment + + curTimeIndicator.hour * hourSize + + (curTimeIndicator.minute / 60) * hourSize - + earliestStartHour * hourSize} + + + {Intl.DateTimeFormat(getLocale(), { + hour: "2-digit", + minute: "2-digit", + timeZone: getLocalTimeZone().toString(), + }).format(toCalendarDateTime($clock).toDate(getLocalTimeZone()))} + + + {/if} + {/if} + diff --git a/src/routes/(pages)/dashboard/calendar/(components)/CalendarLines.svelte b/src/routes/(pages)/dashboard/calendar/(components)/CalendarLines.svelte new file mode 100644 index 0000000..7a8875e --- /dev/null +++ b/src/routes/(pages)/dashboard/calendar/(components)/CalendarLines.svelte @@ -0,0 +1,90 @@ + + +{#if !isLoading} + + + + + + + {#if !isLoading && curTimeIndicator} + {@const isToday = toCalendarDate($clock).toString() === today(getLocalTimeZone()).toString()} + {@const isNotAfterHours = + latestEndHour * hourSize + hourSize / 2 > curTimeIndicator.hour * hourSize} + {@const isNotBeforeHours = + earliestStartHour * hourSize - hourSize * 2 < curTimeIndicator.hour * hourSize} + {#if isToday && isNotAfterHours && isNotBeforeHours} + {@const top = + focusAdjustment + + curTimeIndicator.hour * hourSize + + (curTimeIndicator.minute / 60) * hourSize - + earliestStartHour * hourSize} + + + + {/if} + {/if} + + + {#each shownHours as hour (`hour-${hour}`)} + + {#if !isWeekView} + + {Intl.DateTimeFormat(getLocale(), { + hour: "2-digit", + minute: "2-digit", + timeZone: getLocalTimeZone().toString(), + }).format(toCalendarDateTime(day).set({ hour }).toDate(getLocalTimeZone()))} + + {/if} + + + + {/each} + +{/if} diff --git a/src/routes/(pages)/dashboard/calendar/(components)/CalendarWeek.svelte b/src/routes/(pages)/dashboard/calendar/(components)/CalendarWeek.svelte new file mode 100644 index 0000000..662bdbd --- /dev/null +++ b/src/routes/(pages)/dashboard/calendar/(components)/CalendarWeek.svelte @@ -0,0 +1,64 @@ + + + +{#each weekDays as weekDay (weekDay.toString())} + +{/each} + diff --git a/src/routes/(pages)/dashboard/calendar/(components)/queries.ts b/src/routes/(pages)/dashboard/calendar/(components)/queries.ts index 73710da..493dd01 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/queries.ts +++ b/src/routes/(pages)/dashboard/calendar/(components)/queries.ts @@ -1,9 +1,10 @@ import type { CalendarDate } from "@internationalized/date"; import { fetchCalendar } from "./utils"; +import { browser } from "$app/env"; export const calendarMonthQuery = (tenant: string | undefined | null, date: CalendarDate) => ({ queryKey: ["calendar", `${date.year}-${date.month}`], queryFn: () => fetchCalendar({ tenant: tenant as string, selectedDate: date }), - enabled: Boolean(tenant), + enabled: Boolean(tenant) && browser, staleTime: 5000, }); diff --git a/src/routes/(pages)/dashboard/calendar/(components)/utils.ts b/src/routes/(pages)/dashboard/calendar/(components)/utils.ts index 00b24c6..fc35122 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/utils.ts +++ b/src/routes/(pages)/dashboard/calendar/(components)/utils.ts @@ -2,17 +2,28 @@ import { browser } from "$app/environment"; import { goto } from "$app/navigation"; import { resolve } from "$app/paths"; import { ROUTES } from "$lib/const/routes"; +import type { AppointmentWithKeyShare } from "$lib/server/services/schedule-service"; import { auth } from "$lib/stores/auth"; import { calendarStore } from "$lib/stores/calendar"; import { staffCrypto } from "$lib/stores/staff-crypto"; -import type { TCalendar, TCalendarItem } from "$lib/types/calendar"; -import { localToUTCWithoutDST } from "$lib/utils/datetime"; -import { getLocalTimeZone, type CalendarDate } from "@internationalized/date"; +import type { AppointmentStatus, TCalendar, TCalendarItem } from "$lib/types/calendar"; +import type { TChannel } from "$lib/types/channel"; +import { getWeekStartsOn, localToUTCWithoutDST } from "$lib/utils/datetime"; +import { + getLocalTimeZone, + parseAbsoluteToLocal, + toCalendarDate, + type CalendarDate, +} from "@internationalized/date"; import { get } from "svelte/store"; +export const CALENDAR_ZOOM_STEPS = [1, 2, 3, 4]; + export const fetchCalendar = async (opts: { tenant: string; selectedDate: CalendarDate }) => { if (!browser) return; + const weekStartsOn = getWeekStartsOn(); + const localStartDate = new Date( opts.selectedDate.year, opts.selectedDate.month - 1, @@ -22,6 +33,11 @@ export const fetchCalendar = async (opts: { tenant: string; selectedDate: Calend 0, 0, ); + // Roll back to the start of the week + while (localStartDate.getDay() !== weekStartsOn) { + localStartDate.setDate(localStartDate.getDate() - 1); + } + const localEndDate = new Date( opts.selectedDate.year, opts.selectedDate.month, @@ -31,6 +47,11 @@ export const fetchCalendar = async (opts: { tenant: string; selectedDate: Calend 59, 999, ); + // Roll forward to the end of the week + const weekEndDay = (weekStartsOn + 6) % 7; + while (localEndDate.getDay() !== weekEndDay) { + localEndDate.setDate(localEndDate.getDate() + 1); + } const params = new URLSearchParams({ startDate: localStartDate.toISOString(), @@ -249,29 +270,57 @@ export const confirmAppointment = async (opts: { }; export const openAppointmentById = async ( - appointments: TCalendarItem[], + calendar: TCalendar, + channels: TChannel[], appointmentId: string, callback: () => void, ) => { - const appointment = appointments?.find((i) => i.id === appointmentId); + const allAppointments: AppointmentWithKeyShare[] = calendar.calendar.reduce((all, item) => { + return [...all, ...Object.values(item.channels).flatMap((it) => it.appointments)]; + }, [] as AppointmentWithKeyShare[]); + const appointment = allAppointments?.find((i) => i.id === appointmentId); const staffCryptoStore = get(staffCrypto); if ( staffCryptoStore.crypto && - appointment?.appointment?.encryptedPayload && - appointment.appointment.iv && - appointment.appointment.authTag && - appointment.appointment.staffKeyShare + appointment?.encryptedPayload && + appointment.iv && + appointment.authTag && + appointment.staffKeyShare ) { const decrypted = await staffCryptoStore.crypto.decryptStaffAppointment({ encryptedAppointment: { - encryptedPayload: appointment.appointment.encryptedPayload, - iv: appointment.appointment.iv, - authTag: appointment.appointment.authTag, + encryptedPayload: appointment.encryptedPayload, + iv: appointment.iv, + authTag: appointment.authTag, }, - staffKeyShare: appointment.appointment.staffKeyShare, + staffKeyShare: appointment.staffKeyShare, }); - calendarStore.setCurItem({ appointment, decrypted }); + const channel = channels.find((it) => it.id === appointment.channelId); + const curCalendarItem: TCalendarItem = { + date: appointment.appointmentDate.toString(), + id: appointment.id, + duration: appointment.duration, + status: appointment.status as AppointmentStatus, + start: "08:00", + channelId: appointment.channelId, + color: channel?.color || "", + appointment: { + ...appointment, + encryptedPayload: appointment.encryptedPayload, + iv: appointment.iv, + authTag: appointment.authTag, + dateTime: new Date(appointment.appointmentDate), + }, + // TODO: get proper values + column: 0, + }; + calendarStore.setCurItem({ appointment: curCalendarItem, decrypted }); } callback(); }; + +export const convertDate = (dateStr: string) => { + const zonedDateTime = parseAbsoluteToLocal(dateStr); + return toCalendarDate(zonedDateTime); +}; diff --git a/src/routes/(pages)/dashboard/calendar/+page.server.ts b/src/routes/(pages)/dashboard/calendar/+page.server.ts new file mode 100644 index 0000000..be89fdc --- /dev/null +++ b/src/routes/(pages)/dashboard/calendar/+page.server.ts @@ -0,0 +1,19 @@ +import { CALENDAR_ZOOM_STEPS } from "./(components)/utils.js"; +import type { CalendarView } from "./types.js"; + +export const load = ({ cookies }) => { + // zoom + const cookieZoom = cookies.get("calendarZoom"); + const parsedCookieZoom = cookieZoom && parseInt(cookieZoom); + const calendarZoom = + parsedCookieZoom && CALENDAR_ZOOM_STEPS.includes(parsedCookieZoom) ? parsedCookieZoom : 1; + + // view + const cookieView = cookies.get("calendarView"); + const calendarView: CalendarView = + cookieView && ["day", "week", "week-workdays"].includes(cookieView) + ? (cookieView as CalendarView) + : "day"; + + return { calendarView, calendarZoom }; +}; diff --git a/src/routes/(pages)/dashboard/calendar/+page.svelte b/src/routes/(pages)/dashboard/calendar/+page.svelte index 82c00cc..ac23845 100644 --- a/src/routes/(pages)/dashboard/calendar/+page.svelte +++ b/src/routes/(pages)/dashboard/calendar/+page.svelte @@ -2,7 +2,6 @@ import { replaceState } from "$app/navigation"; import { page } from "$app/state"; import { m } from "$i18n/messages"; - import { MaxPageWidth } from "$lib/components/layouts/max-page-width"; import { SidebarLayout } from "$lib/components/layouts/sidebar-layout"; import { Button } from "$lib/components/ui/button"; import { ResponsiveDialog } from "$lib/components/ui/responsive-dialog"; @@ -12,31 +11,24 @@ import { calendarStore } from "$lib/stores/calendar"; import { channels as channelsStore } from "$lib/stores/channels"; import { sidebar } from "$lib/stores/sidebar"; - import type { TAppointmentFilter, TCalendarItem } from "$lib/types/calendar"; - import { timeUTCToLocalWithoutOffset, utcToLocalWithoutDST } from "$lib/utils/datetime"; + import type { TAppointmentFilter } from "$lib/types/calendar"; + import { timeUTCToLocalWithoutOffset } from "$lib/utils/datetime"; import { getCurrentTranlslation } from "$lib/utils/localizations"; - import { - getLocalTimeZone, - parseAbsoluteToLocal, - toCalendarDate, - today, - type CalendarDate, - } from "@internationalized/date"; + import { getLocalTimeZone, today, type CalendarDate } from "@internationalized/date"; import { SlidersHorizontal } from "@lucide/svelte"; + import { createQuery, useQueryClient } from "@tanstack/svelte-query"; import { onMount } from "svelte"; import AddAppointment from "./(components)/add-appointment/AddAppointment.svelte"; import Appointment from "./(components)/Appointment.svelte"; import CalendarDay from "./(components)/CalendarDay.svelte"; import CalendarFilters from "./(components)/CalendarFilters.svelte"; import CalendarHeader from "./(components)/CalendarHeader.svelte"; - import { openAppointmentById } from "./(components)/utils"; - import { createQuery, useQueryClient } from "@tanstack/svelte-query"; + import CalendarLegend from "./(components)/CalendarLegend.svelte"; + import CalendarLines from "./(components)/CalendarLines.svelte"; + import CalendarWeek from "./(components)/CalendarWeek.svelte"; import { calendarMonthQuery } from "./(components)/queries"; - - const convertDate = (dateStr: string) => { - const zonedDateTime = parseAbsoluteToLocal(dateStr); - return toCalendarDate(zonedDateTime); - }; + import { convertDate, openAppointmentById } from "./(components)/utils"; + import type { CalendarView } from "./types"; const queryClient = useQueryClient(); const tenantId = $derived($auth.user?.tenantId); @@ -54,6 +46,7 @@ let shownAppointments: TAppointmentFilter = $state("all"); let shownChannels: string[] = $state([]); let shownAgents: string[] = $state([]); + let view: CalendarView = $state(page.data.calendarView); let hours = $derived.by(() => { const from = channels .map((c) => c.slotTemplates.map((t) => t.from)) @@ -71,7 +64,7 @@ }); return { from: Math.min(...from), to: Math.max(...to) }; }); - let scale = $state(1); + let scale = $state(page.data.calendarZoom); $effect(() => { const getMonth = (x: string | undefined) => (x ? new Date(x).getMonth() + 1 : undefined); @@ -81,24 +74,21 @@ }); $effect(() => { - if (items && history.state["sveltekit:states"]?.appointmentId) { + if (calendar && history.state["sveltekit:states"]?.appointmentId) { // Wait 100ms to ensure that the calendar items are rendered setTimeout(() => { - openAppointmentById(items, history.state["sveltekit:states"].appointmentId, () => { - replaceState("", {}); - }); + openAppointmentById( + calendar, + channels, + history.state["sveltekit:states"].appointmentId, + () => { + replaceState("", {}); + }, + ); }, 100); } }); - onMount(() => { - if (history.state["sveltekit:states"]?.date) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { date, ...rest } = history.state["sveltekit:states"]; - replaceState("", rest); - } - }); - // Navigating to appointment, if calendar is already mounted $effect(() => { if ( @@ -111,15 +101,28 @@ selectedDate = date; replaceState("", { appointmentId: page.state.appointmentId }); } else { - if (items) { - openAppointmentById(items, history.state["sveltekit:states"].appointmentId, () => { - replaceState("", {}); - }); + if (calendar) { + openAppointmentById( + calendar, + channels, + history.state["sveltekit:states"].appointmentId, + () => { + replaceState("", {}); + }, + ); } } } }); + onMount(() => { + if (history.state["sveltekit:states"]?.date) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { date, ...rest } = history.state["sveltekit:states"]; + replaceState("", rest); + } + }); + const updateCalendar = async () => { if (tenantId) { queryClient.invalidateQueries({ @@ -131,77 +134,6 @@ const closeAppointmentDetail = () => { $calendarStore.curItem = null; }; - - let items: TCalendarItem[] | undefined = $derived.by(() => { - if (!calendar) return undefined; - const dayEntry = calendar.calendar.find((d) => d.date === selectedDate.toString()); - if (!dayEntry) return []; - return Object.keys(dayEntry.channels).reduce((allItems, channelId) => { - const channelData = dayEntry.channels[channelId]; - const channelItems: TCalendarItem[] = []; - - // Available slots - if (["all", "available"].includes(shownAppointments)) { - if (shownChannels.length === 0 || shownChannels.includes(channelId)) { - channelData.availableSlots.forEach((slot) => { - const isSlotInPast = utcToLocalWithoutDST(new Date(slot.to)) < new Date(); - if (!isSlotInPast) { - if ( - shownAgents.length === 0 || - shownAgents.some((id) => slot.availableAgents.map((a) => a.id).includes(id)) - ) { - channelItems.push({ - id: `${channelId}-${slot.from}`, - date: dayEntry.date, - start: slot.from, - duration: slot.duration, - channelId, - color: channelData.channel.color, - column: 0, - status: "available", - availableAgents: slot.availableAgents, - }); - } - } - }); - } - } - - // Appointments - if (["all", "booked", "reserved"].includes(shownAppointments)) { - channelData.appointments.forEach((appointment) => { - const status = appointment.status === "CONFIRMED" ? "booked" : "reserved"; - if (shownAppointments === "all" || shownAppointments === status) { - if (shownChannels.length === 0 || shownChannels.includes(channelId)) { - if (shownAgents.length === 0 || shownAgents.includes(appointment.agentId)) { - channelItems.push({ - id: appointment.id, - date: dayEntry.date, - start: new Date(appointment.appointmentDate).toISOString(), - duration: appointment.duration, - channelId, - color: channelData.channel.color, - column: 0, - status, - appointment: { - dateTime: new Date(appointment.appointmentDate), - encryptedPayload: appointment.encryptedPayload, - tunnelId: appointment.tunnelId, - agentId: appointment.agentId, - staffKeyShare: appointment.staffKeyShare, - iv: appointment.iv || undefined, - authTag: appointment.authTag || undefined, - }, - }); - } - } - } - }); - } - - return [...allItems, ...channelItems]; - }, []); - }); @@ -209,20 +141,54 @@ - - - - - + + + {#if view === "day"} + - + + + {:else} + + {/if} - + {#snippet headerRight()} {/snippet} diff --git a/src/routes/(pages)/dashboard/calendar/types.ts b/src/routes/(pages)/dashboard/calendar/types.ts new file mode 100644 index 0000000..b14aa6c --- /dev/null +++ b/src/routes/(pages)/dashboard/calendar/types.ts @@ -0,0 +1 @@ +export type CalendarView = "day" | "week" | "week-workdays";