fbi-cde/crime-data-frontend

View on GitHub
src/containers/TrendContainer.js

Summary

Maintainability
A
2 hrs
Test Coverage
import startCase from 'lodash.startcase'
import pluralize from 'pluralize'
import PropTypes from 'prop-types'
import React from 'react'
import { connect } from 'react-redux'

import DownloadDataBtn from '../components/downloads/DownloadDataBtn'
import ErrorCard from '../components/ErrorCard'
import Loading from '../components/Loading'
import NoData from '../components/NoData'
import TrendChart from '../components/trend/TrendChart'
import TrendSourceText from '../components/trend/TrendSourceText'
import { generateCrimeReadme } from '../util/content'
import { getPlaceInfo } from '../util/place'
import { combinePlaces, filterByYear } from '../util/summary'
import { nationalKey } from '../util/usa'
import { lookupDisplayName } from '../util/location'

class TrendContainer extends React.Component {
  constructor(props) {
    super(props)
    const { until } = props
    this.state = { yearSelected: until }
  }

  getContent = ({ crime, filters, places, summaries, placeName }) => {
    const { loading, error } = summaries
    const { yearSelected } = this.state

    if (loading) return <Loading />
    if (error) return <ErrorCard error={error} />

    const offenses =
      crime === 'rape' ? ['rape-legacy', 'rape-revised'] : [crime]

    const filteredByYear = filterByYear(
      summaries.data,
      filters.since,
      filters.until
    )
    const data = combinePlaces(filteredByYear, offenses)
    if (!data || data.length === 0) return <NoData />

    const fname = `${filters.place}-${crime}-${filters.since}-${filters.until}`

    const title =
      `Reported ${pluralize(crime)} in ` +
      `${placeName}, ${filters.since}-${filters.until}`

    const readme = generateCrimeReadme({ crime, title })
    const crimeNorm = crime === 'rape' ? 'rape-legacy' : crime
    const dlData = data.map(d => {
      const placeData = places.map(p => ({ [p]: { ...d[p][crimeNorm] } }))
      return { year: d.year, ...Object.assign(...placeData) }
    })

    const download = [
      { content: readme, filename: 'README.md' },
      { data: dlData, filename: `${fname}.csv` }
    ]

    return (
      <div>
        <TrendChart
          crime={crime}
          filters={filters}
          data={data}
          places={places}
          onChangeYear={this.updateYear}
          initialYearSelected={yearSelected}
          placeName={placeName}
        />
        <DownloadDataBtn
          ariaLabel={`Download ${title} data as a CSV`}
          data={download}
          filename={fname}
        />
      </div>
    )
  }

  updateYear = year => {
    this.setState({ yearSelected: year })
  }

  render() {
    const { places, summaries, region, states, filters } = this.props
    const placeName = lookupDisplayName(filters, region.regions, states.states)
    const isReady = !summaries.loading

    let otherCrimes = []
    if (filters.pageType === 'violent-crime') {
      otherCrimes = ['homicide', 'rape', 'robbery', 'aggravated-assault']
    } else if (filters.pageType === 'property-crime') {
      otherCrimes = ['arson', 'burglary', 'larceny', 'motor-vehicle-theft']
    }
    const crime = filters.pageType
    const placeType = filters.placeType
    return (
      <div className="mb7">
        <div className="mb2 p2 sm-p4 bg-white border-top border-blue border-w8">
          <h2 className="mt0 mb2 sm-mb4 fs-24 sm-fs-28 sans-serif">
            {startCase(filters.pageType)} rate in{' '}
            {placeType === 'region' ? 'the' : ''} {placeName}, {filters.since}-{
              filters.until
            }
          </h2>
          <div className="bg-white">
            {this.getContent({ crime, filters, places, summaries, placeName })}
          </div>
        </div>
        <div>
          <div className="clearfix mxn1 trend-cards">
            {otherCrimes.map((other, i) => {
              const cls = i % 2 === 0 ? 'clear-left' : ''
              return (
                <div key={i} className={`col col-12 sm-col-6 mb2 px1 ${cls}`}>
                  <div className="bg-white p2">
                    <h3 className="mt0 mb2 sm-mb4 fs-18 sans-serif">
                      {startCase(other)}
                    </h3>
                    {this.getContent({
                      crime: other,
                      filters,
                      places,
                      summaries,
                      placeName
                    })}
                  </div>
                </div>
              )
            })}
          </div>
        </div>
        {isReady && (
          <TrendSourceText
            crime={filters.pageType}
            place={filters.place}
            placeType={filters.placeType}
            placeName={placeName}
          />
        )}
      </div>
    )
  }
}
TrendContainer.propTypes = {
  places: PropTypes.arrayOf(PropTypes.string),
  summaries: PropTypes.shape({
    data: PropTypes.object,
    loading: PropTypes.boolean
  }).isRequired,
  region: PropTypes.object,
  states: PropTypes.object
}

export const mapStateToProps = ({ filters, summaries, region, states }) => {
  const { place } = filters

  const places = [place]
  if (place !== nationalKey) places.push(nationalKey)

  return {
    places,
    summaries,
    ...getPlaceInfo(filters),
    region,
    states,
    filters
  }
}

const mapDispatchToProps = dispatch => ({ dispatch })

export default connect(mapStateToProps, mapDispatchToProps)(TrendContainer)