Client - replace 'Activity' with 'Workout' - fix #58
This commit is contained in:
parent
3a80e01cc2
commit
5c04db6c08
@ -4,35 +4,35 @@ from selenium.webdriver.support.ui import Select, WebDriverWait
|
|||||||
from .utils import TEST_URL, register_valid_user
|
from .utils import TEST_URL, register_valid_user
|
||||||
|
|
||||||
|
|
||||||
class TestActivity:
|
class TestWorkout:
|
||||||
def test_user_can_add_activity_without_gpx(self, selenium):
|
def test_user_can_add_workout_without_gpx(self, selenium):
|
||||||
register_valid_user(selenium)
|
register_valid_user(selenium)
|
||||||
nav_items = selenium.find_elements_by_class_name('nav-item')
|
nav_items = selenium.find_elements_by_class_name('nav-item')
|
||||||
|
|
||||||
nav_items[3].click()
|
nav_items[3].click()
|
||||||
selenium.implicitly_wait(1)
|
selenium.implicitly_wait(1)
|
||||||
radio_buttons = selenium.find_elements_by_class_name(
|
radio_buttons = selenium.find_elements_by_class_name(
|
||||||
'add-activity-radio'
|
'add-workout-radio'
|
||||||
)
|
)
|
||||||
radio_buttons[1].click()
|
radio_buttons[1].click()
|
||||||
|
|
||||||
selenium.find_element_by_name('title').send_keys('Activity title')
|
selenium.find_element_by_name('title').send_keys('Workout title')
|
||||||
select = Select(selenium.find_element_by_name('sport_id'))
|
select = Select(selenium.find_element_by_name('sport_id'))
|
||||||
select.select_by_index(1)
|
select.select_by_index(1)
|
||||||
selenium.find_element_by_name('activity_date').send_keys('2018-12-20')
|
selenium.find_element_by_name('workout_date').send_keys('2018-12-20')
|
||||||
selenium.find_element_by_name('activity_time').send_keys('14:05')
|
selenium.find_element_by_name('workout_time').send_keys('14:05')
|
||||||
selenium.find_element_by_name('duration').send_keys('01:00:00')
|
selenium.find_element_by_name('duration').send_keys('01:00:00')
|
||||||
selenium.find_element_by_name('distance').send_keys('10')
|
selenium.find_element_by_name('distance').send_keys('10')
|
||||||
selenium.find_element_by_class_name('btn-primary').click()
|
selenium.find_element_by_class_name('btn-primary').click()
|
||||||
|
|
||||||
WebDriverWait(selenium, 10).until(
|
WebDriverWait(selenium, 10).until(
|
||||||
EC.url_changes(f"{TEST_URL}/activities/add")
|
EC.url_changes(f"{TEST_URL}/workouts/add")
|
||||||
)
|
)
|
||||||
|
|
||||||
activity_details = selenium.find_element_by_class_name(
|
workout_details = selenium.find_element_by_class_name(
|
||||||
'activity-details'
|
'workout-details'
|
||||||
).text
|
).text
|
||||||
assert 'Duration: 1:00:00' in activity_details
|
assert 'Duration: 1:00:00' in workout_details
|
||||||
assert 'Distance: 10 km' in activity_details
|
assert 'Distance: 10 km' in workout_details
|
||||||
assert 'Average speed: 10 km/h' in activity_details
|
assert 'Average speed: 10 km/h' in workout_details
|
||||||
assert 'Max. speed: 10 km/h' in activity_details
|
assert 'Max. speed: 10 km/h' in workout_details
|
12
fittrackee/dist/asset-manifest.json
vendored
12
fittrackee/dist/asset-manifest.json
vendored
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.34182cc5.chunk.css",
|
"main.css": "/static/css/main.06b7c846.chunk.css",
|
||||||
"main.js": "/static/js/main.385ae13c.chunk.js",
|
"main.js": "/static/js/main.c96409fd.chunk.js",
|
||||||
"main.js.map": "/static/js/main.385ae13c.chunk.js.map",
|
"main.js.map": "/static/js/main.c96409fd.chunk.js.map",
|
||||||
"runtime-main.js": "/static/js/runtime-main.1240af94.js",
|
"runtime-main.js": "/static/js/runtime-main.1240af94.js",
|
||||||
"runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map",
|
"runtime-main.js.map": "/static/js/runtime-main.1240af94.js.map",
|
||||||
"static/js/2.1b88ef3c.chunk.js": "/static/js/2.1b88ef3c.chunk.js",
|
"static/js/2.1b88ef3c.chunk.js": "/static/js/2.1b88ef3c.chunk.js",
|
||||||
"static/js/2.1b88ef3c.chunk.js.map": "/static/js/2.1b88ef3c.chunk.js.map",
|
"static/js/2.1b88ef3c.chunk.js.map": "/static/js/2.1b88ef3c.chunk.js.map",
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
"static/css/main.34182cc5.chunk.css.map": "/static/css/main.34182cc5.chunk.css.map",
|
"static/css/main.06b7c846.chunk.css.map": "/static/css/main.06b7c846.chunk.css.map",
|
||||||
"static/js/2.1b88ef3c.chunk.js.LICENSE.txt": "/static/js/2.1b88ef3c.chunk.js.LICENSE.txt",
|
"static/js/2.1b88ef3c.chunk.js.LICENSE.txt": "/static/js/2.1b88ef3c.chunk.js.LICENSE.txt",
|
||||||
"static/media/en.9e6dbfb0.svg": "/static/media/en.9e6dbfb0.svg",
|
"static/media/en.9e6dbfb0.svg": "/static/media/en.9e6dbfb0.svg",
|
||||||
"static/media/fr.d0f9280c.svg": "/static/media/fr.d0f9280c.svg",
|
"static/media/fr.d0f9280c.svg": "/static/media/fr.d0f9280c.svg",
|
||||||
@ -18,7 +18,7 @@
|
|||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/js/runtime-main.1240af94.js",
|
"static/js/runtime-main.1240af94.js",
|
||||||
"static/js/2.1b88ef3c.chunk.js",
|
"static/js/2.1b88ef3c.chunk.js",
|
||||||
"static/css/main.34182cc5.chunk.css",
|
"static/css/main.06b7c846.chunk.css",
|
||||||
"static/js/main.385ae13c.chunk.js"
|
"static/js/main.c96409fd.chunk.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
2
fittrackee/dist/index.html
vendored
2
fittrackee/dist/index.html
vendored
@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.34182cc5.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.1b88ef3c.chunk.js"></script><script src="/static/js/main.385ae13c.chunk.js"></script></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.7/css/fork-awesome.min.css" integrity="sha256-gsmEoJAws/Kd3CjuOQzLie5Q3yshhvmo7YNtBG7aaEY=" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation-icons/3.0/foundation-icons.min.css"><link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""><title>FitTrackee</title><link href="/static/css/main.06b7c846.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script><script type="text/javascript">$(document).ready((function(){$("li.nav-item").click((function(){$("button.navbar-toggler").toggleClass("collapsed"),$("#navbarSupportedContent").toggleClass("show")}))}))</script><script>!function(e){function t(t){for(var n,i,l=t[0],f=t[1],a=t[2],p=0,s=[];p<l.length;p++)i=l[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(t);s.length;)s.shift()();return u.push.apply(u,a||[]),r()}function r(){for(var e,t=0;t<u.length;t++){for(var r=u[t],n=!0,l=1;l<r.length;l++){var f=r[l];0!==o[f]&&(n=!1)}n&&(u.splice(t--,1),e=i(i.s=r[0]))}return e}var n={},o={1:0},u=[];function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}i.m=e,i.c=n,i.d=function(e,t,r){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(i.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)i.d(r,n,function(t){return e[t]}.bind(null,n));return r},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="/";var l=this.webpackJsonpfittrackee_client=this.webpackJsonpfittrackee_client||[],f=l.push.bind(l);l.push=t,l=l.slice();for(var a=0;a<l.length;a++)t(l[a]);var c=f;r()}([])</script><script src="/static/js/2.1b88ef3c.chunk.js"></script><script src="/static/js/main.c96409fd.chunk.js"></script></body></html>
|
2
fittrackee/dist/static/css/main.06b7c846.chunk.css
vendored
Normal file
2
fittrackee/dist/static/css/main.06b7c846.chunk.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/css/main.06b7c846.chunk.css.map
vendored
Normal file
1
fittrackee/dist/static/css/main.06b7c846.chunk.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
fittrackee/dist/static/js/main.c96409fd.chunk.js
vendored
Normal file
2
fittrackee/dist/static/js/main.c96409fd.chunk.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
fittrackee/dist/static/js/main.c96409fd.chunk.js.map
vendored
Normal file
1
fittrackee/dist/static/js/main.c96409fd.chunk.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -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
|
canDispatch = true
|
||||||
) => dispatch => {
|
) => dispatch => {
|
||||||
dispatch(setLoading(true))
|
dispatch(setLoading(true))
|
||||||
if (data && data.id && target !== 'activities' && isNaN(data.id)) {
|
if (data && data.id && target !== 'workouts' && isNaN(data.id)) {
|
||||||
dispatch(setLoading(false))
|
dispatch(setLoading(false))
|
||||||
return dispatch(setError(`${target}|Incorrect id`))
|
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) {
|
export default function AdminDashboard(props) {
|
||||||
const { appConfig, t } = props
|
const { appConfig, t } = props
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<strong>{t('administration:Administration')}</strong>
|
<strong>{t('administration:Administration')}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,13 +94,13 @@ class AdminSports extends React.Component {
|
|||||||
updateSport(sport.id, !sport.is_active)
|
updateSport(sport.id, !sport.is_active)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{sport.has_activities && (
|
{sport.has_workouts && (
|
||||||
<span className="admin-message">
|
<span className="admin-message">
|
||||||
<i
|
<i
|
||||||
className="fa fa-warning custom-fa"
|
className="fa fa-warning custom-fa"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
{t('administration:activities exist')}
|
{t('administration:workouts exist')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
@ -16,7 +16,7 @@ class AdminStats extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-users fa-3x fa-color" />
|
<i className="fa fa-users fa-3x fa-color" />
|
||||||
@ -35,7 +35,7 @@ class AdminStats extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-tags fa-3x fa-color" />
|
<i className="fa fa-tags fa-3x fa-color" />
|
||||||
@ -52,17 +52,17 @@ class AdminStats extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-calendar fa-3x fa-color" />
|
<i className="fa fa-calendar fa-3x fa-color" />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-9 text-right">
|
<div className="col-9 text-right">
|
||||||
<div className="huge">
|
<div className="huge">
|
||||||
{appStats.activities ? appStats.activities : 0}
|
{appStats.workouts ? appStats.workouts : 0}
|
||||||
</div>
|
</div>
|
||||||
<div>{`${
|
<div>{`${
|
||||||
appStats.activities === 1
|
appStats.workouts === 1
|
||||||
? t('common:workout')
|
? t('common:workout')
|
||||||
: t('common:workouts')
|
: t('common:workouts')
|
||||||
}`}</div>
|
}`}</div>
|
||||||
@ -71,7 +71,7 @@ class AdminStats extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-folder-open fa-3x fa-color" />
|
<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:Username')}</th>
|
||||||
<th>{t('user:Email')}</th>
|
<th>{t('user:Email')}</th>
|
||||||
<th>{t('user:Registration Date')}</th>
|
<th>{t('user:Registration Date')}</th>
|
||||||
<th>{t('activities:Activities')}</th>
|
<th>{t('workouts:Workouts')}</th>
|
||||||
<th>{t('user:Admin')}</th>
|
<th>{t('user:Admin')}</th>
|
||||||
<th>{t('administration:Actions')}</th>
|
<th>{t('administration:Actions')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -176,9 +176,9 @@ class AdminUsers extends React.Component {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
{t('activities:Activities')}
|
{t('workouts:Workouts')}
|
||||||
</span>
|
</span>
|
||||||
{user.nb_activities}
|
{user.nb_workouts}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
|
@ -64,106 +64,11 @@ label {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activities-result {
|
.add-workout {
|
||||||
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 {
|
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-activity-radio {
|
.add-workout-radio {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +104,7 @@ label {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-activities {
|
.chart-workouts {
|
||||||
margin-left: 60px;
|
margin-left: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +143,7 @@ label {
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-activity-logo{
|
.col-workout-logo{
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,6 +468,101 @@ label {
|
|||||||
padding: 0.1em;
|
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 */
|
/* responsive table */
|
||||||
/* adapted from https://uglyduck.ca/making-tables-responsive-with-minimal-css/ */
|
/* adapted from https://uglyduck.ca/making-tables-responsive-with-minimal-css/ */
|
||||||
.heading-span,
|
.heading-span,
|
||||||
@ -745,7 +745,7 @@ label {
|
|||||||
background: #eff1f3;
|
background: #eff1f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-activity,
|
.calendar-workout,
|
||||||
.calendar-more {
|
.calendar-more {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -767,15 +767,15 @@ label {
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-activity-more {
|
.calendar-workout-more {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 992px) {
|
@media only screen and (max-width: 992px) {
|
||||||
|
|
||||||
.calendar-activity:nth-child(-n+2),
|
.calendar-workout:nth-child(-n+2),
|
||||||
.calendar-activity:nth-child(n+3) ~ .calendar-more,
|
.calendar-workout:nth-child(n+3) ~ .calendar-more,
|
||||||
.calendar-activity-more:nth-child(n+3) {
|
.calendar-workout-more:nth-child(n+3) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,9 +783,9 @@ label {
|
|||||||
|
|
||||||
@media only screen and (min-width: 992px) and (max-width: 1200px) {
|
@media only screen and (min-width: 992px) and (max-width: 1200px) {
|
||||||
|
|
||||||
.calendar-activity:nth-child(-n+4),
|
.calendar-workout:nth-child(-n+4),
|
||||||
.calendar-activity:nth-child(n+5) ~ .calendar-more,
|
.calendar-workout:nth-child(n+5) ~ .calendar-more,
|
||||||
.calendar-activity-more:nth-child(n+5) {
|
.calendar-workout-more:nth-child(n+5) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -793,9 +793,9 @@ label {
|
|||||||
|
|
||||||
@media only screen and (min-width: 1200px) {
|
@media only screen and (min-width: 1200px) {
|
||||||
|
|
||||||
.calendar-activity:nth-child(-n+6),
|
.calendar-workout:nth-child(-n+6),
|
||||||
.calendar-activity:nth-child(n+7) ~ .calendar-more,
|
.calendar-workout:nth-child(n+7) ~ .calendar-more,
|
||||||
.calendar-activity-more:nth-child(n+7) {
|
.calendar-workout-more:nth-child(n+7) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import { Route, Switch } from 'react-router-dom'
|
|||||||
|
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import Admin from './Admin'
|
import Admin from './Admin'
|
||||||
import Activity from './Activity'
|
import Workout from './Workout'
|
||||||
import Activities from './Activities'
|
import Workouts from './Workouts'
|
||||||
import CurrentUserProfile from './User/CurrentUserProfile'
|
import CurrentUserProfile from './User/CurrentUserProfile'
|
||||||
import Dashboard from './Dashboard'
|
import Dashboard from './Dashboard'
|
||||||
import Footer from './Footer'
|
import Footer from './Footer'
|
||||||
@ -68,10 +68,10 @@ class App extends React.Component {
|
|||||||
<Route exact path="/logout" component={Logout} />
|
<Route exact path="/logout" component={Logout} />
|
||||||
<Route exact path="/profile/edit" component={ProfileEdit} />
|
<Route exact path="/profile/edit" component={ProfileEdit} />
|
||||||
<Route exact path="/profile" component={CurrentUserProfile} />
|
<Route exact path="/profile" component={CurrentUserProfile} />
|
||||||
<Route exact path="/activities/history" component={Activities} />
|
<Route exact path="/workouts/history" component={Workouts} />
|
||||||
<Route exact path="/activities/statistics" component={Statistics} />
|
<Route exact path="/workouts/statistics" component={Statistics} />
|
||||||
<Route exact path="/users/:userName" component={UserProfile} />
|
<Route exact path="/users/:userName" component={UserProfile} />
|
||||||
<Route path="/activities" component={Activity} />
|
<Route path="/workouts" component={Workout} />
|
||||||
<Route path="/admin" component={Admin} />
|
<Route path="/admin" component={Admin} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
export default class NoActivities extends React.PureComponent {
|
export default class NoWorkouts extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.props
|
const { t } = this.props
|
||||||
return (
|
return (
|
||||||
<div className="card text-center">
|
<div className="card text-center">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{t('common:No workouts.')}{' '}
|
{t('common:No workouts.')}{' '}
|
||||||
<Link to={{ pathname: '/activities/add' }}>
|
<Link to={{ pathname: '/workouts/add' }}>
|
||||||
{t('dashboard:Upload one !')}
|
{t('dashboard:Upload one !')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
@ -4,13 +4,13 @@ import { apiUrl } from '../../utils'
|
|||||||
|
|
||||||
export default class StaticMap extends React.PureComponent {
|
export default class StaticMap extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { activity, display } = this.props
|
const { display, workout } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`activity-map${display === 'list' ? '-list' : ''}`}>
|
<div className={`workout-map${display === 'list' ? '-list' : ''}`}>
|
||||||
<img
|
<img
|
||||||
src={`${apiUrl}activities/map/${activity.map}?${Date.now()}`}
|
src={`${apiUrl}workouts/map/${workout.map}?${Date.now()}`}
|
||||||
alt="activity map"
|
alt="workout map"
|
||||||
/>
|
/>
|
||||||
<div className={`map-attribution${display === 'list' ? '-list' : ''}`}>
|
<div className={`map-attribution${display === 'list' ? '-list' : ''}`}>
|
||||||
<span className="map-attribution-text">©</span>
|
<span className="map-attribution-text">©</span>
|
||||||
|
@ -8,8 +8,8 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
|
|
||||||
import { activityColors } from '../../../utils/activities'
|
|
||||||
import { formatValue } from '../../../utils/stats'
|
import { formatValue } from '../../../utils/stats'
|
||||||
|
import { workoutColors } from '../../../utils/workouts'
|
||||||
import CustomTooltip from './CustomTooltip'
|
import CustomTooltip from './CustomTooltip'
|
||||||
import CustomLabel from './CustomLabel'
|
import CustomLabel from './CustomLabel'
|
||||||
|
|
||||||
@ -56,11 +56,11 @@ export default class StatsCharts extends React.PureComponent {
|
|||||||
<label className="radioLabel col">
|
<label className="radioLabel col">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="activities"
|
name="workouts"
|
||||||
checked={displayedData === 'activities'}
|
checked={displayedData === 'workouts'}
|
||||||
onChange={e => this.handleRadioChange(e)}
|
onChange={e => this.handleRadioChange(e)}
|
||||||
/>
|
/>
|
||||||
{t('statistics:activities')}
|
{t('statistics:workouts')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<ResponsiveContainer height={300}>
|
<ResponsiveContainer height={300}>
|
||||||
@ -81,7 +81,7 @@ export default class StatsCharts extends React.PureComponent {
|
|||||||
key={s.id}
|
key={s.id}
|
||||||
dataKey={s.label}
|
dataKey={s.label}
|
||||||
stackId="a"
|
stackId="a"
|
||||||
fill={activityColors[i]}
|
fill={workoutColors[i]}
|
||||||
label={
|
label={
|
||||||
i === sports.length - 1 ? (
|
i === sports.length - 1 ? (
|
||||||
<CustomLabel displayedData={displayedData} />
|
<CustomLabel displayedData={displayedData} />
|
||||||
|
@ -23,7 +23,7 @@ class Statistics extends React.PureComponent {
|
|||||||
|
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.props.user.username) {
|
if (this.props.user.username) {
|
||||||
this.props.loadActivities(
|
this.props.loadWorkouts(
|
||||||
this.props.user.username,
|
this.props.user.username,
|
||||||
this.props.user.weekm,
|
this.props.user.weekm,
|
||||||
this.props.statsParams
|
this.props.statsParams
|
||||||
@ -62,7 +62,7 @@ export default connect(
|
|||||||
user: state.user,
|
user: state.user,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivities: (userName, weekm, data) => {
|
loadWorkouts: (userName, weekm, data) => {
|
||||||
const dateFormat = 'yyyy-MM-dd'
|
const dateFormat = 'yyyy-MM-dd'
|
||||||
// depends on user config (first day of week)
|
// depends on user config (first day of week)
|
||||||
const time =
|
const time =
|
||||||
|
@ -17,8 +17,8 @@ import { enGB, fr } from 'date-fns/locale'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import CalendarActivities from './CalendarActivities'
|
import CalendarWorkouts from './CalendarWorkouts'
|
||||||
import { getMonthActivities } from '../../actions/activities'
|
import { getMonthWorkouts } from '../../actions/workouts'
|
||||||
import { getDateWithTZ } from '../../utils'
|
import { getDateWithTZ } from '../../utils'
|
||||||
|
|
||||||
const getStartAndEndMonth = (date, weekStartOnMonday) => {
|
const getStartAndEndMonth = (date, weekStartOnMonday) => {
|
||||||
@ -44,7 +44,7 @@ class Calendar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadMonthActivities(this.state.startDate, this.state.endDate)
|
this.props.loadMonthWorkouts(this.state.startDate, this.state.endDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader(localeOptions) {
|
renderHeader(localeOptions) {
|
||||||
@ -81,11 +81,11 @@ class Calendar extends React.Component {
|
|||||||
return <div className="days row">{days}</div>
|
return <div className="days row">{days}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
filterActivities(day) {
|
filterWorkouts(day) {
|
||||||
const { activities, user } = this.props
|
const { workouts, user } = this.props
|
||||||
if (activities) {
|
if (workouts) {
|
||||||
return activities.filter(act =>
|
return workouts.filter(act =>
|
||||||
isSameDay(getDateWithTZ(act.activity_date, user.timezone), day)
|
isSameDay(getDateWithTZ(act.workout_date, user.timezone), day)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
@ -105,7 +105,7 @@ class Calendar extends React.Component {
|
|||||||
while (day <= endDate) {
|
while (day <= endDate) {
|
||||||
for (let i = 0; i < 7; i++) {
|
for (let i = 0; i < 7; i++) {
|
||||||
formattedDate = format(day, dateFormat)
|
formattedDate = format(day, dateFormat)
|
||||||
const dayActivities = this.filterActivities(day)
|
const dayWorkouts = this.filterWorkouts(day)
|
||||||
const isDisabled = isSameMonth(day, currentMonth) ? '' : '-disabled'
|
const isDisabled = isSameMonth(day, currentMonth) ? '' : '-disabled'
|
||||||
const isWeekEnd = weekStartOnMonday
|
const isWeekEnd = weekStartOnMonday
|
||||||
? [5, 6].includes(i)
|
? [5, 6].includes(i)
|
||||||
@ -119,8 +119,8 @@ class Calendar extends React.Component {
|
|||||||
>
|
>
|
||||||
<div className={`img${isDisabled}`}>
|
<div className={`img${isDisabled}`}>
|
||||||
<span className="number">{formattedDate}</span>
|
<span className="number">{formattedDate}</span>
|
||||||
<CalendarActivities
|
<CalendarWorkouts
|
||||||
dayActivities={dayActivities}
|
dayWorkouts={dayWorkouts}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
sports={sports}
|
sports={sports}
|
||||||
/>
|
/>
|
||||||
@ -149,7 +149,7 @@ class Calendar extends React.Component {
|
|||||||
startDate: start,
|
startDate: start,
|
||||||
endDate: end,
|
endDate: end,
|
||||||
})
|
})
|
||||||
this.props.loadMonthActivities(start, end)
|
this.props.loadMonthWorkouts(start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNextMonth() {
|
handleNextMonth() {
|
||||||
@ -167,7 +167,7 @@ class Calendar extends React.Component {
|
|||||||
locale: this.props.language === 'fr' ? fr : enGB,
|
locale: this.props.language === 'fr' ? fr : enGB,
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="calendar">
|
<div className="calendar">
|
||||||
{this.renderHeader(localeOptions)}
|
{this.renderHeader(localeOptions)}
|
||||||
{this.renderDays(localeOptions)}
|
{this.renderDays(localeOptions)}
|
||||||
@ -180,16 +180,16 @@ class Calendar extends React.Component {
|
|||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
activities: state.calendarActivities.data,
|
workouts: state.calendarWorkouts.data,
|
||||||
language: state.language,
|
language: state.language,
|
||||||
sports: state.sports.data,
|
sports: state.sports.data,
|
||||||
user: state.user,
|
user: state.user,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadMonthActivities: (start, end) => {
|
loadMonthWorkouts: (start, end) => {
|
||||||
const dateFormat = 'yyyy-MM-dd'
|
const dateFormat = 'yyyy-MM-dd'
|
||||||
dispatch(
|
dispatch(
|
||||||
getMonthActivities(format(start, dateFormat), format(end, dateFormat))
|
getMonthWorkouts(format(start, dateFormat), format(end, dateFormat))
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import { recordsLabels } from '../../utils/activities'
|
import { recordsLabels } from '../../utils/workouts'
|
||||||
|
|
||||||
export default function CalendarActivity(props) {
|
export default function CalendarWorkout(props) {
|
||||||
const { activity, isDisabled, isMore, sportImg } = props
|
const { isDisabled, isMore, sportImg, workout } = props
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={`calendar-activity${isMore}`}
|
className={`calendar-workout${isMore}`}
|
||||||
to={`/activities/${activity.id}`}
|
to={`/workouts/${workout.id}`}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<img
|
<img
|
||||||
alt="activity sport logo"
|
alt="workout sport logo"
|
||||||
className={`activity-sport ${isDisabled}`}
|
className={`workout-sport ${isDisabled}`}
|
||||||
src={sportImg}
|
src={sportImg}
|
||||||
title={activity.title}
|
title={workout.title}
|
||||||
/>
|
/>
|
||||||
{activity.records.length > 0 && (
|
{workout.records.length > 0 && (
|
||||||
<sup>
|
<sup>
|
||||||
<i
|
<i
|
||||||
className="fa fa-trophy custom-fa-small"
|
className="fa fa-trophy custom-fa-small"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title={activity.records.map(
|
title={workout.records.map(
|
||||||
rec =>
|
rec =>
|
||||||
` ${
|
` ${
|
||||||
recordsLabels.filter(
|
recordsLabels.filter(
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react'
|
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) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -17,33 +17,33 @@ export default class CalendarActivities extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dayActivities, isDisabled, sports } = this.props
|
const { dayWorkouts, isDisabled, sports } = this.props
|
||||||
const { isHidden } = this.state
|
const { isHidden } = this.state
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{dayActivities.map(act => (
|
{dayWorkouts.map(act => (
|
||||||
<CalendarActivity
|
<CalendarWorkout
|
||||||
key={act.id}
|
key={act.id}
|
||||||
activity={act}
|
workout={act}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isMore=""
|
isMore=""
|
||||||
sportImg={sports.filter(s => s.id === act.sport_id).map(s => s.img)}
|
sportImg={sports.filter(s => s.id === act.sport_id).map(s => s.img)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{dayActivities.length > 2 && (
|
{dayWorkouts.length > 2 && (
|
||||||
<i
|
<i
|
||||||
className={`fa fa-${isHidden ? 'plus' : 'times'} calendar-more`}
|
className={`fa fa-${isHidden ? 'plus' : 'times'} calendar-more`}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
onClick={() => this.handleDisplayMore()}
|
onClick={() => this.handleDisplayMore()}
|
||||||
title="show more activities"
|
title="show more workouts"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isHidden && (
|
{!isHidden && (
|
||||||
<div className="calendar-display-more">
|
<div className="calendar-display-more">
|
||||||
{dayActivities.map(act => (
|
{dayWorkouts.map(act => (
|
||||||
<CalendarActivity
|
<CalendarWorkout
|
||||||
key={act.id}
|
key={act.id}
|
||||||
activity={act}
|
workout={act}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
isMore="-more"
|
isMore="-more"
|
||||||
sportImg={sports
|
sportImg={sports
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import { formatRecord, translateSports } from '../../utils/activities'
|
import { formatRecord, translateSports } from '../../utils/workouts'
|
||||||
|
|
||||||
export default function RecordsCard(props) {
|
export default function RecordsCard(props) {
|
||||||
const { records, sports, t, user } = props
|
const { records, sports, t, user } = props
|
||||||
@ -19,8 +19,8 @@ export default function RecordsCard(props) {
|
|||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-header">{t('activities:Personal records')}</div>
|
<div className="card-header">{t('workouts:Personal records')}</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{Object.keys(recordsBySport).length === 0
|
{Object.keys(recordsBySport).length === 0
|
||||||
? t('common:No records.')
|
? t('common:No records.')
|
||||||
@ -54,12 +54,12 @@ export default function RecordsCard(props) {
|
|||||||
{recordsBySport[sportLabel].records.map(rec => (
|
{recordsBySport[sportLabel].records.map(rec => (
|
||||||
<tr className="record-tr" key={rec.id}>
|
<tr className="record-tr" key={rec.id}>
|
||||||
<td className="record-td">
|
<td className="record-td">
|
||||||
{t(`activities:${rec.record_type}`)}
|
{t(`workouts:${rec.record_type}`)}
|
||||||
</td>
|
</td>
|
||||||
<td className="record-td text-right">{rec.value}</td>
|
<td className="record-td text-right">{rec.value}</td>
|
||||||
<td className="record-td text-right">
|
<td className="record-td text-right">
|
||||||
<Link to={`/activities/${rec.activity_id}`}>
|
<Link to={`/workouts/${rec.workout_id}`}>
|
||||||
{rec.activity_date}
|
{rec.workout_date}
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -18,7 +18,7 @@ export default class Statistics extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const { t } = this.props
|
const { t } = this.props
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-header">{t('dashboard:This month')}</div>
|
<div className="card-header">{t('dashboard:This month')}</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<Stats displayEmpty={false} statsParams={this.state} t={t} />
|
<Stats displayEmpty={false} statsParams={this.state} t={t} />
|
||||||
|
@ -14,15 +14,15 @@ export default function UserStatistics(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-calendar fa-3x fa-color" />
|
<i className="fa fa-calendar fa-3x fa-color" />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-9 text-right">
|
<div className="col-9 text-right">
|
||||||
<div className="huge">{user.nb_activities}</div>
|
<div className="huge">{user.nb_workouts}</div>
|
||||||
<div>{`${
|
<div>{`${
|
||||||
user.nb_activities === 1
|
user.nb_workouts === 1
|
||||||
? t('common:workout')
|
? t('common:workout')
|
||||||
: t('common:workouts')
|
: t('common:workouts')
|
||||||
}`}</div>
|
}`}</div>
|
||||||
@ -31,7 +31,7 @@ export default function UserStatistics(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-road fa-3x fa-color" />
|
<i className="fa fa-road fa-3x fa-color" />
|
||||||
@ -46,7 +46,7 @@ export default function UserStatistics(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-clock-o fa-3x fa-color" />
|
<i className="fa fa-clock-o fa-3x fa-color" />
|
||||||
@ -59,7 +59,7 @@ export default function UserStatistics(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-3 col-md-6 col-sm-6">
|
<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="card-body row">
|
||||||
<div className="col-3">
|
<div className="col-3">
|
||||||
<i className="fa fa-tags fa-3x fa-color" />
|
<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 StaticMap from '../Common/StaticMap'
|
||||||
import { getDateWithTZ } from '../../utils'
|
import { getDateWithTZ } from '../../utils'
|
||||||
|
|
||||||
export default function ActivityCard(props) {
|
export default function WorkoutCard(props) {
|
||||||
const { activity, sports, t, user } = props
|
const { sports, t, user, workout } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card text-center">
|
<div className="card workout-card text-center">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<Link to={`/activities/${activity.id}`}>
|
<Link to={`/workouts/${workout.id}`}>
|
||||||
{sports
|
{sports
|
||||||
.filter(sport => sport.id === activity.sport_id)
|
.filter(sport => sport.id === workout.sport_id)
|
||||||
.map(sport => t(`sports:${sport.label}`))}{' '}
|
.map(sport => t(`sports:${sport.label}`))}{' '}
|
||||||
-{' '}
|
-{' '}
|
||||||
{format(
|
{format(
|
||||||
getDateWithTZ(activity.activity_date, user.timezone),
|
getDateWithTZ(workout.workout_date, user.timezone),
|
||||||
'dd/MM/yyyy HH:mm'
|
'dd/MM/yyyy HH:mm'
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{activity.map && (
|
{workout.map && (
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<StaticMap activity={activity} />
|
<StaticMap workout={workout} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<p>
|
<p>
|
||||||
<i className="fa fa-clock-o" aria-hidden="true" />{' '}
|
<i className="fa fa-clock-o" aria-hidden="true" />{' '}
|
||||||
{t('activities:Duration')}: {activity.moving}
|
{t('workouts:Duration')}: {workout.moving}
|
||||||
{activity.map ? (
|
{workout.map ? (
|
||||||
<span>
|
<span>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
@ -42,7 +42,7 @@ export default function ActivityCard(props) {
|
|||||||
' - '
|
' - '
|
||||||
)}
|
)}
|
||||||
<i className="fa fa-road" aria-hidden="true" />{' '}
|
<i className="fa fa-road" aria-hidden="true" />{' '}
|
||||||
{t('activities:Distance')}: {activity.distance} km
|
{t('workouts:Distance')}: {workout.distance} km
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -3,15 +3,15 @@ import { Helmet } from 'react-helmet'
|
|||||||
import { withTranslation } from 'react-i18next'
|
import { withTranslation } from 'react-i18next'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import ActivityCard from './ActivityCard'
|
|
||||||
import Calendar from './Calendar'
|
import Calendar from './Calendar'
|
||||||
import Message from '../Common/Message'
|
import Message from '../Common/Message'
|
||||||
import NoActivities from '../Common/NoActivities'
|
import NoWorkouts from '../Common/NoWorkouts'
|
||||||
import Records from './Records'
|
import Records from './Records'
|
||||||
import Statistics from './Statistics'
|
import Statistics from './Statistics'
|
||||||
import UserStatistics from './UserStatistics'
|
import UserStatistics from './UserStatistics'
|
||||||
|
import WorkoutCard from './WorkoutCard'
|
||||||
import { getOrUpdateData } from '../../actions'
|
import { getOrUpdateData } from '../../actions'
|
||||||
import { getMoreActivities } from '../../actions/activities'
|
import { getMoreWorkouts } from '../../actions/workouts'
|
||||||
|
|
||||||
class DashBoard extends React.Component {
|
class DashBoard extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
@ -22,22 +22,22 @@ class DashBoard extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadActivities()
|
this.props.loadWorkouts()
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
activities,
|
loadMoreWorkouts,
|
||||||
loadMoreActivities,
|
|
||||||
message,
|
message,
|
||||||
records,
|
records,
|
||||||
sports,
|
sports,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
|
workouts,
|
||||||
} = this.props
|
} = this.props
|
||||||
const paginationEnd =
|
const paginationEnd =
|
||||||
activities.length > 0
|
workouts.length > 0
|
||||||
? activities[activities.length - 1].previous_activity === null
|
? workouts[workouts.length - 1].previous_workout === null
|
||||||
: true
|
: true
|
||||||
const { page } = this.state
|
const { page } = this.state
|
||||||
return (
|
return (
|
||||||
@ -48,7 +48,7 @@ class DashBoard extends React.Component {
|
|||||||
{message ? (
|
{message ? (
|
||||||
<Message message={message} t={t} />
|
<Message message={message} t={t} />
|
||||||
) : (
|
) : (
|
||||||
activities &&
|
workouts &&
|
||||||
user.total_duration &&
|
user.total_duration &&
|
||||||
sports.length > 0 && (
|
sports.length > 0 && (
|
||||||
<div className="container dashboard">
|
<div className="container dashboard">
|
||||||
@ -65,26 +65,26 @@ class DashBoard extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
<Calendar weekm={user.weekm} />
|
<Calendar weekm={user.weekm} />
|
||||||
{activities.length > 0 ? (
|
{workouts.length > 0 ? (
|
||||||
activities.map(activity => (
|
workouts.map(workout => (
|
||||||
<ActivityCard
|
<WorkoutCard
|
||||||
activity={activity}
|
workout={workout}
|
||||||
key={activity.id}
|
key={workout.id}
|
||||||
sports={sports}
|
sports={sports}
|
||||||
t={t}
|
t={t}
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<NoActivities t={t} />
|
<NoWorkouts t={t} />
|
||||||
)}
|
)}
|
||||||
{!paginationEnd && (
|
{!paginationEnd && (
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-default btn-md btn-block"
|
className="btn btn-default btn-md btn-block"
|
||||||
value="Load more activities"
|
value="Load more workouts"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
loadMoreActivities(page + 1)
|
loadMoreWorkouts(page + 1)
|
||||||
this.setState({ page: page + 1 })
|
this.setState({ page: page + 1 })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -102,19 +102,19 @@ class DashBoard extends React.Component {
|
|||||||
export default withTranslation()(
|
export default withTranslation()(
|
||||||
connect(
|
connect(
|
||||||
state => ({
|
state => ({
|
||||||
activities: state.activities.data,
|
workouts: state.workouts.data,
|
||||||
message: state.message,
|
message: state.message,
|
||||||
records: state.records.data,
|
records: state.records.data,
|
||||||
sports: state.sports.data,
|
sports: state.sports.data,
|
||||||
user: state.user,
|
user: state.user,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivities: () => {
|
loadWorkouts: () => {
|
||||||
dispatch(getOrUpdateData('getData', 'activities', { page: 1 }))
|
dispatch(getOrUpdateData('getData', 'workouts', { page: 1 }))
|
||||||
dispatch(getOrUpdateData('getData', 'records'))
|
dispatch(getOrUpdateData('getData', 'records'))
|
||||||
},
|
},
|
||||||
loadMoreActivities: page => {
|
loadMoreWorkouts: page => {
|
||||||
dispatch(getMoreActivities({ page }))
|
dispatch(getMoreWorkouts({ page }))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)(DashBoard)
|
)(DashBoard)
|
||||||
|
@ -45,7 +45,7 @@ class NavBar extends React.PureComponent {
|
|||||||
<Link
|
<Link
|
||||||
className="nav-link"
|
className="nav-link"
|
||||||
to={{
|
to={{
|
||||||
pathname: '/activities/history',
|
pathname: '/workouts/history',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Workouts')}
|
{t('Workouts')}
|
||||||
@ -57,7 +57,7 @@ class NavBar extends React.PureComponent {
|
|||||||
<Link
|
<Link
|
||||||
className="nav-link"
|
className="nav-link"
|
||||||
to={{
|
to={{
|
||||||
pathname: '/activities/statistics',
|
pathname: '/workouts/statistics',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('common:Statistics')}
|
{t('common:Statistics')}
|
||||||
@ -81,7 +81,7 @@ class NavBar extends React.PureComponent {
|
|||||||
<Link
|
<Link
|
||||||
className="nav-link"
|
className="nav-link"
|
||||||
to={{
|
to={{
|
||||||
pathname: '/activities/add',
|
pathname: '/workouts/add',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong>{t('common:Add workout')}</strong>
|
<strong>{t('common:Add workout')}</strong>
|
||||||
|
@ -17,9 +17,9 @@ import { Helmet } from 'react-helmet'
|
|||||||
import { withTranslation } from 'react-i18next'
|
import { withTranslation } from 'react-i18next'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import NoActivities from '../Common/NoActivities'
|
import NoWorkouts from '../Common/NoWorkouts'
|
||||||
import Stats from '../Common/Stats'
|
import Stats from '../Common/Stats'
|
||||||
import { activityColors, translateSports } from '../../utils/activities'
|
import { workoutColors, translateSports } from '../../utils/workouts'
|
||||||
|
|
||||||
const durations = ['week', 'month', 'year']
|
const durations = ['week', 'month', 'year']
|
||||||
|
|
||||||
@ -127,11 +127,11 @@ class Statistics extends React.Component {
|
|||||||
<title>FitTrackee - {t('statistics:Statistics')}</title>
|
<title>FitTrackee - {t('statistics:Statistics')}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="container dashboard">
|
<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-header">{t('statistics:Statistics')}</div>
|
||||||
<div
|
<div
|
||||||
className={`card-body${
|
className={`card-body${
|
||||||
user.nb_activities === 0 ? ' stats-disabled' : ''
|
user.nb_workouts === 0 ? ' stats-disabled' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="chart-filters row">
|
<div className="chart-filters row">
|
||||||
@ -176,16 +176,16 @@ class Statistics extends React.Component {
|
|||||||
statsParams={statsParams}
|
statsParams={statsParams}
|
||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
<div className="row chart-activities">
|
<div className="row chart-workouts">
|
||||||
{translatedSports.map(sport => (
|
{translatedSports.map(sport => (
|
||||||
<label className="col activity-label" key={sport.id}>
|
<label className="col workout-label" key={sport.id}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={displayedSports.includes(sport.id)}
|
checked={displayedSports.includes(sport.id)}
|
||||||
name={sport.label}
|
name={sport.label}
|
||||||
onChange={() => this.handleOnChangeSports(sport.id)}
|
onChange={() => this.handleOnChangeSports(sport.id)}
|
||||||
/>
|
/>
|
||||||
<span style={{ color: activityColors[sport.id - 1] }}>
|
<span style={{ color: workoutColors[sport.id - 1] }}>
|
||||||
{` ${sport.label}`}
|
{` ${sport.label}`}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@ -193,7 +193,7 @@ class Statistics extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{user.nb_activities === 0 && <NoActivities t={t} />}
|
{user.nb_workouts === 0 && <NoWorkouts t={t} />}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import ActivityAddOrEdit from './ActivityAddOrEdit'
|
import WorkoutAddOrEdit from './WorkoutAddOrEdit'
|
||||||
|
|
||||||
function ActivityAdd(props) {
|
function WorkoutAdd(props) {
|
||||||
const { message, sports } = props
|
const { message, sports } = props
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ActivityAddOrEdit activity={null} message={message} sports={sports} />
|
<WorkoutAddOrEdit workout={null} message={message} sports={sports} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -16,4 +16,4 @@ export default connect(state => ({
|
|||||||
message: state.message,
|
message: state.message,
|
||||||
sports: state.sports.data,
|
sports: state.sports.data,
|
||||||
user: state.user,
|
user: state.user,
|
||||||
}))(ActivityAdd)
|
}))(WorkoutAdd)
|
@ -3,11 +3,11 @@ import { Helmet } from 'react-helmet'
|
|||||||
import { withTranslation } from 'react-i18next'
|
import { withTranslation } from 'react-i18next'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import FormWithGpx from './ActivityForms/FormWithGpx'
|
import FormWithGpx from './WorkoutForms/FormWithGpx'
|
||||||
import FormWithoutGpx from './ActivityForms/FormWithoutGpx'
|
import FormWithoutGpx from './WorkoutForms/FormWithoutGpx'
|
||||||
import Message from '../Common/Message'
|
import Message from '../Common/Message'
|
||||||
|
|
||||||
class ActivityAddEdit extends React.Component {
|
class WorkoutAddEdit extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -25,16 +25,16 @@ class ActivityAddEdit extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { activity, loading, message, sports, t } = this.props
|
const { loading, message, sports, t, workout } = this.props
|
||||||
const { withGpx } = this.state
|
const { withGpx } = this.state
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>
|
<title>
|
||||||
FitTrackee -{' '}
|
FitTrackee -{' '}
|
||||||
{activity
|
{workout
|
||||||
? t('activities:Edit a workout')
|
? t('workouts:Edit a workout')
|
||||||
: t('activities:Add a workout')}
|
: t('workouts:Add a workout')}
|
||||||
</title>
|
</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<br />
|
<br />
|
||||||
@ -44,22 +44,18 @@ class ActivityAddEdit extends React.Component {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-2" />
|
<div className="col-md-2" />
|
||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
<div className="card add-activity">
|
<div className="card add-workout">
|
||||||
<h2 className="card-header text-center">
|
<h2 className="card-header text-center">
|
||||||
{activity
|
{workout
|
||||||
? t('activities:Edit a workout')
|
? t('workouts:Edit a workout')
|
||||||
: t('activities:Add a workout')}
|
: t('workouts:Add a workout')}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{activity ? (
|
{workout ? (
|
||||||
activity.with_gpx ? (
|
workout.with_gpx ? (
|
||||||
<FormWithGpx activity={activity} sports={sports} t={t} />
|
<FormWithGpx workout={workout} sports={sports} t={t} />
|
||||||
) : (
|
) : (
|
||||||
<FormWithoutGpx
|
<FormWithoutGpx workout={workout} sports={sports} t={t} />
|
||||||
activity={activity}
|
|
||||||
sports={sports}
|
|
||||||
t={t}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
@ -68,7 +64,7 @@ class ActivityAddEdit extends React.Component {
|
|||||||
<div className="col">
|
<div className="col">
|
||||||
<label className="radioLabel">
|
<label className="radioLabel">
|
||||||
<input
|
<input
|
||||||
className="add-activity-radio"
|
className="add-workout-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="withGpx"
|
name="withGpx"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@ -77,13 +73,13 @@ class ActivityAddEdit extends React.Component {
|
|||||||
this.handleRadioChange(event)
|
this.handleRadioChange(event)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{t('activities:with gpx file')}
|
{t('workouts:with gpx file')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<label className="radioLabel">
|
<label className="radioLabel">
|
||||||
<input
|
<input
|
||||||
className="add-activity-radio"
|
className="add-workout-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="withoutGpx"
|
name="withoutGpx"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@ -92,7 +88,7 @@ class ActivityAddEdit extends React.Component {
|
|||||||
this.handleRadioChange(event)
|
this.handleRadioChange(event)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{t('activities:without gpx file')}
|
{t('workouts:without gpx file')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,5 +114,5 @@ class ActivityAddEdit extends React.Component {
|
|||||||
export default withTranslation()(
|
export default withTranslation()(
|
||||||
connect(state => ({
|
connect(state => ({
|
||||||
loading: state.loading,
|
loading: state.loading,
|
||||||
}))(ActivityAddEdit)
|
}))(WorkoutAddEdit)
|
||||||
)
|
)
|
@ -12,7 +12,7 @@ export default function Map({ bounds, coordinates, jsonData, mapAttribution }) {
|
|||||||
<TileLayer
|
<TileLayer
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
attribution={mapAttribution}
|
attribution={mapAttribution}
|
||||||
url={`${apiUrl}activities/map_tile/{s}/{z}/{x}/{y}.png`}
|
url={`${apiUrl}workouts/map_tile/{s}/{z}/{x}/{y}.png`}
|
||||||
/>
|
/>
|
||||||
<GeoJSON
|
<GeoJSON
|
||||||
// hash as a key to force re-rendering
|
// hash as a key to force re-rendering
|
@ -2,11 +2,10 @@ import React from 'react'
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import { getDateWithTZ } from '../../../utils'
|
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 {
|
const {
|
||||||
activity,
|
|
||||||
dataType,
|
dataType,
|
||||||
displayModal,
|
displayModal,
|
||||||
segmentId,
|
segmentId,
|
||||||
@ -14,22 +13,23 @@ export default function ActivityCardHeader(props) {
|
|||||||
t,
|
t,
|
||||||
title,
|
title,
|
||||||
user,
|
user,
|
||||||
|
workout,
|
||||||
} = props
|
} = props
|
||||||
const activityDate = activity
|
const workoutDate = workout
|
||||||
? formatActivityDate(getDateWithTZ(activity.activity_date, user.timezone))
|
? formatWorkoutDate(getDateWithTZ(workout.workout_date, user.timezone))
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const previousUrl =
|
const previousUrl =
|
||||||
dataType === 'segment' && segmentId !== 1
|
dataType === 'segment' && segmentId !== 1
|
||||||
? `/activities/${activity.id}/segment/${segmentId - 1}`
|
? `/workouts/${workout.id}/segment/${segmentId - 1}`
|
||||||
: dataType === 'activity' && activity.previous_activity
|
: dataType === 'workout' && workout.previous_workout
|
||||||
? `/activities/${activity.previous_activity}`
|
? `/workouts/${workout.previous_workout}`
|
||||||
: null
|
: null
|
||||||
const nextUrl =
|
const nextUrl =
|
||||||
dataType === 'segment' && segmentId < activity.segments.length
|
dataType === 'segment' && segmentId < workout.segments.length
|
||||||
? `/activities/${activity.id}/segment/${segmentId + 1}`
|
? `/workouts/${workout.id}/segment/${segmentId + 1}`
|
||||||
: dataType === 'activity' && activity.next_activity
|
: dataType === 'workout' && workout.next_workout
|
||||||
? `/activities/${activity.next_activity}`
|
? `/workouts/${workout.next_workout}`
|
||||||
: null
|
: null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,53 +41,53 @@ export default function ActivityCardHeader(props) {
|
|||||||
<i
|
<i
|
||||||
className="fa fa-chevron-left"
|
className="fa fa-chevron-left"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title={t(`activities:See previous ${dataType}`)}
|
title={t(`workouts:See previous ${dataType}`)}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="fa fa-chevron-left inactive-link"
|
className="fa fa-chevron-left inactive-link"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title={t(`activities:No previous ${dataType}`)}
|
title={t(`workouts:No previous ${dataType}`)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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" />
|
<img className="sport-img-medium" src={sport.img} alt="sport logo" />
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
{dataType === 'activity' ? (
|
{dataType === 'workout' ? (
|
||||||
<>
|
<>
|
||||||
{title}{' '}
|
{title}{' '}
|
||||||
<Link className="unlink" to={`/activities/${activity.id}/edit`}>
|
<Link className="unlink" to={`/workouts/${workout.id}/edit`}>
|
||||||
<i
|
<i
|
||||||
className="fa fa-edit custom-fa"
|
className="fa fa-edit custom-fa"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title={t('activities:Edit activity')}
|
title={t('workouts:Edit workout')}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<i
|
<i
|
||||||
className="fa fa-trash custom-fa"
|
className="fa fa-trash custom-fa"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
onClick={() => displayModal(true)}
|
onClick={() => displayModal(true)}
|
||||||
title={t('activities:Delete activity')}
|
title={t('workouts:Delete workout')}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* prettier-ignore */}
|
{/* prettier-ignore */}
|
||||||
<Link
|
<Link
|
||||||
to={`/activities/${activity.id}`}
|
to={`/workouts/${workout.id}`}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
- {t('activities:segment')} {segmentId}
|
- {t('workouts:segment')} {segmentId}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<br />
|
<br />
|
||||||
{activityDate && (
|
{workoutDate && (
|
||||||
<span className="activity-date">
|
<span className="workout-date">
|
||||||
{`${activityDate.activity_date} - ${activityDate.activity_time}`}
|
{`${workoutDate.workout_date} - ${workoutDate.workout_time}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -97,14 +97,14 @@ export default function ActivityCardHeader(props) {
|
|||||||
<i
|
<i
|
||||||
className="fa fa-chevron-right"
|
className="fa fa-chevron-right"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title={t(`activities:See next ${dataType}`)}
|
title={t(`workouts:See next ${dataType}`)}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<i
|
<i
|
||||||
className="fa fa-chevron-right inactive-link"
|
className="fa fa-chevron-right inactive-link"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title={t(`activities:No next ${dataType}`)}
|
title={t(`workouts:No next ${dataType}`)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
@ -12,11 +12,11 @@ import {
|
|||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getActivityChartData,
|
|
||||||
getSegmentChartData,
|
getSegmentChartData,
|
||||||
} from '../../../actions/activities'
|
getWorkoutChartData,
|
||||||
|
} from '../../../actions/workouts'
|
||||||
|
|
||||||
class ActivityCharts extends React.Component {
|
class WorkoutCharts extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -26,31 +26,31 @@ class ActivityCharts extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.dataType === 'activity') {
|
if (this.props.dataType === 'workout') {
|
||||||
this.props.loadActivityData(this.props.activity.id)
|
this.props.loadWorkoutData(this.props.workout.id)
|
||||||
} else {
|
} else {
|
||||||
this.props.loadSegmentData(this.props.activity.id, this.props.segmentId)
|
this.props.loadSegmentData(this.props.workout.id, this.props.segmentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (
|
if (
|
||||||
(this.props.dataType === 'activity' &&
|
(this.props.dataType === 'workout' &&
|
||||||
prevProps.activity.id !== this.props.activity.id) ||
|
prevProps.workout.id !== this.props.workout.id) ||
|
||||||
(this.props.dataType === 'activity' && prevProps.dataType === 'segment')
|
(this.props.dataType === 'workout' && prevProps.dataType === 'segment')
|
||||||
) {
|
) {
|
||||||
this.props.loadActivityData(this.props.activity.id)
|
this.props.loadWorkoutData(this.props.workout.id)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.props.dataType === 'segment' &&
|
this.props.dataType === 'segment' &&
|
||||||
prevProps.segmentId !== this.props.segmentId
|
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() {
|
componentWillUnmount() {
|
||||||
this.props.loadActivityData(null)
|
this.props.loadWorkoutData(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRadioChange(changeEvent) {
|
handleRadioChange(changeEvent) {
|
||||||
@ -102,7 +102,7 @@ class ActivityCharts extends React.Component {
|
|||||||
checked={displayDistance}
|
checked={displayDistance}
|
||||||
onChange={e => this.handleRadioChange(e)}
|
onChange={e => this.handleRadioChange(e)}
|
||||||
/>
|
/>
|
||||||
{t('activities:distance')}
|
{t('workouts:distance')}
|
||||||
</label>
|
</label>
|
||||||
<label className="radioLabel col-md-1">
|
<label className="radioLabel col-md-1">
|
||||||
<input
|
<input
|
||||||
@ -111,7 +111,7 @@ class ActivityCharts extends React.Component {
|
|||||||
checked={!displayDistance}
|
checked={!displayDistance}
|
||||||
onChange={e => this.handleRadioChange(e)}
|
onChange={e => this.handleRadioChange(e)}
|
||||||
/>
|
/>
|
||||||
{t('activities:duration')}
|
{t('workouts:duration')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="row chart-radio">
|
<div className="row chart-radio">
|
||||||
@ -123,7 +123,7 @@ class ActivityCharts extends React.Component {
|
|||||||
checked={this.displayData('speed')}
|
checked={this.displayData('speed')}
|
||||||
onChange={e => this.handleLegendChange(e)}
|
onChange={e => this.handleLegendChange(e)}
|
||||||
/>
|
/>
|
||||||
{t('activities:speed')}
|
{t('workouts:speed')}
|
||||||
</label>
|
</label>
|
||||||
<label className="radioLabel col-md-1">
|
<label className="radioLabel col-md-1">
|
||||||
<input
|
<input
|
||||||
@ -132,7 +132,7 @@ class ActivityCharts extends React.Component {
|
|||||||
checked={this.displayData('elevation')}
|
checked={this.displayData('elevation')}
|
||||||
onChange={e => this.handleLegendChange(e)}
|
onChange={e => this.handleLegendChange(e)}
|
||||||
/>
|
/>
|
||||||
{t('activities:elevation')}
|
{t('workouts:elevation')}
|
||||||
</label>
|
</label>
|
||||||
<div className="col-md-5" />
|
<div className="col-md-5" />
|
||||||
</div>
|
</div>
|
||||||
@ -148,7 +148,7 @@ class ActivityCharts extends React.Component {
|
|||||||
allowDecimals={false}
|
allowDecimals={false}
|
||||||
dataKey={xDataKey}
|
dataKey={xDataKey}
|
||||||
label={{
|
label={{
|
||||||
value: t(`activities:${xDataKey}`),
|
value: t(`workouts:${xDataKey}`),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
}}
|
}}
|
||||||
@ -161,7 +161,7 @@ class ActivityCharts extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
label={{
|
label={{
|
||||||
value: `${t('activities:speed')} (km/h)`,
|
value: `${t('workouts:speed')} (km/h)`,
|
||||||
angle: -90,
|
angle: -90,
|
||||||
position: 'left',
|
position: 'left',
|
||||||
}}
|
}}
|
||||||
@ -169,7 +169,7 @@ class ActivityCharts extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
label={{
|
label={{
|
||||||
value: `${t('activities:elevation')} (m)`,
|
value: `${t('workouts:elevation')} (m)`,
|
||||||
angle: -90,
|
angle: -90,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
}}
|
}}
|
||||||
@ -181,7 +181,7 @@ class ActivityCharts extends React.Component {
|
|||||||
yAxisId="right"
|
yAxisId="right"
|
||||||
type="linear"
|
type="linear"
|
||||||
dataKey="elevation"
|
dataKey="elevation"
|
||||||
name={t('activities:elevation')}
|
name={t('workouts:elevation')}
|
||||||
fill="#e5e5e5"
|
fill="#e5e5e5"
|
||||||
stroke="#cccccc"
|
stroke="#cccccc"
|
||||||
dot={false}
|
dot={false}
|
||||||
@ -193,7 +193,7 @@ class ActivityCharts extends React.Component {
|
|||||||
yAxisId="left"
|
yAxisId="left"
|
||||||
type="linear"
|
type="linear"
|
||||||
dataKey="speed"
|
dataKey="speed"
|
||||||
name={t('activities:speed')}
|
name={t('workouts:speed')}
|
||||||
stroke="#8884d8"
|
stroke="#8884d8"
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={false}
|
dot={false}
|
||||||
@ -203,8 +203,8 @@ class ActivityCharts extends React.Component {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
labelFormatter={value =>
|
labelFormatter={value =>
|
||||||
displayDistance
|
displayDistance
|
||||||
? `${t('activities:distance')}: ${value} km`
|
? `${t('workouts:distance')}: ${value} km`
|
||||||
: `${t('activities:duration')}: ${format(
|
: `${t('workouts:duration')}: ${format(
|
||||||
value,
|
value,
|
||||||
'HH:mm:ss'
|
'HH:mm:ss'
|
||||||
)}`
|
)}`
|
||||||
@ -214,11 +214,11 @@ class ActivityCharts extends React.Component {
|
|||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
<div className="chart-info">
|
<div className="chart-info">
|
||||||
{t('activities:data from gpx, without any cleaning')}
|
{t('workouts:data from gpx, without any cleaning')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
t('activities:No data to display')
|
t('workouts:No data to display')
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -230,11 +230,11 @@ export default connect(
|
|||||||
chartData: state.chartData,
|
chartData: state.chartData,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivityData: activityId => {
|
loadWorkoutData: workoutId => {
|
||||||
dispatch(getActivityChartData(activityId))
|
dispatch(getWorkoutChartData(workoutId))
|
||||||
},
|
},
|
||||||
loadSegmentData: (activityId, segmentId) => {
|
loadSegmentData: (workoutId, segmentId) => {
|
||||||
dispatch(getSegmentChartData(activityId, 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 { connect } from 'react-redux'
|
||||||
|
|
||||||
import Map from './Map'
|
import Map from './Map'
|
||||||
import { getActivityGpx, getSegmentGpx } from '../../../actions/activities'
|
import { getSegmentGpx, getWorkoutGpx } from '../../../actions/workouts'
|
||||||
import { getGeoJson } from '../../../utils/activities'
|
import { getGeoJson } from '../../../utils/workouts'
|
||||||
|
|
||||||
class ActivityMap extends React.Component {
|
class WorkoutMap extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -15,39 +15,39 @@ class ActivityMap extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.props.dataType === 'activity') {
|
if (this.props.dataType === 'workout') {
|
||||||
this.props.loadActivityGpx(this.props.activity.id)
|
this.props.loadWorkoutGpx(this.props.workout.id)
|
||||||
} else {
|
} else {
|
||||||
this.props.loadSegmentGpx(this.props.activity.id, this.props.segmentId)
|
this.props.loadSegmentGpx(this.props.workout.id, this.props.segmentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (
|
if (
|
||||||
(this.props.dataType === 'activity' &&
|
(this.props.dataType === 'workout' &&
|
||||||
prevProps.activity.id !== this.props.activity.id) ||
|
prevProps.workout.id !== this.props.workout.id) ||
|
||||||
(this.props.dataType === 'activity' && prevProps.dataType === 'segment')
|
(this.props.dataType === 'workout' && prevProps.dataType === 'segment')
|
||||||
) {
|
) {
|
||||||
this.props.loadActivityGpx(this.props.activity.id)
|
this.props.loadWorkoutGpx(this.props.workout.id)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
this.props.dataType === 'segment' &&
|
this.props.dataType === 'segment' &&
|
||||||
prevProps.segmentId !== this.props.segmentId
|
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() {
|
componentWillUnmount() {
|
||||||
this.props.loadActivityGpx(null)
|
this.props.loadWorkoutGpx(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { activity, coordinates, gpxContent, mapAttribution } = this.props
|
const { coordinates, gpxContent, mapAttribution, workout } = this.props
|
||||||
const { jsonData } = getGeoJson(gpxContent)
|
const { jsonData } = getGeoJson(gpxContent)
|
||||||
const bounds = [
|
const bounds = [
|
||||||
[activity.bounds[0], activity.bounds[1]],
|
[workout.bounds[0], workout.bounds[1]],
|
||||||
[activity.bounds[2], activity.bounds[3]],
|
[workout.bounds[2], workout.bounds[3]],
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -77,11 +77,11 @@ export default connect(
|
|||||||
mapAttribution: state.application.config.map_attribution,
|
mapAttribution: state.application.config.map_attribution,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivityGpx: activityId => {
|
loadWorkoutGpx: workoutId => {
|
||||||
dispatch(getActivityGpx(activityId))
|
dispatch(getWorkoutGpx(workoutId))
|
||||||
},
|
},
|
||||||
loadSegmentGpx: (activityId, segmentId) => {
|
loadSegmentGpx: (workoutId, segmentId) => {
|
||||||
dispatch(getSegmentGpx(activityId, 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'
|
import React from 'react'
|
||||||
|
|
||||||
export default function ActivityNotes(props) {
|
export default function WorkoutNotes(props) {
|
||||||
const { notes, t } = props
|
const { notes, t } = props
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
Notes
|
Notes
|
||||||
<div className="activity-notes">
|
<div className="workout-notes">
|
||||||
{notes ? notes : t('activities:No notes')}
|
{notes ? notes : t('workouts:No notes')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,31 +1,31 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
export default function ActivitySegments(props) {
|
export default function WorkoutSegments(props) {
|
||||||
const { segments, t } = props
|
const { segments, t } = props
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{t('activities:Segments')}
|
{t('workouts:Segments')}
|
||||||
<div className="activity-segments">
|
<div className="workout-segments">
|
||||||
<ul>
|
<ul>
|
||||||
{segments.map((segment, index) => (
|
{segments.map((segment, index) => (
|
||||||
<li
|
<li
|
||||||
className="activity-segments-list"
|
className="workout-segments-list"
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
key={`segment-${index}`}
|
key={`segment-${index}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
to={`/activities/${segment.activity_id}/segment/${
|
to={`/workouts/${segment.workout_id}/segment/${
|
||||||
index + 1
|
index + 1
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t('activities:segment')} {index + 1}
|
{t('workouts:segment')} {index + 1}
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
({t('activities:distance')}: {segment.distance} km,{' '}
|
({t('workouts:distance')}: {segment.distance} km,{' '}
|
||||||
{t('activities:duration')}: {segment.duration})
|
{t('workouts:duration')}: {segment.duration})
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
@ -1,32 +1,32 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function ActivityWeather(props) {
|
export default function WorkoutWeather(props) {
|
||||||
const { activity, t } = props
|
const { t, workout } = props
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
{activity.weather_start && activity.weather_end && (
|
{workout.weather_start && workout.weather_end && (
|
||||||
<table className="table table-borderless weather-table text-center">
|
<table className="table table-borderless weather-table text-center">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th />
|
||||||
<th>
|
<th>
|
||||||
{t('activities:Start')}
|
{t('workouts:Start')}
|
||||||
<br />
|
<br />
|
||||||
<img
|
<img
|
||||||
className="weather-img"
|
className="weather-img"
|
||||||
src={`/img/weather/${activity.weather_start.icon}.png`}
|
src={`/img/weather/${workout.weather_start.icon}.png`}
|
||||||
alt={`activity weather (${activity.weather_start.icon})`}
|
alt={`workout weather (${workout.weather_start.icon})`}
|
||||||
title={activity.weather_start.summary}
|
title={workout.weather_start.summary}
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{t('activities:End')}
|
{t('workouts:End')}
|
||||||
<br />
|
<br />
|
||||||
<img
|
<img
|
||||||
className="weather-img"
|
className="weather-img"
|
||||||
src={`/img/weather/${activity.weather_end.icon}.png`}
|
src={`/img/weather/${workout.weather_end.icon}.png`}
|
||||||
alt={`activity weather (${activity.weather_end.icon})`}
|
alt={`workout weather (${workout.weather_end.icon})`}
|
||||||
title={activity.weather_end.summary}
|
title={workout.weather_end.summary}
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -40,8 +40,8 @@ export default function ActivityWeather(props) {
|
|||||||
alt="Temperatures"
|
alt="Temperatures"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{Number(activity.weather_start.temperature).toFixed(1)}°C</td>
|
<td>{Number(workout.weather_start.temperature).toFixed(1)}°C</td>
|
||||||
<td>{Number(activity.weather_end.temperature).toFixed(1)}°C</td>
|
<td>{Number(workout.weather_end.temperature).toFixed(1)}°C</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@ -52,9 +52,9 @@ export default function ActivityWeather(props) {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{Number(activity.weather_start.humidity * 100).toFixed(1)}%
|
{Number(workout.weather_start.humidity * 100).toFixed(1)}%
|
||||||
</td>
|
</td>
|
||||||
<td>{Number(activity.weather_end.humidity * 100).toFixed(1)}%</td>
|
<td>{Number(workout.weather_end.humidity * 100).toFixed(1)}%</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@ -64,8 +64,8 @@ export default function ActivityWeather(props) {
|
|||||||
alt="Temperatures"
|
alt="Temperatures"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{Number(activity.weather_start.wind).toFixed(1)}m/s</td>
|
<td>{Number(workout.weather_start.wind).toFixed(1)}m/s</td>
|
||||||
<td>{Number(activity.weather_end.wind).toFixed(1)}m/s</td>
|
<td>{Number(workout.weather_end.wind).toFixed(1)}m/s</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
@ -3,19 +3,19 @@ import { Helmet } from 'react-helmet'
|
|||||||
import { withTranslation } from 'react-i18next'
|
import { withTranslation } from 'react-i18next'
|
||||||
import { connect } from 'react-redux'
|
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 CustomModal from '../../Common/CustomModal'
|
||||||
import Message from '../../Common/Message'
|
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 { 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) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -28,14 +28,14 @@ class ActivityDisplay extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadActivity(this.props.match.params.activityId)
|
this.props.loadWorkout(this.props.match.params.workoutId)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (
|
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() {
|
render() {
|
||||||
const {
|
const { message, onDeleteWorkout, sports, t, user, workouts } = this.props
|
||||||
activities,
|
|
||||||
message,
|
|
||||||
onDeleteActivity,
|
|
||||||
sports,
|
|
||||||
t,
|
|
||||||
user,
|
|
||||||
} = this.props
|
|
||||||
const { coordinates, displayModal } = this.state
|
const { coordinates, displayModal } = this.state
|
||||||
const [activity] = activities
|
const [workout] = workouts
|
||||||
const title = activity ? activity.title : t('activities:Activity')
|
const title = workout ? workout.title : t('workouts:Workout')
|
||||||
const [sport] = activity
|
const [sport] = workout ? sports.filter(s => s.id === workout.sport_id) : []
|
||||||
? sports.filter(s => s.id === activity.sport_id)
|
|
||||||
: []
|
|
||||||
const segmentId = parseInt(this.props.match.params.segmentId)
|
const segmentId = parseInt(this.props.match.params.segmentId)
|
||||||
const dataType = segmentId >= 0 ? 'segment' : 'activity'
|
const dataType = segmentId >= 0 ? 'segment' : 'workout'
|
||||||
return (
|
return (
|
||||||
<div className="activity-page">
|
<div className="workout-page">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - {title}</title>
|
<title>FitTrackee - {title}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
@ -93,23 +84,23 @@ class ActivityDisplay extends React.Component {
|
|||||||
<CustomModal
|
<CustomModal
|
||||||
title={t('common:Confirmation')}
|
title={t('common:Confirmation')}
|
||||||
text={t(
|
text={t(
|
||||||
'activities:Are you sure you want to delete this activity?'
|
'workouts:Are you sure you want to delete this workout?'
|
||||||
)}
|
)}
|
||||||
confirm={() => {
|
confirm={() => {
|
||||||
onDeleteActivity(activity.id)
|
onDeleteWorkout(workout.id)
|
||||||
this.displayModal(false)
|
this.displayModal(false)
|
||||||
}}
|
}}
|
||||||
close={() => this.displayModal(false)}
|
close={() => this.displayModal(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activity && sport && activities.length === 1 && (
|
{workout && sport && workouts.length === 1 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<ActivityCardHeader
|
<WorkoutCardHeader
|
||||||
activity={activity}
|
workout={workout}
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
segmentId={segmentId}
|
segmentId={segmentId}
|
||||||
sport={sport}
|
sport={sport}
|
||||||
@ -122,23 +113,23 @@ class ActivityDisplay extends React.Component {
|
|||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
{activity.with_gpx ? (
|
{workout.with_gpx ? (
|
||||||
<ActivityMap
|
<WorkoutMap
|
||||||
activity={activity}
|
workout={workout}
|
||||||
coordinates={coordinates}
|
coordinates={coordinates}
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
segmentId={segmentId}
|
segmentId={segmentId}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ActivityNoMap t={t} />
|
<WorkoutNoMap t={t} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<ActivityDetails
|
<WorkoutDetails
|
||||||
activity={
|
workout={
|
||||||
dataType === 'activity'
|
dataType === 'workout'
|
||||||
? activity
|
? workout
|
||||||
: activity.segments[segmentId - 1]
|
: workout.segments[segmentId - 1]
|
||||||
}
|
}
|
||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
@ -148,18 +139,18 @@ class ActivityDisplay extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{activity.with_gpx && (
|
{workout.with_gpx && (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<div className="chart-title">
|
<div className="chart-title">
|
||||||
{t('activities:Chart')}
|
{t('workouts:Chart')}
|
||||||
</div>
|
</div>
|
||||||
<ActivityCharts
|
<WorkoutCharts
|
||||||
activity={activity}
|
workout={workout}
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
segmentId={segmentId}
|
segmentId={segmentId}
|
||||||
t={t}
|
t={t}
|
||||||
@ -174,11 +165,11 @@ class ActivityDisplay extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{dataType === 'activity' && (
|
{dataType === 'workout' && (
|
||||||
<>
|
<>
|
||||||
<ActivityNotes notes={activity.notes} t={t} />
|
<WorkoutNotes notes={workout.notes} t={t} />
|
||||||
{activity.segments.length > 1 && (
|
{workout.segments.length > 1 && (
|
||||||
<ActivitySegments segments={activity.segments} t={t} />
|
<WorkoutSegments segments={workout.segments} t={t} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -194,18 +185,18 @@ class ActivityDisplay extends React.Component {
|
|||||||
export default withTranslation()(
|
export default withTranslation()(
|
||||||
connect(
|
connect(
|
||||||
state => ({
|
state => ({
|
||||||
activities: state.activities.data,
|
workouts: state.workouts.data,
|
||||||
message: state.message,
|
message: state.message,
|
||||||
sports: state.sports.data,
|
sports: state.sports.data,
|
||||||
user: state.user,
|
user: state.user,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivity: activityId => {
|
loadWorkout: workoutId => {
|
||||||
dispatch(getOrUpdateData('getData', 'activities', { id: activityId }))
|
dispatch(getOrUpdateData('getData', 'workouts', { id: workoutId }))
|
||||||
},
|
},
|
||||||
onDeleteActivity: activityId => {
|
onDeleteWorkout: workoutId => {
|
||||||
dispatch(deleteActivity(activityId))
|
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 { connect } from 'react-redux'
|
||||||
|
|
||||||
import { setLoading } from '../../../actions/index'
|
import { setLoading } from '../../../actions/index'
|
||||||
import { addActivity, editActivity } from '../../../actions/activities'
|
import { addWorkout, editWorkout } from '../../../actions/workouts'
|
||||||
import { history } from '../../../index'
|
import { history } from '../../../index'
|
||||||
import { getFileSize } from '../../../utils'
|
import { getFileSize } from '../../../utils'
|
||||||
import { translateSports } from '../../../utils/activities'
|
import { translateSports } from '../../../utils/workouts'
|
||||||
|
|
||||||
function FormWithGpx(props) {
|
function FormWithGpx(props) {
|
||||||
const {
|
const {
|
||||||
activity,
|
|
||||||
appConfig,
|
appConfig,
|
||||||
loading,
|
loading,
|
||||||
onAddActivity,
|
onAddWorkout,
|
||||||
onEditActivity,
|
onEditWorkout,
|
||||||
sports,
|
sports,
|
||||||
t,
|
t,
|
||||||
|
workout,
|
||||||
} = props
|
} = props
|
||||||
const sportId = activity ? activity.sport_id : ''
|
const sportId = workout ? workout.sport_id : ''
|
||||||
const translatedSports = translateSports(sports, t, true)
|
const translatedSports = translateSports(sports, t, true)
|
||||||
const zipTooltip = `${t('activities:no folder inside')}, ${
|
const zipTooltip = `${t('workouts:no folder inside')}, ${
|
||||||
appConfig.gpx_limit_import
|
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
|
appConfig.max_zip_file_size
|
||||||
)}`
|
)}`
|
||||||
const fileSizeLimit = getFileSize(appConfig.max_single_file_size)
|
const fileSizeLimit = getFileSize(appConfig.max_single_file_size)
|
||||||
@ -51,13 +51,13 @@ function FormWithGpx(props) {
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{activity ? (
|
{workout ? (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Title')}:
|
{t('workouts:Title')}:
|
||||||
<input
|
<input
|
||||||
name="title"
|
name="title"
|
||||||
defaultValue={activity ? activity.title : ''}
|
defaultValue={workout ? workout.title : ''}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
/>
|
/>
|
||||||
@ -66,7 +66,7 @@ function FormWithGpx(props) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
<Trans i18nKey="activities:gpxFile">
|
<Trans i18nKey="workouts:gpxFile">
|
||||||
<strong>gpx</strong> file
|
<strong>gpx</strong> file
|
||||||
</Trans>
|
</Trans>
|
||||||
<sup>
|
<sup>
|
||||||
@ -74,10 +74,10 @@ function FormWithGpx(props) {
|
|||||||
className="fa fa-question-circle"
|
className="fa fa-question-circle"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
title={`${t('activities:max size')}: ${fileSizeLimit}`}
|
title={`${t('workouts:max size')}: ${fileSizeLimit}`}
|
||||||
/>
|
/>
|
||||||
</sup>{' '}
|
</sup>{' '}
|
||||||
<Trans i18nKey="activities:zipFile">
|
<Trans i18nKey="workouts:zipFile">
|
||||||
or <strong> zip</strong> file containing <strong>gpx </strong>
|
or <strong> zip</strong> file containing <strong>gpx </strong>
|
||||||
files
|
files
|
||||||
</Trans>
|
</Trans>
|
||||||
@ -104,10 +104,10 @@ function FormWithGpx(props) {
|
|||||||
)}
|
)}
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Notes')}:
|
{t('workouts:Notes')}:
|
||||||
<textarea
|
<textarea
|
||||||
name="notes"
|
name="notes"
|
||||||
defaultValue={activity ? activity.notes : ''}
|
defaultValue={workout ? workout.notes : ''}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
maxLength="500"
|
maxLength="500"
|
||||||
@ -122,7 +122,7 @@ function FormWithGpx(props) {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={event =>
|
onClick={event =>
|
||||||
activity ? onEditActivity(event, activity) : onAddActivity(event)
|
workout ? onEditWorkout(event, workout) : onAddWorkout(event)
|
||||||
}
|
}
|
||||||
value={t('common:Submit')}
|
value={t('common:Submit')}
|
||||||
/>
|
/>
|
||||||
@ -144,7 +144,7 @@ export default connect(
|
|||||||
loading: state.loading,
|
loading: state.loading,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
onAddActivity: e => {
|
onAddWorkout: e => {
|
||||||
dispatch(setLoading(true))
|
dispatch(setLoading(true))
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
form.append('file', e.target.form.gpxFile.files[0])
|
form.append('file', e.target.form.gpxFile.files[0])
|
||||||
@ -154,12 +154,12 @@ export default connect(
|
|||||||
`{"sport_id": ${e.target.form.sport.value
|
`{"sport_id": ${e.target.form.sport.value
|
||||||
}, "notes": "${e.target.form.notes.value}"}`
|
}, "notes": "${e.target.form.notes.value}"}`
|
||||||
)
|
)
|
||||||
dispatch(addActivity(form))
|
dispatch(addWorkout(form))
|
||||||
},
|
},
|
||||||
onEditActivity: (e, activity) => {
|
onEditWorkout: (e, workout) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
editActivity({
|
editWorkout({
|
||||||
id: activity.id,
|
id: workout.id,
|
||||||
notes: e.target.form.notes.value,
|
notes: e.target.form.notes.value,
|
||||||
sport_id: +e.target.form.sport.value,
|
sport_id: +e.target.form.sport.value,
|
||||||
title: e.target.form.title.value,
|
title: e.target.form.title.value,
|
@ -1,38 +1,35 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import {
|
import { addWorkoutWithoutGpx, editWorkout } from '../../../actions/workouts'
|
||||||
addActivityWithoutGpx,
|
|
||||||
editActivity,
|
|
||||||
} from '../../../actions/activities'
|
|
||||||
import { history } from '../../../index'
|
import { history } from '../../../index'
|
||||||
import { getDateWithTZ } from '../../../utils'
|
import { getDateWithTZ } from '../../../utils'
|
||||||
import { formatActivityDate, translateSports } from '../../../utils/activities'
|
import { formatWorkoutDate, translateSports } from '../../../utils/workouts'
|
||||||
|
|
||||||
function FormWithoutGpx(props) {
|
function FormWithoutGpx(props) {
|
||||||
const { activity, onAddOrEdit, sports, t, user } = props
|
const { onAddOrEdit, sports, t, user, workout } = props
|
||||||
const translatedSports = translateSports(sports, t, true)
|
const translatedSports = translateSports(sports, t, true)
|
||||||
let activityDate,
|
let workoutDate,
|
||||||
activityTime,
|
workoutTime,
|
||||||
sportId = ''
|
sportId = ''
|
||||||
if (activity) {
|
if (workout) {
|
||||||
const activityDateTime = formatActivityDate(
|
const workoutDateTime = formatWorkoutDate(
|
||||||
getDateWithTZ(activity.activity_date, user.timezone),
|
getDateWithTZ(workout.workout_date, user.timezone),
|
||||||
'yyyy-MM-dd'
|
'yyyy-MM-dd'
|
||||||
)
|
)
|
||||||
activityDate = activityDateTime.activity_date
|
workoutDate = workoutDateTime.workout_date
|
||||||
activityTime = activityDateTime.activity_time
|
workoutTime = workoutDateTime.workout_time
|
||||||
sportId = activity.sport_id
|
sportId = workout.sport_id
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={event => event.preventDefault()}>
|
<form onSubmit={event => event.preventDefault()}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Title')}:
|
{t('workouts:Title')}:
|
||||||
<input
|
<input
|
||||||
name="title"
|
name="title"
|
||||||
defaultValue={activity ? activity.title : ''}
|
defaultValue={workout ? workout.title : ''}
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
@ -57,19 +54,19 @@ function FormWithoutGpx(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Activity Date')}:
|
{t('workouts:Workout Date')}:
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<input
|
<input
|
||||||
name="activity_date"
|
name="workout_date"
|
||||||
defaultValue={activityDate}
|
defaultValue={workoutDate}
|
||||||
className="form-control col-md"
|
className="form-control col-md"
|
||||||
required
|
required
|
||||||
type="date"
|
type="date"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
name="activity_time"
|
name="workout_time"
|
||||||
defaultValue={activityTime}
|
defaultValue={workoutTime}
|
||||||
className="form-control col-md"
|
className="form-control col-md"
|
||||||
required
|
required
|
||||||
type="time"
|
type="time"
|
||||||
@ -80,10 +77,10 @@ function FormWithoutGpx(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Duration')}:
|
{t('workouts:Duration')}:
|
||||||
<input
|
<input
|
||||||
name="duration"
|
name="duration"
|
||||||
defaultValue={activity ? activity.duration : ''}
|
defaultValue={workout ? workout.duration : ''}
|
||||||
className="form-control col-xs-4"
|
className="form-control col-xs-4"
|
||||||
pattern="^([0-9]*[0-9]):([0-5][0-9]):([0-5][0-9])$"
|
pattern="^([0-9]*[0-9]):([0-5][0-9]):([0-5][0-9])$"
|
||||||
placeholder="hh:mm:ss"
|
placeholder="hh:mm:ss"
|
||||||
@ -94,10 +91,10 @@ function FormWithoutGpx(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Distance')} (km):
|
{t('workouts:Distance')} (km):
|
||||||
<input
|
<input
|
||||||
name="distance"
|
name="distance"
|
||||||
defaultValue={activity ? activity.distance : ''}
|
defaultValue={workout ? workout.distance : ''}
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
min={0}
|
min={0}
|
||||||
required
|
required
|
||||||
@ -108,10 +105,10 @@ function FormWithoutGpx(props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Notes')}:
|
{t('workouts:Notes')}:
|
||||||
<textarea
|
<textarea
|
||||||
name="notes"
|
name="notes"
|
||||||
defaultValue={activity ? activity.notes : ''}
|
defaultValue={workout ? workout.notes : ''}
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
maxLength="500"
|
maxLength="500"
|
||||||
/>
|
/>
|
||||||
@ -120,7 +117,7 @@ function FormWithoutGpx(props) {
|
|||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={event => onAddOrEdit(event, activity)}
|
onClick={event => onAddOrEdit(event, workout)}
|
||||||
value={t('common:Submit')}
|
value={t('common:Submit')}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
@ -138,27 +135,27 @@ export default connect(
|
|||||||
user: state.user,
|
user: state.user,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
onAddOrEdit: (e, activity) => {
|
onAddOrEdit: (e, workout) => {
|
||||||
const d = e.target.form.duration.value.split(':')
|
const d = e.target.form.duration.value.split(':')
|
||||||
const duration = +d[0] * 60 * 60 + +d[1] * 60 + +d[2]
|
const duration = +d[0] * 60 * 60 + +d[1] * 60 + +d[2]
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
const activityDate = `${e.target.form.activity_date.value
|
const workoutDate = `${e.target.form.workout_date.value
|
||||||
} ${ e.target.form.activity_time.value}`
|
} ${ e.target.form.workout_time.value}`
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
activity_date: activityDate,
|
workout_date: workoutDate,
|
||||||
distance: +e.target.form.distance.value,
|
distance: +e.target.form.distance.value,
|
||||||
duration,
|
duration,
|
||||||
notes: e.target.form.notes.value,
|
notes: e.target.form.notes.value,
|
||||||
sport_id: +e.target.form.sport_id.value,
|
sport_id: +e.target.form.sport_id.value,
|
||||||
title: e.target.form.title.value,
|
title: e.target.form.title.value,
|
||||||
}
|
}
|
||||||
if (activity) {
|
if (workout) {
|
||||||
data.id = activity.id
|
data.id = workout.id
|
||||||
dispatch(editActivity(data))
|
dispatch(editWorkout(data))
|
||||||
} else {
|
} 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 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() {
|
render() {
|
||||||
const { loadActivities, sports, t, updateParams } = this.props
|
const { loadWorkouts, sports, t, updateParams } = this.props
|
||||||
const translatedSports = translateSports(sports, t)
|
const translatedSports = translateSports(sports, t)
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body activity-filter">
|
<div className="card-body workout-filter">
|
||||||
<form onSubmit={event => event.preventDefault()}>
|
<form onSubmit={event => event.preventDefault()}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:From')}:
|
{t('workouts:From')}:
|
||||||
<input
|
<input
|
||||||
className="form-control col-md"
|
className="form-control col-md"
|
||||||
name="from"
|
name="from"
|
||||||
@ -21,7 +21,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
{t('activities:To')}:
|
{t('workouts:To')}:
|
||||||
<input
|
<input
|
||||||
className="form-control col-md"
|
className="form-control col-md"
|
||||||
name="to"
|
name="to"
|
||||||
@ -49,7 +49,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Distance')} (km):
|
{t('workouts:Distance')} (km):
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-5">
|
<div className="col-5">
|
||||||
@ -81,7 +81,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Duration')}:
|
{t('workouts:Duration')}:
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-5">
|
<div className="col-5">
|
||||||
@ -113,7 +113,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Average speed')} (km/h):
|
{t('workouts:Average speed')} (km/h):
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-5">
|
<div className="col-5">
|
||||||
@ -145,7 +145,7 @@ export default class ActivitiesFilter extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
{t('activities:Max. speed')} (km/h):
|
{t('workouts:Max. speed')} (km/h):
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-5">
|
<div className="col-5">
|
||||||
@ -177,9 +177,9 @@ export default class ActivitiesFilter extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
className="btn btn-primary btn-lg btn-block"
|
className="btn btn-primary btn-lg btn-block"
|
||||||
onClick={() => loadActivities()}
|
onClick={() => loadWorkouts()}
|
||||||
type="submit"
|
type="submit"
|
||||||
value={t('activities:Filter')}
|
value={t('workouts:Filter')}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
@ -5,28 +5,28 @@ import { Link } from 'react-router-dom'
|
|||||||
import StaticMap from '../Common/StaticMap'
|
import StaticMap from '../Common/StaticMap'
|
||||||
import { getDateWithTZ } from '../../utils'
|
import { getDateWithTZ } from '../../utils'
|
||||||
|
|
||||||
export default class ActivitiesList extends React.PureComponent {
|
export default class WorkoutsList extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { activities, loading, sports, t, user } = this.props
|
const { loading, sports, t, user, workouts } = this.props
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card">
|
<div className="card workout-card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" />
|
<th scope="col" />
|
||||||
<th scope="col">{t('common:Workout')}</th>
|
<th scope="col">{t('common:Workout')}</th>
|
||||||
<th scope="col">{t('activities:Date')}</th>
|
<th scope="col">{t('workouts:Date')}</th>
|
||||||
<th scope="col">{t('activities:Distance')}</th>
|
<th scope="col">{t('workouts:Distance')}</th>
|
||||||
<th scope="col">{t('activities:Duration')}</th>
|
<th scope="col">{t('workouts:Duration')}</th>
|
||||||
<th scope="col">{t('activities:Ave. speed')}</th>
|
<th scope="col">{t('workouts:Ave. speed')}</th>
|
||||||
<th scope="col">{t('activities:Max. speed')}</th>
|
<th scope="col">{t('workouts:Max. speed')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{!loading &&
|
{!loading &&
|
||||||
sports &&
|
sports &&
|
||||||
activities.map((activity, idx) => (
|
workouts.map((workout, idx) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<tr key={idx}>
|
<tr key={idx}>
|
||||||
<td>
|
<td>
|
||||||
@ -34,56 +34,56 @@ export default class ActivitiesList extends React.PureComponent {
|
|||||||
{t('common:Sport')}
|
{t('common:Sport')}
|
||||||
</span>
|
</span>
|
||||||
<img
|
<img
|
||||||
className="activity-sport"
|
className="workout-sport"
|
||||||
src={sports
|
src={sports
|
||||||
.filter(s => s.id === activity.sport_id)
|
.filter(s => s.id === workout.sport_id)
|
||||||
.map(s => s.img)}
|
.map(s => s.img)}
|
||||||
alt="activity sport logo"
|
alt="workout sport logo"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="activity-title">
|
<td className="workout-title">
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
{t('common:Workout')}
|
{t('common:Workout')}
|
||||||
</span>
|
</span>
|
||||||
<Link to={`/activities/${activity.id}`}>
|
<Link to={`/workouts/${workout.id}`}>
|
||||||
{activity.title}
|
{workout.title}
|
||||||
</Link>
|
</Link>
|
||||||
{activity.map && (
|
{workout.map && (
|
||||||
<StaticMap activity={activity} display="list" />
|
<StaticMap workout={workout} display="list" />
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
{t('activities:Date')}
|
{t('workouts:Date')}
|
||||||
</span>
|
</span>
|
||||||
{format(
|
{format(
|
||||||
getDateWithTZ(activity.activity_date, user.timezone),
|
getDateWithTZ(workout.workout_date, user.timezone),
|
||||||
'dd/MM/yyyy HH:mm'
|
'dd/MM/yyyy HH:mm'
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
{t('activities:Distance')}
|
{t('workouts:Distance')}
|
||||||
</span>
|
</span>
|
||||||
{Number(activity.distance).toFixed(2)} km
|
{Number(workout.distance).toFixed(2)} km
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
{t('activities:Duration')}
|
{t('workouts:Duration')}
|
||||||
</span>
|
</span>
|
||||||
{activity.moving}
|
{workout.moving}
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
{t('activities:Ave. speed')}
|
{t('workouts:Ave. speed')}
|
||||||
</span>
|
</span>
|
||||||
{activity.ave_speed} km/h
|
{workout.ave_speed} km/h
|
||||||
</td>
|
</td>
|
||||||
<td className="text-right">
|
<td className="text-right">
|
||||||
<span className="heading-span-absolute">
|
<span className="heading-span-absolute">
|
||||||
{t('activities:Max. speed')}
|
{t('workouts:Max. speed')}
|
||||||
</span>
|
</span>
|
||||||
{activity.max_speed} km/h
|
{workout.max_speed} km/h
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
@ -3,14 +3,14 @@ import { Helmet } from 'react-helmet'
|
|||||||
import { withTranslation } from 'react-i18next'
|
import { withTranslation } from 'react-i18next'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import ActivitiesFilter from './ActivitiesFilter'
|
|
||||||
import ActivitiesList from './ActivitiesList'
|
|
||||||
import Message from '../Common/Message'
|
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 { 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) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -22,7 +22,7 @@ class Activities extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadActivities(this.state.params)
|
this.props.loadWorkouts(this.state.params)
|
||||||
}
|
}
|
||||||
|
|
||||||
setParams(e) {
|
setParams(e) {
|
||||||
@ -37,19 +37,19 @@ class Activities extends React.Component {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
activities,
|
|
||||||
loading,
|
loading,
|
||||||
loadActivities,
|
loadWorkouts,
|
||||||
loadMoreActivities,
|
loadMoreWorkouts,
|
||||||
message,
|
message,
|
||||||
sports,
|
sports,
|
||||||
t,
|
t,
|
||||||
user,
|
user,
|
||||||
|
workouts,
|
||||||
} = this.props
|
} = this.props
|
||||||
const { params } = this.state
|
const { params } = this.state
|
||||||
const paginationEnd =
|
const paginationEnd =
|
||||||
activities.length > 0
|
workouts.length > 0
|
||||||
? activities[activities.length - 1].previous_activity === null
|
? workouts[workouts.length - 1].previous_workout === null
|
||||||
: true
|
: true
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -62,16 +62,16 @@ class Activities extends React.Component {
|
|||||||
<div className="container history">
|
<div className="container history">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-3">
|
<div className="col-md-3">
|
||||||
<ActivitiesFilter
|
<WorkoutsFilter
|
||||||
sports={sports}
|
sports={sports}
|
||||||
loadActivities={() => loadActivities(params)}
|
loadWorkouts={() => loadWorkouts(params)}
|
||||||
t={t}
|
t={t}
|
||||||
updateParams={e => this.setParams(e)}
|
updateParams={e => this.setParams(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-9 activities-result">
|
<div className="col-md-9 workouts-result">
|
||||||
<ActivitiesList
|
<WorkoutsList
|
||||||
activities={activities}
|
workouts={workouts}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
sports={sports}
|
sports={sports}
|
||||||
t={t}
|
t={t}
|
||||||
@ -81,15 +81,15 @@ class Activities extends React.Component {
|
|||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-default btn-md btn-block"
|
className="btn btn-default btn-md btn-block"
|
||||||
value="Load more activities"
|
value="Load more workouts"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
params.page += 1
|
params.page += 1
|
||||||
loadMoreActivities(params)
|
loadMoreWorkouts(params)
|
||||||
this.setState(params)
|
this.setState(params)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activities.length === 0 && <NoActivities t={t} />}
|
{workouts.length === 0 && <NoWorkouts t={t} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -102,19 +102,19 @@ class Activities extends React.Component {
|
|||||||
export default withTranslation()(
|
export default withTranslation()(
|
||||||
connect(
|
connect(
|
||||||
state => ({
|
state => ({
|
||||||
activities: state.activities.data,
|
workouts: state.workouts.data,
|
||||||
loading: state.loading,
|
loading: state.loading,
|
||||||
message: state.message,
|
message: state.message,
|
||||||
sports: state.sports.data,
|
sports: state.sports.data,
|
||||||
user: state.user,
|
user: state.user,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivities: params => {
|
loadWorkouts: params => {
|
||||||
dispatch(getOrUpdateData('getData', 'activities', params))
|
dispatch(getOrUpdateData('getData', 'workouts', params))
|
||||||
},
|
},
|
||||||
loadMoreActivities: params => {
|
loadMoreWorkouts: params => {
|
||||||
dispatch(getMoreActivities(params))
|
dispatch(getMoreWorkouts(params))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)(Activities)
|
)(Workouts)
|
||||||
)
|
)
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Actions": "Actions",
|
"Actions": "Actions",
|
||||||
"Active": "Active",
|
"Active": "Active",
|
||||||
"activities exist": "activities exist",
|
"workouts exist": "workouts exist",
|
||||||
"Add admin rights": "Add admin rights",
|
"Add admin rights": "Add admin rights",
|
||||||
"Add/remove admin rights, delete user account.": "Add/remove admin rights, delete user account.",
|
"Add/remove admin rights, delete user account.": "Add/remove admin rights, delete user account.",
|
||||||
"Administration": "Administration",
|
"Administration": "Administration",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"activities count": "activities count",
|
"workouts count": "workouts count",
|
||||||
"Add workout": "Add workout",
|
"Add workout": "Add workout",
|
||||||
"admin rights": "admin rights",
|
"admin rights": "admin rights",
|
||||||
"ascending": "ascending",
|
"ascending": "ascending",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"3 to 12 characters required for username.": "3 to 12 characters required for username.",
|
"3 to 12 characters required for username.": "3 to 12 characters required for username.",
|
||||||
"8 characters required for password.": "8 characters required for password.",
|
"8 characters required for password.": "8 characters required for password.",
|
||||||
"An error occurred. Please contact the administrator.": "An error occurred. Please contact the administrator.",
|
"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 deletion.": "Error during picture deletion.",
|
||||||
"Error during picture update.": "Error during picture update.",
|
"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.",
|
"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 picture.": "No picture.",
|
||||||
"No selected file.": "No selected file.",
|
"No selected file.": "No selected file.",
|
||||||
"no correct file.": "no correct 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.",
|
"Password and password confirmation don't match.": "Password and password confirmation don't match.",
|
||||||
"Provide a valid auth token": "Provide a valid auth token",
|
"Provide a valid auth token": "Provide a valid auth token",
|
||||||
"records": "records",
|
"records": "records",
|
||||||
"Signature expired. Please log in again.": "Signature expired. Please log in again.",
|
"Signature expired. Please log in again.": "Signature expired. Please log in again.",
|
||||||
"Sorry. That user already exists.": "Sorry. That user already exists.",
|
"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.",
|
"Sport does not exist.": "Sport does not exist.",
|
||||||
"sports": "sports",
|
"sports": "sports",
|
||||||
"statistics": "statistiques",
|
"statistics": "statistiques",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import EnActivitiesTranslations from './activities.json'
|
import EnWorkoutsTranslations from './workouts.json'
|
||||||
import EnAdministrationTranslations from './administration.json'
|
import EnAdministrationTranslations from './administration.json'
|
||||||
import EnCommonTranslations from './common.json'
|
import EnCommonTranslations from './common.json'
|
||||||
import EnDashboardTranslations from './dashboard.json'
|
import EnDashboardTranslations from './dashboard.json'
|
||||||
@ -8,7 +8,7 @@ import EnStatisticsTranslations from './statistics.json'
|
|||||||
import EnUserTranslations from './user.json'
|
import EnUserTranslations from './user.json'
|
||||||
|
|
||||||
export const enResources = {
|
export const enResources = {
|
||||||
activities: EnActivitiesTranslations,
|
workouts: EnWorkoutsTranslations,
|
||||||
administration: EnAdministrationTranslations,
|
administration: EnAdministrationTranslations,
|
||||||
common: EnCommonTranslations,
|
common: EnCommonTranslations,
|
||||||
dashboard: EnDashboardTranslations,
|
dashboard: EnDashboardTranslations,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"activities": "activities",
|
"workouts": "workouts",
|
||||||
"distance": "distance",
|
"distance": "distance",
|
||||||
"duration": "duration",
|
"duration": "duration",
|
||||||
"month": "month",
|
"month": "month",
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"Activities": "Activities",
|
"Workouts": "Workouts",
|
||||||
"Activity": "Activity",
|
"Workout": "Workout",
|
||||||
"Activity Date": "Activity Date",
|
"Workout Date": "Workout Date",
|
||||||
"Add a workout": "Add a workout",
|
"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",
|
"Ave. speed": "Ave. speed",
|
||||||
"Ascent": "Ascent",
|
"Ascent": "Ascent",
|
||||||
"Average speed": "Average speed",
|
"Average speed": "Average speed",
|
||||||
"Chart": "Chart",
|
"Chart": "Chart",
|
||||||
"data from gpx, without any cleaning": "data from gpx, without any cleaning",
|
"data from gpx, without any cleaning": "data from gpx, without any cleaning",
|
||||||
"Date": "Date",
|
"Date": "Date",
|
||||||
"Delete activity": "Delete activity",
|
"Delete workout": "Delete workout",
|
||||||
"Descent": "Descent",
|
"Descent": "Descent",
|
||||||
"Distance": "Distance",
|
"Distance": "Distance",
|
||||||
"distance": "distance",
|
"distance": "distance",
|
||||||
"Duration": "Duration",
|
"Duration": "Duration",
|
||||||
"duration": "duration",
|
"duration": "duration",
|
||||||
"Edit a workout": "Edit a workout",
|
"Edit a workout": "Edit a workout",
|
||||||
"Edit activity": "Edit activity",
|
"Edit workout": "Edit workout",
|
||||||
"elevation": "elevation",
|
"elevation": "elevation",
|
||||||
"End": "End",
|
"End": "End",
|
||||||
"Farest distance": "Farest distance",
|
"Farest distance": "Farest distance",
|
||||||
@ -33,17 +33,17 @@
|
|||||||
"max size": "max size",
|
"max size": "max size",
|
||||||
"No data to display": "No data to display",
|
"No data to display": "No data to display",
|
||||||
"No Map": "No Map",
|
"No Map": "No Map",
|
||||||
"No next activity": "No next activity",
|
"No next workout": "No next workout",
|
||||||
"No next segment": "No next segment",
|
"No next segment": "No next segment",
|
||||||
"No notes": "No notes",
|
"No notes": "No notes",
|
||||||
"No previous activity": "No previous activity",
|
"No previous workout": "No previous workout",
|
||||||
"No previous segment": "No previous segment",
|
"No previous segment": "No previous segment",
|
||||||
"Notes": "Notes",
|
"Notes": "Notes",
|
||||||
"pauses": "pauses",
|
"pauses": "pauses",
|
||||||
"Personal records": "Personal records",
|
"Personal records": "Personal records",
|
||||||
"See next activity": "See next activity",
|
"See next workout": "See next workout",
|
||||||
"See next segment": "See next segment",
|
"See next segment": "See next segment",
|
||||||
"See previous activity": "See previous activity",
|
"See previous workout": "See previous workout",
|
||||||
"See previous segment": "See previous segment",
|
"See previous segment": "See previous segment",
|
||||||
"segment": "segment",
|
"segment": "segment",
|
||||||
"Segments": "Segments",
|
"Segments": "Segments",
|
@ -4,7 +4,7 @@
|
|||||||
"Add admin rights": "Ajouter des droits d'admin",
|
"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.",
|
"Add/remove admin rights, delete user account.": "Ajouter/retirer des droits d'adminsitration, supprimer des comptes utilisateurs.",
|
||||||
"Administration": "Administration",
|
"Administration": "Administration",
|
||||||
"activities exist": "des activités existent",
|
"workouts exist": "des séances existent",
|
||||||
"Application": "Application",
|
"Application": "Application",
|
||||||
"Application configuration": "Configuration de l'application",
|
"Application configuration": "Configuration de l'application",
|
||||||
"Back": "Retour",
|
"Back": "Retour",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"activities count": "nombre d'activités",
|
"workouts count": "nombre d'séances",
|
||||||
"Add workout": "Ajouter une activité",
|
"Add workout": "Ajouter une séance",
|
||||||
"admin rights": "droits d'admin",
|
"admin rights": "droits d'admin",
|
||||||
"ascending": "ascendant",
|
"ascending": "ascendant",
|
||||||
"Back": "Revenir à la page précédente",
|
"Back": "Revenir à la page précédente",
|
||||||
@ -16,7 +16,7 @@
|
|||||||
"No": "Non",
|
"No": "Non",
|
||||||
"no": "non",
|
"no": "non",
|
||||||
"No records.": "Pas de records.",
|
"No records.": "Pas de records.",
|
||||||
"No workouts.": "Pas d'activités.",
|
"No workouts.": "Pas d'séances.",
|
||||||
"Page not found": "Page introuvable",
|
"Page not found": "Page introuvable",
|
||||||
"Previous": "Page précédente",
|
"Previous": "Page précédente",
|
||||||
"registration date": "date d'inscription",
|
"registration date": "date d'inscription",
|
||||||
@ -30,10 +30,10 @@
|
|||||||
"Submit": "Valider",
|
"Submit": "Valider",
|
||||||
"to": "à",
|
"to": "à",
|
||||||
"user name": "utilisateur",
|
"user name": "utilisateur",
|
||||||
"Workout": "Activité",
|
"Workout": "Séance",
|
||||||
"Workouts": "Activités",
|
"Workouts": "Séances",
|
||||||
"workout": "activité",
|
"workout": "séance",
|
||||||
"workouts": "activités",
|
"workouts": "séances",
|
||||||
"Yes": "Oui",
|
"Yes": "Oui",
|
||||||
"yes": "oui"
|
"yes": "oui"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Personal records": "Mes records",
|
"Personal records": "Mes records",
|
||||||
"This month": "Ce mois",
|
"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.",
|
"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.",
|
"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.",
|
"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 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.": "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",
|
"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 picture.": "Pas d'image.",
|
||||||
"No selected file.": "Pas de fichier sélectionné.",
|
"No selected file.": "Pas de fichier sélectionné.",
|
||||||
"no correct file.": "fichier incorrect",
|
"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.",
|
"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",
|
"Provide a valid auth token": "Merci de fournir un jeton valide",
|
||||||
"records": "records",
|
"records": "records",
|
||||||
"Signature expired. Please log in again.": "Signature expirée. Merci de vous reconnecter.",
|
"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à.",
|
"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.",
|
"Sport does not exist.": "Le sport n'existe pas.",
|
||||||
"sports": "sports",
|
"sports": "sports",
|
||||||
"statistics": "statistics",
|
"statistics": "statistics",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import FrActivitiesTranslations from './activities.json'
|
import FrWorkoutsTranslations from './workouts.json'
|
||||||
import FrAdministrationTranslations from './administration.json'
|
import FrAdministrationTranslations from './administration.json'
|
||||||
import FrCommonTranslations from './common.json'
|
import FrCommonTranslations from './common.json'
|
||||||
import FrDashboardTranslations from './dashboard.json'
|
import FrDashboardTranslations from './dashboard.json'
|
||||||
@ -8,7 +8,7 @@ import FrStatisticsTranslations from './statistics.json'
|
|||||||
import FrUserTranslations from './user.json'
|
import FrUserTranslations from './user.json'
|
||||||
|
|
||||||
export const frResources = {
|
export const frResources = {
|
||||||
activities: FrActivitiesTranslations,
|
workouts: FrWorkoutsTranslations,
|
||||||
administration: FrAdministrationTranslations,
|
administration: FrAdministrationTranslations,
|
||||||
common: FrCommonTranslations,
|
common: FrCommonTranslations,
|
||||||
dashboard: FrDashboardTranslations,
|
dashboard: FrDashboardTranslations,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"activities": "activités",
|
"workouts": "séances",
|
||||||
"distance": "distance",
|
"distance": "distance",
|
||||||
"duration": "durée",
|
"duration": "durée",
|
||||||
"month": "mois",
|
"month": "mois",
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"Activities": "Activités",
|
"Workouts": "Séances",
|
||||||
"Activity": "Activité",
|
"Workout": "Séance",
|
||||||
"Activity Date": "Date de l'activité",
|
"Workout Date": "Date de l'séance",
|
||||||
"Add a workout": "Ajouter une activité",
|
"Add a workout": "Ajouter une séance",
|
||||||
"Are you sure you want to delete this activity?": "Etes-vous sûr de vouloir supprimer cette activité ?",
|
"Are you sure you want to delete this workout?": "Etes-vous sûr de vouloir supprimer cette séance ?",
|
||||||
"Ave. speed": "Vitesse moyenne",
|
"Ave. speed": "Vitesse moyenne",
|
||||||
"Ascent": "Dénivelé positif",
|
"Ascent": "Dénivelé positif",
|
||||||
"Average speed": "Vitesse moyenne",
|
"Average speed": "Vitesse moyenne",
|
||||||
"Chart": "Analyse",
|
"Chart": "Analyse",
|
||||||
"data from gpx, without any cleaning": "données issues du fichier gpx, sans correction",
|
"data from gpx, without any cleaning": "données issues du fichier gpx, sans correction",
|
||||||
"Date": "Date",
|
"Date": "Date",
|
||||||
"Delete activity": "Supprimer l'activité",
|
"Delete workout": "Supprimer l'séance",
|
||||||
"Descent": "Dénivelé négatif",
|
"Descent": "Dénivelé négatif",
|
||||||
"Distance": "Distance",
|
"Distance": "Distance",
|
||||||
"distance": "distance",
|
"distance": "distance",
|
||||||
"Duration": "Durée",
|
"Duration": "Durée",
|
||||||
"duration": "durée",
|
"duration": "durée",
|
||||||
"Edit a workout": "Editer une activité",
|
"Edit a workout": "Editer une séance",
|
||||||
"Edit activity": "Editer une activity",
|
"Edit workout": "Editer une workout",
|
||||||
"elevation": "altitude",
|
"elevation": "altitude",
|
||||||
"End": "Arrivée",
|
"End": "Arrivée",
|
||||||
"Farest distance": "Distance la + longue",
|
"Farest distance": "Distance la + longue",
|
||||||
@ -33,17 +33,17 @@
|
|||||||
"max size": "taille max",
|
"max size": "taille max",
|
||||||
"No data to display": "Pas de données à afficher",
|
"No data to display": "Pas de données à afficher",
|
||||||
"No Map": "Pas de carte",
|
"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 next segment": "Pas de segment suivant",
|
||||||
"No notes": "Pas de notes",
|
"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",
|
"No previous segment": "Pas de segment précédent",
|
||||||
"Notes": "Notes",
|
"Notes": "Notes",
|
||||||
"pauses": "pauses",
|
"pauses": "pauses",
|
||||||
"Personal records": "Records personnels",
|
"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 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",
|
"See previous segment": "Voir le segment précédent",
|
||||||
"segment": "segment",
|
"segment": "segment",
|
||||||
"Segments": "Segments",
|
"Segments": "Segments",
|
@ -23,22 +23,22 @@ const handleDataAndError = (state, type, action) => {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
const activities = (state = initial.activities, action) => {
|
const workouts = (state = initial.workouts, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'LOGOUT':
|
case 'LOGOUT':
|
||||||
return initial.activities
|
return initial.workouts
|
||||||
case 'PUSH_ACTIVITIES':
|
case 'PUSH_WORKOUTS':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
data: state.data.concat(action.activities),
|
data: state.data.concat(action.workouts),
|
||||||
}
|
}
|
||||||
case 'REMOVE_ACTIVITY':
|
case 'REMOVE_WORKOUT':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
data: state.data.filter(activity => activity.id !== action.activityId),
|
data: state.data.filter(workout => workout.id !== action.workoutId),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return handleDataAndError(state, 'activities', action)
|
return handleDataAndError(state, 'workouts', action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,17 +58,17 @@ const application = (state = initial.application, action) => {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
const calendarActivities = (state = initial.calendarActivities, action) => {
|
const calendarWorkouts = (state = initial.calendarWorkouts, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'LOGOUT':
|
case 'LOGOUT':
|
||||||
return initial.calendarActivities
|
return initial.calendarWorkouts
|
||||||
case 'UPDATE_CALENDAR':
|
case 'UPDATE_CALENDAR':
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
data: action.activities,
|
data: action.workouts,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return handleDataAndError(state, 'calendarActivities', action)
|
return handleDataAndError(state, 'calendarWorkouts', action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,9 +191,9 @@ const statistics = (state = initial.statistics, action) => {
|
|||||||
|
|
||||||
export default history =>
|
export default history =>
|
||||||
combineReducers({
|
combineReducers({
|
||||||
activities,
|
workouts,
|
||||||
application,
|
application,
|
||||||
calendarActivities,
|
calendarWorkouts,
|
||||||
chartData,
|
chartData,
|
||||||
gpx,
|
gpx,
|
||||||
language,
|
language,
|
||||||
|
@ -9,7 +9,7 @@ export default {
|
|||||||
user: {
|
user: {
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
},
|
},
|
||||||
activities: {
|
workouts: {
|
||||||
...emptyData,
|
...emptyData,
|
||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
@ -23,7 +23,7 @@ export default {
|
|||||||
registration: null,
|
registration: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
calendarActivities: {
|
calendarWorkouts: {
|
||||||
...emptyData,
|
...emptyData,
|
||||||
},
|
},
|
||||||
chartData: [],
|
chartData: [],
|
||||||
|
@ -24,7 +24,7 @@ export const apiUrl =
|
|||||||
: `${process.env.REACT_APP_API_URL}/api/`
|
: `${process.env.REACT_APP_API_URL}/api/`
|
||||||
|
|
||||||
export const userFilters = [
|
export const userFilters = [
|
||||||
{ key: 'activities_count', label: 'activities count' },
|
{ key: 'workouts_count', label: 'workouts count' },
|
||||||
{ key: 'admin', label: 'admin rights' },
|
{ key: 'admin', label: 'admin rights' },
|
||||||
{ key: 'created_at', label: 'registration date' },
|
{ key: 'created_at', label: 'registration date' },
|
||||||
{ key: 'username', label: 'user name' },
|
{ key: 'username', label: 'user name' },
|
||||||
|
@ -66,7 +66,7 @@ const startDate = (duration, day, weekm) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const formatStats = (stats, sports, params, displayedSports, weekm) => {
|
export const formatStats = (stats, sports, params, displayedSports, weekm) => {
|
||||||
const nbActivitiesStats = []
|
const nbWorkoutsStats = []
|
||||||
const distanceStats = []
|
const distanceStats = []
|
||||||
const durationStats = []
|
const durationStats = []
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export const formatStats = (stats, sports, params, displayedSports, weekm) => {
|
|||||||
)
|
)
|
||||||
const date = format(day, xAxisFormat.dateFormat)
|
const date = format(day, xAxisFormat.dateFormat)
|
||||||
const xAxis = format(day, xAxisFormat.xAxis)
|
const xAxis = format(day, xAxisFormat.xAxis)
|
||||||
const dataNbActivities = { date: xAxis }
|
const dataNbWorkouts = { date: xAxis }
|
||||||
const dataDistance = { date: xAxis }
|
const dataDistance = { date: xAxis }
|
||||||
const dataDuration = { date: xAxis }
|
const dataDuration = { date: xAxis }
|
||||||
|
|
||||||
@ -91,19 +91,19 @@ export const formatStats = (stats, sports, params, displayedSports, weekm) => {
|
|||||||
)
|
)
|
||||||
.map(sportId => {
|
.map(sportId => {
|
||||||
const sportLabel = sports.filter(s => s.id === +sportId)[0].label
|
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
|
dataDistance[sportLabel] = stats[date][sportId].total_distance
|
||||||
dataDuration[sportLabel] = stats[date][sportId].total_duration
|
dataDuration[sportLabel] = stats[date][sportId].total_duration
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
nbActivitiesStats.push(dataNbActivities)
|
nbWorkoutsStats.push(dataNbWorkouts)
|
||||||
distanceStats.push(dataDistance)
|
distanceStats.push(dataDistance)
|
||||||
durationStats.push(dataDuration)
|
durationStats.push(dataDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activities: nbActivitiesStats,
|
workouts: nbWorkoutsStats,
|
||||||
distance: distanceStats,
|
distance: distanceStats,
|
||||||
duration: durationStats,
|
duration: durationStats,
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import togeojson from '@mapbox/togeojson'
|
|||||||
|
|
||||||
import { getDateWithTZ } from './index'
|
import { getDateWithTZ } from './index'
|
||||||
|
|
||||||
export const activityColors = [
|
export const workoutColors = [
|
||||||
'#55a8a3',
|
'#55a8a3',
|
||||||
'#98C3A9',
|
'#98C3A9',
|
||||||
'#D0838A',
|
'#D0838A',
|
||||||
@ -29,7 +29,7 @@ export const getGeoJson = gpxContent => {
|
|||||||
return { jsonData }
|
return { jsonData }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatActivityDate = (
|
export const formatWorkoutDate = (
|
||||||
dateTime,
|
dateTime,
|
||||||
dateFormat = null,
|
dateFormat = null,
|
||||||
timeFormat = null
|
timeFormat = null
|
||||||
@ -41,12 +41,12 @@ export const formatActivityDate = (
|
|||||||
timeFormat = 'HH:mm'
|
timeFormat = 'HH:mm'
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
activity_date: dateTime ? format(dateTime, dateFormat) : null,
|
workout_date: dateTime ? format(dateTime, dateFormat) : null,
|
||||||
activity_time: dateTime ? format(dateTime, timeFormat) : null,
|
workout_time: dateTime ? format(dateTime, timeFormat) : null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatActivityDuration = seconds => {
|
export const formatWorkoutDuration = seconds => {
|
||||||
let newDate = new Date(0)
|
let newDate = new Date(0)
|
||||||
newDate = subHours(newDate.setSeconds(seconds), 1)
|
newDate = subHours(newDate.setSeconds(seconds), 1)
|
||||||
return newDate.getTime()
|
return newDate.getTime()
|
||||||
@ -55,7 +55,7 @@ export const formatActivityDuration = seconds => {
|
|||||||
export const formatChartData = chartData => {
|
export const formatChartData = chartData => {
|
||||||
for (let i = 0; i < chartData.length; i++) {
|
for (let i = 0; i < chartData.length; i++) {
|
||||||
chartData[i].time = new Date(chartData[i].time).getTime()
|
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
|
return chartData
|
||||||
}
|
}
|
||||||
@ -78,9 +78,9 @@ export const formatRecord = (record, tz) => {
|
|||||||
r => r.record_type === record.record_type
|
r => r.record_type === record.record_type
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
activity_date: formatActivityDate(getDateWithTZ(record.activity_date, tz))
|
workout_date: formatWorkoutDate(getDateWithTZ(record.workout_date, tz))
|
||||||
.activity_date,
|
.workout_date,
|
||||||
activity_id: record.activity_id,
|
workout_id: record.workout_id,
|
||||||
id: record.id,
|
id: record.id,
|
||||||
record_type: recordType.label,
|
record_type: recordType.label,
|
||||||
value: value,
|
value: value,
|
Loading…
Reference in New Issue
Block a user