diff --git a/example-config.yml b/example-config.yml index 3dd4183d1..0312aa3ac 100644 --- a/example-config.yml +++ b/example-config.yml @@ -112,6 +112,11 @@ persistence: # iconUrl: '' # href: '' +### These settings are only used for the field trip features. +dateTime: + timeFormat: h:mm a + dateFormat: MM/dd/yyyy + map: initLat: 45.52 initLon: -122.682 @@ -411,6 +416,8 @@ itinerary: displayA11yError: false # Whether to display itinerary info in the side of the preview or next to the departure times showInlineItinerarySummary: false + # Whether to sync the sort type with the depart/arrive time in the date/time modal + syncSortWithDepartArrive: true # The sort option to use by default # Available sort options: 'BEST', 'DURATION', 'ARRIVALTIME', 'WALKTIME', 'COST', 'DEPARTURETIME' # defaultSort: "BEST" # Default diff --git a/lib/components/form/date-time-modal.js b/lib/components/form/date-time-modal.js deleted file mode 100644 index 0936ebbe9..000000000 --- a/lib/components/form/date-time-modal.js +++ /dev/null @@ -1,76 +0,0 @@ -// TODO: TypeScript with props. -/* eslint-disable react/prop-types */ -import { connect } from 'react-redux' -import coreUtils from '@opentripplanner/core-utils' -import PropTypes from 'prop-types' -import React, { Component } from 'react' - -import { setQueryParam } from '../../actions/form' - -import { StyledDateTimeSelector } from './styled' - -class DateTimeModal extends Component { - static propTypes = { - setQueryParam: PropTypes.func - } - - render() { - const { - config, - date, - dateFormatLegacy, - departArrive, - setQueryParam, - time, - timeFormatLegacy - } = this.props - const { homeTimezone, isTouchScreenOnDesktop } = config - const touchClassName = isTouchScreenOnDesktop - ? 'with-desktop-touchscreen' - : '' - - return ( -
-
- `. - // These props are not relevant in modern browsers, - // where `` already - // formats the time|date according to the OS settings. - // eslint-disable-next-line react/jsx-sort-props - timeFormatLegacy={timeFormatLegacy} - timeZone={homeTimezone} - /> -
-
- ) - } -} - -const mapStateToProps = (state) => { - const { date, departArrive, time } = state.otp.currentQuery - const config = state.otp.config - return { - config, - date, - // This prop is for legacy browsers (see render method above). - dateFormatLegacy: coreUtils.time.getDateFormat(config), - departArrive, - time, - // This prop is for legacy browsers (see render method above). - timeFormatLegacy: coreUtils.time.getTimeFormat(config) - } -} - -const mapDispatchToProps = { - setQueryParam -} - -export default connect(mapStateToProps, mapDispatchToProps)(DateTimeModal) diff --git a/lib/components/form/date-time-modal.tsx b/lib/components/form/date-time-modal.tsx new file mode 100644 index 000000000..e9f31407b --- /dev/null +++ b/lib/components/form/date-time-modal.tsx @@ -0,0 +1,116 @@ +import { connect } from 'react-redux' +import coreUtils from '@opentripplanner/core-utils' +import React, { useCallback } from 'react' + +import * as formActions from '../../actions/form' +import * as narrativeActions from '../../actions/narrative' +import { AppConfig } from '../../util/config-types' +import { AppReduxState, FilterType, SortType } from '../../util/state-types' + +import { StyledDateTimeSelector } from './styled' + +type Props = { + config: AppConfig + date: string + dateFormatLegacy?: string + departArrive: DepartArriveValue + setQueryParam: (params: any) => void + sort: SortType + time: string + timeFormatLegacy?: string + updateItineraryFilter: (payload: FilterType) => void +} + +type DepartArriveValue = 'NOW' | 'DEPART' | 'ARRIVE' + +const DepartArriveTypeMap: Record< + DepartArriveValue, + FilterType['sort']['type'] +> = { + ARRIVE: 'ARRIVALTIME', + DEPART: 'DEPARTURETIME', + NOW: 'DURATION' +} + +function DateTimeModal({ + config, + date, + dateFormatLegacy, + departArrive, + setQueryParam, + sort, + time, + timeFormatLegacy, + updateItineraryFilter +}: Props) { + const { homeTimezone, isTouchScreenOnDesktop } = config + const touchClassName = isTouchScreenOnDesktop + ? 'with-desktop-touchscreen' + : '' + + const syncSortWithDepartArrive = config?.itinerary?.syncSortWithDepartArrive + // Note the side effect that this will resort the results of a previous query + // if the user changes the depart/arrive setting before the query is run. + const setQueryParamMiddleware = useCallback( + (params: any) => { + if (syncSortWithDepartArrive) { + updateItineraryFilter({ + sort: { + ...sort, + type: DepartArriveTypeMap[params.departArrive as DepartArriveValue] + } + }) + } + setQueryParam(params) + }, + [setQueryParam, updateItineraryFilter, sort, syncSortWithDepartArrive] + ) + return ( +
+
+ `. + // These props are not relevant in modern browsers, + // where `` already + // formats the time|date according to the OS settings. + // eslint-disable-next-line react/jsx-sort-props + timeFormatLegacy={timeFormatLegacy} + timeZone={homeTimezone} + /> +
+
+ ) +} + +const mapStateToProps = (state: AppReduxState) => { + const { date, departArrive, time } = state.otp.currentQuery + const config = state.otp.config + const { sort } = state.otp.filter + return { + config, + date, + // This prop is for legacy browsers (see render method above). + // @ts-expect-error Mismatched config types + dateFormatLegacy: coreUtils.time.getDateFormat(config), + departArrive, + sort, + time, + // This prop is for legacy browsers (see render method above). + // @ts-expect-error Mismatched config types + timeFormatLegacy: coreUtils.time.getTimeFormat(config) + } +} + +const mapDispatchToProps = { + setQueryParam: formActions.setQueryParam, + updateItineraryFilter: narrativeActions.updateItineraryFilter +} + +export default connect(mapStateToProps, mapDispatchToProps)(DateTimeModal) diff --git a/lib/components/narrative/narrative-itineraries-header.tsx b/lib/components/narrative/narrative-itineraries-header.tsx index 46fe56dff..921140c09 100644 --- a/lib/components/narrative/narrative-itineraries-header.tsx +++ b/lib/components/narrative/narrative-itineraries-header.tsx @@ -46,7 +46,6 @@ export default function NarrativeItinerariesHeader({ enabledSortModes, errors, itineraries, - itinerary, itineraryIsExpanded, onSortChange, onSortDirChange, @@ -109,13 +108,6 @@ export default function NarrativeItinerariesHeader({ const sortOptionsArr = sortOptions(intl, enabledSortModes) const sortText = sortOptionsArr.find((x) => x.value === sort.type)?.text - const handleSortClick = useCallback( - (value) => { - onSortChange(value) - }, - [onSortChange] - ) - return (
handleSortClick(sortOption.value)} + onClick={() => onSortChange(sortOption.value)} role="option" > {sortOption.text} diff --git a/lib/util/config-types.ts b/lib/util/config-types.ts index 120bf1bad..3f71126cb 100644 --- a/lib/util/config-types.ts +++ b/lib/util/config-types.ts @@ -291,6 +291,7 @@ export interface ItineraryConfig { showPlanFirstLastButtons?: boolean showRouteFares?: boolean sortModes?: ItinerarySortOption[] + syncSortWithDepartArrive?: boolean weights?: ItineraryCostWeights } @@ -362,6 +363,11 @@ export interface StopScheduleViewerConfig { showBlockIds?: boolean } +export interface DateTimeConfig { + dateFormat: string + timeFormat: string +} + /** The main application configuration object */ export interface AppConfig { accessibilityScore?: AccessibilityScoreConfig @@ -375,6 +381,7 @@ export interface AppConfig { bugsnag?: BugsnagConfig co2?: CO2Config companies?: Company[] + dateTime?: DateTimeConfig elevationProfile?: boolean extraMenuItems?: AppMenuItemConfig[] geocoder: GeocoderConfig diff --git a/lib/util/state-types.ts b/lib/util/state-types.ts index 99c321a3b..4c5c70a3b 100644 --- a/lib/util/state-types.ts +++ b/lib/util/state-types.ts @@ -14,11 +14,7 @@ export interface OtpState { activeSearchId?: string config: AppConfig currentQuery: any - filter: { - sort: { - type: string - } - } + filter: FilterType location: any modeSettingDefinitions: ModeSetting[] overlay: any @@ -31,6 +27,15 @@ export interface OtpState { ui: any // TODO } +export interface SortType { + direction: string + type: string +} + +export interface FilterType { + sort: SortType +} + export interface UserState { itineraryExistence?: ItineraryExistence localUser?: any