diff --git a/mpwo_client/src/actions/activities.js b/mpwo_client/src/actions/activities.js index 2875c403..999b54ec 100644 --- a/mpwo_client/src/actions/activities.js +++ b/mpwo_client/src/actions/activities.js @@ -1,6 +1,7 @@ import mpwoGenericApi from '../mwpoApi' import mpwoApi from '../mwpoApi/activities' import { history } from '../index' +import { formatChartData } from '../utils' import { setError } from './index' export const pushActivities = activities => ({ @@ -13,6 +14,11 @@ export const setGpx = gpxContent => ({ gpxContent, }) +export const setChartData = chartData => ({ + type: 'SET_CHART_DATA', + chartData, +}) + export const addActivity = form => dispatch => mpwoApi .addActivity(form) .then(ret => { @@ -54,6 +60,23 @@ export const getActivityGpx = activityId => dispatch => { } +export const getActivityChartData = activityId => dispatch => { + if (activityId) { + return mpwoApi + .getActivityChartData(activityId) + .then(ret => { + if (ret.status === 'success') { + dispatch(setChartData(formatChartData(ret.data.chart_data))) + } else { + dispatch(setError(`activities: ${ret.message}`)) + } + }) + .catch(error => dispatch(setError(`activities: ${error}`))) + } + dispatch(setChartData(null)) +} + + export const deleteActivity = id => dispatch => mpwoGenericApi .deleteData('activities', id) .then(ret => { diff --git a/mpwo_client/src/components/Activities/ActivityDisplay/ActivityCharts.jsx b/mpwo_client/src/components/Activities/ActivityDisplay/ActivityCharts.jsx new file mode 100644 index 00000000..561bd62e --- /dev/null +++ b/mpwo_client/src/components/Activities/ActivityDisplay/ActivityCharts.jsx @@ -0,0 +1,84 @@ +import { format } from 'date-fns' +import React from 'react' +import { connect } from 'react-redux' +import { + Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis +} from 'recharts' + +import { getActivityChartData } from '../../../actions/activities' + + +class ActivityCharts extends React.Component { + componentDidMount() { + this.props.loadActivityData(this.props.activity.id) + } + + componentDidUpdate(prevProps) { + if (prevProps.activity.id !== + this.props.activity.id) { + this.props.loadActivityData(this.props.activity.id) + } + } + + componentWillUnmount() { + this.props.loadActivityData(null) + } + + render() { + const { chartData } = this.props + return ( +
+ + + format(time, 'HH:mm:ss')} + type="number" + /> + + + + + format(time, 'HH:mm:ss')} + /> + + +
+ ) + } +} + +export default connect( + state => ({ + chartData: state.chartData + }), + dispatch => ({ + loadActivityData: activityId => { + dispatch(getActivityChartData(activityId)) + }, + }) +)(ActivityCharts) diff --git a/mpwo_client/src/components/Activities/ActivityDisplay/index.jsx b/mpwo_client/src/components/Activities/ActivityDisplay/index.jsx index ad89d637..9b7eca5e 100644 --- a/mpwo_client/src/components/Activities/ActivityDisplay/index.jsx +++ b/mpwo_client/src/components/Activities/ActivityDisplay/index.jsx @@ -3,6 +3,7 @@ import { Helmet } from 'react-helmet' import { connect } from 'react-redux' import ActivityCardHeader from './ActivityCardHeader' +import ActivityCharts from './ActivityCharts' import ActivityDetails from './ActivityDetails' import ActivityMap from './ActivityMap' import CustomModal from './../../Others/CustomModal' @@ -61,26 +62,41 @@ class ActivityDisplay extends React.Component { close={() => this.displayModal(false)} />} {activity && sport && activities.length === 1 && ( -
-
-
-
- this.displayModal(true)} - /> -
-
-
- {activity.with_gpx && ( -
- +
+
+
+
+
+ this.displayModal(true)} + /> +
+
+
+ {activity.with_gpx && ( +
+ +
+ )} +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
- )} -
-
diff --git a/mpwo_client/src/mwpoApi/activities.js b/mpwo_client/src/mwpoApi/activities.js index a74f618b..7fb433d1 100644 --- a/mpwo_client/src/mwpoApi/activities.js +++ b/mpwo_client/src/mwpoApi/activities.js @@ -29,4 +29,12 @@ export default class MpwoApi { return createRequest(params) } + static getActivityChartData(activityId) { + const params = { + url: `${apiUrl}activities/${activityId}/chart_data`, + method: 'GET', + } + return createRequest(params) + } + } diff --git a/mpwo_client/src/reducers/index.js b/mpwo_client/src/reducers/index.js index 58dcc549..bc653d4e 100644 --- a/mpwo_client/src/reducers/index.js +++ b/mpwo_client/src/reducers/index.js @@ -31,6 +31,15 @@ const activities = (state = initial.activities, action) => { } } +const chartData = (state = initial.chartData, action) => { + switch (action.type) { + case 'SET_CHART_DATA': + return action.chartData + default: + return state + } +} + const formData = (state = initial.formData, action) => { switch (action.type) { case 'UPDATE_USER_FORMDATA': @@ -163,6 +172,7 @@ const user = (state = initial.user, action) => { const reducers = combineReducers({ activities, + chartData, formData, formProfile, gpx, diff --git a/mpwo_client/src/reducers/initial.js b/mpwo_client/src/reducers/initial.js index 2bb2a795..a6fcf7b7 100644 --- a/mpwo_client/src/reducers/initial.js +++ b/mpwo_client/src/reducers/initial.js @@ -41,6 +41,7 @@ export default { activities: { ...emptyData, }, + chartData: [], // check if storing gpx content is OK gpx: null, records: { diff --git a/mpwo_client/src/utils.js b/mpwo_client/src/utils.js index cc06667b..137e7dae 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 } from 'date-fns' +import { format, parse, subHours } from 'date-fns' export const apiUrl = `${process.env.REACT_APP_API_URL}/api/` export const thunderforestApiKey = `${ @@ -90,3 +90,17 @@ export const formatRecord = record => { value: value, } } + +const formatDuration = seconds => { + let newDate = new Date(0) + newDate = subHours(newDate.setSeconds(seconds), 1) + return newDate.getTime() +} + +export const formatChartData = chartData => { + for (let i = 0; i < chartData.length; i++) { + chartData[i].time = new Date(chartData[i].time).getTime() + chartData[i].duration = formatDuration(chartData[i].duration) + } + return chartData +} diff --git a/package.json b/package.json index ee206bab..41c4c053 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react-router-dom": "^4.2.2", "react-router-redux": "^5.0.0-alpha.9", "react-scripts": "1.1.4", + "recharts": "^1.0.0-beta.10", "redux": "4.0.0", "redux-thunk": "^2.2.0" }, diff --git a/yarn.lock b/yarn.lock index 756f737a..122f8de4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1870,6 +1870,10 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" + clean-css@4.1.x: version "4.1.11" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a" @@ -2128,6 +2132,10 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" +core-js@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" + core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" @@ -2342,6 +2350,60 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" +d3-array@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" + +d3-collection@1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" + +d3-color@1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a" + +d3-format@1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11" + +d3-interpolate@1, d3-interpolate@^1.1.5: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.2.0.tgz#40d81bd8e959ff021c5ea7545bc79b8d22331c41" + dependencies: + d3-color "1" + +d3-path@1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" + +d3-scale@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.6.tgz#bce19da80d3a0cf422c9543ae3322086220b34ed" + dependencies: + d3-array "^1.2.0" + d3-collection "1" + d3-color "1" + d3-format "1" + d3-interpolate "1" + d3-time "1" + d3-time-format "2" + +d3-shape@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" + dependencies: + d3-path "1" + +d3-time-format@2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.1.tgz#85b7cdfbc9ffca187f14d3c456ffda268081bb31" + dependencies: + d3-time "1" + +d3-time@1: + version "1.0.8" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" + d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" @@ -2654,6 +2716,10 @@ dom-converter@~0.1: dependencies: utila "~0.3" +dom-helpers@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" + dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -5536,7 +5602,7 @@ lodash@4.16.4: version "4.16.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.4.tgz#01ce306b9bad1319f2a5528674f88297aeb70127" -"lodash@4.6.1 || ^4.16.1", "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0: +"lodash@4.6.1 || ^4.16.1", "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.4: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -6887,7 +6953,7 @@ promisify-event@^1.0.0: dependencies: pinkie-promise "^2.0.0" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1: version "15.6.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" dependencies: @@ -6985,7 +7051,7 @@ querystringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" -raf@3.4.0: +raf@3.4.0, raf@^3.2.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" dependencies: @@ -7103,6 +7169,12 @@ react-redux@^5.0.7: loose-envify "^1.1.0" prop-types "^15.6.0" +react-resize-detector@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-1.1.0.tgz#4a9831fa3caad32230478dd0185cbd2aa91a5ebf" + dependencies: + prop-types "^15.5.10" + react-router-dom@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" @@ -7186,6 +7258,23 @@ react-side-effect@^1.1.0: exenv "^1.2.1" shallowequal "^1.0.1" +react-smooth@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.0.tgz#b29dbebddddb06d21b5b08962167fb9eac1897d8" + dependencies: + lodash "~4.17.4" + prop-types "^15.6.0" + raf "^3.2.0" + react-transition-group "^2.2.1" + +react-transition-group@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.3.1.tgz#31d611b33e143a5e0f2d94c348e026a0f3b474b6" + dependencies: + dom-helpers "^3.3.1" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react@^16.4.0: version "16.4.0" resolved "https://registry.yarnpkg.com/react/-/react-16.4.0.tgz#402c2db83335336fba1962c08b98c6272617d585" @@ -7307,6 +7396,26 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" +recharts-scale@0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.3.2.tgz#dac7621714a4765d152cb2adbc30c73b831208c9" + +recharts@^1.0.0-beta.10: + version "1.0.0-beta.10" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.0.0-beta.10.tgz#d3cd15df6b7879d5968da3c942b5fcdaf2504fe1" + dependencies: + classnames "2.2.5" + core-js "2.5.1" + d3-interpolate "^1.1.5" + d3-scale "1.0.6" + d3-shape "1.2.0" + lodash "~4.17.4" + prop-types "^15.6.0" + react-resize-detector "1.1.0" + react-smooth "1.0.0" + recharts-scale "0.3.2" + reduce-css-calc "1.3.0" + recursive-readdir@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99" @@ -7320,7 +7429,7 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -reduce-css-calc@^1.2.6: +reduce-css-calc@1.3.0, reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" dependencies: