Skip to content

Commit

Permalink
filters and tweets
Browse files Browse the repository at this point in the history
  • Loading branch information
ahgroner committed Jun 10, 2020
1 parent 03acc4e commit 6f10eed
Show file tree
Hide file tree
Showing 21 changed files with 17,683 additions and 73 deletions.
16,848 changes: 16,848 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/lodash": "^4.14.155",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"@types/styled-components": "^5.1.0",
"axios": "^0.19.2",
"lodash": "^4.17.15",
"moment": "^2.26.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"react-twitter-embed": "^3.0.3",
"styled-components": "^5.1.1",
"typescript": "~3.7.2"
},
"scripts": {
Expand Down
4 changes: 4 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
name="description"
content="Web site created using create-react-app"
/>
<link
href="https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;700;900&display=swap"
rel="stylesheet"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
Expand Down
12 changes: 12 additions & 0 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "drone_360.mp4",
"type": "video/mp4"
},
{
"src": "drone_720.mp4",
"type": "video/mp4"
},
{
"src": "drone_1080.mp4",
"type": "video/mp4"
}
],
"start_url": ".",
Expand Down
38 changes: 0 additions & 38 deletions src/App.css

This file was deleted.

29 changes: 13 additions & 16 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { DataContextProvider } from './components/data-context';
import { Header } from './components/header';
import { List } from './components/list';

import { GlobalStyle } from './components/global-style';
import { Filter } from './components/filter';


function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<GlobalStyle />
<DataContextProvider>
<Header />
<Filter />
<List />
</DataContextProvider>
</div>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import axios from 'axios';

export const fetchEvents = () =>
axios.get(
"https://raw.githubusercontent.com/2020PB/police-brutality/data_build/all-locations.json"
);
88 changes: 88 additions & 0 deletions src/components/data-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState, createContext, useMemo } from "react";
import { fetchEvents } from "../actions";
import { Incident, Categories } from "../types";
import { cleanData, getCategoryCounts, compareByDate } from "../utils/data";
import _ from "lodash";

interface DataContextProps {
incidents: Incident[];
totalCount: number;
categoryCounts: Record<Categories, number>;
category?: Categories;
setCategory: (cat?: Categories) => void;
cityState?: string;
setCityState: (cityState?: string) => void;
cityStateCounts: Record<string, number>;
}

const DataContext = createContext<DataContextProps>({
totalCount: 0,
incidents: [],
categoryCounts: {} as Record<Categories, number>,
setCategory: () => {},
cityState: undefined,
setCityState: () => {},
cityStateCounts: {},
});

export const useData = () => React.useContext(DataContext);

export const DataContextProvider = ({ children }: any) => {
const [incidents, setIncidents] = useState<Incident[]>([]);
const [category, setCategory] = useState<Categories | undefined>();
const [cityState, setCityState] = useState<string>();

React.useEffect(() => {
fetchEvents().then((res) => {
const {
data: { data },
} = res;
const cleaned = cleanData(data);
const cleanedSorted = cleaned.sort(compareByDate);
setIncidents(cleanedSorted);
});
}, []);

const categoryCounts = useMemo(() => getCategoryCounts(incidents), [
incidents,
]);

const categoryFilteredIncidents = useMemo(
() =>
category
? incidents.filter((i) => i.categories.includes(category))
: incidents,
[incidents, category]
);

const groupedByCityState = useMemo(
() => _.groupBy(categoryFilteredIncidents, (i) => `${i.city}, ${i.state}`),
[categoryFilteredIncidents]
);

const cityStateCounts = _.mapValues(groupedByCityState, (arr) => arr.length);

const categoryCityStateFilteredIncidents = useMemo(
() =>
cityState ? groupedByCityState[cityState] : categoryFilteredIncidents,
[cityState, groupedByCityState, categoryFilteredIncidents]
);

const loading = !incidents.length;
return (
<DataContext.Provider
value={{
incidents: categoryCityStateFilteredIncidents,
categoryCounts,
category,
setCategory,
cityStateCounts,
cityState,
setCityState,
totalCount: incidents.length,
}}
>
{loading ? loading : children}
</DataContext.Provider>
);
};
161 changes: 161 additions & 0 deletions src/components/filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React, { useState } from "react";
import styled from "styled-components";
import { mobileRules } from "../constants/style";
import { useData } from "./data-context";
import { Categories } from "../types";
// import CrowdBannerVid from "../assets/drone_720_trimmed.mp4";

export const Filter = () => {
const {
categoryCounts,
category,
setCategory,
cityStateCounts,
cityState,
setCityState,
} = useData();

const categoryOptions = [
{
onClick: () => setCategory(),
text: `Show all incidents`,
},
{
onClick: () => setCategory(Categories.gas),
text: `${categoryCounts[Categories.gas]} Chemical attacks`,
},
{
onClick: () => setCategory(Categories.shooting_gun),
text: `${categoryCounts[Categories.shooting_gun]} Shootings by firearms`,
},
{
onClick: () => setCategory(Categories.shooting_rubber),
text: `${
categoryCounts[Categories.shooting_rubber]
} Shootings by rubber bullets/projectiles`,
},
{
onClick: () => setCategory(Categories.assault),
text: `${categoryCounts[Categories.assault]} Assaults`,
},
{
onClick: () => setCategory(Categories.arrest),
text: `${categoryCounts[Categories.arrest]} Unjustified arrests`,
},
{
onClick: () => setCategory(Categories.other),
text: `${categoryCounts[Categories.arrest]} other bullsh*t`,
},
];
const cityStateOptions = Object.keys(cityStateCounts)
.sort(
(cityStateA, cityStateB) =>
cityStateCounts[cityStateB] - cityStateCounts[cityStateA]
)
.map((cityState) => ({
text: `${cityState}, (${cityStateCounts[cityState]})`,
onClick: () => {
setCityState(cityState);
},
}));

const allCityStateOptions = [
{
text: `ENTIRE U.S.`,
onClick: () => setCityState(),
},
...cityStateOptions,
];

const categoryLabel = category?.toUpperCase() || "ALL INCIDENTS";
const cityStateLabel = cityState?.toUpperCase() || "ENTIRE U.S.";

return (
<FilterText>
SEE <Dropdown label={categoryLabel} options={categoryOptions} /> IN
<Dropdown label={cityStateLabel} options={allCityStateOptions} /> BELOW
</FilterText>
);
};

const FilterText = styled.div`
font-size: 36px;
font-weight: 700;
padding: 16px 48px;
display: flex;
background: yellow;
color: black;
margin-bottom: 48px;
display: flex;
flex-flow: row;
align-items: center;
`;

const DropdownWrapper = styled.div`
position: relative;
`;

const DropdownOptions = styled.div`
position: absolute;
top: 100%;
left: 16px;
background: black;
color: white;
font-weight: 500;
border: 8px solid white;
width: 500px;
text-transform: uppercase;
max-height: 80vh;
overflow: auto;
z-index: 1;
`;

const Option = styled.div`
padding: 16px;
border-bottom: 4px solid white;
cursor: pointer;
&:hover {
background: rgba(255, 0, 0, 0.3);
}
`;

const DropdownToggle = styled.div`
border: 8px solid black;
padding: 24px;
margin: 0 24px;
cursor: pointer;
`;

interface DropdownProps {
label: string;
options: { text: string; onClick?: any }[];
}

const Dropdown = ({ label, options }: DropdownProps) => {
const [open, setOpen] = useState<boolean>(false);
return (
<DropdownWrapper>
<DropdownToggle
onClick={() => {
setOpen(!open);
}}
>
{label}
</DropdownToggle>
{open && (
<DropdownOptions>
{options.map((option: any) => (
<Option
onClick={() => {
setOpen(false);
option.onClick && option.onClick();
}}
>
{option.text}
</Option>
))}
</DropdownOptions>
)}
</DropdownWrapper>
);
};
12 changes: 12 additions & 0 deletions src/components/global-style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createGlobalStyle } from "styled-components";

export const GlobalStyle = createGlobalStyle`
body {
background: black;
color: white;
font-family: 'Rubik', sans-serif;
}
.EmbeddedTweet {
border: none;
}
`;
Loading

0 comments on commit 6f10eed

Please sign in to comment.