Client - replace 'Activity' with 'Workout' - fix #58
This commit is contained in:
@ -1,192 +0,0 @@
|
||||
import FitTrackeeGenericApi from '../fitTrackeeApi'
|
||||
import { history } from '../index'
|
||||
import { formatChartData } from '../utils/activities'
|
||||
import { setError, setLoading } from './index'
|
||||
import { loadProfile } from './user'
|
||||
|
||||
export const pushActivities = activities => ({
|
||||
type: 'PUSH_ACTIVITIES',
|
||||
activities,
|
||||
})
|
||||
|
||||
export const removeActivity = activityId => ({
|
||||
type: 'REMOVE_ACTIVITY',
|
||||
activityId,
|
||||
})
|
||||
|
||||
export const updateCalendar = activities => ({
|
||||
type: 'UPDATE_CALENDAR',
|
||||
activities,
|
||||
})
|
||||
|
||||
export const setGpx = gpxContent => ({
|
||||
type: 'SET_GPX',
|
||||
gpxContent,
|
||||
})
|
||||
|
||||
export const setChartData = chartData => ({
|
||||
type: 'SET_CHART_DATA',
|
||||
chartData,
|
||||
})
|
||||
|
||||
export const addActivity = form => dispatch =>
|
||||
FitTrackeeGenericApi.addDataWithFile('activities', form)
|
||||
.then(ret => {
|
||||
if (ret.status === 'created') {
|
||||
if (ret.data.activities.length === 0) {
|
||||
dispatch(setError('activities|no correct file.'))
|
||||
} else if (ret.data.activities.length === 1) {
|
||||
dispatch(loadProfile())
|
||||
history.push(`/activities/${ret.data.activities[0].id}`)
|
||||
} else {
|
||||
// ret.data.activities.length > 1
|
||||
dispatch(loadProfile())
|
||||
history.push('/')
|
||||
}
|
||||
} else if (ret.status === 413) {
|
||||
dispatch(
|
||||
setError('activities|File size is greater than the allowed size')
|
||||
)
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.message}`))
|
||||
}
|
||||
dispatch(setLoading(false))
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch(setLoading(false))
|
||||
dispatch(setError(`activities|${error}`))
|
||||
})
|
||||
|
||||
export const addActivityWithoutGpx = form => dispatch =>
|
||||
FitTrackeeGenericApi.addData('activities/no_gpx', form)
|
||||
.then(ret => {
|
||||
if (ret.status === 'created') {
|
||||
dispatch(loadProfile())
|
||||
history.push(`/activities/${ret.data.activities[0].id}`)
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`activities|${error}`)))
|
||||
|
||||
export const getActivityGpx = activityId => dispatch => {
|
||||
if (activityId) {
|
||||
return FitTrackeeGenericApi.getData(`activities/${activityId}/gpx`)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setGpx(ret.data.gpx))
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`activities|${error}`)))
|
||||
}
|
||||
dispatch(setGpx(null))
|
||||
}
|
||||
|
||||
export const getSegmentGpx = (activityId, segmentId) => dispatch => {
|
||||
if (activityId) {
|
||||
return FitTrackeeGenericApi.getData(
|
||||
`activities/${activityId}/gpx/segment/${segmentId}`
|
||||
)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setGpx(ret.data.gpx))
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`activities|${error}`)))
|
||||
}
|
||||
dispatch(setGpx(null))
|
||||
}
|
||||
|
||||
export const getActivityChartData = activityId => dispatch => {
|
||||
if (activityId) {
|
||||
return FitTrackeeGenericApi.getData(`activities/${activityId}/chart_data`)
|
||||
.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 getSegmentChartData = (activityId, segmentId) => dispatch => {
|
||||
if (activityId) {
|
||||
return FitTrackeeGenericApi.getData(
|
||||
`activities/${activityId}/chart_data/segment/${segmentId}`
|
||||
)
|
||||
.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 =>
|
||||
FitTrackeeGenericApi.deleteData('activities', id)
|
||||
.then(ret => {
|
||||
if (ret.status === 204) {
|
||||
Promise.resolve(dispatch(removeActivity(id)))
|
||||
.then(() => dispatch(loadProfile()))
|
||||
.then(() => history.push('/'))
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.status}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`activities|${error}`)))
|
||||
|
||||
export const editActivity = form => dispatch =>
|
||||
FitTrackeeGenericApi.updateData('activities', form)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(loadProfile())
|
||||
history.push(`/activities/${ret.data.activities[0].id}`)
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.message}`))
|
||||
}
|
||||
dispatch(setLoading(false))
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch(setLoading(false))
|
||||
dispatch(setError(`activities|${error}`))
|
||||
})
|
||||
|
||||
export const getMoreActivities = params => dispatch =>
|
||||
FitTrackeeGenericApi.getData('activities', params)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
if (ret.data.activities.length > 0) {
|
||||
dispatch(pushActivities(ret.data.activities))
|
||||
}
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`activities|${error}`)))
|
||||
|
||||
export const getMonthActivities = (from, to) => dispatch =>
|
||||
FitTrackeeGenericApi.getData('activities', {
|
||||
from,
|
||||
to,
|
||||
order: 'asc',
|
||||
per_page: 100,
|
||||
})
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(updateCalendar(ret.data.activities))
|
||||
} else {
|
||||
dispatch(setError(`activities|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`activities|${error}`)))
|
@ -47,7 +47,7 @@ export const getOrUpdateData = (
|
||||
canDispatch = true
|
||||
) => dispatch => {
|
||||
dispatch(setLoading(true))
|
||||
if (data && data.id && target !== 'activities' && isNaN(data.id)) {
|
||||
if (data && data.id && target !== 'workouts' && isNaN(data.id)) {
|
||||
dispatch(setLoading(false))
|
||||
return dispatch(setError(`${target}|Incorrect id`))
|
||||
}
|
||||
|
192
fittrackee_client/src/actions/workouts.js
Normal file
192
fittrackee_client/src/actions/workouts.js
Normal file
@ -0,0 +1,192 @@
|
||||
import FitTrackeeGenericApi from '../fitTrackeeApi'
|
||||
import { history } from '../index'
|
||||
import { formatChartData } from '../utils/workouts'
|
||||
import { setError, setLoading } from './index'
|
||||
import { loadProfile } from './user'
|
||||
|
||||
export const pushWorkouts = workouts => ({
|
||||
type: 'PUSH_WORKOUTS',
|
||||
workouts,
|
||||
})
|
||||
|
||||
export const removeWorkout = workoutId => ({
|
||||
type: 'REMOVE_WORKOUT',
|
||||
workoutId,
|
||||
})
|
||||
|
||||
export const updateCalendar = workouts => ({
|
||||
type: 'UPDATE_CALENDAR',
|
||||
workouts,
|
||||
})
|
||||
|
||||
export const setGpx = gpxContent => ({
|
||||
type: 'SET_GPX',
|
||||
gpxContent,
|
||||
})
|
||||
|
||||
export const setChartData = chartData => ({
|
||||
type: 'SET_CHART_DATA',
|
||||
chartData,
|
||||
})
|
||||
|
||||
export const addWorkout = form => dispatch =>
|
||||
FitTrackeeGenericApi.addDataWithFile('workouts', form)
|
||||
.then(ret => {
|
||||
if (ret.status === 'created') {
|
||||
if (ret.data.workouts.length === 0) {
|
||||
dispatch(setError('workouts|no correct file.'))
|
||||
} else if (ret.data.workouts.length === 1) {
|
||||
dispatch(loadProfile())
|
||||
history.push(`/workouts/${ret.data.workouts[0].id}`)
|
||||
} else {
|
||||
// ret.data.workouts.length > 1
|
||||
dispatch(loadProfile())
|
||||
history.push('/')
|
||||
}
|
||||
} else if (ret.status === 413) {
|
||||
dispatch(
|
||||
setError('workouts|File size is greater than the allowed size')
|
||||
)
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
dispatch(setLoading(false))
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch(setLoading(false))
|
||||
dispatch(setError(`workouts|${error}`))
|
||||
})
|
||||
|
||||
export const addWorkoutWithoutGpx = form => dispatch =>
|
||||
FitTrackeeGenericApi.addData('workouts/no_gpx', form)
|
||||
.then(ret => {
|
||||
if (ret.status === 'created') {
|
||||
dispatch(loadProfile())
|
||||
history.push(`/workouts/${ret.data.workouts[0].id}`)
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
||||
|
||||
export const getWorkoutGpx = workoutId => dispatch => {
|
||||
if (workoutId) {
|
||||
return FitTrackeeGenericApi.getData(`workouts/${workoutId}/gpx`)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setGpx(ret.data.gpx))
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
||||
}
|
||||
dispatch(setGpx(null))
|
||||
}
|
||||
|
||||
export const getSegmentGpx = (workoutId, segmentId) => dispatch => {
|
||||
if (workoutId) {
|
||||
return FitTrackeeGenericApi.getData(
|
||||
`workouts/${workoutId}/gpx/segment/${segmentId}`
|
||||
)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setGpx(ret.data.gpx))
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
||||
}
|
||||
dispatch(setGpx(null))
|
||||
}
|
||||
|
||||
export const getWorkoutChartData = workoutId => dispatch => {
|
||||
if (workoutId) {
|
||||
return FitTrackeeGenericApi.getData(`workouts/${workoutId}/chart_data`)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setChartData(formatChartData(ret.data.chart_data)))
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
||||
}
|
||||
dispatch(setChartData(null))
|
||||
}
|
||||
|
||||
export const getSegmentChartData = (workoutId, segmentId) => dispatch => {
|
||||
if (workoutId) {
|
||||
return FitTrackeeGenericApi.getData(
|
||||
`workouts/${workoutId}/chart_data/segment/${segmentId}`
|
||||
)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setChartData(formatChartData(ret.data.chart_data)))
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
||||
}
|
||||
dispatch(setChartData(null))
|
||||
}
|
||||
|
||||
export const deleteWorkout = id => dispatch =>
|
||||
FitTrackeeGenericApi.deleteData('workouts', id)
|
||||
.then(ret => {
|
||||
if (ret.status === 204) {
|
||||
Promise.resolve(dispatch(removeWorkout(id)))
|
||||
.then(() => dispatch(loadProfile()))
|
||||
.then(() => history.push('/'))
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.status}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
||||
|
||||
export const editWorkout = form => dispatch =>
|
||||
FitTrackeeGenericApi.updateData('workouts', form)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(loadProfile())
|
||||
history.push(`/workouts/${ret.data.workouts[0].id}`)
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
dispatch(setLoading(false))
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch(setLoading(false))
|
||||
dispatch(setError(`workouts|${error}`))
|
||||
})
|
||||
|
||||
export const getMoreWorkouts = params => dispatch =>
|
||||
FitTrackeeGenericApi.getData('workouts', params)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
if (ret.data.workouts.length > 0) {
|
||||
dispatch(pushWorkouts(ret.data.workouts))
|
||||
}
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
||||
|
||||
export const getMonthWorkouts = (from, to) => dispatch =>
|
||||
FitTrackeeGenericApi.getData('workouts', {
|
||||
from,
|
||||
to,
|
||||
order: 'asc',
|
||||
per_page: 100,
|
||||
})
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(updateCalendar(ret.data.workouts))
|
||||
} else {
|
||||
dispatch(setError(`workouts|${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`workouts|${error}`)))
|
@ -1,73 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
import ActivityWeather from './ActivityWeather'
|
||||
|
||||
export default function ActivityDetails(props) {
|
||||
const { activity, t } = props
|
||||
const withPauses = activity.pauses !== '0:00:00' && activity.pauses !== null
|
||||
return (
|
||||
<div className="activity-details">
|
||||
<p>
|
||||
<i className="fa fa-clock-o custom-fa" aria-hidden="true" />
|
||||
{t('activities:Duration')}: {activity.moving}
|
||||
{activity.records &&
|
||||
activity.records.find(r => r.record_type === 'LD') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
{withPauses && (
|
||||
<span>
|
||||
<br />({t('activities:pauses')}: {activity.pauses},{' '}
|
||||
{t('activities:total duration')}: {activity.duration})
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<i className="fa fa-road custom-fa" aria-hidden="true" />
|
||||
{t('activities:Distance')}: {activity.distance} km
|
||||
{activity.records &&
|
||||
activity.records.find(r => r.record_type === 'FD') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<i className="fa fa-tachometer custom-fa" aria-hidden="true" />
|
||||
{t('activities:Average speed')}: {activity.ave_speed} km/h
|
||||
{activity.records &&
|
||||
activity.records.find(r => r.record_type === 'AS') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
<br />
|
||||
{t('activities:Max. speed')}: {activity.max_speed} km/h
|
||||
{activity.records &&
|
||||
activity.records.find(r => r.record_type === 'MS') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
</p>
|
||||
{activity.min_alt && activity.max_alt && (
|
||||
<p>
|
||||
<i className="fi-mountains custom-fa" />
|
||||
{t('activities:Min. altitude')}: {activity.min_alt}m
|
||||
<br />
|
||||
{t('activities:Max. altitude')}: {activity.max_alt}m
|
||||
</p>
|
||||
)}
|
||||
{activity.ascent && activity.descent && (
|
||||
<p>
|
||||
<i className="fa fa-location-arrow custom-fa" />
|
||||
{t('activities:Ascent')}: {activity.ascent}m
|
||||
<br />
|
||||
{t('activities:Descent')}: {activity.descent}m
|
||||
</p>
|
||||
)}
|
||||
<ActivityWeather activity={activity} t={t} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function ActivityNoMap(props) {
|
||||
const { t } = props
|
||||
return (
|
||||
<div className="activity-no-map text-center">{t('activities:No Map')}</div>
|
||||
)
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import ActivityAddOrEdit from './ActivityAddOrEdit'
|
||||
import { getOrUpdateData } from '../../actions'
|
||||
|
||||
class ActivityEdit extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadActivity(this.props.match.params.activityId)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activities, message, sports } = this.props
|
||||
const [activity] = activities
|
||||
return (
|
||||
<div>
|
||||
{sports.length > 0 && (
|
||||
<ActivityAddOrEdit
|
||||
activity={activity}
|
||||
message={message}
|
||||
sports={sports}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivity: activityId => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', { id: activityId }))
|
||||
},
|
||||
})
|
||||
)(ActivityEdit)
|
@ -1,42 +0,0 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Redirect, Route, Switch } from 'react-router-dom'
|
||||
|
||||
import ActivityAdd from './ActivityAdd'
|
||||
import ActivityDisplay from './ActivityDisplay'
|
||||
import ActivityEdit from './ActivityEdit'
|
||||
import NotFound from './../Others/NotFound'
|
||||
import { isLoggedIn } from '../../utils'
|
||||
|
||||
function Activity() {
|
||||
return (
|
||||
<div>
|
||||
{isLoggedIn() ? (
|
||||
<Switch>
|
||||
<Route exact path="/activities/add" component={ActivityAdd} />
|
||||
<Route
|
||||
exact
|
||||
path="/activities/:activityId"
|
||||
component={ActivityDisplay}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/activities/:activityId/edit"
|
||||
component={ActivityEdit}
|
||||
/>
|
||||
<Route
|
||||
path="/activities/:activityId/segment/:segmentId"
|
||||
component={ActivityDisplay}
|
||||
/>
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
) : (
|
||||
<Redirect to="/login" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
user: state.user,
|
||||
}))(Activity)
|
@ -6,7 +6,7 @@ import AdminStats from './AdminStats'
|
||||
export default function AdminDashboard(props) {
|
||||
const { appConfig, t } = props
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-header">
|
||||
<strong>{t('administration:Administration')}</strong>
|
||||
</div>
|
||||
|
@ -94,13 +94,13 @@ class AdminSports extends React.Component {
|
||||
updateSport(sport.id, !sport.is_active)
|
||||
}
|
||||
/>
|
||||
{sport.has_activities && (
|
||||
{sport.has_workouts && (
|
||||
<span className="admin-message">
|
||||
<i
|
||||
className="fa fa-warning custom-fa"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{t('administration:activities exist')}
|
||||
{t('administration:workouts exist')}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
|
@ -16,7 +16,7 @@ class AdminStats extends React.Component {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-users fa-3x fa-color" />
|
||||
@ -35,7 +35,7 @@ class AdminStats extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-tags fa-3x fa-color" />
|
||||
@ -52,17 +52,17 @@ class AdminStats extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-calendar fa-3x fa-color" />
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">
|
||||
{appStats.activities ? appStats.activities : 0}
|
||||
{appStats.workouts ? appStats.workouts : 0}
|
||||
</div>
|
||||
<div>{`${
|
||||
appStats.activities === 1
|
||||
appStats.workouts === 1
|
||||
? t('common:workout')
|
||||
: t('common:workouts')
|
||||
}`}</div>
|
||||
@ -71,7 +71,7 @@ class AdminStats extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-folder-open fa-3x fa-color" />
|
||||
|
@ -126,7 +126,7 @@ class AdminUsers extends React.Component {
|
||||
<th>{t('user:Username')}</th>
|
||||
<th>{t('user:Email')}</th>
|
||||
<th>{t('user:Registration Date')}</th>
|
||||
<th>{t('activities:Activities')}</th>
|
||||
<th>{t('workouts:Workouts')}</th>
|
||||
<th>{t('user:Admin')}</th>
|
||||
<th>{t('administration:Actions')}</th>
|
||||
</tr>
|
||||
@ -176,9 +176,9 @@ class AdminUsers extends React.Component {
|
||||
</td>
|
||||
<td>
|
||||
<span className="heading-span-absolute">
|
||||
{t('activities:Activities')}
|
||||
{t('workouts:Workouts')}
|
||||
</span>
|
||||
{user.nb_activities}
|
||||
{user.nb_workouts}
|
||||
</td>
|
||||
<td>
|
||||
<span className="heading-span-absolute">
|
||||
|
@ -64,106 +64,11 @@ label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.activities-result {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.activity-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.activity-details {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
.activity-date {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.activity-filter {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.activity-filter .col-2, .col-5{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.activity-label {
|
||||
font-size: 0.8em;
|
||||
color: #666
|
||||
}
|
||||
|
||||
.activity-logo {
|
||||
margin: 0 5px;
|
||||
max-width: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.activity-map {
|
||||
background-color: #eaeaea;
|
||||
height: 225px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.activity-no-map {
|
||||
background-color: #eaeaea;
|
||||
color: #666666;
|
||||
font-style: italic;
|
||||
height: 400px;
|
||||
line-height: 400px;
|
||||
}
|
||||
|
||||
.activity-notes, .actvitiy-segments {
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
margin-top: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.activity-page {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.activity-segments-list {
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
.activity-sport {
|
||||
margin-right: 1px;
|
||||
max-width: 18px;
|
||||
max-height: 18px;
|
||||
}
|
||||
|
||||
.activity-title img, .activity-title .map-attribution-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.activity-title img {
|
||||
border: 1px solid lightgrey;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
display: none;
|
||||
margin-left: 20px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.activity-title .map-attribution-list {
|
||||
display: none;
|
||||
font-size: 11px;
|
||||
margin-left: 20px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.activity-title:hover img, .activity-title:hover .map-attribution-list {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.add-activity {
|
||||
.add-workout {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.add-activity-radio {
|
||||
.add-workout-radio {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@ -199,7 +104,7 @@ label {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.chart-activities {
|
||||
.chart-workouts {
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
@ -238,7 +143,7 @@ label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.col-activity-logo{
|
||||
.col-workout-logo{
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@ -563,6 +468,101 @@ label {
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
.workouts-result {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.workout-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.workout-details {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
.workout-date {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.workout-filter {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.workout-filter .col-2, .col-5{
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.workout-label {
|
||||
font-size: 0.8em;
|
||||
color: #666
|
||||
}
|
||||
|
||||
.workout-logo {
|
||||
margin: 0 5px;
|
||||
max-width: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.workout-map {
|
||||
background-color: #eaeaea;
|
||||
height: 225px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.workout-no-map {
|
||||
background-color: #eaeaea;
|
||||
color: #666666;
|
||||
font-style: italic;
|
||||
height: 400px;
|
||||
line-height: 400px;
|
||||
}
|
||||
|
||||
.workout-notes, .actvitiy-segments {
|
||||
font-size: 0.9em;
|
||||
font-style: italic;
|
||||
margin-top: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.workout-page {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.workout-segments-list {
|
||||
list-style: square;
|
||||
}
|
||||
|
||||
.workout-sport {
|
||||
margin-right: 1px;
|
||||
max-width: 18px;
|
||||
max-height: 18px;
|
||||
}
|
||||
|
||||
.workout-title img, .workout-title .map-attribution-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.workout-title img {
|
||||
border: 1px solid lightgrey;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
display: none;
|
||||
margin-left: 20px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.workout-title .map-attribution-list {
|
||||
display: none;
|
||||
font-size: 11px;
|
||||
margin-left: 20px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.workout-title:hover img, .workout-title:hover .map-attribution-list {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* responsive table */
|
||||
/* adapted from https://uglyduck.ca/making-tables-responsive-with-minimal-css/ */
|
||||
.heading-span,
|
||||
@ -745,7 +745,7 @@ label {
|
||||
background: #eff1f3;
|
||||
}
|
||||
|
||||
.calendar-activity,
|
||||
.calendar-workout,
|
||||
.calendar-more {
|
||||
display: none;
|
||||
}
|
||||
@ -767,15 +767,15 @@ label {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.calendar-activity-more {
|
||||
.calendar-workout-more {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) {
|
||||
|
||||
.calendar-activity:nth-child(-n+2),
|
||||
.calendar-activity:nth-child(n+3) ~ .calendar-more,
|
||||
.calendar-activity-more:nth-child(n+3) {
|
||||
.calendar-workout:nth-child(-n+2),
|
||||
.calendar-workout:nth-child(n+3) ~ .calendar-more,
|
||||
.calendar-workout-more:nth-child(n+3) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@ -783,9 +783,9 @@ label {
|
||||
|
||||
@media only screen and (min-width: 992px) and (max-width: 1200px) {
|
||||
|
||||
.calendar-activity:nth-child(-n+4),
|
||||
.calendar-activity:nth-child(n+5) ~ .calendar-more,
|
||||
.calendar-activity-more:nth-child(n+5) {
|
||||
.calendar-workout:nth-child(-n+4),
|
||||
.calendar-workout:nth-child(n+5) ~ .calendar-more,
|
||||
.calendar-workout-more:nth-child(n+5) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@ -793,9 +793,9 @@ label {
|
||||
|
||||
@media only screen and (min-width: 1200px) {
|
||||
|
||||
.calendar-activity:nth-child(-n+6),
|
||||
.calendar-activity:nth-child(n+7) ~ .calendar-more,
|
||||
.calendar-activity-more:nth-child(n+7) {
|
||||
.calendar-workout:nth-child(-n+6),
|
||||
.calendar-workout:nth-child(n+7) ~ .calendar-more,
|
||||
.calendar-workout-more:nth-child(n+7) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,8 @@ import { Route, Switch } from 'react-router-dom'
|
||||
|
||||
import './App.css'
|
||||
import Admin from './Admin'
|
||||
import Activity from './Activity'
|
||||
import Activities from './Activities'
|
||||
import Workout from './Workout'
|
||||
import Workouts from './Workouts'
|
||||
import CurrentUserProfile from './User/CurrentUserProfile'
|
||||
import Dashboard from './Dashboard'
|
||||
import Footer from './Footer'
|
||||
@ -68,10 +68,10 @@ class App extends React.Component {
|
||||
<Route exact path="/logout" component={Logout} />
|
||||
<Route exact path="/profile/edit" component={ProfileEdit} />
|
||||
<Route exact path="/profile" component={CurrentUserProfile} />
|
||||
<Route exact path="/activities/history" component={Activities} />
|
||||
<Route exact path="/activities/statistics" component={Statistics} />
|
||||
<Route exact path="/workouts/history" component={Workouts} />
|
||||
<Route exact path="/workouts/statistics" component={Statistics} />
|
||||
<Route exact path="/users/:userName" component={UserProfile} />
|
||||
<Route path="/activities" component={Activity} />
|
||||
<Route path="/workouts" component={Workout} />
|
||||
<Route path="/admin" component={Admin} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default class NoActivities extends React.PureComponent {
|
||||
export default class NoWorkouts extends React.PureComponent {
|
||||
render() {
|
||||
const { t } = this.props
|
||||
return (
|
||||
<div className="card text-center">
|
||||
<div className="card-body">
|
||||
{t('common:No workouts.')}{' '}
|
||||
<Link to={{ pathname: '/activities/add' }}>
|
||||
<Link to={{ pathname: '/workouts/add' }}>
|
||||
{t('dashboard:Upload one !')}
|
||||
</Link>
|
||||
</div>
|
@ -4,13 +4,13 @@ import { apiUrl } from '../../utils'
|
||||
|
||||
export default class StaticMap extends React.PureComponent {
|
||||
render() {
|
||||
const { activity, display } = this.props
|
||||
const { display, workout } = this.props
|
||||
|
||||
return (
|
||||
<div className={`activity-map${display === 'list' ? '-list' : ''}`}>
|
||||
<div className={`workout-map${display === 'list' ? '-list' : ''}`}>
|
||||
<img
|
||||
src={`${apiUrl}activities/map/${activity.map}?${Date.now()}`}
|
||||
alt="activity map"
|
||||
src={`${apiUrl}workouts/map/${workout.map}?${Date.now()}`}
|
||||
alt="workout map"
|
||||
/>
|
||||
<div className={`map-attribution${display === 'list' ? '-list' : ''}`}>
|
||||
<span className="map-attribution-text">©</span>
|
||||
|
@ -8,8 +8,8 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts'
|
||||
|
||||
import { activityColors } from '../../../utils/activities'
|
||||
import { formatValue } from '../../../utils/stats'
|
||||
import { workoutColors } from '../../../utils/workouts'
|
||||
import CustomTooltip from './CustomTooltip'
|
||||
import CustomLabel from './CustomLabel'
|
||||
|
||||
@ -56,11 +56,11 @@ export default class StatsCharts extends React.PureComponent {
|
||||
<label className="radioLabel col">
|
||||
<input
|
||||
type="radio"
|
||||
name="activities"
|
||||
checked={displayedData === 'activities'}
|
||||
name="workouts"
|
||||
checked={displayedData === 'workouts'}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
{t('statistics:activities')}
|
||||
{t('statistics:workouts')}
|
||||
</label>
|
||||
</div>
|
||||
<ResponsiveContainer height={300}>
|
||||
@ -81,7 +81,7 @@ export default class StatsCharts extends React.PureComponent {
|
||||
key={s.id}
|
||||
dataKey={s.label}
|
||||
stackId="a"
|
||||
fill={activityColors[i]}
|
||||
fill={workoutColors[i]}
|
||||
label={
|
||||
i === sports.length - 1 ? (
|
||||
<CustomLabel displayedData={displayedData} />
|
||||
|
@ -23,7 +23,7 @@ class Statistics extends React.PureComponent {
|
||||
|
||||
updateData() {
|
||||
if (this.props.user.username) {
|
||||
this.props.loadActivities(
|
||||
this.props.loadWorkouts(
|
||||
this.props.user.username,
|
||||
this.props.user.weekm,
|
||||
this.props.statsParams
|
||||
@ -62,7 +62,7 @@ export default connect(
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivities: (userName, weekm, data) => {
|
||||
loadWorkouts: (userName, weekm, data) => {
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
// depends on user config (first day of week)
|
||||
const time =
|
||||
|
@ -17,8 +17,8 @@ import { enGB, fr } from 'date-fns/locale'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import CalendarActivities from './CalendarActivities'
|
||||
import { getMonthActivities } from '../../actions/activities'
|
||||
import CalendarWorkouts from './CalendarWorkouts'
|
||||
import { getMonthWorkouts } from '../../actions/workouts'
|
||||
import { getDateWithTZ } from '../../utils'
|
||||
|
||||
const getStartAndEndMonth = (date, weekStartOnMonday) => {
|
||||
@ -44,7 +44,7 @@ class Calendar extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadMonthActivities(this.state.startDate, this.state.endDate)
|
||||
this.props.loadMonthWorkouts(this.state.startDate, this.state.endDate)
|
||||
}
|
||||
|
||||
renderHeader(localeOptions) {
|
||||
@ -81,11 +81,11 @@ class Calendar extends React.Component {
|
||||
return <div className="days row">{days}</div>
|
||||
}
|
||||
|
||||
filterActivities(day) {
|
||||
const { activities, user } = this.props
|
||||
if (activities) {
|
||||
return activities.filter(act =>
|
||||
isSameDay(getDateWithTZ(act.activity_date, user.timezone), day)
|
||||
filterWorkouts(day) {
|
||||
const { workouts, user } = this.props
|
||||
if (workouts) {
|
||||
return workouts.filter(act =>
|
||||
isSameDay(getDateWithTZ(act.workout_date, user.timezone), day)
|
||||
)
|
||||
}
|
||||
return []
|
||||
@ -105,7 +105,7 @@ class Calendar extends React.Component {
|
||||
while (day <= endDate) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
formattedDate = format(day, dateFormat)
|
||||
const dayActivities = this.filterActivities(day)
|
||||
const dayWorkouts = this.filterWorkouts(day)
|
||||
const isDisabled = isSameMonth(day, currentMonth) ? '' : '-disabled'
|
||||
const isWeekEnd = weekStartOnMonday
|
||||
? [5, 6].includes(i)
|
||||
@ -119,8 +119,8 @@ class Calendar extends React.Component {
|
||||
>
|
||||
<div className={`img${isDisabled}`}>
|
||||
<span className="number">{formattedDate}</span>
|
||||
<CalendarActivities
|
||||
dayActivities={dayActivities}
|
||||
<CalendarWorkouts
|
||||
dayWorkouts={dayWorkouts}
|
||||
isDisabled={isDisabled}
|
||||
sports={sports}
|
||||
/>
|
||||
@ -149,7 +149,7 @@ class Calendar extends React.Component {
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
})
|
||||
this.props.loadMonthActivities(start, end)
|
||||
this.props.loadMonthWorkouts(start, end)
|
||||
}
|
||||
|
||||
handleNextMonth() {
|
||||
@ -167,7 +167,7 @@ class Calendar extends React.Component {
|
||||
locale: this.props.language === 'fr' ? fr : enGB,
|
||||
}
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="calendar">
|
||||
{this.renderHeader(localeOptions)}
|
||||
{this.renderDays(localeOptions)}
|
||||
@ -180,16 +180,16 @@ class Calendar extends React.Component {
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
activities: state.calendarActivities.data,
|
||||
workouts: state.calendarWorkouts.data,
|
||||
language: state.language,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadMonthActivities: (start, end) => {
|
||||
loadMonthWorkouts: (start, end) => {
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
dispatch(
|
||||
getMonthActivities(format(start, dateFormat), format(end, dateFormat))
|
||||
getMonthWorkouts(format(start, dateFormat), format(end, dateFormat))
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -1,28 +1,28 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { recordsLabels } from '../../utils/activities'
|
||||
import { recordsLabels } from '../../utils/workouts'
|
||||
|
||||
export default function CalendarActivity(props) {
|
||||
const { activity, isDisabled, isMore, sportImg } = props
|
||||
export default function CalendarWorkout(props) {
|
||||
const { isDisabled, isMore, sportImg, workout } = props
|
||||
return (
|
||||
<Link
|
||||
className={`calendar-activity${isMore}`}
|
||||
to={`/activities/${activity.id}`}
|
||||
className={`calendar-workout${isMore}`}
|
||||
to={`/workouts/${workout.id}`}
|
||||
>
|
||||
<>
|
||||
<img
|
||||
alt="activity sport logo"
|
||||
className={`activity-sport ${isDisabled}`}
|
||||
alt="workout sport logo"
|
||||
className={`workout-sport ${isDisabled}`}
|
||||
src={sportImg}
|
||||
title={activity.title}
|
||||
title={workout.title}
|
||||
/>
|
||||
{activity.records.length > 0 && (
|
||||
{workout.records.length > 0 && (
|
||||
<sup>
|
||||
<i
|
||||
className="fa fa-trophy custom-fa-small"
|
||||
aria-hidden="true"
|
||||
title={activity.records.map(
|
||||
title={workout.records.map(
|
||||
rec =>
|
||||
` ${
|
||||
recordsLabels.filter(
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
import CalendarActivity from './CalendarActivity'
|
||||
import CalendarWorkout from './CalendarWorkout'
|
||||
|
||||
export default class CalendarActivities extends React.Component {
|
||||
export default class CalendarWorkouts extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
@ -17,33 +17,33 @@ export default class CalendarActivities extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dayActivities, isDisabled, sports } = this.props
|
||||
const { dayWorkouts, isDisabled, sports } = this.props
|
||||
const { isHidden } = this.state
|
||||
return (
|
||||
<div>
|
||||
{dayActivities.map(act => (
|
||||
<CalendarActivity
|
||||
{dayWorkouts.map(act => (
|
||||
<CalendarWorkout
|
||||
key={act.id}
|
||||
activity={act}
|
||||
workout={act}
|
||||
isDisabled={isDisabled}
|
||||
isMore=""
|
||||
sportImg={sports.filter(s => s.id === act.sport_id).map(s => s.img)}
|
||||
/>
|
||||
))}
|
||||
{dayActivities.length > 2 && (
|
||||
{dayWorkouts.length > 2 && (
|
||||
<i
|
||||
className={`fa fa-${isHidden ? 'plus' : 'times'} calendar-more`}
|
||||
aria-hidden="true"
|
||||
onClick={() => this.handleDisplayMore()}
|
||||
title="show more activities"
|
||||
title="show more workouts"
|
||||
/>
|
||||
)}
|
||||
{!isHidden && (
|
||||
<div className="calendar-display-more">
|
||||
{dayActivities.map(act => (
|
||||
<CalendarActivity
|
||||
{dayWorkouts.map(act => (
|
||||
<CalendarWorkout
|
||||
key={act.id}
|
||||
activity={act}
|
||||
workout={act}
|
||||
isDisabled={isDisabled}
|
||||
isMore="-more"
|
||||
sportImg={sports
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { formatRecord, translateSports } from '../../utils/activities'
|
||||
import { formatRecord, translateSports } from '../../utils/workouts'
|
||||
|
||||
export default function RecordsCard(props) {
|
||||
const { records, sports, t, user } = props
|
||||
@ -19,8 +19,8 @@ export default function RecordsCard(props) {
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="card-header">{t('activities:Personal records')}</div>
|
||||
<div className="card workout-card">
|
||||
<div className="card-header">{t('workouts:Personal records')}</div>
|
||||
<div className="card-body">
|
||||
{Object.keys(recordsBySport).length === 0
|
||||
? t('common:No records.')
|
||||
@ -54,12 +54,12 @@ export default function RecordsCard(props) {
|
||||
{recordsBySport[sportLabel].records.map(rec => (
|
||||
<tr className="record-tr" key={rec.id}>
|
||||
<td className="record-td">
|
||||
{t(`activities:${rec.record_type}`)}
|
||||
{t(`workouts:${rec.record_type}`)}
|
||||
</td>
|
||||
<td className="record-td text-right">{rec.value}</td>
|
||||
<td className="record-td text-right">
|
||||
<Link to={`/activities/${rec.activity_id}`}>
|
||||
{rec.activity_date}
|
||||
<Link to={`/workouts/${rec.workout_id}`}>
|
||||
{rec.workout_date}
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -18,7 +18,7 @@ export default class Statistics extends React.Component {
|
||||
render() {
|
||||
const { t } = this.props
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-header">{t('dashboard:This month')}</div>
|
||||
<div className="card-body">
|
||||
<Stats displayEmpty={false} statsParams={this.state} t={t} />
|
||||
|
@ -14,15 +14,15 @@ export default function UserStatistics(props) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-calendar fa-3x fa-color" />
|
||||
</div>
|
||||
<div className="col-9 text-right">
|
||||
<div className="huge">{user.nb_activities}</div>
|
||||
<div className="huge">{user.nb_workouts}</div>
|
||||
<div>{`${
|
||||
user.nb_activities === 1
|
||||
user.nb_workouts === 1
|
||||
? t('common:workout')
|
||||
: t('common:workouts')
|
||||
}`}</div>
|
||||
@ -31,7 +31,7 @@ export default function UserStatistics(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-road fa-3x fa-color" />
|
||||
@ -46,7 +46,7 @@ export default function UserStatistics(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-clock-o fa-3x fa-color" />
|
||||
@ -59,7 +59,7 @@ export default function UserStatistics(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body row">
|
||||
<div className="col-3">
|
||||
<i className="fa fa-tags fa-3x fa-color" />
|
||||
|
@ -5,35 +5,35 @@ import { Link } from 'react-router-dom'
|
||||
import StaticMap from '../Common/StaticMap'
|
||||
import { getDateWithTZ } from '../../utils'
|
||||
|
||||
export default function ActivityCard(props) {
|
||||
const { activity, sports, t, user } = props
|
||||
export default function WorkoutCard(props) {
|
||||
const { sports, t, user, workout } = props
|
||||
|
||||
return (
|
||||
<div className="card activity-card text-center">
|
||||
<div className="card workout-card text-center">
|
||||
<div className="card-header">
|
||||
<Link to={`/activities/${activity.id}`}>
|
||||
<Link to={`/workouts/${workout.id}`}>
|
||||
{sports
|
||||
.filter(sport => sport.id === activity.sport_id)
|
||||
.filter(sport => sport.id === workout.sport_id)
|
||||
.map(sport => t(`sports:${sport.label}`))}{' '}
|
||||
-{' '}
|
||||
{format(
|
||||
getDateWithTZ(activity.activity_date, user.timezone),
|
||||
getDateWithTZ(workout.workout_date, user.timezone),
|
||||
'dd/MM/yyyy HH:mm'
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
{activity.map && (
|
||||
{workout.map && (
|
||||
<div className="col">
|
||||
<StaticMap activity={activity} />
|
||||
<StaticMap workout={workout} />
|
||||
</div>
|
||||
)}
|
||||
<div className="col">
|
||||
<p>
|
||||
<i className="fa fa-clock-o" aria-hidden="true" />{' '}
|
||||
{t('activities:Duration')}: {activity.moving}
|
||||
{activity.map ? (
|
||||
{t('workouts:Duration')}: {workout.moving}
|
||||
{workout.map ? (
|
||||
<span>
|
||||
<br />
|
||||
<br />
|
||||
@ -42,7 +42,7 @@ export default function ActivityCard(props) {
|
||||
' - '
|
||||
)}
|
||||
<i className="fa fa-road" aria-hidden="true" />{' '}
|
||||
{t('activities:Distance')}: {activity.distance} km
|
||||
{t('workouts:Distance')}: {workout.distance} km
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -3,15 +3,15 @@ import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import ActivityCard from './ActivityCard'
|
||||
import Calendar from './Calendar'
|
||||
import Message from '../Common/Message'
|
||||
import NoActivities from '../Common/NoActivities'
|
||||
import NoWorkouts from '../Common/NoWorkouts'
|
||||
import Records from './Records'
|
||||
import Statistics from './Statistics'
|
||||
import UserStatistics from './UserStatistics'
|
||||
import WorkoutCard from './WorkoutCard'
|
||||
import { getOrUpdateData } from '../../actions'
|
||||
import { getMoreActivities } from '../../actions/activities'
|
||||
import { getMoreWorkouts } from '../../actions/workouts'
|
||||
|
||||
class DashBoard extends React.Component {
|
||||
constructor(props, context) {
|
||||
@ -22,22 +22,22 @@ class DashBoard extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadActivities()
|
||||
this.props.loadWorkouts()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
activities,
|
||||
loadMoreActivities,
|
||||
loadMoreWorkouts,
|
||||
message,
|
||||
records,
|
||||
sports,
|
||||
t,
|
||||
user,
|
||||
workouts,
|
||||
} = this.props
|
||||
const paginationEnd =
|
||||
activities.length > 0
|
||||
? activities[activities.length - 1].previous_activity === null
|
||||
workouts.length > 0
|
||||
? workouts[workouts.length - 1].previous_workout === null
|
||||
: true
|
||||
const { page } = this.state
|
||||
return (
|
||||
@ -48,7 +48,7 @@ class DashBoard extends React.Component {
|
||||
{message ? (
|
||||
<Message message={message} t={t} />
|
||||
) : (
|
||||
activities &&
|
||||
workouts &&
|
||||
user.total_duration &&
|
||||
sports.length > 0 && (
|
||||
<div className="container dashboard">
|
||||
@ -65,26 +65,26 @@ class DashBoard extends React.Component {
|
||||
</div>
|
||||
<div className="col-md-8">
|
||||
<Calendar weekm={user.weekm} />
|
||||
{activities.length > 0 ? (
|
||||
activities.map(activity => (
|
||||
<ActivityCard
|
||||
activity={activity}
|
||||
key={activity.id}
|
||||
{workouts.length > 0 ? (
|
||||
workouts.map(workout => (
|
||||
<WorkoutCard
|
||||
workout={workout}
|
||||
key={workout.id}
|
||||
sports={sports}
|
||||
t={t}
|
||||
user={user}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<NoActivities t={t} />
|
||||
<NoWorkouts t={t} />
|
||||
)}
|
||||
{!paginationEnd && (
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-default btn-md btn-block"
|
||||
value="Load more activities"
|
||||
value="Load more workouts"
|
||||
onClick={() => {
|
||||
loadMoreActivities(page + 1)
|
||||
loadMoreWorkouts(page + 1)
|
||||
this.setState({ page: page + 1 })
|
||||
}}
|
||||
/>
|
||||
@ -102,19 +102,19 @@ class DashBoard extends React.Component {
|
||||
export default withTranslation()(
|
||||
connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
workouts: state.workouts.data,
|
||||
message: state.message,
|
||||
records: state.records.data,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivities: () => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', { page: 1 }))
|
||||
loadWorkouts: () => {
|
||||
dispatch(getOrUpdateData('getData', 'workouts', { page: 1 }))
|
||||
dispatch(getOrUpdateData('getData', 'records'))
|
||||
},
|
||||
loadMoreActivities: page => {
|
||||
dispatch(getMoreActivities({ page }))
|
||||
loadMoreWorkouts: page => {
|
||||
dispatch(getMoreWorkouts({ page }))
|
||||
},
|
||||
})
|
||||
)(DashBoard)
|
||||
|
@ -45,7 +45,7 @@ class NavBar extends React.PureComponent {
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/history',
|
||||
pathname: '/workouts/history',
|
||||
}}
|
||||
>
|
||||
{t('Workouts')}
|
||||
@ -57,7 +57,7 @@ class NavBar extends React.PureComponent {
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/statistics',
|
||||
pathname: '/workouts/statistics',
|
||||
}}
|
||||
>
|
||||
{t('common:Statistics')}
|
||||
@ -81,7 +81,7 @@ class NavBar extends React.PureComponent {
|
||||
<Link
|
||||
className="nav-link"
|
||||
to={{
|
||||
pathname: '/activities/add',
|
||||
pathname: '/workouts/add',
|
||||
}}
|
||||
>
|
||||
<strong>{t('common:Add workout')}</strong>
|
||||
|
@ -17,9 +17,9 @@ import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import NoActivities from '../Common/NoActivities'
|
||||
import NoWorkouts from '../Common/NoWorkouts'
|
||||
import Stats from '../Common/Stats'
|
||||
import { activityColors, translateSports } from '../../utils/activities'
|
||||
import { workoutColors, translateSports } from '../../utils/workouts'
|
||||
|
||||
const durations = ['week', 'month', 'year']
|
||||
|
||||
@ -127,11 +127,11 @@ class Statistics extends React.Component {
|
||||
<title>FitTrackee - {t('statistics:Statistics')}</title>
|
||||
</Helmet>
|
||||
<div className="container dashboard">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-header">{t('statistics:Statistics')}</div>
|
||||
<div
|
||||
className={`card-body${
|
||||
user.nb_activities === 0 ? ' stats-disabled' : ''
|
||||
user.nb_workouts === 0 ? ' stats-disabled' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="chart-filters row">
|
||||
@ -176,16 +176,16 @@ class Statistics extends React.Component {
|
||||
statsParams={statsParams}
|
||||
t={t}
|
||||
/>
|
||||
<div className="row chart-activities">
|
||||
<div className="row chart-workouts">
|
||||
{translatedSports.map(sport => (
|
||||
<label className="col activity-label" key={sport.id}>
|
||||
<label className="col workout-label" key={sport.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={displayedSports.includes(sport.id)}
|
||||
name={sport.label}
|
||||
onChange={() => this.handleOnChangeSports(sport.id)}
|
||||
/>
|
||||
<span style={{ color: activityColors[sport.id - 1] }}>
|
||||
<span style={{ color: workoutColors[sport.id - 1] }}>
|
||||
{` ${sport.label}`}
|
||||
</span>
|
||||
</label>
|
||||
@ -193,7 +193,7 @@ class Statistics extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{user.nb_activities === 0 && <NoActivities t={t} />}
|
||||
{user.nb_workouts === 0 && <NoWorkouts t={t} />}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import ActivityAddOrEdit from './ActivityAddOrEdit'
|
||||
import WorkoutAddOrEdit from './WorkoutAddOrEdit'
|
||||
|
||||
function ActivityAdd(props) {
|
||||
function WorkoutAdd(props) {
|
||||
const { message, sports } = props
|
||||
return (
|
||||
<div>
|
||||
<ActivityAddOrEdit activity={null} message={message} sports={sports} />
|
||||
<WorkoutAddOrEdit workout={null} message={message} sports={sports} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -16,4 +16,4 @@ export default connect(state => ({
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}))(ActivityAdd)
|
||||
}))(WorkoutAdd)
|
@ -3,11 +3,11 @@ import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import FormWithGpx from './ActivityForms/FormWithGpx'
|
||||
import FormWithoutGpx from './ActivityForms/FormWithoutGpx'
|
||||
import FormWithGpx from './WorkoutForms/FormWithGpx'
|
||||
import FormWithoutGpx from './WorkoutForms/FormWithoutGpx'
|
||||
import Message from '../Common/Message'
|
||||
|
||||
class ActivityAddEdit extends React.Component {
|
||||
class WorkoutAddEdit extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
@ -25,16 +25,16 @@ class ActivityAddEdit extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activity, loading, message, sports, t } = this.props
|
||||
const { loading, message, sports, t, workout } = this.props
|
||||
const { withGpx } = this.state
|
||||
return (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>
|
||||
FitTrackee -{' '}
|
||||
{activity
|
||||
? t('activities:Edit a workout')
|
||||
: t('activities:Add a workout')}
|
||||
{workout
|
||||
? t('workouts:Edit a workout')
|
||||
: t('workouts:Add a workout')}
|
||||
</title>
|
||||
</Helmet>
|
||||
<br />
|
||||
@ -44,22 +44,18 @@ class ActivityAddEdit extends React.Component {
|
||||
<div className="row">
|
||||
<div className="col-md-2" />
|
||||
<div className="col-md-8">
|
||||
<div className="card add-activity">
|
||||
<div className="card add-workout">
|
||||
<h2 className="card-header text-center">
|
||||
{activity
|
||||
? t('activities:Edit a workout')
|
||||
: t('activities:Add a workout')}
|
||||
{workout
|
||||
? t('workouts:Edit a workout')
|
||||
: t('workouts:Add a workout')}
|
||||
</h2>
|
||||
<div className="card-body">
|
||||
{activity ? (
|
||||
activity.with_gpx ? (
|
||||
<FormWithGpx activity={activity} sports={sports} t={t} />
|
||||
{workout ? (
|
||||
workout.with_gpx ? (
|
||||
<FormWithGpx workout={workout} sports={sports} t={t} />
|
||||
) : (
|
||||
<FormWithoutGpx
|
||||
activity={activity}
|
||||
sports={sports}
|
||||
t={t}
|
||||
/>
|
||||
<FormWithoutGpx workout={workout} sports={sports} t={t} />
|
||||
)
|
||||
) : (
|
||||
<div>
|
||||
@ -68,7 +64,7 @@ class ActivityAddEdit extends React.Component {
|
||||
<div className="col">
|
||||
<label className="radioLabel">
|
||||
<input
|
||||
className="add-activity-radio"
|
||||
className="add-workout-radio"
|
||||
type="radio"
|
||||
name="withGpx"
|
||||
disabled={loading}
|
||||
@ -77,13 +73,13 @@ class ActivityAddEdit extends React.Component {
|
||||
this.handleRadioChange(event)
|
||||
}
|
||||
/>
|
||||
{t('activities:with gpx file')}
|
||||
{t('workouts:with gpx file')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<label className="radioLabel">
|
||||
<input
|
||||
className="add-activity-radio"
|
||||
className="add-workout-radio"
|
||||
type="radio"
|
||||
name="withoutGpx"
|
||||
disabled={loading}
|
||||
@ -92,7 +88,7 @@ class ActivityAddEdit extends React.Component {
|
||||
this.handleRadioChange(event)
|
||||
}
|
||||
/>
|
||||
{t('activities:without gpx file')}
|
||||
{t('workouts:without gpx file')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -118,5 +114,5 @@ class ActivityAddEdit extends React.Component {
|
||||
export default withTranslation()(
|
||||
connect(state => ({
|
||||
loading: state.loading,
|
||||
}))(ActivityAddEdit)
|
||||
}))(WorkoutAddEdit)
|
||||
)
|
@ -12,7 +12,7 @@ export default function Map({ bounds, coordinates, jsonData, mapAttribution }) {
|
||||
<TileLayer
|
||||
// eslint-disable-next-line max-len
|
||||
attribution={mapAttribution}
|
||||
url={`${apiUrl}activities/map_tile/{s}/{z}/{x}/{y}.png`}
|
||||
url={`${apiUrl}workouts/map_tile/{s}/{z}/{x}/{y}.png`}
|
||||
/>
|
||||
<GeoJSON
|
||||
// hash as a key to force re-rendering
|
@ -2,11 +2,10 @@ import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { getDateWithTZ } from '../../../utils'
|
||||
import { formatActivityDate } from '../../../utils/activities'
|
||||
import { formatWorkoutDate } from '../../../utils/workouts'
|
||||
|
||||
export default function ActivityCardHeader(props) {
|
||||
export default function WorkoutCardHeader(props) {
|
||||
const {
|
||||
activity,
|
||||
dataType,
|
||||
displayModal,
|
||||
segmentId,
|
||||
@ -14,22 +13,23 @@ export default function ActivityCardHeader(props) {
|
||||
t,
|
||||
title,
|
||||
user,
|
||||
workout,
|
||||
} = props
|
||||
const activityDate = activity
|
||||
? formatActivityDate(getDateWithTZ(activity.activity_date, user.timezone))
|
||||
const workoutDate = workout
|
||||
? formatWorkoutDate(getDateWithTZ(workout.workout_date, user.timezone))
|
||||
: null
|
||||
|
||||
const previousUrl =
|
||||
dataType === 'segment' && segmentId !== 1
|
||||
? `/activities/${activity.id}/segment/${segmentId - 1}`
|
||||
: dataType === 'activity' && activity.previous_activity
|
||||
? `/activities/${activity.previous_activity}`
|
||||
? `/workouts/${workout.id}/segment/${segmentId - 1}`
|
||||
: dataType === 'workout' && workout.previous_workout
|
||||
? `/workouts/${workout.previous_workout}`
|
||||
: null
|
||||
const nextUrl =
|
||||
dataType === 'segment' && segmentId < activity.segments.length
|
||||
? `/activities/${activity.id}/segment/${segmentId + 1}`
|
||||
: dataType === 'activity' && activity.next_activity
|
||||
? `/activities/${activity.next_activity}`
|
||||
dataType === 'segment' && segmentId < workout.segments.length
|
||||
? `/workouts/${workout.id}/segment/${segmentId + 1}`
|
||||
: dataType === 'workout' && workout.next_workout
|
||||
? `/workouts/${workout.next_workout}`
|
||||
: null
|
||||
|
||||
return (
|
||||
@ -41,53 +41,53 @@ export default function ActivityCardHeader(props) {
|
||||
<i
|
||||
className="fa fa-chevron-left"
|
||||
aria-hidden="true"
|
||||
title={t(`activities:See previous ${dataType}`)}
|
||||
title={t(`workouts:See previous ${dataType}`)}
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
<i
|
||||
className="fa fa-chevron-left inactive-link"
|
||||
aria-hidden="true"
|
||||
title={t(`activities:No previous ${dataType}`)}
|
||||
title={t(`workouts:No previous ${dataType}`)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-auto col-activity-logo">
|
||||
<div className="col-auto col-workout-logo">
|
||||
<img className="sport-img-medium" src={sport.img} alt="sport logo" />
|
||||
</div>
|
||||
<div className="col">
|
||||
{dataType === 'activity' ? (
|
||||
{dataType === 'workout' ? (
|
||||
<>
|
||||
{title}{' '}
|
||||
<Link className="unlink" to={`/activities/${activity.id}/edit`}>
|
||||
<Link className="unlink" to={`/workouts/${workout.id}/edit`}>
|
||||
<i
|
||||
className="fa fa-edit custom-fa"
|
||||
aria-hidden="true"
|
||||
title={t('activities:Edit activity')}
|
||||
title={t('workouts:Edit workout')}
|
||||
/>
|
||||
</Link>
|
||||
<i
|
||||
className="fa fa-trash custom-fa"
|
||||
aria-hidden="true"
|
||||
onClick={() => displayModal(true)}
|
||||
title={t('activities:Delete activity')}
|
||||
title={t('workouts:Delete workout')}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* prettier-ignore */}
|
||||
<Link
|
||||
to={`/activities/${activity.id}`}
|
||||
to={`/workouts/${workout.id}`}
|
||||
>
|
||||
{title}
|
||||
</Link>{' '}
|
||||
- {t('activities:segment')} {segmentId}
|
||||
- {t('workouts:segment')} {segmentId}
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
{activityDate && (
|
||||
<span className="activity-date">
|
||||
{`${activityDate.activity_date} - ${activityDate.activity_time}`}
|
||||
{workoutDate && (
|
||||
<span className="workout-date">
|
||||
{`${workoutDate.workout_date} - ${workoutDate.workout_time}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -97,14 +97,14 @@ export default function ActivityCardHeader(props) {
|
||||
<i
|
||||
className="fa fa-chevron-right"
|
||||
aria-hidden="true"
|
||||
title={t(`activities:See next ${dataType}`)}
|
||||
title={t(`workouts:See next ${dataType}`)}
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
<i
|
||||
className="fa fa-chevron-right inactive-link"
|
||||
aria-hidden="true"
|
||||
title={t(`activities:No next ${dataType}`)}
|
||||
title={t(`workouts:No next ${dataType}`)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
@ -12,11 +12,11 @@ import {
|
||||
} from 'recharts'
|
||||
|
||||
import {
|
||||
getActivityChartData,
|
||||
getSegmentChartData,
|
||||
} from '../../../actions/activities'
|
||||
getWorkoutChartData,
|
||||
} from '../../../actions/workouts'
|
||||
|
||||
class ActivityCharts extends React.Component {
|
||||
class WorkoutCharts extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
@ -26,31 +26,31 @@ class ActivityCharts extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.dataType === 'activity') {
|
||||
this.props.loadActivityData(this.props.activity.id)
|
||||
if (this.props.dataType === 'workout') {
|
||||
this.props.loadWorkoutData(this.props.workout.id)
|
||||
} else {
|
||||
this.props.loadSegmentData(this.props.activity.id, this.props.segmentId)
|
||||
this.props.loadSegmentData(this.props.workout.id, this.props.segmentId)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
(this.props.dataType === 'activity' &&
|
||||
prevProps.activity.id !== this.props.activity.id) ||
|
||||
(this.props.dataType === 'activity' && prevProps.dataType === 'segment')
|
||||
(this.props.dataType === 'workout' &&
|
||||
prevProps.workout.id !== this.props.workout.id) ||
|
||||
(this.props.dataType === 'workout' && prevProps.dataType === 'segment')
|
||||
) {
|
||||
this.props.loadActivityData(this.props.activity.id)
|
||||
this.props.loadWorkoutData(this.props.workout.id)
|
||||
}
|
||||
if (
|
||||
this.props.dataType === 'segment' &&
|
||||
prevProps.segmentId !== this.props.segmentId
|
||||
) {
|
||||
this.props.loadSegmentData(this.props.activity.id, this.props.segmentId)
|
||||
this.props.loadSegmentData(this.props.workout.id, this.props.segmentId)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.loadActivityData(null)
|
||||
this.props.loadWorkoutData(null)
|
||||
}
|
||||
|
||||
handleRadioChange(changeEvent) {
|
||||
@ -102,7 +102,7 @@ class ActivityCharts extends React.Component {
|
||||
checked={displayDistance}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
{t('activities:distance')}
|
||||
{t('workouts:distance')}
|
||||
</label>
|
||||
<label className="radioLabel col-md-1">
|
||||
<input
|
||||
@ -111,7 +111,7 @@ class ActivityCharts extends React.Component {
|
||||
checked={!displayDistance}
|
||||
onChange={e => this.handleRadioChange(e)}
|
||||
/>
|
||||
{t('activities:duration')}
|
||||
{t('workouts:duration')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="row chart-radio">
|
||||
@ -123,7 +123,7 @@ class ActivityCharts extends React.Component {
|
||||
checked={this.displayData('speed')}
|
||||
onChange={e => this.handleLegendChange(e)}
|
||||
/>
|
||||
{t('activities:speed')}
|
||||
{t('workouts:speed')}
|
||||
</label>
|
||||
<label className="radioLabel col-md-1">
|
||||
<input
|
||||
@ -132,7 +132,7 @@ class ActivityCharts extends React.Component {
|
||||
checked={this.displayData('elevation')}
|
||||
onChange={e => this.handleLegendChange(e)}
|
||||
/>
|
||||
{t('activities:elevation')}
|
||||
{t('workouts:elevation')}
|
||||
</label>
|
||||
<div className="col-md-5" />
|
||||
</div>
|
||||
@ -148,7 +148,7 @@ class ActivityCharts extends React.Component {
|
||||
allowDecimals={false}
|
||||
dataKey={xDataKey}
|
||||
label={{
|
||||
value: t(`activities:${xDataKey}`),
|
||||
value: t(`workouts:${xDataKey}`),
|
||||
offset: 0,
|
||||
position: 'bottom',
|
||||
}}
|
||||
@ -161,7 +161,7 @@ class ActivityCharts extends React.Component {
|
||||
/>
|
||||
<YAxis
|
||||
label={{
|
||||
value: `${t('activities:speed')} (km/h)`,
|
||||
value: `${t('workouts:speed')} (km/h)`,
|
||||
angle: -90,
|
||||
position: 'left',
|
||||
}}
|
||||
@ -169,7 +169,7 @@ class ActivityCharts extends React.Component {
|
||||
/>
|
||||
<YAxis
|
||||
label={{
|
||||
value: `${t('activities:elevation')} (m)`,
|
||||
value: `${t('workouts:elevation')} (m)`,
|
||||
angle: -90,
|
||||
position: 'right',
|
||||
}}
|
||||
@ -181,7 +181,7 @@ class ActivityCharts extends React.Component {
|
||||
yAxisId="right"
|
||||
type="linear"
|
||||
dataKey="elevation"
|
||||
name={t('activities:elevation')}
|
||||
name={t('workouts:elevation')}
|
||||
fill="#e5e5e5"
|
||||
stroke="#cccccc"
|
||||
dot={false}
|
||||
@ -193,7 +193,7 @@ class ActivityCharts extends React.Component {
|
||||
yAxisId="left"
|
||||
type="linear"
|
||||
dataKey="speed"
|
||||
name={t('activities:speed')}
|
||||
name={t('workouts:speed')}
|
||||
stroke="#8884d8"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
@ -203,8 +203,8 @@ class ActivityCharts extends React.Component {
|
||||
<Tooltip
|
||||
labelFormatter={value =>
|
||||
displayDistance
|
||||
? `${t('activities:distance')}: ${value} km`
|
||||
: `${t('activities:duration')}: ${format(
|
||||
? `${t('workouts:distance')}: ${value} km`
|
||||
: `${t('workouts:duration')}: ${format(
|
||||
value,
|
||||
'HH:mm:ss'
|
||||
)}`
|
||||
@ -214,11 +214,11 @@ class ActivityCharts extends React.Component {
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="chart-info">
|
||||
{t('activities:data from gpx, without any cleaning')}
|
||||
{t('workouts:data from gpx, without any cleaning')}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
t('activities:No data to display')
|
||||
t('workouts:No data to display')
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@ -230,11 +230,11 @@ export default connect(
|
||||
chartData: state.chartData,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivityData: activityId => {
|
||||
dispatch(getActivityChartData(activityId))
|
||||
loadWorkoutData: workoutId => {
|
||||
dispatch(getWorkoutChartData(workoutId))
|
||||
},
|
||||
loadSegmentData: (activityId, segmentId) => {
|
||||
dispatch(getSegmentChartData(activityId, segmentId))
|
||||
loadSegmentData: (workoutId, segmentId) => {
|
||||
dispatch(getSegmentChartData(workoutId, segmentId))
|
||||
},
|
||||
})
|
||||
)(ActivityCharts)
|
||||
)(WorkoutCharts)
|
@ -0,0 +1,73 @@
|
||||
import React from 'react'
|
||||
|
||||
import WorkoutWeather from './WorkoutWeather'
|
||||
|
||||
export default function WorkoutDetails(props) {
|
||||
const { t, workout } = props
|
||||
const withPauses = workout.pauses !== '0:00:00' && workout.pauses !== null
|
||||
return (
|
||||
<div className="workout-details">
|
||||
<p>
|
||||
<i className="fa fa-clock-o custom-fa" aria-hidden="true" />
|
||||
{t('workouts:Duration')}: {workout.moving}
|
||||
{workout.records &&
|
||||
workout.records.find(record => record.record_type === 'LD') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
{withPauses && (
|
||||
<span>
|
||||
<br />({t('workouts:pauses')}: {workout.pauses},{' '}
|
||||
{t('workouts:total duration')}: {workout.duration})
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<i className="fa fa-road custom-fa" aria-hidden="true" />
|
||||
{t('workouts:Distance')}: {workout.distance} km
|
||||
{workout.records &&
|
||||
workout.records.find(record => record.record_type === 'FD') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<i className="fa fa-tachometer custom-fa" aria-hidden="true" />
|
||||
{t('workouts:Average speed')}: {workout.ave_speed} km/h
|
||||
{workout.records &&
|
||||
workout.records.find(record => record.record_type === 'AS') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
<br />
|
||||
{t('workouts:Max. speed')}: {workout.max_speed} km/h
|
||||
{workout.records &&
|
||||
workout.records.find(record => record.record_type === 'MS') && (
|
||||
<sup>
|
||||
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||
</sup>
|
||||
)}
|
||||
</p>
|
||||
{workout.min_alt && workout.max_alt && (
|
||||
<p>
|
||||
<i className="fi-mountains custom-fa" />
|
||||
{t('workouts:Min. altitude')}: {workout.min_alt}m
|
||||
<br />
|
||||
{t('workouts:Max. altitude')}: {workout.max_alt}m
|
||||
</p>
|
||||
)}
|
||||
{workout.ascent && workout.descent && (
|
||||
<p>
|
||||
<i className="fa fa-location-arrow custom-fa" />
|
||||
{t('workouts:Ascent')}: {workout.ascent}m
|
||||
<br />
|
||||
{t('workouts:Descent')}: {workout.descent}m
|
||||
</p>
|
||||
)}
|
||||
<WorkoutWeather workout={workout} t={t} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -3,10 +3,10 @@ import { MapContainer } from 'react-leaflet'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import Map from './Map'
|
||||
import { getActivityGpx, getSegmentGpx } from '../../../actions/activities'
|
||||
import { getGeoJson } from '../../../utils/activities'
|
||||
import { getSegmentGpx, getWorkoutGpx } from '../../../actions/workouts'
|
||||
import { getGeoJson } from '../../../utils/workouts'
|
||||
|
||||
class ActivityMap extends React.Component {
|
||||
class WorkoutMap extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
@ -15,39 +15,39 @@ class ActivityMap extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.dataType === 'activity') {
|
||||
this.props.loadActivityGpx(this.props.activity.id)
|
||||
if (this.props.dataType === 'workout') {
|
||||
this.props.loadWorkoutGpx(this.props.workout.id)
|
||||
} else {
|
||||
this.props.loadSegmentGpx(this.props.activity.id, this.props.segmentId)
|
||||
this.props.loadSegmentGpx(this.props.workout.id, this.props.segmentId)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
(this.props.dataType === 'activity' &&
|
||||
prevProps.activity.id !== this.props.activity.id) ||
|
||||
(this.props.dataType === 'activity' && prevProps.dataType === 'segment')
|
||||
(this.props.dataType === 'workout' &&
|
||||
prevProps.workout.id !== this.props.workout.id) ||
|
||||
(this.props.dataType === 'workout' && prevProps.dataType === 'segment')
|
||||
) {
|
||||
this.props.loadActivityGpx(this.props.activity.id)
|
||||
this.props.loadWorkoutGpx(this.props.workout.id)
|
||||
}
|
||||
if (
|
||||
this.props.dataType === 'segment' &&
|
||||
prevProps.segmentId !== this.props.segmentId
|
||||
) {
|
||||
this.props.loadSegmentGpx(this.props.activity.id, this.props.segmentId)
|
||||
this.props.loadSegmentGpx(this.props.workout.id, this.props.segmentId)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.loadActivityGpx(null)
|
||||
this.props.loadWorkoutGpx(null)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { activity, coordinates, gpxContent, mapAttribution } = this.props
|
||||
const { coordinates, gpxContent, mapAttribution, workout } = this.props
|
||||
const { jsonData } = getGeoJson(gpxContent)
|
||||
const bounds = [
|
||||
[activity.bounds[0], activity.bounds[1]],
|
||||
[activity.bounds[2], activity.bounds[3]],
|
||||
[workout.bounds[0], workout.bounds[1]],
|
||||
[workout.bounds[2], workout.bounds[3]],
|
||||
]
|
||||
|
||||
return (
|
||||
@ -77,11 +77,11 @@ export default connect(
|
||||
mapAttribution: state.application.config.map_attribution,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivityGpx: activityId => {
|
||||
dispatch(getActivityGpx(activityId))
|
||||
loadWorkoutGpx: workoutId => {
|
||||
dispatch(getWorkoutGpx(workoutId))
|
||||
},
|
||||
loadSegmentGpx: (activityId, segmentId) => {
|
||||
dispatch(getSegmentGpx(activityId, segmentId))
|
||||
loadSegmentGpx: (workoutId, segmentId) => {
|
||||
dispatch(getSegmentGpx(workoutId, segmentId))
|
||||
},
|
||||
})
|
||||
)(ActivityMap)
|
||||
)(WorkoutMap)
|
@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function WorkoutNoMap(props) {
|
||||
const { t } = props
|
||||
return (
|
||||
<div className="workout-no-map text-center">{t('workouts:No Map')}</div>
|
||||
)
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function ActivityNotes(props) {
|
||||
export default function WorkoutNotes(props) {
|
||||
const { notes, t } = props
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body">
|
||||
Notes
|
||||
<div className="activity-notes">
|
||||
{notes ? notes : t('activities:No notes')}
|
||||
<div className="workout-notes">
|
||||
{notes ? notes : t('workouts:No notes')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,31 +1,31 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function ActivitySegments(props) {
|
||||
export default function WorkoutSegments(props) {
|
||||
const { segments, t } = props
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body">
|
||||
{t('activities:Segments')}
|
||||
<div className="activity-segments">
|
||||
{t('workouts:Segments')}
|
||||
<div className="workout-segments">
|
||||
<ul>
|
||||
{segments.map((segment, index) => (
|
||||
<li
|
||||
className="activity-segments-list"
|
||||
className="workout-segments-list"
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={`segment-${index}`}
|
||||
>
|
||||
<Link
|
||||
to={`/activities/${segment.activity_id}/segment/${
|
||||
to={`/workouts/${segment.workout_id}/segment/${
|
||||
index + 1
|
||||
}`}
|
||||
>
|
||||
{t('activities:segment')} {index + 1}
|
||||
{t('workouts:segment')} {index + 1}
|
||||
</Link>{' '}
|
||||
({t('activities:distance')}: {segment.distance} km,{' '}
|
||||
{t('activities:duration')}: {segment.duration})
|
||||
({t('workouts:distance')}: {segment.distance} km,{' '}
|
||||
{t('workouts:duration')}: {segment.duration})
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
@ -1,32 +1,32 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function ActivityWeather(props) {
|
||||
const { activity, t } = props
|
||||
export default function WorkoutWeather(props) {
|
||||
const { t, workout } = props
|
||||
return (
|
||||
<div className="container">
|
||||
{activity.weather_start && activity.weather_end && (
|
||||
{workout.weather_start && workout.weather_end && (
|
||||
<table className="table table-borderless weather-table text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
{t('activities:Start')}
|
||||
{t('workouts:Start')}
|
||||
<br />
|
||||
<img
|
||||
className="weather-img"
|
||||
src={`/img/weather/${activity.weather_start.icon}.png`}
|
||||
alt={`activity weather (${activity.weather_start.icon})`}
|
||||
title={activity.weather_start.summary}
|
||||
src={`/img/weather/${workout.weather_start.icon}.png`}
|
||||
alt={`workout weather (${workout.weather_start.icon})`}
|
||||
title={workout.weather_start.summary}
|
||||
/>
|
||||
</th>
|
||||
<th>
|
||||
{t('activities:End')}
|
||||
{t('workouts:End')}
|
||||
<br />
|
||||
<img
|
||||
className="weather-img"
|
||||
src={`/img/weather/${activity.weather_end.icon}.png`}
|
||||
alt={`activity weather (${activity.weather_end.icon})`}
|
||||
title={activity.weather_end.summary}
|
||||
src={`/img/weather/${workout.weather_end.icon}.png`}
|
||||
alt={`workout weather (${workout.weather_end.icon})`}
|
||||
title={workout.weather_end.summary}
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
@ -40,8 +40,8 @@ export default function ActivityWeather(props) {
|
||||
alt="Temperatures"
|
||||
/>
|
||||
</td>
|
||||
<td>{Number(activity.weather_start.temperature).toFixed(1)}°C</td>
|
||||
<td>{Number(activity.weather_end.temperature).toFixed(1)}°C</td>
|
||||
<td>{Number(workout.weather_start.temperature).toFixed(1)}°C</td>
|
||||
<td>{Number(workout.weather_end.temperature).toFixed(1)}°C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
@ -52,9 +52,9 @@ export default function ActivityWeather(props) {
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{Number(activity.weather_start.humidity * 100).toFixed(1)}%
|
||||
{Number(workout.weather_start.humidity * 100).toFixed(1)}%
|
||||
</td>
|
||||
<td>{Number(activity.weather_end.humidity * 100).toFixed(1)}%</td>
|
||||
<td>{Number(workout.weather_end.humidity * 100).toFixed(1)}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
@ -64,8 +64,8 @@ export default function ActivityWeather(props) {
|
||||
alt="Temperatures"
|
||||
/>
|
||||
</td>
|
||||
<td>{Number(activity.weather_start.wind).toFixed(1)}m/s</td>
|
||||
<td>{Number(activity.weather_end.wind).toFixed(1)}m/s</td>
|
||||
<td>{Number(workout.weather_start.wind).toFixed(1)}m/s</td>
|
||||
<td>{Number(workout.weather_end.wind).toFixed(1)}m/s</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -3,19 +3,19 @@ import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import ActivityCardHeader from './ActivityCardHeader'
|
||||
import ActivityCharts from './ActivityCharts'
|
||||
import ActivityDetails from './ActivityDetails'
|
||||
import ActivityMap from './ActivityMap'
|
||||
import ActivityNoMap from './ActivityNoMap'
|
||||
import ActivityNotes from './ActivityNotes'
|
||||
import ActivitySegments from './ActivitySegments'
|
||||
import CustomModal from '../../Common/CustomModal'
|
||||
import Message from '../../Common/Message'
|
||||
import WorkoutCardHeader from './WorkoutCardHeader'
|
||||
import WorkoutCharts from './WorkoutCharts'
|
||||
import WorkoutDetails from './WorkoutDetails'
|
||||
import WorkoutMap from './WorkoutMap'
|
||||
import WorkoutNoMap from './WorkoutNoMap'
|
||||
import WorkoutNotes from './WorkoutNotes'
|
||||
import WorkoutSegments from './WorkoutSegments'
|
||||
import { getOrUpdateData } from '../../../actions'
|
||||
import { deleteActivity } from '../../../actions/activities'
|
||||
import { deleteWorkout } from '../../../actions/workouts'
|
||||
|
||||
class ActivityDisplay extends React.Component {
|
||||
class WorkoutDisplay extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
@ -28,14 +28,14 @@ class ActivityDisplay extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadActivity(this.props.match.params.activityId)
|
||||
this.props.loadWorkout(this.props.match.params.workoutId)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (
|
||||
prevProps.match.params.activityId !== this.props.match.params.activityId
|
||||
prevProps.match.params.workoutId !== this.props.match.params.workoutId
|
||||
) {
|
||||
this.props.loadActivity(this.props.match.params.activityId)
|
||||
this.props.loadWorkout(this.props.match.params.workoutId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,24 +64,15 @@ class ActivityDisplay extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
activities,
|
||||
message,
|
||||
onDeleteActivity,
|
||||
sports,
|
||||
t,
|
||||
user,
|
||||
} = this.props
|
||||
const { message, onDeleteWorkout, sports, t, user, workouts } = this.props
|
||||
const { coordinates, displayModal } = this.state
|
||||
const [activity] = activities
|
||||
const title = activity ? activity.title : t('activities:Activity')
|
||||
const [sport] = activity
|
||||
? sports.filter(s => s.id === activity.sport_id)
|
||||
: []
|
||||
const [workout] = workouts
|
||||
const title = workout ? workout.title : t('workouts:Workout')
|
||||
const [sport] = workout ? sports.filter(s => s.id === workout.sport_id) : []
|
||||
const segmentId = parseInt(this.props.match.params.segmentId)
|
||||
const dataType = segmentId >= 0 ? 'segment' : 'activity'
|
||||
const dataType = segmentId >= 0 ? 'segment' : 'workout'
|
||||
return (
|
||||
<div className="activity-page">
|
||||
<div className="workout-page">
|
||||
<Helmet>
|
||||
<title>FitTrackee - {title}</title>
|
||||
</Helmet>
|
||||
@ -93,23 +84,23 @@ class ActivityDisplay extends React.Component {
|
||||
<CustomModal
|
||||
title={t('common:Confirmation')}
|
||||
text={t(
|
||||
'activities:Are you sure you want to delete this activity?'
|
||||
'workouts:Are you sure you want to delete this workout?'
|
||||
)}
|
||||
confirm={() => {
|
||||
onDeleteActivity(activity.id)
|
||||
onDeleteWorkout(workout.id)
|
||||
this.displayModal(false)
|
||||
}}
|
||||
close={() => this.displayModal(false)}
|
||||
/>
|
||||
)}
|
||||
{activity && sport && activities.length === 1 && (
|
||||
{workout && sport && workouts.length === 1 && (
|
||||
<div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-header">
|
||||
<ActivityCardHeader
|
||||
activity={activity}
|
||||
<WorkoutCardHeader
|
||||
workout={workout}
|
||||
dataType={dataType}
|
||||
segmentId={segmentId}
|
||||
sport={sport}
|
||||
@ -122,23 +113,23 @@ class ActivityDisplay extends React.Component {
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col-md-8">
|
||||
{activity.with_gpx ? (
|
||||
<ActivityMap
|
||||
activity={activity}
|
||||
{workout.with_gpx ? (
|
||||
<WorkoutMap
|
||||
workout={workout}
|
||||
coordinates={coordinates}
|
||||
dataType={dataType}
|
||||
segmentId={segmentId}
|
||||
/>
|
||||
) : (
|
||||
<ActivityNoMap t={t} />
|
||||
<WorkoutNoMap t={t} />
|
||||
)}
|
||||
</div>
|
||||
<div className="col">
|
||||
<ActivityDetails
|
||||
activity={
|
||||
dataType === 'activity'
|
||||
? activity
|
||||
: activity.segments[segmentId - 1]
|
||||
<WorkoutDetails
|
||||
workout={
|
||||
dataType === 'workout'
|
||||
? workout
|
||||
: workout.segments[segmentId - 1]
|
||||
}
|
||||
t={t}
|
||||
/>
|
||||
@ -148,18 +139,18 @@ class ActivityDisplay extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{activity.with_gpx && (
|
||||
{workout.with_gpx && (
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="chart-title">
|
||||
{t('activities:Chart')}
|
||||
{t('workouts:Chart')}
|
||||
</div>
|
||||
<ActivityCharts
|
||||
activity={activity}
|
||||
<WorkoutCharts
|
||||
workout={workout}
|
||||
dataType={dataType}
|
||||
segmentId={segmentId}
|
||||
t={t}
|
||||
@ -174,11 +165,11 @@ class ActivityDisplay extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{dataType === 'activity' && (
|
||||
{dataType === 'workout' && (
|
||||
<>
|
||||
<ActivityNotes notes={activity.notes} t={t} />
|
||||
{activity.segments.length > 1 && (
|
||||
<ActivitySegments segments={activity.segments} t={t} />
|
||||
<WorkoutNotes notes={workout.notes} t={t} />
|
||||
{workout.segments.length > 1 && (
|
||||
<WorkoutSegments segments={workout.segments} t={t} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
@ -194,18 +185,18 @@ class ActivityDisplay extends React.Component {
|
||||
export default withTranslation()(
|
||||
connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
workouts: state.workouts.data,
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivity: activityId => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', { id: activityId }))
|
||||
loadWorkout: workoutId => {
|
||||
dispatch(getOrUpdateData('getData', 'workouts', { id: workoutId }))
|
||||
},
|
||||
onDeleteActivity: activityId => {
|
||||
dispatch(deleteActivity(activityId))
|
||||
onDeleteWorkout: workoutId => {
|
||||
dispatch(deleteWorkout(workoutId))
|
||||
},
|
||||
})
|
||||
)(ActivityDisplay)
|
||||
)(WorkoutDisplay)
|
||||
)
|
41
fittrackee_client/src/components/Workout/WorkoutEdit.jsx
Normal file
41
fittrackee_client/src/components/Workout/WorkoutEdit.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import WorkoutAddOrEdit from './WorkoutAddOrEdit'
|
||||
import { getOrUpdateData } from '../../actions'
|
||||
|
||||
class WorkoutEdit extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadWorkout(this.props.match.params.workoutId)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, sports, workouts } = this.props
|
||||
const [workout] = workouts
|
||||
return (
|
||||
<div>
|
||||
{sports.length > 0 && (
|
||||
<WorkoutAddOrEdit
|
||||
workout={workout}
|
||||
message={message}
|
||||
sports={sports}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
workouts: state.workouts.data,
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadWorkout: workoutId => {
|
||||
dispatch(getOrUpdateData('getData', 'workouts', { id: workoutId }))
|
||||
},
|
||||
})
|
||||
)(WorkoutEdit)
|
@ -3,26 +3,26 @@ import { Trans } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { setLoading } from '../../../actions/index'
|
||||
import { addActivity, editActivity } from '../../../actions/activities'
|
||||
import { addWorkout, editWorkout } from '../../../actions/workouts'
|
||||
import { history } from '../../../index'
|
||||
import { getFileSize } from '../../../utils'
|
||||
import { translateSports } from '../../../utils/activities'
|
||||
import { translateSports } from '../../../utils/workouts'
|
||||
|
||||
function FormWithGpx(props) {
|
||||
const {
|
||||
activity,
|
||||
appConfig,
|
||||
loading,
|
||||
onAddActivity,
|
||||
onEditActivity,
|
||||
onAddWorkout,
|
||||
onEditWorkout,
|
||||
sports,
|
||||
t,
|
||||
workout,
|
||||
} = props
|
||||
const sportId = activity ? activity.sport_id : ''
|
||||
const sportId = workout ? workout.sport_id : ''
|
||||
const translatedSports = translateSports(sports, t, true)
|
||||
const zipTooltip = `${t('activities:no folder inside')}, ${
|
||||
const zipTooltip = `${t('workouts:no folder inside')}, ${
|
||||
appConfig.gpx_limit_import
|
||||
} ${t('activities:files max')}, ${t('activities:max size')}: ${getFileSize(
|
||||
} ${t('workouts:files max')}, ${t('workouts:max size')}: ${getFileSize(
|
||||
appConfig.max_zip_file_size
|
||||
)}`
|
||||
const fileSizeLimit = getFileSize(appConfig.max_single_file_size)
|
||||
@ -51,13 +51,13 @@ function FormWithGpx(props) {
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
{activity ? (
|
||||
{workout ? (
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Title')}:
|
||||
{t('workouts:Title')}:
|
||||
<input
|
||||
name="title"
|
||||
defaultValue={activity ? activity.title : ''}
|
||||
defaultValue={workout ? workout.title : ''}
|
||||
disabled={loading}
|
||||
className="form-control input-lg"
|
||||
/>
|
||||
@ -66,7 +66,7 @@ function FormWithGpx(props) {
|
||||
) : (
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans i18nKey="activities:gpxFile">
|
||||
<Trans i18nKey="workouts:gpxFile">
|
||||
<strong>gpx</strong> file
|
||||
</Trans>
|
||||
<sup>
|
||||
@ -74,10 +74,10 @@ function FormWithGpx(props) {
|
||||
className="fa fa-question-circle"
|
||||
aria-hidden="true"
|
||||
data-toggle="tooltip"
|
||||
title={`${t('activities:max size')}: ${fileSizeLimit}`}
|
||||
title={`${t('workouts:max size')}: ${fileSizeLimit}`}
|
||||
/>
|
||||
</sup>{' '}
|
||||
<Trans i18nKey="activities:zipFile">
|
||||
<Trans i18nKey="workouts:zipFile">
|
||||
or <strong> zip</strong> file containing <strong>gpx </strong>
|
||||
files
|
||||
</Trans>
|
||||
@ -104,10 +104,10 @@ function FormWithGpx(props) {
|
||||
)}
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Notes')}:
|
||||
{t('workouts:Notes')}:
|
||||
<textarea
|
||||
name="notes"
|
||||
defaultValue={activity ? activity.notes : ''}
|
||||
defaultValue={workout ? workout.notes : ''}
|
||||
disabled={loading}
|
||||
className="form-control input-lg"
|
||||
maxLength="500"
|
||||
@ -122,7 +122,7 @@ function FormWithGpx(props) {
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
onClick={event =>
|
||||
activity ? onEditActivity(event, activity) : onAddActivity(event)
|
||||
workout ? onEditWorkout(event, workout) : onAddWorkout(event)
|
||||
}
|
||||
value={t('common:Submit')}
|
||||
/>
|
||||
@ -144,7 +144,7 @@ export default connect(
|
||||
loading: state.loading,
|
||||
}),
|
||||
dispatch => ({
|
||||
onAddActivity: e => {
|
||||
onAddWorkout: e => {
|
||||
dispatch(setLoading(true))
|
||||
const form = new FormData()
|
||||
form.append('file', e.target.form.gpxFile.files[0])
|
||||
@ -154,12 +154,12 @@ export default connect(
|
||||
`{"sport_id": ${e.target.form.sport.value
|
||||
}, "notes": "${e.target.form.notes.value}"}`
|
||||
)
|
||||
dispatch(addActivity(form))
|
||||
dispatch(addWorkout(form))
|
||||
},
|
||||
onEditActivity: (e, activity) => {
|
||||
onEditWorkout: (e, workout) => {
|
||||
dispatch(
|
||||
editActivity({
|
||||
id: activity.id,
|
||||
editWorkout({
|
||||
id: workout.id,
|
||||
notes: e.target.form.notes.value,
|
||||
sport_id: +e.target.form.sport.value,
|
||||
title: e.target.form.title.value,
|
@ -1,38 +1,35 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import {
|
||||
addActivityWithoutGpx,
|
||||
editActivity,
|
||||
} from '../../../actions/activities'
|
||||
import { addWorkoutWithoutGpx, editWorkout } from '../../../actions/workouts'
|
||||
import { history } from '../../../index'
|
||||
import { getDateWithTZ } from '../../../utils'
|
||||
import { formatActivityDate, translateSports } from '../../../utils/activities'
|
||||
import { formatWorkoutDate, translateSports } from '../../../utils/workouts'
|
||||
|
||||
function FormWithoutGpx(props) {
|
||||
const { activity, onAddOrEdit, sports, t, user } = props
|
||||
const { onAddOrEdit, sports, t, user, workout } = props
|
||||
const translatedSports = translateSports(sports, t, true)
|
||||
let activityDate,
|
||||
activityTime,
|
||||
let workoutDate,
|
||||
workoutTime,
|
||||
sportId = ''
|
||||
if (activity) {
|
||||
const activityDateTime = formatActivityDate(
|
||||
getDateWithTZ(activity.activity_date, user.timezone),
|
||||
if (workout) {
|
||||
const workoutDateTime = formatWorkoutDate(
|
||||
getDateWithTZ(workout.workout_date, user.timezone),
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
activityDate = activityDateTime.activity_date
|
||||
activityTime = activityDateTime.activity_time
|
||||
sportId = activity.sport_id
|
||||
workoutDate = workoutDateTime.workout_date
|
||||
workoutTime = workoutDateTime.workout_time
|
||||
sportId = workout.sport_id
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={event => event.preventDefault()}>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Title')}:
|
||||
{t('workouts:Title')}:
|
||||
<input
|
||||
name="title"
|
||||
defaultValue={activity ? activity.title : ''}
|
||||
defaultValue={workout ? workout.title : ''}
|
||||
className="form-control input-lg"
|
||||
/>
|
||||
</label>
|
||||
@ -57,19 +54,19 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Activity Date')}:
|
||||
{t('workouts:Workout Date')}:
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<input
|
||||
name="activity_date"
|
||||
defaultValue={activityDate}
|
||||
name="workout_date"
|
||||
defaultValue={workoutDate}
|
||||
className="form-control col-md"
|
||||
required
|
||||
type="date"
|
||||
/>
|
||||
<input
|
||||
name="activity_time"
|
||||
defaultValue={activityTime}
|
||||
name="workout_time"
|
||||
defaultValue={workoutTime}
|
||||
className="form-control col-md"
|
||||
required
|
||||
type="time"
|
||||
@ -80,10 +77,10 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Duration')}:
|
||||
{t('workouts:Duration')}:
|
||||
<input
|
||||
name="duration"
|
||||
defaultValue={activity ? activity.duration : ''}
|
||||
defaultValue={workout ? workout.duration : ''}
|
||||
className="form-control col-xs-4"
|
||||
pattern="^([0-9]*[0-9]):([0-5][0-9]):([0-5][0-9])$"
|
||||
placeholder="hh:mm:ss"
|
||||
@ -94,10 +91,10 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Distance')} (km):
|
||||
{t('workouts:Distance')} (km):
|
||||
<input
|
||||
name="distance"
|
||||
defaultValue={activity ? activity.distance : ''}
|
||||
defaultValue={workout ? workout.distance : ''}
|
||||
className="form-control input-lg"
|
||||
min={0}
|
||||
required
|
||||
@ -108,10 +105,10 @@ function FormWithoutGpx(props) {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Notes')}:
|
||||
{t('workouts:Notes')}:
|
||||
<textarea
|
||||
name="notes"
|
||||
defaultValue={activity ? activity.notes : ''}
|
||||
defaultValue={workout ? workout.notes : ''}
|
||||
className="form-control input-lg"
|
||||
maxLength="500"
|
||||
/>
|
||||
@ -120,7 +117,7 @@ function FormWithoutGpx(props) {
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
onClick={event => onAddOrEdit(event, activity)}
|
||||
onClick={event => onAddOrEdit(event, workout)}
|
||||
value={t('common:Submit')}
|
||||
/>
|
||||
<input
|
||||
@ -138,27 +135,27 @@ export default connect(
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
onAddOrEdit: (e, activity) => {
|
||||
onAddOrEdit: (e, workout) => {
|
||||
const d = e.target.form.duration.value.split(':')
|
||||
const duration = +d[0] * 60 * 60 + +d[1] * 60 + +d[2]
|
||||
|
||||
/* prettier-ignore */
|
||||
const activityDate = `${e.target.form.activity_date.value
|
||||
} ${ e.target.form.activity_time.value}`
|
||||
const workoutDate = `${e.target.form.workout_date.value
|
||||
} ${ e.target.form.workout_time.value}`
|
||||
|
||||
const data = {
|
||||
activity_date: activityDate,
|
||||
workout_date: workoutDate,
|
||||
distance: +e.target.form.distance.value,
|
||||
duration,
|
||||
notes: e.target.form.notes.value,
|
||||
sport_id: +e.target.form.sport_id.value,
|
||||
title: e.target.form.title.value,
|
||||
}
|
||||
if (activity) {
|
||||
data.id = activity.id
|
||||
dispatch(editActivity(data))
|
||||
if (workout) {
|
||||
data.id = workout.id
|
||||
dispatch(editWorkout(data))
|
||||
} else {
|
||||
dispatch(addActivityWithoutGpx(data))
|
||||
dispatch(addWorkoutWithoutGpx(data))
|
||||
}
|
||||
},
|
||||
})
|
38
fittrackee_client/src/components/Workout/index.jsx
Normal file
38
fittrackee_client/src/components/Workout/index.jsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Redirect, Route, Switch } from 'react-router-dom'
|
||||
|
||||
import NotFound from './../Others/NotFound'
|
||||
import WorkoutAdd from './WorkoutAdd'
|
||||
import WorkoutDisplay from './WorkoutDisplay'
|
||||
import WorkoutEdit from './WorkoutEdit'
|
||||
import { isLoggedIn } from '../../utils'
|
||||
|
||||
function Workout() {
|
||||
return (
|
||||
<div>
|
||||
{isLoggedIn() ? (
|
||||
<Switch>
|
||||
<Route exact path="/workouts/add" component={WorkoutAdd} />
|
||||
<Route exact path="/workouts/:workoutId" component={WorkoutDisplay} />
|
||||
<Route
|
||||
exact
|
||||
path="/workouts/:workoutId/edit"
|
||||
component={WorkoutEdit}
|
||||
/>
|
||||
<Route
|
||||
path="/workouts/:workoutId/segment/:segmentId"
|
||||
component={WorkoutDisplay}
|
||||
/>
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
) : (
|
||||
<Redirect to="/login" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
user: state.user,
|
||||
}))(Workout)
|
@ -1,18 +1,18 @@
|
||||
import React from 'react'
|
||||
|
||||
import { translateSports } from '../../utils/activities'
|
||||
import { translateSports } from '../../utils/workouts'
|
||||
|
||||
export default class ActivitiesFilter extends React.PureComponent {
|
||||
export default class WorkoutsFilter extends React.PureComponent {
|
||||
render() {
|
||||
const { loadActivities, sports, t, updateParams } = this.props
|
||||
const { loadWorkouts, sports, t, updateParams } = this.props
|
||||
const translatedSports = translateSports(sports, t)
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-body activity-filter">
|
||||
<div className="card-body workout-filter">
|
||||
<form onSubmit={event => event.preventDefault()}>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:From')}:
|
||||
{t('workouts:From')}:
|
||||
<input
|
||||
className="form-control col-md"
|
||||
name="from"
|
||||
@ -21,7 +21,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
{t('activities:To')}:
|
||||
{t('workouts:To')}:
|
||||
<input
|
||||
className="form-control col-md"
|
||||
name="to"
|
||||
@ -49,7 +49,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Distance')} (km):
|
||||
{t('workouts:Distance')} (km):
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -81,7 +81,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Duration')}:
|
||||
{t('workouts:Duration')}:
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -113,7 +113,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Average speed')} (km/h):
|
||||
{t('workouts:Average speed')} (km/h):
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -145,7 +145,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
{t('activities:Max. speed')} (km/h):
|
||||
{t('workouts:Max. speed')} (km/h):
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-5">
|
||||
@ -177,9 +177,9 @@ export default class ActivitiesFilter extends React.PureComponent {
|
||||
</div>
|
||||
<input
|
||||
className="btn btn-primary btn-lg btn-block"
|
||||
onClick={() => loadActivities()}
|
||||
onClick={() => loadWorkouts()}
|
||||
type="submit"
|
||||
value={t('activities:Filter')}
|
||||
value={t('workouts:Filter')}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
@ -5,28 +5,28 @@ import { Link } from 'react-router-dom'
|
||||
import StaticMap from '../Common/StaticMap'
|
||||
import { getDateWithTZ } from '../../utils'
|
||||
|
||||
export default class ActivitiesList extends React.PureComponent {
|
||||
export default class WorkoutsList extends React.PureComponent {
|
||||
render() {
|
||||
const { activities, loading, sports, t, user } = this.props
|
||||
const { loading, sports, t, user, workouts } = this.props
|
||||
return (
|
||||
<div className="card activity-card">
|
||||
<div className="card workout-card">
|
||||
<div className="card-body">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" />
|
||||
<th scope="col">{t('common:Workout')}</th>
|
||||
<th scope="col">{t('activities:Date')}</th>
|
||||
<th scope="col">{t('activities:Distance')}</th>
|
||||
<th scope="col">{t('activities:Duration')}</th>
|
||||
<th scope="col">{t('activities:Ave. speed')}</th>
|
||||
<th scope="col">{t('activities:Max. speed')}</th>
|
||||
<th scope="col">{t('workouts:Date')}</th>
|
||||
<th scope="col">{t('workouts:Distance')}</th>
|
||||
<th scope="col">{t('workouts:Duration')}</th>
|
||||
<th scope="col">{t('workouts:Ave. speed')}</th>
|
||||
<th scope="col">{t('workouts:Max. speed')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!loading &&
|
||||
sports &&
|
||||
activities.map((activity, idx) => (
|
||||
workouts.map((workout, idx) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<tr key={idx}>
|
||||
<td>
|
||||
@ -34,56 +34,56 @@ export default class ActivitiesList extends React.PureComponent {
|
||||
{t('common:Sport')}
|
||||
</span>
|
||||
<img
|
||||
className="activity-sport"
|
||||
className="workout-sport"
|
||||
src={sports
|
||||
.filter(s => s.id === activity.sport_id)
|
||||
.filter(s => s.id === workout.sport_id)
|
||||
.map(s => s.img)}
|
||||
alt="activity sport logo"
|
||||
alt="workout sport logo"
|
||||
/>
|
||||
</td>
|
||||
<td className="activity-title">
|
||||
<td className="workout-title">
|
||||
<span className="heading-span-absolute">
|
||||
{t('common:Workout')}
|
||||
</span>
|
||||
<Link to={`/activities/${activity.id}`}>
|
||||
{activity.title}
|
||||
<Link to={`/workouts/${workout.id}`}>
|
||||
{workout.title}
|
||||
</Link>
|
||||
{activity.map && (
|
||||
<StaticMap activity={activity} display="list" />
|
||||
{workout.map && (
|
||||
<StaticMap workout={workout} display="list" />
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<span className="heading-span-absolute">
|
||||
{t('activities:Date')}
|
||||
{t('workouts:Date')}
|
||||
</span>
|
||||
{format(
|
||||
getDateWithTZ(activity.activity_date, user.timezone),
|
||||
getDateWithTZ(workout.workout_date, user.timezone),
|
||||
'dd/MM/yyyy HH:mm'
|
||||
)}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="heading-span-absolute">
|
||||
{t('activities:Distance')}
|
||||
{t('workouts:Distance')}
|
||||
</span>
|
||||
{Number(activity.distance).toFixed(2)} km
|
||||
{Number(workout.distance).toFixed(2)} km
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="heading-span-absolute">
|
||||
{t('activities:Duration')}
|
||||
{t('workouts:Duration')}
|
||||
</span>
|
||||
{activity.moving}
|
||||
{workout.moving}
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="heading-span-absolute">
|
||||
{t('activities:Ave. speed')}
|
||||
{t('workouts:Ave. speed')}
|
||||
</span>
|
||||
{activity.ave_speed} km/h
|
||||
{workout.ave_speed} km/h
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<span className="heading-span-absolute">
|
||||
{t('activities:Max. speed')}
|
||||
{t('workouts:Max. speed')}
|
||||
</span>
|
||||
{activity.max_speed} km/h
|
||||
{workout.max_speed} km/h
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
@ -3,14 +3,14 @@ import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import ActivitiesFilter from './ActivitiesFilter'
|
||||
import ActivitiesList from './ActivitiesList'
|
||||
import Message from '../Common/Message'
|
||||
import NoActivities from '../Common/NoActivities'
|
||||
import NoWorkouts from '../Common/NoWorkouts'
|
||||
import WorkoutsFilter from './WorkoutsFilter'
|
||||
import WorkoutsList from './WorkoutsList'
|
||||
import { getOrUpdateData } from '../../actions'
|
||||
import { getMoreActivities } from '../../actions/activities'
|
||||
import { getMoreWorkouts } from '../../actions/workouts'
|
||||
|
||||
class Activities extends React.Component {
|
||||
class Workouts extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = {
|
||||
@ -22,7 +22,7 @@ class Activities extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.loadActivities(this.state.params)
|
||||
this.props.loadWorkouts(this.state.params)
|
||||
}
|
||||
|
||||
setParams(e) {
|
||||
@ -37,19 +37,19 @@ class Activities extends React.Component {
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
activities,
|
||||
loading,
|
||||
loadActivities,
|
||||
loadMoreActivities,
|
||||
loadWorkouts,
|
||||
loadMoreWorkouts,
|
||||
message,
|
||||
sports,
|
||||
t,
|
||||
user,
|
||||
workouts,
|
||||
} = this.props
|
||||
const { params } = this.state
|
||||
const paginationEnd =
|
||||
activities.length > 0
|
||||
? activities[activities.length - 1].previous_activity === null
|
||||
workouts.length > 0
|
||||
? workouts[workouts.length - 1].previous_workout === null
|
||||
: true
|
||||
return (
|
||||
<div>
|
||||
@ -62,16 +62,16 @@ class Activities extends React.Component {
|
||||
<div className="container history">
|
||||
<div className="row">
|
||||
<div className="col-md-3">
|
||||
<ActivitiesFilter
|
||||
<WorkoutsFilter
|
||||
sports={sports}
|
||||
loadActivities={() => loadActivities(params)}
|
||||
loadWorkouts={() => loadWorkouts(params)}
|
||||
t={t}
|
||||
updateParams={e => this.setParams(e)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-9 activities-result">
|
||||
<ActivitiesList
|
||||
activities={activities}
|
||||
<div className="col-md-9 workouts-result">
|
||||
<WorkoutsList
|
||||
workouts={workouts}
|
||||
loading={loading}
|
||||
sports={sports}
|
||||
t={t}
|
||||
@ -81,15 +81,15 @@ class Activities extends React.Component {
|
||||
<input
|
||||
type="submit"
|
||||
className="btn btn-default btn-md btn-block"
|
||||
value="Load more activities"
|
||||
value="Load more workouts"
|
||||
onClick={() => {
|
||||
params.page += 1
|
||||
loadMoreActivities(params)
|
||||
loadMoreWorkouts(params)
|
||||
this.setState(params)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activities.length === 0 && <NoActivities t={t} />}
|
||||
{workouts.length === 0 && <NoWorkouts t={t} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -102,19 +102,19 @@ class Activities extends React.Component {
|
||||
export default withTranslation()(
|
||||
connect(
|
||||
state => ({
|
||||
activities: state.activities.data,
|
||||
workouts: state.workouts.data,
|
||||
loading: state.loading,
|
||||
message: state.message,
|
||||
sports: state.sports.data,
|
||||
user: state.user,
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivities: params => {
|
||||
dispatch(getOrUpdateData('getData', 'activities', params))
|
||||
loadWorkouts: params => {
|
||||
dispatch(getOrUpdateData('getData', 'workouts', params))
|
||||
},
|
||||
loadMoreActivities: params => {
|
||||
dispatch(getMoreActivities(params))
|
||||
loadMoreWorkouts: params => {
|
||||
dispatch(getMoreWorkouts(params))
|
||||
},
|
||||
})
|
||||
)(Activities)
|
||||
)(Workouts)
|
||||
)
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"Actions": "Actions",
|
||||
"Active": "Active",
|
||||
"activities exist": "activities exist",
|
||||
"workouts exist": "workouts exist",
|
||||
"Add admin rights": "Add admin rights",
|
||||
"Add/remove admin rights, delete user account.": "Add/remove admin rights, delete user account.",
|
||||
"Administration": "Administration",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"activities count": "activities count",
|
||||
"workouts count": "workouts count",
|
||||
"Add workout": "Add workout",
|
||||
"admin rights": "admin rights",
|
||||
"ascending": "ascending",
|
||||
|
@ -2,7 +2,7 @@
|
||||
"3 to 12 characters required for username.": "3 to 12 characters required for username.",
|
||||
"8 characters required for password.": "8 characters required for password.",
|
||||
"An error occurred. Please contact the administrator.": "An error occurred. Please contact the administrator.",
|
||||
"activities": "activities",
|
||||
"workouts": "workouts",
|
||||
"Error during picture deletion.": "Error during picture deletion.",
|
||||
"Error during picture update.": "Error during picture update.",
|
||||
"Error during picture update, file size exceeds max size.": "Error during picture update, file size exceeds max size.",
|
||||
@ -18,13 +18,13 @@
|
||||
"No picture.": "No picture.",
|
||||
"No selected file.": "No selected file.",
|
||||
"no correct file.": "no correct file.",
|
||||
"no gpx file for this activity": "no gpx file for this activity",
|
||||
"no gpx file for this workout": "no gpx file for this workout",
|
||||
"Password and password confirmation don't match.": "Password and password confirmation don't match.",
|
||||
"Provide a valid auth token": "Provide a valid auth token",
|
||||
"records": "records",
|
||||
"Signature expired. Please log in again.": "Signature expired. Please log in again.",
|
||||
"Sorry. That user already exists.": "Sorry. That user already exists.",
|
||||
"Sport can not be disabled, activities exist." : "Sport can not be disabled, activities exist.",
|
||||
"Sport can not be disabled, workouts exist." : "Sport can not be disabled, workouts exist.",
|
||||
"Sport does not exist.": "Sport does not exist.",
|
||||
"sports": "sports",
|
||||
"statistics": "statistiques",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import EnActivitiesTranslations from './activities.json'
|
||||
import EnWorkoutsTranslations from './workouts.json'
|
||||
import EnAdministrationTranslations from './administration.json'
|
||||
import EnCommonTranslations from './common.json'
|
||||
import EnDashboardTranslations from './dashboard.json'
|
||||
@ -8,7 +8,7 @@ import EnStatisticsTranslations from './statistics.json'
|
||||
import EnUserTranslations from './user.json'
|
||||
|
||||
export const enResources = {
|
||||
activities: EnActivitiesTranslations,
|
||||
workouts: EnWorkoutsTranslations,
|
||||
administration: EnAdministrationTranslations,
|
||||
common: EnCommonTranslations,
|
||||
dashboard: EnDashboardTranslations,
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"activities": "activities",
|
||||
"workouts": "workouts",
|
||||
"distance": "distance",
|
||||
"duration": "duration",
|
||||
"month": "month",
|
||||
|
@ -1,23 +1,23 @@
|
||||
{
|
||||
"Activities": "Activities",
|
||||
"Activity": "Activity",
|
||||
"Activity Date": "Activity Date",
|
||||
"Workouts": "Workouts",
|
||||
"Workout": "Workout",
|
||||
"Workout Date": "Workout Date",
|
||||
"Add a workout": "Add a workout",
|
||||
"Are you sure you want to delete this activity?": "Are you sure you want to delete this activity?",
|
||||
"Are you sure you want to delete this workout?": "Are you sure you want to delete this workout?",
|
||||
"Ave. speed": "Ave. speed",
|
||||
"Ascent": "Ascent",
|
||||
"Average speed": "Average speed",
|
||||
"Chart": "Chart",
|
||||
"data from gpx, without any cleaning": "data from gpx, without any cleaning",
|
||||
"Date": "Date",
|
||||
"Delete activity": "Delete activity",
|
||||
"Delete workout": "Delete workout",
|
||||
"Descent": "Descent",
|
||||
"Distance": "Distance",
|
||||
"distance": "distance",
|
||||
"Duration": "Duration",
|
||||
"duration": "duration",
|
||||
"Edit a workout": "Edit a workout",
|
||||
"Edit activity": "Edit activity",
|
||||
"Edit workout": "Edit workout",
|
||||
"elevation": "elevation",
|
||||
"End": "End",
|
||||
"Farest distance": "Farest distance",
|
||||
@ -33,17 +33,17 @@
|
||||
"max size": "max size",
|
||||
"No data to display": "No data to display",
|
||||
"No Map": "No Map",
|
||||
"No next activity": "No next activity",
|
||||
"No next workout": "No next workout",
|
||||
"No next segment": "No next segment",
|
||||
"No notes": "No notes",
|
||||
"No previous activity": "No previous activity",
|
||||
"No previous workout": "No previous workout",
|
||||
"No previous segment": "No previous segment",
|
||||
"Notes": "Notes",
|
||||
"pauses": "pauses",
|
||||
"Personal records": "Personal records",
|
||||
"See next activity": "See next activity",
|
||||
"See next workout": "See next workout",
|
||||
"See next segment": "See next segment",
|
||||
"See previous activity": "See previous activity",
|
||||
"See previous workout": "See previous workout",
|
||||
"See previous segment": "See previous segment",
|
||||
"segment": "segment",
|
||||
"Segments": "Segments",
|
@ -4,7 +4,7 @@
|
||||
"Add admin rights": "Ajouter des droits d'admin",
|
||||
"Add/remove admin rights, delete user account.": "Ajouter/retirer des droits d'adminsitration, supprimer des comptes utilisateurs.",
|
||||
"Administration": "Administration",
|
||||
"activities exist": "des activités existent",
|
||||
"workouts exist": "des séances existent",
|
||||
"Application": "Application",
|
||||
"Application configuration": "Configuration de l'application",
|
||||
"Back": "Retour",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"activities count": "nombre d'activités",
|
||||
"Add workout": "Ajouter une activité",
|
||||
"workouts count": "nombre d'séances",
|
||||
"Add workout": "Ajouter une séance",
|
||||
"admin rights": "droits d'admin",
|
||||
"ascending": "ascendant",
|
||||
"Back": "Revenir à la page précédente",
|
||||
@ -16,7 +16,7 @@
|
||||
"No": "Non",
|
||||
"no": "non",
|
||||
"No records.": "Pas de records.",
|
||||
"No workouts.": "Pas d'activités.",
|
||||
"No workouts.": "Pas d'séances.",
|
||||
"Page not found": "Page introuvable",
|
||||
"Previous": "Page précédente",
|
||||
"registration date": "date d'inscription",
|
||||
@ -30,10 +30,10 @@
|
||||
"Submit": "Valider",
|
||||
"to": "à",
|
||||
"user name": "utilisateur",
|
||||
"Workout": "Activité",
|
||||
"Workouts": "Activités",
|
||||
"workout": "activité",
|
||||
"workouts": "activités",
|
||||
"Workout": "Séance",
|
||||
"Workouts": "Séances",
|
||||
"workout": "séance",
|
||||
"workouts": "séances",
|
||||
"Yes": "Oui",
|
||||
"yes": "oui"
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"Personal records": "Mes records",
|
||||
"This month": "Ce mois",
|
||||
"Upload one !": "Ajoutez votre première activité !"
|
||||
"Upload one !": "Ajoutez votre première séance !"
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"3 to 12 characters required for username.": "3 à 12 caractères requis pour le nom.",
|
||||
"8 characters required for password.": "8 caractères minimum pour le mot de passe.",
|
||||
"An error occurred. Please contact the administrator.": "Une erreur s'est produite. Merci de contacter l'administrateur.",
|
||||
"activities": "activités",
|
||||
"workouts": "séances",
|
||||
"Error during picture deletion.": "Erreur lors de la suppression de l'image.",
|
||||
"Error during picture update.": "Erreur lors de la mise à jour de l'image.",
|
||||
"Error during picture update, file size exceeds max size.": "Erreur lors de la mise à jour de l'image, la taille du ficher dépasse la taille maximum autorisée",
|
||||
@ -18,13 +18,13 @@
|
||||
"No picture.": "Pas d'image.",
|
||||
"No selected file.": "Pas de fichier sélectionné.",
|
||||
"no correct file.": "fichier incorrect",
|
||||
"no gpx file for this activity": "pas de fichier gpx pour cette activité",
|
||||
"no gpx file for this workout": "pas de fichier gpx pour cette séance",
|
||||
"Password and password confirmation don't match.": "Les mots de passe saisis sont différents.",
|
||||
"Provide a valid auth token": "Merci de fournir un jeton valide",
|
||||
"records": "records",
|
||||
"Signature expired. Please log in again.": "Signature expirée. Merci de vous reconnecter.",
|
||||
"Sorry. That user already exists.": "Désolé. Cet utilisateur existe déjà.",
|
||||
"Sport can not be disabled, activities exist." : "Le sport ne peut être désactivé, des activitées existent",
|
||||
"Sport can not be disabled, workouts exist." : "Le sport ne peut être désactivé, des séancees existent",
|
||||
"Sport does not exist.": "Le sport n'existe pas.",
|
||||
"sports": "sports",
|
||||
"statistics": "statistics",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import FrActivitiesTranslations from './activities.json'
|
||||
import FrWorkoutsTranslations from './workouts.json'
|
||||
import FrAdministrationTranslations from './administration.json'
|
||||
import FrCommonTranslations from './common.json'
|
||||
import FrDashboardTranslations from './dashboard.json'
|
||||
@ -8,7 +8,7 @@ import FrStatisticsTranslations from './statistics.json'
|
||||
import FrUserTranslations from './user.json'
|
||||
|
||||
export const frResources = {
|
||||
activities: FrActivitiesTranslations,
|
||||
workouts: FrWorkoutsTranslations,
|
||||
administration: FrAdministrationTranslations,
|
||||
common: FrCommonTranslations,
|
||||
dashboard: FrDashboardTranslations,
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"activities": "activités",
|
||||
"workouts": "séances",
|
||||
"distance": "distance",
|
||||
"duration": "durée",
|
||||
"month": "mois",
|
||||
|
@ -1,23 +1,23 @@
|
||||
{
|
||||
"Activities": "Activités",
|
||||
"Activity": "Activité",
|
||||
"Activity Date": "Date de l'activité",
|
||||
"Add a workout": "Ajouter une activité",
|
||||
"Are you sure you want to delete this activity?": "Etes-vous sûr de vouloir supprimer cette activité ?",
|
||||
"Workouts": "Séances",
|
||||
"Workout": "Séance",
|
||||
"Workout Date": "Date de l'séance",
|
||||
"Add a workout": "Ajouter une séance",
|
||||
"Are you sure you want to delete this workout?": "Etes-vous sûr de vouloir supprimer cette séance ?",
|
||||
"Ave. speed": "Vitesse moyenne",
|
||||
"Ascent": "Dénivelé positif",
|
||||
"Average speed": "Vitesse moyenne",
|
||||
"Chart": "Analyse",
|
||||
"data from gpx, without any cleaning": "données issues du fichier gpx, sans correction",
|
||||
"Date": "Date",
|
||||
"Delete activity": "Supprimer l'activité",
|
||||
"Delete workout": "Supprimer l'séance",
|
||||
"Descent": "Dénivelé négatif",
|
||||
"Distance": "Distance",
|
||||
"distance": "distance",
|
||||
"Duration": "Durée",
|
||||
"duration": "durée",
|
||||
"Edit a workout": "Editer une activité",
|
||||
"Edit activity": "Editer une activity",
|
||||
"Edit a workout": "Editer une séance",
|
||||
"Edit workout": "Editer une workout",
|
||||
"elevation": "altitude",
|
||||
"End": "Arrivée",
|
||||
"Farest distance": "Distance la + longue",
|
||||
@ -33,17 +33,17 @@
|
||||
"max size": "taille max",
|
||||
"No data to display": "Pas de données à afficher",
|
||||
"No Map": "Pas de carte",
|
||||
"No next activity": "Pas d'activité suivante",
|
||||
"No next workout": "Pas d'séance suivante",
|
||||
"No next segment": "Pas de segment suivant",
|
||||
"No notes": "Pas de notes",
|
||||
"No previous activity": "Pas d'activité précédente",
|
||||
"No previous workout": "Pas d'séance précédente",
|
||||
"No previous segment": "Pas de segment précédent",
|
||||
"Notes": "Notes",
|
||||
"pauses": "pauses",
|
||||
"Personal records": "Records personnels",
|
||||
"See next activity": "Voir l'activité suivante",
|
||||
"See next workout": "Voir l'séance suivante",
|
||||
"See next segment": "Voir le segment suivant",
|
||||
"See previous activity": "Voir l'activité précédente",
|
||||
"See previous workout": "Voir l'séance précédente",
|
||||
"See previous segment": "Voir le segment précédent",
|
||||
"segment": "segment",
|
||||
"Segments": "Segments",
|
@ -23,22 +23,22 @@ const handleDataAndError = (state, type, action) => {
|
||||
return state
|
||||
}
|
||||
|
||||
const activities = (state = initial.activities, action) => {
|
||||
const workouts = (state = initial.workouts, action) => {
|
||||
switch (action.type) {
|
||||
case 'LOGOUT':
|
||||
return initial.activities
|
||||
case 'PUSH_ACTIVITIES':
|
||||
return initial.workouts
|
||||
case 'PUSH_WORKOUTS':
|
||||
return {
|
||||
...state,
|
||||
data: state.data.concat(action.activities),
|
||||
data: state.data.concat(action.workouts),
|
||||
}
|
||||
case 'REMOVE_ACTIVITY':
|
||||
case 'REMOVE_WORKOUT':
|
||||
return {
|
||||
...state,
|
||||
data: state.data.filter(activity => activity.id !== action.activityId),
|
||||
data: state.data.filter(workout => workout.id !== action.workoutId),
|
||||
}
|
||||
default:
|
||||
return handleDataAndError(state, 'activities', action)
|
||||
return handleDataAndError(state, 'workouts', action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,17 +58,17 @@ const application = (state = initial.application, action) => {
|
||||
return state
|
||||
}
|
||||
|
||||
const calendarActivities = (state = initial.calendarActivities, action) => {
|
||||
const calendarWorkouts = (state = initial.calendarWorkouts, action) => {
|
||||
switch (action.type) {
|
||||
case 'LOGOUT':
|
||||
return initial.calendarActivities
|
||||
return initial.calendarWorkouts
|
||||
case 'UPDATE_CALENDAR':
|
||||
return {
|
||||
...state,
|
||||
data: action.activities,
|
||||
data: action.workouts,
|
||||
}
|
||||
default:
|
||||
return handleDataAndError(state, 'calendarActivities', action)
|
||||
return handleDataAndError(state, 'calendarWorkouts', action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,9 +191,9 @@ const statistics = (state = initial.statistics, action) => {
|
||||
|
||||
export default history =>
|
||||
combineReducers({
|
||||
activities,
|
||||
workouts,
|
||||
application,
|
||||
calendarActivities,
|
||||
calendarWorkouts,
|
||||
chartData,
|
||||
gpx,
|
||||
language,
|
||||
|
@ -9,7 +9,7 @@ export default {
|
||||
user: {
|
||||
isAuthenticated: false,
|
||||
},
|
||||
activities: {
|
||||
workouts: {
|
||||
...emptyData,
|
||||
},
|
||||
application: {
|
||||
@ -23,7 +23,7 @@ export default {
|
||||
registration: null,
|
||||
},
|
||||
},
|
||||
calendarActivities: {
|
||||
calendarWorkouts: {
|
||||
...emptyData,
|
||||
},
|
||||
chartData: [],
|
||||
|
@ -24,7 +24,7 @@ export const apiUrl =
|
||||
: `${process.env.REACT_APP_API_URL}/api/`
|
||||
|
||||
export const userFilters = [
|
||||
{ key: 'activities_count', label: 'activities count' },
|
||||
{ key: 'workouts_count', label: 'workouts count' },
|
||||
{ key: 'admin', label: 'admin rights' },
|
||||
{ key: 'created_at', label: 'registration date' },
|
||||
{ key: 'username', label: 'user name' },
|
||||
|
@ -66,7 +66,7 @@ const startDate = (duration, day, weekm) => {
|
||||
}
|
||||
|
||||
export const formatStats = (stats, sports, params, displayedSports, weekm) => {
|
||||
const nbActivitiesStats = []
|
||||
const nbWorkoutsStats = []
|
||||
const distanceStats = []
|
||||
const durationStats = []
|
||||
|
||||
@ -80,7 +80,7 @@ export const formatStats = (stats, sports, params, displayedSports, weekm) => {
|
||||
)
|
||||
const date = format(day, xAxisFormat.dateFormat)
|
||||
const xAxis = format(day, xAxisFormat.xAxis)
|
||||
const dataNbActivities = { date: xAxis }
|
||||
const dataNbWorkouts = { date: xAxis }
|
||||
const dataDistance = { date: xAxis }
|
||||
const dataDuration = { date: xAxis }
|
||||
|
||||
@ -91,19 +91,19 @@ export const formatStats = (stats, sports, params, displayedSports, weekm) => {
|
||||
)
|
||||
.map(sportId => {
|
||||
const sportLabel = sports.filter(s => s.id === +sportId)[0].label
|
||||
dataNbActivities[sportLabel] = stats[date][sportId].nb_activities
|
||||
dataNbWorkouts[sportLabel] = stats[date][sportId].nb_workouts
|
||||
dataDistance[sportLabel] = stats[date][sportId].total_distance
|
||||
dataDuration[sportLabel] = stats[date][sportId].total_duration
|
||||
return null
|
||||
})
|
||||
}
|
||||
nbActivitiesStats.push(dataNbActivities)
|
||||
nbWorkoutsStats.push(dataNbWorkouts)
|
||||
distanceStats.push(dataDistance)
|
||||
durationStats.push(dataDuration)
|
||||
}
|
||||
|
||||
return {
|
||||
activities: nbActivitiesStats,
|
||||
workouts: nbWorkoutsStats,
|
||||
distance: distanceStats,
|
||||
duration: durationStats,
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import togeojson from '@mapbox/togeojson'
|
||||
|
||||
import { getDateWithTZ } from './index'
|
||||
|
||||
export const activityColors = [
|
||||
export const workoutColors = [
|
||||
'#55a8a3',
|
||||
'#98C3A9',
|
||||
'#D0838A',
|
||||
@ -29,7 +29,7 @@ export const getGeoJson = gpxContent => {
|
||||
return { jsonData }
|
||||
}
|
||||
|
||||
export const formatActivityDate = (
|
||||
export const formatWorkoutDate = (
|
||||
dateTime,
|
||||
dateFormat = null,
|
||||
timeFormat = null
|
||||
@ -41,12 +41,12 @@ export const formatActivityDate = (
|
||||
timeFormat = 'HH:mm'
|
||||
}
|
||||
return {
|
||||
activity_date: dateTime ? format(dateTime, dateFormat) : null,
|
||||
activity_time: dateTime ? format(dateTime, timeFormat) : null,
|
||||
workout_date: dateTime ? format(dateTime, dateFormat) : null,
|
||||
workout_time: dateTime ? format(dateTime, timeFormat) : null,
|
||||
}
|
||||
}
|
||||
|
||||
export const formatActivityDuration = seconds => {
|
||||
export const formatWorkoutDuration = seconds => {
|
||||
let newDate = new Date(0)
|
||||
newDate = subHours(newDate.setSeconds(seconds), 1)
|
||||
return newDate.getTime()
|
||||
@ -55,7 +55,7 @@ export const formatActivityDuration = seconds => {
|
||||
export const formatChartData = chartData => {
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
chartData[i].time = new Date(chartData[i].time).getTime()
|
||||
chartData[i].duration = formatActivityDuration(chartData[i].duration)
|
||||
chartData[i].duration = formatWorkoutDuration(chartData[i].duration)
|
||||
}
|
||||
return chartData
|
||||
}
|
||||
@ -78,9 +78,9 @@ export const formatRecord = (record, tz) => {
|
||||
r => r.record_type === record.record_type
|
||||
)
|
||||
return {
|
||||
activity_date: formatActivityDate(getDateWithTZ(record.activity_date, tz))
|
||||
.activity_date,
|
||||
activity_id: record.activity_id,
|
||||
workout_date: formatWorkoutDate(getDateWithTZ(record.workout_date, tz))
|
||||
.workout_date,
|
||||
workout_id: record.workout_id,
|
||||
id: record.id,
|
||||
record_type: recordType.label,
|
||||
value: value,
|
Reference in New Issue
Block a user