Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix schedule view input #1021

Merged
merged 17 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9eb732c
feat(service-time-range-retriever): Introduce service time range retr…
binh-dam-ibigroup Sep 29, 2023
4961f16
refactor(service-time-range-retriever): Fix import and redux update.
binh-dam-ibigroup Sep 29, 2023
456810c
refactor(otp-reducer): Add logic to handle pending and error retrievi…
binh-dam-ibigroup Sep 29, 2023
f5ee9c2
improvement(StopViewer): Apply serviceTimeRange to date picker in sch…
binh-dam-ibigroup Sep 29, 2023
19eb1e7
refactor(stop-viewer): Add TODOs for upcoming tasks [skip ci]
binh-dam-ibigroup Sep 29, 2023
38dc85e
docs: Fix typos
binh-dam-ibigroup Oct 2, 2023
f982d0d
improvement(StopViewer): Send schedule request only with date within …
binh-dam-ibigroup Oct 3, 2023
8e8edde
test(percy): Update mock.har
binh-dam-ibigroup Oct 3, 2023
1098175
Merge branch 'dev' into fix-schedule-view-input
binh-dam-ibigroup Oct 3, 2023
4c84ef3
refactor(StopViewer): Use the date-fns format functions correctly.
binh-dam-ibigroup Oct 3, 2023
929e91d
improvement(StopViewer): Make the sched view tz code based on input d…
binh-dam-ibigroup Oct 3, 2023
d4c5ac2
fix(StopViewer): Compute schedule TZ only if valid date is entered.
binh-dam-ibigroup Oct 4, 2023
0388ec4
docs(StopViewer): Fix typo [skip ci]
binh-dam-ibigroup Oct 4, 2023
4d564a8
Merge branch 'dev' into fix-schedule-view-input
binh-dam-ibigroup Oct 4, 2023
4d1e615
improvement(stop-viewer): Hide timezone warning if date is invalid.
binh-dam-ibigroup Oct 12, 2023
cc7e503
improvement(stop-viewer): Show message and hide schedule for invalid …
binh-dam-ibigroup Oct 12, 2023
3b17891
Merge branch 'dev' into fix-schedule-view-input
binh-dam-ibigroup Oct 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions __tests__/components/viewers/__snapshots__/stop-viewer.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ exports[`components > viewers > stop viewer should render countdown times after
>
<StopViewer
autoRefreshStopTimes={true}
calendarMax="2024-12-31"
calendarMin="2023-01-01"
enableFavoriteStops={false}
favoriteStops={Array []}
fetchStopInfo={[Function]}
Expand Down Expand Up @@ -353,6 +355,11 @@ exports[`components > viewers > stop viewer should render countdown times after
}
/>
</Connect(PageTitle)>
<Connect(ServiceTimeRangeRetriever)>
<ServiceTimeRangeRetriever
retrieveServiceTimeRangeIfNeeded={[Function]}
/>
</Connect(ServiceTimeRangeRetriever)>
<div
className="stop-viewer-header"
>
Expand Down Expand Up @@ -2813,6 +2820,8 @@ exports[`components > viewers > stop viewer should render countdown times for st
>
<StopViewer
autoRefreshStopTimes={true}
calendarMax="2024-12-31"
calendarMin="2023-01-01"
enableFavoriteStops={false}
favoriteStops={Array []}
fetchStopInfo={[Function]}
Expand Down Expand Up @@ -2966,6 +2975,11 @@ exports[`components > viewers > stop viewer should render countdown times for st
}
/>
</Connect(PageTitle)>
<Connect(ServiceTimeRangeRetriever)>
<ServiceTimeRangeRetriever
retrieveServiceTimeRangeIfNeeded={[Function]}
/>
</Connect(ServiceTimeRangeRetriever)>
<div
className="stop-viewer-header"
>
Expand Down Expand Up @@ -4524,6 +4538,8 @@ exports[`components > viewers > stop viewer should render times after midnight w
>
<StopViewer
autoRefreshStopTimes={true}
calendarMax="2024-12-31"
calendarMin="2023-01-01"
enableFavoriteStops={false}
favoriteStops={Array []}
fetchStopInfo={[Function]}
Expand Down Expand Up @@ -4776,6 +4792,11 @@ exports[`components > viewers > stop viewer should render times after midnight w
}
/>
</Connect(PageTitle)>
<Connect(ServiceTimeRangeRetriever)>
<ServiceTimeRangeRetriever
retrieveServiceTimeRangeIfNeeded={[Function]}
/>
</Connect(ServiceTimeRangeRetriever)>
<div
className="stop-viewer-header"
>
Expand Down Expand Up @@ -7279,6 +7300,8 @@ exports[`components > viewers > stop viewer should render with OTP transit index
>
<StopViewer
autoRefreshStopTimes={true}
calendarMax="2024-12-31"
calendarMin="2023-01-01"
enableFavoriteStops={false}
favoriteStops={Array []}
fetchStopInfo={[Function]}
Expand Down Expand Up @@ -7789,6 +7812,11 @@ exports[`components > viewers > stop viewer should render with OTP transit index
}
/>
</Connect(PageTitle)>
<Connect(ServiceTimeRangeRetriever)>
<ServiceTimeRangeRetriever
retrieveServiceTimeRangeIfNeeded={[Function]}
/>
</Connect(ServiceTimeRangeRetriever)>
<div
className="stop-viewer-header"
>
Expand Down Expand Up @@ -13935,6 +13963,8 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
>
<StopViewer
autoRefreshStopTimes={true}
calendarMax="2024-12-31"
calendarMin="2023-01-01"
enableFavoriteStops={false}
favoriteStops={Array []}
fetchStopInfo={[Function]}
Expand Down Expand Up @@ -14440,6 +14470,11 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
}
/>
</Connect(PageTitle)>
<Connect(ServiceTimeRangeRetriever)>
<ServiceTimeRangeRetriever
retrieveServiceTimeRangeIfNeeded={[Function]}
/>
</Connect(ServiceTimeRangeRetriever)>
<div
className="stop-viewer-header"
>
Expand Down Expand Up @@ -18461,6 +18496,8 @@ exports[`components > viewers > stop viewer should render with initial stop id a
>
<StopViewer
autoRefreshStopTimes={true}
calendarMax="2024-12-31"
calendarMin="2023-01-01"
enableFavoriteStops={false}
favoriteStops={Array []}
fetchStopInfo={[Function]}
Expand Down Expand Up @@ -18548,6 +18585,11 @@ exports[`components > viewers > stop viewer should render with initial stop id a
}
/>
</Connect(PageTitle)>
<Connect(ServiceTimeRangeRetriever)>
<ServiceTimeRangeRetriever
retrieveServiceTimeRangeIfNeeded={[Function]}
/>
</Connect(ServiceTimeRangeRetriever)>
<div
className="stop-viewer-header"
>
Expand Down
27 changes: 27 additions & 0 deletions lib/actions/apiV2.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -976,13 +977,39 @@ export function routingQuery(searchId = null, updateSearchInReducer) {
}
}

const requestingServiceTimeRange = createAction('SERVICE_TIME_RANGE_REQUEST')
const receivedServiceTimeRange = createAction('SERVICE_TIME_RANGE_RESPONSE')
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(
`{
serviceTimeRange {
start
end
}
}`,
{},
receivedServiceTimeRange,
receivedServiceTimeRangeError,
{}
)
)
}

export default {
fetchStopInfo,
findPatternsForRoute,
findRoute,
findRoutes,
findTrip,
getVehiclePositionsForRoute,
retrieveServiceTimeRangeIfNeeded,
routingQuery,
vehicleRentalQuery
}
32 changes: 32 additions & 0 deletions lib/components/util/service-time-range-retriever.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { connect } from 'react-redux'
import { useEffect } from 'react'

import 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)
76 changes: 64 additions & 12 deletions lib/components/viewers/stop-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ 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, 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'
import { Search } from '@styled-icons/fa-solid/Search'
import { Star as StarRegular } from '@styled-icons/fa-regular/Star'
Expand All @@ -24,10 +25,11 @@ 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'
import ServiceTimeRangeRetriever from '../util/service-time-range-retriever'
import Strong from '../util/strong-text'
import withMap from '../map/with-map'

Expand All @@ -36,9 +38,13 @@ import StopScheduleTable from './stop-schedule-table'

const { getCurrentDate, getUserTimezone } = coreUtils.time

/** The native date format used with <input type="date" /> 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
}
Expand Down Expand Up @@ -180,8 +186,16 @@ class StopViewer extends Component {
}

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
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 })
}

Expand Down Expand Up @@ -272,11 +286,11 @@ class StopViewer extends Component {

/**
* Plan trip from/to here buttons, plus the schedule/next arrivals toggle.
* TODO: Can this use SetFromToButtons?
*/
_renderControls = () => {
const { homeTimezone, intl, stopData } = this.props
const { isShowingSchedule } = this.state
const { calendarMax, calendarMin, homeTimezone, intl, stopData } =
this.props
const { date, isShowingSchedule } = this.state
const inHomeTimezone = homeTimezone && homeTimezone === getUserTimezone()

// Rewrite stop ID to not include Agency prefix, if present
Expand All @@ -292,11 +306,21 @@ class StopViewer extends Component {

let timezoneWarning
if (!inHomeTimezone) {
const timezoneCode = format(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 live and
// schedule views are in different daylight saving periods.
const timezoneCode = formatTz(
isShowingSchedule && date
? parse(date, inputDateFormat, new Date())
: new Date(), // TODO: mock for percy tests,
'z',
{
Comment on lines +315 to +320
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is causing the timezone code to change & update in the alert if the year starts with 0, which is a little jarring as you're typing.
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed in 4d1e615.

// 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).
Expand Down Expand Up @@ -361,6 +385,8 @@ class StopViewer extends Component {
id: 'components.StopViewer.findSchedule'
})}
className="pull-right"
max={calendarMax}
min={calendarMin}
onChange={this.handleDateChange}
onKeyDown={this.props.onKeyDown}
required
Expand Down Expand Up @@ -453,6 +479,7 @@ class StopViewer extends Component {
return (
<div className="stop-viewer base-color-bg">
<PageTitle title={this.getTitle()} />
<ServiceTimeRangeRetriever />
{/* Header Block */}
{this._renderHeader(agencyCount)}

Expand All @@ -475,7 +502,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
Expand All @@ -484,8 +511,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),
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.)
// 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),
inputDateFormat
)

return {
autoRefreshStopTimes,
calendarMax,
calendarMin,
enableFavoriteStops: getPersistenceMode(persistence).isLocalStorage,
favoriteStops,
homeTimezone,
Expand Down
12 changes: 12 additions & 0 deletions lib/reducers/create-otp-reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +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
}
Expand Down
Loading
Loading