Skip to content

Commit

Permalink
add functionality to booking creation popup and integrate into page (#…
Browse files Browse the repository at this point in the history
…501)

* integrate modal and lazy load protected pages

* include booking slots in component

* change conditional

* update placeholder

* revert lazy changes

* set up code for handling functionality

* fix endpoint

* working implementation

* memoize and add proper handler
  • Loading branch information
choden-dev authored Jun 26, 2024
1 parent 78112bb commit 5576133
Show file tree
Hide file tree
Showing 15 changed files with 467 additions and 184 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,44 @@ import { CombinedUserData } from "models/User"
import Calendar from "components/generic/Calendar/Calendar"
import Button from "components/generic/FigmaButtons/FigmaButton"
import DateRangePicker from "components/generic/DateRangePicker/DateRangePicker"
import { useState, useMemo } from "react"
import { useState, useMemo, useRef } from "react"
import CloseButton from "assets/icons/x.svg?react"
import LeftArrowButton from "assets/icons/leftarrow.svg?react"
import Tick from "assets/icons/tick.svg?react"
import { NEXT_YEAR_FROM_TODAY, TODAY } from "utils/Constants"
import { DateRange, DateUtils } from "components/utils/DateUtils"
import { BookingAvailability } from "models/Booking"
import { Timestamp } from "firebase/firestore"
import { useClickOutside } from "components/utils/Utils"

interface IAdminBookingCreationPopUp {
bookingCreationHandler?: () => void
/**
* Performs the required mutation to add the user(s) to the bookings within date range.
*/
bookingCreationHandler?: (
startDate: Timestamp,
endDate: Timestamp,
selectedUserUid: string
) => void

/**
* Callback for when a 'close' event is triggered with the modal open
*/
handleClose?: () => void

/**
* When the user is in process of being added to avoid extra calls
*/
isPending?: boolean

/**
* The "unfiltered" booking slots for processing
*/
bookingSlots?: BookingAvailability[]

/**
* Users to display during a search
*/
users?: CombinedUserData[]
}

Expand All @@ -24,14 +51,57 @@ enum FlowStages {

const Divider = () => {
return (
<div className="bg-gray-3 ml-auto hidden h-screen w-[1px] sm:block"></div>
<div className="bg-gray-3 ml-auto hidden h-full w-[1px] md:block"></div>
)
}

const AdminBookingCreationPopUp = ({
handleClose,
users = []
bookingCreationHandler,
bookingSlots = [],
users = [],
isPending
}: IAdminBookingCreationPopUp) => {
const containerRef = useRef<HTMLDivElement>(null)
useClickOutside(containerRef, () => handleClose?.())

const [selectedDateRange, setSelectedDateRange] = useState<DateRange>({
startDate: new Date(),
endDate: new Date()
})

const { startDate: currentStartDate, endDate: currentEndDate } =
selectedDateRange

const disabledDates = DateUtils.unavailableDates(bookingSlots)

/**
* Function to be called to confirm the date range selected by the user.
*
* Will notify user if an unavailable date was included in the new date range
*
* @param startDate the first date of the range
* @param endDate the last date of the range
*/
const checkValidRange = (startDate: Date, endDate: Date) => {
const dateArray = DateUtils.datesToDateRange(startDate, endDate)
if (
dateArray.some(
(date) =>
disabledDates.some((disabledDate) =>
DateUtils.dateEqualToTimestamp(date, disabledDate.date)
) ||
!bookingSlots.some((slot) =>
DateUtils.dateEqualToTimestamp(date, slot.date)
)
)
) {
alert("Invalid date range, some dates are unavailable")
return false
}
return true
}

const [currentSearchQuery, setCurrentSearchQuery] = useState<
string | undefined
>(undefined)
Expand Down Expand Up @@ -61,8 +131,9 @@ const AdminBookingCreationPopUp = ({
if (currentSearchQuery) {
return users.filter(
(user) =>
user.email.toLowerCase().includes(currentSearchQuery) ||
user.first_name.toLowerCase().includes(currentSearchQuery) ||
(user.email.toLowerCase().includes(currentSearchQuery) ||
user.first_name.toLowerCase().includes(currentSearchQuery) ||
user.last_name.toLowerCase().includes(currentSearchQuery)) &&
user.membership !== "admin"
)
} else {
Expand Down Expand Up @@ -104,52 +175,63 @@ const AdminBookingCreationPopUp = ({
[usersToDisplay]
)

const DetailedUserInfoPanel = () => (
<div className="border-gray-3 mt-4 flex flex-col gap-3 rounded-sm border px-4 py-3">
<span className="flex">
<h5 className="font-bold uppercase">
{currentlySelectedUser?.membership}
</h5>
<div
onClick={() => setCurrentSelectedUserUid(undefined)}
className="ml-auto h-[15px] w-[15px] cursor-pointer"
>
<CloseButton />
const DetailedUserInfoPanel = useMemo(
() => (
<div className="border-gray-3 mt-4 flex flex-col gap-3 rounded-sm border px-4 py-3">
<span className="flex">
<h5 className="font-bold uppercase">
{currentlySelectedUser?.membership}
</h5>
<div
onClick={() => setCurrentSelectedUserUid(undefined)}
className="ml-auto h-[15px] w-[15px] cursor-pointer"
>
<CloseButton />
</div>
</span>
<p>
{currentlySelectedUser?.first_name} {currentlySelectedUser?.last_name}
</p>
<div className="flex flex-col">
<p className="text-gray-3">Allergies/Dietary Requirements</p>
<p>{currentlySelectedUser?.dietary_requirements}</p>
</div>
</span>
<p>
{currentlySelectedUser?.first_name} {currentlySelectedUser?.last_name}
</p>
<div className="flex flex-col">
<p className="text-gray-3">Allergies/Dietary Requirements</p>
<p>{currentlySelectedUser?.dietary_requirements}</p>
</div>

<div className="flex flex-col">
<p className="text-gray-3">Email</p>
<p> {currentlySelectedUser?.email}</p>
</div>
<div className="flex flex-col">
<p className="text-gray-3">Number</p>
<p> {currentlySelectedUser?.phone_number}</p>
<div className="flex flex-col">
<p className="text-gray-3">Email</p>
<p> {currentlySelectedUser?.email}</p>
</div>
<div className="flex flex-col">
<p className="text-gray-3">Number</p>
<p> {currentlySelectedUser?.phone_number}</p>
</div>
</div>
</div>
),
[currentlySelectedUser]
)

return (
<div className="flex w-full max-w-[820px] flex-col gap-8">
<div
className="flex h-full w-full max-w-[820px] flex-col gap-8 bg-white px-8 py-8"
ref={containerRef}
>
<span className="flex justify-between">
<h3 className="text-dark-blue-100">Add a booking</h3>
<div className="h-[15px] w-[15px] cursor-pointer" onClick={handleClose}>
<CloseButton />
</div>
</span>
<div className="flex w-full flex-col gap-7 sm:flex-row">
<div className="flex w-full flex-col sm:basis-1/2">
<p className="opacity-20">Select user</p>
<AdminSearchBar
onQueryChanged={(newQuery: string) => onQueryChanged(newQuery)}
/>
<div className="flex h-full w-full flex-col gap-7 md:flex-row">
<div className="flex w-full flex-col md:basis-1/2">
<span
className={`${currentStage === FlowStages.SELECT_DATES && "pointer-events-none blur-sm"}`}
>
<p className="opacity-20">Select user</p>
<AdminSearchBar
placeholder="Search for name or email"
onQueryChanged={(newQuery: string) => onQueryChanged(newQuery)}
/>
</span>
{currentStage === FlowStages.SELECT_DATES && (
<div>
<p className="mt-8">Creating booking for:</p>
Expand All @@ -174,12 +256,11 @@ const AdminBookingCreationPopUp = ({
</div>
)}

{currentStage !==
FlowStages.SEARCH_FOR_USER ? null : currentSelectedUserUid ? (
<DetailedUserInfoPanel />
) : (
UserList
)}
{currentStage !== FlowStages.SEARCH_FOR_USER
? null
: currentSelectedUserUid
? DetailedUserInfoPanel
: UserList}
<span className="mt-8">
{currentStage === FlowStages.SELECT_DATES ? (
<button
Expand Down Expand Up @@ -208,15 +289,102 @@ const AdminBookingCreationPopUp = ({
</span>
</div>
<Divider />
<div className="flex sm:basis-1/2">
<div
className={`flex md:basis-1/2 ${currentStage === FlowStages.SELECT_DATES ? "pointer-events-auto" : "pointer-events-none blur-sm"}`}
>
<div className="w-full max-w-[380px]">
<Calendar />
<Calendar
minDate={TODAY}
minDetail="year"
maxDetail="month"
maxDate={NEXT_YEAR_FROM_TODAY}
selectRange
value={
currentStartDate && currentEndDate
? [currentStartDate, currentEndDate]
: undefined
}
tileDisabled={({ date, view }) =>
view !== "year" &&
(!bookingSlots.some((slot) =>
DateUtils.UTCDatesEqual(slot.date, date)
) ||
disabledDates.some((slot) =>
DateUtils.UTCDatesEqual(slot.date, date)
))
}
tileContent={({ date }) => {
const slot = bookingSlots.find(
(slot) =>
DateUtils.UTCDatesEqual(slot.date, date) &&
slot.availableSpaces > 0
)
return slot ? (
<p className="text-xs">
{slot?.availableSpaces}/{slot.maxBookings}
</p>
) : null
}}
onChange={(e) => {
const [start, end] = e as [Date, Date]
if (
checkValidRange(
DateUtils.convertLocalDateToUTCDate(start),
DateUtils.convertLocalDateToUTCDate(end)
)
) {
setSelectedDateRange({
startDate: start,
endDate: end
})
}
}}
returnValue="range"
/>
<DateRangePicker
valueStart={new Date()}
valueEnd={new Date()}
handleDateRangeInputChange={() => {}}
valueStart={selectedDateRange.startDate}
valueEnd={selectedDateRange.endDate}
handleDateRangeInputChange={(start, end) => {
const newStartDate = start || currentStartDate
const newEndDate = end || currentEndDate
if (
checkValidRange(
DateUtils.convertLocalDateToUTCDate(newStartDate),
DateUtils.convertLocalDateToUTCDate(newEndDate)
)
) {
setSelectedDateRange({
startDate: newStartDate,
endDate: newEndDate
})
}
}}
/>
<Button disabled={currentStage !== FlowStages.SELECT_DATES}>
<Button
disabled={currentStage !== FlowStages.SELECT_DATES || isPending}
onClick={() => {
if (
checkValidRange(
DateUtils.convertLocalDateToUTCDate(currentStartDate),
DateUtils.convertLocalDateToUTCDate(currentEndDate)
) &&
currentSelectedUserUid
) {
confirm(`Are you sure you want to add
${currentlySelectedUser?.first_name} ${currentlySelectedUser?.last_name}
to the dates ${selectedDateRange.startDate.toDateString()} - ${selectedDateRange.endDate.toDateString()}`) &&
bookingCreationHandler?.(
Timestamp.fromDate(
DateUtils.convertLocalDateToUTCDate(currentStartDate)
),
Timestamp.fromDate(
DateUtils.convertLocalDateToUTCDate(currentEndDate)
),
currentSelectedUserUid
)
}
}}
>
Add New Booking
</Button>
</div>
Expand Down
Loading

0 comments on commit 5576133

Please sign in to comment.