Skip to content

Commit

Permalink
Change booking system
Browse files Browse the repository at this point in the history
- Replace calendar with table
- Implement booking deletion
  • Loading branch information
danieladugyan committed Aug 21, 2024
1 parent 366149e commit 98bc1ac
Show file tree
Hide file tree
Showing 10 changed files with 332 additions and 92 deletions.
1 change: 1 addition & 0 deletions src/database/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ model Bookable {
/// @@allow('read', has(auth().policies, 'booking_request:read'))
/// @@allow('update', has(auth().policies, 'booking_request:update'))
/// @@allow('delete', has(auth().policies, 'booking_request:delete'))
/// @@allow('delete', auth().memberId == bookerId)
/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
model BookingRequest {
id String @id() @default(dbgenerated("gen_random_uuid()")) @db.Uuid()
Expand Down
1 change: 1 addition & 0 deletions src/database/schema.zmodel
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ model BookingRequest {
@@allow("read", has(auth().policies, "booking_request:read"))
@@allow("update", has(auth().policies, "booking_request:update"))
@@allow("delete", has(auth().policies, "booking_request:delete"))
@@allow("delete", auth().memberId == bookerId)
@@map("booking_requests")
}

Expand Down
102 changes: 102 additions & 0 deletions src/lib/components/ConfirmDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<!--
@component
This component shows a confirmation dialog with a `title`, `description`,
and two buttons. For a basic HTML dialog form, you can use the `formTarget`
and `formData` props to submit a form with a confirm button.
If you need to customize the dialog, you can use the slots to
provide a custom `title`, `description`, and `action` buttons.
@slot `title`
The title of the dialog. Defaults to `h1`
@slot `description`
The description of the dialog. Defaults to `p`
@slot `action`
The action buttons of the dialog. Defaults to two buttons:
- A cancel button that closes the dialog
- A confirm button in a form that submits the form and closes the dialog
-->
<script lang="ts">
import { enhance } from "$app/forms";
import * as m from "$paraglide/messages";
import { twMerge } from "tailwind-merge";
/** The title of the dialog displayed at the top. */
export let title = "Are you sure?";
/** The description of the dialog displayed below the title. */
export let description = "";
/** The text of the confirm button. */
export let confirmText = m.ok();
/** The text of the cancel button. */
export let cancelText = m.cancel();
/** The target URL for the confirm button. */
export let formTarget: string | undefined = undefined;
/** The form data to submit with the confirm button. */
export let formData: Record<string, string> = {};
/** Not recommended. Prefer using `formTarget` and `formData`. */
export let onClose: (() => void) | undefined = undefined;
/** Not recommended. Prefer using `formTarget` and `formData`. */
export let onConfirm: (() => void) | undefined = undefined;
/** Classes to apply to the confirm button. */
export let confirmClass: string | undefined = undefined;
/** The dialog element. */
export let modal: HTMLDialogElement;
</script>

<dialog bind:this={modal} class="modal modal-bottom sm:modal-middle">
<div class="modal-box">
<slot name="title">
<h1 class="mb-4 text-lg font-bold">{title}</h1>
</slot>

<slot name="description">
<p>{description}</p>
</slot>

<div class="modal-action">
<slot name="action">
<button
class="btn"
on:click={() => {
modal?.close();
onClose?.();
}}
>
{cancelText}
</button>
<form method="POST" action={formTarget} use:enhance>
{#each Object.entries(formData) as [name, value]}
<input type="hidden" {name} {value} />
{/each}
<button
type="submit"
class={twMerge("btn btn-primary", confirmClass)}
on:click={() => {
modal?.close();
onConfirm?.();
}}
>
{confirmText}
</button>
</form>
</slot>
</div>
</div>

<form method="dialog" class="modal-backdrop">
<button class="cursor-auto" />
</form>
</dialog>
7 changes: 5 additions & 2 deletions src/lib/utils/client/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import type {
Position,
} from "@prisma/client";
export type MemberNames = Pick<Member, "firstName" | "nickname" | "lastName">;
export const getFullName = (member: MemberNames) => {
if (member.nickname) {
type Options = {
hideNickname?: boolean;
};
export const getFullName = (member: MemberNames, options: Options = {}) => {
if (member.nickname && !options.hideNickname) {
if (member.firstName && member.lastName)
return `${member.firstName} "${member.nickname}" ${member.lastName}`;
return `"${member.nickname}"`;
Expand Down
50 changes: 48 additions & 2 deletions src/routes/(app)/booking/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,56 @@
export const load = async ({ locals }) => {
const { prisma } = locals;
import apiNames from "$lib/utils/apiNames";
import * as m from "$paraglide/messages";
import { isAuthorized } from "$lib/utils/authorization";
import { redirect } from "$lib/utils/redirect";
import { error } from "@sveltejs/kit";

export const load = async (event) => {
const { prisma, user } = event.locals;
const bookingRequests = await prisma.bookingRequest.findMany({
where: {
bookerId: user.memberId,
},
include: {
bookables: true,
},
});

if (bookingRequests.length === 0) {
const isAdmin = isAuthorized(apiNames.BOOKINGS.UPDATE, user);
if (isAdmin) {
return redirect(
"/booking/admin",
{
message: m.booking_noBookings(),
type: "info",
},
event,
);
}
redirect(
"/booking/create",
{
message: m.booking_noBookings(),
type: "info",
},
event,
);
}

return { bookingRequests };
};

export const actions = {
delete: async ({ request, locals }) => {
const { prisma } = locals;
const formData = await request.formData();
const id = formData.get("id");
if (id && typeof id === "string") {
await prisma.bookingRequest.delete({
where: { id },
});
} else {
error(422, "Invalid booking request id");
}
},
};
97 changes: 95 additions & 2 deletions src/routes/(app)/booking/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,104 @@
<script lang="ts">
import SetPageTitle from "$lib/components/nav/SetPageTitle.svelte";
import Calendar from "./Calendar.svelte";
import * as m from "$paraglide/messages";
import { isAuthorized } from "$lib/utils/authorization";
import { page } from "$app/stores";
import apiNames from "$lib/utils/apiNames";
import StatusComponent from "./admin/StatusComponent.svelte";
import dayjs from "dayjs";
import ConfirmDialog from "$lib/components/ConfirmDialog.svelte";
export let data;
let deleteModal: HTMLDialogElement;
let selectedBooking: (typeof data.bookingRequests)[number] | undefined =
undefined;
</script>

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

<Calendar bookingRequests={data.bookingRequests} />
<div class="mb-8 flex gap-4">
<a class="btn" href="/booking/create">
{m.booking_createBooking()}
</a>

{#if isAuthorized(apiNames.BOOKINGS.UPDATE, $page.data.user)}
<a class="btn" href="/booking/admin">
{m.booking_manageBookings()}
</a>
{/if}
</div>

<div class="overflow-x-auto">
<table class="table">
<thead>
<tr class="bg-base-200">
<th>{m.booking_booking()}</th>
<th>{m.booking_from()}</th>
<th>{m.booking_until()}</th>
<th>{m.booking_event()}</th>
<th>{m.booking_status()}</th>
<th />
</tr>
</thead>

<tbody>
{#each data.bookingRequests as bookingRequest (bookingRequest.id)}
<tr>
<td>
{#each bookingRequest.bookables.map((a) => a.name) as bookable}
<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>
<StatusComponent
bind:bookingRequest
bind:bookingRequests={data.bookingRequests}
/>
</td>
<td>
<div class="form-control gap-2">
<!-- <a
href="/booking/{bookingRequest.id}/edit"
class="btn btn-xs px-8"
>
{m.booking_edit()}
</a> -->
<button
class="btn btn-xs px-8"
on:click={() => {
deleteModal?.showModal();
selectedBooking = bookingRequest;
}}
>
{m.booking_delete()}
</button>
</div>
</td>
</tr>
{/each}
</tbody>
</table>
</div>

<ConfirmDialog
bind:modal={deleteModal}
title={m.booking_deleteTitle()}
confirmText={m.booking_delete()}
confirmClass="btn-error"
formTarget="/booking?/delete"
formData={{ id: selectedBooking?.id ?? "" }}
>
<p slot="description">
{#if selectedBooking}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html m.booking_deleteMyAreYouSure({
bookables: selectedBooking?.bookables
.map(({ name }) => name)
.join(", "),
})}
{/if}
</p>
</ConfirmDialog>
76 changes: 0 additions & 76 deletions src/routes/(app)/booking/Calendar.svelte

This file was deleted.

Loading

0 comments on commit 98bc1ac

Please sign in to comment.