From 19556185659532379802e2abbe2bb5fd9afcd876 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 7 Jun 2024 14:04:24 -0400 Subject: [PATCH] fix timeline initial date range regression When refactoring into TimelineContext, there was a regression regarding the date range that is initially loaded. https://github.com/e-mission/e-mission-phone/pull/1142#issuecomment-2141345034 It used to load 7 days before the pipelineEnd up to today. After the change, it would load 7 days before today up to today. If the user has travel everyday this ends up being equivalent. But if there isn't recent travel, this becomes a problem. So in TimelineContext, the initial dateRange needs to depend on pipelineRange. instead of giving an initial value upfront, we'll let dateRange be null until pipelineRange is set, at which point we'll also set dateRange. This means we have to consider some extra cases where dateRange can be null. --- www/js/TimelineContext.ts | 53 +++++++++++++++++--------------- www/js/diary/list/DateSelect.tsx | 2 +- www/js/metrics/MetricsTab.tsx | 4 +-- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/www/js/TimelineContext.ts b/www/js/TimelineContext.ts index e263ca0bb..203051094 100644 --- a/www/js/TimelineContext.ts +++ b/www/js/TimelineContext.ts @@ -25,9 +25,7 @@ import { EnketoUserInputEntry } from './survey/enketo/enketoHelper'; import { VehicleIdentity } from './types/appConfigTypes'; import { primarySectionForTrip } from './diary/diaryHelper'; -// initial query range is the past 7 days, including today -const today = DateTime.now().toISODate().substring(0, 10); -const initialQueryRange: [string, string] = [isoDateWithOffset(today, -6), today]; +const TODAY_DATE = DateTime.now().toISODate(); type ContextProps = { labelOptions: LabelOptions | null; @@ -43,7 +41,7 @@ type ContextProps = { addUserInputToEntry: (oid: string, userInput: any, inputType: 'label' | 'note') => void; pipelineRange: TimestampRange | null; queriedDateRange: [string, string] | null; // YYYY-MM-DD format - dateRange: [string, string]; // YYYY-MM-DD format + dateRange: [string, string] | null; // YYYY-MM-DD format timelineIsLoading: string | false; loadMoreDays: (when: 'past' | 'future', nDays: number) => boolean | void; loadDateRange: (d: [string, string]) => boolean | void; @@ -62,7 +60,7 @@ export const useTimelineContext = (): ContextProps => { // date range (inclusive) that has been loaded into the UI [YYYY-MM-DD, YYYY-MM-DD] const [queriedDateRange, setQueriedDateRange] = useState<[string, string] | null>(null); // date range (inclusive) chosen by datepicker [YYYY-MM-DD, YYYY-MM-DD] - const [dateRange, setDateRange] = useState<[string, string]>(initialQueryRange); + const [dateRange, setDateRange] = useState<[string, string] | null>(null); // map of timeline entries (trips, places, untracked time), ids to objects const [timelineMap, setTimelineMap] = useState(null); const [timelineIsLoading, setTimelineIsLoading] = useState('replace'); @@ -84,26 +82,24 @@ export const useTimelineContext = (): ContextProps => { } }, [appConfig, refreshTime]); - // when a new date range is chosen, load more date, then update the queriedDateRange + // when a new date range is chosen, load more data, then update the queriedDateRange useEffect(() => { const onDateRangeChange = async () => { if (!dateRange) return logDebug('No dateRange chosen, skipping onDateRangeChange'); - if (!pipelineRange) return logDebug('No pipelineRange yet, skipping onDateRangeChange'); - logDebug('Timeline: onDateRangeChange with dateRange = ' + dateRange?.join(' to ')); // determine if this will be a new range or an expansion of the existing range let mode: 'replace' | 'prepend' | 'append'; let dateRangeToQuery = dateRange; - if (queriedDateRange?.[0] == dateRange[0] && queriedDateRange?.[1] == dateRange[1]) { + if (queriedDateRange?.[0] == dateRange?.[0] && queriedDateRange?.[1] == dateRange?.[1]) { // same range, so we are refreshing the data mode = 'replace'; - } else if (queriedDateRange?.[0] == dateRange[0]) { + } else if (dateRange && queriedDateRange?.[0] == dateRange[0]) { // same start date, so we are loading more data into the future mode = 'append'; const nextDate = isoDateWithOffset(queriedDateRange[1], 1); dateRangeToQuery = [nextDate, dateRange[1]]; - } else if (queriedDateRange?.[1] == dateRange[1]) { + } else if (dateRange && queriedDateRange?.[1] == dateRange[1]) { // same end date, so we are loading more data into the past mode = 'prepend'; const prevDate = isoDateWithOffset(queriedDateRange[0], -1); @@ -124,10 +120,7 @@ export const useTimelineContext = (): ContextProps => { setTimelineIsLoading(false); displayError(e, 'While loading date range ' + dateRange?.join(' to ')); } - }, [dateRange, pipelineRange]); - - // const onDateRangeChange = useCallback(async (dateRange: [string, string], pipelineRange) => { - // }, []); + }, [dateRange]); useEffect(() => { if (!timelineMap) return; @@ -158,6 +151,16 @@ export const useTimelineContext = (): ContextProps => { `); } setPipelineRange(pipelineRange); + if (pipelineRange.end_ts) { + // set initial date range to [pipelineEndDate - 7 days, TODAY_DATE] + setDateRange([ + DateTime.fromSeconds(pipelineRange.end_ts).minus({ days: 7 }).toISODate(), + TODAY_DATE, + ]); + } else { + logWarn('Timeline: no pipeline end date. dateRange will stay null'); + setTimelineIsLoading(false); + } } catch (e) { displayError(e, t('errors.while-loading-pipeline-range')); setTimelineIsLoading(false); @@ -165,13 +168,16 @@ export const useTimelineContext = (): ContextProps => { } function loadMoreDays(when: 'past' | 'future', nDays: number) { - const existingRange = queriedDateRange || initialQueryRange; + if (!queriedDateRange) { + logWarn('No queriedDateRange yet - early return from loadMoreDays'); + return; + } logDebug(`Timeline: loadMoreDays, ${nDays} days into the ${when}; - queriedDateRange = ${queriedDateRange}; existingRange = ${existingRange}`); + queriedDateRange = ${queriedDateRange}`); return loadDateRange( when == 'past' - ? [isoDateWithOffset(existingRange[0], -nDays), existingRange[1]] - : [existingRange[0], isoDateWithOffset(existingRange[1], nDays)], + ? [isoDateWithOffset(queriedDateRange[0], -nDays), queriedDateRange[1]] + : [queriedDateRange[0], isoDateWithOffset(queriedDateRange[1], nDays)], ); } @@ -182,13 +188,12 @@ export const useTimelineContext = (): ContextProps => { return; } const pipelineStartDate = DateTime.fromSeconds(pipelineRange.start_ts).toISODate(); - const todayDate = DateTime.now().toISODate(); - // clamp range to ensure it is within [pipelineStartDate, todayDate] + // clamp range to ensure it is within [pipelineStartDate, TODAY_DATE] const clampedDateRange: [string, string] = [ new Date(range[0]) < new Date(pipelineStartDate) ? pipelineStartDate : range[0], - new Date(range[1]) > new Date(todayDate) ? todayDate : range[1], + new Date(range[1]) > new Date(TODAY_DATE) ? TODAY_DATE : range[1], ]; - if (clampedDateRange[0] != dateRange[0] || clampedDateRange[1] != dateRange[1]) { + if (clampedDateRange[0] != dateRange?.[0] || clampedDateRange[1] != dateRange?.[1]) { logDebug('Timeline: loadDateRange setting new date range = ' + clampedDateRange); setTimelineIsLoading('queued'); setDateRange(clampedDateRange); @@ -259,7 +264,7 @@ export const useTimelineContext = (): ContextProps => { try { logDebug('timelineContext: refreshTimeline'); setTimelineIsLoading('replace'); - setDateRange(initialQueryRange); + setDateRange(null); setQueriedDateRange(null); setTimelineMap(null); setRefreshTime(new Date()); diff --git a/www/js/diary/list/DateSelect.tsx b/www/js/diary/list/DateSelect.tsx index 849f23188..d79568e91 100644 --- a/www/js/diary/list/DateSelect.tsx +++ b/www/js/diary/list/DateSelect.tsx @@ -31,7 +31,7 @@ const DateSelect = ({ mode, onChoose, ...rest }: Props) => { const { colors } = useTheme(); const [open, setOpen] = React.useState(false); const minMaxDates = useMemo(() => { - if (!pipelineRange) return { startDate: new Date(), endDate: new Date() }; + if (!pipelineRange?.start_ts) return { startDate: new Date(), endDate: new Date() }; return { startDate: new Date(pipelineRange?.start_ts * 1000), // start of pipeline endDate: new Date(), // today diff --git a/www/js/metrics/MetricsTab.tsx b/www/js/metrics/MetricsTab.tsx index 6393dc518..3d4748f67 100644 --- a/www/js/metrics/MetricsTab.tsx +++ b/www/js/metrics/MetricsTab.tsx @@ -111,7 +111,7 @@ const MetricsTab = () => { const [aggMetricsIsLoading, setAggMetricsIsLoading] = useState(false); const readyToLoad = useMemo(() => { - if (!appConfig) return false; + if (!appConfig || !dateRange) return false; const dateRangeDays = isoDatesDifference(...dateRange); if (dateRangeDays < N_DAYS_TO_LOAD) { logDebug('MetricsTab: not enough days loaded, trying to load more'); @@ -132,7 +132,7 @@ const MetricsTab = () => { }, [readyToLoad, appConfig, timelineIsLoading, timelineMap, timelineLabelMap]); useEffect(() => { - if (!readyToLoad || !appConfig) return; + if (!readyToLoad || !appConfig || !dateRange) return; logDebug('MetricsTab: ready to fetch aggMetrics'); setAggMetricsIsLoading(true); fetchAggMetrics(metricList, dateRange, appConfig).then((response) => {