Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(create-booking): create client side modal for new booking #1621

Merged
80 changes: 80 additions & 0 deletions app/components/booking/create-booking-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { cloneElement, useState } from "react";
import type { TeamMember } from "@prisma/client";
import { useLoaderData } from "@remix-run/react";
import { CalendarRangeIcon } from "lucide-react";
import { useSearchParams } from "~/hooks/search-params";
import { getBookingDefaultStartEndTimes } from "~/utils/date-fns";
import { tw } from "~/utils/tw";
import { BookingForm } from "./form";
import { Dialog, DialogPortal } from "../layout/dialog";

type CreateBookingDialogProps = {
className?: string;
trigger: React.ReactElement<{ onClick: () => void }>;
};

export default function CreateBookingDialog({
className,
trigger,
}: CreateBookingDialogProps) {
const { teamMembers, isSelfServiceOrBase } = useLoaderData<{
teamMembers: TeamMember[];
isSelfServiceOrBase: boolean;
}>();

// The loader already takes care of returning only the current user so we just get the first and only element in the array
const custodianRef = isSelfServiceOrBase ? teamMembers[0]?.id : undefined;

const [isDialogOpen, setIsDialogOpen] = useState(false);
const [searchParams] = useSearchParams();

const assetIds = searchParams.getAll("assetId");

const { startDate, endDate } = getBookingDefaultStartEndTimes();

function openDialog() {
setIsDialogOpen(true);
}

function closeDialog() {
setIsDialogOpen(false);
}

return (
<>
{cloneElement(trigger, { onClick: openDialog })}

<DialogPortal>
<Dialog
className={tw("overflow-auto md:h-screen", className)}
open={isDialogOpen}
onClose={closeDialog}
title={
<div className="mt-4 inline-flex items-center justify-center rounded-full border-4 border-solid border-primary-50 bg-primary-100 p-1.5 text-primary">
<CalendarRangeIcon />
</div>
}
>
<div className="px-6 py-4">
<div className="mb-5">
<h4>Create new booking</h4>
<p>
Choose a name for your booking, select a start and end time and
choose the custodian. Based on the selected information, asset
availability will be determined.
</p>
</div>

<BookingForm
startDate={startDate}
endDate={endDate}
assetIds={assetIds}
custodianRef={custodianRef}
action="/bookings/new"
/>
</div>
</Dialog>
</DialogPortal>
</>
);
}
9 changes: 8 additions & 1 deletion app/components/booking/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ type BookingFormData = {
bookingFlags?: BookingFlags;
assetIds?: string[] | null;
description?: string | null;

/**
* In case if the form is rendered outside of /edit or /new booking,
* then we can pass `action` to submit form
*/
action?: string;
};

export function BookingForm({
Expand All @@ -136,6 +142,7 @@ export function BookingForm({
bookingFlags,
assetIds,
description,
action,
}: BookingFormData) {
const navigation = useNavigation();
const { teamMembers } = useLoaderData<typeof loader>();
Expand Down Expand Up @@ -191,7 +198,7 @@ export function BookingForm({

return (
<div>
<Form ref={zo.ref} method="post">
<Form ref={zo.ref} method="post" action={action}>
{/* Hidden input for expired state. Helps is know what status we should set on the server, when the booking is getting checked out */}
{isExpired && <input type="hidden" name="isExpired" value="true" />}

Expand Down
22 changes: 13 additions & 9 deletions app/routes/_layout+/bookings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Link, Outlet, useMatches, useNavigate } from "@remix-run/react";
import { ChevronRight } from "lucide-react";
import { AvailabilityBadge } from "~/components/booking/availability-label";
import BulkActionsDropdown from "~/components/booking/bulk-actions-dropdown";
import CreateBookingDialog from "~/components/booking/create-booking-dialog";
import { StatusFilter } from "~/components/booking/status-filter";
import DynamicDropdown from "~/components/dynamic-dropdown/dynamic-dropdown";
import { ErrorContent } from "~/components/errors";
Expand Down Expand Up @@ -185,6 +186,7 @@ export async function loader({ context, request }: LoaderFunctionArgs) {
perPage,
modelName,
...teamMembersData,
isSelfServiceOrBase,
}),
{
headers: [
Expand Down Expand Up @@ -272,15 +274,17 @@ export default function BookingsIndexPage({
>
{!isChildBookingsPage ? (
<Header>
<Button
to="new"
role="link"
aria-label={`new booking`}
data-test-id="createNewBooking"
prefetch="none"
>
New booking
</Button>
<CreateBookingDialog
trigger={
<Button
aria-label="new booking"
data-test-id="createNewBooking"
prefetch="none"
>
New booking
</Button>
}
/>
</Header>
) : null}

Expand Down