Skip to content

Commit

Permalink
Send notifications to KM when new booking requests are made + new boo…
Browse files Browse the repository at this point in the history
…king review page (#527)

* Initial concept for sending booking notifications to KM

* Removed unnecessary type

* Improved notification message

* Fixed incorrect time when editing booking requests

* Added new page for accepting/denying booking requests and linked notifications there

* Refactored identical code for accepting/rejecting booking requests to sharedActions.ts

* Refactored code for handling accept/reject actions into one function

* Refactored code for getting weekly booking requests

* Changed StatusComponent.svelte to support two different modes

* Formatting fix and added link to inspect page from /booking/admin

* Fixed incorrect date format on /booking/admin

* Refactored identical code for getting bookingRequest and form into sharedUtils.ts

* Removed old, unnecessary code

* Fixed invalid bookingRequest id error

* Removed unused parameter

* Removed old testing code

* Added error handling for KM notifications

* Removed unused variable

* Moved sharedUtils.ts

* Edited import

* Added TODO

* Changed the booking inspector to match the updated booking editor

* Update to booking inspector:

* Added review mode and functionality to BookingEditor.svelte

* Removed old BookingInspector.svelte

* Renamed instances of "inspect" to "review"

* Added translations message for admin/[id] page title

* Moved StatusComponent.svelte to reflect its new usage

* Changed notification bookable names to nameEn

* Fixed the calendar on review booking page so it does not display *all* bookings

* General refactoring
  • Loading branch information
Fiery-132 authored Nov 9, 2024
1 parent 3f5f058 commit 9a7fbd3
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 124 deletions.
3 changes: 2 additions & 1 deletion src/routes/(app)/booking/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { isAuthorized } from "$lib/utils/authorization";
import { page } from "$app/stores";
import apiNames from "$lib/utils/apiNames";
import StatusComponent from "./admin/StatusComponent.svelte";
import StatusComponent from "./StatusComponent.svelte";
import dayjs from "dayjs";
import ConfirmDialog from "$lib/components/ConfirmDialog.svelte";
import BookingCalendar from "./BookingCalendar.svelte";
Expand Down Expand Up @@ -60,6 +60,7 @@
<StatusComponent
bind:bookingRequest
bind:bookingRequests={data.bookingRequests}
class="flex-col"
/>
</td>
<td>
Expand Down
73 changes: 63 additions & 10 deletions src/routes/(app)/booking/BookingEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
import type { BookingSchema } from "./schema";
import { superForm } from "$lib/utils/client/superForms";
import * as m from "$paraglide/messages";
import type { Bookable } from "@prisma/client";
import type { Bookable, BookingRequest } from "@prisma/client";
import StatusComponent from "./StatusComponent.svelte";
type BookingRequestWithBookables = BookingRequest & { bookables: Bookable[] };
export let data: {
form: SuperValidated<Infer<BookingSchema>>;
bookables: Bookable[];
booking?: BookingRequestWithBookables;
allBookingRequests?: BookingRequestWithBookables[];
};
$: bookingRequest = data.booking;
const { form, errors, enhance, constraints } = superForm(data.form);
export let mode: "create" | "edit" = "create";
export let mode: "create" | "edit" | "review" = "create";
let start = $form.start;
let end = $form.end;
Expand Down Expand Up @@ -60,6 +66,23 @@
</script>

<form method="POST" use:enhance class="form-control mx-auto max-w-5xl gap-4">
{#if mode === "review"}
<div class="flex flex-col gap-5">
<div class="w-fit">
<a class="btn" href="/booking/admin">
<span class="i-mdi-arrow-expand-left" />
{m.booking_goBack()}
</a>
</div>
{#if bookingRequest && data.allBookingRequests}
<StatusComponent
bind:bookingRequest
bind:bookingRequests={data.allBookingRequests}
class="flex-row"
/>
{/if}
</div>
{/if}
<fieldset
class="input-bordered grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] rounded-xl border px-6 py-2"
class:border-error={$errors.bookables?._errors ?? 0 > 0}
Expand All @@ -73,6 +96,7 @@
name="bookables"
value={bookable.id}
bind:group={$form.bookables}
disabled={mode === "review"}
/>
<span class="label-text">{bookable.name}</span>
</label>
Expand All @@ -89,6 +113,7 @@
bind:value={start}
on:change={handleStartChange}
{...$constraints.start}
disabled={mode === "review"}
/>
</label>

Expand All @@ -103,6 +128,7 @@
bind:value={end}
on:change={handleEndChange}
{...$constraints.end}
disabled={mode === "review"}
/>
</label>

Expand All @@ -114,15 +140,42 @@
class="input input-bordered w-full"
bind:value={$form.name}
{...$constraints.name}
disabled={mode === "review"}
/>
</label>

<div class="flex *:flex-1">
<a class="btn" href="/booking">{m.booking_goBack()}</a>
{#if mode === "edit"}
<button class="btn btn-primary">{m.save()}</button>
{:else if mode === "create"}
<button class="btn btn-primary">{m.booking_create()}</button>
{/if}
</div>
{#if mode === "review" && bookingRequest}
<div class="flex *:flex-1">
<input hidden name="id" type="text" bind:value={bookingRequest.id} />
<button
formaction="?/accept"
class="btn btn-outline btn-success"
class:btn-disabled={bookingRequest.status === "ACCEPTED"}
aria-label={m.booking_accept()}
>
{m.booking_accept()}
<span class="i-mdi-check" />
</button>
<button
formaction="?/reject"
class="btn btn-outline btn-error"
class:btn-disabled={bookingRequest.status === "DENIED"}
aria-label={m.booking_deny()}
>
{m.booking_deny()}
<span class="i-mdi-close" />
</button>
</div>
{/if}

{#if mode !== "review"}
<div class="flex *:flex-1">
<a class="btn" href="/booking">{m.booking_goBack()}</a>
{#if mode === "edit"}
<button class="btn btn-primary">{m.save()}</button>
{:else if mode === "create"}
<button class="btn btn-primary">{m.booking_create()}</button>
{/if}
</div>
{/if}
</form>
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
import type { Bookable, BookingRequest } from "@prisma/client";
import dayjs from "dayjs";
import * as m from "$paraglide/messages";
import { twMerge } from "tailwind-merge";
type T = BookingRequest & { bookables: Bookable[] };
export let bookingRequest: T;
export let bookingRequests: T[];
let clazz: string | undefined = undefined;
export { clazz as class };
$: otherBookingRequests = bookingRequests.filter(
(br) => br.id !== bookingRequest.id && br.status !== "DENIED",
);
Expand All @@ -29,7 +33,7 @@
$: conflictWarning = conflict && !conflictError;
</script>

<div class="flex flex-col gap-1">
<div class={twMerge("flex gap-1", clazz)}>
{#if bookingRequest.status === "ACCEPTED"}
<div class="badge badge-success">
<span class="i-mdi-check-circle mr-1" />{m.booking_accepted()}
Expand Down
25 changes: 3 additions & 22 deletions src/routes/(app)/booking/[id]/edit/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,14 @@ import { redirect } from "$lib/utils/redirect";
import * as m from "$paraglide/messages";
import { isAuthorized } from "$lib/utils/authorization";
import apiNames from "$lib/utils/apiNames";
import { error } from "@sveltejs/kit";
import dayjs from "dayjs";
import { getBookingRequestOrThrow, getSuperValidatedForm } from "../../utils";

export const load = async ({ locals, params }) => {
const { prisma } = locals;
const bookables = await prisma.bookable.findMany();

const bookingRequest = await prisma.bookingRequest.findUnique({
where: { id: params.id },
include: { bookables: true },
});

if (!bookingRequest) {
throw error(404, m.booking_errors_notFound());
}

const initialData = {
name: bookingRequest.event ?? undefined,
start: bookingRequest.start
? dayjs(bookingRequest.start).format("YYYY-MM-DDTHH:MM")
: undefined,
end: bookingRequest.end
? dayjs(bookingRequest.end).format("YYYY-MM-DDTHH:MM")
: undefined,
bookables: bookingRequest.bookables?.map((bookable) => bookable.id),
};
const form = await superValidate(initialData, zod(bookingSchema));
const bookingRequest = await getBookingRequestOrThrow(prisma, params.id);
const form = await getSuperValidatedForm(bookingRequest);

return { bookables, form, booking: bookingRequest };
};
Expand Down
89 changes: 5 additions & 84 deletions src/routes/(app)/booking/admin/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,15 @@
import apiNames from "$lib/utils/apiNames";
import { authorize } from "$lib/utils/authorization";
import sendNotification from "$lib/utils/notifications";
import { NotificationType } from "$lib/utils/notifications/types";
import dayjs from "dayjs";
import type { Actions, PageServerLoad } from "./$types";
import type { PageServerLoad } from "./$types";
import { actions, getUpcomingBookingRequests } from "../utils";

export const load: PageServerLoad = async ({ locals }) => {
const { prisma, user } = locals;
authorize(apiNames.BOOKINGS.UPDATE, user);

const bookingRequests = await prisma.bookingRequest.findMany({
where: {
start: {
gte: dayjs().subtract(1, "week").toDate(),
},
},
orderBy: [{ start: "asc" }, { end: "asc" }, { status: "asc" }],
include: {
bookables: true,
booker: true,
},
});
const bookingRequests = await getUpcomingBookingRequests(prisma);

return { bookingRequests };
};

export const actions: Actions = {
accept: async (event) => {
const { request, locals } = event;
const { prisma, user } = locals;
const formData = await request.formData();
const id = formData.get("id");
if (id && typeof id === "string") {
await prisma.bookingRequest.update({
where: { id },
data: {
status: "ACCEPTED",
},
});
const request = await prisma.bookingRequest.findFirst({
where: {
id,
},
select: {
bookerId: true,
event: true,
},
});
if (request && request.bookerId != null && user && user.memberId) {
sendNotification({
title: "Booking request accepted",
message: `Your booking request for ${request.event} has been accepted`,
type: NotificationType.BOOKING_REQUEST,
link: "/booking",
memberIds: [request.bookerId],
fromMemberId: user.memberId,
});
}
}
},
reject: async (event) => {
const { request, locals } = event;
const { prisma, user } = locals;
const formData = await request.formData();
const id = formData.get("id");
if (id && typeof id === "string") {
await prisma.bookingRequest.update({
where: { id },
data: {
status: "DENIED",
},
});
const request = await prisma.bookingRequest.findFirst({
where: {
id,
},
select: {
bookerId: true,
event: true,
},
});
if (request && request.bookerId != null && user && user.memberId) {
sendNotification({
title: "Booking request denied",
message: `Your booking request for ${request.event} has been denied`,
type: NotificationType.BOOKING_REQUEST,
link: "/booking",
memberIds: [request.bookerId],
fromMemberId: user.memberId,
});
}
}
},
};
export { actions };
16 changes: 12 additions & 4 deletions src/routes/(app)/booking/admin/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import dayjs from "dayjs";
import StatusComponent from "./StatusComponent.svelte";
import StatusComponent from "../StatusComponent.svelte";
import { enhance } from "$app/forms";
import { getFullName } from "$lib/utils/client/member";
import MemberAvatar from "$lib/components/socials/MemberAvatar.svelte";
Expand Down Expand Up @@ -48,9 +48,16 @@
<p class="min-w-max">{bookable}</p>
{/each}
</td>
<td>{dayjs(bookingRequest.start).format("YYYY-MM-DD HH:MM")}</td>
<td>{dayjs(bookingRequest.end).format("YYYY-MM-DD HH:MM")}</td>
<td>{bookingRequest.event}</td>
<td>{dayjs(bookingRequest.start).format("YYYY-MM-DD HH:mm")}</td>
<td>{dayjs(bookingRequest.end).format("YYYY-MM-DD HH:mm")}</td>
<td>
<a
href="/booking/admin/{bookingRequest.id}"
class="link-hover link"
>
{bookingRequest.event}
</a>
</td>
<td>
<div class="flex items-center gap-2">
{#if bookingRequest.booker}
Expand All @@ -70,6 +77,7 @@
<StatusComponent
bind:bookingRequest
bind:bookingRequests={data.bookingRequests}
class="flex-col"
/>
</td>
<td>
Expand Down
40 changes: 40 additions & 0 deletions src/routes/(app)/booking/admin/[id]/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { authorize } from "$lib/utils/authorization";
import apiNames from "$lib/utils/apiNames";
import dayjs from "dayjs";
import {
actions,
getUpcomingBookingRequests,
getBookingRequestOrThrow,
getSuperValidatedForm,
} from "../../utils";

export const load = async ({ locals, params }) => {
const { prisma, user } = locals;
authorize(apiNames.BOOKINGS.UPDATE, user);
const bookables = await prisma.bookable.findMany();

const allBookingRequests = await prisma.bookingRequest.findMany({
where: {
start: {
gte: dayjs().subtract(1, "week").toDate(),
},
},
orderBy: [{ start: "asc" }, { end: "asc" }, { status: "asc" }],
include: {
bookables: true,
},
});

const bookingRequest = await getBookingRequestOrThrow(prisma, params.id);
const form = await getSuperValidatedForm(bookingRequest);

return {
bookables,
form,
booking: bookingRequest,
allBookingRequests,
bookingRequests: await getUpcomingBookingRequests(prisma),
};
};

export { actions };
15 changes: 15 additions & 0 deletions src/routes/(app)/booking/admin/[id]/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import type { PageData } from "./$types";
import SetPageTitle from "$lib/components/nav/SetPageTitle.svelte";
import BookingCalendar from "../../BookingCalendar.svelte";
import BookingEditor from "../../BookingEditor.svelte";
import * as m from "$paraglide/messages";
export let data: PageData;
</script>

<SetPageTitle title={m.booking_reviewBooking()} />

<BookingEditor {data} mode="review" />

<BookingCalendar {...data} class="mt-16" />
Loading

0 comments on commit 9a7fbd3

Please sign in to comment.