@@ -33,14 +33,32 @@ class RouteViewer extends Component {
33
33
static propTypes = {
34
34
hideBackButton : PropTypes . bool ,
35
35
routes : PropTypes . object
36
- }
36
+ } ;
37
+
38
+ state = {
39
+ agency : null ,
40
+ mode : null
41
+ } ;
37
42
38
- _backClicked = ( ) => this . props . setMainPanelContent ( null )
43
+ _backClicked = ( ) => this . props . setMainPanelContent ( null ) ;
39
44
40
45
componentDidMount ( ) {
41
46
this . props . findRoutes ( )
42
47
}
43
48
49
+ /**
50
+ * Handle filter dropdown change. Id of the filter is equivalent to the key in the
51
+ * route object
52
+ */
53
+ onFilterChange = ( event ) => {
54
+ const { target } = event
55
+ const { id, value } = target
56
+ // id will be either 'agency' or 'mode' based on the dropdown used
57
+ this . setState ( {
58
+ [ id ] : value
59
+ } )
60
+ } ;
61
+
44
62
render ( ) {
45
63
const {
46
64
findRoute,
@@ -51,11 +69,36 @@ class RouteViewer extends Component {
51
69
transitOperators,
52
70
viewedRoute
53
71
} = this . props
54
- const sortedRoutes = routes
55
- ? Object . values ( routes ) . sort (
56
- coreUtils . route . makeRouteComparator ( transitOperators )
72
+ const { agency, mode } = this . state
73
+ const routesArray = routes ? Object . values ( routes ) : [ ]
74
+
75
+ const filteredAndSortedRoutes = routesArray
76
+ . filter (
77
+ ( route ) =>
78
+ // If the filter isn't defined, don't check.
79
+ ( ! agency || agency === route . agencyName ) &&
80
+ ( ! mode || mode === route . mode )
81
+ )
82
+ . sort ( coreUtils . route . makeRouteComparator ( transitOperators ) )
83
+
84
+ // get agency and mode lists from route list
85
+ const agencies = Array . from (
86
+ new Set ( routesArray . map ( ( route ) => route . agencyName || route . agency . name ) )
87
+ )
88
+ . filter ( ( agency ) => agency !== undefined )
89
+ . sort ( )
90
+
91
+ const modes = Array . from (
92
+ new Set (
93
+ routesArray
94
+ . filter ( ( route ) => route . mode !== undefined )
95
+ . map ( ( route ) => route . mode . toLowerCase ( ) )
57
96
)
58
- : [ ]
97
+ )
98
+ // Correct capitalization
99
+ . map ( ( mode ) => mode [ 0 ] . toUpperCase ( ) + mode . substr ( 1 ) . toLowerCase ( ) )
100
+ . sort ( )
101
+
59
102
return (
60
103
< div className = 'route-viewer' >
61
104
{ /* Header Block */ }
@@ -74,32 +117,51 @@ class RouteViewer extends Component {
74
117
< div className = 'header-text' >
75
118
{ languageConfig . routeViewer || 'Route Viewer' }
76
119
</ div >
77
- < div className = '' >
78
- { languageConfig . routeViewerDetails }
120
+ < div className = '' > { languageConfig . routeViewerDetails } </ div >
121
+ < div className = 'search-and-filter' >
122
+ < Icon type = 'filter' />
123
+ < select id = 'agency' onBlur = { this . onFilterChange } onChange = { this . onFilterChange } >
124
+ < option value = '' > All agencies</ option >
125
+ { agencies . map ( ( agency ) => (
126
+ < option key = { agency } > { agency } </ option >
127
+ ) ) }
128
+ </ select >
129
+ < select id = 'mode' onBlur = { this . onFilterChange } onChange = { this . onFilterChange } >
130
+ < option value = '' > All modes</ option >
131
+ { modes . map ( ( mode ) => (
132
+ < option key = { mode } value = { mode . toUpperCase ( ) } >
133
+ { mode }
134
+ </ option >
135
+ ) ) }
136
+ </ select >
79
137
</ div >
80
138
< div style = { { clear : 'both' } } />
81
139
</ div >
82
140
83
141
< div className = 'route-viewer-body' >
84
- { sortedRoutes
85
- . map ( route => {
86
- // Find operator based on agency_id (extracted from OTP route ID).
87
- const operator = coreUtils . route . getTransitOperatorFromOtpRoute (
142
+ { filteredAndSortedRoutes . map ( ( route ) => {
143
+ // Find operator based on agency_id (extracted from OTP route ID).
144
+ const operator =
145
+ coreUtils . route . getTransitOperatorFromOtpRoute (
88
146
route ,
89
147
transitOperators
90
148
) || { }
91
- return (
92
- < RouteRow
93
- findRoute = { findRoute }
94
- isActive = { viewedRoute && viewedRoute . routeId === route . id }
95
- key = { route . id }
96
- operator = { operator }
97
- route = { route }
98
- setViewedRoute = { setViewedRoute }
99
- />
100
- )
101
- } )
102
- }
149
+ return (
150
+ < RouteRow
151
+ findRoute = { findRoute }
152
+ isActive = { viewedRoute && viewedRoute . routeId === route . id }
153
+ key = { route . id }
154
+ operator = { operator }
155
+ route = { route }
156
+ setViewedRoute = { setViewedRoute }
157
+ />
158
+ )
159
+ } ) }
160
+ { ( agency || mode ) && filteredAndSortedRoutes . length === 0 && (
161
+ < span className = 'noRoutesFoundMessage' >
162
+ No routes match your filter!
163
+ </ span >
164
+ ) }
103
165
</ div >
104
166
</ div >
105
167
)
@@ -177,9 +239,10 @@ class RouteRow extends PureComponent {
177
239
// Attempt to split route name if splitter is defined for operator (to
178
240
// remove short name value from start of long name value).
179
241
const nameParts = route . longName . split ( longNameSplitter )
180
- longName = ( longNameSplitter && nameParts . length > 1 )
181
- ? nameParts [ 1 ]
182
- : route . longName
242
+ longName =
243
+ longNameSplitter && nameParts . length > 1
244
+ ? nameParts [ 1 ]
245
+ : route . longName
183
246
// If long name and short name are identical, set long name to be an empty
184
247
// string.
185
248
if ( longName === route . shortName ) longName = ''
@@ -191,14 +254,17 @@ class RouteRow extends PureComponent {
191
254
const { isActive, operator, route } = this . props
192
255
const { ModeIcon } = this . context
193
256
194
- const { defaultRouteColor, defaultRouteTextColor, longNameSplitter} = operator || { }
257
+ const { defaultRouteColor, defaultRouteTextColor, longNameSplitter } =
258
+ operator || { }
195
259
const backgroundColor = `#${ defaultRouteColor || route . color || 'ffffff' } `
196
260
// NOTE: text color is not a part of short response route object, so there
197
261
// is no way to determine from OTP what the text color should be if the
198
262
// background color is, say, black. Instead, determine the appropriate
199
263
// contrast color and use that if no text color is available.
200
264
const contrastColor = getContrastYIQ ( backgroundColor )
201
- const color = `#${ defaultRouteTextColor || route . textColor || contrastColor } `
265
+ const color = `#${ defaultRouteTextColor ||
266
+ route . textColor ||
267
+ contrastColor } `
202
268
// Default long name is empty string (long name is an optional GTFS value).
203
269
const longName = this . getCleanRouteLongName ( { longNameSplitter, route } )
204
270
return (
@@ -208,12 +274,9 @@ class RouteRow extends PureComponent {
208
274
onClick = { this . _onClick }
209
275
>
210
276
< RouteRowElement >
211
- { operator && operator . logo &&
212
- < OperatorImg
213
- alt = { `${ operator . name } logo` }
214
- src = { operator . logo }
215
- />
216
- }
277
+ { operator && operator . logo && (
278
+ < OperatorImg alt = { `${ operator . name } logo` } src = { operator . logo } />
279
+ ) }
217
280
</ RouteRowElement >
218
281
< ModeIconElement >
219
282
< ModeIcon height = { 22 } mode = { getModeFromRoute ( route ) } width = { 22 } />
@@ -226,13 +289,19 @@ class RouteRow extends PureComponent {
226
289
< b > { route . shortName } </ b > { longName }
227
290
</ RouteNameElement >
228
291
</ RouteRowButton >
229
- < VelocityTransitionGroup enter = { { animation : 'slideDown' } } leave = { { animation : 'slideUp' } } >
292
+ < VelocityTransitionGroup
293
+ enter = { { animation : 'slideDown' } }
294
+ leave = { { animation : 'slideUp' } }
295
+ >
230
296
{ isActive && (
231
297
< RouteDetails >
232
- { route . url
233
- ? < a href = { route . url } target = '_blank' > Route Details</ a >
234
- : 'No route URL provided.'
235
- }
298
+ { route . url ? (
299
+ < a href = { route . url } target = '_blank' >
300
+ Route Details
301
+ </ a >
302
+ ) : (
303
+ 'No route URL provided.'
304
+ ) }
236
305
</ RouteDetails >
237
306
) }
238
307
</ VelocityTransitionGroup >
0 commit comments