From 30f112c094a04bd846d3a3cd32fb0f590bba4601 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 6 Jun 2018 20:17:37 +0200 Subject: [PATCH] API & Client: chart with month activities on dashboard - #9 (WIP) --- mpwo_api/mpwo_api/activities/stats.py | 6 +- mpwo_api/mpwo_api/tests/test_stats_api.py | 28 ++-- mpwo_client/src/actions/stats.js | 13 ++ mpwo_client/src/components/App.css | 4 + .../src/components/Dashboard/Statistics.jsx | 137 ++++++++++++++++-- .../src/components/Dashboard/index.jsx | 2 +- mpwo_client/src/mwpoApi/index.js | 10 +- mpwo_client/src/mwpoApi/stats.js | 25 ++++ mpwo_client/src/reducers/index.js | 4 + mpwo_client/src/reducers/initial.js | 5 +- mpwo_client/src/utils.js | 37 ++++- 11 files changed, 235 insertions(+), 36 deletions(-) create mode 100644 mpwo_client/src/actions/stats.js create mode 100644 mpwo_client/src/mwpoApi/stats.js diff --git a/mpwo_api/mpwo_api/activities/stats.py b/mpwo_api/mpwo_api/activities/stats.py index 928bffeb..484c3145 100644 --- a/mpwo_api/mpwo_api/activities/stats.py +++ b/mpwo_api/mpwo_api/activities/stats.py @@ -70,14 +70,12 @@ def get_activities(user_id, type): activity_date = activity.activity_date - timedelta( days=activity.activity_date.isoweekday() ) - time_period = datetime.strftime(activity_date, - "%Y-%m-%d_W%U") + time_period = datetime.strftime(activity_date, "%Y-%m-%d") elif time == 'weekm': # week start Monday activity_date = activity.activity_date - timedelta( days=activity.activity_date.weekday() ) - time_period = datetime.strftime(activity_date, - "%Y-%m-%d_W%W") + time_period = datetime.strftime(activity_date, "%Y-%m-%d") elif time == 'month': time_period = datetime.strftime(activity.activity_date, "%Y-%m") # noqa elif time == 'year' or not time: diff --git a/mpwo_api/mpwo_api/tests/test_stats_api.py b/mpwo_api/mpwo_api/tests/test_stats_api.py index da435ebc..23795863 100644 --- a/mpwo_api/mpwo_api/tests/test_stats_api.py +++ b/mpwo_api/mpwo_api/tests/test_stats_api.py @@ -468,7 +468,7 @@ def test_get_stats_by_week_all_activities( assert 'success' in data['status'] assert data['data']['statistics'] == \ { - '2017-03-19_W12': + '2017-03-19': { '1': { @@ -477,7 +477,7 @@ def test_get_stats_by_week_all_activities( 'total_duration': 1024 } }, - '2017-05-28_W22': + '2017-05-28': { '1': { @@ -486,7 +486,7 @@ def test_get_stats_by_week_all_activities( 'total_duration': 3456 } }, - '2017-12-31_W53': + '2017-12-31': { '1': { @@ -495,7 +495,7 @@ def test_get_stats_by_week_all_activities( 'total_duration': 1024 } }, - '2018-02-18_W07': + '2018-02-18': { '1': { @@ -504,7 +504,7 @@ def test_get_stats_by_week_all_activities( 'total_duration': 1600 } }, - '2018-03-25_W12': + '2018-03-25': { '1': { @@ -519,7 +519,7 @@ def test_get_stats_by_week_all_activities( 'total_duration': 6000 } }, - '2018-05-06_W18': + '2018-05-06': { '1': { @@ -558,7 +558,7 @@ def test_get_stats_by_week_all_activities_week_13( assert 'success' in data['status'] assert data['data']['statistics'] == \ { - '2018-03-25_W12': + '2018-03-25': { '1': { @@ -603,7 +603,7 @@ def test_get_stats_by_weekm_all_activities( assert 'success' in data['status'] assert data['data']['statistics'] == \ { - '2017-03-20_W12': + '2017-03-20': { '1': { @@ -612,7 +612,7 @@ def test_get_stats_by_weekm_all_activities( 'total_duration': 1024 } }, - '2017-05-29_W22': + '2017-05-29': { '1': { @@ -621,7 +621,7 @@ def test_get_stats_by_weekm_all_activities( 'total_duration': 3456 } }, - '2018-01-01_W01': + '2018-01-01': { '1': { @@ -630,7 +630,7 @@ def test_get_stats_by_weekm_all_activities( 'total_duration': 1024 } }, - '2018-02-19_W08': + '2018-02-19': { '1': { @@ -639,7 +639,7 @@ def test_get_stats_by_weekm_all_activities( 'total_duration': 1600 } }, - '2018-03-26_W13': + '2018-03-26': { '1': { @@ -654,7 +654,7 @@ def test_get_stats_by_weekm_all_activities( 'total_duration': 6000 } }, - '2018-05-07_W19': + '2018-05-07': { '1': { @@ -693,7 +693,7 @@ def test_get_stats_by_weekm_all_activities_week_13( assert 'success' in data['status'] assert data['data']['statistics'] == \ { - '2018-03-26_W13': + '2018-03-26': { '1': { diff --git a/mpwo_client/src/actions/stats.js b/mpwo_client/src/actions/stats.js new file mode 100644 index 00000000..3562eea9 --- /dev/null +++ b/mpwo_client/src/actions/stats.js @@ -0,0 +1,13 @@ +import mpwoApi from '../mwpoApi/stats' +import { setData, setError } from './index' + +export const getStats = (userId, type, data) => dispatch => mpwoApi + .getStats(userId, type, data) + .then(ret => { + if (ret.status === 'success') { + dispatch(setData('statistics', ret.data)) + } else { + dispatch(setError(`statistics: ${ret.message}`)) + } + }) + .catch(error => dispatch(setError(`statistics: ${error}`))) diff --git a/mpwo_client/src/components/App.css b/mpwo_client/src/components/App.css index 36ce15a3..3f72fc1b 100644 --- a/mpwo_client/src/components/App.css +++ b/mpwo_client/src/components/App.css @@ -115,6 +115,10 @@ input, textarea { font-style: italic; } +.chart-month { + font-size: 0.8em; +} + .chart-radio { display: flex; font-size: 0.8em; diff --git a/mpwo_client/src/components/Dashboard/Statistics.jsx b/mpwo_client/src/components/Dashboard/Statistics.jsx index 6e9ce731..ea7f67bf 100644 --- a/mpwo_client/src/components/Dashboard/Statistics.jsx +++ b/mpwo_client/src/components/Dashboard/Statistics.jsx @@ -1,15 +1,132 @@ +import { endOfMonth, format, startOfMonth } from 'date-fns' import React from 'react' +import { connect } from 'react-redux' +import { + Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis +} from 'recharts' + +import { getStats } from '../../actions/stats' +import { formatStats } from '../../utils' -export default function ActivityCard () { - return ( -
-
- Statistics +class Statistics extends React.Component { + constructor(props, context) { + super(props, context) + const date = new Date() + this.state = { + start: startOfMonth(date), + end: endOfMonth(date), + displayedData: 'distance' + } + } + + componentDidMount() { + this.props.loadMonthActivities( + this.props.user.id, + this.state.start, + this.state.end, + ) + } + + handleRadioChange (changeEvent) { + this.setState({ + displayedData: changeEvent.target.name + }) + } + + + render() { + const { sports, statistics } = this.props + const { displayedData, end, start } = this.state + const data = [] + const stats = formatStats(statistics, sports, start, end) + const colors = [ + '#55a8a3', + '#98C3A9', + '#D0838A', + '#ECC77E', + '#926692', + '#929292' + ] + return ( +
+
+ This month +
+
+ {data === [] ? ( + 'No activities' + ) : ( +
+
+ + + +
+ + + + + + {sports.map((s, i) => ( + + ))} + + +
+ )} +
-
- coming soon... -
-
- ) + ) + } } + +export default connect( + state => ({ + sports: state.sports.data, + statistics: state.statistics.data, + user: state.user, + }), + dispatch => ({ + loadMonthActivities: (userId, start, end) => { + const dateFormat = 'YYYY-MM-DD' + const params = { + start: format(start, dateFormat), + end: format(end, dateFormat), + time: 'week' + } + dispatch(getStats(userId, 'by_time', params)) + }, + }) +)(Statistics) diff --git a/mpwo_client/src/components/Dashboard/index.jsx b/mpwo_client/src/components/Dashboard/index.jsx index ab7e882d..8da14004 100644 --- a/mpwo_client/src/components/Dashboard/index.jsx +++ b/mpwo_client/src/components/Dashboard/index.jsx @@ -43,8 +43,8 @@ class DashBoard extends React.Component {
- +
diff --git a/mpwo_client/src/mwpoApi/index.js b/mpwo_client/src/mwpoApi/index.js index f4b42e82..f8227caf 100644 --- a/mpwo_client/src/mwpoApi/index.js +++ b/mpwo_client/src/mwpoApi/index.js @@ -10,15 +10,15 @@ export default class MpwoApi { } else if (Object.keys(data).length > 0) { url = `${url}?${ data.page ? `&page=${data.page}` : '' - }${ + }${ data.start ? `&from=${data.start}` : '' - }${ + }${ data.end ? `&to=${data.end}` : '' - }${ + }${ data.order ? `&order=${data.order}` : '' - }${ + }${ data.per_page ? `&per_page=${data.per_page}` : '' - }` + }` } const params = { url: url, diff --git a/mpwo_client/src/mwpoApi/stats.js b/mpwo_client/src/mwpoApi/stats.js new file mode 100644 index 00000000..276459d4 --- /dev/null +++ b/mpwo_client/src/mwpoApi/stats.js @@ -0,0 +1,25 @@ +import { apiUrl, createRequest } from '../utils' + +export default class MpwoApi { + + static getStats(userID, type, data = {}) { + let url = `${apiUrl}stats/${userID}/${type}` + if (Object.keys(data).length > 0) { + url = `${url}?${ + data.start ? `&from=${data.start}` : '' + }${ + data.end ? `&to=${data.end}` : '' + }${ + data.time ? `&time=${data.time}` : '' + }${ + data.sport_id ? `&sport_id=${data.sport_id}` : '' + }` + } + const params = { + url: url, + method: 'GET', + } + return createRequest(params) + } + +} diff --git a/mpwo_client/src/reducers/index.js b/mpwo_client/src/reducers/index.js index 6a574d5a..bb94822d 100644 --- a/mpwo_client/src/reducers/index.js +++ b/mpwo_client/src/reducers/index.js @@ -195,6 +195,9 @@ const user = (state = initial.user, action) => { } } +const statistics = (state = initial.statistics, action) => + handleDataAndError(state, 'statistics', action) + const reducers = combineReducers({ activities, calendarActivities, @@ -208,6 +211,7 @@ const reducers = combineReducers({ records, router: routerReducer, sports, + statistics, user, }) diff --git a/mpwo_client/src/reducers/initial.js b/mpwo_client/src/reducers/initial.js index b1771c4d..f0fb1b05 100644 --- a/mpwo_client/src/reducers/initial.js +++ b/mpwo_client/src/reducers/initial.js @@ -53,5 +53,8 @@ export default { }, sports: { ...emptyData, - } + }, + statistics: { + data: {}, + }, } diff --git a/mpwo_client/src/utils.js b/mpwo_client/src/utils.js index a0d363c2..70753a97 100644 --- a/mpwo_client/src/utils.js +++ b/mpwo_client/src/utils.js @@ -1,5 +1,5 @@ import togeojson from '@mapbox/togeojson' -import { format, parse, subHours } from 'date-fns' +import { addDays, format, parse, startOfWeek, subHours } from 'date-fns' export const apiUrl = `${process.env.REACT_APP_API_URL}/api/` export const thunderforestApiKey = `${ @@ -106,3 +106,38 @@ export const formatChartData = chartData => { } return chartData } + +export const formatStats = (stats, sports, startDate, endDate) => { + const nbActivitiesStats = [] + const distanceStats = [] + const durationStats = [] + + for (let day = startOfWeek(startDate); + day <= endDate; + day = addDays(day, 7) + ) { + const date = format(day, 'YYYY-MM-DD') + const dataNbActivities = { date } + const dataDistance = { date } + const dataDuration = { date } + + if (stats[date]) { + Object.keys(stats[date]).map(sportId => { + const sportLabel = sports.filter(s => s.id === +sportId)[0].label + dataNbActivities[sportLabel] = stats[date][sportId].nb_activities + dataDistance[sportLabel] = stats[date][sportId].total_distance + dataDuration[sportLabel] = stats[date][sportId].total_duration + return null + }) + } + nbActivitiesStats.push(dataNbActivities) + distanceStats.push(dataDistance) + durationStats.push(dataDuration) + } + + return { + activities: nbActivitiesStats, + distance: distanceStats, + duration: durationStats + } +}