import React, { forwardRef } from 'react' import { addDays, startOfDay } from 'date-fns' import { fi, enUS } from 'date-fns/locale' import { useTranslation } from 'react-i18next' import { DateRange, DayPicker } from 'react-day-picker' import { useMediaQuery } from 'react-responsive' import { CalendarTypes } from './CalendarTypes' import { IconsSvg } from '../../../assets/IconsSvg' import { calendarSelectionRules, checkForContinuousSelection, handleCalendarModifiers, disabledDatesByPage as handleDisabledDatesByPage, handleRangeContextDisabledDates, } from './utils' import { useCalendarLoadingSpinner, useCalendarTooltips, useUpdateDisabledDates, } from './hooks' import { useAutoFocus } from '../../hooks' import 'react-day-picker/dist/style.css' import './Calendar.css' export const Calendar = forwardRef( ( { selectedPath, calendarRange, setCalendarRange, language, disableCalendarDates, requestDates, disabledDates, updateCalendarMonthNavigation, setUpdateCalendarMonthNavigation, updateCalendarDefaultMonth, loadingData, showFeedback, palette, setCalendarHasError, setUpdatedForSubmit, rangeContext, calendarHasError, autoFocus, }: CalendarTypes, ref ) => { // Translations const { t } = useTranslation('common') const calendarContainerRef = useAutoFocus(autoFocus) const isTablet = useMediaQuery({ maxWidth: 960 }) const today = startOfDay(new Date()) const selectedStartDate = calendarRange?.from const rangeContextStartDate = rangeContext?.from // Handle initial disable dates including overlapping availableDates.lastCheckOut and disabledDates.start const { newDisableCalendarDates, overlappingDate, lastPossibleCheckout } = useUpdateDisabledDates({ disableCalendarDates, calendarRange, updateCalendarMonthNavigation, updateCalendarDefaultMonth, }) // Handle disable dates by page const disabledDatesByPage = handleDisabledDatesByPage({ today, selectedPath, newDisableCalendarDates, }) // Handle tooltip useCalendarTooltips({ showFeedback, }) // Handle loading spinner useCalendarLoadingSpinner({ loadingData, }) // Handle the date selection and availability for selection logic. const handleOnSelect = (range?: DateRange) => { setCalendarHasError && calendarHasError && setCalendarHasError(false) calendarSelectionRules({ range, newDisableCalendarDates, setCalendarRange, calendarRange, overlappingDate, setCalendarHasError, rangeContext, }) setUpdatedForSubmit && setUpdatedForSubmit(true) } // Handle disabled dates for range context const { findFirstPossibleRangeContextCheckIn, findLastPossibleRangeContextCheckOut, firstPossibleRangeContextCheckIn, lastPossibleRangeContextCheckOut, currentSelectionAvailability, } = handleRangeContextDisabledDates({ rangeContext, availableDates: newDisableCalendarDates?.availableDates, calendarRange, }) // Handle check for continuous selection in the range context checkForContinuousSelection({ setCalendarHasError, rangeContext, calendarRange, calendarHasError, disabledDates: newDisableCalendarDates?.disabledDates, }) const disabledInsideSelectableRange = () => { if ( // Range end already selected calendarRange?.to || // No current check-in availability !currentSelectionAvailability || // No gap between check-in and first possible check-out, nothing to disable addDays(currentSelectionAvailability.checkIn, 1) >= currentSelectionAvailability.firstCheckOut ) { return [] } // Disable dates between current check-in and first possible check-out return [ { from: addDays(currentSelectionAvailability.checkIn, 1), to: addDays(currentSelectionAvailability.firstCheckOut, -1), }, ] } const base = disabledDatesByPage.length ? disabledDatesByPage : disabledDates?.length ? disabledDates : newDisableCalendarDates?.disabledDates || [] const disabled = disabledDatesByPage.length ? base : [ lastPossibleCheckout, ...base, ...firstPossibleRangeContextCheckIn, ...lastPossibleRangeContextCheckOut, ...disabledInsideSelectableRange(), ] return (
handleOnSelect(range)} captionLayout="dropdown-buttons" defaultMonth={ calendarRange?.from || selectedStartDate || rangeContextStartDate || (newDisableCalendarDates?.disabledDates?.length ? newDisableCalendarDates.disabledDates[0].from : today) } disabled={disabled} fromMonth={today} onMonthChange={(val) => { requestDates?.(val) setUpdateCalendarMonthNavigation?.((prev) => !prev) }} classNames={{ day_range_start: calendarRange?.from ? 'rdp-day_range_start' : '', day_range_end: calendarRange?.to ? 'rdp-day_range_end' : '', }} modifiersClassNames={{ today: 'my-today', booked: 'booked', disabledAfterCheckIn: 'disabled-after-check-in', overlappingDate: 'overlapping-date', noActiveSelectionStart: 'no-active-selection-start', noActiveSelectionMid: 'no-active-selection-mid', noActiveSelectionEnd: 'no-active-selection-end', checkoutOptionsMid: 'rdp-day_selected rdp-day_range_middle checkout-option', checkInOnly: 'check-in-only', checkOutOnly: 'check-out-only', }} modifiers={ // This function handles conditions for applying the modifiersClassNames handleCalendarModifiers({ calendarRange, disabledDates: disabled, overlappingDate, rangeContext, findFirstPossibleRangeContextCheckIn, findLastPossibleRangeContextCheckOut, currentSelectionLastCheckoutDate: currentSelectionAvailability, }) } />
{t('noCheckIn')}
{t('noCheckOut')}
{t('checkOutOnly')}
{t('checkInOnly')}
{t('checkOutOnly')}
) } )