-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Replace calendar with table - Implement booking deletion
- Loading branch information
1 parent
366149e
commit 98bc1ac
Showing
10 changed files
with
332 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.