import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid"; import { EventType, PeriodType } from "@prisma/client"; import dayjs, { Dayjs } from "dayjs"; import dayjsBusinessTime from "dayjs-business-time"; import timezone from "dayjs/plugin/timezone"; import utc from "dayjs/plugin/utc"; import { useEffect, useMemo, useState } from "react"; import classNames from "@lib/classNames"; import { timeZone } from "@lib/clock"; import { weekdayNames } from "@lib/core/i18n/weekday"; import { useLocale } from "@lib/hooks/useLocale"; import getSlots from "@lib/slots"; import { WorkingHours } from "@lib/types/schedule"; import Loader from "@components/Loader"; dayjs.extend(dayjsBusinessTime); dayjs.extend(utc); dayjs.extend(timezone); type DatePickerProps = { weekStart: string; onDatePicked: (pickedDate: Dayjs) => void; workingHours: WorkingHours[]; eventLength: number; date: Dayjs | null; periodType: PeriodType; periodStartDate: Date | null; periodEndDate: Date | null; periodDays: number | null; periodCountCalendarDays: boolean | null; minimumBookingNotice: number; }; function isOutOfBounds( time: dayjs.ConfigType, { periodType, periodDays, periodCountCalendarDays, periodStartDate, periodEndDate, }: Pick< EventType, "periodType" | "periodDays" | "periodCountCalendarDays" | "periodStartDate" | "periodEndDate" > ) { const date = dayjs(time); switch (periodType) { case PeriodType.ROLLING: { const periodRollingEndDay = periodCountCalendarDays ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion dayjs().utcOffset(date.utcOffset()).add(periodDays!, "days").endOf("day") : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion dayjs().utcOffset(date.utcOffset()).addBusinessTime(periodDays!, "days").endOf("day"); return date.endOf("day").isAfter(periodRollingEndDay); } case PeriodType.RANGE: { const periodRangeStartDay = dayjs(periodStartDate).utcOffset(date.utcOffset()).endOf("day"); const periodRangeEndDay = dayjs(periodEndDate).utcOffset(date.utcOffset()).endOf("day"); return date.endOf("day").isBefore(periodRangeStartDay) || date.endOf("day").isAfter(periodRangeEndDay); } case PeriodType.UNLIMITED: default: return false; } } function DatePicker({ weekStart, onDatePicked, workingHours, eventLength, date, periodType = PeriodType.UNLIMITED, periodStartDate, periodEndDate, periodDays, periodCountCalendarDays, minimumBookingNotice, }: DatePickerProps): JSX.Element { const { i18n } = useLocale(); const [browsingDate, setBrowsingDate] = useState(date); const [month, setMonth] = useState(""); const [year, setYear] = useState(""); const [isFirstMonth, setIsFirstMonth] = useState(false); useEffect(() => { if (!browsingDate || (date && browsingDate.utcOffset() !== date?.utcOffset())) { setBrowsingDate(date || dayjs().tz(timeZone())); } }, [date, browsingDate]); useEffect(() => { if (browsingDate) { setMonth(browsingDate.toDate().toLocaleString(i18n.language, { month: "long" })); setYear(browsingDate.format("YYYY")); setIsFirstMonth(browsingDate.startOf("month").isBefore(dayjs())); } }, [browsingDate, i18n.language]); const days = useMemo(() => { if (!browsingDate) { return []; } // Create placeholder elements for empty days in first week let weekdayOfFirst = browsingDate.date(1).day(); if (weekStart === "Monday") { weekdayOfFirst -= 1; if (weekdayOfFirst < 0) weekdayOfFirst = 6; } const days = Array(weekdayOfFirst).fill(null); const isDisabled = (day: number) => { const date = browsingDate.startOf("day").date(day); return ( isOutOfBounds(date, { periodType, periodStartDate, periodEndDate, periodCountCalendarDays, periodDays, }) || !getSlots({ inviteeDate: date, frequency: eventLength, minimumBookingNotice, workingHours, }).length ); }; const daysInMonth = browsingDate.daysInMonth(); for (let i = 1; i <= daysInMonth; i++) { days.push({ disabled: isDisabled(i), date: i }); } return days; // eslint-disable-next-line react-hooks/exhaustive-deps }, [browsingDate]); if (!browsingDate) { return ; } // Handle month changes const incrementMonth = () => { setBrowsingDate(browsingDate?.add(1, "month")); }; const decrementMonth = () => { setBrowsingDate(browsingDate?.subtract(1, "month")); }; return (
{month}{" "} {year}
{weekdayNames(i18n.language, weekStart === "Sunday" ? 0 : 1, "short").map((weekDay) => (
{weekDay}
))}
{days.map((day, idx) => (
{day === null ? (
) : ( )}
))}
); } export default DatePicker;