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

Make date selector work on date ranges within the past week #1273

Merged
merged 3 commits into from
Jul 11, 2022
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
8 changes: 5 additions & 3 deletions client/components/Map/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { getDataRequestSuccess } from '@reducers/data';
import { updateMapPosition } from '@reducers/ui';
import { trackMapExport } from '@reducers/analytics';
import CookieNotice from '../main/CookieNotice';
import { DATE_SPEC } from '../common/CONSTANTS';
// import "mapbox-gl/dist/mapbox-gl.css";
import Map from './Map';
import moment from 'moment';
Expand Down Expand Up @@ -64,8 +63,8 @@ class MapContainer extends React.Component {
getAllRequests = async () => {
const { startDate, endDate } = this.props;
const url = new URL(`${process.env.API_URL}/requests`);
url.searchParams.append("start_date", moment(startDate, DATE_SPEC).format('YYYY-MM-DD'));
url.searchParams.append("end_date", moment(endDate, DATE_SPEC).format('YYYY-MM-DD'));
url.searchParams.append("start_date", startDate);
url.searchParams.append("end_date", endDate);
url.searchParams.append("limit", `${REQUEST_BATCH_SIZE}`);
var returned_length = REQUEST_BATCH_SIZE;
var skip = 0;
Expand Down Expand Up @@ -100,6 +99,9 @@ class MapContainer extends React.Component {
requestId: request.requestId,
typeId: request.typeId,
closedDate: request.closedDate,
// Store this in milliseconds so that it's easy to do date comparisons
// using Mapbox GL JS filters.
createdDateMs: moment(request.createdDate).valueOf(),
},
geometry: {
type: 'Point',
Expand Down
50 changes: 41 additions & 9 deletions client/components/Map/layers/RequestsLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { INTERNAL_DATE_SPEC } from '../../common/CONSTANTS';
import moment from 'moment';

// put layer underneath this layer (from original mapbox tiles)
// so you don't cover up important labels
Expand Down Expand Up @@ -72,6 +74,23 @@ function statusFilter(requestStatus) {
return ['!=', [GET, CLOSED_DATE], [LITERAL, null]];
}

/**
* Gets a MapBox GL JS filter specification to filter requests by date range.
*
* @param {string} startDate The start date, in YYYY-MM-DD format.
* @param {string} endDate The end date, in YYYY-MM-DD format.
* @return {Array} A Mapbox GL JS filter specification that filters out
* requests outside of the date range.
*/
function dateFilter(startDate, endDate) {
const startDateMs = moment(startDate, INTERNAL_DATE_SPEC).valueOf();
// Make the end date inclusive by adding 1 day.
const endDateMs = moment(endDate, INTERNAL_DATE_SPEC).add(1, 'days').valueOf();
const afterStartDate = ['>=', [GET, 'createdDateMs'], [LITERAL, startDateMs]];
const beforeEndDate = ['<=', [GET, 'createdDateMs'], [LITERAL, endDateMs]];
return ['all', afterStartDate, beforeEndDate];
}

class RequestsLayer extends React.Component {
constructor(props) {
super(props);
Expand All @@ -92,18 +111,23 @@ class RequestsLayer extends React.Component {
requestStatus,
requests,
colorScheme,
startDate,
endDate,
} = this.props;

if (activeLayer !== prev.activeLayer)
this.setActiveLayer(activeLayer);

// Check if the selected types OR the request status has changed.
// Check if the selected types OR the request status OR the date range has
// changed.
// These filters need to be updated together, since they are
// actually composed into a single filter.
if (selectedTypes !== prev.selectedTypes ||
requestStatus.open !== prev.requestStatus.open ||
requestStatus.closed !== prev.requestStatus.closed) {
this.setFilters(selectedTypes, requestStatus);
requestStatus.closed !== prev.requestStatus.closed ||
startDate != prev.startDate ||
endDate != prev.endDate) {
this.setFilters(selectedTypes, requestStatus, startDate, endDate);
}
if (requests !== prev.requests && this.ready) {
this.setRequests(requests);
Expand All @@ -128,6 +152,8 @@ class RequestsLayer extends React.Component {
colorScheme,
requestTypes,
requestStatus,
startDate,
endDate,
} = this.props;

this.map.addLayer({
Expand All @@ -148,7 +174,8 @@ class RequestsLayer extends React.Component {
'circle-color': circleColors(requestTypes),
'circle-opacity': 0.8,
},
filter: this.getFilterSpec(selectedTypes, requestStatus),
filter: this.getFilterSpec(selectedTypes, requestStatus, startDate,
endDate),
}, BEFORE_ID);

// this.map.addLayer({
Expand Down Expand Up @@ -190,16 +217,19 @@ class RequestsLayer extends React.Component {
* @param {Object} requestStatus A mapping of k:v, where k is a request status
* (either open or closed), and v is a boolean indicating whether the request
* status is selected.
* @return {Array} A Mapbox GL JS filter specification that filters out the
* @param {string} startDate The start date, in YYYY-MM-DD format.
* @param {string} endDate The end date, in YYYY-MM-DD format.
* @return {Array} A Mapbox GL JS filter specification that filters out the
* unselected types and statuses.
*/
getFilterSpec = (selectedTypes, requestStatus) => {
return ['all', typeFilter(selectedTypes), statusFilter(requestStatus)];
getFilterSpec = (selectedTypes, requestStatus, startDate, endDate) => {
return ['all', typeFilter(selectedTypes), statusFilter(requestStatus),
dateFilter(startDate, endDate)];
};

setFilters = (selectedTypes, requestStatus) => {
setFilters = (selectedTypes, requestStatus, startDate, endDate) => {
this.map.setFilter('request-circles',
this.getFilterSpec(selectedTypes, requestStatus));
this.getFilterSpec(selectedTypes, requestStatus, startDate, endDate));
// Currently, we do not support heatmap. If we did, we'd want to update
// its filter here as well.
};
Expand Down Expand Up @@ -235,6 +265,8 @@ const mapStateToProps = state => ({
selectedTypes: state.filters.requestTypes,
requestStatus: state.filters.requestStatus,
requests: state.data.requests,
startDate: state.filters.startDate,
endDate: state.filters.endDate,
});

// We need to specify forwardRef to allow refs on connected components.
Expand Down
14 changes: 10 additions & 4 deletions client/components/common/CONSTANTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -829,11 +829,17 @@ export const MAP_DATE_RANGES = (() => {
];
})();

export const DATE_SPEC = 'MM/DD/YYYY';
// The user gets this date format since it's used most commonly in the US.
export const USER_DATE_SPEC = 'MM/DD/YYYY';
// Internally, we use this date spec. This is what our server expects when we
// request data from a certain date range.
export const INTERNAL_DATE_SPEC = 'YYYY-MM-DD';

export const DATE_RANGES = (() => {
const endDate = moment().format(DATE_SPEC);
const priorDate = (num, timeInterval) => moment().subtract(num, timeInterval).format(DATE_SPEC);
const endDate = moment().format(USER_DATE_SPEC);
function priorDate(num, timeInterval) {
return moment().subtract(num, timeInterval).format(USER_DATE_SPEC);
}

return [
{
Expand Down Expand Up @@ -875,7 +881,7 @@ export const DATE_RANGES = (() => {
{
id: 'YEAR_TO_DATE',
label: 'Year to Date',
startDate: moment().startOf('year').format(DATE_SPEC),
startDate: moment().startOf('year').format(USER_DATE_SPEC),
endDate,
},
{
Expand Down
32 changes: 27 additions & 5 deletions client/components/common/ReactDayPicker/ReactDayPicker.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import 'react-day-picker/lib/style.css';

import {
updateEndDate as reduxUpdateEndDate,
updateStartDate as reduxUpdateStartDate,
} from '@reducers/filters';
import moment from 'moment';
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import DayPicker, { DateUtils } from 'react-day-picker';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { INTERNAL_DATE_SPEC } from '../CONSTANTS';
import Styles from './Styles';
import WeekDay from './Weekday';

import 'react-day-picker/lib/style.css';

const getInitialState = initialDates => {
const [from, to] = initialDates;
return {
Expand All @@ -17,7 +25,10 @@ const getInitialState = initialDates => {

const defaultState = { from: null, to: null };

function ReactDayPicker({ onChange, initialDates, range }) {
function ReactDayPicker({
onChange, initialDates, range, updateStartDate,
updateEndDate,
}) {
const [state, setState] = useState(getInitialState(initialDates));

const isSelectingFirstDay = (from, to, day) => {
Expand All @@ -37,6 +48,7 @@ function ReactDayPicker({ onChange, initialDates, range }) {
to: null,
enteredTo: null,
}));
updateStartDate(moment(day).format(INTERNAL_DATE_SPEC));
onChange([day]);
};

Expand All @@ -46,6 +58,7 @@ function ReactDayPicker({ onChange, initialDates, range }) {
to: day,
enteredTo: day,
}));
updateEndDate(moment(day).format(INTERNAL_DATE_SPEC));
onChange([state.from, day]);
};

Expand Down Expand Up @@ -106,12 +119,21 @@ ReactDayPicker.propTypes = {
range: PropTypes.bool,
onChange: PropTypes.func,
initialDates: PropTypes.arrayOf(Date),
updateStartDate: PropTypes.func,
updateEndDate: PropTypes.func,
};

ReactDayPicker.defaultProps = {
range: false,
onChange: null,
initialDates: [],
updateStartDate: null,
updateEndDate: null,
};

export default ReactDayPicker;
const mapDispatchToProps = dispatch => ({
updateStartDate: date => dispatch(reduxUpdateStartDate(date)),
updateEndDate: date => dispatch(reduxUpdateEndDate(date)),
});

export default connect(null, mapDispatchToProps)(ReactDayPicker);
8 changes: 5 additions & 3 deletions client/redux/reducers/filters.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DATE_RANGES } from '@components/common/CONSTANTS';
import { DATE_RANGES, INTERNAL_DATE_SPEC, USER_DATE_SPEC } from '@components/common/CONSTANTS';
import moment from 'moment';

export const types = {
UPDATE_START_DATE: 'UPDATE_START_DATE',
Expand Down Expand Up @@ -37,8 +38,9 @@ export const updateRequestStatus = status => ({

const initialState = {
// dateRange: null,
startDate: DATE_RANGES[0].startDate,
endDate: DATE_RANGES[0].endDate,
// Always store dates using the INTERNAL_DATE_SPEC.
startDate: moment(DATE_RANGES[0].startDate, USER_DATE_SPEC).format(INTERNAL_DATE_SPEC),
endDate: moment(DATE_RANGES[0].endDate, USER_DATE_SPEC).format(INTERNAL_DATE_SPEC),
councilId: null,
requestTypes: {
1: false,
Expand Down