Harden navigation for notifications on a all pages

This commit is contained in:
Karl Ludwig Weise
2026-01-28 20:32:50 +01:00
parent c5c86e6973
commit 9a9abdcf46
6 changed files with 74 additions and 28 deletions
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { resolve } from "$app/paths";
import { page } from "$app/state";
import { m } from "$i18n/messages";
import { getLocale } from "$i18n/runtime";
import { Button } from "$lib/components/ui/button";
@@ -16,8 +17,10 @@
let {
item,
closePopover,
}: {
item: TNotification;
closePopover: () => void;
} = $props();
const channels = $derived($channelsStore.channels);
@@ -53,12 +56,23 @@
class=" flex h-auto w-full shrink flex-col items-start gap-1 rounded-none px-3 last:rounded-b-md focus:ring-inset"
onclick={() => {
if (appointment) {
goto(resolve(ROUTES.DASHBOARD.CALENDAR), {
state: {
date: appointment.appointmentDate,
appointmentId: appointment.id,
},
});
closePopover();
if (page.url.pathname === ROUTES.DASHBOARD.CALENDAR) {
goto(resolve(ROUTES.DASHBOARD.CALENDAR), {
state: {
isNavigatingOnCalendarPage: true,
date: appointment.appointmentDate,
appointmentId: appointment.id,
},
});
} else {
goto(resolve(ROUTES.DASHBOARD.CALENDAR), {
state: {
date: appointment.appointmentDate,
appointmentId: appointment.id,
},
});
}
}
}}
>
@@ -79,7 +79,7 @@
{/if}
{#each notifications as notification (notification.id)}
<div class="[&+&]:border-t-muted relative [&+&]:border-t">
<NotificationItem item={notification} />
<NotificationItem item={notification} closePopover={() => (open = false)} />
<Button
variant="ghost"
class="absolute top-1.5 right-1.5 rounded-md p-1!"
@@ -100,7 +100,7 @@
{/if}
</Dialog.Trigger>
{/if}
<Dialog.Content class="max-h-[95vh] sm:max-w-[425px]">
<Dialog.Content class="max-h-[95vh] sm:max-w-106.25">
<Dialog.Header>
<Dialog.Title class={cn(description ? "" : "-mb-1")}>{title}</Dialog.Title>
{#if description}
@@ -4,7 +4,7 @@
import { Text } from "$lib/components/ui/typography";
import { type CurAppointmentItem } from "$lib/stores/calendar";
import { getLocalTimeZone } from "@internationalized/date";
import { Calendar, Mail, Phone } from "@lucide/svelte";
import { Calendar, Mail, Phone, User } from "@lucide/svelte";
import { cancelAppointment } from "./utils";
import { toast } from "svelte-sonner";
import { m } from "$i18n/messages";
@@ -42,6 +42,12 @@
{#if item.appointment.appointment}
<div class="flex flex-col items-start gap-3">
<div class="flex gap-2 p-1">
<User class="size-4 " />
<Text style="sm">
{item.decrypted.name}
</Text>
</div>
<div class="flex flex-col items-start gap-2">
<Button
class="h-auto w-auto justify-start gap-2 rounded-sm p-1"
@@ -122,7 +122,7 @@ export function positionItems(items: TCalendarItem[] | undefined) {
export const cancelAppointment = async (opts: { tenant: string; appointment: string }) => {
if (!browser) return;
const res = await fetch(`/api/tenants/${opts.tenant}/appointments/${opts.appointment}`, {
const res = await fetch(`/api/tenants/${opts.tenant}/appointments/${opts.appointment}/delete`, {
method: "DELETE",
});
@@ -1,10 +1,12 @@
<script lang="ts">
import { replaceState } from "$app/navigation";
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";
import { ROUTES } from "$lib/const/routes";
import { agents as agentsStore } from "$lib/stores/agents";
import { auth } from "$lib/stores/auth";
import { calendarStore } from "$lib/stores/calendar";
import { channels as channelsStore } from "$lib/stores/channels";
@@ -26,13 +28,22 @@
import CalendarFilters from "./(components)/CalendarFilters.svelte";
import CalendarHeader from "./(components)/CalendarHeader.svelte";
import { fetchCalendar, openAppointmentById } from "./(components)/utils";
import { staffCrypto } from "$lib/stores/staff-crypto";
import { replaceState } from "$app/navigation";
import { page } from "$app/state";
const convertDate = (dateStr: string) => {
const zonedDateTime = parseAbsoluteToLocal(dateStr);
return toCalendarDate(zonedDateTime);
};
const tenantId = $derived($auth.user?.tenantId);
const curItem = $derived($calendarStore.curItem);
const channels = $derived($channelsStore.channels);
let startDate: CalendarDate = $state(today(getLocalTimeZone()));
const agents = $derived($agentsStore.agents);
let startDate: CalendarDate = $state(
history.state["sveltekit:states"]?.date
? convertDate(history.state["sveltekit:states"].date)
: today(getLocalTimeZone()),
);
let calender: TCalendar | undefined = $state();
let shownAppointments: TAppointmentFilter = $state("all");
let shownChannels: string[] = $state([]);
@@ -55,33 +66,47 @@
return { from: Math.min(...from), to: Math.max(...to) };
});
let scale = $state(1);
let openAppointmentAfterUpdate: string | undefined = $state();
$effect(() => {
updateCalendar();
});
$effect(() => {
// Wait 100ms to ensure that the calendar items are rendered
setTimeout(() => {
if (items && openAppointmentAfterUpdate) {
openAppointmentById(items, openAppointmentAfterUpdate, () => {
openAppointmentAfterUpdate = undefined;
if (items && history.state["sveltekit:states"]?.appointmentId) {
// Wait 100ms to ensure that the calendar items are rendered
setTimeout(() => {
openAppointmentById(items, history.state["sveltekit:states"].appointmentId, () => {
replaceState("", {});
});
}
}, 100);
}, 100);
}
});
onMount(() => {
if (history.state["sveltekit:states"]?.date) {
const isoDateString = history.state["sveltekit:states"].date;
const zonedDateTime = parseAbsoluteToLocal(isoDateString);
startDate = toCalendarDate(zonedDateTime);
const { date, ...rest } = history.state["sveltekit:states"];
replaceState("", rest);
}
});
if (history.state["sveltekit:states"]?.appointmentId) {
openAppointmentAfterUpdate = history.state["sveltekit:states"].appointmentId;
// Navigating to appointment, if calendar is already mounted
$effect(() => {
if (
"isNavigatingOnCalendarPage" in page.state &&
"date" in page.state &&
"appointmentId" in page.state
) {
const date = convertDate(page.state.date as string);
if (date.toString() !== startDate.toString()) {
startDate = date;
replaceState("", { appointmentId: page.state.appointmentId });
} else {
if (items) {
openAppointmentById(items, history.state["sveltekit:states"].appointmentId, () => {
replaceState("", {});
});
}
}
replaceState("", "");
}
});
@@ -202,9 +227,10 @@
{#if curItem && tenantId}
{@const channel = channels.find((c) => c.id === curItem.appointment.channelId)}
{@const agent = agents.find((a) => a.id === curItem.appointment.appointment?.agentId)}
<ResponsiveDialog
id="current-calendar-item"
title={curItem.decrypted.name}
title={agent?.name || "unkown agent"}
description={channel ? getCurrentTranlslation(channel.names) : undefined}
triggerHidden={true}
>