init statistics page (wip) - #13
This commit is contained in:
		@@ -11,6 +11,7 @@ import NavBar from './NavBar'
 | 
				
			|||||||
import NotFound from './Others/NotFound'
 | 
					import NotFound from './Others/NotFound'
 | 
				
			||||||
import Profile from './User/Profile'
 | 
					import Profile from './User/Profile'
 | 
				
			||||||
import ProfileEdit from './User/ProfileEdit'
 | 
					import ProfileEdit from './User/ProfileEdit'
 | 
				
			||||||
 | 
					import Statistics from './Statistics'
 | 
				
			||||||
import UserForm from './User/UserForm'
 | 
					import UserForm from './User/UserForm'
 | 
				
			||||||
import { isLoggedIn } from '../utils'
 | 
					import { isLoggedIn } from '../utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -89,6 +90,10 @@ export default class App extends React.Component {
 | 
				
			|||||||
            exact path="/activities/history"
 | 
					            exact path="/activities/history"
 | 
				
			||||||
            component={Activities}
 | 
					            component={Activities}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
 | 
					          <Route
 | 
				
			||||||
 | 
					            exact path="/activities/statistics"
 | 
				
			||||||
 | 
					            component={Statistics}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
          <Route path="/activities" component={Activity} />
 | 
					          <Route path="/activities" component={Activity} />
 | 
				
			||||||
          {/* <Route path="/admin" component={Admin} /> */}
 | 
					          {/* <Route path="/admin" component={Admin} /> */}
 | 
				
			||||||
          <Route component={NotFound} />
 | 
					          <Route component={NotFound} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,18 @@ function NavBar(props) {
 | 
				
			|||||||
                  </Link>
 | 
					                  </Link>
 | 
				
			||||||
                </li>
 | 
					                </li>
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
 | 
					              {props.user.isAuthenticated && (
 | 
				
			||||||
 | 
					                <li className="nav-item">
 | 
				
			||||||
 | 
					                  <Link
 | 
				
			||||||
 | 
					                    className="nav-link"
 | 
				
			||||||
 | 
					                    to={{
 | 
				
			||||||
 | 
					                      pathname: '/activities/statistics',
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    Statistics
 | 
				
			||||||
 | 
					                  </Link>
 | 
				
			||||||
 | 
					                </li>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
              {props.user.isAuthenticated && (
 | 
					              {props.user.isAuthenticated && (
 | 
				
			||||||
                <li className="nav-item">
 | 
					                <li className="nav-item">
 | 
				
			||||||
                  <Link
 | 
					                  <Link
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										142
									
								
								fittrackee_client/src/components/Statistics/index.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								fittrackee_client/src/components/Statistics/index.jsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
				
			|||||||
 | 
					import { endOfMonth, format, startOfMonth, subMonths } 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 { activityColors, formatDuration, formatStats } from '../../utils'
 | 
				
			||||||
 | 
					import CustomTooltip from '../Others/CustomTooltip'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Statistics extends React.Component {
 | 
				
			||||||
 | 
					  constructor(props, context) {
 | 
				
			||||||
 | 
					    super(props, context)
 | 
				
			||||||
 | 
					    const date = new Date()
 | 
				
			||||||
 | 
					    this.state = {
 | 
				
			||||||
 | 
					      start: startOfMonth(subMonths(date, 12)),
 | 
				
			||||||
 | 
					      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 stats = formatStats(statistics, sports, start, end, 'month')
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className="container dashboard">
 | 
				
			||||||
 | 
					        <div className="card activity-card">
 | 
				
			||||||
 | 
					          <div className="card-header">
 | 
				
			||||||
 | 
					            Statistics
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className="card-body">
 | 
				
			||||||
 | 
					            {Object.keys(statistics).length === 0 ? (
 | 
				
			||||||
 | 
					              'No workouts'
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <div className="chart-month">
 | 
				
			||||||
 | 
					                <div className="row chart-radio">
 | 
				
			||||||
 | 
					                  <label className="radioLabel col">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="radio"
 | 
				
			||||||
 | 
					                      name="distance"
 | 
				
			||||||
 | 
					                      checked={displayedData === 'distance'}
 | 
				
			||||||
 | 
					                      onChange={e => this.handleRadioChange(e)}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    distance
 | 
				
			||||||
 | 
					                  </label>
 | 
				
			||||||
 | 
					                  <label className="radioLabel col">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="radio"
 | 
				
			||||||
 | 
					                      name="duration"
 | 
				
			||||||
 | 
					                      checked={displayedData === 'duration'}
 | 
				
			||||||
 | 
					                      onChange={e => this.handleRadioChange(e)}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    duration
 | 
				
			||||||
 | 
					                  </label>
 | 
				
			||||||
 | 
					                  <label className="radioLabel col">
 | 
				
			||||||
 | 
					                    <input
 | 
				
			||||||
 | 
					                      type="radio"
 | 
				
			||||||
 | 
					                      name="activities"
 | 
				
			||||||
 | 
					                      checked={displayedData === 'activities'}
 | 
				
			||||||
 | 
					                      onChange={e => this.handleRadioChange(e)}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    activities
 | 
				
			||||||
 | 
					                  </label>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <ResponsiveContainer height={300}>
 | 
				
			||||||
 | 
					                  <BarChart
 | 
				
			||||||
 | 
					                    data={stats[displayedData]}
 | 
				
			||||||
 | 
					                    margin={{ top: 15, bottom: 0 }}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <XAxis
 | 
				
			||||||
 | 
					                      dataKey="date"
 | 
				
			||||||
 | 
					                      interval={0} // to force to display all ticks
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    <YAxis
 | 
				
			||||||
 | 
					                      tickFormatter={value => displayedData === 'distance'
 | 
				
			||||||
 | 
					                        ? `${value} km`
 | 
				
			||||||
 | 
					                        : displayedData === 'duration'
 | 
				
			||||||
 | 
					                          ? format(formatDuration(value), 'HH:mm')
 | 
				
			||||||
 | 
					                          : value
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    <Tooltip content={
 | 
				
			||||||
 | 
					                      <CustomTooltip
 | 
				
			||||||
 | 
					                        displayedData={displayedData}
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    {sports.map((s, i) => (
 | 
				
			||||||
 | 
					                      <Bar
 | 
				
			||||||
 | 
					                        key={s.id}
 | 
				
			||||||
 | 
					                        dataKey={s.label}
 | 
				
			||||||
 | 
					                        stackId="a"
 | 
				
			||||||
 | 
					                        fill={activityColors[i]}
 | 
				
			||||||
 | 
					                        unit={displayedData === 'distance' ? ' km' : ''}
 | 
				
			||||||
 | 
					                      />
 | 
				
			||||||
 | 
					                    ))}
 | 
				
			||||||
 | 
					                  </BarChart>
 | 
				
			||||||
 | 
					                </ResponsiveContainer>
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = {
 | 
				
			||||||
 | 
					        from: format(start, dateFormat),
 | 
				
			||||||
 | 
					        to: format(end, dateFormat),
 | 
				
			||||||
 | 
					        time: 'month'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      dispatch(getStats(userId, 'by_time', params))
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					)(Statistics)
 | 
				
			||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
import togeojson from '@mapbox/togeojson'
 | 
					import togeojson from '@mapbox/togeojson'
 | 
				
			||||||
import { addDays, format, parse, startOfWeek, subHours } from 'date-fns'
 | 
					import {
 | 
				
			||||||
 | 
					  addDays, addMonths, addYears, format, parse, startOfWeek, subHours
 | 
				
			||||||
 | 
					} from 'date-fns'
 | 
				
			||||||
import { DateTime } from 'luxon'
 | 
					import { DateTime } from 'luxon'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
 | 
					export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
 | 
				
			||||||
@@ -137,17 +139,37 @@ export const formatChartData = chartData => {
 | 
				
			|||||||
  return chartData
 | 
					  return chartData
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const formatStats = (stats, sports, startDate, endDate) => {
 | 
					const xAxisFormats = [
 | 
				
			||||||
 | 
					  { duration: 'week', dateFormat: 'YYYY-MM-DD', xAxis: 'DD/MM' },
 | 
				
			||||||
 | 
					  { duration: 'month', dateFormat: 'YYYY-MM', xAxis: 'MM/YYYY' },
 | 
				
			||||||
 | 
					  { duration: 'year', dateFormat: 'YYYY', xAxis: 'YYYY' },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dateIncrement = (duration, day) => {
 | 
				
			||||||
 | 
					  switch (duration) {
 | 
				
			||||||
 | 
					    case 'week':
 | 
				
			||||||
 | 
					      return addDays(day, 7)
 | 
				
			||||||
 | 
					    case 'month':
 | 
				
			||||||
 | 
					      return addMonths(day, 1)
 | 
				
			||||||
 | 
					    case 'year':
 | 
				
			||||||
 | 
					      return addYears(day, 1)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const formatStats = (
 | 
				
			||||||
 | 
					  stats, sports, startDate, endDate, duration = 'week'
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
  const nbActivitiesStats = []
 | 
					  const nbActivitiesStats = []
 | 
				
			||||||
  const distanceStats = []
 | 
					  const distanceStats = []
 | 
				
			||||||
  const durationStats = []
 | 
					  const durationStats = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (let day = startOfWeek(startDate);
 | 
					  for (let day = startOfWeek(startDate);
 | 
				
			||||||
       day <= endDate;
 | 
					       day <= endDate;
 | 
				
			||||||
       day = addDays(day, 7)
 | 
					       day = dateIncrement(duration, day)
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    const date = format(day, 'YYYY-MM-DD')
 | 
					    const [xAxisFormat] = xAxisFormats.filter(x => x.duration === duration)
 | 
				
			||||||
    const xAxis = format(day, 'DD/MM')
 | 
					    const date = format(day, xAxisFormat.dateFormat)
 | 
				
			||||||
 | 
					    const xAxis = format(day, xAxisFormat.xAxis)
 | 
				
			||||||
    const dataNbActivities = { date: xAxis }
 | 
					    const dataNbActivities = { date: xAxis }
 | 
				
			||||||
    const dataDistance = { date: xAxis }
 | 
					    const dataDistance = { date: xAxis }
 | 
				
			||||||
    const dataDuration = { date: xAxis }
 | 
					    const dataDuration = { date: xAxis }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user