Skip to content

Commit

Permalink
Merge pull request #801 from 18F/bjs-agency-search-refine
Browse files Browse the repository at this point in the history
agency search refine
  • Loading branch information
jeremiak authored Jun 1, 2017
2 parents 02ff1f5 + 69240c3 commit 042eb5c
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 121 deletions.
108 changes: 58 additions & 50 deletions src/components/AgencySearch.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import PropTypes from 'prop-types'
import React, { Component } from 'react'

import AgencySearchResults from './AgencySearchResults'

class AgencySearch extends Component {
state = { search: '', showSelection: true }
constructor(props) {
super(props)

const search = props.agency
const hasSelection = !!search
this.state = { search, hasSelection, showResults: false }
}

handleChange = e => {
this.setState({ search: e.target.value })
this.setState({
search: e.target.value,
hasSelection: false,
showResults: true,
})
}

handleClick = d => e => {
e.preventDefault()
this.setState({ search: '', showSelection: true })
this.setState({ search: d.agency_name, hasSelection: true })
this.props.onChange({ place: d.ori, placeType: 'agency' })
}

removeSelection = () => {
this.setState({ showSelection: false, search: '' })
clearInput = () => {
this.setState({ search: '', hasSelection: false })
}

render() {
const { agencies, ori, state } = this.props
const { showSelection, search } = this.state

const data = Object.keys(agencies[state]).map(id => ({
ori: id,
...agencies[state][id],
}))
toggleResults = () => {
this.setState(prevState => ({ showResults: !prevState.showResults }))
}

const selected = data.find(d => d.ori === ori)
render() {
const { data } = this.props
const { search, hasSelection, showResults } = this.state

// get unique set of counties (for result grouping)
const counties = {}
Expand All @@ -42,48 +50,48 @@ class AgencySearch extends Component {
return words.includes(searchUpper)
})

const showOris = !!search.length && dataFiltered.length > 0

return (
<div className="mt2">
{selected && showSelection
? <div className="mb2 relative">
<input
type="text"
className="col-12 field field-sm bg-white border-blue pr5 truncate"
defaultValue={selected.agency_name}
<div className="relative">
<div className="relative">
<input
type="text"
className="col-12 field field-sm bold bg-white border-blue rounded-none"
placeholder="Search for an agency..."
value={search}
onChange={this.handleChange}
/>
<button
className="absolute btn p0 line-height-1"
style={{ top: '.5rem', right: '1rem' }}
onClick={hasSelection ? this.clearInput : this.toggleResults}
>
<img
src={`/img/${hasSelection ? 'x-navy' : 'chevron-down-navy'}.svg`}
alt="close"
width="12"
height="12"
/>
<button
className="absolute btn p0 line-height-1"
style={{ top: '.5rem', right: '1rem' }}
onClick={this.removeSelection}
>
<img src="/img/x-navy.svg" alt="close" width="12" height="12" />
</button>
</div>
: <div className="relative">
<div className="relative">
<input
type="text"
className="col-12 field field-sm bg-white border-blue rounded-none"
placeholder="Search for an agency..."
value={search}
onChange={this.handleChange}
/>
</div>
{showOris &&
<AgencySearchResults
data={dataFiltered.sort(
(a, b) => a.agency_name > b.agency_name,
)}
groupKey="primary_county"
groupValues={Object.keys(counties).sort()}
onClick={this.handleClick}
/>}
</div>}
</button>
</div>
{!hasSelection &&
showResults &&
dataFiltered.length > 0 &&
<AgencySearchResults
data={dataFiltered.sort((a, b) => a.agency_name > b.agency_name)}
groupKey="primary_county"
groupValues={Object.keys(counties).sort()}
onClick={this.handleClick}
/>}
</div>
</div>
)
}
}

AgencySearch.propTypes = {
agency: PropTypes.string.isRequired,
data: PropTypes.arrayOf(PropTypes.object).isRequired,
}

export default AgencySearch
4 changes: 2 additions & 2 deletions src/components/Explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Explorer extends React.Component {
}

render() {
const { appState, dispatch, params, router } = this.props
const { appState, dispatch, params } = this.props
const { crime } = params
const { place, placeType } = getPlaceInfo(params)

Expand Down Expand Up @@ -91,7 +91,7 @@ class Explorer extends React.Component {
</button>
</div>
</div>
<SidebarContainer onChange={this.handleSidebarChange} router={router} />
<SidebarContainer onChange={this.handleSidebarChange} />
<div className="site-content">
<div className="container-main mx-auto px2 md-py3 lg-px8">
<ExplorerHeader
Expand Down
43 changes: 17 additions & 26 deletions src/components/LocationFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,30 @@ import React from 'react'

import AgencySearch from './AgencySearch'
import LocationSelect from './LocationSelect'
import { oriToState } from '../util/ori'
import { nationalKey } from '../util/usa'

const LocationFilter = ({
agencies,
agency,
agencyData,
onChange,
place,
placeType,
showSearch,
}) => {
const isNational = place === nationalKey
const isAgency = placeType === 'agency'
const selected = isAgency ? oriToState(place) : place

return (
<div id="location" className="mb4">
<div className="mb3 fs-22 bold border-bottom">Location</div>
<LocationSelect onChange={onChange} selected={startCase(selected)} />
{showSearch &&
selected &&
!isNational &&
<AgencySearch
agencies={agencies.data}
onChange={onChange}
ori={isAgency && place}
state={selected}
/>}
</div>
)
}
usState,
}) => (
<div id="location" className="mb4">
<div className="mb3 fs-22 bold border-bottom">Location</div>
<LocationSelect onChange={onChange} selected={startCase(usState)} />
{showSearch &&
usState !== nationalKey &&
<AgencySearch
onChange={onChange}
agency={(agency || {}).agency_name || ''}
data={agencyData}
/>}
</div>
)

LocationFilter.defaultProps = {
selected: '',
usState: '',
showSearch: false,
}

Expand Down
107 changes: 64 additions & 43 deletions src/components/SidebarContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,80 @@ import CrimeTypeFilter from './CrimeTypeFilter'
import LocationFilter from './LocationFilter'
import TimePeriodFilter from './TimePeriodFilter'
import { hideSidebar } from '../actions/sidebar'
import { getAgency, oriToState } from '../util/ori'

const SidebarContainer = ({
agencies,
dispatch,
agency,
agencyData,
crime,
hide,
filters,
isOpen,
onChange,
}) => {
const { crime, place, placeType } = filters
const hide = () => dispatch(hideSidebar())

return (
<nav className={`site-sidebar bg-white ${isOpen ? 'open' : ''}`}>
<div className="p2 bg-red-bright line-height-1 md-hide lg-hide">
<button
type="button"
className="right btn p0 fs-12 caps line-height-4 black"
onClick={hide}
>
Close
</button>
<img
className="align-middle"
width="22"
height="20"
src="/img/filters.svg"
alt="filters"
/>
</div>
<div className="p6 sm-p3 md-p4">
<LocationFilter
agencies={agencies}
onChange={onChange}
place={place}
placeType={placeType}
showSearch={filters.agencySearch === 'true'}
/>
<TimePeriodFilter onChange={onChange} {...filters} />
<CrimeTypeFilter onChange={onChange} selected={crime} />
</div>
</nav>
)
}
showSearch,
usState,
}) => (
<nav className={`site-sidebar bg-white ${isOpen ? 'open' : ''}`}>
<div className="p2 bg-red-bright line-height-1 md-hide lg-hide">
<button
type="button"
className="right btn p0 fs-12 caps line-height-4 black"
onClick={hide}
>
Close
</button>
<img
className="align-middle"
width="22"
height="20"
src="/img/filters.svg"
alt="filters"
/>
</div>
<div className="p6 sm-p3 md-p4">
<LocationFilter
agency={agency}
agencyData={agencyData}
onChange={onChange}
showSearch={showSearch}
usState={usState}
/>
<TimePeriodFilter onChange={onChange} {...filters} />
<CrimeTypeFilter onChange={onChange} selected={crime} />
</div>
</nav>
)

SidebarContainer.propTypes = {
onChange: PropTypes.func,
}

const mapStateToProps = ({ agencies, filters, sidebar }) => ({
agencies,
filters,
isOpen: sidebar.isOpen,
const formatAgencyData = (agencies, state) =>
Object.keys(agencies[state]).map(id => ({
ori: id,
...agencies[state][id],
}))

const mapStateToProps = ({ agencies, filters, sidebar }) => {
const { agencySearch, crime, place, placeType } = filters

const isAgency = placeType === 'agency'
const usState = isAgency ? oriToState(place) : place
const agency = isAgency && getAgency(agencies, place)
const agencyData = usState && formatAgencyData(agencies.data, usState)

return {
agency,
agencyData,
crime,
filters,
isOpen: sidebar.isOpen,
showSearch: agencySearch === 'true',
usState,
}
}
const mapDispatchToProps = dispatch => ({
hide: () => dispatch(hideSidebar()),
})
const mapDispatchToProps = dispatch => ({ dispatch })

export default connect(mapStateToProps, mapDispatchToProps)(SidebarContainer)

0 comments on commit 042eb5c

Please sign in to comment.