From 40059676002c54e3024a3ff72322edba20b4b666 Mon Sep 17 00:00:00 2001 From: Artem Bulgakov Date: Thu, 17 Oct 2024 01:19:25 +0300 Subject: [PATCH] feat(room-booking): add modal for viewing details of clicked booking --- .../room-booking/RoomBookingPage.tsx | 15 +- .../room-booking/ViewBookingModal.tsx | 145 ++++++++++++++++++ 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/components/room-booking/ViewBookingModal.tsx diff --git a/src/components/room-booking/RoomBookingPage.tsx b/src/components/room-booking/RoomBookingPage.tsx index 326da84..7773499 100644 --- a/src/components/room-booking/RoomBookingPage.tsx +++ b/src/components/room-booking/RoomBookingPage.tsx @@ -1,6 +1,7 @@ import { BookModal } from "@/components/room-booking/BookModal.tsx"; +import { ViewBookingModal } from "@/components/room-booking/ViewBookingModal.tsx"; import { lazy, Suspense, useState } from "react"; -import type { Slot } from "./BookingTimeline.vue"; +import type { Booking, Slot } from "./BookingTimeline.vue"; const BookingTimeline = lazy( () => import("@/components/room-booking/BookingTimeline.tsx"), @@ -9,6 +10,9 @@ const BookingTimeline = lazy( export function RoomBookingPage() { const [bookingModalData, setBookingModalData] = useState(); const [modalOpen, setModalOpen] = useState(false); + const [viewBookingModalData, setViewBookingModalData] = useState(); + const [viewModalOpen, setViewModalOpen] = useState(false); + return ( <> @@ -18,6 +22,10 @@ export function RoomBookingPage() { setBookingModalData(newBooking); setModalOpen(true); }} + onBookingClick={(booking: Booking) => { + setViewBookingModalData(booking); + setViewModalOpen(true); + }} /> + ); } diff --git a/src/components/room-booking/ViewBookingModal.tsx b/src/components/room-booking/ViewBookingModal.tsx new file mode 100644 index 0000000..3602250 --- /dev/null +++ b/src/components/room-booking/ViewBookingModal.tsx @@ -0,0 +1,145 @@ +import { $roomBooking } from "@/api/room-booking"; +import { + FloatingFocusManager, + FloatingOverlay, + FloatingPortal, + useDismiss, + useFloating, + useInteractions, + useRole, + useTransitionStyles, +} from "@floating-ui/react"; +import type { Booking } from "./BookingTimeline.vue"; + +function durationFormatted(durationMs: number): string { + const hours = Math.floor(durationMs / (3600 * 1000)); + const minutes = Math.floor((durationMs % (3600 * 1000)) / (60 * 1000)); + return `${hours > 0 ? `${hours}h ` : ""}${minutes > 0 ? `${minutes}m` : ""}`.trim(); +} + +function sanitizeTitle(title: string | undefined): string { + if (!title) return ""; + return title.replace("Students Booking Service", "").trim(); +} + +export function ViewBookingModal({ + data, + open, + onOpenChange, +}: { + data?: Booking; + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const { context, refs } = useFloating({ open, onOpenChange }); + // Transition effect + const { isMounted, styles: transitionStyles } = useTransitionStyles(context); + // Event listeners to change the open state + const dismiss = useDismiss(context, { outsidePressEvent: "mousedown" }); + // Role props for screen readers + const role = useRole(context); + const { getFloatingProps } = useInteractions([dismiss, role]); + + const { data: rooms } = $roomBooking.useQuery("get", "/rooms/"); + + const room = rooms?.find((room) => room.id === data?.room_id); + + if (!isMounted) { + return null; + } + + return ( + + + +
+
+
+ {/* Heading and description */} +
+
+ Booking details +
+ +
+ +
+
+
+ +
+

+ {sanitizeTitle(data?.title)} +

+
+ +
+
+ +
+

+ {room?.title} +

+
+ +
+
+ +
+

+ {data?.startsAt.toLocaleString("en-US", { + day: "2-digit", + month: "long", + })} + ,{" "} + {data?.startsAt.toLocaleString("en-US", { + weekday: "long", + })} +

+
+ +
+
+ +
+ {data && ( +

+ {data.startsAt.toLocaleString("ru-RU", { + hour: "2-digit", + minute: "2-digit", + }) + + " - " + + data.endsAt.toLocaleString("ru-RU", { + hour: "2-digit", + minute: "2-digit", + }) + + " (" + + durationFormatted( + data.endsAt.getTime() - data.startsAt.getTime(), + ) + + ")"} +

+ )} +
+
+
+
+
+
+
+
+ ); +}