diff --git a/src/components/DateSelector/DateSelector.jsx b/src/components/DateSelector/DateSelector.jsx index a03f098af..9930876dc 100644 --- a/src/components/DateSelector/DateSelector.jsx +++ b/src/components/DateSelector/DateSelector.jsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import moment from 'moment'; @@ -12,7 +12,7 @@ import Typography from '@mui/material/Typography'; import ArrowToolTip from '@components/common/ArrowToolTip'; import options from './options'; import useStyles from './useStyles'; -import DateRanges from './DateRanges'; +import ReactDayPicker from '@components/common/ReactDayPicker'; const dateFormat = 'YYYY-MM-DD'; @@ -20,20 +20,56 @@ function DateSelector({ range, updateStartDate, updateEndDate, + startDate, + endDate, }) { const [expandedMenu, setExpandedMenu] = useState(false); + const [activeField, setActiveField] = useState(null); + const [initialStart, setInitialStart] = useState(null); + const [initialEnd, setInitialEnd] = useState(null); + const displayRef = useRef(null); + const collapseRef = useRef(null); + const endDateBtnRef = useRef(null); const classes = useStyles(); + // Close on outside click and revert if the selection was not completed + useEffect(() => { + const handleDocClick = (e) => { + if (!expandedMenu) return; + if (displayRef.current && displayRef.current.contains(e.target)) return; + if (collapseRef.current && collapseRef.current.contains(e.target)) return; + + // clicked outside both display and collapse + const selectionCompleted = !range + ? (startDate && startDate !== initialStart) + : (startDate && endDate); + + if (!selectionCompleted) { + // revert to initial values captured when the collapse opened + updateStartDate(initialStart); + updateEndDate(initialEnd); + } + + setExpandedMenu(false); + setActiveField(null); + }; + + document.addEventListener('mousedown', handleDocClick); + return () => document.removeEventListener('mousedown', handleDocClick); + }, [expandedMenu, initialStart, initialEnd, startDate, endDate, range, updateStartDate, updateEndDate]); + const handleOptionSelect = optionDates => { const formattedStart = moment(optionDates[0]).format(dateFormat); const formattedEnd = moment(optionDates[1]).format(dateFormat); updateStartDate(formattedStart); updateEndDate(formattedEnd); setExpandedMenu(false); + setActiveField(null); }; const closeOptionsOnDateToggle = useCallback(() => { setExpandedMenu(false); + setActiveField(null); }, []); const { @@ -64,34 +100,98 @@ function DateSelector({ - setExpandedMenu(!expandedMenu)} expanded={expandedMenu}> + setExpandedMenu(!expandedMenu)} expanded={expandedMenu} arrowHidden> -
+
{ + // capture initial values for possible revert + setInitialStart(startDate); + setInitialEnd(endDate); + setActiveField(field); + setExpandedMenu(true); + }} + onCloseCollapse={() => { + setExpandedMenu(false); + setActiveField(null); + }} + activeField={activeField} + displayRef={displayRef} + endDateBtnRef={endDateBtnRef} /> -
- +
+ { + // If ReactDayPicker provided the selection object, immediately + // ensure the redux store and our captured initial values match + // the selection. This avoids a race where the collapse closes + // before connected props reflect the new dates, which caused + // stale highlights when reopening the calendar. + if (selection && typeof selection.startDate !== 'undefined') { + if (selection.startDate !== startDate) updateStartDate(selection.startDate); + if (typeof selection.endDate !== 'undefined' && selection.endDate !== endDate) updateEndDate(selection.endDate); + setInitialStart(selection.startDate); + setInitialEnd(selection.endDate); + } + + // If the selection contains only a start date (user picked Start + // and still needs to pick an End), keep the collapse open and + // move focus to the End field so it's clear the user should pick + // an end date next. + if (selection && selection.startDate && (typeof selection.endDate === 'undefined' || selection.endDate === null)) { + // ensure the store has the new startDate (done above) and + // mark the active field as 'end' + setActiveField('end'); + setExpandedMenu(true); + + const focusEndDate = () => { + const endBtn = endDateBtnRef.current || displayRef.current?.querySelector?.('#endDate'); + if (endBtn) { + endBtn.focus(); + } + } + + // move focus to the End button using requestAnimationFrame for reliable DOM updates + requestAnimationFrame(() => { + requestAnimationFrame(focusEndDate); + }); + return; + } + + // Otherwise (selection includes end or is complete) close the + // collapse after letting React/Redux flush updates. + Promise.resolve().then(() => { + setExpandedMenu(false); + setActiveField(null); + }); + }} + /> +
); } +const mapStateToProps = state => ({ + startDate: state.filters.startDate, + endDate: state.filters.endDate, +}); + const mapDispatchToProps = dispatch => ({ updateStartDate: date => dispatch(reduxUpdateStartDate(date)), updateEndDate: date => dispatch(reduxUpdateEndDate(date)), }); -export default connect(null, mapDispatchToProps)(DateSelector); +export default connect(mapStateToProps, mapDispatchToProps)(DateSelector); DateSelector.propTypes = { range: PropTypes.bool, diff --git a/src/components/common/DatePicker/DatePicker.jsx b/src/components/common/DatePicker/DatePicker.jsx index 5d3dc97af..b975eccb1 100644 --- a/src/components/common/DatePicker/DatePicker.jsx +++ b/src/components/common/DatePicker/DatePicker.jsx @@ -1,13 +1,11 @@ import React, { useRef, useState, useEffect, useCallback, } from 'react'; +import clsx from 'clsx'; import { connect } from 'react-redux'; import moment from 'moment'; import PropTypes from 'prop-types'; -import CalendarIcon from '@mui/icons-material/CalendarToday'; -import IconButton from '@mui/material/IconButton'; import makeStyles from '@mui/styles/makeStyles'; -import useOutsideClick from '@hooks/useOutsideClick'; import ReactDayPicker from '@components/common/ReactDayPicker'; import { updateEndDate as reduxUpdateEndDate, @@ -15,20 +13,27 @@ import { } from '@reducers/filters'; // TODO: Apply gaps (margin, padding) from theme -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles(theme => { + // local style constants to avoid repeating magic numbers + const BORDER_RADIUS = 5; + const GAP = '5px'; + const PADDING = '8px 10px'; + const BORDER_WIDTH = 2; + const ICON_SIZE = 20; + + return ({ selector: { display: 'flex', - justifyContent: 'space-between', + justifyContent: 'flex-start', alignItems: 'center', width: '100%', - maxWidth: 268, backgroundColor: theme.palette.primary.dark, - padding: 10, borderRadius: 5, - fontSize: '12px', + fontSize: '14px', color: theme.palette.text.secondaryLight, - '& > div': { + '& > *': { cursor: 'pointer', + flex: '1 1 0%', }, }, placeholder: { @@ -38,6 +43,28 @@ const useStyles = makeStyles(theme => ({ position: 'fixed', zIndex: 1, }, + datePicker: { + display: 'flex', + alignItems: 'center', + gap: GAP, + backgroundColor: theme.palette.primary.dark, + border: `${BORDER_WIDTH}px solid ${theme.palette.primary.dark}`, + color: theme.palette.text.secondaryLight, + padding: PADDING, + margin: 0, + boxSizing: 'border-box', + flex: '1 1 0%', + borderRadius: BORDER_RADIUS, + transition: 'border-color 150ms ease, box-shadow 150ms ease', + + '&:focus, &:focus-visible': { + borderColor: theme.palette.secondaryFocus || theme.palette.textFocus || '#87C8BC', + outline: 'none', + }, + }, + active: { + borderColor: theme.palette.secondaryFocus || theme.palette.textFocus || '#87C8BC', + }, button: { padding: 0, color: theme.palette.text.dark, @@ -45,131 +72,107 @@ const useStyles = makeStyles(theme => ({ backgroundColor: theme.palette.primary.dark, }, '& svg': { - fontSize: 20, - fill: theme.palette.text.secondaryLight, + fontSize: ICON_SIZE, + fill: theme.palette.text.textSecondaryDark, }, }, -})); + }); +}); -const renderSelectedDays = (dates, classes, range) => { +/** + * renderSelectedDays + * - If fieldIndex is provided (0 = start, 1 = end) it returns a single element + * suitable for populating an individual field (shows the date or a placeholder). + * - If fieldIndex is not provided it returns the combined display (same as before): + * either "from - to", a single date, or the generic placeholder. + */ +const renderSelectedDays = (dates, classes, range, fieldIndex) => { const [from, to] = dates; const isFromSelected = Boolean(from); const isBothSelected = Boolean(from && to); - const selectedDaysElements = []; + // If caller asks for a specific field (start or end), return a single element + if (typeof fieldIndex === 'number') { + if (fieldIndex === 0) { + // Start field + if (isFromSelected) return {moment(from).format('L')}; + return Start Date; + } + if (fieldIndex === 1) { + // End field + if (to) return {moment(to).format('L')}; + return End Date; + } + } + // Backwards-compatible combined rendering if (isBothSelected) { - selectedDaysElements.push( + return [ {moment(from).format('L')}, - , {moment(to).format('L')}, - ); - return selectedDaysElements; + ]; } if (isFromSelected) { - selectedDaysElements.push( - - {' '} - {moment(from).format('L')} - {' '} - , - ); - return selectedDaysElements; + return [( + {moment(from).format('L')} + )]; } - selectedDaysElements.push( + return [( Select a date {' '} {range ? ' range' : ''} - , - ); - return selectedDaysElements; + + )]; }; function DatePicker({ - open, onTogglePresets, range, startDate, endDate, updateStartDate, updateEndDate, + // controlled by parent DateSelector + onOpenCollapse, onCloseCollapse, activeField, + range, startDate, endDate, updateStartDate, updateEndDate, displayRef, endDateBtnRef, }) { - const [showCalendar, setShowCalendar] = useState(() => open); - const [initialStartDate, setInitialStartDate] = useState(startDate); - const [initialEndDate, setInitialEndDate] = useState(); const classes = useStyles(); - const ref = useRef(null); - - const closeCalendar = useCallback( - () => { - if (startDate && endDate){ - setShowCalendar(false); - } else if (startDate && !endDate){ - // The calendar was closed with an incomplete date range selection so we need to restart - // startDate and endDate to their initial values - updateStartDate(initialStartDate); - updateEndDate(initialEndDate); - setShowCalendar(false); - } else { - // This should never happen. Log a warning. - console.warn('Try to set a new date selection. Dates were in an invalid state. StartDate: ', startDate, " endDate: ", endDate); + const ref = displayRef || useRef(null); + + const handleFieldClick = (field) => { + // parent will open the SelectorBox collapse and tell ReactDayPicker which field is active + if (activeField === field) { + if (onCloseCollapse) onCloseCollapse(); + } else if (onOpenCollapse) { + onOpenCollapse(field); } - }, [startDate, endDate]); - useOutsideClick(ref, closeCalendar); - - const openCalendar = () => { - setInitialStartDate(startDate); - setInitialEndDate(endDate); - setShowCalendar(true); - } - - useEffect(() => { - setShowCalendar(false); - setInitialStartDate(startDate) - setInitialEndDate(endDate) - }, [open]); - - const getCoordinates = () => { - if (ref.current) { - const { left, top, height } = - ref.current.getClientRects()[0] ?? ref.current.getBoundingClientRect(); - const offsetFromSelectorDisplay = 2; - return { - left, - top: top + height + offsetFromSelectorDisplay, - }; - } - return {}; - }; - - const toggleCalendar = () => { - if (showCalendar) { - closeCalendar(); - } else { - openCalendar(); - } - if (onTogglePresets) onTogglePresets(); }; return (
-
- {renderSelectedDays([startDate, endDate], classes, range)} -
- handleFieldClick('start')} + className={clsx(classes.datePicker, activeField === 'start' && classes.active)} + aria-haspopup="dialog" + aria-expanded={activeField === 'start'} + aria-label="Start date" + > + + + + {renderSelectedDays([startDate, endDate], classes, range, 0)} + + +
); } @@ -177,17 +180,25 @@ function DatePicker({ DatePicker.propTypes = { range: PropTypes.bool, open: PropTypes.bool, - onTogglePresets: PropTypes.func, + onOpenCollapse: PropTypes.func, + onCloseCollapse: PropTypes.func, + activeField: PropTypes.string, + displayRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), startDate: PropTypes.string, endDate: PropTypes.string, + endDateBtnRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }; DatePicker.defaultProps = { open: false, range: false, - onTogglePresets: null, + onOpenCollapse: null, + onCloseCollapse: null, + activeField: null, + displayRef: null, startDate: null, endDate: null, + endDateBtnRef: null, }; const mapStateToProps = state => ({ diff --git a/src/components/common/ReactDayPicker/ReactDayPicker.jsx b/src/components/common/ReactDayPicker/ReactDayPicker.jsx index b1569fcd8..2b83324e8 100644 --- a/src/components/common/ReactDayPicker/ReactDayPicker.jsx +++ b/src/components/common/ReactDayPicker/ReactDayPicker.jsx @@ -2,7 +2,7 @@ import 'react-day-picker/lib/style.css'; import moment from 'moment'; import PropTypes from 'prop-types'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import DayPicker from 'react-day-picker'; import { connect } from 'react-redux'; import makeStyles from '@mui/styles/makeStyles'; @@ -10,23 +10,36 @@ import clsx from 'clsx'; import fonts from '@theme/fonts'; import colors from '@theme/colors'; import { INTERNAL_DATE_SPEC } from '../CONSTANTS'; -// import Styles from './Styles'; import WeekDay from './Weekday'; +// style constants used within this file to reduce magic numbers +const STYLE = { + BORDER_RADIUS: '5px', + MIN_WIDTH: 277, // px + MONTH_MARGIN: '1em', + DAY_PADDING: '5px 8px', + DAY_MAX_WIDTH: 32, // px + NAV_LEFT: '1.5rem', + NAV_FILTER: 'invert(44%) sepia(99%) saturate(1089%) hue-rotate(6deg) brightness(106%) contrast(96%)', + SELECTED_BEFORE_EXTRA: '5px', + WEEK_MARGIN_BOTTOM: '8px', + WEEKDAY_FONT_SIZE: '12px', +}; + const useStyles = makeStyles(theme => ({ root: { fontFamily: fonts.family.roboto, background: theme.palette.primary.dark, - borderRadius: '5px', - minWidth: '297px', + borderRadius: STYLE.BORDER_RADIUS, + minWidth: `${STYLE.MIN_WIDTH}px`, '& .DayPicker-Months': { display: 'block', - width: '83%', + width: '100%', marginRight: 'auto', marginLeft: 'auto', }, - '& DayPicker-Month': { - margin: '0 11px', + '& .DayPicker-Month': { + margin: `${STYLE.MONTH_MARGIN} auto !important`, }, '& .DayPicker-Body': { fontSize: '0.9rem', @@ -37,64 +50,80 @@ const useStyles = makeStyles(theme => ({ }, /* Selected range without start and end dates */ - '& .DayPicker-Day--selected:not(.DayPicker-Day--outside)': { - backgroundColor: `${theme.palette.selected.primary} !important`, + backgroundColor: `${theme.palette.primary.light} !important`, }, /* Disabled cell */ - '& .DayPicker-Day--disabled': { color: colors.textSecondaryDark, pointerEvents: 'none', }, /* Day cell hover */ - '& .DayPicker:not(.DayPicker--interactionDisabled), .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover': { backgroundColor: `${theme.palette.selected.primary} !important`, borderRadius: '50% !important', }, /* General day cell */ - '& .DayPicker-Day': { borderRadius: '0 !important', display: 'block', // causing dates to run down in a single vertical column flexGrow: 1, - maxWidth: '32px', + maxWidth: `${STYLE.DAY_MAX_WIDTH}px`, }, /* Today cell */ - '& .DayPicker-Day.DayPicker-Day--selected.DayPicker-Day--today, .DayPicker-Day--today': { color: theme.palette.primary.focus, }, /* Selected start and end days */ - '& .DayPicker-Day.DayPicker-Day--start.DayPicker-Day--selected, .DayPicker-Day.DayPicker-Day--end.DayPicker-Day--selected': { position: 'relative', zIndex: 1, }, + /* Selected start day */ + '& .DayPicker-Day.DayPicker-Day--start.DayPicker-Day--selected': { + color: '#0F181F !important', + '&::before': { + backgroundColor: `#87C8BC !important`, + background: `#87C8BC !important`, + border: 'none !important', + height: 'calc(100% + 0px) !important', + width: 'calc(100% + 0px) !important', + } + }, + + /* Selected end day */ + '& .DayPicker-Day.DayPicker-Day--end.DayPicker-Day--selected': { + '&::before': { + border: '1px solid #87C8BC !important', + height: 'calc(100% + 0px) !important', + width: 'calc(100% + 0px) !important', + } + }, + /* next and prev arrows */ '& .DayPicker-NavButton.DayPicker-NavButton': { top: 0, + filter: STYLE.NAV_FILTER, }, '& .DayPicker-NavButton.DayPicker-NavButton--prev': { - left: '1.5rem', + left: STYLE.NAV_LEFT, + filter: STYLE.NAV_FILTER, }, /* Rounded border with volume for selected start and end days of a range */ - '& .DayPicker-Day.DayPicker-Day--start.DayPicker-Day--selected:not(.DayPicker-Day--outside):before, .DayPicker-Day.DayPicker-Day--end.DayPicker-Day--selected:not(.DayPicker-Day--outside):before': { content: '""', position: 'absolute', border: '2px solid white', - height: 'calc(100% + 5px)', - width: 'calc(100% + 5px)', + height: `calc(100% + ${STYLE.SELECTED_BEFORE_EXTRA})`, + width: `calc(100% + ${STYLE.SELECTED_BEFORE_EXTRA})`, borderRadius: '50%', top: '50%', left: '50%', @@ -105,18 +134,17 @@ const useStyles = makeStyles(theme => ({ /* Layout styling, Initial styling was table based. See docs: */ /* https://react-day-picker.js.org/examples/selected-range-enter */ - '& .DayPicker-Caption, .DayPicker-Weekdays, .DayPicker-WeekdaysRow, .DayPicker-Body': { display: 'block', width: '100%', }, '& .DayPicker-Week .DayPicker-Day': { - padding: '5px 8px', + padding: STYLE.DAY_PADDING, }, '& .DayPicker-Week': { - marginBottom: '8px', + marginBottom: STYLE.WEEK_MARGIN_BOTTOM, }, '& .DayPicker-Week, .DayPicker-WeekdaysRow': { @@ -127,7 +155,7 @@ const useStyles = makeStyles(theme => ({ '& .DayPicker-Weekday': { display: 'block', - fontSize: '12px', + fontSize: STYLE.WEEKDAY_FONT_SIZE, }, }, hasRange: { @@ -148,14 +176,28 @@ const useStyles = makeStyles(theme => ({ }, })); -/** A wrapper around react-day-picker that selects a date range. */ +/* A wrapper around react-day-picker that selects a date range. */ function ReactDayPicker({ - range, updateStartDate, updateEndDate, startDate, endDate, + range, updateStartDate, updateEndDate, startDate, endDate, activeField, onSelectionComplete, }) { const classes = useStyles(); // enteredTo represents the day that the user is currently hovering over. const [enteredTo, setEnteredTo] = useState(endDate); + // pendingSelection holds a selection object { startDate, endDate } that + // reflects the user's most recent click before Redux-connected props update. + // This lets the calendar highlight the newly-selected day immediately. + const [pendingSelection, setPendingSelection] = useState(null); + + // Clear pendingSelection when the connected props catch up to it. + useEffect(() => { + if (!pendingSelection) return; + const pendingStart = pendingSelection.startDate || null; + const pendingEnd = typeof pendingSelection.endDate !== 'undefined' ? pendingSelection.endDate : null; + if (pendingStart === startDate && pendingEnd === endDate) { + setPendingSelection(null); + } + }, [startDate, endDate, pendingSelection]); const setFromDay = day => { updateStartDate(moment(day).format(INTERNAL_DATE_SPEC)); @@ -165,34 +207,108 @@ function ReactDayPicker({ updateEndDate(moment(day).format(INTERNAL_DATE_SPEC)); }; + const handleDayClick = day => { + const clicked = moment(day).format(INTERNAL_DATE_SPEC); + if (!range) { setFromDay(day); + if (onSelectionComplete) Promise.resolve().then(() => onSelectionComplete()); return; } - - // If both startDate and endDate were already selected. Start a new range selection. - if (startDate && endDate){ - setFromDay(day); - updateEndDate(null); - setEnteredTo(null); - // If startDate is selected and endDate is unselected, complete the range selection. - } else if (startDate && !endDate){ + + // When editing the end field + if (activeField === 'end') { + if (startDate) { + if (clicked < startDate) { + // reflect immediately in UI + setPendingSelection({ startDate: clicked, endDate: null }); + setFromDay(day); + updateEndDate(null); + setEnteredTo(null); + if (onSelectionComplete) { + Promise.resolve().then(() => + onSelectionComplete({ startDate: clicked, endDate: null }) + ); + } + return; // keep picker open for user to choose end + } + + // clicked >= startDate -> set as end and complete + // show immediately + setPendingSelection({ startDate: startDate, endDate: clicked }); + setToDay(day); + if (onSelectionComplete) { + Promise.resolve().then(() => onSelectionComplete({ startDate, endDate: clicked })); + } + return; // CRITICAL: exit here + } + + // no startDate, treat clicked as start + // reflect immediately + const startStr = moment(day).format(INTERNAL_DATE_SPEC); + setPendingSelection({ startDate: startStr, endDate: null }); + setFromDay(day); + if (onSelectionComplete) { + Promise.resolve().then(() => onSelectionComplete({ startDate: startStr, endDate: null })); + } + return; // CRITICAL: exit here + } + + // Editing start (or default behavior) + if (startDate && endDate) { + // start a new range selection + setPendingSelection({ startDate: moment(day).format(INTERNAL_DATE_SPEC), endDate: null }); + setFromDay(day); + updateEndDate(null); + setEnteredTo(null); + if (onSelectionComplete) { + Promise.resolve().then(() => + onSelectionComplete({ startDate: moment(day).format(INTERNAL_DATE_SPEC), endDate: null }) + ); + } + return; // CRITICAL: exit here + } + if (startDate && !endDate) { // If the user selects the startDate then chooses an endDate that precedes it, // swap the values of startDate and endDate - if (moment(day).format(INTERNAL_DATE_SPEC) < startDate) { + if (clicked < startDate) { const tempDate = startDate; + // reflect swap immediately + setPendingSelection({ startDate: clicked, endDate: tempDate }); setToDay(moment(tempDate).toDate()); setFromDay(day); updateEndDate(tempDate); setEnteredTo(moment(tempDate).toDate()); - } else { - setToDay(day); + if (onSelectionComplete) { + Promise.resolve().then(() => + onSelectionComplete({ startDate: clicked, endDate: tempDate }) + ); + } + return; } - } else { - // This should never happen. Log a warning. - console.warn('Try to set a new date selection. Dates were in an invalid state. StartDate: ', startDate, " endDate: ", endDate); - } + + setPendingSelection({ startDate: startDate, endDate: clicked }); + setToDay(day); + if (onSelectionComplete) { + Promise.resolve().then(() => onSelectionComplete({ startDate, endDate: clicked })); + } + return; + } + + // no start selected + const startStr = moment(day).format(INTERNAL_DATE_SPEC); + setPendingSelection({ startDate: startStr, endDate: null }); + setFromDay(day); + // callback to signal that a start date has been selected + if (onSelectionComplete) { + Promise.resolve().then(() => + onSelectionComplete({ + startDate: startStr, + endDate: null, + }) + ); + } }; const handleDayMouseEnter = day => { @@ -202,20 +318,42 @@ function ReactDayPicker({ } }; - const from = moment(startDate).toDate(); - const enteredToDate = moment(enteredTo).toDate(); + // Use pendingSelection when present so the calendar highlights the user's + // choice immediately even before Redux props propagate. + const effectiveStart = pendingSelection && pendingSelection.startDate ? pendingSelection.startDate : startDate; + // For effectiveEnd, prefer (in order): pendingSelection.endDate, redux endDate prop, + // then the hovered enteredTo (used when user has selected a start but not an end). + let effectiveEnd; + if (pendingSelection && typeof pendingSelection.endDate !== 'undefined') { + effectiveEnd = pendingSelection.endDate; + } else if (endDate) { + effectiveEnd = endDate; + } else { + effectiveEnd = enteredTo; + } + + const from = effectiveStart ? moment(effectiveStart).toDate() : undefined; + const enteredToDate = effectiveEnd ? moment(effectiveEnd).toDate() : undefined; + useEffect(() => { + }, [pendingSelection, effectiveStart, effectiveEnd, startDate, endDate, enteredTo]); const today = new Date(); const currentMonth = today.getFullYear(); const currentYear = today.getMonth(); const lastThreeMonths = new Date(currentYear, currentMonth - 3, today.getDate()); + // determine initial month to display based on which field the user is editing + const initialMonth = (activeField === 'start' && startDate) + ? moment(startDate).toDate() + : (activeField === 'end' && endDate) + ? moment(endDate).toDate() + : undefined; + return ( <> - {/* */}