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

Mobility profile for Calltaker #1327

Closed
wants to merge 10 commits into from
Closed
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 change: 1 addition & 0 deletions i18n/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ components:
mobility scooter: Mobility scooter
none: No assistive device
service animal: Service animal
some limitations: Some mobility limitations
stroller: Stroller
wheeled walker: Wheeled walker
white cane: White cane
Expand Down
1 change: 1 addition & 0 deletions i18n/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ components:
mobility scooter: Scooter électrique
none: Aucun
service animal: Animal de service
some limitations: Mobilité partiellement limitée
stroller: Poussette
wheeled walker: Déambulateur à roues
white cane: Canne blanche
Expand Down
6 changes: 4 additions & 2 deletions lib/components/app/call-taker-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class CallTakerPanel extends Component {
currentQuery,
fieldTripVisible,
groupSize,
hasMobilityProfile,
intl,
mainPanelContent,
maxGroupSize,
Expand All @@ -169,8 +170,7 @@ class CallTakerPanel extends Component {
setQueryParam,
setUrlSearch,
showUserSettings,
timeFormat,
usingOtp2
timeFormat
} = this.props
// FIXME: Remove showPlanTripButton
const showPlanTripButton =
Expand Down Expand Up @@ -313,6 +313,7 @@ class CallTakerPanel extends Component {
<AdvancedOptions
currentQuery={currentQuery}
findRoutesIfNeeded={this.props.findRoutesIfNeeded}
hasMobilityProfile={hasMobilityProfile}
modes={modes}
modeSettings={this.props.modeSettingDefinitions}
modeSettingValues={this.props.modeSettingValues}
Expand Down Expand Up @@ -358,6 +359,7 @@ const mapStateToProps = (state) => {
currentQuery: state.otp.currentQuery,
fieldTripVisible: visible,
groupSize,
hasMobilityProfile: state.otp.config.mobilityProfile,
mainPanelContent: state.otp.ui.mainPanelContent,
maxGroupSize: getGroupSize(request),
modeDropdownOptions:
Expand Down
34 changes: 6 additions & 28 deletions lib/components/form/advanced-settings-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
addSettingsToButton,
AdvancedModeSubsettingsContainer,
DropdownSelector,
ModeSettingRenderer,
populateSettingWithValue
} from '@opentripplanner/trip-form'
Expand All @@ -16,7 +15,6 @@ import {
ModeSetting,
ModeSettingValues
} from '@opentripplanner/types'
import { QueryParamChangeEvent } from '@opentripplanner/trip-form/lib/types'
import React, {
RefObject,
useCallback,
Expand All @@ -35,6 +33,7 @@ import { ComponentContext } from '../../util/contexts'
import { generateModeSettingValues } from '../../util/api'
import { getDependentName } from '../../util/user'
import { User } from '../user/types'
import MobilityProfileSelector from '../user/mobility-profile/mobility-profile-selector'

import {
addCustomSettingLabels,
Expand Down Expand Up @@ -135,13 +134,6 @@ const MobilityProfileContainer = styled.div`
margin: 60px 0 60px 5px;
`

const MobilityProfileDropdown = styled(DropdownSelector)`
margin: 20px 0px;
label {
padding-left: 0;
}
`

const AdvancedSettingsPanel = ({
autoPlan,
closeAdvancedSettings,
Expand Down Expand Up @@ -173,12 +165,10 @@ const AdvancedSettingsPanel = ({
modeSettingValues: ModeSettingValues
saveAndReturnButton?: boolean
setCloseAdvancedSettingsWithDelay: () => void
setQueryParam: (evt: any) => void
setQueryParam: (args: Record<string, unknown>) => void
}): JSX.Element => {
const intl = useIntl()
const [closingBySave, setClosingBySave] = useState(false)
const [selectedMobilityProfile, setSelectedMobilityProfile] =
useState<string>(currentQuery.forEmail || loggedInUser?.email)
const dependents = useMemo(
() => loggedInUser?.dependents || [],
[loggedInUser]
Expand Down Expand Up @@ -257,16 +247,6 @@ const AdvancedSettingsPanel = ({
closePanel()
}, [closePanel, setCloseAdvancedSettingsWithDelay])

const onMobilityProfileChange = useCallback(
(evt: QueryParamChangeEvent) => {
const value = evt.forEmail
setSelectedMobilityProfile(value as string)
setQueryParam({
forEmail: value
})
},
[setSelectedMobilityProfile, setQueryParam]
)
return (
<PanelOverlay className="advanced-settings" ref={innerRef}>
<HeaderContainer>
Expand Down Expand Up @@ -301,12 +281,10 @@ const AdvancedSettingsPanel = ({
<FormattedMessage id="components.MobilityProfile.MobilityPane.header" />
</VisibleSubheader>
<FormattedMessage id="components.MobilityProfile.MobilityPane.planTripDescription" />
<MobilityProfileDropdown
label={intl.formatMessage({
id: 'components.MobilityProfile.dropdownLabel'
})}
<MobilityProfileSelector
name="forEmail"
onChange={onMobilityProfileChange}
// Don't use onSettingsUpdate, so that the dependent's mobility profile doesn't appear in the URL.
onSettingsUpdate={setQueryParam}
options={[
{
text: intl.formatMessage({
Expand All @@ -319,7 +297,7 @@ const AdvancedSettingsPanel = ({
value: user.email
})) || [])
]}
value={selectedMobilityProfile}
value={currentQuery.forEmail || loggedInUser?.email}
/>
</MobilityProfileContainer>
)}
Expand Down
40 changes: 21 additions & 19 deletions lib/components/form/call-taker/advanced-options.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/* eslint-disable react/prop-types */
// FIXME: Remove the following eslint rule exception.
/* eslint-disable jsx-a11y/label-has-for */
import * as TripFormClasses from '@opentripplanner/trip-form/lib/styled'
import { checkIfModeSettingApplies } from '@opentripplanner/trip-form/lib/MetroModeSelector/utils'
import { injectIntl } from 'react-intl'
Expand All @@ -9,12 +7,14 @@ import {
populateSettingWithValue,
SubmodeSelector
} from '@opentripplanner/trip-form'
import isEmpty from 'lodash.isempty'
import React, { Component, lazy, Suspense } from 'react'
import styled from 'styled-components'

import { ComponentContext } from '../../../util/contexts'
import { getMobilityProfileOptions } from '../../../util/mobility-profile'
import { modeButtonButtonCss } from '../styled'
import { onSettingsUpdate } from '../util'
import MobilityProfileSelector from '../../user/mobility-profile/mobility-profile-selector'

export const StyledSubmodeSelector = styled(SubmodeSelector)`
${TripFormClasses.SubmodeSelector.Row} {
Expand Down Expand Up @@ -43,9 +43,6 @@ export const StyledSubmodeSelector = styled(SubmodeSelector)`
margin: 5px 0;
`

const metersToMiles = (meters) => Math.round(meters * 0.000621371 * 100) / 100
const milesToMeters = (miles) => miles / 0.000621371

/**
* Converts a new TransportMode object to legacy style underscore qualifier
*/
Expand Down Expand Up @@ -80,7 +77,7 @@ class AdvancedOptions extends Component {
this.props.findRoutesIfNeeded()
}

componentDidUpdate(prevProps) {
componentDidUpdate() {
const { routes } = this.props
// Once routes are available, map them to the route options format.
const routeOptions = Object.values(routes).map(this.routeToOption)
Expand Down Expand Up @@ -113,17 +110,6 @@ class AdvancedOptions extends Component {
return bannedRoutes && bannedRoutes.find((o) => o.value === option.value)
}

getDistanceStep = (distanceInMeters) => {
// Determine step for max walk/bike based on current value. Increment by a
// quarter mile if dealing with small values, whatever number will round off
// the number if it is not an integer, or default to one mile.
return metersToMiles(distanceInMeters) <= 2
? '.25'
: metersToMiles(distanceInMeters) % 1 !== 0
? `${metersToMiles(distanceInMeters) % 1}`
: '1'
}

_onSubModeChange = (changedMode) => {
// Get previous transit modes from state and all modes from query.
const transitModes = [...this.state.transitModes]
Expand Down Expand Up @@ -190,7 +176,14 @@ class AdvancedOptions extends Component {
}

render() {
const { currentQuery, intl, modes, onKeyDown } = this.props
const {
currentQuery,
hasMobilityProfile,
intl,
modes,
onKeyDown,
setQueryParam
} = this.props
const { ModeIcon } = this.context

const Select = lazy(() => import('react-select'))
Expand Down Expand Up @@ -238,6 +231,15 @@ class AdvancedOptions extends Component {
onKeyDown={onKeyDown}
/>
</div>
{hasMobilityProfile && (
<MobilityProfileSelector
name="mobilityProfile"
// Use onSettingsUpdate to include the mobility profile in the URL
onSettingsUpdate={onSettingsUpdate(setQueryParam)}
options={getMobilityProfileOptions(intl)}
value={currentQuery.mobilityProfile}
/>
)}
<Suspense fallback={<span>...</span>}>
<Select
id="preferredRoutes"
Expand Down
51 changes: 51 additions & 0 deletions lib/components/user/mobility-profile/mobility-profile-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { DropdownSelector } from '@opentripplanner/trip-form'
import { QueryParamChangeEvent } from '@opentripplanner/trip-form/lib/types'
import { useIntl } from 'react-intl'
import React, { useCallback } from 'react'
import styled from 'styled-components'

const MobilityProfileDropdown = styled(DropdownSelector)`
margin: 20px 0px;
label {
padding-left: 0;
}
`

const MobilityProfileSelector = ({
name,
onSettingsUpdate,
options,
value
}: {
name: string
onSettingsUpdate: (args: Record<string, unknown>) => void
options: {
text: string
value: string
}[]
value: string
}): JSX.Element => {
const intl = useIntl()

const onMobilityProfileChange = useCallback(
(evt: QueryParamChangeEvent) => {
const paramValue = evt[name]
onSettingsUpdate({ [name]: paramValue })
},
[name, onSettingsUpdate]
)

return (
<MobilityProfileDropdown
label={intl.formatMessage({
id: 'components.MobilityProfile.dropdownLabel'
})}
name={name}
onChange={onMobilityProfileChange}
options={options}
value={value}
/>
)
}

export default MobilityProfileSelector
87 changes: 87 additions & 0 deletions lib/util/mobility-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { IntlShape } from 'react-intl'

/**
* Gets a list of standard options for the MobilityProfileSelector
* by combining assistive device use and vision limitations.
*/
export function getMobilityProfileOptions(intl: IntlShape): {
text: string
value: string
}[] {
// Device and vision codes used below are from DevicePane > devices and
// LimitationsPane > visionLimitation in the language files.
const rawProfiles = [
{
device: 'none',
value: 'None'
},
{
// 'some limitations' is not technically a device, but we've added in there to build the mobility profile.
device: ['some limitations', 'none'],
value: 'Some'
},
{
device: [
'cane',
'crutches',
'manual walker',
'service animal',
'stroller',
'wheeled walker'
],
value: 'Device'
},
{
device: 'manual wheelchair',
value: 'WChairM'
},
{
device: 'electric wheelchair',
value: 'WChairE'
},
{
device: 'mobility scooter',
value: 'MScooter'
}
]

const visionLevels = [
{
level: undefined,
value: ''
},
{
level: 'low-vision',
value: 'LowVision'
},
{
level: 'legally-blind',
value: 'Blind'
}
]

return rawProfiles.flatMap((p) =>
visionLevels.map((vision) => ({
text:
(typeof p.device === 'string' ? [p.device] : p.device)
.map((device) =>
intl.formatMessage({
id: `components.MobilityProfile.DevicesPane.devices.${device}`
})
)
.join('/') +
(vision.level
? ' + ' +
intl.formatMessage({
id: `components.MobilityProfile.LimitationsPane.visionLimitations.${vision.level}`
})
: ''),
value:
p.value !== 'None'
? p.value + (vision.level ? '-' + vision.value : '')
: vision.level
? vision.value
: 'None'
}))
)
}
Loading