init statistics page (wip) - #13
This commit is contained in:
		@@ -11,6 +11,7 @@ import NavBar from './NavBar'
 | 
			
		||||
import NotFound from './Others/NotFound'
 | 
			
		||||
import Profile from './User/Profile'
 | 
			
		||||
import ProfileEdit from './User/ProfileEdit'
 | 
			
		||||
import Statistics from './Statistics'
 | 
			
		||||
import UserForm from './User/UserForm'
 | 
			
		||||
import { isLoggedIn } from '../utils'
 | 
			
		||||
 | 
			
		||||
@@ -89,6 +90,10 @@ export default class App extends React.Component {
 | 
			
		||||
            exact path="/activities/history"
 | 
			
		||||
            component={Activities}
 | 
			
		||||
          />
 | 
			
		||||
          <Route
 | 
			
		||||
            exact path="/activities/statistics"
 | 
			
		||||
            component={Statistics}
 | 
			
		||||
          />
 | 
			
		||||
          <Route path="/activities" component={Activity} />
 | 
			
		||||
          {/* <Route path="/admin" component={Admin} /> */}
 | 
			
		||||
          <Route component={NotFound} />
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,18 @@ function NavBar(props) {
 | 
			
		||||
                  </Link>
 | 
			
		||||
                </li>
 | 
			
		||||
              )}
 | 
			
		||||
              {props.user.isAuthenticated && (
 | 
			
		||||
                <li className="nav-item">
 | 
			
		||||
                  <Link
 | 
			
		||||
                    className="nav-link"
 | 
			
		||||
                    to={{
 | 
			
		||||
                      pathname: '/activities/statistics',
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    Statistics
 | 
			
		||||
                  </Link>
 | 
			
		||||
                </li>
 | 
			
		||||
              )}
 | 
			
		||||
              {props.user.isAuthenticated && (
 | 
			
		||||
                <li className="nav-item">
 | 
			
		||||
                  <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 { addDays, format, parse, startOfWeek, subHours } from 'date-fns'
 | 
			
		||||
import {
 | 
			
		||||
  addDays, addMonths, addYears, format, parse, startOfWeek, subHours
 | 
			
		||||
} from 'date-fns'
 | 
			
		||||
import { DateTime } from 'luxon'
 | 
			
		||||
 | 
			
		||||
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
 | 
			
		||||
@@ -137,17 +139,37 @@ export const formatChartData = 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 distanceStats = []
 | 
			
		||||
  const durationStats = []
 | 
			
		||||
 | 
			
		||||
  for (let day = startOfWeek(startDate);
 | 
			
		||||
       day <= endDate;
 | 
			
		||||
       day = addDays(day, 7)
 | 
			
		||||
       day = dateIncrement(duration, day)
 | 
			
		||||
  ) {
 | 
			
		||||
    const date = format(day, 'YYYY-MM-DD')
 | 
			
		||||
    const xAxis = format(day, 'DD/MM')
 | 
			
		||||
    const [xAxisFormat] = xAxisFormats.filter(x => x.duration === duration)
 | 
			
		||||
    const date = format(day, xAxisFormat.dateFormat)
 | 
			
		||||
    const xAxis = format(day, xAxisFormat.xAxis)
 | 
			
		||||
    const dataNbActivities = { date: xAxis }
 | 
			
		||||
    const dataDistance = { date: xAxis }
 | 
			
		||||
    const dataDuration = { date: xAxis }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user