// We do not need to worry about importing framer-motion here as it is lazy imported in Booker. import * as HoverCard from "@radix-ui/react-hover-card"; import { AnimatePresence, m } from "framer-motion"; import { CalendarX2, ChevronRight } from "lucide-react"; import { useCallback, useState } from "react"; import dayjs from "@calcom/dayjs"; import type { Slots } from "@calcom/features/schedules"; import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Button, SkeletonText } from "@calcom/ui"; import { useBookerStore } from "../Booker/store"; import { useEvent } from "../Booker/utils/event"; import { getQueryParam } from "../Booker/utils/query-param"; import { useTimePreferences } from "../lib"; import { useCheckOverlapWithOverlay } from "../lib/useCheckOverlapWithOverlay"; import { SeatsAvailabilityText } from "./SeatsAvailabilityText"; type TOnTimeSelect = ( time: string, attendees: number, seatsPerTimeSlot?: number | null, bookingUid?: string ) => void; type AvailableTimesProps = { slots: Slots[string]; onTimeSelect: TOnTimeSelect; seatsPerTimeSlot?: number | null; showAvailableSeatsCount?: boolean | null; showTimeFormatToggle?: boolean; className?: string; selectedSlots?: string[]; }; const SlotItem = ({ slot, seatsPerTimeSlot, selectedSlots, onTimeSelect, showAvailableSeatsCount, }: { slot: Slots[string][number]; seatsPerTimeSlot?: number | null; selectedSlots?: string[]; onTimeSelect: TOnTimeSelect; showAvailableSeatsCount?: boolean | null; }) => { const { t } = useLocale(); const overlayCalendarToggled = getQueryParam("overlayCalendar") === "true" || localStorage.getItem("overlayCalendarSwitchDefault"); const [timeFormat, timezone] = useTimePreferences((state) => [state.timeFormat, state.timezone]); const bookingData = useBookerStore((state) => state.bookingData); const layout = useBookerStore((state) => state.layout); const { data: event } = useEvent(); const hasTimeSlots = !!seatsPerTimeSlot; const computedDateWithUsersTimezone = dayjs.utc(slot.time).tz(timezone); const bookingFull = !!(hasTimeSlots && slot.attendees && slot.attendees >= seatsPerTimeSlot); const isHalfFull = slot.attendees && seatsPerTimeSlot && slot.attendees / seatsPerTimeSlot >= 0.5; const isNearlyFull = slot.attendees && seatsPerTimeSlot && slot.attendees / seatsPerTimeSlot >= 0.83; const colorClass = isNearlyFull ? "bg-rose-600" : isHalfFull ? "bg-yellow-500" : "bg-emerald-400"; const nowDate = dayjs(); const usersTimezoneDate = nowDate.tz(timezone); const offset = (usersTimezoneDate.utcOffset() - nowDate.utcOffset()) / 60; const { isOverlapping, overlappingTimeEnd, overlappingTimeStart } = useCheckOverlapWithOverlay({ start: computedDateWithUsersTimezone, selectedDuration: event?.length ?? 0, offset, }); const [overlapConfirm, setOverlapConfirm] = useState(false); const onButtonClick = useCallback(() => { if (!overlayCalendarToggled) { onTimeSelect(slot.time, slot?.attendees || 0, seatsPerTimeSlot, slot.bookingUid); return; } if (isOverlapping && overlapConfirm) { setOverlapConfirm(false); return; } if (isOverlapping && !overlapConfirm) { setOverlapConfirm(true); return; } if (!overlapConfirm) { onTimeSelect(slot.time, slot?.attendees || 0, seatsPerTimeSlot, slot.bookingUid); } }, [ overlayCalendarToggled, isOverlapping, overlapConfirm, onTimeSelect, slot.time, slot?.attendees, slot.bookingUid, seatsPerTimeSlot, ]); return (
{overlapConfirm && isOverlapping && (

Busy

{overlappingTimeStart} - {overlappingTimeEnd}

)}
); }; export const AvailableTimes = ({ slots, onTimeSelect, seatsPerTimeSlot, showAvailableSeatsCount, showTimeFormatToggle = true, className, selectedSlots, }: AvailableTimesProps) => { const { t } = useLocale(); return (
{!slots.length && (

{t("all_booked_today")}

)} {slots.map((slot) => ( ))}
); }; export const AvailableTimesSkeleton = () => (
{/* Random number of elements between 1 and 6. */} {Array.from({ length: Math.floor(Math.random() * 6) + 1 }).map((_, i) => ( ))}
);