Skip to content

Commit 3d4386f

Browse files
Merge pull request #367 from opentripplanner/dev
Next release
2 parents f4b3a25 + 24cf3d0 commit 3d4386f

18 files changed

+693
-353
lines changed

Diff for: lib/actions/call-taker.js

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { serialize } from 'object-to-formdata'
33
import qs from 'qs'
44
import { createAction } from 'redux-actions'
55

6+
import {toggleFieldTrips} from './field-trip'
7+
import {resetForm} from './form'
68
import {searchToQuery, sessionIsInvalid} from '../util/call-taker'
79
import {URL_ROOT} from '../util/constants'
810
import {getTimestamp} from '../util/state'
@@ -23,6 +25,17 @@ const storeSession = createAction('STORE_SESSION')
2325
export const beginCall = createAction('BEGIN_CALL')
2426
export const toggleCallHistory = createAction('TOGGLE_CALL_HISTORY')
2527

28+
/**
29+
* Fully reset form and toggle call history (and close field trips if open).
30+
*/
31+
export function resetAndToggleCallHistory () {
32+
return function (dispatch, getState) {
33+
dispatch(resetForm(true))
34+
dispatch(toggleCallHistory())
35+
if (getState().callTaker.fieldTrip.visible) dispatch(toggleFieldTrips())
36+
}
37+
}
38+
2639
/**
2740
* End the active call and store the queries made during the call.
2841
*/

Diff for: lib/actions/field-trip.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import qs from 'qs'
66
import { createAction } from 'redux-actions'
77

88
import {routingQuery} from './api'
9-
import {setQueryParam} from './form'
9+
import {toggleCallHistory} from './call-taker'
10+
import {resetForm, setQueryParam} from './form'
1011
import {getGroupSize, getTripFromRequest, sessionIsInvalid} from '../util/call-taker'
1112

1213
if (typeof (fetch) === 'undefined') require('isomorphic-fetch')
@@ -25,6 +26,17 @@ export const setActiveFieldTrip = createAction('SET_ACTIVE_FIELD_TRIP')
2526
export const setGroupSize = createAction('SET_GROUP_SIZE')
2627
export const toggleFieldTrips = createAction('TOGGLE_FIELD_TRIPS')
2728

29+
/**
30+
* Fully reset form and toggle field trips (and close call history if open).
31+
*/
32+
export function resetAndToggleFieldTrips () {
33+
return async function (dispatch, getState) {
34+
dispatch(resetForm(true))
35+
dispatch(toggleFieldTrips())
36+
if (getState().callTaker.callHistory.visible) dispatch(toggleCallHistory())
37+
}
38+
}
39+
2840
/**
2941
* Fetch all field trip requests (as summaries).
3042
*/

Diff for: lib/actions/form.js

+39-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1+
import coreUtils from '@opentripplanner/core-utils'
12
import debounce from 'lodash.debounce'
23
import isEqual from 'lodash.isequal'
34
import moment from 'moment'
4-
import coreUtils from '@opentripplanner/core-utils'
5+
import qs from 'qs'
56
import { createAction } from 'redux-actions'
67

7-
import { queryIsValid } from '../util/state'
8+
import { routingQuery } from './api'
9+
import { setLocation } from './map'
810
import {
911
MobileScreens,
12+
routeTo,
1013
setMainPanelContent,
1114
setMobileScreen
12-
} from '../actions/ui'
13-
14-
import { routingQuery } from './api'
15+
} from './ui'
16+
import { queryIsValid } from '../util/state'
1517

1618
const {
1719
getDefaultQuery,
@@ -26,7 +28,12 @@ export const setActiveSearch = createAction('SET_ACTIVE_SEARCH')
2628
export const clearDefaultSettings = createAction('CLEAR_DEFAULT_SETTINGS')
2729
export const storeDefaultSettings = createAction('STORE_DEFAULT_SETTINGS')
2830

29-
export function resetForm () {
31+
/**
32+
* Reset the trip form parameters to their default values.
33+
* @param {Boolean} [full=false] if set to true, the from/to locations and URL
34+
* query parameters will also be reset.
35+
*/
36+
export function resetForm (full = false) {
3037
return function (dispatch, getState) {
3138
const otpState = getState().otp
3239
const { transitModes } = otpState.config.modes
@@ -46,6 +53,19 @@ export function resetForm () {
4653
options.mode = ['WALK', ...transitModes.map(m => m.mode)].join(',')
4754
dispatch(settingQueryParam(options))
4855
}
56+
if (full) {
57+
// If fully resetting form, also clear the active search, from/to
58+
// locations, and query params.
59+
dispatch(clearActiveSearch())
60+
dispatch(setLocation({location: null, locationType: 'from'}))
61+
dispatch(setLocation({location: null, locationType: 'to'}))
62+
// Get query params. Delete everything except sessionId.
63+
const params = getUrlParams()
64+
for (const key in params) {
65+
if (key !== 'sessionId') delete params[key]
66+
}
67+
dispatch(routeTo('/', qs.stringify(params, { addQueryPrefix: true })))
68+
}
4969
}
5070
}
5171

@@ -61,14 +81,25 @@ export function setQueryParam (payload, searchId) {
6181
}
6282
}
6383

64-
export function parseUrlQueryString (params = getUrlParams()) {
84+
/**
85+
* An action that parses the active URL's query params (or whatever is passed in)
86+
* and updates the state with the OTP plan query params. A new search will be
87+
* performed according to the behavior of setQueryParam (i.e., if the passed
88+
* searchId is not null).
89+
* @param {Object} params an object containing URL query params
90+
* @param {string} source optionally contains the source of the query params
91+
* (e.g., _CALL indicates that the source is from a past
92+
* query made during a call taker session)
93+
*/
94+
export function parseUrlQueryString (params = getUrlParams(), source) {
6595
return function (dispatch, getState) {
6696
// Filter out the OTP (i.e. non-UI) params and set the initial query
6797
const planParams = {}
6898
Object.keys(params).forEach(key => {
6999
if (!key.startsWith('ui_')) planParams[key] = params[key]
70100
})
71-
const searchId = params.ui_activeSearch || coreUtils.storage.randId()
101+
let searchId = params.ui_activeSearch || coreUtils.storage.randId()
102+
if (source) searchId += source
72103
// Convert strings to numbers/objects and dispatch
73104
planParamsToQueryAsync(planParams, getState().otp.config)
74105
.then(query => dispatch(setQueryParam(query, searchId)))

Diff for: lib/components/admin/call-record.js

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import humanizeDuration from 'humanize-duration'
12
import moment from 'moment'
23
import React, { Component } from 'react'
34

45
import CallTimeCounter from './call-time-counter'
56
import Icon from '../narrative/icon'
67
import QueryRecord from './query-record'
8+
import {CallRecordButton, CallRecordIcon, QueryList} from './styled'
79
import {searchToQuery} from '../../util/call-taker'
810

911
/**
@@ -14,6 +16,14 @@ export default class CallRecord extends Component {
1416
expanded: false
1517
}
1618

19+
_getCallDuration = () => {
20+
const {call} = this.props
21+
const start = moment(call.startTime)
22+
const end = moment(call.endTime)
23+
const millis = moment.duration(end.diff(start)).asMilliseconds()
24+
return humanizeDuration(millis)
25+
}
26+
1727
_toggleExpanded = () => {
1828
const {call, fetchQueries} = this.props
1929
const {expanded} = this.state
@@ -24,7 +34,7 @@ export default class CallRecord extends Component {
2434
render () {
2535
// FIXME: consolidate red color with call taker controls
2636
const RED = '#C35134'
27-
const {call, index, inProgress, searches} = this.props
37+
const {call, inProgress, searches} = this.props
2838
const {expanded} = this.state
2939
if (!call) return null
3040
if (inProgress) {
@@ -47,36 +57,46 @@ export default class CallRecord extends Component {
4757
In progress... click <Icon type='stop' /> to save{' '}
4858
({call.searches.length} searches)
4959
</small>
50-
<div>
60+
<QueryList>
5161
{activeQueries.length > 0
5262
? activeQueries.map((query, i) => (
5363
<QueryRecord key={i} query={query} index={i} />
5464
))
5565
: 'No queries recorded.'
5666
}
57-
</div>
67+
</QueryList>
5868
</div>
5969
)
6070
}
71+
// Default (no active call) view
72+
const startTimeMoment = moment(call.startTime)
6173
return (
62-
<div style={{margin: '5px 0'}}>
63-
<button
64-
style={{width: '100%'}}
74+
<div style={{borderBottom: '1px solid grey', margin: '5px 0', paddingBottom: '2px'}}>
75+
<CallRecordButton
6576
className='clear-button-formatting'
6677
onClick={this._toggleExpanded}
6778
>
68-
<Icon type='phone' className='fa-flip-horizontal' />
69-
Call {index} ({moment(call.endTime).fromNow()})
70-
</button>
79+
<CallRecordIcon className='fa-flip-horizontal' type='phone' />
80+
<span style={{flex: '0 0 120px'}}>
81+
{startTimeMoment.format('h:mm a, MMM D')}
82+
</span>
83+
<small
84+
className='text-muted'
85+
style={{marginLeft: '5px', paddingTop: '2px', width: '60%'}}
86+
>
87+
<Icon type='clock-o' />{' '}
88+
{this._getCallDuration()}
89+
</small>
90+
</CallRecordButton>
7191
{expanded
72-
? <ul className='list-unstyled'>
92+
? <QueryList>
7393
{call.queries && call.queries.length > 0
7494
? call.queries.map((query, i) => (
7595
<QueryRecord key={i} query={query} index={i} />
7696
))
7797
: 'No queries recorded.'
7898
}
79-
</ul>
99+
</QueryList>
80100
: null
81101
}
82102
</div>

Diff for: lib/components/admin/call-taker-controls.js

+13-11
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,16 @@ class CallTakerControls extends Component {
6464
)
6565
}
6666

67-
_onToggleCallHistory = () => this.props.toggleCallHistory()
68-
69-
_onToggleFieldTrips = () => this.props.toggleFieldTrips()
70-
71-
_callInProgress = () => Boolean(this.props.activeCall)
67+
_callInProgress = () => Boolean(this.props.callTaker.activeCall)
7268

7369
render () {
74-
const {callTakerEnabled, fieldTripEnabled, session} = this.props
70+
const {
71+
callTakerEnabled,
72+
fieldTripEnabled,
73+
session,
74+
resetAndToggleCallHistory,
75+
resetAndToggleFieldTrips
76+
} = this.props
7577
// If no valid session is found, do not show calltaker controls.
7678
if (!session) return null
7779
return (
@@ -94,7 +96,7 @@ class CallTakerControls extends Component {
9496
{callTakerEnabled &&
9597
<CallHistoryButton
9698
className='call-taker-button'
97-
onClick={this._onToggleCallHistory}
99+
onClick={resetAndToggleCallHistory}
98100
>
99101
<Icon
100102
className='fa-2x'
@@ -107,7 +109,7 @@ class CallTakerControls extends Component {
107109
{fieldTripEnabled &&
108110
<FieldTripsButton
109111
className='call-taker-button'
110-
onClick={this._onToggleFieldTrips}
112+
onClick={resetAndToggleFieldTrips}
111113
>
112114
<Icon
113115
className='fa-2x'
@@ -123,7 +125,7 @@ class CallTakerControls extends Component {
123125

124126
const mapStateToProps = (state, ownProps) => {
125127
return {
126-
activeCall: state.callTaker.activeCall,
128+
callTaker: state.callTaker,
127129
callTakerEnabled: Boolean(state.otp.config.modules.find(m => m.id === 'call')),
128130
fieldTripEnabled: Boolean(state.otp.config.modules.find(m => m.id === 'ft')),
129131
session: state.callTaker.session
@@ -137,8 +139,8 @@ const mapDispatchToProps = {
137139
fetchFieldTrips: fieldTripActions.fetchFieldTrips,
138140
routingQuery: apiActions.routingQuery,
139141
setMainPanelContent: uiActions.setMainPanelContent,
140-
toggleCallHistory: callTakerActions.toggleCallHistory,
141-
toggleFieldTrips: fieldTripActions.toggleFieldTrips
142+
resetAndToggleCallHistory: callTakerActions.resetAndToggleCallHistory,
143+
resetAndToggleFieldTrips: fieldTripActions.resetAndToggleFieldTrips
142144
}
143145

144146
export default connect(mapStateToProps, mapDispatchToProps)(CallTakerControls)

Diff for: lib/components/admin/call-taker-windows.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class CallTakerWindows extends Component {
2020
<DraggableWindow
2121
header={<WindowHeader><Icon type='history' /> Call history</WindowHeader>}
2222
onClickClose={toggleCallHistory}
23-
style={{right: '15px', top: '50px'}}
23+
style={{right: '15px', top: '50px', width: '450px'}}
2424
>
2525
{activeCall
2626
? <CallRecord

Diff for: lib/components/admin/query-record.js

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,50 @@
1+
import { planParamsToQuery } from '@opentripplanner/core-utils/lib/query'
2+
import { getTimeFormat, OTP_API_TIME_FORMAT } from '@opentripplanner/core-utils/lib/time'
3+
import moment from 'moment'
14
import React, { Component } from 'react'
25
import { connect } from 'react-redux'
36

47
import * as formActions from '../../actions/form'
5-
import Icon from '../narrative/icon'
8+
import {CallRecordButton, CallRecordIcon} from './styled'
69

710
/**
811
* Displays information for a query stored for the Call Taker module.
912
*/
1013
class QueryRecordLayout extends Component {
14+
_getParams = () => {
15+
const {query} = this.props
16+
return planParamsToQuery(JSON.parse(query.queryParams))
17+
}
18+
1119
_viewQuery = () => {
12-
const {parseUrlQueryString, query} = this.props
13-
const params = JSON.parse(query.queryParams)
14-
if ('arriveBy' in params) {
15-
params.departArrive = params.arriveBy ? 'ARRIVE' : 'DEPART'
16-
}
17-
parseUrlQueryString(params)
20+
const {parseUrlQueryString} = this.props
21+
const params = this._getParams()
22+
params.departArrive = params.arriveBy ? 'ARRIVE' : 'DEPART'
23+
parseUrlQueryString(params, '_CALL')
1824
}
1925

2026
render () {
21-
const {index} = this.props
27+
const {query, timeFormat} = this.props
28+
const params = this._getParams()
29+
const time = query.timeStamp
30+
? moment(query.timeStamp).format(timeFormat)
31+
: moment(params.time, OTP_API_TIME_FORMAT).format(timeFormat)
2232
return (
2333
<li>
24-
<button onClick={this._viewQuery} className='clear-button-formatting'>
25-
<Icon type='search' />
26-
</button>{' '}
27-
Query {index + 1}
34+
<CallRecordButton onClick={this._viewQuery} className='clear-button-formatting'>
35+
<CallRecordIcon type='search' />
36+
{time}<br />
37+
{params.from.name} to {params.to.name}
38+
</CallRecordButton>
2839
</li>
2940
)
3041
}
3142
}
3243

3344
const mapStateToProps = (state, ownProps) => {
34-
return {}
45+
return {
46+
timeFormat: getTimeFormat(state.otp.config)
47+
}
3548
}
3649

3750
const {parseUrlQueryString} = formActions

0 commit comments

Comments
 (0)