import moment, { type Moment } from 'moment' import React, { useEffect, useReducer } from 'react' import { type DateRange } from 'react-day-picker' import Button from '../../Button/Button' import DatePicker from '../../DatePicker/DatePicker' import Icon from '../../Icons/Icon' import Select from '../../Form/Select/Select' import TextInput from '../../Form/TextInput' import { compareToOptions } from './compareTo' import styles from './comparison_date_ranges.module.scss' import stylesDatePicker from '../../DatePicker/_datePicker.module.scss' import { type AlertPropsWithDates } from '../Timeframe' import { c } from '../../../translations/LibraryTranslationService' import { adjustDateRangeWithRestrictions, getSelectedDateRange, isEmptyOrWhitespace, parseDateRangeString, } from '../../DatePicker/DatePickerHelper' type CompareToType = { id: number period: string getCompareToDates: (state: SelectionState) => { previousStartDate: PickerDate previousEndDate: PickerDate } } export type ComparisonDatePickerProps = { alertProps?: AlertPropsWithDates initial_firstRange_startDate?: PickerDate initial_firstRange_endDate?: PickerDate initial_secondRange_startDate?: PickerDate initial_secondRange_endDate?: PickerDate /** Toggle to determine if the comparison date range option be visible */ showComparisonRange?: boolean /** Toggle to determine if the `Compare To` dropdown selector is visible */ showCompareToSelector?: boolean selectionStateCallout?: ( /** selectionState will include all four dates, compareTo value & resetInputs boolean */ selectionState: SelectionState, /** dispatch action to update the value of the selectionState */ selectionStateDispatch: React.Dispatch, ) => void children?: ( // TODO: look into removing this with toggle global_filter_side_drawer_change /** selectionState will include all four dates, compareTo value & resetInputs boolean */ selectionState: SelectionState, /** dispatch action to update the value of the selectionState */ selectionStateDispatch: React.Dispatch, ) => React.ReactNode /** Label for the first range */ firstRangeLabel?: string /** Label for the second range */ secondRangeLabel?: string } type DateRangeTypeNew = 'firstDateRange' | 'secondDateRange' export function ComparisonDatePicker({ alertProps, initial_firstRange_startDate, initial_firstRange_endDate, initial_secondRange_startDate, initial_secondRange_endDate, showCompareToSelector, showComparisonRange, selectionStateCallout, children, firstRangeLabel = c('firstRange'), secondRangeLabel = c('secondRange'), }: ComparisonDatePickerProps): React.JSX.Element { const [activeField, setActiveField] = React.useState('firstDateRange') const [isAutoFocus, setIsAutoFocus] = React.useState(true) const [selectionState, selectionStateDispatch] = useReducer( (prevState: SelectionState, payload: SelectionStateDispatchActions) => rangeReducer(prevState, payload, setActiveField, () => setIsAutoFocus(true), ), { firstRange_startDate: initial_firstRange_startDate ?? initialState().firstRange_startDate, firstRange_endDate: initial_firstRange_endDate ?? initialState().firstRange_endDate, secondRange_startDate: initial_secondRange_startDate ?? initialState().secondRange_startDate, secondRange_endDate: initial_secondRange_endDate ?? initialState().secondRange_endDate, selecting: initialState().selecting, showComparisonRange: showComparisonRange ?? initialState().showComparisonRange, compareTo: compareToOptions[0], resetInputs: true, }, ) const submitDateNew = ( inputValue: DateRangeTypeNew, dateRange: DateRange, ) => { if (dateRange && dateRange.from) { if (inputValue === 'firstDateRange') { selectionStateDispatch({ action: 'dateInputChanged', date: moment(dateRange.from), inputName: 'firstRange_startDate', }) selectionStateDispatch({ action: 'dateInputChanged', date: moment(dateRange.to), inputName: 'firstRange_endDate', }) // Auto-focus to second range only on initial load when first range is complete if ( isAutoFocus && selectionState.showComparisonRange && selectionState.compareTo === compareToOptions[0] && dateRange.from && dateRange.to ) { const fromDate = moment(dateRange.from) const toDate = moment(dateRange.to) if (!fromDate.isSame(toDate, 'day')) { setActiveField('secondDateRange') setIsAutoFocus(false) } } } else { selectionStateDispatch({ action: 'dateInputChanged', date: moment(dateRange.from), inputName: 'secondRange_startDate', }) selectionStateDispatch({ action: 'dateInputChanged', date: moment(dateRange.to), inputName: 'secondRange_endDate', }) } } } useEffect(() => { // The alert message might depend on the selected dates. So, we provide it though this callout. alertProps?.selectedDatesCallout?.({ startDate: selectionState?.firstRange_startDate?.toString() ?? '', endDate: selectionState?.firstRange_endDate?.toString() ?? '', comparisonStartDate: selectionState?.secondRange_startDate?.toString() ?? '', comparisonEndDate: selectionState?.secondRange_endDate?.toString() ?? '', }) // The actual State Update of the changed dates selectionStateCallout?.(selectionState, selectionStateDispatch) }, [alertProps, selectionState, selectionStateCallout]) const [isDateValid1, setIsDateValid1] = React.useState(true) const [inputDate1, setInputDate1] = React.useState('') const [isDateValid2, setIsDateValid2] = React.useState(true) const [inputDate2, setInputDate2] = React.useState('') const dateRestrictions = { afterDate: moment().toDate() } const isEditingRef1 = React.useRef(false) const isEditingRef2 = React.useRef(false) const getFormattedDateValue = ( dateRange: DateRange | undefined, isEditingRef: React.RefObject, inputDate: string, ) => { if (isEditingRef.current) { return inputDate } else if (dateRange?.from) { return `${moment(getSelectedDateRange(dateRange, dateRestrictions)?.from).format('MM/DD/YYYY')} - ${moment(getSelectedDateRange(dateRange, dateRestrictions)?.to).format('MM/DD/YYYY')}` } return '' } const DateInput = ({ labelText, dateRange, onDateChange, isDateValid, setInputDate, isEditingRef, inputDate, currentField, activeField, }: { labelText: string dateRange: DateRange | undefined onDateChange: (dateRange: DateRange | undefined) => void isDateValid: boolean setInputDate: (inputDate: string) => void isEditingRef: React.RefObject inputDate: string currentField: string activeField: string }) => { const inputRef = React.useRef(null) return (
{ setActiveField(currentField) }} callout={(_, value) => { const stringValue = String(value) setInputDate(stringValue) if (isEmptyOrWhitespace(stringValue)) { onDateChange(undefined) return } const dateRange = parseDateRangeString(stringValue) if (dateRange) { const adjustedRange = adjustDateRangeWithRestrictions( dateRange, dateRestrictions, ) onDateChange(adjustedRange) } else { isEditingRef.current = true isDateValid = false onDateChange(undefined) } }} />
) } return (
{/* Time Period Comparisons Dropdown */} {showCompareToSelector && selectionState.showComparisonRange && (