Added dummy flow to add appointments using the calendar

This commit is contained in:
Karl Ludwig Weise
2026-02-12 22:04:03 +01:00
parent 68bfcfb609
commit fe9ea0703c
23 changed files with 615 additions and 24 deletions
+28
View File
@@ -946,6 +946,34 @@
"action": "Termin bestätigen",
"success": "Termin erfolgreich bestätigt",
"error": "Fehler beim Bestätigen des Termins"
},
"addAppointment": {
"preview": "Neuer Termin {time} Uhr {channel}",
"steps": {
"selectClient": {
"hasNoEmail": "Klient hat keine E-Mail Adresse",
"action": "Klient suchen"
},
"agents": {
"title": "Akteur auswählen"
},
"clientData": {
"shareEmail": "Klient wünscht Benachrichtigungen per E-Mail",
"phoneHint": "Format: +1234567890 (optional)",
"action": "Daten hinzufügen"
},
"summary": {
"action": "Termin hinzufügen"
},
"error": {
"title": "Fehler beim Hinzufügen des Termins",
"description": "Das Hinzufügen des Termins ist fehlgeschlagen. Bitte versuchen Sie es erneut."
},
"success": {
"title": "Termin hinzugefügt",
"description": "Der Termin wurde erfolgreich hinzugefügt"
}
}
}
},
"emails": {
+28
View File
@@ -955,6 +955,34 @@
"action": "Confirm Appointment",
"success": "Appointment confirmed successfully",
"error": "Failed to confirm appointment"
},
"addAppointment": {
"preview": "Add {time} {channel}",
"steps": {
"selectClient": {
"hasNoEmail": "Client has no e-mail",
"action": "Search Client"
},
"agents": {
"title": "Select an Agent"
},
"clientData": {
"shareEmail": "Client wishes updates via E-Mail",
"phoneHint": "Format: +1234567890 (optional)",
"action": "Add Client Details"
},
"summary": {
"action": "Add Appointment"
},
"error": {
"title": "Error adding Appointment",
"description": "Adding an appointment failed. Please try again."
},
"success": {
"title": "Appointment added",
"description": "This appointment was added successfully"
}
}
}
},
"emails": {
+10 -1
View File
@@ -25,16 +25,25 @@
onclick?.();
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Enter") {
if (!disabledStates.includes(state)) {
onclick?.();
}
}
};
</script>
<Button
variant="outline"
onclick={onClick}
class={cn(
"flex h-[40px] w-full justify-start",
"flex h-10 w-full justify-start",
state === "error" ? "dark:border-destructive border-destructive text-destructive" : "",
className,
)}
onkeydown={onKeyDown}
disabled={disabledStates.includes(state)}
>
{#if state === "initial"}
@@ -9,6 +9,7 @@
<a class="button" {href}>{@render children?.()}</a>
</div>
<span class="html-only md">
<!-- eslint-disable-next-line svelte/no-navigation-without-resolve -->
{m["emails.buttonAlternativeLink"]()}: <a {href}>{href}</a>
</span>
+12 -1
View File
@@ -1,6 +1,6 @@
import type { AppointmentData } from "$lib/client/appointment-crypto";
import { openDialog } from "$lib/components/ui/responsive-dialog";
import type { TCalendarItem } from "$lib/types/calendar";
import type { TCalendarItem, TCalendarSlot } from "$lib/types/calendar";
import { writable } from "svelte/store";
export type CurAppointmentItem = {
@@ -10,11 +10,13 @@ export type CurAppointmentItem = {
interface CalendarState {
curItem: CurAppointmentItem | null;
curEmptySlot: TCalendarSlot | null;
}
const createCalendarStore = () => {
const store = writable<CalendarState>({
curItem: null,
curEmptySlot: null,
});
return {
@@ -28,6 +30,15 @@ const createCalendarStore = () => {
openDialog("current-calendar-item");
}
},
setCurSlot: (curEmptySlot: TCalendarSlot | null) => {
store.update((state) => {
return { ...state, curEmptySlot };
});
if (curEmptySlot) {
openDialog("current-calendar-slot");
}
},
};
};
+14 -9
View File
@@ -1,3 +1,4 @@
import type { SelectAgent } from "$lib/server/db/tenant-schema";
import type { DaySchedule } from "$lib/server/services/schedule-service";
export type AppointmentStatus = "available" | "booked" | "reserved";
@@ -11,15 +12,7 @@ export type TCalendar = {
calendar: DaySchedule[];
};
export type TCalendarItem = {
date: string; // YYYY-MM-DD
id: string;
start: string; // HH:mm
duration: number; // in minutes
status: AppointmentStatus;
color: string | null;
column: number;
channelId: string;
export type TCalendarItem = TCalendarSlot & {
appointment?: {
dateTime: Date;
encryptedPayload: string | null;
@@ -30,3 +23,15 @@ export type TCalendarItem = {
authTag?: string;
};
};
export type TCalendarSlot = {
date: string; // YYYY-MM-DD
id: string;
start: string; // HH:mm
duration: number; // in minutes
status: AppointmentStatus;
color: string | null;
column: number;
channelId: string;
availableAgents?: SelectAgent[];
};
+18 -5
View File
@@ -1,15 +1,28 @@
import { getLocale } from "$i18n/runtime";
import type { TCalendarSlot } from "$lib/types/calendar";
import { parseAbsoluteToLocal, toCalendarDateTime } from "@internationalized/date";
export const toDisplayDateTime = (date: Date) => {
export const toDisplayDateTime = (date: Date, opts?: Intl.DateTimeFormatOptions) => {
const hours = date.getHours();
const minutes = date.getMinutes();
const formatter = new Intl.DateTimeFormat(navigator.language, {
dateStyle: "short",
...(hours !== 0 || minutes !== 0 ? { timeStyle: "short" } : {}),
});
const formatter = new Intl.DateTimeFormat(
getLocale(),
opts ?? {
dateStyle: "short",
...(hours !== 0 || minutes !== 0 ? { timeStyle: "short" } : {}),
},
);
return formatter.format(date);
};
export const calendarItemToDate = (item: TCalendarSlot) => {
const [year, month, day] = item.date.split("-").map(Number);
const [hours, minutes] = item.start.split(":").map((it) => parseInt(it));
const date = new Date(year, month - 1, day);
date.setHours(hours, minutes, 0, 0);
return date;
};
export const toInputDateTime = (dateStr: string) => {
return toCalendarDateTime(parseAbsoluteToLocal(dateStr));
};
@@ -12,7 +12,7 @@ export const createFormSchema = (requirePhone?: boolean) => {
export const formSchema = z.object({
name: z.string().min(2, m["form.errors.name"]()).max(50, m["form.errors.name"]()),
email: z.string().email(m["form.errors.email"]()),
email: z.email(m["form.errors.email"]()),
phone: z.e164(m["form.errors.phoneNoInvalid"]()).optional(),
});
@@ -1,5 +1,4 @@
<script lang="ts">
import { getLocale } from "$i18n/runtime";
import { Button } from "$lib/components/ui/button";
import { Text } from "$lib/components/ui/typography";
import { type CurAppointmentItem } from "$lib/stores/calendar";
@@ -8,6 +7,7 @@
import { cancelAppointment, denyAppointment, confirmAppointment } from "./utils";
import { toast } from "svelte-sonner";
import { m } from "$i18n/messages";
import { toDisplayDateTime } from "$lib/utils/datetime";
let {
tenantId,
@@ -117,7 +117,7 @@
<div class="flex gap-2 p-1">
<Calendar class="size-4 " />
<Text style="sm">
{Intl.DateTimeFormat(getLocale(), {
{toDisplayDateTime(item.appointment.appointment.dateTime, {
year: "numeric",
month: "long",
day: "numeric",
@@ -125,7 +125,7 @@
hour: "2-digit",
minute: "2-digit",
timeZone: getLocalTimeZone().toString(),
}).format(item.appointment.appointment.dateTime)}
})}
</Text>
</div>
<div class="mt-5 flex w-full flex-col gap-4">
@@ -18,6 +18,7 @@
import { positionItems } from "./utils";
import AppointmentPreview from "./AppointmentPreview.svelte";
import { getContrastColor } from "$lib/utils/color";
import SlotPreview from "./SlotPreview.svelte";
let {
day = $bindable(),
@@ -117,6 +118,8 @@
>
{#if ["booked", "reserved"].includes(item.status)}
<AppointmentPreview {item} />
{:else if item.status === "available"}
<SlotPreview {item} />
{/if}
</div>
</div>
@@ -33,8 +33,8 @@
<div
class="flex flex-col items-start justify-between gap-2 min-[500px]:flex-row min-[500px]:items-center"
>
<div class="-ml-1 flex w-[350px] items-center justify-between gap-5">
<Button size="sm" variant="ghost" class="h-6 !p-1" onclick={prev}>
<div class="-ml-1 flex w-87.5 items-center justify-between gap-5">
<Button size="sm" variant="ghost" class="h-6 p-1!" onclick={prev}>
<ChevronLeft />
</Button>
<div>
@@ -46,7 +46,7 @@
timeZone: getLocalTimeZone().toString(),
}).format(startDate.toDate(getLocalTimeZone()))}
</div>
<Button size="sm" variant="ghost" class="h-6 !p-1" onclick={next}>
<Button size="sm" variant="ghost" class="h-6 p-1!" onclick={next}>
<ChevronRight />
</Button>
</div>
@@ -0,0 +1,39 @@
<script lang="ts">
import { m } from "$i18n/messages";
import { Button } from "$lib/components/ui/button";
import { calendarStore } from "$lib/stores/calendar";
import { channels as channelsStore } from "$lib/stores/channels";
import { type TCalendarItem } from "$lib/types/calendar";
import { calendarItemToDate, toDisplayDateTime } from "$lib/utils/datetime";
import { getCurrentTranlslation } from "$lib/utils/localizations";
let {
item,
}: {
item: TCalendarItem;
} = $props();
let channel = $derived(
$channelsStore.channels.filter((x) => !x.archived).find((x) => x.id === item.channelId),
);
const setCalendarItem = () => {
calendarStore.setCurSlot(item);
};
</script>
<Button
class="m-0 h-full w-full cursor-pointer justify-start rounded-none px-1 leading-none text-(--channel-color-contrast) hover:bg-transparent! hover:text-(--channel-color-contrast) focus:ring-1"
variant="ghost"
onclick={setCalendarItem}
>
<span class="sr-only">
{m["calendar.addAppointment.preview"]({
time: toDisplayDateTime(calendarItemToDate(item), {
hour: "2-digit",
minute: "2-digit",
}),
channel: channel ? getCurrentTranlslation(channel.names) : "unkown channel",
})}
</span>
</Button>
@@ -0,0 +1,85 @@
<script lang="ts">
import { m } from "$i18n/messages";
import { CenterState } from "$lib/components/templates/empty-state";
import Button from "$lib/components/ui/button/button.svelte";
import type { TCalendarSlot } from "$lib/types/calendar";
import { calendarItemToDate } from "$lib/utils/datetime";
import { BanIcon, Check } from "@lucide/svelte";
import { SearchClientForm } from "./search-client-form";
import SelectAgent from "./SelectAgent.svelte";
import Summary from "./Summary.svelte";
import type { TAddAppointment, TAddAppointmentStep } from "./types";
import { ClientDataForm } from "./client-data-form";
let {
tenantId,
item = $bindable(),
updateCalendar,
}: {
tenantId: string;
item: TCalendarSlot;
updateCalendar: () => void;
} = $props();
let step: TAddAppointmentStep = $state("email");
let newAppointment: TAddAppointment = $state({ dateTime: calendarItemToDate(item) });
const proceed = (data: TAddAppointment) => {
switch (true) {
case Boolean(data.name): {
newAppointment = {
...data,
};
step = "summary";
break;
}
case Boolean(data.agentId): {
newAppointment = {
...data,
};
step = "client";
break;
}
case Boolean(data.email) || data.hasNoEmail: {
newAppointment = {
...data,
};
step = "agent";
break;
}
}
};
const addAppointment = async () => {
console.log("adding appointment", newAppointment);
updateCalendar();
step = "success";
};
</script>
<Summary {step} {newAppointment} />
{#if step === "email"}
<SearchClientForm {tenantId} {newAppointment} {proceed} />
{:else if step === "agent" && item.availableAgents}
<SelectAgent availableAgents={item.availableAgents} {newAppointment} {proceed} />
{:else if step === "summary"}
<Button onclick={addAppointment} class="w-full">
{m["calendar.addAppointment.steps.summary.action"]()}
</Button>
{:else if step === "client"}
<ClientDataForm {newAppointment} {proceed} />
{:else if step === "success"}
<CenterState
headline={m["calendar.addAppointment.steps.success.title"]()}
description={m["calendar.addAppointment.steps.success.description"]()}
Icon={Check}
size="sm"
/>
{:else}
<CenterState
headline={m["calendar.addAppointment.steps.error.title"]()}
description={m["calendar.addAppointment.steps.error.description"]()}
Icon={BanIcon}
size="sm"
/>
{/if}
@@ -0,0 +1,53 @@
<script lang="ts">
import { m } from "$i18n/messages";
import { Button } from "$lib/components/ui/button";
import { Text } from "$lib/components/ui/typography";
import type { SelectAgent } from "$lib/server/db/tenant-schema";
import type { TAddAppointment } from "./types";
import UnknownItemIcon from "@lucide/svelte/icons/user-star";
let {
newAppointment,
availableAgents,
proceed,
}: {
newAppointment: TAddAppointment;
availableAgents: SelectAgent[];
proceed: (data: TAddAppointment) => void;
} = $props();
</script>
<div class="flex flex-col gap-2">
<Text style="sm" class="block font-medium">
{m["calendar.addAppointment.steps.agents.title"]()}
</Text>
<ul class="flex w-full flex-col gap-2">
{#each availableAgents as agent (agent.id)}
<li>
<Button
variant="outline"
class="flex h-auto w-full cursor-pointer items-center gap-4 px-2 py-1.5 text-left"
onclick={() => proceed({ ...newAppointment, agentId: agent.id })}
>
{#if agent.image}
<img
src={agent.image}
alt={agent.name}
class="size-8 rounded-full border object-cover object-center"
loading="lazy"
/>
{:else}
<UnknownItemIcon
class="bg-muted text-muted-foreground size-15 rounded-full border stroke-1 p-1"
/>
{/if}
<div class="flex grow flex-col gap-1">
<Text style="md" class="font-medium">
{agent.name}
</Text>
</div>
</Button>
</li>
{/each}
</ul>
</div>
@@ -0,0 +1,76 @@
<script lang="ts">
import { Button } from "$lib/components/ui/button";
import { Separator } from "$lib/components/ui/separator";
import { Text } from "$lib/components/ui/typography";
import { agents as agentsStore } from "$lib/stores/agents";
import { toDisplayDateTime } from "$lib/utils/datetime";
import { Calendar, Mail, Phone, User, UserStar } from "@lucide/svelte";
import type { TAddAppointment, TAddAppointmentStep } from "./types";
let {
step,
newAppointment,
}: {
step: TAddAppointmentStep;
newAppointment: TAddAppointment;
} = $props();
let agent = $derived($agentsStore.agents.find((a) => a.id === newAppointment.agentId));
</script>
<div class="flex flex-col items-start gap-3">
<div class="flex gap-2">
<Calendar class="size-4 " />
<Text style="sm">
{toDisplayDateTime(newAppointment.dateTime, {
year: "numeric",
month: "long",
day: "numeric",
weekday: "short",
hour: "2-digit",
minute: "2-digit",
})}
</Text>
</div>
{#if newAppointment.name}
<div class="flex gap-2">
<User class="size-4 " />
<Text style="sm">
{newAppointment.name}
</Text>
</div>
{/if}
{#if newAppointment.email}
<Button
class="h-auto w-auto justify-start gap-2 rounded-sm p-1"
variant="link"
href={`mailto:${newAppointment.email}`}
>
<Mail class="size-4 " />
{newAppointment.email}
</Button>
{/if}
{#if newAppointment.phone}
<Button
class="h-auto w-auto justify-start gap-2 rounded-sm p-1"
variant="link"
href={`tel:${newAppointment.phone}`}
>
<Phone class="size-4 " />
{newAppointment.phone}
</Button>
{/if}
{#if agent}
<div class="flex gap-2">
<UserStar class="size-4 " />
<Text style="sm">
{agent.name}
</Text>
</div>
{/if}
</div>
<div class="my-5">
{#if step !== "summary"}
<Separator />
{/if}
</div>
@@ -0,0 +1,90 @@
<script lang="ts">
import { m } from "$i18n/messages.js";
import * as Form from "$lib/components/ui/form";
import { Input } from "$lib/components/ui/input";
import { superForm } from "sveltekit-superforms";
import { zod4Client as zodClient } from "sveltekit-superforms/adapters";
import { formSchema } from ".";
import type { TAddAppointment } from "../types";
import { CheckboxWithLabel } from "$lib/components/ui/checkbox-with-label";
let {
newAppointment,
proceed,
}: {
newAppointment: TAddAppointment;
proceed: (data: TAddAppointment) => void;
} = $props();
const form = superForm(
// eslint-disable-next-line no-constant-condition
{ name: "", phone: true ? "" : undefined, shareEmail: false },
{
validators: zodClient(formSchema),
onSubmit: async ({ cancel }) => {
if ($formData.phone === "") {
$formData.phone = undefined;
}
const validation = await validateForm();
if (validation.valid) {
cancel();
proceed({
...newAppointment,
name: $formData.name,
phone: $formData.phone,
shareEmail: $formData.shareEmail,
});
}
},
},
);
const { form: formData, enhance, validateForm } = form;
</script>
<Form.Root {enhance} class="w-full">
<Form.Field {form} name="name">
<Form.Control>
{#snippet children({ props })}
<Form.Label>{m["form.name"]()}</Form.Label>
<Input {...props} bind:value={$formData.name} type="name" />
{/snippet}
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="phone">
<Form.Control>
{#snippet children({ props })}
<Form.Label>{m["form.phone"]()}</Form.Label>
<Input {...props} bind:value={$formData.phone} type="phone" />
{/snippet}
</Form.Control>
<Form.FieldErrors />
<Form.Description class="mt-1">
{m["calendar.addAppointment.steps.clientData.phoneHint"]()}
</Form.Description>
</Form.Field>
{#if newAppointment.email}
<Form.Field {form} name="shareEmail">
<Form.Control>
{#snippet children({ props })}
<CheckboxWithLabel
{...props}
bind:value={$formData.shareEmail}
label={m["calendar.addAppointment.steps.clientData.shareEmail"]()}
onCheckedChange={(v) => {
$formData.shareEmail = v;
}}
/>
{/snippet}
</Form.Control>
<Form.FieldErrors />
</Form.Field>
{/if}
<div class="mt-0 flex flex-col gap-4">
<Form.Button size="lg" type="submit">
{m["calendar.addAppointment.steps.clientData.action"]()}
</Form.Button>
</div>
</Form.Root>
@@ -0,0 +1,5 @@
import ClientDataForm from "./client-data-form.svelte";
export { ClientDataForm };
export { formSchema } from "./schema";
export type { FormSchema } from "./schema";
@@ -0,0 +1,10 @@
import { m } from "$i18n/messages";
import { z } from "zod";
export const formSchema = z.object({
shareEmail: z.boolean(),
name: z.string().min(2, m["form.errors.name"]()).max(50, m["form.errors.name"]()),
phone: z.e164(m["form.errors.phoneNoInvalid"]()).optional(),
});
export type FormSchema = typeof formSchema;
@@ -0,0 +1,5 @@
import SearchClientForm from "./search-client-form.svelte";
export { SearchClientForm };
export { formSchema } from "./schema";
export type { FormSchema } from "./schema";
@@ -0,0 +1,8 @@
import { m } from "$i18n/messages";
import { z } from "zod";
export const formSchema = z.object({
email: z.email(m["form.errors.email"]()),
});
export type FormSchema = typeof formSchema;
@@ -0,0 +1,95 @@
<script lang="ts">
import { m } from "$i18n/messages.js";
import * as Form from "$lib/components/ui/form";
import { Input } from "$lib/components/ui/input";
import { superForm } from "sveltekit-superforms";
import { zod4Client as zodClient } from "sveltekit-superforms/adapters";
import { formSchema } from ".";
import type { TAddAppointment } from "../types";
import Button from "$lib/components/ui/button/button.svelte";
let {
tenantId,
newAppointment,
proceed,
}: {
tenantId: string;
newAppointment: TAddAppointment;
proceed: (data: TAddAppointment) => void;
} = $props();
const form = superForm(
{ email: "" },
{
validators: zodClient(formSchema),
onSubmit: async ({ cancel }) => {
const validation = await validateForm();
if (validation.valid) {
cancel();
isSubmitting = true;
console.log("tenantId", tenantId);
console.log("email", $formData.email);
const hashedEmail = await hashEmail($formData.email);
console.log("hashed", hashedEmail);
proceed({ ...newAppointment, email: $formData.email, hasNoEmail: false });
// const success = await confirmAppointment({
// tenant: tenantId,
// appointment: item.appointment.id,
// email: item.decrypted.shareEmail ? item.decrypted.email : undefined,
// locale: "de", // TODO: Use client language as soon as available in appointment
// });
// if (success) {
// toast.success(m["calendar.confirmAppointment.success"]());
// updateCalendar();
// close();
// } else {
// toast.error(m["calendar.confirmAppointment.error"]());
// }
isSubmitting = false;
}
},
},
);
let isSubmitting = $state(false);
const { form: formData, enhance, validateForm } = form;
// TODO: Use the version from appointment-crypto
const hashEmail = async (email: string): Promise<string> => {
const emailNormalized = email.toLowerCase().trim();
const encoder = new TextEncoder();
const data = encoder.encode(emailNormalized);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
};
const proceedWithoutEmail = () => {
proceed({ ...newAppointment, email: undefined, hasNoEmail: true });
};
</script>
<Form.Root {enhance} class="w-full">
<Form.Field {form} name="email">
<Form.Control>
{#snippet children({ props })}
<Form.Label>{m["form.email"]()}</Form.Label>
<Input {...props} bind:value={$formData.email} type="email" />
{/snippet}
</Form.Control>
<Form.FieldErrors />
<Form.Description>
<Button size="sm" variant="link" onclick={proceedWithoutEmail}>
{m["calendar.addAppointment.steps.selectClient.hasNoEmail"]()}
</Button>
</Form.Description>
</Form.Field>
<div class="mt-0 flex flex-col gap-4">
<Form.Button size="lg" type="submit" isLoading={isSubmitting} disabled={isSubmitting}>
{m["calendar.addAppointment.steps.selectClient.action"]()}
</Form.Button>
</div>
</Form.Root>
@@ -0,0 +1,11 @@
export type TAddAppointment = {
dateTime: Date;
agentId?: string;
email?: string;
hasNoEmail?: boolean;
shareEmail?: boolean;
name?: string;
phone?: string;
};
export type TAddAppointmentStep = "email" | "agent" | "client" | "summary" | "success" | "error";
@@ -1,5 +1,6 @@
<script lang="ts">
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";
@@ -23,12 +24,12 @@
} from "@internationalized/date";
import { Funnel } from "@lucide/svelte";
import { onMount } from "svelte";
import AddAppointment from "./(components)/add-appointment/AddAppointment.svelte";
import AppointmentDetail from "./(components)/AppointmentDetail.svelte";
import CalendarDay from "./(components)/CalendarDay.svelte";
import CalendarFilters from "./(components)/CalendarFilters.svelte";
import CalendarHeader from "./(components)/CalendarHeader.svelte";
import { fetchCalendar, openAppointmentById } from "./(components)/utils";
import { page } from "$app/state";
const convertDate = (dateStr: string) => {
const zonedDateTime = parseAbsoluteToLocal(dateStr);
@@ -36,6 +37,7 @@
};
const tenantId = $derived($auth.user?.tenantId);
const curEmptySlot = $derived($calendarStore.curEmptySlot);
const curItem = $derived($calendarStore.curItem);
const channels = $derived($channelsStore.channels);
const agents = $derived($agentsStore.agents);
@@ -84,6 +86,7 @@
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);
}
@@ -146,6 +149,7 @@
color: channelData.channel.color,
column: 0,
status: "available",
availableAgents: slot.availableAgents,
});
}
});
@@ -237,3 +241,15 @@
<AppointmentDetail {tenantId} item={curItem} {updateCalendar} close={closeAppointmentDetail} />
</ResponsiveDialog>
{/if}
{#if curEmptySlot && tenantId}
{@const channel = channels.find((c) => c.id === curEmptySlot.channelId)}
<ResponsiveDialog
id="current-calendar-slot"
title="Add Appointment"
description={channel ? getCurrentTranlslation(channel.names) : undefined}
triggerHidden={true}
>
<AddAppointment {tenantId} item={curEmptySlot} {updateCalendar} />
</ResponsiveDialog>
{/if}