cal.pub0.org/packages/features/bookings/Booker/components/AvailableTimeSlots.tsx

116 lines
3.8 KiB
TypeScript

import { useMemo, useRef, useEffect } from "react";
import dayjs from "@calcom/dayjs";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import { AvailableTimes, AvailableTimesSkeleton } from "@calcom/features/bookings";
import { useSlotsForMultipleDates } from "@calcom/features/schedules/lib/use-schedule/useSlotsForDate";
import { classNames } from "@calcom/lib";
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
import { useBookerStore } from "../store";
import { useEvent, useScheduleForEvent } from "../utils/event";
type AvailableTimeSlotsProps = {
extraDays?: number;
limitHeight?: boolean;
seatsPerTimeSlot?: number | null;
};
/**
* Renders available time slots for a given date.
* It will extract the date from the booker store.
* Next to that you can also pass in the `extraDays` prop, this
* will also fetch the next `extraDays` days and show multiple days
* in columns next to each other.
*/
export const AvailableTimeSlots = ({ extraDays, limitHeight, seatsPerTimeSlot }: AvailableTimeSlotsProps) => {
const isMobile = useMediaQuery("(max-width: 768px)");
const selectedDate = useBookerStore((state) => state.selectedDate);
const setSelectedTimeslot = useBookerStore((state) => state.setSelectedTimeslot);
const setSeatedEventData = useBookerStore((state) => state.setSeatedEventData);
const isEmbed = useIsEmbed();
const event = useEvent();
const date = selectedDate || dayjs().format("YYYY-MM-DD");
const containerRef = useRef<HTMLDivElement | null>(null);
const onTimeSelect = (
time: string,
attendees: number,
seatsPerTimeSlot?: number | null,
bookingUid?: string
) => {
setSelectedTimeslot(time);
if (seatsPerTimeSlot) {
setSeatedEventData({
seatsPerTimeSlot,
attendees,
bookingUid,
});
if (seatsPerTimeSlot && seatsPerTimeSlot - attendees > 1) {
return;
}
}
if (!event.data) return;
};
const schedule = useScheduleForEvent({
prefetchNextMonth: !!extraDays && dayjs(date).month() !== dayjs(date).add(extraDays, "day").month(),
});
// Creates an array of dates to fetch slots for.
// If `extraDays` is passed in, we will extend the array with the next `extraDays` days.
const dates = useMemo(
() =>
!extraDays
? [date]
: [
// If NO date is selected yet, we show by default the upcomming `nextDays` days.
date,
...Array.from({ length: extraDays }).map((_, index) =>
dayjs(date)
.add(index + 1, "day")
.format("YYYY-MM-DD")
),
],
[date, extraDays]
);
const isMultipleDates = dates.length > 1;
const slotsPerDay = useSlotsForMultipleDates(dates, schedule?.data?.slots);
useEffect(() => {
if (isEmbed) return;
if (containerRef.current && !schedule.isLoading && isMobile) {
containerRef.current.scrollIntoView({ behavior: "smooth", block: "center" });
}
}, [containerRef, schedule.isLoading, isEmbed, isMobile]);
return (
<div
ref={containerRef}
className={classNames(
limitHeight && "flex-grow md:h-[400px]",
!limitHeight && "flex h-full w-full flex-row gap-4"
)}>
{schedule.isLoading
? // Shows exact amount of days as skeleton.
Array.from({ length: 1 + (extraDays ?? 0) }).map((_, i) => <AvailableTimesSkeleton key={i} />)
: slotsPerDay.length > 0 &&
slotsPerDay.map((slots) => (
<AvailableTimes
className="w-full"
key={slots.date}
date={dayjs(slots.date)}
slots={slots.slots}
onTimeSelect={onTimeSelect}
seatsPerTimeSlot={seatsPerTimeSlot}
showTimeFormatToggle={!isMultipleDates}
/>
))}
</div>
);
};