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

Set up routes so that we can easily add an agency view #703

Merged
merged 8 commits into from
Apr 24, 2017
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
13 changes: 5 additions & 8 deletions src/actions/composite.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@ import { fetchSummaries } from '../actions/summary'
import { fetchUcrParticipation } from '../actions/ucr'
import history, { createNewLocation } from '../util/history'
import { shouldFetchUcr, shouldFetchSummaries, shouldFetchNibrs } from '../util/ucr'
import { nationalKey } from '../util/usa'


const fetchData = () => (dispatch, getState) => {
const { filters, ucr } = getState()
const fetchNational = !ucr.data[nationalKey] && filters.place !== nationalKey
const { filters } = getState()

if (fetchNational) dispatch(fetchUcrParticipation(nationalKey))
if (shouldFetchUcr(filters)) dispatch(fetchUcrParticipation(filters.place))
if (shouldFetchUcr(filters)) dispatch(fetchUcrParticipation(filters))
if (shouldFetchSummaries(filters)) dispatch(fetchSummaries(filters))
if (shouldFetchNibrs(filters)) dispatch(fetchNibrs(filters))
}

export const updateApp = (change, location) => dispatch => {
export const updateApp = (change, router) => dispatch => {
dispatch(updateFilters(change))

if (location) {
history.push(createNewLocation({ change, location }))
if (router) {
history.push(createNewLocation({ change, router }))
}

return dispatch(fetchData())
Expand Down
17 changes: 11 additions & 6 deletions src/actions/ucr.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ export const fetchingUcrParticipation = () => ({
type: UCR_PARTICIPATION_FETCHING,
})

export const receivedUcrParticipation = ({ place, results }) => ({
export const receivedUcrParticipation = results => ({
type: UCR_PARTICIPATION_RECEIVED,
place,
results,
})

export const fetchUcrParticipation = place => dispatch => {
export const fetchUcrParticipation = params => dispatch => {
dispatch(fetchingUcrParticipation())

return api.getUcrParticipation(place).then(d => (
dispatch(receivedUcrParticipation(d))
))
const requests = api.getUcrParticipationRequests(params)

return Promise.all(requests).then(data => {
const results = Object.assign(
...data.map(d => ({ [d.place]: d.results })),
)

dispatch(receivedUcrParticipation(results))
})
}
7 changes: 6 additions & 1 deletion src/components/CrimeTypeFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import { Link } from 'react-router'

import FilterGroup from './FilterGroup'
import { crimeTypes } from '../util/data'
import { crimeTypes } from '../util/offenses'


const { violentCrime, propertyCrime } = crimeTypes
Expand Down Expand Up @@ -33,6 +33,11 @@ const CrimeTypeFilter = ({ onChange, selected }) => (
</div>
)

CrimeTypeFilter.propTypes = {
onChange: React.PropTypes.func.isRequired,
selected: React.PropTypes.string,
}

CrimeTypeFilter.defaultProps = {
selected: '',
}
Expand Down
91 changes: 27 additions & 64 deletions src/components/Explorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,12 @@ import { updateApp } from '../actions/composite'
import { hideSidebar, showSidebar } from '../actions/sidebar'
import offenses from '../util/offenses'
import ucrParticipation from '../util/ucr'
import lookup from '../util/usa'
import lookup, { nationalKey } from '../util/usa'


const filterNibrsData = (data, { since, until }) => {
if (!data) return false
const filtered = {}
Object.keys(data).forEach(key => {
filtered[key] = data[key].filter(d => {
const year = parseInt(d.year, 10)
return year >= since && year <= until
})
})

return filtered
}

const dataByYear = data => (
Object.assign(
...Object.keys(data).map(k => ({
[k]: Object.assign(...data[k].map(d => ({ [d.year]: d }))),
})),
)
)

const mungeSummaryData = (summaries, ucr, place) => {
if (!summaries || !summaries[place]) return false

const keys = Object.keys(summaries)
const summaryByYear = dataByYear(summaries)
const ucrByYear = dataByYear(ucr)

return summaries[place].map(d => (
Object.assign(
{ date: d.year },
...keys.map(k => {
const count = summaryByYear[k][d.year].actual
const pop = ucrByYear[k][d.year].total_population
return { [k]: { count, pop, rate: (count / pop) * 100000 } }
}),
)
))
}
const getPlaceInfo = ({ place, placeType }) => ({
place: place || nationalKey,
placeType: placeType || 'national',
})

class Explorer extends React.Component {
constructor(props) {
Expand All @@ -64,15 +28,17 @@ class Explorer extends React.Component {

componentDidMount() {
const { appState, dispatch, router } = this.props
const { since, until } = appState.filters
const { query } = router.location
const { since, until } = appState.filters
const { place } = getPlaceInfo(appState.filters)

const clean = (val, alt) => {
const yr = +val
return yr >= 1960 && yr <= 2014 ? yr : alt
}

const filters = {
place,
...this.props.filters,
...router.params,
since: clean(query.since, since),
Expand All @@ -83,8 +49,8 @@ class Explorer extends React.Component {
}

handleSidebarChange(change) {
const { location } = this.props.router
this.props.dispatch(updateApp(change, location))
const { router } = this.props
this.props.dispatch(updateApp(change, router))
}

toggleSidebar() {
Expand All @@ -97,18 +63,18 @@ class Explorer extends React.Component {

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

// show not found page if crime or place unfamiliar
if (!offenses.includes(crime) || !lookup(place)) return <NotFound />
if (!offenses.includes(crime) || !lookup(place, placeType)) {
return <NotFound />
}

const { filters, nibrs, sidebar, summaries, ucr } = appState
const nibrsData = filterNibrsData(nibrs.data, filters)
const noNibrs = ['violent-crime', 'property-crime']
const participation = ucrParticipation(place)
const showNibrs = (!noNibrs.includes(crime) && participation.nibrs)
const trendData = mungeSummaryData(summaries.data, ucr.data, place)
const trendKeys = Object.keys(summaries.data).map(k => startCase(k))

return (
<div className='site-wrapper'>
Expand Down Expand Up @@ -144,28 +110,29 @@ class Explorer extends React.Component {
</div>
<UcrParticipationInformation
dispatch={dispatch}
place={params.place}
place={place}
placeType={placeType}
until={filters.until}
ucr={ucr}
/>
<hr className='mt0 mb3' />
<TrendContainer
crime={crime}
place={place}
filters={filters}
data={trendData}
dispatch={dispatch}
loading={summaries.loading}
keys={trendKeys}
place={place}
placeType={placeType}
since={filters.since}
summaries={summaries}
ucr={ucr}
until={filters.until}
/>
{showNibrs && (<NibrsContainer
crime={params.crime}
data={nibrsData}
dispatch={dispatch}
error={nibrs.error}
filters={filters}
loading={nibrs.loading}
place={params.place}
nibrs={nibrs}
place={place}
since={filters.since}
until={filters.until}
/>)}
<hr className='mt0 mb3' />
<AboutTheData crime={crime} place={place} />
Expand All @@ -176,8 +143,4 @@ class Explorer extends React.Component {
}
}

Explorer.defaultProps = {
params: { crime: 'murder', place: 'ohio' },
}

export default Explorer
2 changes: 1 addition & 1 deletion src/components/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const links = [
},
{
text: 'Explorer',
href: '/explorer/united-states/violent-crime',
href: '/explorer/violent-crime',
},
{
text: 'About',
Expand Down
4 changes: 1 addition & 3 deletions src/components/Header.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from 'react'
import { Link } from 'react-router'

import { nationalKey } from '../util/usa'


const isHome = loc => loc.pathname === '/'
const isExplorer = loc => loc.pathname.includes('explorer')
Expand Down Expand Up @@ -60,7 +58,7 @@ class Header extends React.Component {
</li>
<li className='mb1 md-m0'>
<Link
to={`/explorer/${nationalKey}/violent-crime`}
to={'/explorer/violent-crime'}
className='mx1 lg-mx2 fs-14 md-fs-18 white'
style={isExplorer(location) ? active : {}}
>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import stateLookup from '../util/usa'
import otherDataSets from '../../content/datasets.yml'


const Home = ({ appState, dispatch, location }) => {
const Home = ({ appState, dispatch, router }) => {
const { crime, place } = appState.filters
const isValid = !!(crime && place) || false

Expand All @@ -24,7 +24,7 @@ const Home = ({ appState, dispatch, location }) => {

const handleSearchClick = () => {
const change = { crime, place }
dispatch(updateApp(change, location))
dispatch(updateApp(change, router))
}

const selectCrime = e => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'


const Loading = ({ text }) => (
<div className='mt3 mb8 fs-14 caps center'>
<div className='mt3 mb8 fs-14 caps sans-serif center'>
<img
className='align-middle mr1'
width='30'
Expand Down
43 changes: 34 additions & 9 deletions src/components/NibrsContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,30 @@ const initialNibrsYear = ({ place, since }) => {
return since
}

const filterNibrsData = (data, { since, until }) => {
if (!data) return false
const filtered = {}
Object.keys(data).forEach(key => {
filtered[key] = data[key].filter(d => {
const year = parseInt(d.year, 10)
return year >= since && year <= until
})
})

return filtered
}

const NibrsContainer = ({
crime,
data,
dispatch,
error,
filters,
loading,
nibrs,
place,
since,
until,
}) => {
const { since, until } = filters
const { data, error, loading } = nibrs

const nibrs = (
const nibrsTerm = (
<Term id='national incident-based reporting system (NIBRS)' dispatch={dispatch}>
incident-based (NIBRS)
</Term>
Expand All @@ -42,7 +54,8 @@ const NibrsContainer = ({

let [totalCount, content] = [0, <Loading />]
if (!loading && data) {
const dataParsed = parseNibrs(data)
const filteredData = filterNibrsData(data, { since, until })
const dataParsed = parseNibrs(filteredData)
totalCount = data.offenderRaceCode.reduce((a, b) => (a + b.count), 0)
content = (
<div className='clearfix mxn1'>
Expand Down Expand Up @@ -76,7 +89,7 @@ const NibrsContainer = ({
</h2>
{(nibrsFirstYear !== since) && (
<p className='my-tiny'>
{startCase(place)} started reporting {nibrs} data
{startCase(place)} started reporting {nibrsTerm} data
to the FBI in {nibrsFirstYear}.
</p>
)}
Expand All @@ -94,11 +107,23 @@ const NibrsContainer = ({
{content}
{!loading && (
<div className='center italic fs-12 mb8'>
<p>Source: Reported {nibrs} data from {startCase(place)}, {nibrsFirstYear}–{until}.</p>
<p>Source: Reported {nibrsTerm} data from {startCase(place)}, {nibrsFirstYear}–{until}.</p>
</div>
)}
</div>
)
}

NibrsContainer.propTypes = {
crime: React.PropTypes.string.isRequired,
dispatch: React.PropTypes.func.isRequired,
nibrs: React.PropTypes.shape({
data: React.PropTypes.object,
loading: React.PropTypes.boolean,
}).isRequired,
place: React.PropTypes.string.isRequired,
since: React.PropTypes.number.isRequired,
until: React.PropTypes.number.isRequired,
}

export default NibrsContainer
4 changes: 2 additions & 2 deletions src/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import TimePeriodFilter from './TimePeriodFilter'
import { hideSidebar } from '../actions/sidebar'


const Sidebar = ({ dispatch, filters, isOpen, onChange, router }) => {
const { crime, place } = router.params
const Sidebar = ({ dispatch, filters, isOpen, onChange }) => {
const { crime, place } = filters
const hide = () => dispatch(hideSidebar())

return (
Expand Down
Loading