import React, { FC, useEffect, useMemo, useRef, useState } from "react"; import classNames from "classnames"; import { format, isAfter, isBefore, parse } from "date-fns"; import { Box } from "../Box"; import { Button, BUTTON_ICON_POSITION, BUTTON_VARIANT } from "../Button"; import { MenuRenderer } from "../MenuRenderer"; import { PopoverMenu } from "../PopoverMenu"; import { Icon, ICON_TYPE } from "../Icon"; import { Flex } from "../Flex"; import { Text } from "../Text"; import { CustomDateRangeInput } from "./CustomDateRangeInput"; import { DATE_FORMAT, parseDate } from "../../utilities"; import { DatePicker } from "../DatePicker"; import { isValidDate } from "../../utilities/isValidDate"; import { cn, RangePickerPreset, DATE_RANGE_PICKER_CUSTOM_PRESET, dateRangePickerFocusInput, getMatchingDateRangePickerPreset, } from "./utils"; import { DateRangePickerLegacyProps } from "./types"; export const DateRangePickerLegacy: FC = ({ className, onChange, align = "left", presets, value, minDate, maxDate, ...rest }) => { const placement = align === "left" ? "bottom-start" : "bottom-end"; const [selectingCustomDate, setSelectingCustomDate] = useState( !getMatchingDateRangePickerPreset(value, presets) && value !== null, ); const [ customStartDateInputVal, setCustomStartDateInputVal, ] = useState(""); const [customStartDateError, setCustomStartDateError] = useState(""); const [customEndDateInputVal, setCustomEndDateInputVal] = useState( "", ); const [customEndDateError, setCustomEndDateError] = useState(""); const [customSelectingWhichDate, setCustomSelectingWhichDate] = useState< "start" | "end" >("start"); const startInput = useRef(null); const endInput = useRef(null); const handleClose = () => { setCustomStartDateError(""); if (selectingCustomDate && value && "name" in value) { setSelectingCustomDate(false); } }; const selectedPreset = useMemo(() => { if (!value) return null; if (selectingCustomDate) return DATE_RANGE_PICKER_CUSTOM_PRESET; const matchingPreset = getMatchingDateRangePickerPreset(value, presets); return matchingPreset ? matchingPreset.name : DATE_RANGE_PICKER_CUSTOM_PRESET; }, [presets, value, selectingCustomDate]); const customDateInputsValid = useMemo(() => { const parsedStart = parseDate(customStartDateInputVal); const parsedEnd = parseDate(customEndDateInputVal); return !!(parsedStart && parsedEnd); }, [customStartDateInputVal, customEndDateInputVal]); const displayText = useMemo(() => { if (!value) return "Select Date Range"; const matchingPreset = getMatchingDateRangePickerPreset(value, presets); if (selectingCustomDate && matchingPreset) return DATE_RANGE_PICKER_CUSTOM_PRESET; return selectedPreset === DATE_RANGE_PICKER_CUSTOM_PRESET ? `${format(value.startDate, DATE_FORMAT)} to ${format( value.endDate, DATE_FORMAT, )}` : selectedPreset; }, [selectedPreset, value, selectingCustomDate, presets]); useEffect(() => { if (!selectingCustomDate || customDateInputsValid) return; const inputToFocus = customSelectingWhichDate === "start" ? startInput : endInput; dateRangePickerFocusInput(inputToFocus); }, [customSelectingWhichDate, selectingCustomDate, customDateInputsValid]); const validDates = useMemo(() => { let start = null; let end = null; // Parse start date if (customStartDateInputVal) { const parsed = parse(customStartDateInputVal, DATE_FORMAT, new Date()); start = isValidDate(parsed) ? parsed : null; } // Parse end date if (customEndDateInputVal) { const parsed = parse(customEndDateInputVal, DATE_FORMAT, new Date()); end = isValidDate(parsed) ? parsed : null; } // Start date before minDate or after maxDate if ( start && ((minDate && isBefore(start, minDate)) || (maxDate && isAfter(start, maxDate))) ) { setCustomStartDateError("Outside allowed date range"); start = null; } // End date before minDate or after maxDate if ( end && ((minDate && isBefore(end, minDate)) || (maxDate && isAfter(end, maxDate))) ) { setCustomEndDateError("Outside allowed date range"); end = null; } // Start date after endDate if (start && end && isAfter(start, end)) { setCustomEndDateError("End date cannot be before start date"); end = null; } // If start is a Date, remove any error if (start) { setCustomStartDateError(""); } // If end is a Date, remove any error if (end) { setCustomEndDateError(""); } return { start, end }; }, [customStartDateInputVal, customEndDateInputVal, minDate, maxDate]); const handleDatePickerChange = ([newStartDate, newEndDate]: [Date, Date]) => { if (!newEndDate) dateRangePickerFocusInput(endInput); const newStartString = newStartDate ? format(newStartDate, DATE_FORMAT) : ""; const newEndString = newEndDate ? format(newEndDate, DATE_FORMAT) : ""; setCustomStartDateInputVal(newStartString); setCustomEndDateInputVal(newEndString); }; const handleDatePickerSelect = (date: Date) => { // Handles situation where an end date is provided // without a start date if (!validDates.start && validDates.end) { setCustomStartDateInputVal(format(date, DATE_FORMAT)); } }; const customApplyBtnDisabled = !validDates.start || !validDates.end || !!customStartDateError || !!customEndDateError; return ( ( )} {...rest} > {({ listProps, onClose }) => ( {presets.map((p) => ( { onChange(p); if (onClose) { onClose(); } }} isSelected={p.name === selectedPreset} > {p.name} ))} { if (selectingCustomDate) return; setSelectingCustomDate(true); }} isSelected={selectedPreset === DATE_RANGE_PICKER_CUSTOM_PRESET} > Custom {selectedPreset === DATE_RANGE_PICKER_CUSTOM_PRESET ? ( setCustomSelectingWhichDate("start")} /> to setCustomSelectingWhichDate("end")} /> ) : null} )} ); };