Client - replace 'Activity' with 'Workout' - fix #58

This commit is contained in:
Sam
2021-01-10 11:39:48 +01:00
parent 3a80e01cc2
commit 5c04db6c08
75 changed files with 986 additions and 1006 deletions

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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">

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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} />

View File

@ -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 =

View File

@ -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))
)
},
})

View File

@ -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(

View File

@ -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

View File

@ -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>

View File

@ -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} />

View File

@ -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" />

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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>
</>
)

View File

@ -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)

View File

@ -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)
)

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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>
)
}

View File

@ -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)

View File

@ -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>
)
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)
)

View 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)

View File

@ -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,

View File

@ -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))
}
},
})

View 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)

View File

@ -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>

View File

@ -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>
))}

View File

@ -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)
)