Skip to content

Import new DT selector #1395

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

Merged
merged 18 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1,186 changes: 593 additions & 593 deletions __tests__/components/viewers/__snapshots__/nearby-view.js.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ exports[`components > viewers > stop viewer should render with initial stop id a
>
<Styled(styled.span)>
<span
className="sc-bSFUlv sc-jvfqPk gFxSud bAWwqY"
className="sc-hkwmXC sc-bSFUlv eFsrhn gAUjbw"
>
<ArrowLeft>
<StyledIconBase
Expand Down Expand Up @@ -267,11 +267,11 @@ exports[`components > viewers > stop viewer should render with initial stop id a
</div>
<styled.div>
<div
className="sc-hRxcUd bbPqpb"
className="sc-bTRMho fIdSEr"
>
<styled.div>
<div
className="sc-hYAvtR eOlYfy"
className="sc-iWRHom bUIXtU"
>
<h1>
<FormattedMessage
Expand Down
29 changes: 26 additions & 3 deletions lib/actions/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,40 @@ const checkIfOriginSetToCurrentPosition = (query, currentPosition) =>
query?.from?.lat === currentPosition?.coords?.latitude &&
query?.from?.lon === currentPosition?.coords?.longitude

function setTimeToNow() {
return function (dispatch, getState) {
const { homeTimezone } = getState().otp.config
const now = coreUtils.time.getCurrentTime(homeTimezone)
dispatch(settingQueryParam({ time: now }))
}
}
/**
* If departArrive is set to 'NOW', update the query time to current
*/
export function updateQueryTimeIfLeavingNow() {
return function (dispatch, getState) {
const state = getState()
const { currentQuery } = state.otp
if (currentQuery.departArrive === 'NOW') {
dispatch(setTimeToNow())
}
}
}
/**
* If d/t selector is blank, update the form fields on trip plan to avoid confusion
*/
export function updateDateTimeIfEmpty() {
return function (dispatch, getState) {
const state = getState()
const { config, currentQuery } = state.otp
const { date, time } = currentQuery
const { homeTimezone } = config
if (currentQuery.departArrive === 'NOW') {
const now = coreUtils.time.getCurrentTime(homeTimezone)
dispatch(settingQueryParam({ time: now }))
if (!time) {
dispatch(setTimeToNow())
}
if (!date) {
const today = coreUtils.time.getCurrentDate(homeTimezone)
dispatch(settingQueryParam({ date: today }))
}
}
}
Expand Down
12 changes: 10 additions & 2 deletions lib/components/app/batch-routing-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface Props {
mobile?: boolean
routingQuery: () => void
showUserSettings: boolean
updateDateTimeIfEmpty: () => void
updateQueryTimeIfLeavingNow: () => void
}

Expand Down Expand Up @@ -81,9 +82,15 @@ class BatchRoutingPanel extends Component<Props> {
handleSubmit = (e: FormEvent) => e.preventDefault()

handlePlanTripClick = () => {
const { currentQuery, intl, routingQuery, updateQueryTimeIfLeavingNow } =
this.props
const {
currentQuery,
intl,
routingQuery,
updateDateTimeIfEmpty,
updateQueryTimeIfLeavingNow
} = this.props
updateQueryTimeIfLeavingNow()
updateDateTimeIfEmpty()
alertUserTripPlan(
intl,
currentQuery,
Expand Down Expand Up @@ -250,6 +257,7 @@ const mapStateToProps = (state: any) => {

const mapDispatchToProps = {
routingQuery: apiActions.routingQuery,
updateDateTimeIfEmpty: formActions.updateDateTimeIfEmpty,
updateQueryTimeIfLeavingNow: formActions.updateQueryTimeIfLeavingNow
}

Expand Down
1 change: 0 additions & 1 deletion lib/components/form/advanced-settings-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const StyledTransparentButton = styled.button`
color: ${grey[800]};
display: flex;
gap: 7px;
margin-bottom: 5px;
`

const AdvancedSettingsButton = ({ onClick }: Props): JSX.Element => (
Expand Down
6 changes: 5 additions & 1 deletion lib/components/form/advanced-settings-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ const PanelOverlay = styled.div`
top: 0;
width: 100%;
z-index: 100;

@media (max-width: 768px) {
padding: 1em;
}
`

const GlobalSettingsContainer = styled.div`
Expand Down Expand Up @@ -283,7 +287,7 @@ const AdvancedSettingsPanel = ({
<h1 className="header-text">{headerText}</h1>
</HeaderContainer>
<DtSelectorContainer>
<DateTimeModal />
<DateTimeModal departArriveDropdown />
</DtSelectorContainer>
{processedGlobalSettings.length > 0 && (
<>
Expand Down
98 changes: 83 additions & 15 deletions lib/components/form/batch-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,60 @@
import { connect } from 'react-redux'
import { decodeQueryParams } from 'use-query-params'
import { MetroModeSelector } from '@opentripplanner/trip-form'
import {
DepartArriveDropdown,
MetroModeSelector
} from '@opentripplanner/trip-form'
import { ModeButtonDefinition } from '@opentripplanner/types'
import { Search } from '@styled-icons/fa-solid/Search'
import { SyncAlt } from '@styled-icons/fa-solid/SyncAlt'
import { useIntl } from 'react-intl'
import React, { useContext, useState } from 'react'
import React, { useCallback, useContext, useEffect } from 'react'

import * as apiActions from '../../actions/api'
import * as formActions from '../../actions/form'
import * as narrativeActions from '../../actions/narrative'
import { ComponentContext } from '../../util/contexts'
import { getActiveSearch, hasValidLocation } from '../../util/state'
import { getBaseColor, getDarkenedBaseColor } from '../util/colors'
import { StyledIconWrapper } from '../util/styledIcon'
import AnimateHeight from 'react-animate-height'

import {
addModeButtonIcon,
alertUserTripPlan,
modesQueryParamConfig,
onSettingsUpdate,
pipe,
setModeButton
} from './util'
import {
AdvancedOptionsContainer,
MainSettingsRow,
ModeSelectorContainer,
PlanTripButton
} from './batch-styled'
import AdvancedSettingsButton from './advanced-settings-button'
import DateTimeButton from './date-time-button'
import DateTimeModal, {
DepartArriveValue,
setQueryParamMiddleware
} from './date-time-modal'

// TYPESCRIPT TODO: better types
type Props = {
activeSearch: any
currentQuery: any
departArrive: DepartArriveValue
enabledModeButtons: string[]
fillModeIcons?: boolean
homeTimezone: string
modeButtonOptions: ModeButtonDefinition[]
onPlanTripClick: () => void
openAdvancedSettings: () => void
routingQuery: any
setQueryParam: (evt: any) => void
spacedOutModeSelector?: boolean
sort: any
syncSortWithDepartArrive: any
updateItineraryFilter: any
}

export function setModeButtonEnabled(enabledKeys: string[]) {
Expand All @@ -56,19 +72,21 @@ export function setModeButtonEnabled(enabledKeys: string[]) {
function BatchSettings({
activeSearch,
currentQuery,
departArrive,
enabledModeButtons,
fillModeIcons,
homeTimezone,
modeButtonOptions,
onPlanTripClick,
openAdvancedSettings,
routingQuery,
setQueryParam,
spacedOutModeSelector
sort,
syncSortWithDepartArrive,
updateItineraryFilter
}: Props) {
const intl = useIntl()

// Whether the date/time selector is open
const [dateTimeOpen, setDateTimeOpen] = useState(false)

// @ts-expect-error Context not typed
const { ModeIcon } = useContext(ComponentContext)

Expand All @@ -80,11 +98,54 @@ function BatchSettings({

const accentColor = getDarkenedBaseColor()

const onQueryParamChange = useCallback(
(params) => {
setQueryParamMiddleware(
syncSortWithDepartArrive,
updateItineraryFilter,
params,
setQueryParam,
sort
)
},
[syncSortWithDepartArrive, updateItineraryFilter, setQueryParam, sort]
)

const dtSelectorOpen = departArrive !== 'NOW'

// If the user selects depart or arrive, set the focus to the time input
useEffect(() => {
const dtTimeInput = document.querySelector(
".date-time-selector input[type='time']"
)
if (dtSelectorOpen) {
// eslint-disable-next-line prettier/prettier
(dtTimeInput as HTMLElement)?.focus()
}
}, [dtSelectorOpen, departArrive])

return (
<MainSettingsRow>
<DateTimeButton open={dateTimeOpen} setOpen={setDateTimeOpen} />
<AdvancedSettingsButton onClick={openAdvancedSettings} />
<ModeSelectorContainer squashed={!spacedOutModeSelector}>
<MainSettingsRow className="main-settings-row">
<AdvancedOptionsContainer>
<DepartArriveDropdown
departArrive={departArrive}
onQueryParamChange={onQueryParamChange}
timeZone={homeTimezone}
/>
<AdvancedSettingsButton onClick={openAdvancedSettings} />
</AdvancedOptionsContainer>
<AnimateHeight
duration={200}
height={dtSelectorOpen ? 'auto' : 0}
style={{
marginBottom: dtSelectorOpen ? '10px' : 0,
transition: 'ease all 200ms'
}}
>
<DateTimeModal />
</AnimateHeight>

<ModeSelectorContainer>
<MetroModeSelector
accentColor={baseColor}
activeHoverColor={accentColor.toHexString()}
Expand Down Expand Up @@ -125,10 +186,12 @@ function BatchSettings({
// TODO: Typescript
const mapStateToProps = (state: any) => {
const urlSearchParams = new URLSearchParams(state.router.location.search)
const { modes } = state.otp.config
const { homeTimezone, modes } = state.otp.config
const { departArrive } = state.otp.currentQuery
return {
activeSearch: getActiveSearch(state),
currentQuery: state.otp.currentQuery,
departArrive,
// TODO: Duplicated in apiv2.js
enabledModeButtons:
decodeQueryParams(modesQueryParamConfig, {
Expand All @@ -137,13 +200,18 @@ const mapStateToProps = (state: any) => {
modes?.initialState?.enabledModeButtons ||
{},
fillModeIcons: state.otp.config.itinerary?.fillModeIcons,
homeTimezone,
modeButtonOptions: modes?.modeButtons || [],
spacedOutModeSelector: modes?.spacedOut
sort: state.otp.filter.sort,
syncSortWithDepartArrive:
state.otp.config?.itinerary?.syncSortWithDepartArrive
}
}

const mapDispatchToProps = {
setQueryParam: formActions.setQueryParam
routingQuery: apiActions.routingQuery,
setQueryParam: formActions.setQueryParam,
updateItineraryFilter: narrativeActions.updateItineraryFilter
}

export default connect(mapStateToProps, mapDispatchToProps)(BatchSettings)
36 changes: 15 additions & 21 deletions lib/components/form/batch-styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ export const Button = styled.button`
${buttonCss}
`

export const TripFormButtonContainer = styled.div`
display: flex;
gap: 2px;
`

export const AdvancedOptionsContainer = styled.div`
align-items: center;
color: ${grey[800]};
display: flex;
justify-content: space-between;
`

export const PlanTripButton = styled(Button)`
background-color: green;
color: #ffffffdd;
Expand All @@ -61,23 +73,13 @@ export const PlanTripButton = styled(Button)`
}
`

export const ModeSelectorContainer = styled.div<{ squashed?: boolean }>`
export const ModeSelectorContainer = styled.div`
align-items: flex-start;
display: flex;
float: right;
justify-content: space-between;
width: 100%;

${PlanTripButton} {
border-bottom-left-radius: ${(props) => (props.squashed ? 0 : 'invalid')};
border-top-left-radius: ${(props) => (props.squashed ? 0 : 'invalid')};
margin-top: 0px;
margin-left: ${(props) => (props.squashed ? 0 : '3px')};
}
label:last-of-type {
border-bottom-right-radius: ${(props) => (props.squashed ? 0 : 'invalid')};
border-top-right-radius: ${(props) => (props.squashed ? 0 : 'invalid')};
}
fieldset {
gap: 0 2px;
margin: 0 2px 0 0;
Expand All @@ -89,15 +91,7 @@ export const ModeSelectorContainer = styled.div<{ squashed?: boolean }>`
`

export const MainSettingsRow = styled.div`
align-items: top;
display: flex;
flex-flow: wrap;
gap: 5px 0;
justify-content: space-between;
margin-bottom: 5px;

label {
/* Cancel bottom margin of bootstrap labels in mode selector. */
margin-bottom: 0;
}
flex-direction: column;
gap: 5px;
`
Loading
Loading