From 9eb732c74cc58e0bfb96888d849d69b383d21c6f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:46:47 -0400 Subject: [PATCH 01/14] feat(service-time-range-retriever): Introduce service time range retriever component, action, and re --- lib/actions/apiV2.js | 28 ++++++++++++++++ .../util/service-time-range-retriever.ts | 32 +++++++++++++++++++ lib/reducers/create-otp-reducer.js | 4 +++ 3 files changed, 64 insertions(+) create mode 100644 lib/components/util/service-time-range-retriever.ts diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index ae64d539c..4710cc6f0 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -2,6 +2,7 @@ import { aggregateModes, populateSettingWithValue } from '@opentripplanner/trip-form' +import { createAction } from 'redux-actions' import { decodeQueryParams, DelimitedArrayParam } from 'use-query-params' import clone from 'clone' import coreUtils from '@opentripplanner/core-utils' @@ -976,6 +977,32 @@ export function routingQuery(searchId = null, updateSearchInReducer) { } } +const receivedServiceTimeRange = createAction('SERVICE_TIME_RANGE_RESPONSE') +// Not handled +const receivedServiceTimeRangeError = createAction( + 'SERVICE_TIME_RANGE_RESPONSE_ERROR' +) + +/** Queries for service time range. */ +const retrieveServiceTimeRangeIfNeeded = () => + function (dispatch, getState) { + if (getState().otp.serviceTimeRange) return + return dispatch( + createGraphQLQueryAction( + `{ + serviceTimeRange { + start + end + } + }`, + {}, + receivedServiceTimeRange, + receivedServiceTimeRangeError, + {} + ) + ) + } + export default { fetchStopInfo, findPatternsForRoute, @@ -983,6 +1010,7 @@ export default { findRoutes, findTrip, getVehiclePositionsForRoute, + retrieveServiceTimeRangeIfNeeded, routingQuery, vehicleRentalQuery } diff --git a/lib/components/util/service-time-range-retriever.ts b/lib/components/util/service-time-range-retriever.ts new file mode 100644 index 000000000..2707ceb16 --- /dev/null +++ b/lib/components/util/service-time-range-retriever.ts @@ -0,0 +1,32 @@ +import { connect } from 'react-redux' +import { useEffect } from 'react' + +import * as apiActionsV2 from '../../actions/apiV2' + +interface Props { + retrieveServiceTimeRangeIfNeeded: () => void +} + +/** + * Invisible component that retrieves the date range available + * for OTP planning and schedule retrieval. + */ +const ServiceTimeRangeRetriever = ({ + retrieveServiceTimeRangeIfNeeded +}: Props): null => { + // If not already done, retrieve the OTP available date range on mount. + useEffect(retrieveServiceTimeRangeIfNeeded, [ + retrieveServiceTimeRangeIfNeeded + ]) + + // Component renders nothing + return null +} + +// Connect to redux +const mapDispatchToProps = { + retrieveServiceTimeRangeIfNeeded: + apiActionsV2.retrieveServiceTimeRangeIfNeeded +} + +export default connect(null, mapDispatchToProps)(ServiceTimeRangeRetriever) diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 0888474e9..7c33ebe0c 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -1088,6 +1088,10 @@ function createOtpReducer(config) { } } }) + case 'SERVICE_TIME_RANGE_RESPONSE': + return update(state, { + serviceTimeRange: { $set: action.payload } + }) default: return state } From 4961f16a34429a301f0258ea2e647804bdf2425a Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:00:19 -0400 Subject: [PATCH 02/14] refactor(service-time-range-retriever): Fix import and redux update. --- lib/components/util/service-time-range-retriever.ts | 2 +- lib/components/viewers/stop-viewer.js | 2 ++ lib/reducers/create-otp-reducer.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/components/util/service-time-range-retriever.ts b/lib/components/util/service-time-range-retriever.ts index 2707ceb16..c52ffb48f 100644 --- a/lib/components/util/service-time-range-retriever.ts +++ b/lib/components/util/service-time-range-retriever.ts @@ -1,7 +1,7 @@ import { connect } from 'react-redux' import { useEffect } from 'react' -import * as apiActionsV2 from '../../actions/apiV2' +import apiActionsV2 from '../../actions/apiV2' interface Props { retrieveServiceTimeRangeIfNeeded: () => void diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index f907f0d88..923d9dde1 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -28,6 +28,7 @@ import { navigateBack } from '../../util/ui' import { stopIsFlex } from '../../util/viewer' import OperatorLogo from '../util/operator-logo' import PageTitle from '../util/page-title' +import ServiceTimeRangeRetriever from '../util/service-time-range-retriever' import Strong from '../util/strong-text' import withMap from '../map/with-map' @@ -453,6 +454,7 @@ class StopViewer extends Component { return (
+ {/* Header Block */} {this._renderHeader(agencyCount)} diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 7c33ebe0c..ea9d52304 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -1090,7 +1090,7 @@ function createOtpReducer(config) { }) case 'SERVICE_TIME_RANGE_RESPONSE': return update(state, { - serviceTimeRange: { $set: action.payload } + serviceTimeRange: { $set: action.payload.data.serviceTimeRange } }) default: return state From 456810cd0492ac890ecbae74e4cda2637a3b840c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 29 Sep 2023 11:38:11 -0400 Subject: [PATCH 03/14] refactor(otp-reducer): Add logic to handle pending and error retrieving service time range. --- lib/actions/apiV2.js | 7 +++---- lib/components/util/service-time-range-retriever.ts | 6 +++--- lib/reducers/create-otp-reducer.js | 8 ++++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 4710cc6f0..a1577ee7a 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -977,16 +977,15 @@ export function routingQuery(searchId = null, updateSearchInReducer) { } } +const requestingServiceTimeRange = createAction('SERVICE_TIME_RANGE_REQUEST') const receivedServiceTimeRange = createAction('SERVICE_TIME_RANGE_RESPONSE') -// Not handled -const receivedServiceTimeRangeError = createAction( - 'SERVICE_TIME_RANGE_RESPONSE_ERROR' -) +const receivedServiceTimeRangeError = createAction('SERVICE_TIME_RANGE_ERROR') /** Queries for service time range. */ const retrieveServiceTimeRangeIfNeeded = () => function (dispatch, getState) { if (getState().otp.serviceTimeRange) return + dispatch(requestingServiceTimeRange) return dispatch( createGraphQLQueryAction( `{ diff --git a/lib/components/util/service-time-range-retriever.ts b/lib/components/util/service-time-range-retriever.ts index c52ffb48f..6a0625fb7 100644 --- a/lib/components/util/service-time-range-retriever.ts +++ b/lib/components/util/service-time-range-retriever.ts @@ -15,9 +15,9 @@ const ServiceTimeRangeRetriever = ({ retrieveServiceTimeRangeIfNeeded }: Props): null => { // If not already done, retrieve the OTP available date range on mount. - useEffect(retrieveServiceTimeRangeIfNeeded, [ - retrieveServiceTimeRangeIfNeeded - ]) + useEffect(() => { + retrieveServiceTimeRangeIfNeeded() + }, [retrieveServiceTimeRangeIfNeeded]) // Component renders nothing return null diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index ea9d52304..6fc02bb58 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -1088,10 +1088,18 @@ function createOtpReducer(config) { } } }) + case 'SERVICE_TIME_RANGE_REQUEST': + return update(state, { + serviceTimeRange: { $set: { pending: true } } + }) case 'SERVICE_TIME_RANGE_RESPONSE': return update(state, { serviceTimeRange: { $set: action.payload.data.serviceTimeRange } }) + case 'SERVICE_TIME_RANGE_ERROR': + return update(state, { + serviceTimeRange: { $set: { error: true } } + }) default: return state } From f5ee9c258dc5478b6180117963c2d01f2cfd4a32 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 29 Sep 2023 15:53:59 -0400 Subject: [PATCH 04/14] improvement(StopViewer): Apply serviceTimeRange to date picker in schedule view. --- .../viewers/__snapshots__/stop-viewer.js.snap | 42 +++++++++++++++++++ lib/components/viewers/stop-viewer.js | 35 ++++++++++++++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 411b2ec9d..323d9a7b5 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -101,6 +101,8 @@ exports[`components > viewers > stop viewer should render countdown times after > viewers > stop viewer should render countdown times after } /> + + +
@@ -3056,6 +3063,8 @@ exports[`components > viewers > stop viewer should render countdown times for st > viewers > stop viewer should render countdown times for st } /> + + +
@@ -4866,6 +4880,8 @@ exports[`components > viewers > stop viewer should render times after midnight w > viewers > stop viewer should render times after midnight w } /> + + +
@@ -7864,6 +7885,8 @@ exports[`components > viewers > stop viewer should render with OTP transit index > viewers > stop viewer should render with OTP transit index } /> + + +
@@ -14937,6 +14965,8 @@ exports[`components > viewers > stop viewer should render with TriMet transit in > viewers > stop viewer should render with TriMet transit in } /> + + +
@@ -19706,6 +19741,8 @@ exports[`components > viewers > stop viewer should render with initial stop id a > viewers > stop viewer should render with initial stop id a } /> + + +
diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 923d9dde1..1e3573382 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -4,12 +4,13 @@ import { ArrowLeft } from '@styled-icons/fa-solid/ArrowLeft' import { Calendar } from '@styled-icons/fa-solid/Calendar' import { Clock } from '@styled-icons/fa-regular/Clock' import { connect } from 'react-redux' -import { format } from 'date-fns-tz' +import { format } from 'date-fns' import { FormattedMessage, injectIntl } from 'react-intl' import { InfoCircle } from '@styled-icons/fa-solid/InfoCircle' import { Search } from '@styled-icons/fa-solid/Search' import { Star as StarRegular } from '@styled-icons/fa-regular/Star' import { Star as StarSolid } from '@styled-icons/fa-solid/Star' +import { utcToZonedTime } from 'date-fns-tz' import coreUtils from '@opentripplanner/core-utils' import dateFnsUSLocale from 'date-fns/locale/en-US' import FromToLocationPicker from '@opentripplanner/from-to-location-picker' @@ -276,7 +277,8 @@ class StopViewer extends Component { * TODO: Can this use SetFromToButtons? */ _renderControls = () => { - const { homeTimezone, intl, stopData } = this.props + const { calendarMax, calendarMin, homeTimezone, intl, stopData } = + this.props const { isShowingSchedule } = this.state const inHomeTimezone = homeTimezone && homeTimezone === getUserTimezone() @@ -362,6 +364,8 @@ class StopViewer extends Component { id: 'components.StopViewer.findSchedule' })} className="pull-right" + max={calendarMax} + min={calendarMin} onChange={this.handleDateChange} onKeyDown={this.props.onKeyDown} required @@ -477,7 +481,7 @@ class StopViewer extends Component { const mapStateToProps = (state) => { const showUserSettings = getShowUserSettings(state) const stopViewerConfig = getStopViewerConfig(state) - const { config, transitIndex, ui } = state.otp + const { config, serviceTimeRange = {}, transitIndex, ui } = state.otp const { homeTimezone, language, persistence, stopViewer, transitOperators } = config const { autoRefreshStopTimes = true, favoriteStops } = state.user.localUser @@ -486,8 +490,33 @@ const mapStateToProps = (state) => { const nearbyStops = Array.from(new Set(stopData?.nearbyStops))?.map( (stopId) => stopLookup[stopId] ) + const now = new Date() + const thisYear = now.getFullYear() + const { end = 0, start = 0 } = serviceTimeRange + // If start is not provided, default to the first day of the current calendar year in the user's timezone. + // (No timezone conversion is needed in this case.) + // If start is provided in OTP, convert that date in the agency's home time zone. + const calendarMin = format( + start + ? utcToZonedTime(start * 1000, homeTimezone) + : new Date(thisYear, 0, 1), + 'yyyy-MM-dd' + ) + // If end is not provided, default to the last day of the next calendar year in the user's timezone. + // (No timezone conversion is needed in this case.) + // If end date is provided and falls at midnight agency time, + // use the previous second to get the last service day available. + const calendarMax = format( + end + ? utcToZonedTime((end - 1) * 1000, homeTimezone) + : new Date(thisYear + 1, 11, 31), + 'yyyy-MM-dd' + ) + return { autoRefreshStopTimes, + calendarMax, + calendarMin, enableFavoriteStops: getPersistenceMode(persistence).isLocalStorage, favoriteStops, homeTimezone, From 19eb1e70df7bf71f2129afb06e0626e5891c8a36 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 29 Sep 2023 16:09:54 -0400 Subject: [PATCH 05/14] refactor(stop-viewer): Add TODOs for upcoming tasks [skip ci] --- lib/components/viewers/stop-viewer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 1e3573382..60e109629 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -182,6 +182,9 @@ class StopViewer extends Component { } handleDateChange = (evt) => { + // TODO: check for empty date and that date is within range. + // (Users can enter a date outside of the range using the Up/Down arrow keys in Firefox and Safari.) + console.log('Date changed to ' + evt.target.value) const date = evt.target.value this._findStopTimesForDate(date) this.setState({ date }) @@ -458,6 +461,7 @@ class StopViewer extends Component { return (
+ {/* TODO: Add the corresponding mock query in percy tests. */} {/* Header Block */} {this._renderHeader(agencyCount)} From 38dc85e90a49088715a4f455f70c4c2f571e4685 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 2 Oct 2023 19:49:03 -0400 Subject: [PATCH 06/14] docs: Fix typos --- lib/components/viewers/next-arrival-for-pattern.tsx | 2 +- lib/util/viewer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/components/viewers/next-arrival-for-pattern.tsx b/lib/components/viewers/next-arrival-for-pattern.tsx index 2fceb4cf8..bed35582c 100644 --- a/lib/components/viewers/next-arrival-for-pattern.tsx +++ b/lib/components/viewers/next-arrival-for-pattern.tsx @@ -21,7 +21,7 @@ type Props = { pattern: Pattern // Not the true operator type, but the one that's used here // It is annoying to shoehorn the operator in here like this, but passing - // it in indvidually would cause a situation where a list of routes + // it in individually would cause a situation where a list of routes // needs to be matched up with a list of operators route: Route & { operator?: { colorMode?: string } } routeColor: string diff --git a/lib/util/viewer.js b/lib/util/viewer.js index bdb27b3cb..9f1f9ef9d 100644 --- a/lib/util/viewer.js +++ b/lib/util/viewer.js @@ -40,7 +40,7 @@ function excludeLastStop(stopTime) { * Checks that the given route object from an OTP pattern is valid. * If it is not, logs a warning message. * - * FIXME: there is currently a bug with the alernative transit index + * FIXME: there is currently a bug with the alternative transit index * where routes are not associated with the stop if the only stoptimes * for the stop are drop off only. See https://github.com/ibi-group/trimet-mod-otp/issues/217 * From f982d0d8fd9f22c39f834c25db27c3f8090710fa Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:16:43 -0400 Subject: [PATCH 07/14] improvement(StopViewer): Send schedule request only with date within range. --- lib/components/viewers/stop-viewer.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 60e109629..519fd0fb6 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -25,7 +25,7 @@ import * as userActions from '../../actions/user' import { getPersistenceMode } from '../../util/user' import { getShowUserSettings, getStopViewerConfig } from '../../util/state' import { Icon, IconWithText, StyledIconWrapper } from '../util/styledIcon' -import { navigateBack } from '../../util/ui' +import { isBlank, navigateBack } from '../../util/ui' import { stopIsFlex } from '../../util/viewer' import OperatorLogo from '../util/operator-logo' import PageTitle from '../util/page-title' @@ -182,11 +182,16 @@ class StopViewer extends Component { } handleDateChange = (evt) => { - // TODO: check for empty date and that date is within range. + // Check for non-empty date, and that date is within range before making request. // (Users can enter a date outside of the range using the Up/Down arrow keys in Firefox and Safari.) - console.log('Date changed to ' + evt.target.value) const date = evt.target.value - this._findStopTimesForDate(date) + if (!isBlank(date)) { + const { calendarMax, calendarMin } = this.props + // Lazily using lexicographic order ("2023-04-01" > "2023-01-01") + if (date >= calendarMin && date <= calendarMax) { + this._findStopTimesForDate(date) + } + } this.setState({ date }) } @@ -277,7 +282,6 @@ class StopViewer extends Component { /** * Plan trip from/to here buttons, plus the schedule/next arrivals toggle. - * TODO: Can this use SetFromToButtons? */ _renderControls = () => { const { calendarMax, calendarMin, homeTimezone, intl, stopData } = From 8e8edde8ef2561ae05187aabd0270307aa07e1e4 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 3 Oct 2023 15:45:35 -0400 Subject: [PATCH 08/14] test(percy): Update mock.har --- percy/mock.har | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/percy/mock.har b/percy/mock.har index 168b2f6c5..5762498e1 100644 --- a/percy/mock.har +++ b/percy/mock.har @@ -327,6 +327,42 @@ "_blocked_queueing": 8.100999999442138 } }, + { + "request": { + "bodySize": 118, + "method": "POST", + "url": "http://localhost:9999/otp2/routers/default/index/graphql", + "httpVersion": "HTTP/2", + "queryString": [], + "postData": { + "mimeType": "application/json", + "text": "{\"query\":\"{\\n serviceTimeRange {\\n start\\n end\\n }\\n }\",\"variables\":{}}" + } + }, + "response": { + "status": 200, + "statusText": "", + "httpVersion": "HTTP/2", + "content": { + "mimeType": "application/json", + "size": 67, + "text": "{\"data\":{\"serviceTimeRange\":{\"start\":1661745600,\"end\":1735707600}}}" + }, + "headersSize": 502, + "bodySize": 569 + }, + "cache": {}, + "timings": { + "blocked": -1, + "dns": 0, + "connect": 0, + "ssl": 0, + "send": 0, + "wait": 204, + "receive": 0 + }, + "time": 204 + }, { "request": { "method": "GET", From 4c84ef3f2268ab487e0d02b86b2b9f45e280ae6c Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:27:33 -0400 Subject: [PATCH 09/14] refactor(StopViewer): Use the date-fns format functions correctly. --- lib/components/viewers/stop-viewer.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 519fd0fb6..34066ced5 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -6,11 +6,11 @@ import { Clock } from '@styled-icons/fa-regular/Clock' import { connect } from 'react-redux' import { format } from 'date-fns' import { FormattedMessage, injectIntl } from 'react-intl' +import { format as formatTz, utcToZonedTime } from 'date-fns-tz' import { InfoCircle } from '@styled-icons/fa-solid/InfoCircle' import { Search } from '@styled-icons/fa-solid/Search' import { Star as StarRegular } from '@styled-icons/fa-regular/Star' import { Star as StarSolid } from '@styled-icons/fa-solid/Star' -import { utcToZonedTime } from 'date-fns-tz' import coreUtils from '@opentripplanner/core-utils' import dateFnsUSLocale from 'date-fns/locale/en-US' import FromToLocationPicker from '@opentripplanner/from-to-location-picker' @@ -302,7 +302,7 @@ class StopViewer extends Component { let timezoneWarning if (!inHomeTimezone) { - const timezoneCode = format(Date.now(), 'z', { + const timezoneCode = formatTz(Date.now(), 'z', { // To avoid ambiguities for now, use the English-US timezone abbreviations ("EST", "PDT", etc.) locale: dateFnsUSLocale, timeZone: homeTimezone @@ -465,7 +465,6 @@ class StopViewer extends Component { return (
- {/* TODO: Add the corresponding mock query in percy tests. */} {/* Header Block */} {this._renderHeader(agencyCount)} From 929e91dcaa3a6469130f92973cc1e03bb1328b21 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:49:22 -0400 Subject: [PATCH 10/14] improvement(StopViewer): Make the sched view tz code based on input date. --- lib/components/viewers/stop-viewer.js | 32 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 34066ced5..680f10c3d 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -4,7 +4,7 @@ import { ArrowLeft } from '@styled-icons/fa-solid/ArrowLeft' import { Calendar } from '@styled-icons/fa-solid/Calendar' import { Clock } from '@styled-icons/fa-regular/Clock' import { connect } from 'react-redux' -import { format } from 'date-fns' +import { format, parse } from 'date-fns' import { FormattedMessage, injectIntl } from 'react-intl' import { format as formatTz, utcToZonedTime } from 'date-fns-tz' import { InfoCircle } from '@styled-icons/fa-solid/InfoCircle' @@ -38,9 +38,13 @@ import StopScheduleTable from './stop-schedule-table' const { getCurrentDate, getUserTimezone } = coreUtils.time +/** The native date format used with elements */ +const inputDateFormat = 'yyyy-MM-dd' + function getDefaultState(timeZone) { return { // Compare dates/times in the stop viewer based on the agency's timezone. + // TODO: mock this date for percy tests. date: getCurrentDate(timeZone), isShowingSchedule: false } @@ -286,7 +290,7 @@ class StopViewer extends Component { _renderControls = () => { const { calendarMax, calendarMin, homeTimezone, intl, stopData } = this.props - const { isShowingSchedule } = this.state + const { date, isShowingSchedule } = this.state const inHomeTimezone = homeTimezone && homeTimezone === getUserTimezone() // Rewrite stop ID to not include Agency prefix, if present @@ -302,11 +306,21 @@ class StopViewer extends Component { let timezoneWarning if (!inHomeTimezone) { - const timezoneCode = formatTz(Date.now(), 'z', { - // To avoid ambiguities for now, use the English-US timezone abbreviations ("EST", "PDT", etc.) - locale: dateFnsUSLocale, - timeZone: homeTimezone - }) + // In schedule view, the time zone code should be that of the entered date, + // or the current day in the live view. + // This is to account for daylight time changes, especially when the liva and + // schedule views are in different daylight saving periods. + const timezoneCode = formatTz( + isShowingSchedule + ? parse(date, inputDateFormat, new Date()) + : new Date(), // TODO: mock for percy tests, + 'z', + { + // To avoid ambiguities for now, use the English-US timezone abbreviations ("EST", "PDT", etc.) + locale: dateFnsUSLocale, + timeZone: homeTimezone + } + ) // Display a banner about the departure timezone if user's timezone is not the configured 'homeTimezone' // (e.g. cases where a user in New York looks at a schedule in Los Angeles). @@ -507,7 +521,7 @@ const mapStateToProps = (state) => { start ? utcToZonedTime(start * 1000, homeTimezone) : new Date(thisYear, 0, 1), - 'yyyy-MM-dd' + inputDateFormat ) // If end is not provided, default to the last day of the next calendar year in the user's timezone. // (No timezone conversion is needed in this case.) @@ -517,7 +531,7 @@ const mapStateToProps = (state) => { end ? utcToZonedTime((end - 1) * 1000, homeTimezone) : new Date(thisYear + 1, 11, 31), - 'yyyy-MM-dd' + inputDateFormat ) return { From d4c5ac291e6938f60d9f7c0ee6ce638491af4e24 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:41:04 -0400 Subject: [PATCH 11/14] fix(StopViewer): Compute schedule TZ only if valid date is entered. --- lib/components/viewers/stop-viewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 680f10c3d..c714b3010 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -311,7 +311,7 @@ class StopViewer extends Component { // This is to account for daylight time changes, especially when the liva and // schedule views are in different daylight saving periods. const timezoneCode = formatTz( - isShowingSchedule + isShowingSchedule && date ? parse(date, inputDateFormat, new Date()) : new Date(), // TODO: mock for percy tests, 'z', From 0388ec440548f1e41ca85da3e382ded5cb1ab94f Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:51:55 -0400 Subject: [PATCH 12/14] docs(StopViewer): Fix typo [skip ci] --- lib/components/viewers/stop-viewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index c714b3010..0a712812d 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -308,7 +308,7 @@ class StopViewer extends Component { if (!inHomeTimezone) { // In schedule view, the time zone code should be that of the entered date, // or the current day in the live view. - // This is to account for daylight time changes, especially when the liva and + // This is to account for daylight time changes, especially when the live and // schedule views are in different daylight saving periods. const timezoneCode = formatTz( isShowingSchedule && date From 4d1e6153d292e5f4458d7e873458fe3afd3442f2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 12 Oct 2023 16:50:10 -0400 Subject: [PATCH 13/14] improvement(stop-viewer): Hide timezone warning if date is invalid. --- lib/components/viewers/stop-viewer.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/components/viewers/stop-viewer.js b/lib/components/viewers/stop-viewer.js index 0a712812d..48d91d3cb 100644 --- a/lib/components/viewers/stop-viewer.js +++ b/lib/components/viewers/stop-viewer.js @@ -185,16 +185,17 @@ class StopViewer extends Component { } } + _isDateWithinRange = (date) => { + const { calendarMax, calendarMin } = this.props + return !isBlank(date) && date >= calendarMin && date <= calendarMax + } + handleDateChange = (evt) => { // Check for non-empty date, and that date is within range before making request. // (Users can enter a date outside of the range using the Up/Down arrow keys in Firefox and Safari.) const date = evt.target.value - if (!isBlank(date)) { - const { calendarMax, calendarMin } = this.props - // Lazily using lexicographic order ("2023-04-01" > "2023-01-01") - if (date >= calendarMin && date <= calendarMax) { - this._findStopTimesForDate(date) - } + if (this._isDateWithinRange(date)) { + this._findStopTimesForDate(date) } this.setState({ date }) } @@ -305,7 +306,7 @@ class StopViewer extends Component { const isFlex = stopIsFlex(stopData) let timezoneWarning - if (!inHomeTimezone) { + if (!inHomeTimezone && this._isDateWithinRange(date)) { // In schedule view, the time zone code should be that of the entered date, // or the current day in the live view. // This is to account for daylight time changes, especially when the live and From cc7e5038fc25fb883d8d4a657e695e3bf2bd653e Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:28:58 -0400 Subject: [PATCH 14/14] improvement(stop-viewer): Show message and hide schedule for invalid date. --- .../viewers/__snapshots__/stop-viewer.js.snap | 425 ++++++++++++++++++ lib/components/viewers/stop-viewer.js | 29 +- 2 files changed, 446 insertions(+), 8 deletions(-) diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap index 98d4f6a63..4cf2c86dd 100644 --- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap +++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap @@ -832,6 +832,91 @@ exports[`components > viewers > stop viewer should render countdown times after + + +
+ +
+ + + + + + + + + + + + + + components.StopViewer.noStopsFound + + +
+
+
+
+
viewers > stop viewer should render countdown times for st + + +
+ +
+ + + + + + + + + + + + + + components.StopViewer.noStopsFound + + +
+
+
+
+
viewers > stop viewer should render times after midnight w + + +
+ +
+ + + + + + + + + + + + + + components.StopViewer.noStopsFound + + +
+
+
+
+
viewers > stop viewer should render with OTP transit index + + +
+ +
+ + + + + + + + + + + + + + components.StopViewer.noStopsFound + + +
+
+
+
+
viewers > stop viewer should render with TriMet transit in + + +
+ +
+ + + + + + + + + + + + + + components.StopViewer.noStopsFound + + +
+
+
+
+
+ + + + + ) + } + return (
- ) + if (this._isDateWithinRange(date)) { + contents = ( + + ) + } } else { contents = ( <>