Client - reformat js files w/ prettier
This commit is contained in:
parent
c8ea44eecc
commit
2a52b9081d
@ -29,155 +29,154 @@ export const setChartData = chartData => ({
|
|||||||
chartData,
|
chartData,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const addActivity = form => dispatch => FitTrackeeGenericApi
|
export const addActivity = form => dispatch =>
|
||||||
.addDataWithFile('activities', form)
|
FitTrackeeGenericApi.addDataWithFile('activities', form)
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'created') {
|
if (ret.status === 'created') {
|
||||||
if (ret.data.activities.length === 0) {
|
if (ret.data.activities.length === 0) {
|
||||||
dispatch(setError('activities: no correct file'))
|
dispatch(setError('activities: no correct file'))
|
||||||
} else if (ret.data.activities.length === 1) {
|
} else if (ret.data.activities.length === 1) {
|
||||||
dispatch(loadProfile())
|
dispatch(loadProfile())
|
||||||
history.push(`/activities/${ret.data.activities[0].id}`)
|
history.push(`/activities/${ret.data.activities[0].id}`)
|
||||||
} else { // ret.data.activities.length > 1
|
} else {
|
||||||
|
// ret.data.activities.length > 1
|
||||||
dispatch(loadProfile())
|
dispatch(loadProfile())
|
||||||
history.push('/')
|
history.push('/')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
}
|
}
|
||||||
} else {
|
dispatch(setLoading(false))
|
||||||
dispatch(setError(`activities: ${ret.message}`))
|
})
|
||||||
}
|
.catch(error => dispatch(setError(`activities: ${error}`)))
|
||||||
dispatch(setLoading(false))
|
|
||||||
})
|
|
||||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
|
||||||
|
|
||||||
|
export const addActivityWithoutGpx = form => dispatch =>
|
||||||
export const addActivityWithoutGpx = form => dispatch => FitTrackeeGenericApi
|
FitTrackeeGenericApi.addData('activities/no_gpx', form)
|
||||||
.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 => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'created') {
|
||||||
dispatch(setGpx(ret.data.gpx))
|
dispatch(loadProfile())
|
||||||
|
history.push(`/activities/${ret.data.activities[0].id}`)
|
||||||
} else {
|
} else {
|
||||||
dispatch(setError(`activities: ${ret.message}`))
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
.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))
|
dispatch(setGpx(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSegmentGpx = (activityId, segmentId) => dispatch => {
|
export const getSegmentGpx = (activityId, segmentId) => dispatch => {
|
||||||
if (activityId) {
|
if (activityId) {
|
||||||
return FitTrackeeGenericApi
|
return FitTrackeeGenericApi.getData(
|
||||||
.getData(`activities/${activityId}/gpx/segment/${segmentId}`)
|
`activities/${activityId}/gpx/segment/${segmentId}`
|
||||||
.then(ret => {
|
)
|
||||||
if (ret.status === 'success') {
|
.then(ret => {
|
||||||
dispatch(setGpx(ret.data.gpx))
|
if (ret.status === 'success') {
|
||||||
} else {
|
dispatch(setGpx(ret.data.gpx))
|
||||||
dispatch(setError(`activities: ${ret.message}`))
|
} else {
|
||||||
}
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
})
|
}
|
||||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
})
|
||||||
|
.catch(error => dispatch(setError(`activities: ${error}`)))
|
||||||
}
|
}
|
||||||
dispatch(setGpx(null))
|
dispatch(setGpx(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getActivityChartData = activityId => dispatch => {
|
export const getActivityChartData = activityId => dispatch => {
|
||||||
if (activityId) {
|
if (activityId) {
|
||||||
return FitTrackeeGenericApi
|
return FitTrackeeGenericApi.getData(`activities/${activityId}/chart_data`)
|
||||||
.getData(`activities/${activityId}/chart_data`)
|
.then(ret => {
|
||||||
.then(ret => {
|
if (ret.status === 'success') {
|
||||||
if (ret.status === 'success') {
|
dispatch(setChartData(formatChartData(ret.data.chart_data)))
|
||||||
dispatch(setChartData(formatChartData(ret.data.chart_data)))
|
} else {
|
||||||
} else {
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
dispatch(setError(`activities: ${ret.message}`))
|
}
|
||||||
}
|
})
|
||||||
})
|
.catch(error => dispatch(setError(`activities: ${error}`)))
|
||||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
|
||||||
}
|
}
|
||||||
dispatch(setChartData(null))
|
dispatch(setChartData(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSegmentChartData = (activityId, segmentId) => dispatch => {
|
export const getSegmentChartData = (activityId, segmentId) => dispatch => {
|
||||||
if (activityId) {
|
if (activityId) {
|
||||||
return FitTrackeeGenericApi
|
return FitTrackeeGenericApi.getData(
|
||||||
.getData(`activities/${activityId}/chart_data/segment/${segmentId}`)
|
`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 => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
dispatch(setChartData(formatChartData(ret.data.chart_data)))
|
dispatch(loadProfile())
|
||||||
|
history.push(`/activities/${ret.data.activities[0].id}`)
|
||||||
|
} else {
|
||||||
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
|
}
|
||||||
|
dispatch(setLoading(false))
|
||||||
|
})
|
||||||
|
.catch(error => 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 {
|
} else {
|
||||||
dispatch(setError(`activities: ${ret.message}`))
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
.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(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 =>
|
export const getMonthActivities = (from, to) => dispatch =>
|
||||||
FitTrackeeGenericApi
|
FitTrackeeGenericApi.getData('activities', {
|
||||||
.getData('activities', { from, to, order: 'asc', per_page: 100 })
|
from,
|
||||||
.then(ret => {
|
to,
|
||||||
if (ret.status === 'success') {
|
order: 'asc',
|
||||||
dispatch(updateCalendar(ret.data.activities))
|
per_page: 100,
|
||||||
} else {
|
|
||||||
dispatch(setError(`activities: ${ret.message}`))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
.then(ret => {
|
||||||
|
if (ret.status === 'success') {
|
||||||
|
dispatch(updateCalendar(ret.data.activities))
|
||||||
|
} else {
|
||||||
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => dispatch(setError(`activities: ${error}`)))
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import FitTrackeeApi from '../fitTrackeeApi/index'
|
import FitTrackeeApi from '../fitTrackeeApi/index'
|
||||||
import { history } from '../index'
|
import { history } from '../index'
|
||||||
|
|
||||||
|
|
||||||
export const setData = (target, data) => ({
|
export const setData = (target, data) => ({
|
||||||
type: 'SET_DATA',
|
type: 'SET_DATA',
|
||||||
data,
|
data,
|
||||||
@ -15,7 +14,7 @@ export const setError = message => ({
|
|||||||
|
|
||||||
export const setLoading = loading => ({
|
export const setLoading = loading => ({
|
||||||
type: 'SET_LOADING',
|
type: 'SET_LOADING',
|
||||||
loading
|
loading,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getOrUpdateData = (action, target, data) => dispatch => {
|
export const getOrUpdateData = (action, target, data) => dispatch => {
|
||||||
@ -23,39 +22,38 @@ export const getOrUpdateData = (action, target, data) => dispatch => {
|
|||||||
return dispatch(setError(target, `${target}: Incorrect id`))
|
return dispatch(setError(target, `${target}: Incorrect id`))
|
||||||
}
|
}
|
||||||
return FitTrackeeApi[action](target, data)
|
return FitTrackeeApi[action](target, data)
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
dispatch(setData(target, ret.data))
|
dispatch(setData(target, ret.data))
|
||||||
} else {
|
} else {
|
||||||
dispatch(setError(`${target}: ${ret.message || ret.status}`))
|
dispatch(setError(`${target}: ${ret.message || ret.status}`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addData = (target, data) => dispatch => FitTrackeeApi
|
export const addData = (target, data) => dispatch =>
|
||||||
.addData(target, data)
|
FitTrackeeApi.addData(target, data)
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'created') {
|
if (ret.status === 'created') {
|
||||||
history.push(`/admin/${target}`)
|
history.push(`/admin/${target}`)
|
||||||
} else {
|
} else {
|
||||||
dispatch(setError(`${target}: ${ret.status}`))
|
dispatch(setError(`${target}: ${ret.status}`))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
||||||
|
|
||||||
export const deleteData = (target, id) => dispatch => {
|
export const deleteData = (target, id) => dispatch => {
|
||||||
if (isNaN(id)) {
|
if (isNaN(id)) {
|
||||||
return dispatch(setError(target, `${target}: Incorrect id`))
|
return dispatch(setError(target, `${target}: Incorrect id`))
|
||||||
}
|
}
|
||||||
return FitTrackeeApi
|
return FitTrackeeApi.deleteData(target, id)
|
||||||
.deleteData(target, id)
|
.then(ret => {
|
||||||
.then(ret => {
|
if (ret.status === 204) {
|
||||||
if (ret.status === 204) {
|
history.push(`/admin/${target}`)
|
||||||
history.push(`/admin/${target}`)
|
} else {
|
||||||
} else {
|
dispatch(setError(`${target}: ${ret.message || ret.status}`))
|
||||||
dispatch(setError(`${target}: ${ret.message || ret.status}`))
|
}
|
||||||
}
|
})
|
||||||
})
|
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
||||||
.catch(error => dispatch(setError(`${target}: ${error}`)))
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import FitTrackeeGenericApi from '../fitTrackeeApi'
|
import FitTrackeeGenericApi from '../fitTrackeeApi'
|
||||||
import { setData, setError } from './index'
|
import { setData, setError } from './index'
|
||||||
|
|
||||||
export const getStats = (userId, type, data) => dispatch => FitTrackeeGenericApi
|
export const getStats = (userId, type, data) => dispatch =>
|
||||||
.getData(`stats/${userId}/${type}`, data)
|
FitTrackeeGenericApi.getData(`stats/${userId}/${type}`, data)
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
dispatch(setData('statistics', ret.data))
|
dispatch(setData('statistics', ret.data))
|
||||||
} else {
|
} else {
|
||||||
dispatch(setError(`statistics: ${ret.message}`))
|
dispatch(setError(`statistics: ${ret.message}`))
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import { history } from '../index'
|
|||||||
import { generateIds } from '../utils'
|
import { generateIds } from '../utils'
|
||||||
import { getOrUpdateData } from './index'
|
import { getOrUpdateData } from './index'
|
||||||
|
|
||||||
|
|
||||||
const AuthError = message => ({ type: 'AUTH_ERROR', message })
|
const AuthError = message => ({ type: 'AUTH_ERROR', message })
|
||||||
|
|
||||||
const AuthErrors = messages => ({ type: 'AUTH_ERRORS', messages })
|
const AuthErrors = messages => ({ type: 'AUTH_ERRORS', messages })
|
||||||
@ -16,7 +15,8 @@ const ProfileSuccess = profil => ({ type: 'PROFILE_SUCCESS', profil })
|
|||||||
const ProfileError = message => ({ type: 'PROFILE_ERROR', message })
|
const ProfileError = message => ({ type: 'PROFILE_ERROR', message })
|
||||||
|
|
||||||
const ProfileUpdateError = message => ({
|
const ProfileUpdateError = message => ({
|
||||||
type: 'PROFILE_UPDATE_ERROR', message
|
type: 'PROFILE_UPDATE_ERROR',
|
||||||
|
message,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const logout = () => ({ type: 'LOGOUT' })
|
export const logout = () => ({ type: 'LOGOUT' })
|
||||||
@ -28,32 +28,32 @@ export const loadProfile = () => dispatch => {
|
|||||||
return { type: 'LOGOUT' }
|
return { type: 'LOGOUT' }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getProfile = () => dispatch => FitTrackeeGenericApi
|
export const getProfile = () => dispatch =>
|
||||||
.getData('auth/profile')
|
FitTrackeeGenericApi.getData('auth/profile')
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
dispatch(getOrUpdateData('getData', 'sports'))
|
dispatch(getOrUpdateData('getData', 'sports'))
|
||||||
ret.data.isAuthenticated = true
|
ret.data.isAuthenticated = true
|
||||||
return dispatch(ProfileSuccess(ret.data))
|
return dispatch(ProfileSuccess(ret.data))
|
||||||
}
|
}
|
||||||
return dispatch(ProfileError(ret.message))
|
return dispatch(ProfileError(ret.message))
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loginOrRegister = (target, formData) => dispatch => FitTrackeeApi
|
export const loginOrRegister = (target, formData) => dispatch =>
|
||||||
.loginOrRegister(target, formData)
|
FitTrackeeApi.loginOrRegister(target, formData)
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
window.localStorage.setItem('authToken', ret.auth_token)
|
window.localStorage.setItem('authToken', ret.auth_token)
|
||||||
return dispatch(getProfile())
|
return dispatch(getProfile())
|
||||||
}
|
}
|
||||||
return dispatch(AuthError(ret.message))
|
return dispatch(AuthError(ret.message))
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
|
||||||
const RegisterFormControl = formData => {
|
const RegisterFormControl = formData => {
|
||||||
const errMsg = []
|
const errMsg = []
|
||||||
@ -61,7 +61,7 @@ const RegisterFormControl = formData => {
|
|||||||
errMsg.push('Username: 3 to 12 characters required.')
|
errMsg.push('Username: 3 to 12 characters required.')
|
||||||
}
|
}
|
||||||
if (formData.password !== formData.password_conf) {
|
if (formData.password !== formData.password_conf) {
|
||||||
errMsg.push('Password and password confirmation don\'t match.')
|
errMsg.push("Password and password confirmation don't match.")
|
||||||
}
|
}
|
||||||
if (formData.password.length < 8) {
|
if (formData.password.length < 8) {
|
||||||
errMsg.push('Password: 8 characters required.')
|
errMsg.push('Password: 8 characters required.')
|
||||||
@ -81,13 +81,12 @@ export const handleUserFormSubmit = (formData, formType) => dispatch => {
|
|||||||
|
|
||||||
export const handleProfileFormSubmit = formData => dispatch => {
|
export const handleProfileFormSubmit = formData => dispatch => {
|
||||||
if (!formData.password === formData.password_conf) {
|
if (!formData.password === formData.password_conf) {
|
||||||
return dispatch(ProfileUpdateError(
|
return dispatch(
|
||||||
'Password and password confirmation don\'t match.'
|
ProfileUpdateError("Password and password confirmation don't match.")
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
delete formData.id
|
delete formData.id
|
||||||
return FitTrackeeGenericApi
|
return FitTrackeeGenericApi.postData('auth/profile/edit', formData)
|
||||||
.postData('auth/profile/edit', formData)
|
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
dispatch(getProfile())
|
dispatch(getProfile())
|
||||||
@ -105,8 +104,7 @@ export const uploadPicture = event => dispatch => {
|
|||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
form.append('file', event.target.picture.files[0])
|
form.append('file', event.target.picture.files[0])
|
||||||
event.target.reset()
|
event.target.reset()
|
||||||
return FitTrackeeGenericApi
|
return FitTrackeeGenericApi.addDataWithFile('auth/picture', form)
|
||||||
.addDataWithFile('auth/picture', form)
|
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 'success') {
|
if (ret.status === 'success') {
|
||||||
return dispatch(getProfile())
|
return dispatch(getProfile())
|
||||||
@ -118,14 +116,14 @@ export const uploadPicture = event => dispatch => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deletePicture = () => dispatch => FitTrackeeApi
|
export const deletePicture = () => dispatch =>
|
||||||
.deletePicture()
|
FitTrackeeApi.deletePicture()
|
||||||
.then(ret => {
|
.then(ret => {
|
||||||
if (ret.status === 204) {
|
if (ret.status === 204) {
|
||||||
return dispatch(getProfile())
|
return dispatch(getProfile())
|
||||||
}
|
}
|
||||||
return dispatch(PictureError(ret.message))
|
return dispatch(PictureError(ret.message))
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
|
@ -12,48 +12,49 @@ export default class ActivitiesList extends React.PureComponent {
|
|||||||
<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">Workout</th>
|
<th scope="col">Workout</th>
|
||||||
<th scope="col">Date</th>
|
<th scope="col">Date</th>
|
||||||
<th scope="col">Distance</th>
|
<th scope="col">Distance</th>
|
||||||
<th scope="col">Duration</th>
|
<th scope="col">Duration</th>
|
||||||
<th scope="col">Ave. speed</th>
|
<th scope="col">Ave. speed</th>
|
||||||
<th scope="col">Max. speed</th>
|
<th scope="col">Max. speed</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{sports && activities.map((activity, idx) => (
|
{sports &&
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
activities.map((activity, idx) => (
|
||||||
<tr key={idx}>
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<td>
|
<tr key={idx}>
|
||||||
<img
|
<td>
|
||||||
className="activity-sport"
|
<img
|
||||||
src={sports
|
className="activity-sport"
|
||||||
.filter(s => s.id === activity.sport_id)
|
src={sports
|
||||||
.map(s => s.img)}
|
.filter(s => s.id === activity.sport_id)
|
||||||
alt="activity sport logo"
|
.map(s => s.img)}
|
||||||
/>
|
alt="activity sport logo"
|
||||||
</td>
|
/>
|
||||||
<td>
|
</td>
|
||||||
<Link to={`/activities/${activity.id}`}>
|
<td>
|
||||||
{activity.title}
|
<Link to={`/activities/${activity.id}`}>
|
||||||
</Link>
|
{activity.title}
|
||||||
</td>
|
</Link>
|
||||||
<td>
|
</td>
|
||||||
{format(
|
<td>
|
||||||
getDateWithTZ(activity.activity_date, user.timezone),
|
{format(
|
||||||
'dd/MM/yyyy HH:mm'
|
getDateWithTZ(activity.activity_date, user.timezone),
|
||||||
)}
|
'dd/MM/yyyy HH:mm'
|
||||||
</td>
|
)}
|
||||||
<td className="text-right">
|
</td>
|
||||||
{Number(activity.distance).toFixed(2)} km
|
<td className="text-right">
|
||||||
</td>
|
{Number(activity.distance).toFixed(2)} km
|
||||||
<td className="text-right">{activity.moving}</td>
|
</td>
|
||||||
<td className="text-right">{activity.ave_speed} km/h</td>
|
<td className="text-right">{activity.moving}</td>
|
||||||
<td className="text-right">{activity.max_speed} km/h</td>
|
<td className="text-right">{activity.ave_speed} km/h</td>
|
||||||
</tr>
|
<td className="text-right">{activity.max_speed} km/h</td>
|
||||||
))}
|
</tr>
|
||||||
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,6 @@ import ActivitiesList from './ActivitiesList'
|
|||||||
import { getOrUpdateData } from '../../actions'
|
import { getOrUpdateData } from '../../actions'
|
||||||
import { getMoreActivities } from '../../actions/activities'
|
import { getMoreActivities } from '../../actions/activities'
|
||||||
|
|
||||||
|
|
||||||
class Activities extends React.Component {
|
class Activities extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
@ -36,12 +35,18 @@ class Activities extends React.Component {
|
|||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
activities, loadActivities, loadMoreActivities, message, sports, user
|
activities,
|
||||||
|
loadActivities,
|
||||||
|
loadMoreActivities,
|
||||||
|
message,
|
||||||
|
sports,
|
||||||
|
user,
|
||||||
} = this.props
|
} = this.props
|
||||||
const { params } = this.state
|
const { params } = this.state
|
||||||
const paginationEnd = activities.length > 0
|
const paginationEnd =
|
||||||
? activities[activities.length - 1].previous_activity === null
|
activities.length > 0
|
||||||
: true
|
? activities[activities.length - 1].previous_activity === null
|
||||||
|
: true
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -65,7 +70,7 @@ class Activities extends React.Component {
|
|||||||
sports={sports}
|
sports={sports}
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
{!paginationEnd &&
|
{!paginationEnd && (
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-default btn-md btn-block"
|
className="btn btn-default btn-md btn-block"
|
||||||
@ -76,11 +81,11 @@ class Activities extends React.Component {
|
|||||||
this.setState(params)
|
this.setState(params)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{activities.length === 0 && (
|
{activities.length === 0 && (
|
||||||
<div className="card text-center">
|
<div className="card text-center">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
No workouts. {' '}
|
No workouts.{' '}
|
||||||
<Link to={{ pathname: '/activities/add' }}>
|
<Link to={{ pathname: '/activities/add' }}>
|
||||||
Upload one !
|
Upload one !
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -3,24 +3,17 @@ import { connect } from 'react-redux'
|
|||||||
|
|
||||||
import ActivityAddOrEdit from './ActivityAddOrEdit'
|
import ActivityAddOrEdit from './ActivityAddOrEdit'
|
||||||
|
|
||||||
|
function ActivityAdd(props) {
|
||||||
function ActivityAdd (props) {
|
|
||||||
const { message, sports } = props
|
const { message, sports } = props
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ActivityAddOrEdit
|
<ActivityAddOrEdit activity={null} message={message} sports={sports} />
|
||||||
activity={null}
|
|
||||||
message={message}
|
|
||||||
sports={sports}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(state => ({
|
||||||
state => ({
|
message: state.message,
|
||||||
message: state.message,
|
sports: state.sports.data,
|
||||||
sports: state.sports.data,
|
user: state.user,
|
||||||
user: state.user,
|
}))(ActivityAdd)
|
||||||
}),
|
|
||||||
)(ActivityAdd)
|
|
||||||
|
@ -13,11 +13,12 @@ class ActivityAddEdit extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRadioChange (changeEvent) {
|
handleRadioChange(changeEvent) {
|
||||||
this.setState({
|
this.setState({
|
||||||
withGpx:
|
withGpx:
|
||||||
changeEvent.target.name === 'withGpx'
|
changeEvent.target.name === 'withGpx'
|
||||||
? changeEvent.target.value : !changeEvent.target.value
|
? changeEvent.target.value
|
||||||
|
: !changeEvent.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,15 +28,13 @@ class ActivityAddEdit extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - {activity
|
<title>
|
||||||
? 'Edit a workout'
|
FitTrackee - {activity ? 'Edit a workout' : 'Add a workout'}
|
||||||
: 'Add a workout'}
|
</title>
|
||||||
</title>
|
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<br /><br />
|
<br />
|
||||||
{message && (
|
<br />
|
||||||
<code>{message}</code>
|
{message && <code>{message}</code>}
|
||||||
)}
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-2" />
|
<div className="col-md-2" />
|
||||||
@ -57,27 +56,31 @@ class ActivityAddEdit extends React.Component {
|
|||||||
<div className="form-group row">
|
<div className="form-group row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<label className="radioLabel">
|
<label className="radioLabel">
|
||||||
<input
|
<input
|
||||||
className="add-activity-radio"
|
className="add-activity-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="withGpx"
|
name="withGpx"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
checked={withGpx}
|
checked={withGpx}
|
||||||
onChange={event => this.handleRadioChange(event)}
|
onChange={event =>
|
||||||
/>
|
this.handleRadioChange(event)
|
||||||
|
}
|
||||||
|
/>
|
||||||
with gpx file
|
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-activity-radio"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="withoutGpx"
|
name="withoutGpx"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
checked={!withGpx}
|
checked={!withGpx}
|
||||||
onChange={event => this.handleRadioChange(event)}
|
onChange={event =>
|
||||||
/>
|
this.handleRadioChange(event)
|
||||||
|
}
|
||||||
|
/>
|
||||||
without gpx file
|
without gpx file
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -101,8 +104,6 @@ class ActivityAddEdit extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(state => ({
|
||||||
state => ({
|
loading: state.loading,
|
||||||
loading: state.loading
|
}))(ActivityAddEdit)
|
||||||
}),
|
|
||||||
)(ActivityAddEdit)
|
|
||||||
|
@ -4,26 +4,30 @@ import { Link } from 'react-router-dom'
|
|||||||
import { getDateWithTZ } from '../../../utils'
|
import { getDateWithTZ } from '../../../utils'
|
||||||
import { formatActivityDate } from '../../../utils/activities'
|
import { formatActivityDate } from '../../../utils/activities'
|
||||||
|
|
||||||
|
|
||||||
export default function ActivityCardHeader(props) {
|
export default function ActivityCardHeader(props) {
|
||||||
const {
|
const {
|
||||||
activity, dataType, displayModal, segmentId, sport, title, user
|
activity,
|
||||||
|
dataType,
|
||||||
|
displayModal,
|
||||||
|
segmentId,
|
||||||
|
sport,
|
||||||
|
title,
|
||||||
|
user,
|
||||||
} = props
|
} = props
|
||||||
const activityDate = activity
|
const activityDate = activity
|
||||||
? formatActivityDate(
|
? formatActivityDate(getDateWithTZ(activity.activity_date, user.timezone))
|
||||||
getDateWithTZ(activity.activity_date, user.timezone)
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const previousUrl = dataType === 'segment' && segmentId !== 1
|
const previousUrl =
|
||||||
? `/activities/${activity.id}/segment/${segmentId - 1}`
|
dataType === 'segment' && segmentId !== 1
|
||||||
: dataType === 'activity' && activity.previous_activity
|
? `/activities/${activity.id}/segment/${segmentId - 1}`
|
||||||
|
: dataType === 'activity' && activity.previous_activity
|
||||||
? `/activities/${activity.previous_activity}`
|
? `/activities/${activity.previous_activity}`
|
||||||
: null
|
: null
|
||||||
const nextUrl =
|
const nextUrl =
|
||||||
dataType === 'segment' && segmentId < activity.segments.length
|
dataType === 'segment' && segmentId < activity.segments.length
|
||||||
? `/activities/${activity.id}/segment/${segmentId + 1}`
|
? `/activities/${activity.id}/segment/${segmentId + 1}`
|
||||||
: dataType === 'activity' && activity.next_activity
|
: dataType === 'activity' && activity.next_activity
|
||||||
? `/activities/${activity.next_activity}`
|
? `/activities/${activity.next_activity}`
|
||||||
: null
|
: null
|
||||||
|
|
||||||
@ -32,10 +36,7 @@ export default function ActivityCardHeader(props) {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
{previousUrl ? (
|
{previousUrl ? (
|
||||||
<Link
|
<Link className="unlink" to={previousUrl}>
|
||||||
className="unlink"
|
|
||||||
to={previousUrl}
|
|
||||||
>
|
|
||||||
<i
|
<i
|
||||||
className="fa fa-chevron-left"
|
className="fa fa-chevron-left"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@ -51,35 +52,29 @@ export default function ActivityCardHeader(props) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-auto col-activity-logo">
|
<div className="col-auto col-activity-logo">
|
||||||
<img
|
<img className="sport-img-medium" src={sport.img} alt="sport logo" />
|
||||||
className="sport-img-medium"
|
|
||||||
src={sport.img}
|
|
||||||
alt="sport logo"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
{dataType === 'activity' ? (
|
{dataType === 'activity' ? (
|
||||||
<>
|
<>
|
||||||
{title}{' '}
|
{title}{' '}
|
||||||
<Link
|
<Link className="unlink" to={`/activities/${activity.id}/edit`}>
|
||||||
className="unlink"
|
<i
|
||||||
to={`/activities/${activity.id}/edit`}
|
className="fa fa-edit custom-fa"
|
||||||
>
|
aria-hidden="true"
|
||||||
|
title="Edit activity"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
<i
|
<i
|
||||||
className="fa fa-edit custom-fa"
|
className="fa fa-trash custom-fa"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title="Edit activity"
|
onClick={() => displayModal(true)}
|
||||||
|
title="Delete activity"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</>
|
||||||
<i
|
|
||||||
className="fa fa-trash custom-fa"
|
|
||||||
aria-hidden="true"
|
|
||||||
onClick={() => displayModal(true)}
|
|
||||||
title="Delete activity"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
{/* prettier-ignore */}
|
||||||
<Link
|
<Link
|
||||||
to={`/activities/${activity.id}`}
|
to={`/activities/${activity.id}`}
|
||||||
>
|
>
|
||||||
@ -91,16 +86,13 @@ export default function ActivityCardHeader(props) {
|
|||||||
<br />
|
<br />
|
||||||
{activityDate && (
|
{activityDate && (
|
||||||
<span className="activity-date">
|
<span className="activity-date">
|
||||||
{`${activityDate.activity_date} - ${activityDate.activity_time}`}
|
{`${activityDate.activity_date} - ${activityDate.activity_time}`}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
{nextUrl ? (
|
{nextUrl ? (
|
||||||
<Link
|
<Link className="unlink" to={nextUrl}>
|
||||||
className="unlink"
|
|
||||||
to={nextUrl}
|
|
||||||
>
|
|
||||||
<i
|
<i
|
||||||
className="fa fa-chevron-right"
|
className="fa fa-chevron-right"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
@ -2,20 +2,26 @@ import { format } from 'date-fns'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import {
|
import {
|
||||||
Area, ComposedChart, Line, ResponsiveContainer, Tooltip, XAxis, YAxis
|
Area,
|
||||||
|
ComposedChart,
|
||||||
|
Line,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Tooltip,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getActivityChartData, getSegmentChartData
|
getActivityChartData,
|
||||||
|
getSegmentChartData,
|
||||||
} from '../../../actions/activities'
|
} from '../../../actions/activities'
|
||||||
|
|
||||||
|
|
||||||
class ActivityCharts extends React.Component {
|
class ActivityCharts extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
displayDistance: true,
|
displayDistance: true,
|
||||||
dataToHide: []
|
dataToHide: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,13 +34,15 @@ class ActivityCharts extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.dataType === 'activity' && (
|
if (
|
||||||
prevProps.activity.id !== this.props.activity.id)
|
this.props.dataType === 'activity' &&
|
||||||
|
prevProps.activity.id !== this.props.activity.id
|
||||||
) {
|
) {
|
||||||
this.props.loadActivityData(this.props.activity.id)
|
this.props.loadActivityData(this.props.activity.id)
|
||||||
}
|
}
|
||||||
if (this.props.dataType === 'segment' && (
|
if (
|
||||||
prevProps.segmentId !== this.props.segmentId)
|
this.props.dataType === 'segment' &&
|
||||||
|
prevProps.segmentId !== this.props.segmentId
|
||||||
) {
|
) {
|
||||||
this.props.loadSegmentData(this.props.activity.id, this.props.segmentId)
|
this.props.loadSegmentData(this.props.activity.id, this.props.segmentId)
|
||||||
}
|
}
|
||||||
@ -44,16 +52,16 @@ class ActivityCharts extends React.Component {
|
|||||||
this.props.loadActivityData(null)
|
this.props.loadActivityData(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRadioChange (changeEvent) {
|
handleRadioChange(changeEvent) {
|
||||||
this.setState({
|
this.setState({
|
||||||
displayDistance:
|
displayDistance:
|
||||||
changeEvent.target.name === 'distance'
|
changeEvent.target.name === 'distance'
|
||||||
? changeEvent.target.value
|
? changeEvent.target.value
|
||||||
: !changeEvent.target.value
|
: !changeEvent.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLegendChange (e) {
|
handleLegendChange(e) {
|
||||||
const { dataToHide } = this.state
|
const { dataToHide } = this.state
|
||||||
const name = e.target.name // eslint-disable-line prefer-destructuring
|
const name = e.target.name // eslint-disable-line prefer-destructuring
|
||||||
if (dataToHide.find(d => d === name)) {
|
if (dataToHide.find(d => d === name)) {
|
||||||
@ -64,7 +72,7 @@ class ActivityCharts extends React.Component {
|
|||||||
this.setState({ dataToHide })
|
this.setState({ dataToHide })
|
||||||
}
|
}
|
||||||
|
|
||||||
displayData (name) {
|
displayData(name) {
|
||||||
const { dataToHide } = this.state
|
const { dataToHide } = this.state
|
||||||
return !dataToHide.find(d => d === name)
|
return !dataToHide.find(d => d === name)
|
||||||
}
|
}
|
||||||
@ -141,22 +149,27 @@ class ActivityCharts extends React.Component {
|
|||||||
label={{ value: xDataKey, offset: 0, position: 'bottom' }}
|
label={{ value: xDataKey, offset: 0, position: 'bottom' }}
|
||||||
scale={xScale}
|
scale={xScale}
|
||||||
interval={xInterval}
|
interval={xInterval}
|
||||||
tickFormatter={value => displayDistance
|
tickFormatter={value =>
|
||||||
? value
|
displayDistance ? value : format(value, 'HH:mm:ss')
|
||||||
: format(value, 'HH:mm:ss')}
|
}
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
label={{
|
label={{
|
||||||
value: 'speed (km/h)', angle: -90, position: 'left'
|
value: 'speed (km/h)',
|
||||||
|
angle: -90,
|
||||||
|
position: 'left',
|
||||||
}}
|
}}
|
||||||
yAxisId="left"
|
yAxisId="left"
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
label={{
|
label={{
|
||||||
value: 'altitude (m)', angle: -90, position: 'right'
|
value: 'altitude (m)',
|
||||||
|
angle: -90,
|
||||||
|
position: 'right',
|
||||||
}}
|
}}
|
||||||
yAxisId="right" orientation="right"
|
yAxisId="right"
|
||||||
|
orientation="right"
|
||||||
/>
|
/>
|
||||||
{this.displayData('elevation') && (
|
{this.displayData('elevation') && (
|
||||||
<Area
|
<Area
|
||||||
@ -181,9 +194,11 @@ class ActivityCharts extends React.Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
labelFormatter={value => displayDistance
|
labelFormatter={value =>
|
||||||
? `distance: ${value} km`
|
displayDistance
|
||||||
: `duration: ${format(value, 'HH:mm:ss')}`}
|
? `distance: ${value} km`
|
||||||
|
: `duration: ${format(value, 'HH:mm:ss')}`
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
@ -202,7 +217,7 @@ class ActivityCharts extends React.Component {
|
|||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
chartData: state.chartData
|
chartData: state.chartData,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivityData: activityId => {
|
loadActivityData: activityId => {
|
||||||
|
@ -8,20 +8,14 @@ export default function ActivityDetails(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="activity-details">
|
<div className="activity-details">
|
||||||
<p>
|
<p>
|
||||||
<i
|
<i className="fa fa-clock-o custom-fa" aria-hidden="true" />
|
||||||
className="fa fa-clock-o custom-fa"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Duration: {activity.moving}
|
Duration: {activity.moving}
|
||||||
{activity.records && activity.records.find(r => r.record_type === 'LD'
|
{activity.records &&
|
||||||
) && (
|
activity.records.find(r => r.record_type === 'LD') && (
|
||||||
<sup>
|
<sup>
|
||||||
<i
|
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||||
className="fa fa-trophy custom-fa"
|
</sup>
|
||||||
aria-hidden="true"
|
)}
|
||||||
/>
|
|
||||||
</sup>
|
|
||||||
)}
|
|
||||||
{withPauses && (
|
{withPauses && (
|
||||||
<span>
|
<span>
|
||||||
<br />
|
<br />
|
||||||
@ -30,47 +24,32 @@ export default function ActivityDetails(props) {
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<i
|
<i className="fa fa-road custom-fa" aria-hidden="true" />
|
||||||
className="fa fa-road custom-fa"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Distance: {activity.distance} km
|
Distance: {activity.distance} km
|
||||||
{activity.records && activity.records.find(r => r.record_type === 'FD'
|
{activity.records &&
|
||||||
) && (
|
activity.records.find(r => r.record_type === 'FD') && (
|
||||||
<sup>
|
<sup>
|
||||||
<i
|
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||||
className="fa fa-trophy custom-fa"
|
</sup>
|
||||||
aria-hidden="true"
|
)}
|
||||||
/>
|
|
||||||
</sup>
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<i
|
<i className="fa fa-tachometer custom-fa" aria-hidden="true" />
|
||||||
className="fa fa-tachometer custom-fa"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
Average speed: {activity.ave_speed} km/h
|
Average speed: {activity.ave_speed} km/h
|
||||||
{activity.records && activity.records.find(r => r.record_type === 'AS'
|
{activity.records &&
|
||||||
) && (
|
activity.records.find(r => r.record_type === 'AS') && (
|
||||||
<sup>
|
<sup>
|
||||||
<i
|
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||||
className="fa fa-trophy custom-fa"
|
</sup>
|
||||||
aria-hidden="true"
|
)}
|
||||||
/>
|
|
||||||
</sup>
|
|
||||||
)}
|
|
||||||
<br />
|
<br />
|
||||||
Max speed : {activity.max_speed} km/h
|
Max speed : {activity.max_speed} km/h
|
||||||
{activity.records && activity.records.find(r => r.record_type === 'MS'
|
{activity.records &&
|
||||||
) && (
|
activity.records.find(r => r.record_type === 'MS') && (
|
||||||
<sup>
|
<sup>
|
||||||
<i
|
<i className="fa fa-trophy custom-fa" aria-hidden="true" />
|
||||||
className="fa fa-trophy custom-fa"
|
</sup>
|
||||||
aria-hidden="true"
|
)}
|
||||||
/>
|
|
||||||
</sup>
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
{activity.min_alt && activity.max_alt && (
|
{activity.min_alt && activity.max_alt && (
|
||||||
<p>
|
<p>
|
||||||
|
@ -8,7 +8,6 @@ import { thunderforestApiKey } from '../../../utils'
|
|||||||
import { getGeoJson } from '../../../utils/activities'
|
import { getGeoJson } from '../../../utils/activities'
|
||||||
|
|
||||||
class ActivityMap extends React.Component {
|
class ActivityMap extends React.Component {
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -25,13 +24,15 @@ class ActivityMap extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.dataType === 'activity' && (
|
if (
|
||||||
prevProps.activity.id !== this.props.activity.id)
|
this.props.dataType === 'activity' &&
|
||||||
|
prevProps.activity.id !== this.props.activity.id
|
||||||
) {
|
) {
|
||||||
this.props.loadActivityGpx(this.props.activity.id)
|
this.props.loadActivityGpx(this.props.activity.id)
|
||||||
}
|
}
|
||||||
if (this.props.dataType === 'segment' && (
|
if (
|
||||||
prevProps.segmentId !== this.props.segmentId)
|
this.props.dataType === 'segment' &&
|
||||||
|
prevProps.segmentId !== this.props.segmentId
|
||||||
) {
|
) {
|
||||||
this.props.loadSegmentGpx(this.props.activity.id, this.props.segmentId)
|
this.props.loadSegmentGpx(this.props.activity.id, this.props.segmentId)
|
||||||
}
|
}
|
||||||
@ -42,13 +43,11 @@ class ActivityMap extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { activity, coordinates, gpxContent } = this.props
|
||||||
activity, coordinates, gpxContent
|
|
||||||
} = this.props
|
|
||||||
const { jsonData } = getGeoJson(gpxContent)
|
const { jsonData } = getGeoJson(gpxContent)
|
||||||
const bounds = [
|
const bounds = [
|
||||||
[activity.bounds[0], activity.bounds[1]],
|
[activity.bounds[0], activity.bounds[1]],
|
||||||
[activity.bounds[2], activity.bounds[3]]
|
[activity.bounds[2], activity.bounds[3]],
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -78,14 +77,13 @@ class ActivityMap extends React.Component {
|
|||||||
</Map>
|
</Map>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
gpxContent: state.gpx
|
gpxContent: state.gpx,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadActivityGpx: activityId => {
|
loadActivityGpx: activityId => {
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function ActivityNoMap() {
|
export default function ActivityNoMap() {
|
||||||
return (
|
return <div className="activity-no-map text-center">No Map</div>
|
||||||
<div className="activity-no-map text-center">
|
|
||||||
No Map
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,7 @@ export default function ActivityNotes(props) {
|
|||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
Notes
|
Notes
|
||||||
<div className="activity-notes">
|
<div className="activity-notes">{notes ? notes : 'No notes'}</div>
|
||||||
{notes ? notes : 'No notes'}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,8 +18,8 @@ export default function ActivitySegments(props) {
|
|||||||
key={`segment-${index}`}
|
key={`segment-${index}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
to={`/activities/${
|
to={`/activities/${segment.activity_id}/segment/${index +
|
||||||
segment.activity_id}/segment/${index + 1}`}
|
1}`}
|
||||||
>
|
>
|
||||||
segment {index + 1}
|
segment {index + 1}
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
|
@ -5,9 +5,7 @@ export default function ActivityWeather(props) {
|
|||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
{activity.weather_start && activity.weather_end && (
|
{activity.weather_start && activity.weather_end && (
|
||||||
<table
|
<table className="table table-borderless weather-table text-center">
|
||||||
className="table table-borderless weather-table text-center"
|
|
||||||
>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th />
|
||||||
@ -42,12 +40,8 @@ export default function ActivityWeather(props) {
|
|||||||
alt="Temperatures"
|
alt="Temperatures"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>{Number(activity.weather_start.temperature).toFixed(1)}°C</td>
|
||||||
{Number(activity.weather_start.temperature).toFixed(1)}°C
|
<td>{Number(activity.weather_end.temperature).toFixed(1)}°C</td>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{Number(activity.weather_end.temperature).toFixed(1)}°C
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@ -60,9 +54,7 @@ export default function ActivityWeather(props) {
|
|||||||
<td>
|
<td>
|
||||||
{Number(activity.weather_start.humidity * 100).toFixed(1)}%
|
{Number(activity.weather_start.humidity * 100).toFixed(1)}%
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>{Number(activity.weather_end.humidity * 100).toFixed(1)}%</td>
|
||||||
{Number(activity.weather_end.humidity * 100).toFixed(1)}%
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@ -72,12 +64,8 @@ export default function ActivityWeather(props) {
|
|||||||
alt="Temperatures"
|
alt="Temperatures"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>{Number(activity.weather_start.wind).toFixed(1)}m/s</td>
|
||||||
{Number(activity.weather_start.wind).toFixed(1)}m/s
|
<td>{Number(activity.weather_end.wind).toFixed(1)}m/s</td>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{Number(activity.weather_end.wind).toFixed(1)}m/s
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -30,8 +30,9 @@ class ActivityDisplay extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (prevProps.match.params.activityId !==
|
if (
|
||||||
this.props.match.params.activityId) {
|
prevProps.match.params.activityId !== this.props.match.params.activityId
|
||||||
|
) {
|
||||||
this.props.loadActivity(this.props.match.params.activityId)
|
this.props.loadActivity(this.props.match.params.activityId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,23 +40,24 @@ class ActivityDisplay extends React.Component {
|
|||||||
displayModal(value) {
|
displayModal(value) {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
displayModal: value
|
displayModal: value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCoordinates(activePayload) {
|
updateCoordinates(activePayload) {
|
||||||
const coordinates = (activePayload && activePayload.length > 0)
|
const coordinates =
|
||||||
? {
|
activePayload && activePayload.length > 0
|
||||||
latitude: activePayload[0].payload.latitude,
|
? {
|
||||||
longitude: activePayload[0].payload.longitude,
|
latitude: activePayload[0].payload.latitude,
|
||||||
}
|
longitude: activePayload[0].payload.longitude,
|
||||||
: {
|
}
|
||||||
latitude: null,
|
: {
|
||||||
longitude: null,
|
latitude: null,
|
||||||
}
|
longitude: null,
|
||||||
|
}
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
coordinates
|
coordinates,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,9 +70,7 @@ class ActivityDisplay extends React.Component {
|
|||||||
? sports.filter(s => s.id === activity.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
|
const dataType = segmentId >= 0 ? 'segment' : 'activity'
|
||||||
? 'segment'
|
|
||||||
: 'activity'
|
|
||||||
return (
|
return (
|
||||||
<div className="activity-page">
|
<div className="activity-page">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -80,16 +80,17 @@ class ActivityDisplay extends React.Component {
|
|||||||
<code>{message}</code>
|
<code>{message}</code>
|
||||||
) : (
|
) : (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
{displayModal &&
|
{displayModal && (
|
||||||
<CustomModal
|
<CustomModal
|
||||||
title="Confirmation"
|
title="Confirmation"
|
||||||
text="Are you sure you want to delete this activity?"
|
text="Are you sure you want to delete this activity?"
|
||||||
confirm={() => {
|
confirm={() => {
|
||||||
onDeleteActivity(activity.id)
|
onDeleteActivity(activity.id)
|
||||||
this.displayModal(false)
|
this.displayModal(false)
|
||||||
}}
|
}}
|
||||||
close={() => this.displayModal(false)}
|
close={() => this.displayModal(false)}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
{activity && sport && activities.length === 1 && (
|
{activity && sport && activities.length === 1 && (
|
||||||
<div>
|
<div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -122,9 +123,11 @@ class ActivityDisplay extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<ActivityDetails
|
<ActivityDetails
|
||||||
activity={dataType === 'activity'
|
activity={
|
||||||
? activity
|
dataType === 'activity'
|
||||||
: activity.segments[segmentId - 1]}
|
? activity
|
||||||
|
: activity.segments[segmentId - 1]
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -144,8 +147,8 @@ class ActivityDisplay extends React.Component {
|
|||||||
activity={activity}
|
activity={activity}
|
||||||
dataType={dataType}
|
dataType={dataType}
|
||||||
segmentId={segmentId}
|
segmentId={segmentId}
|
||||||
updateCoordinates={
|
updateCoordinates={e =>
|
||||||
e => this.updateCoordinates(e)
|
this.updateCoordinates(e)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,12 +4,9 @@ import { connect } from 'react-redux'
|
|||||||
import ActivityAddOrEdit from './ActivityAddOrEdit'
|
import ActivityAddOrEdit from './ActivityAddOrEdit'
|
||||||
import { getOrUpdateData } from '../../actions'
|
import { getOrUpdateData } from '../../actions'
|
||||||
|
|
||||||
|
|
||||||
class ActivityEdit extends React.Component {
|
class ActivityEdit extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadActivity(
|
this.props.loadActivity(this.props.match.params.activityId)
|
||||||
this.props.match.params.activityId
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -6,11 +6,8 @@ import { addActivity, editActivity } from '../../../actions/activities'
|
|||||||
import { history } from '../../../index'
|
import { history } from '../../../index'
|
||||||
import { gpxLimit } from '../../../utils'
|
import { gpxLimit } from '../../../utils'
|
||||||
|
|
||||||
|
function FormWithGpx(props) {
|
||||||
function FormWithGpx (props) {
|
const { activity, loading, onAddActivity, onEditActivity, sports } = props
|
||||||
const {
|
|
||||||
activity, loading, onAddActivity, onEditActivity, sports
|
|
||||||
} = props
|
|
||||||
const sportId = activity ? activity.sport_id : ''
|
const sportId = activity ? activity.sport_id : ''
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
@ -52,9 +49,10 @@ function FormWithGpx (props) {
|
|||||||
) : (
|
) : (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
<strong>gpx</strong> file or <strong>zip</strong>{' '}
|
{/* prettier-ignore */}
|
||||||
file containing <strong>gpx</strong> (no folder inside, {
|
<strong>gpx</strong> file or <strong>zip</strong> file containing
|
||||||
gpxLimit} files max):
|
{/* prettier-ignore */}
|
||||||
|
<strong> gpx</strong> (no folder inside, {gpxLimit} files max):
|
||||||
<input
|
<input
|
||||||
accept=".gpx, .zip"
|
accept=".gpx, .zip"
|
||||||
className="form-control form-control-file gpx-file"
|
className="form-control form-control-file gpx-file"
|
||||||
@ -85,10 +83,8 @@ function FormWithGpx (props) {
|
|||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-primary btn-lg btn-block"
|
className="btn btn-primary btn-lg btn-block"
|
||||||
onClick={
|
onClick={event =>
|
||||||
event => activity
|
activity ? onEditActivity(event, activity) : onAddActivity(event)
|
||||||
? onEditActivity(event, activity)
|
|
||||||
: onAddActivity(event)
|
|
||||||
}
|
}
|
||||||
value="Submit"
|
value="Submit"
|
||||||
/>
|
/>
|
||||||
@ -106,13 +102,14 @@ function FormWithGpx (props) {
|
|||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
state => ({
|
state => ({
|
||||||
loading: state.loading
|
loading: state.loading,
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
onAddActivity: e => {
|
onAddActivity: 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])
|
||||||
|
/* prettier-ignore */
|
||||||
form.append(
|
form.append(
|
||||||
'data',
|
'data',
|
||||||
`{"sport_id": ${e.target.form.sport.value
|
`{"sport_id": ${e.target.form.sport.value
|
||||||
@ -121,12 +118,14 @@ export default connect(
|
|||||||
dispatch(addActivity(form))
|
dispatch(addActivity(form))
|
||||||
},
|
},
|
||||||
onEditActivity: (e, activity) => {
|
onEditActivity: (e, activity) => {
|
||||||
dispatch(editActivity({
|
dispatch(
|
||||||
id: activity.id,
|
editActivity({
|
||||||
notes: e.target.form.notes.value,
|
id: activity.id,
|
||||||
sport_id: +e.target.form.sport.value,
|
notes: e.target.form.notes.value,
|
||||||
title: e.target.form.title.value,
|
sport_id: +e.target.form.sport.value,
|
||||||
}))
|
title: e.target.form.title.value,
|
||||||
|
})
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)(FormWithGpx)
|
)(FormWithGpx)
|
||||||
|
@ -2,14 +2,17 @@ import React from 'react'
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addActivityWithoutGpx, editActivity
|
addActivityWithoutGpx,
|
||||||
|
editActivity,
|
||||||
} from '../../../actions/activities'
|
} from '../../../actions/activities'
|
||||||
import { history } from '../../../index'
|
import { history } from '../../../index'
|
||||||
import { formatActivityDate } from '../../../utils/activities'
|
import { formatActivityDate } from '../../../utils/activities'
|
||||||
|
|
||||||
function FormWithoutGpx (props) {
|
function FormWithoutGpx(props) {
|
||||||
const { activity, onAddOrEdit, sports } = props
|
const { activity, onAddOrEdit, sports } = props
|
||||||
let activityDate, activityTime, sportId = ''
|
let activityDate,
|
||||||
|
activityTime,
|
||||||
|
sportId = ''
|
||||||
if (activity) {
|
if (activity) {
|
||||||
const activityDateTime = formatActivityDate(
|
const activityDateTime = formatActivityDate(
|
||||||
activity.activity_date,
|
activity.activity_date,
|
||||||
@ -21,9 +24,7 @@ function FormWithoutGpx (props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form onSubmit={event => event.preventDefault()}>
|
||||||
onSubmit={event => event.preventDefault()}
|
|
||||||
>
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
Title:
|
Title:
|
||||||
@ -78,15 +79,15 @@ function FormWithoutGpx (props) {
|
|||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
Duration:
|
Duration:
|
||||||
<input
|
<input
|
||||||
name="duration"
|
name="duration"
|
||||||
defaultValue={activity ? activity.duration : ''}
|
defaultValue={activity ? activity.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"
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@ -131,12 +132,13 @@ function FormWithoutGpx (props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
() => ({ }),
|
() => ({}),
|
||||||
dispatch => ({
|
dispatch => ({
|
||||||
onAddOrEdit: (e, activity) => {
|
onAddOrEdit: (e, activity) => {
|
||||||
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 */
|
||||||
const activityDate = `${e.target.form.activity_date.value
|
const activityDate = `${e.target.form.activity_date.value
|
||||||
} ${ e.target.form.activity_time.value}`
|
} ${ e.target.form.activity_time.value}`
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import ActivityEdit from './ActivityEdit'
|
|||||||
import NotFound from './../Others/NotFound'
|
import NotFound from './../Others/NotFound'
|
||||||
import { isLoggedIn } from '../../utils'
|
import { isLoggedIn } from '../../utils'
|
||||||
|
|
||||||
function Activity () {
|
function Activity() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -19,11 +19,13 @@ function Activity () {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/activities/add" component={ActivityAdd} />
|
<Route exact path="/activities/add" component={ActivityAdd} />
|
||||||
<Route
|
<Route
|
||||||
exact path="/activities/:activityId"
|
exact
|
||||||
|
path="/activities/:activityId"
|
||||||
component={ActivityDisplay}
|
component={ActivityDisplay}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact path="/activities/:activityId/edit"
|
exact
|
||||||
|
path="/activities/:activityId/edit"
|
||||||
component={ActivityEdit}
|
component={ActivityEdit}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
@ -32,13 +34,13 @@ function Activity () {
|
|||||||
/>
|
/>
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
) : (<Redirect to="/login" />)}
|
) : (
|
||||||
|
<Redirect to="/login" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(state => ({
|
||||||
state => ({
|
user: state.user,
|
||||||
user: state.user,
|
}))(Activity)
|
||||||
})
|
|
||||||
)(Activity)
|
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
export default function AdminMenu () {
|
export default function AdminMenu() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
@ -17,10 +17,7 @@ class AdminSports extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AdminDetail
|
<AdminDetail results={sports} target="sports" />
|
||||||
results={sports}
|
|
||||||
target="sports"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,14 @@ import AdminPage from '../generic/AdminPage'
|
|||||||
|
|
||||||
class AdminSports extends React.Component {
|
class AdminSports extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.loadSports()
|
this.props.loadSports()
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const { sports } = this.props
|
const { sports } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AdminPage
|
<AdminPage data={sports} target="sports" />
|
||||||
data={sports}
|
|
||||||
target="sports"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { addData } from '../../../actions/index'
|
|||||||
import { history } from '../../../index'
|
import { history } from '../../../index'
|
||||||
|
|
||||||
class AdminSportsAdd extends React.Component {
|
class AdminSportsAdd extends React.Component {
|
||||||
componentDidMount() { }
|
componentDidMount() {}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message, onAddSport } = this.props
|
const { message, onAddSport } = this.props
|
||||||
@ -16,25 +16,17 @@ class AdminSportsAdd extends React.Component {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - Admin - Add Sport</title>
|
<title>FitTrackee - Admin - Add Sport</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<h1 className="page-title">
|
<h1 className="page-title">Administration - Sport</h1>
|
||||||
Administration - Sport
|
{message && <code>{message}</code>}
|
||||||
</h1>
|
|
||||||
{message && (
|
|
||||||
<code>{message}</code>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<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">
|
<div className="card">
|
||||||
<div className="card-header">
|
<div className="card-header">Add a sport</div>
|
||||||
Add a sport
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<form onSubmit={event =>
|
<form onSubmit={event => event.preventDefault()}>
|
||||||
event.preventDefault()}
|
|
||||||
>
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
Label:
|
Label:
|
||||||
|
@ -6,7 +6,6 @@ import { deleteData, getOrUpdateData } from '../../../actions/index'
|
|||||||
import { history } from '../../../index'
|
import { history } from '../../../index'
|
||||||
|
|
||||||
class AdminDetail extends React.Component {
|
class AdminDetail extends React.Component {
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -15,13 +14,7 @@ class AdminDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { message, onDataUpdate, onDataDelete, results, target } = this.props
|
||||||
message,
|
|
||||||
onDataUpdate,
|
|
||||||
onDataDelete,
|
|
||||||
results,
|
|
||||||
target,
|
|
||||||
} = this.props
|
|
||||||
const { isInEdition } = this.state
|
const { isInEdition } = this.state
|
||||||
const title = target.charAt(0).toUpperCase() + target.slice(1)
|
const title = target.charAt(0).toUpperCase() + target.slice(1)
|
||||||
|
|
||||||
@ -30,100 +23,98 @@ class AdminDetail extends React.Component {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - Admin</title>
|
<title>FitTrackee - Admin</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<h1 className="page-title">
|
<h1 className="page-title">Administration - {title}</h1>
|
||||||
Administration - {title}
|
|
||||||
</h1>
|
|
||||||
{message ? (
|
{message ? (
|
||||||
<code>{message}</code>
|
<code>{message}</code>
|
||||||
) : (
|
) : (
|
||||||
results.length === 1 && (
|
results.length === 1 && (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-2" />
|
<div className="col-md-2" />
|
||||||
<div className="col-md-8 card">
|
<div className="col-md-8 card">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<form onSubmit={event =>
|
<form onSubmit={event => event.preventDefault()}>
|
||||||
event.preventDefault()}
|
{Object.keys(results[0])
|
||||||
>
|
|
||||||
{ Object.keys(results[0])
|
|
||||||
.filter(key => key.charAt(0) !== '_')
|
.filter(key => key.charAt(0) !== '_')
|
||||||
.map(key => (
|
.map(key => (
|
||||||
<div className="form-group" key={key}>
|
<div className="form-group" key={key}>
|
||||||
<label>
|
<label>
|
||||||
{key}:
|
{key}:
|
||||||
{key === 'img' ? (
|
{key === 'img' ? (
|
||||||
<img
|
<img
|
||||||
src={results[0][key]
|
src={
|
||||||
? results[0][key]
|
results[0][key]
|
||||||
: '/img/photo.png'}
|
? results[0][key]
|
||||||
alt="property"
|
: '/img/photo.png'
|
||||||
/>
|
}
|
||||||
) : (
|
alt="property"
|
||||||
<input
|
/>
|
||||||
className="form-control input-lg"
|
) : (
|
||||||
name={key}
|
<input
|
||||||
readOnly={key === 'id' || !isInEdition}
|
className="form-control input-lg"
|
||||||
defaultValue={results[0][key]}
|
name={key}
|
||||||
/>
|
readOnly={key === 'id' || !isInEdition}
|
||||||
)}
|
defaultValue={results[0][key]}
|
||||||
</label>
|
/>
|
||||||
</div>
|
)}
|
||||||
))
|
</label>
|
||||||
}
|
</div>
|
||||||
{isInEdition ? (
|
))}
|
||||||
<div>
|
{isInEdition ? (
|
||||||
<input
|
<div>
|
||||||
type="submit"
|
<input
|
||||||
className="btn btn-primary btn-lg btn-block"
|
type="submit"
|
||||||
onClick={event => {
|
className="btn btn-primary btn-lg btn-block"
|
||||||
|
onClick={event => {
|
||||||
onDataUpdate(event, target)
|
onDataUpdate(event, target)
|
||||||
this.setState({ isInEdition: false })
|
this.setState({ isInEdition: false })
|
||||||
|
}}
|
||||||
|
value="Submit"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-secondary btn-lg btn-block"
|
||||||
|
onClick={event => {
|
||||||
|
event.target.form.reset()
|
||||||
|
this.setState({ isInEdition: false })
|
||||||
|
}}
|
||||||
|
value="Cancel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary btn-lg btn-block"
|
||||||
|
onClick={() => this.setState({ isInEdition: true })}
|
||||||
|
value="Edit"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-danger btn-lg btn-block"
|
||||||
|
disabled={!results[0]._can_be_deleted}
|
||||||
|
onClick={event => onDataDelete(event, target)}
|
||||||
|
title={
|
||||||
|
results[0]._can_be_deleted
|
||||||
|
? ''
|
||||||
|
: "Can't be deleted, associated data exist"
|
||||||
}
|
}
|
||||||
}
|
value="Delete"
|
||||||
value="Submit"
|
/>
|
||||||
/>
|
<input
|
||||||
<input
|
type="submit"
|
||||||
type="submit"
|
className="btn btn-secondary btn-lg btn-block"
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
onClick={() => history.push(`/admin/${target}`)}
|
||||||
onClick={event => {
|
value="Back to the list"
|
||||||
event.target.form.reset()
|
/>
|
||||||
this.setState({ isInEdition: false })
|
</div>
|
||||||
}}
|
)}
|
||||||
value="Cancel"
|
</form>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary btn-lg btn-block"
|
|
||||||
onClick={() => this.setState({ isInEdition: true })}
|
|
||||||
value="Edit"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-danger btn-lg btn-block"
|
|
||||||
disabled={!results[0]._can_be_deleted}
|
|
||||||
onClick={event => onDataDelete(event, target)}
|
|
||||||
title={results[0]._can_be_deleted
|
|
||||||
? ''
|
|
||||||
: 'Can\'t be deleted, associated data exist'}
|
|
||||||
value="Delete"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
|
||||||
onClick={() => history.push(`/admin/${target}`)}
|
|
||||||
value="Back to the list"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-md-2" />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-2" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,6 @@ import { Link } from 'react-router-dom'
|
|||||||
import { history } from '../../../index'
|
import { history } from '../../../index'
|
||||||
|
|
||||||
export default function AdminPage(props) {
|
export default function AdminPage(props) {
|
||||||
|
|
||||||
const { data, target } = props
|
const { data, target } = props
|
||||||
const { error } = data
|
const { error } = data
|
||||||
const results = data.data
|
const results = data.data
|
||||||
@ -22,9 +21,7 @@ export default function AdminPage(props) {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - Admin</title>
|
<title>FitTrackee - Admin</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<h1 className="page-title">
|
<h1 className="page-title">Administration - {title}</h1>
|
||||||
Administration - {title}
|
|
||||||
</h1>
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<code>{error}</code>
|
<code>{error}</code>
|
||||||
) : (
|
) : (
|
||||||
@ -36,40 +33,45 @@ export default function AdminPage(props) {
|
|||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{tbKeys.map(
|
{tbKeys.map(tbKey => (
|
||||||
tbKey => <th key={tbKey} scope="col">{tbKey}</th>
|
<th key={tbKey} scope="col">
|
||||||
)}
|
{tbKey}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ results.map((result, idx) => (
|
{results.map((result, 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}>
|
||||||
{ Object.keys(result)
|
{Object.keys(result)
|
||||||
.filter(key => key.charAt(0) !== '_')
|
.filter(key => key.charAt(0) !== '_')
|
||||||
.map(key => {
|
.map(key => {
|
||||||
if (key === 'id') {
|
if (key === 'id') {
|
||||||
return (
|
return (
|
||||||
<th key={key} scope="row">
|
<th key={key} scope="row">
|
||||||
<Link to={`/admin/${target}/${result[key]}`}>
|
<Link to={`/admin/${target}/${result[key]}`}>
|
||||||
{result[key]}
|
{result[key]}
|
||||||
</Link>
|
</Link>
|
||||||
</th>
|
</th>
|
||||||
)
|
)
|
||||||
} else if (key === 'img') {
|
} else if (key === 'img') {
|
||||||
return (<td key={key}>
|
return (
|
||||||
<img
|
<td key={key}>
|
||||||
className="admin-img"
|
<img
|
||||||
src={result[key]
|
className="admin-img"
|
||||||
? result[key]
|
src={
|
||||||
: '/img/photo.png'}
|
result[key]
|
||||||
alt="logo"
|
? result[key]
|
||||||
/>
|
: '/img/photo.png'
|
||||||
</td>)
|
}
|
||||||
}
|
alt="logo"
|
||||||
return <td key={key}>{result[key]}</td>
|
/>
|
||||||
})
|
</td>
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
return <td key={key}>{result[key]}</td>
|
||||||
|
})}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -88,9 +90,9 @@ export default function AdminPage(props) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-2" />
|
<div className="col-md-2" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -11,7 +11,7 @@ import AccessDenied from './../Others/AccessDenied'
|
|||||||
import NotFound from './../Others/NotFound'
|
import NotFound from './../Others/NotFound'
|
||||||
import { isLoggedIn } from '../../utils'
|
import { isLoggedIn } from '../../utils'
|
||||||
|
|
||||||
function Admin (props) {
|
function Admin(props) {
|
||||||
const { user } = props
|
const { user } = props
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -23,26 +23,20 @@ function Admin (props) {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/admin" component={AdminMenu} />
|
<Route exact path="/admin" component={AdminMenu} />
|
||||||
<Route exact path="/admin/sports" component={AdminSports} />
|
<Route exact path="/admin/sports" component={AdminSports} />
|
||||||
<Route
|
<Route exact path="/admin/sports/add" component={AdminSportsAdd} />
|
||||||
exact path="/admin/sports/add"
|
<Route exact path="/admin/sports/:sportId" component={AdminSport} />
|
||||||
component={AdminSportsAdd}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact path="/admin/sports/:sportId"
|
|
||||||
component={AdminSport}
|
|
||||||
/>
|
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
) : (
|
) : (
|
||||||
<AccessDenied />
|
<AccessDenied />
|
||||||
)
|
)
|
||||||
) : (<Redirect to="/login" />)}
|
) : (
|
||||||
|
<Redirect to="/login" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(state => ({
|
||||||
state => ({
|
user: state.user,
|
||||||
user: state.user,
|
}))(Admin)
|
||||||
})
|
|
||||||
)(Admin)
|
|
||||||
|
@ -17,7 +17,6 @@ import UserForm from './User/UserForm'
|
|||||||
import { isLoggedIn } from '../utils'
|
import { isLoggedIn } from '../utils'
|
||||||
|
|
||||||
export default class App extends React.Component {
|
export default class App extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.props = props
|
this.props = props
|
||||||
@ -29,78 +28,57 @@ export default class App extends React.Component {
|
|||||||
<NavBar />
|
<NavBar />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact path="/"
|
exact
|
||||||
render={() => (
|
path="/"
|
||||||
isLoggedIn() ? (
|
render={() =>
|
||||||
<Dashboard />
|
isLoggedIn() ? <Dashboard /> : <Redirect to="/login" />
|
||||||
) : (
|
}
|
||||||
<Redirect to="/login" />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact path="/register"
|
exact
|
||||||
render={() => (
|
path="/register"
|
||||||
|
render={() =>
|
||||||
isLoggedIn() ? (
|
isLoggedIn() ? (
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
) : (
|
) : (
|
||||||
<UserForm
|
<UserForm formType={'register'} />
|
||||||
formType={'register'}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
)}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact path="/login"
|
exact
|
||||||
render={() => (
|
path="/login"
|
||||||
|
render={() =>
|
||||||
isLoggedIn() ? (
|
isLoggedIn() ? (
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
) : (
|
) : (
|
||||||
<UserForm
|
<UserForm formType={'login'} />
|
||||||
formType={'login'}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
)}
|
}
|
||||||
/>
|
/>
|
||||||
<Route exact path="/logout" component={Logout} />
|
<Route exact path="/logout" component={Logout} />
|
||||||
<Route
|
<Route
|
||||||
exact path="/profile/edit"
|
exact
|
||||||
render={() => (
|
path="/profile/edit"
|
||||||
isLoggedIn() ? (
|
render={() =>
|
||||||
<ProfileEdit />
|
isLoggedIn() ? <ProfileEdit /> : <UserForm formType={'login'} />
|
||||||
) : (
|
}
|
||||||
<UserForm
|
|
||||||
formType={'login'}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact path="/profile"
|
exact
|
||||||
render={() => (
|
path="/profile"
|
||||||
isLoggedIn() ? (
|
render={() =>
|
||||||
<Profile />
|
isLoggedIn() ? <Profile /> : <UserForm formType={'login'} />
|
||||||
) : (
|
}
|
||||||
<UserForm
|
|
||||||
formType={'login'}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact path="/activities/history"
|
|
||||||
component={Activities}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact path="/activities/statistics"
|
|
||||||
component={Statistics}
|
|
||||||
/>
|
/>
|
||||||
|
<Route exact path="/activities/history" component={Activities} />
|
||||||
|
<Route exact path="/activities/statistics" component={Statistics} />
|
||||||
<Route path="/activities" component={Activity} />
|
<Route path="/activities" component={Activity} />
|
||||||
{/* <Route path="/admin" component={Admin} /> */}
|
{/* <Route path="/admin" component={Admin} /> */}
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import { formatValue } from '../../../utils/stats'
|
|||||||
/**
|
/**
|
||||||
* @return {null}
|
* @return {null}
|
||||||
*/
|
*/
|
||||||
export default function CustomLabel (props) {
|
export default function CustomLabel(props) {
|
||||||
const { displayedData, x, y, width, value } = props
|
const { displayedData, x, y, width, value } = props
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null
|
return null
|
||||||
|
@ -2,34 +2,32 @@ import React from 'react'
|
|||||||
|
|
||||||
import { formatDuration } from '../../../utils/stats'
|
import { formatDuration } from '../../../utils/stats'
|
||||||
|
|
||||||
const formatValue = (displayedData, value) => displayedData === 'duration'
|
const formatValue = (displayedData, value) =>
|
||||||
? formatDuration(value, true)
|
displayedData === 'duration'
|
||||||
: displayedData === 'distance'
|
? formatDuration(value, true)
|
||||||
|
: displayedData === 'distance'
|
||||||
? value.toFixed(2)
|
? value.toFixed(2)
|
||||||
: value
|
: value
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {null}
|
* @return {null}
|
||||||
*/
|
*/
|
||||||
export default function CustomTooltip (props) {
|
export default function CustomTooltip(props) {
|
||||||
const { active } = props
|
const { active } = props
|
||||||
if (active) {
|
if (active) {
|
||||||
const { displayedData, payload, label } = props
|
const { displayedData, payload, label } = props
|
||||||
let total = 0
|
let total = 0
|
||||||
payload.map(p => total += p.value)
|
payload.map(p => (total += p.value))
|
||||||
return (
|
return (
|
||||||
<div className="custom-tooltip">
|
<div className="custom-tooltip">
|
||||||
<p className="custom-tooltip-label">{label}</p>
|
<p className="custom-tooltip-label">{label}</p>
|
||||||
{payload.map(p => (
|
{payload.map(p => (
|
||||||
<p key={p.name} style={{ color: p.fill }}>
|
<p key={p.name} style={{ color: p.fill }}>
|
||||||
{p.name}: {formatValue(displayedData, p.value)} {p.unit}
|
{p.name}: {formatValue(displayedData, p.value)} {p.unit}
|
||||||
</p>))
|
|
||||||
}
|
|
||||||
{payload.length > 0 && (
|
|
||||||
<p>
|
|
||||||
Total: {formatValue(displayedData, total)}
|
|
||||||
</p>
|
</p>
|
||||||
|
))}
|
||||||
|
{payload.length > 0 && (
|
||||||
|
<p>Total: {formatValue(displayedData, total)}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis
|
Bar,
|
||||||
|
BarChart,
|
||||||
|
ResponsiveContainer,
|
||||||
|
Tooltip,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
} from 'recharts'
|
} from 'recharts'
|
||||||
|
|
||||||
import { activityColors } from '../../../utils/activities'
|
import { activityColors } from '../../../utils/activities'
|
||||||
@ -8,17 +13,16 @@ import { formatValue } from '../../../utils/stats'
|
|||||||
import CustomTooltip from './CustomTooltip'
|
import CustomTooltip from './CustomTooltip'
|
||||||
import CustomLabel from './CustomLabel'
|
import CustomLabel from './CustomLabel'
|
||||||
|
|
||||||
|
|
||||||
export default class StatsCharts extends React.PureComponent {
|
export default class StatsCharts extends React.PureComponent {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
displayedData: 'distance'
|
displayedData: 'distance',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleRadioChange (changeEvent) {
|
handleRadioChange(changeEvent) {
|
||||||
this.setState({
|
this.setState({
|
||||||
displayedData: changeEvent.target.name
|
displayedData: changeEvent.target.name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,22 +64,14 @@ export default class StatsCharts extends React.PureComponent {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<ResponsiveContainer height={300}>
|
<ResponsiveContainer height={300}>
|
||||||
<BarChart
|
<BarChart data={stats[displayedData]} margin={{ top: 15, bottom: 0 }}>
|
||||||
data={stats[displayedData]}
|
|
||||||
margin={{ top: 15, bottom: 0 }}
|
|
||||||
>
|
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="date"
|
dataKey="date"
|
||||||
interval={0} // to force to display all ticks
|
interval={0} // to force to display all ticks
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis tickFormatter={value => formatValue(displayedData, value)} />
|
||||||
tickFormatter={value => formatValue(displayedData, value)}
|
<Tooltip
|
||||||
/>
|
content={<CustomTooltip displayedData={displayedData} />}
|
||||||
<Tooltip content={
|
|
||||||
<CustomTooltip
|
|
||||||
displayedData={displayedData}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{sports.map((s, i) => (
|
{sports.map((s, i) => (
|
||||||
<Bar
|
<Bar
|
||||||
@ -86,9 +82,12 @@ export default class StatsCharts extends React.PureComponent {
|
|||||||
dataKey={s.label}
|
dataKey={s.label}
|
||||||
stackId="a"
|
stackId="a"
|
||||||
fill={activityColors[i]}
|
fill={activityColors[i]}
|
||||||
label={i === sports.length - 1
|
label={
|
||||||
? <CustomLabel displayedData={displayedData} />
|
i === sports.length - 1 ? (
|
||||||
: ''
|
<CustomLabel displayedData={displayedData} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -6,14 +6,14 @@ import { getStats } from '../../../actions/stats'
|
|||||||
import { formatStats } from '../../../utils/stats'
|
import { formatStats } from '../../../utils/stats'
|
||||||
import StatsChart from './StatsChart'
|
import StatsChart from './StatsChart'
|
||||||
|
|
||||||
|
|
||||||
class Statistics extends React.PureComponent {
|
class Statistics extends React.PureComponent {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.updateData()
|
this.updateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if ((this.props.user.id && (this.props.user.id !== prevProps.user.id)) ||
|
if (
|
||||||
|
(this.props.user.id && this.props.user.id !== prevProps.user.id) ||
|
||||||
this.props.statsParams !== prevProps.statsParams
|
this.props.statsParams !== prevProps.statsParams
|
||||||
) {
|
) {
|
||||||
this.updateData()
|
this.updateData()
|
||||||
@ -22,22 +22,23 @@ class Statistics extends React.PureComponent {
|
|||||||
|
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.props.user.id) {
|
if (this.props.user.id) {
|
||||||
this.props.loadActivities(
|
this.props.loadActivities(this.props.user.id, this.props.statsParams)
|
||||||
this.props.user.id,
|
|
||||||
this.props.statsParams,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
displayedSports, sports, statistics, statsParams, displayEmpty
|
displayedSports,
|
||||||
|
sports,
|
||||||
|
statistics,
|
||||||
|
statsParams,
|
||||||
|
displayEmpty,
|
||||||
} = this.props
|
} = this.props
|
||||||
if (!displayEmpty && Object.keys(statistics).length === 0) {
|
if (!displayEmpty && Object.keys(statistics).length === 0) {
|
||||||
return 'No workouts'
|
return 'No workouts'
|
||||||
}
|
}
|
||||||
const stats = formatStats(statistics, sports, statsParams, displayedSports)
|
const stats = formatStats(statistics, sports, statsParams, displayedSports)
|
||||||
return (<StatsChart sports={sports} stats={stats} />)
|
return <StatsChart sports={sports} stats={stats} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ export default connect(
|
|||||||
const params = {
|
const params = {
|
||||||
from: format(data.start, dateFormat),
|
from: format(data.start, dateFormat),
|
||||||
to: format(data.end, dateFormat),
|
to: format(data.end, dateFormat),
|
||||||
time: data.duration
|
time: data.duration,
|
||||||
}
|
}
|
||||||
dispatch(getStats(userId, data.type, params))
|
dispatch(getStats(userId, data.type, params))
|
||||||
},
|
},
|
||||||
|
@ -4,19 +4,21 @@ import { Link } from 'react-router-dom'
|
|||||||
|
|
||||||
import { apiUrl, getDateWithTZ } from '../../utils'
|
import { apiUrl, getDateWithTZ } from '../../utils'
|
||||||
|
|
||||||
export default function ActivityCard (props) {
|
export default function ActivityCard(props) {
|
||||||
const { activity, sports, user } = props
|
const { activity, sports, user } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card text-center">
|
<div className="card activity-card text-center">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<Link to={`/activities/${activity.id}`}>
|
<Link to={`/activities/${activity.id}`}>
|
||||||
{sports.filter(sport => sport.id === activity.sport_id)
|
{sports
|
||||||
.map(sport => sport.label)} -{' '}
|
.filter(sport => sport.id === activity.sport_id)
|
||||||
{format(
|
.map(sport => sport.label)}{' '}
|
||||||
getDateWithTZ(activity.activity_date, user.timezone),
|
-{' '}
|
||||||
'dd/MM/yyyy HH:mm'
|
{format(
|
||||||
)}
|
getDateWithTZ(activity.activity_date, user.timezone),
|
||||||
|
'dd/MM/yyyy HH:mm'
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
@ -25,15 +27,14 @@ export default function ActivityCard (props) {
|
|||||||
<div className="col">
|
<div className="col">
|
||||||
<img
|
<img
|
||||||
alt="Map"
|
alt="Map"
|
||||||
src={`${apiUrl}activities/map/${activity.map}` +
|
src={
|
||||||
`?${Date.now()}`}
|
`${apiUrl}activities/map/${activity.map}` + `?${Date.now()}`
|
||||||
|
}
|
||||||
className="img-fluid"
|
className="img-fluid"
|
||||||
/>
|
/>
|
||||||
<div className="map-attribution text-right">
|
<div className="map-attribution text-right">
|
||||||
<div>
|
<div>
|
||||||
<span className="map-attribution-text">
|
<span className="map-attribution-text">©</span>
|
||||||
©
|
|
||||||
</span>
|
|
||||||
<a
|
<a
|
||||||
className="map-attribution-text"
|
className="map-attribution-text"
|
||||||
href="http://www.openstreetmap.org/copyright"
|
href="http://www.openstreetmap.org/copyright"
|
||||||
@ -48,15 +49,18 @@ export default function ActivityCard (props) {
|
|||||||
)}
|
)}
|
||||||
<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" /> Duration:{' '}
|
||||||
Duration: {activity.moving}
|
{activity.moving}
|
||||||
{activity.map ? (
|
{activity.map ? (
|
||||||
<span><br /><br /></span>
|
<span>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
) : (
|
) : (
|
||||||
' - '
|
' - '
|
||||||
)}
|
)}
|
||||||
<i className="fa fa-road" aria-hidden="true" />{' '}
|
<i className="fa fa-road" aria-hidden="true" /> Distance:{' '}
|
||||||
Distance: {activity.distance} km
|
{activity.distance} km
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
// source: https://blog.flowandform.agency/create-a-custom-calendar-in-react-3df1bfd0b728
|
// source: https://blog.flowandform.agency/create-a-custom-calendar-in-react-3df1bfd0b728
|
||||||
import {
|
import {
|
||||||
addDays, addMonths, endOfMonth, endOfWeek, format, isSameDay, isSameMonth,
|
addDays,
|
||||||
startOfMonth, startOfWeek, subMonths
|
addMonths,
|
||||||
|
endOfMonth,
|
||||||
|
endOfWeek,
|
||||||
|
format,
|
||||||
|
isSameDay,
|
||||||
|
isSameMonth,
|
||||||
|
startOfMonth,
|
||||||
|
startOfWeek,
|
||||||
|
subMonths,
|
||||||
} from 'date-fns'
|
} from 'date-fns'
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
@ -21,7 +29,6 @@ const getStartAndEndMonth = date => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Calendar extends React.Component {
|
class Calendar extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
@ -42,21 +49,13 @@ class Calendar extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="header row flex-middle">
|
<div className="header row flex-middle">
|
||||||
<div className="col col-start" onClick={() => this.handlePrevMonth()}>
|
<div className="col col-start" onClick={() => this.handlePrevMonth()}>
|
||||||
<i
|
<i className="fa fa-chevron-left" aria-hidden="true" />
|
||||||
className="fa fa-chevron-left"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col col-center">
|
<div className="col col-center">
|
||||||
<span>
|
<span>{format(this.state.currentMonth, dateFormat)}</span>
|
||||||
{format(this.state.currentMonth, dateFormat)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col col-end" onClick={() => this.handleNextMonth()}>
|
<div className="col col-end" onClick={() => this.handleNextMonth()}>
|
||||||
<i
|
<i className="fa fa-chevron-right" aria-hidden="true" />
|
||||||
className="fa fa-chevron-right"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -80,11 +79,9 @@ class Calendar extends React.Component {
|
|||||||
filterActivities(day) {
|
filterActivities(day) {
|
||||||
const { activities, user } = this.props
|
const { activities, user } = this.props
|
||||||
if (activities) {
|
if (activities) {
|
||||||
return activities
|
return activities.filter(act =>
|
||||||
.filter(act => isSameDay(
|
isSameDay(getDateWithTZ(act.activity_date, user.timezone), day)
|
||||||
getDateWithTZ(act.activity_date, user.timezone),
|
)
|
||||||
day
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -104,14 +101,9 @@ class Calendar extends React.Component {
|
|||||||
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 dayActivities = this.filterActivities(day)
|
||||||
const isDisabled = isSameMonth(day, currentMonth)
|
const isDisabled = isSameMonth(day, currentMonth) ? '' : 'disabled'
|
||||||
? ''
|
|
||||||
: 'disabled'
|
|
||||||
days.push(
|
days.push(
|
||||||
<div
|
<div className={`col cell img-${isDisabled}`} key={day}>
|
||||||
className={`col cell img-${isDisabled}`}
|
|
||||||
key={day}
|
|
||||||
>
|
|
||||||
<span className="number">{formattedDate}</span>
|
<span className="number">{formattedDate}</span>
|
||||||
{dayActivities.map(act => (
|
{dayActivities.map(act => (
|
||||||
<Link key={act.id} to={`/activities/${act.id}`}>
|
<Link key={act.id} to={`/activities/${act.id}`}>
|
||||||
@ -129,11 +121,14 @@ class Calendar extends React.Component {
|
|||||||
<i
|
<i
|
||||||
className="fa fa-trophy custom-fa-small"
|
className="fa fa-trophy custom-fa-small"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
title={act.records.map(rec => ` ${
|
title={act.records.map(
|
||||||
recordsLabels.filter(
|
rec =>
|
||||||
r => r.record_type === rec.record_type
|
` ${
|
||||||
)[0].label
|
recordsLabels.filter(
|
||||||
}`)}
|
r => r.record_type === rec.record_type
|
||||||
|
)[0].label
|
||||||
|
}`
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</sup>
|
</sup>
|
||||||
)}
|
)}
|
||||||
@ -196,10 +191,9 @@ export default connect(
|
|||||||
dispatch => ({
|
dispatch => ({
|
||||||
loadMonthActivities: (start, end) => {
|
loadMonthActivities: (start, end) => {
|
||||||
const dateFormat = 'yyyy-MM-dd'
|
const dateFormat = 'yyyy-MM-dd'
|
||||||
dispatch(getMonthActivities(
|
dispatch(
|
||||||
format(start, dateFormat),
|
getMonthActivities(format(start, dateFormat), format(end, dateFormat))
|
||||||
format(end, dateFormat),
|
)
|
||||||
))
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)(Calendar)
|
)(Calendar)
|
||||||
|
@ -3,67 +3,59 @@ import { Link } from 'react-router-dom'
|
|||||||
|
|
||||||
import { formatRecord } from '../../utils/activities'
|
import { formatRecord } from '../../utils/activities'
|
||||||
|
|
||||||
export default function RecordsCard (props) {
|
export default function RecordsCard(props) {
|
||||||
const { records, sports, user } = props
|
const { records, sports, user } = props
|
||||||
const recordsBySport = records.reduce((sportList, record) => {
|
const recordsBySport = records.reduce((sportList, record) => {
|
||||||
const sport = sports.find(s => s.id === record.sport_id)
|
const sport = sports.find(s => s.id === record.sport_id)
|
||||||
if (sportList[sport.label] === void 0) {
|
if (sportList[sport.label] === void 0) {
|
||||||
sportList[sport.label] = {
|
sportList[sport.label] = {
|
||||||
img: sport.img,
|
img: sport.img,
|
||||||
records: [],
|
records: [],
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sportList[sport.label].records.push(formatRecord(record, user.timezone))
|
}
|
||||||
return sportList
|
sportList[sport.label].records.push(formatRecord(record, user.timezone))
|
||||||
|
return sportList
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-header">
|
<div className="card-header">Personal records</div>
|
||||||
Personal records
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{Object.keys(recordsBySport).length === 0
|
{Object.keys(recordsBySport).length === 0
|
||||||
? 'No records'
|
? 'No records'
|
||||||
: (Object.keys(recordsBySport).map(sportLabel => (
|
: Object.keys(recordsBySport).map(sportLabel => (
|
||||||
<table
|
<table
|
||||||
className="table table-borderless table-sm record-table"
|
className="table table-borderless table-sm record-table"
|
||||||
key={sportLabel}
|
key={sportLabel}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colSpan="3">
|
<th colSpan="3">
|
||||||
<img
|
<img
|
||||||
alt={`${sportLabel} logo`}
|
alt={`${sportLabel} logo`}
|
||||||
className="record-logo"
|
className="record-logo"
|
||||||
src={recordsBySport[sportLabel].img}
|
src={recordsBySport[sportLabel].img}
|
||||||
/>
|
/>
|
||||||
{sportLabel}
|
{sportLabel}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{recordsBySport[sportLabel].records.map(rec => (
|
|
||||||
<tr key={rec.id}>
|
|
||||||
<td>
|
|
||||||
{rec.record_type}
|
|
||||||
</td>
|
|
||||||
<td className="text-right">
|
|
||||||
{rec.value}
|
|
||||||
</td>
|
|
||||||
<td className="text-right">
|
|
||||||
<Link to={`/activities/${rec.activity_id}`}>
|
|
||||||
{rec.activity_date}
|
|
||||||
</Link>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>))
|
{recordsBySport[sportLabel].records.map(rec => (
|
||||||
)
|
<tr key={rec.id}>
|
||||||
}
|
<td>{rec.record_type}</td>
|
||||||
|
<td className="text-right">{rec.value}</td>
|
||||||
|
<td className="text-right">
|
||||||
|
<Link to={`/activities/${rec.activity_id}`}>
|
||||||
|
{rec.activity_date}
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import React from 'react'
|
|||||||
|
|
||||||
import Stats from '../Common/Stats'
|
import Stats from '../Common/Stats'
|
||||||
|
|
||||||
|
|
||||||
export default class Statistics extends React.Component {
|
export default class Statistics extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
@ -19,9 +18,7 @@ export default class Statistics extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-header">
|
<div className="card-header">This month</div>
|
||||||
This month
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<Stats displayEmpty={false} statsParams={this.state} />
|
<Stats displayEmpty={false} statsParams={this.state} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
export default function UserStatistics(props) {
|
||||||
export default function UserStatistics (props) {
|
|
||||||
const { user } = props
|
const { user } = props
|
||||||
const days = user.total_duration.match(/day/g)
|
const days = user.total_duration.match(/day/g)
|
||||||
? `${user.total_duration.split(',')[0]},`
|
? `${user.total_duration.split(',')[0]},`
|
||||||
|
@ -25,11 +25,17 @@ class DashBoard extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
activities, loadMoreActivities, message, records, sports, user
|
activities,
|
||||||
|
loadMoreActivities,
|
||||||
|
message,
|
||||||
|
records,
|
||||||
|
sports,
|
||||||
|
user,
|
||||||
} = this.props
|
} = this.props
|
||||||
const paginationEnd = activities.length > 0
|
const paginationEnd =
|
||||||
? activities[activities.length - 1].previous_activity === null
|
activities.length > 0
|
||||||
: true
|
? activities[activities.length - 1].previous_activity === null
|
||||||
|
: true
|
||||||
const { page } = this.state
|
const { page } = this.state
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -39,7 +45,8 @@ class DashBoard extends React.Component {
|
|||||||
{message ? (
|
{message ? (
|
||||||
<code>{message}</code>
|
<code>{message}</code>
|
||||||
) : (
|
) : (
|
||||||
(activities && sports.length > 0) && (
|
activities &&
|
||||||
|
sports.length > 0 && (
|
||||||
<div className="container dashboard">
|
<div className="container dashboard">
|
||||||
<UserStatistics user={user} />
|
<UserStatistics user={user} />
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -51,23 +58,24 @@ class DashBoard extends React.Component {
|
|||||||
<Calendar />
|
<Calendar />
|
||||||
{activities.length > 0 ? (
|
{activities.length > 0 ? (
|
||||||
activities.map(activity => (
|
activities.map(activity => (
|
||||||
<ActivityCard
|
<ActivityCard
|
||||||
activity={activity}
|
activity={activity}
|
||||||
key={activity.id}
|
key={activity.id}
|
||||||
sports={sports}
|
sports={sports}
|
||||||
user={user}
|
user={user}
|
||||||
/>)
|
/>
|
||||||
)) : (
|
))
|
||||||
|
) : (
|
||||||
<div className="card text-center">
|
<div className="card text-center">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
No workouts. {' '}
|
No workouts.{' '}
|
||||||
<Link to={{ pathname: '/activities/add' }}>
|
<Link to={{ pathname: '/activities/add' }}>
|
||||||
Upload one !
|
Upload one !
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!paginationEnd &&
|
{!paginationEnd && (
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-default btn-md btn-block"
|
className="btn btn-default btn-md btn-block"
|
||||||
@ -77,7 +85,7 @@ class DashBoard extends React.Component {
|
|||||||
this.setState({ page: page + 1 })
|
this.setState({ page: page + 1 })
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,8 @@ export default function Footer() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
source code
|
source code
|
||||||
</a> under{' '}
|
</a>{' '}
|
||||||
|
under{' '}
|
||||||
<a
|
<a
|
||||||
href="https://choosealicense.com/licenses/gpl-3.0/"
|
href="https://choosealicense.com/licenses/gpl-3.0/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -21,7 +22,7 @@ export default function Footer() {
|
|||||||
>
|
>
|
||||||
GPLv3
|
GPLv3
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
license -{' '}
|
license -{' '}
|
||||||
<a
|
<a
|
||||||
href="https://samr1.github.io/FitTrackee/"
|
href="https://samr1.github.io/FitTrackee/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -33,4 +34,3 @@ export default function Footer() {
|
|||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,16 +75,16 @@ class NavBar extends React.PureComponent {
|
|||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
{/* {user.admin && ( */}
|
{/* {user.admin && ( */}
|
||||||
{/* <li className="nav-item"> */}
|
{/* <li className="nav-item"> */}
|
||||||
{/* <Link */}
|
{/* <Link */}
|
||||||
{/* className="nav-link" */}
|
{/* className="nav-link" */}
|
||||||
{/* to={{ */}
|
{/* to={{ */}
|
||||||
{/* pathname: '/admin', */}
|
{/* pathname: '/admin', */}
|
||||||
{/* }} */}
|
{/* }} */}
|
||||||
{/* > */}
|
{/* > */}
|
||||||
{/* Admin */}
|
{/* Admin */}
|
||||||
{/* </Link> */}
|
{/* </Link> */}
|
||||||
{/* </li> */}
|
{/* </li> */}
|
||||||
{/* )} */}
|
{/* )} */}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
<ul className="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||||
@ -115,8 +115,7 @@ class NavBar extends React.PureComponent {
|
|||||||
{picture === true && (
|
{picture === true && (
|
||||||
<img
|
<img
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
src={`${apiUrl}users/${id}/picture` +
|
src={`${apiUrl}users/${id}/picture` + `?${Date.now()}`}
|
||||||
`?${Date.now()}`}
|
|
||||||
className="img-fluid App-nav-profile-img"
|
className="img-fluid App-nav-profile-img"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -153,11 +152,9 @@ class NavBar extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(({ user }) => ({
|
||||||
({ user }) => ({
|
id: user.id,
|
||||||
id: user.id,
|
isAuthenticated: user.isAuthenticated,
|
||||||
isAuthenticated: user.isAuthenticated,
|
picture: user.picture,
|
||||||
picture: user.picture,
|
username: user.username,
|
||||||
username: user.username,
|
}))(NavBar)
|
||||||
})
|
|
||||||
)(NavBar)
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
|
||||||
export default function AccessDenied () {
|
export default function AccessDenied() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -9,7 +9,7 @@ export default function AccessDenied () {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
<h1 className="page-title">Access denied</h1>
|
<h1 className="page-title">Access denied</h1>
|
||||||
<p className="App-center">
|
<p className="App-center">
|
||||||
{'You don\'t have permissions to access this page.'}
|
{"You don't have permissions to access this page."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
|
|
||||||
export default function NotFound () {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>fittrackee - 404</title>
|
<title>fittrackee - 404</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<h1 className="page-title">Page not found</h1>
|
<h1 className="page-title">Page not found</h1>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
addYears,
|
addYears,
|
||||||
subMonths,
|
subMonths,
|
||||||
subWeeks,
|
subWeeks,
|
||||||
subYears
|
subYears,
|
||||||
} from 'date-fns'
|
} from 'date-fns'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
@ -32,7 +32,7 @@ class Statistics extends React.Component {
|
|||||||
end: endOfMonth(date),
|
end: endOfMonth(date),
|
||||||
duration: 'month',
|
duration: 'month',
|
||||||
type: 'by_time',
|
type: 'by_time',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,26 +51,26 @@ class Statistics extends React.Component {
|
|||||||
const duration = e.target.name
|
const duration = e.target.name
|
||||||
|
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
const start = duration === 'year'
|
const start =
|
||||||
? startOfYear(subYears(date, 9))
|
duration === 'year'
|
||||||
: duration === 'week'
|
? startOfYear(subYears(date, 9))
|
||||||
|
: duration === 'week'
|
||||||
? startOfMonth(subMonths(date, 2))
|
? startOfMonth(subMonths(date, 2))
|
||||||
: startOfMonth(subMonths(date, 11))
|
: startOfMonth(subMonths(date, 11))
|
||||||
const end = duration === 'year'
|
const end =
|
||||||
? endOfYear(date)
|
duration === 'year'
|
||||||
: duration === 'week'
|
? endOfYear(date)
|
||||||
|
: duration === 'week'
|
||||||
? endOfWeek(date)
|
? endOfWeek(date)
|
||||||
: endOfMonth(date)
|
: endOfMonth(date)
|
||||||
this.setState({ statsParams:
|
this.setState({ statsParams: { duration, end, start, type: 'by_time' } })
|
||||||
{ duration, end, start, type: 'by_time' }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnChangeSports(sportId) {
|
handleOnChangeSports(sportId) {
|
||||||
const { displayedSports } = this.state
|
const { displayedSports } = this.state
|
||||||
if (displayedSports.includes(sportId)) {
|
if (displayedSports.includes(sportId)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
displayedSports: displayedSports.filter(s => s !== sportId)
|
displayedSports: displayedSports.filter(s => s !== sportId),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.setState({ displayedSports: displayedSports.concat([sportId]) })
|
this.setState({ displayedSports: displayedSports.concat([sportId]) })
|
||||||
@ -81,30 +81,34 @@ class Statistics extends React.Component {
|
|||||||
const { start, end, duration } = this.state.statsParams
|
const { start, end, duration } = this.state.statsParams
|
||||||
let newStart, newEnd
|
let newStart, newEnd
|
||||||
if (forward) {
|
if (forward) {
|
||||||
newStart = duration === 'year'
|
newStart =
|
||||||
? startOfYear(subYears(start, 1))
|
duration === 'year'
|
||||||
: duration === 'week'
|
? startOfYear(subYears(start, 1))
|
||||||
|
: duration === 'week'
|
||||||
? startOfWeek(subWeeks(start, 1))
|
? startOfWeek(subWeeks(start, 1))
|
||||||
: startOfMonth(subMonths(start, 1))
|
: startOfMonth(subMonths(start, 1))
|
||||||
newEnd = duration === 'year'
|
newEnd =
|
||||||
? endOfYear(subYears(end, 1))
|
duration === 'year'
|
||||||
: duration === 'week'
|
? endOfYear(subYears(end, 1))
|
||||||
|
: duration === 'week'
|
||||||
? endOfWeek(subWeeks(end, 1))
|
? endOfWeek(subWeeks(end, 1))
|
||||||
: endOfMonth(subMonths(end, 1))
|
: endOfMonth(subMonths(end, 1))
|
||||||
} else {
|
} else {
|
||||||
newStart = duration === 'year'
|
newStart =
|
||||||
? startOfYear(addYears(start, 1))
|
duration === 'year'
|
||||||
: duration === 'week'
|
? startOfYear(addYears(start, 1))
|
||||||
|
: duration === 'week'
|
||||||
? startOfWeek(addWeeks(start, 1))
|
? startOfWeek(addWeeks(start, 1))
|
||||||
: startOfMonth(addMonths(start, 1))
|
: startOfMonth(addMonths(start, 1))
|
||||||
newEnd = duration === 'year'
|
newEnd =
|
||||||
? endOfYear(addYears(end, 1))
|
duration === 'year'
|
||||||
: duration === 'week'
|
? endOfYear(addYears(end, 1))
|
||||||
|
: duration === 'week'
|
||||||
? endOfWeek(addWeeks(end, 1))
|
? endOfWeek(addWeeks(end, 1))
|
||||||
: endOfMonth(addMonths(end, 1))
|
: endOfMonth(addMonths(end, 1))
|
||||||
}
|
}
|
||||||
this.setState({ statsParams:
|
this.setState({
|
||||||
{ duration, end: newEnd, start: newStart, type: 'by_time' }
|
statsParams: { duration, end: newEnd, start: newStart, type: 'by_time' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,9 +122,7 @@ class Statistics extends React.Component {
|
|||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="container dashboard">
|
<div className="container dashboard">
|
||||||
<div className="card activity-card">
|
<div className="card activity-card">
|
||||||
<div className="card-header">
|
<div className="card-header">Statistics</div>
|
||||||
Statistics
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="chart-filters row">
|
<div className="chart-filters row">
|
||||||
<div className="col chart-arrows">
|
<div className="col chart-arrows">
|
||||||
@ -135,7 +137,7 @@ class Statistics extends React.Component {
|
|||||||
<div className="col-md-3 time-frames justify-content-around">
|
<div className="col-md-3 time-frames justify-content-around">
|
||||||
{durations.map(d => (
|
{durations.map(d => (
|
||||||
<div className="time-frame" key={d}>
|
<div className="time-frame" key={d}>
|
||||||
<label >
|
<label>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
id={d}
|
id={d}
|
||||||
@ -143,7 +145,7 @@ class Statistics extends React.Component {
|
|||||||
checked={d === statsParams.duration}
|
checked={d === statsParams.duration}
|
||||||
onChange={e => this.handleOnChangeDuration(e)}
|
onChange={e => this.handleOnChangeDuration(e)}
|
||||||
/>
|
/>
|
||||||
<span>{ d }</span>
|
<span>{d}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -186,8 +188,6 @@ class Statistics extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(state => ({
|
||||||
state => ({
|
sports: state.sports.data,
|
||||||
sports: state.sports.data,
|
}))(Statistics)
|
||||||
})
|
|
||||||
)(Statistics)
|
|
||||||
|
@ -4,7 +4,7 @@ import { Helmet } from 'react-helmet'
|
|||||||
import { history } from '../../index'
|
import { history } from '../../index'
|
||||||
import { isRegistrationAllowed } from '../../utils'
|
import { isRegistrationAllowed } from '../../utils'
|
||||||
|
|
||||||
export default function Form (props) {
|
export default function Form(props) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
@ -17,7 +17,8 @@ export default function Form (props) {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-3" />
|
<div className="col-md-3" />
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<hr /><br />
|
<hr />
|
||||||
|
<br />
|
||||||
{props.formType === 'register' && !isRegistrationAllowed ? (
|
{props.formType === 'register' && !isRegistrationAllowed ? (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-body">Registration is disabled.</div>
|
<div className="card-body">Registration is disabled.</div>
|
||||||
@ -26,27 +27,30 @@ export default function Form (props) {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
className="btn btn-secondary btn-lg btn-block"
|
||||||
onClick={() => history.go(-1)}
|
onClick={() => history.go(-1)}
|
||||||
>Back
|
>
|
||||||
|
Back
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<form onSubmit={event =>
|
<form
|
||||||
props.handleUserFormSubmit(event, props.formType)}
|
onSubmit={event =>
|
||||||
>
|
props.handleUserFormSubmit(event, props.formType)
|
||||||
{props.formType === 'register' &&
|
|
||||||
<div className="form-group">
|
|
||||||
<input
|
|
||||||
className="form-control input-lg"
|
|
||||||
name="username"
|
|
||||||
placeholder="Enter a username"
|
|
||||||
required
|
|
||||||
type="text"
|
|
||||||
value={props.userForm.username}
|
|
||||||
onChange={props.onHandleFormChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
>
|
||||||
|
{props.formType === 'register' && (
|
||||||
|
<div className="form-group">
|
||||||
|
<input
|
||||||
|
className="form-control input-lg"
|
||||||
|
name="username"
|
||||||
|
placeholder="Enter a username"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
value={props.userForm.username}
|
||||||
|
onChange={props.onHandleFormChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<input
|
<input
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
@ -69,19 +73,19 @@ export default function Form (props) {
|
|||||||
onChange={props.onHandleFormChange}
|
onChange={props.onHandleFormChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{props.formType === 'register' &&
|
{props.formType === 'register' && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<input
|
<input
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
name="password_conf"
|
name="password_conf"
|
||||||
placeholder="Enter the password confirmation"
|
placeholder="Enter the password confirmation"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
value={props.userForm.password_conf}
|
value={props.userForm.password_conf}
|
||||||
onChange={props.onHandleFormChange}
|
onChange={props.onHandleFormChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-primary btn-lg btn-block"
|
className="btn btn-primary btn-lg btn-block"
|
||||||
|
@ -16,8 +16,8 @@ class Logout extends React.Component {
|
|||||||
<div className="card col-8">
|
<div className="card col-8">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
You are now logged out.
|
You are now logged out. Click <Link to="/login">here</Link> to
|
||||||
Click <Link to="/login">here</Link> to log back in.
|
log back in.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -35,6 +35,6 @@ export default connect(
|
|||||||
dispatch => ({
|
dispatch => ({
|
||||||
UserLogout: () => {
|
UserLogout: () => {
|
||||||
dispatch(logout())
|
dispatch(logout())
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
)(Logout)
|
)(Logout)
|
||||||
|
@ -7,22 +7,20 @@ import { Link } from 'react-router-dom'
|
|||||||
import { deletePicture, uploadPicture } from '../../actions/user'
|
import { deletePicture, uploadPicture } from '../../actions/user'
|
||||||
import { apiUrl } from '../../utils'
|
import { apiUrl } from '../../utils'
|
||||||
|
|
||||||
function Profile ({ message, onDeletePicture, onUploadPicture, user }) {
|
function Profile({ message, onDeletePicture, onUploadPicture, user }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - Profile</title>
|
<title>FitTrackee - Profile</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{ message !== '' && (
|
{message !== '' && <code>{message}</code>}
|
||||||
<code>{message}</code>
|
|
||||||
)}
|
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<h1 className="page-title">Profile</h1>
|
<h1 className="page-title">Profile</h1>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-header userName">
|
<div className="card-header userName">
|
||||||
{user.username} {' '}
|
{user.username}{' '}
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: '/profile/edit',
|
pathname: '/profile/edit',
|
||||||
@ -35,48 +33,49 @@ function Profile ({ message, onDeletePicture, onUploadPicture, user }) {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
<p>Email: {user.email}</p>
|
<p>Email: {user.email}</p>
|
||||||
<p>Registration Date: {
|
<p>
|
||||||
format(new Date(user.created_at), 'dd/MM/yyyy HH:mm')
|
Registration Date:{' '}
|
||||||
}
|
{format(new Date(user.created_at), 'dd/MM/yyyy HH:mm')}
|
||||||
</p>
|
</p>
|
||||||
<p>First Name: {user.first_name}</p>
|
<p>First Name: {user.first_name}</p>
|
||||||
<p>Last Name: {user.last_name}</p>
|
<p>Last Name: {user.last_name}</p>
|
||||||
<p>Birth Date: {user.birth_date
|
<p>
|
||||||
|
Birth Date:{' '}
|
||||||
|
{user.birth_date
|
||||||
? format(new Date(user.birth_date), 'dd/MM/yyyy')
|
? format(new Date(user.birth_date), 'dd/MM/yyyy')
|
||||||
: ''
|
: ''}
|
||||||
}
|
|
||||||
</p>
|
</p>
|
||||||
<p>Location: {user.location}</p>
|
<p>Location: {user.location}</p>
|
||||||
<p>Bio: {user.bio}</p>
|
<p>Bio: {user.bio}</p>
|
||||||
<p>Time zone: {user.timezone}</p>
|
<p>Time zone: {user.timezone}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-4">
|
<div className="col-md-4">
|
||||||
{ user.picture === true && (
|
{user.picture === true && (
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
src={`${apiUrl}users/${user.id}/picture` +
|
src={
|
||||||
`?${Date.now()}`}
|
`${apiUrl}users/${user.id}/picture` +
|
||||||
className="img-fluid App-profile-img-small"
|
`?${Date.now()}`
|
||||||
/>
|
}
|
||||||
<br />
|
className="img-fluid App-profile-img-small"
|
||||||
<button
|
/>
|
||||||
type="submit"
|
<br />
|
||||||
onClick={() => onDeletePicture()}
|
<button type="submit" onClick={() => onDeletePicture()}>
|
||||||
>
|
Delete picture
|
||||||
Delete picture
|
</button>
|
||||||
</button>
|
<br />
|
||||||
<br /><br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<form
|
<form
|
||||||
encType="multipart/form-data"
|
encType="multipart/form-data"
|
||||||
onSubmit={event => onUploadPicture(event)}
|
onSubmit={event => onUploadPicture(event)}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
name="picture"
|
name="picture"
|
||||||
accept=".png,.jpg,.gif"
|
accept=".png,.jpg,.gif"
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<button type="submit">Send</button>
|
<button type="submit">Send</button>
|
||||||
|
@ -7,12 +7,11 @@ import TimezonePicker from 'react-timezone'
|
|||||||
import { handleProfileFormSubmit } from '../../actions/user'
|
import { handleProfileFormSubmit } from '../../actions/user'
|
||||||
import { history } from '../../index'
|
import { history } from '../../index'
|
||||||
|
|
||||||
|
|
||||||
class ProfileEdit extends React.Component {
|
class ProfileEdit extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
formData: {}
|
formData: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,11 +28,13 @@ class ProfileEdit extends React.Component {
|
|||||||
initForm() {
|
initForm() {
|
||||||
const { user } = this.props
|
const { user } = this.props
|
||||||
const formData = {}
|
const formData = {}
|
||||||
Object.keys(user).map(k => user[k] === null
|
Object.keys(user).map(k =>
|
||||||
? formData[k] = ''
|
user[k] === null
|
||||||
: k === 'birth_date'
|
? (formData[k] = '')
|
||||||
? formData[k] = format(new Date(user[k]), 'yyyy-MM-DD')
|
: k === 'birth_date'
|
||||||
: formData[k] = user[k])
|
? (formData[k] = format(new Date(user[k]), 'yyyy-MM-DD'))
|
||||||
|
: (formData[k] = user[k])
|
||||||
|
)
|
||||||
this.setState({ formData })
|
this.setState({ formData })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ class ProfileEdit extends React.Component {
|
|||||||
this.setState(formData)
|
this.setState(formData)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { onHandleProfileFormSubmit, message, user } = this.props
|
const { onHandleProfileFormSubmit, message, user } = this.props
|
||||||
const { formData } = this.state
|
const { formData } = this.state
|
||||||
return (
|
return (
|
||||||
@ -51,9 +52,7 @@ class ProfileEdit extends React.Component {
|
|||||||
<Helmet>
|
<Helmet>
|
||||||
<title>FitTrackee - Edit Profile</title>
|
<title>FitTrackee - Edit Profile</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
{ message !== '' && (
|
{message !== '' && <code>{message}</code>}
|
||||||
<code>{message}</code>
|
|
||||||
)}
|
|
||||||
{formData.isAuthenticated && (
|
{formData.isAuthenticated && (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<h1 className="page-title">Profile Edition</h1>
|
<h1 className="page-title">Profile Edition</h1>
|
||||||
@ -61,153 +60,153 @@ class ProfileEdit extends React.Component {
|
|||||||
<div className="col-md-2" />
|
<div className="col-md-2" />
|
||||||
<div className="col-md-8">
|
<div className="col-md-8">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-header">
|
<div className="card-header">{user.username}</div>
|
||||||
{user.username}
|
|
||||||
</div>
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-12">
|
<div className="col-md-12">
|
||||||
<form onSubmit={event => {
|
<form
|
||||||
event.preventDefault()
|
onSubmit={event => {
|
||||||
onHandleProfileFormSubmit(formData)
|
event.preventDefault()
|
||||||
}}
|
onHandleProfileFormSubmit(formData)
|
||||||
>
|
}}
|
||||||
<div className="form-group">
|
>
|
||||||
<label>Email:
|
|
||||||
<input
|
|
||||||
name="email"
|
|
||||||
className="form-control input-lg"
|
|
||||||
type="text"
|
|
||||||
value={formData.email}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<label>
|
|
||||||
Registration Date:
|
|
||||||
<input
|
|
||||||
name="createdAt"
|
|
||||||
className="form-control input-lg"
|
|
||||||
type="text"
|
|
||||||
value={formData.created_at}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<label>
|
|
||||||
Password:
|
|
||||||
<input
|
|
||||||
name="password"
|
|
||||||
className="form-control input-lg"
|
|
||||||
type="password"
|
|
||||||
onChange={e => this.handleFormChange(e)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
Password Confirmation:
|
Email:
|
||||||
<input
|
<input
|
||||||
name="password_conf"
|
name="email"
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
type="password"
|
type="text"
|
||||||
onChange={e => this.handleFormChange(e)}
|
value={formData.email}
|
||||||
/>
|
readOnly
|
||||||
</label>
|
/>
|
||||||
</div>
|
</label>
|
||||||
<hr />
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
First Name:
|
Registration Date:
|
||||||
<input
|
<input
|
||||||
name="first_name"
|
name="createdAt"
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.first_name}
|
value={formData.created_at}
|
||||||
onChange={e => this.handleFormChange(e)}
|
disabled
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>
|
<label>
|
||||||
Last Name:
|
Password:
|
||||||
<input
|
<input
|
||||||
name="last_name"
|
name="password"
|
||||||
className="form-control input-lg"
|
className="form-control input-lg"
|
||||||
type="text"
|
type="password"
|
||||||
value={formData.last_name}
|
onChange={e => this.handleFormChange(e)}
|
||||||
onChange={e => this.handleFormChange(e)}
|
/>
|
||||||
/>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<div className="form-group">
|
||||||
<div className="form-group">
|
<label>
|
||||||
<label>
|
Password Confirmation:
|
||||||
Birth Date
|
<input
|
||||||
<input
|
name="password_conf"
|
||||||
name="birth_date"
|
className="form-control input-lg"
|
||||||
className="form-control input-lg"
|
type="password"
|
||||||
type="date"
|
onChange={e => this.handleFormChange(e)}
|
||||||
value={formData.birth_date}
|
/>
|
||||||
onChange={e => this.handleFormChange(e)}
|
</label>
|
||||||
/>
|
</div>
|
||||||
</label>
|
<hr />
|
||||||
</div>
|
<div className="form-group">
|
||||||
<div className="form-group">
|
<label>
|
||||||
<label>
|
First Name:
|
||||||
Location:
|
<input
|
||||||
<input
|
name="first_name"
|
||||||
name="location"
|
className="form-control input-lg"
|
||||||
className="form-control input-lg"
|
type="text"
|
||||||
type="text"
|
value={formData.first_name}
|
||||||
value={formData.location}
|
onChange={e => this.handleFormChange(e)}
|
||||||
onChange={e => this.handleFormChange(e)}
|
/>
|
||||||
/>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<div className="form-group">
|
||||||
<div className="form-group">
|
<label>
|
||||||
<label>
|
Last Name:
|
||||||
Bio:
|
<input
|
||||||
<textarea
|
name="last_name"
|
||||||
name="bio"
|
className="form-control input-lg"
|
||||||
className="form-control input-lg"
|
type="text"
|
||||||
maxLength="200"
|
value={formData.last_name}
|
||||||
value={formData.bio}
|
onChange={e => this.handleFormChange(e)}
|
||||||
onChange={e => this.handleFormChange(e)}
|
/>
|
||||||
/>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<div className="form-group">
|
||||||
<div className="form-group">
|
<label>
|
||||||
<label>
|
Birth Date
|
||||||
Timezone:
|
<input
|
||||||
<TimezonePicker
|
name="birth_date"
|
||||||
className="form-control timezone-custom-height"
|
className="form-control input-lg"
|
||||||
onChange={tz => {
|
type="date"
|
||||||
const e = { target:
|
value={formData.birth_date}
|
||||||
{
|
onChange={e => this.handleFormChange(e)}
|
||||||
name: 'timezone',
|
/>
|
||||||
value: tz ? tz : 'Europe/Paris'
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>
|
||||||
|
Location:
|
||||||
|
<input
|
||||||
|
name="location"
|
||||||
|
className="form-control input-lg"
|
||||||
|
type="text"
|
||||||
|
value={formData.location}
|
||||||
|
onChange={e => this.handleFormChange(e)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>
|
||||||
|
Bio:
|
||||||
|
<textarea
|
||||||
|
name="bio"
|
||||||
|
className="form-control input-lg"
|
||||||
|
maxLength="200"
|
||||||
|
value={formData.bio}
|
||||||
|
onChange={e => this.handleFormChange(e)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>
|
||||||
|
Timezone:
|
||||||
|
<TimezonePicker
|
||||||
|
className="form-control timezone-custom-height"
|
||||||
|
onChange={tz => {
|
||||||
|
const e = {
|
||||||
|
target: {
|
||||||
|
name: 'timezone',
|
||||||
|
value: tz ? tz : 'Europe/Paris',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
this.handleFormChange(e)
|
||||||
this.handleFormChange(e)
|
}}
|
||||||
}}
|
value={formData.timezone}
|
||||||
value={formData.timezone}
|
/>
|
||||||
/>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
</div>
|
<input
|
||||||
<input
|
type="submit"
|
||||||
type="submit"
|
className="btn btn-primary btn-lg btn-block"
|
||||||
className="btn btn-primary btn-lg btn-block"
|
value="Submit"
|
||||||
value="Submit"
|
/>
|
||||||
/>
|
<input
|
||||||
<input
|
type="submit"
|
||||||
type="submit"
|
className="btn btn-secondary btn-lg btn-block"
|
||||||
className="btn btn-secondary btn-lg btn-block"
|
onClick={() => history.push('/profile')}
|
||||||
onClick={() => history.push('/profile')}
|
value="Cancel"
|
||||||
value="Cancel"
|
/>
|
||||||
/>
|
</form>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -220,7 +219,6 @@ class ProfileEdit extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -15,7 +15,7 @@ class UserForm extends React.Component {
|
|||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
password_conf: '',
|
password_conf: '',
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ class UserForm extends React.Component {
|
|||||||
|
|
||||||
emptyForm() {
|
emptyForm() {
|
||||||
const { formData } = this.state
|
const { formData } = this.state
|
||||||
Object.keys(formData).map(k => formData[k] = '')
|
Object.keys(formData).map(k => (formData[k] = ''))
|
||||||
this.setState(formData)
|
this.setState(formData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,12 +38,7 @@ class UserForm extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { formType, message, messages, onHandleUserFormSubmit } = this.props
|
||||||
formType,
|
|
||||||
message,
|
|
||||||
messages,
|
|
||||||
onHandleUserFormSubmit
|
|
||||||
} = this.props
|
|
||||||
const { formData } = this.state
|
const { formData } = this.state
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -51,16 +46,12 @@ class UserForm extends React.Component {
|
|||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
{message !== '' && (
|
{message !== '' && <code>{message}</code>}
|
||||||
<code>{message}</code>
|
|
||||||
)}
|
|
||||||
{messages.length > 0 && (
|
{messages.length > 0 && (
|
||||||
<code>
|
<code>
|
||||||
<ul>
|
<ul>
|
||||||
{messages.map(msg => (
|
{messages.map(msg => (
|
||||||
<li key={msg.id}>
|
<li key={msg.id}>{msg.value}</li>
|
||||||
{msg.value}
|
|
||||||
</li>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</code>
|
</code>
|
||||||
@ -70,8 +61,8 @@ class UserForm extends React.Component {
|
|||||||
userForm={formData}
|
userForm={formData}
|
||||||
onHandleFormChange={event => this.onHandleFormChange(event)}
|
onHandleFormChange={event => this.onHandleFormChange(event)}
|
||||||
handleUserFormSubmit={event => {
|
handleUserFormSubmit={event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
onHandleUserFormSubmit(formData, formType)
|
onHandleUserFormSubmit(formData, formType)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { createApiRequest } from '../utils'
|
import { createApiRequest } from '../utils'
|
||||||
|
|
||||||
export default class FitTrackeeApi {
|
export default class FitTrackeeApi {
|
||||||
|
|
||||||
static getData(target, data = {}) {
|
static getData(target, data = {}) {
|
||||||
let url = target
|
let url = target
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
url = `${url}/${data.id}`
|
url = `${url}/${data.id}`
|
||||||
} else if (Object.keys(data).length > 0) {
|
} else if (Object.keys(data).length > 0) {
|
||||||
url += '?'
|
url += '?'
|
||||||
Object.keys(data).map(key => url += `&${key}=${data[key]}`)
|
Object.keys(data).map(key => (url += `&${key}=${data[key]}`))
|
||||||
}
|
}
|
||||||
const params = {
|
const params = {
|
||||||
url: url,
|
url: url,
|
||||||
@ -39,7 +38,7 @@ export default class FitTrackeeApi {
|
|||||||
|
|
||||||
static postData(target, data) {
|
static postData(target, data) {
|
||||||
const params = {
|
const params = {
|
||||||
url: `${target}${data.id ? `/${data.id}` : '' }`,
|
url: `${target}${data.id ? `/${data.id}` : ''}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: data,
|
body: data,
|
||||||
type: 'application/json',
|
type: 'application/json',
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { createApiRequest } from '../utils'
|
import { createApiRequest } from '../utils'
|
||||||
|
|
||||||
export default class FitTrackeeApi {
|
export default class FitTrackeeApi {
|
||||||
|
|
||||||
static loginOrRegister(target, data) {
|
static loginOrRegister(target, data) {
|
||||||
const params = {
|
const params = {
|
||||||
url: `auth/${target}`,
|
url: `auth/${target}`,
|
||||||
|
@ -15,7 +15,7 @@ import { loadProfile } from './actions/user'
|
|||||||
export const history = createBrowserHistory()
|
export const history = createBrowserHistory()
|
||||||
|
|
||||||
history.listen(() => {
|
history.listen(() => {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const rootNode = document.getElementById('root')
|
export const rootNode = document.getElementById('root')
|
||||||
|
@ -128,17 +128,18 @@ const user = (state = initial.user, action) => {
|
|||||||
const statistics = (state = initial.statistics, action) =>
|
const statistics = (state = initial.statistics, action) =>
|
||||||
handleDataAndError(state, 'statistics', action)
|
handleDataAndError(state, 'statistics', action)
|
||||||
|
|
||||||
export default history => combineReducers({
|
export default history =>
|
||||||
activities,
|
combineReducers({
|
||||||
calendarActivities,
|
activities,
|
||||||
chartData,
|
calendarActivities,
|
||||||
gpx,
|
chartData,
|
||||||
loading,
|
gpx,
|
||||||
message,
|
loading,
|
||||||
messages,
|
message,
|
||||||
records,
|
messages,
|
||||||
router: connectRouter(history),
|
records,
|
||||||
sports,
|
router: connectRouter(history),
|
||||||
statistics,
|
sports,
|
||||||
user,
|
statistics,
|
||||||
})
|
user,
|
||||||
|
})
|
||||||
|
@ -58,13 +58,13 @@ function registerValidSW(swUrl) {
|
|||||||
// the fresh content will have been added to the cache.
|
// the fresh content will have been added to the cache.
|
||||||
// It's the perfect time to display a "New content is
|
// It's the perfect time to display a "New content is
|
||||||
// available; please refresh." message in your web app.
|
// available; please refresh." message in your web app.
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('New content is available; please refresh.')
|
console.log('New content is available; please refresh.')
|
||||||
} else {
|
} else {
|
||||||
// At this point, everything has been precached.
|
// At this point, everything has been precached.
|
||||||
// It's the perfect time to display a
|
// It's the perfect time to display a
|
||||||
// "Content is cached for offline use." message.
|
// "Content is cached for offline use." message.
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('Content is cached for offline use.')
|
console.log('Content is cached for offline use.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ function checkValidServiceWorker(swUrl) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(
|
console.log(
|
||||||
'No internet connection found. App is running in offline mode.'
|
'No internet connection found. App is running in offline mode.'
|
||||||
)
|
)
|
||||||
|
@ -32,7 +32,7 @@ export const getGeoJson = gpxContent => {
|
|||||||
export const formatActivityDate = (
|
export const formatActivityDate = (
|
||||||
dateTime,
|
dateTime,
|
||||||
dateFormat = null,
|
dateFormat = null,
|
||||||
timeFormat = null,
|
timeFormat = null
|
||||||
) => {
|
) => {
|
||||||
if (!dateFormat) {
|
if (!dateFormat) {
|
||||||
dateFormat = 'yyyy/MM/dd'
|
dateFormat = 'yyyy/MM/dd'
|
||||||
@ -70,16 +70,16 @@ export const formatRecord = (record, tz) => {
|
|||||||
case 'FD':
|
case 'FD':
|
||||||
value = `${record.value} km`
|
value = `${record.value} km`
|
||||||
break
|
break
|
||||||
default: // 'LD'
|
default:
|
||||||
|
// 'LD'
|
||||||
value = record.value // eslint-disable-line prefer-destructuring
|
value = record.value // eslint-disable-line prefer-destructuring
|
||||||
}
|
}
|
||||||
const [recordType] = recordsLabels.filter(
|
const [recordType] = recordsLabels.filter(
|
||||||
r => r.record_type === record.record_type
|
r => r.record_type === record.record_type
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
activity_date: formatActivityDate(
|
activity_date: formatActivityDate(getDateWithTZ(record.activity_date, tz))
|
||||||
getDateWithTZ(record.activity_date, tz)
|
.activity_date,
|
||||||
).activity_date,
|
|
||||||
activity_id: record.activity_id,
|
activity_id: record.activity_id,
|
||||||
id: record.id,
|
id: record.id,
|
||||||
record_type: recordType.label,
|
record_type: recordType.label,
|
||||||
|
@ -3,9 +3,10 @@ import { DateTime } from 'luxon'
|
|||||||
|
|
||||||
export const version = '0.2.1-beta' // version stored in 'utils' for now
|
export const version = '0.2.1-beta' // version stored in 'utils' for now
|
||||||
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
|
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
|
||||||
|
/* prettier-ignore */
|
||||||
export const thunderforestApiKey = `${
|
export const thunderforestApiKey = `${
|
||||||
process.env.REACT_APP_THUNDERFOREST_API_KEY
|
process.env.REACT_APP_THUNDERFOREST_API_KEY
|
||||||
}`
|
}`
|
||||||
export const gpxLimit = `${process.env.REACT_APP_GPX_LIMIT_IMPORT}`
|
export const gpxLimit = `${process.env.REACT_APP_GPX_LIMIT_IMPORT}`
|
||||||
export const isRegistrationAllowed =
|
export const isRegistrationAllowed =
|
||||||
process.env.REACT_APP_ALLOW_REGISTRATION !== 'false'
|
process.env.REACT_APP_ALLOW_REGISTRATION !== 'false'
|
||||||
@ -21,12 +22,10 @@ export const generateIds = arr => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const createApiRequest = params => {
|
export const createApiRequest = params => {
|
||||||
const headers = {}
|
const headers = {}
|
||||||
if (!params.noAuthorization) {
|
if (!params.noAuthorization) {
|
||||||
headers.Authorization = `Bearer ${
|
headers.Authorization = `Bearer ${window.localStorage.getItem('authToken')}`
|
||||||
window.localStorage.getItem('authToken')}`
|
|
||||||
}
|
}
|
||||||
if (params.type) {
|
if (params.type) {
|
||||||
headers['Content-Type'] = params.type
|
headers['Content-Type'] = params.type
|
||||||
@ -42,22 +41,23 @@ export const createApiRequest = params => {
|
|||||||
}
|
}
|
||||||
const request = new Request(`${apiUrl}${params.url}`, requestParams)
|
const request = new Request(`${apiUrl}${params.url}`, requestParams)
|
||||||
return fetch(request)
|
return fetch(request)
|
||||||
.then(response => params.method === 'DELETE'
|
.then(response => (params.method === 'DELETE' ? response : response.json()))
|
||||||
? response
|
|
||||||
: response.json())
|
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
return new Error('An error occurred. Please contact the administrator.')
|
return new Error('An error occurred. Please contact the administrator.')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getDateWithTZ = (date, tz) => {
|
export const getDateWithTZ = (date, tz) => {
|
||||||
if (!date) {
|
if (!date) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
const dt = DateTime.fromISO(
|
const dt = DateTime.fromISO(
|
||||||
format(new Date(date), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx")).setZone(tz)
|
format(new Date(date), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx")
|
||||||
|
).setZone(tz)
|
||||||
return parse(
|
return parse(
|
||||||
dt.toFormat('yyyy-MM-dd HH:mm:ss'), 'yyyy-MM-dd HH:mm:ss', new Date())
|
dt.toFormat('yyyy-MM-dd HH:mm:ss'),
|
||||||
|
'yyyy-MM-dd HH:mm:ss',
|
||||||
|
new Date()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
addDays,
|
addDays,
|
||||||
addMonths,
|
addMonths,
|
||||||
addYears, format, startOfMonth,
|
addYears,
|
||||||
|
format,
|
||||||
|
startOfMonth,
|
||||||
startOfWeek,
|
startOfWeek,
|
||||||
startOfYear
|
startOfYear,
|
||||||
} from 'date-fns'
|
} from 'date-fns'
|
||||||
|
|
||||||
|
|
||||||
const xAxisFormats = [
|
const xAxisFormats = [
|
||||||
{ duration: 'week', dateFormat: 'yyyy-MM-dd', xAxis: 'dd/MM' },
|
{ duration: 'week', dateFormat: 'yyyy-MM-dd', xAxis: 'dd/MM' },
|
||||||
{ duration: 'month', dateFormat: 'yyyy-MM', xAxis: 'MM/yyyy' },
|
{ duration: 'month', dateFormat: 'yyyy-MM', xAxis: 'MM/yyyy' },
|
||||||
@ -24,22 +25,21 @@ export const formatDuration = (totalSeconds, formatWithDay = false) => {
|
|||||||
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0')
|
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0')
|
||||||
const seconds = String(totalSeconds % 60).padStart(2, '0')
|
const seconds = String(totalSeconds % 60).padStart(2, '0')
|
||||||
if (formatWithDay) {
|
if (formatWithDay) {
|
||||||
return `${
|
return `${days === '0' ? '' : `${days}d:`}${
|
||||||
days === '0' ? '' : `${days}d:`
|
|
||||||
}${
|
|
||||||
hours === '00' ? '' : `${hours}h:`
|
hours === '00' ? '' : `${hours}h:`
|
||||||
}${minutes}m:${seconds}s`
|
}${minutes}m:${seconds}s`
|
||||||
}
|
}
|
||||||
return `${hours === '00' ? '' : `${hours}:`}${minutes}:${seconds}`
|
return `${hours === '00' ? '' : `${hours}:`}${minutes}:${seconds}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatValue = (displayedData, value) => value === 0
|
export const formatValue = (displayedData, value) =>
|
||||||
|
value === 0
|
||||||
? ''
|
? ''
|
||||||
: displayedData === 'distance'
|
: displayedData === 'distance'
|
||||||
? `${value.toFixed(2)} km`
|
? `${value.toFixed(2)} km`
|
||||||
: displayedData === 'duration'
|
: displayedData === 'duration'
|
||||||
? formatDuration(value)
|
? formatDuration(value)
|
||||||
: value
|
: value
|
||||||
|
|
||||||
const dateIncrement = (duration, day) => {
|
const dateIncrement = (duration, day) => {
|
||||||
switch (duration) {
|
switch (duration) {
|
||||||
@ -65,16 +65,15 @@ const startDate = (duration, day) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatStats = (
|
export const formatStats = (stats, sports, params, displayedSports) => {
|
||||||
stats, sports, params, displayedSports
|
|
||||||
) => {
|
|
||||||
const nbActivitiesStats = []
|
const nbActivitiesStats = []
|
||||||
const distanceStats = []
|
const distanceStats = []
|
||||||
const durationStats = []
|
const durationStats = []
|
||||||
|
|
||||||
for (let day = startDate(params.duration, params.start);
|
for (
|
||||||
day <= params.end;
|
let day = startDate(params.duration, params.start);
|
||||||
day = dateIncrement(params.duration, day)
|
day <= params.end;
|
||||||
|
day = dateIncrement(params.duration, day)
|
||||||
) {
|
) {
|
||||||
const [xAxisFormat] = xAxisFormats.filter(
|
const [xAxisFormat] = xAxisFormats.filter(
|
||||||
x => x.duration === params.duration
|
x => x.duration === params.duration
|
||||||
@ -86,15 +85,17 @@ export const formatStats = (
|
|||||||
const dataDuration = { date: xAxis }
|
const dataDuration = { date: xAxis }
|
||||||
|
|
||||||
if (stats[date]) {
|
if (stats[date]) {
|
||||||
Object.keys(stats[date]).filter(
|
Object.keys(stats[date])
|
||||||
sportId => displayedSports ? displayedSports.includes(+sportId) : true
|
.filter(sportId =>
|
||||||
).map(sportId => {
|
displayedSports ? displayedSports.includes(+sportId) : true
|
||||||
const sportLabel = sports.filter(s => s.id === +sportId)[0].label
|
)
|
||||||
dataNbActivities[sportLabel] = stats[date][sportId].nb_activities
|
.map(sportId => {
|
||||||
dataDistance[sportLabel] = stats[date][sportId].total_distance
|
const sportLabel = sports.filter(s => s.id === +sportId)[0].label
|
||||||
dataDuration[sportLabel] = stats[date][sportId].total_duration
|
dataNbActivities[sportLabel] = stats[date][sportId].nb_activities
|
||||||
return null
|
dataDistance[sportLabel] = stats[date][sportId].total_distance
|
||||||
})
|
dataDuration[sportLabel] = stats[date][sportId].total_duration
|
||||||
|
return null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
nbActivitiesStats.push(dataNbActivities)
|
nbActivitiesStats.push(dataNbActivities)
|
||||||
distanceStats.push(dataDistance)
|
distanceStats.push(dataDistance)
|
||||||
@ -104,6 +105,6 @@ export const formatStats = (
|
|||||||
return {
|
return {
|
||||||
activities: nbActivitiesStats,
|
activities: nbActivitiesStats,
|
||||||
distance: distanceStats,
|
distance: distanceStats,
|
||||||
duration: durationStats
|
duration: durationStats,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user