This commit is contained in:
Sam 2019-01-04 10:07:24 +01:00
parent e412907e36
commit 5de1fe4e94
13 changed files with 250 additions and 235 deletions

View File

@ -2,7 +2,7 @@ import { parse } from 'date-fns'
import FitTrackeeGenericApi from '../fitTrackeeApi' import FitTrackeeGenericApi from '../fitTrackeeApi'
import { history } from '../index' import { history } from '../index'
import { formatChartData } from '../utils' import { formatChartData } from '../utils/stats'
import { setError, setLoading } from './index' import { setError, setLoading } from './index'
import { loadProfile } from './user' import { loadProfile } from './user'

View File

@ -1,7 +1,8 @@
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { formatActivityDate, getDateWithTZ } from '../../../utils' import { getDateWithTZ } from '../../../utils'
import { formatActivityDate } from '../../../utils/activities'
export default function ActivityCardHeader(props) { export default function ActivityCardHeader(props) {

View File

@ -4,7 +4,8 @@ import { GeoJSON, Map, TileLayer } from 'react-leaflet'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { getActivityGpx } from '../../../actions/activities' import { getActivityGpx } from '../../../actions/activities'
import { getGeoJson, thunderforestApiKey } from '../../../utils' import { thunderforestApiKey } from '../../../utils'
import { getGeoJson } from '../../../utils/activities'
class ActivityMap extends React.Component { class ActivityMap extends React.Component {

View File

@ -5,7 +5,7 @@ import {
addActivityWithoutGpx, editActivity addActivityWithoutGpx, editActivity
} from '../../../actions/activities' } from '../../../actions/activities'
import { history } from '../../../index' import { history } from '../../../index'
import { formatActivityDate } from '../../../utils' import { formatActivityDate } from '../../../utils/activities'
function FormWithoutGpx (props) { function FormWithoutGpx (props) {
const { activity, onAddOrEdit, sports } = props const { activity, onAddOrEdit, sports } = props

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { formatDuration } from '../../../utils' import { formatDuration } from '../../../utils/stats'
const formatValue = (displayedData, value) => displayedData === 'duration' const formatValue = (displayedData, value) => displayedData === 'duration'
? formatDuration(value, true) ? formatDuration(value, true)

View File

@ -3,7 +3,8 @@ import {
Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis
} from 'recharts' } from 'recharts'
import { activityColors, formatDuration } from '../../../utils' import { activityColors } from '../../../utils/activities'
import { formatDuration } from '../../../utils/stats'
import CustomTooltip from './CustomTooltip' import CustomTooltip from './CustomTooltip'

View File

@ -3,7 +3,7 @@ import React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { getStats } from '../../../actions/stats' import { getStats } from '../../../actions/stats'
import { formatStats } from '../../../utils' import { formatStats } from '../../../utils/stats'
import StatsChart from './StatsChart' import StatsChart from './StatsChart'

View File

@ -6,7 +6,8 @@ import { connect } from 'react-redux'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { getMonthActivities } from '../../actions/activities' import { getMonthActivities } from '../../actions/activities'
import { getDateWithTZ, recordsLabels } from '../../utils' import { getDateWithTZ } from '../../utils'
import { recordsLabels } from '../../utils/activities'
const getStartAndEndMonth = date => { const getStartAndEndMonth = date => {
const monthStart = dateFns.startOfMonth(date) const monthStart = dateFns.startOfMonth(date)

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { formatRecord } from '../../utils' 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

View File

@ -1,226 +0,0 @@
import togeojson from '@mapbox/togeojson'
import {
addDays, addMonths, addYears, format, parse, startOfMonth, startOfWeek,
startOfYear
} from 'date-fns'
import { DateTime } from 'luxon'
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
export const thunderforestApiKey = `${
process.env.REACT_APP_THUNDERFOREST_API_KEY
}`
export const gpxLimit = `${process.env.REACT_APP_GPX_LIMIT_IMPORT}`
export const activityColors = [
'#55a8a3',
'#98C3A9',
'#D0838A',
'#ECC77E',
'#926692',
'#929292',
'#428bca',
]
export const isLoggedIn = () => !!window.localStorage.authToken
export const generateIds = arr => {
let i = 0
return arr.map(val => {
const obj = { id: i, value: val }
i++
return obj
})
}
export const createRequest = params => {
const headers = {}
if (!params.noAuthorization) {
headers.Authorization = `Bearer ${
window.localStorage.getItem('authToken')}`
}
if (params.type) {
headers['Content-Type'] = params.type
}
const requestParams = {
method: params.method,
headers: headers,
}
if (params.type === 'application/json' && params.body) {
requestParams.body = JSON.stringify(params.body)
} else if (params.body) {
requestParams.body = params.body
}
const request = new Request(params.url, requestParams)
return fetch(request)
.then(response => params.method === 'DELETE'
? response
: response.json())
.catch(error => error)
}
export const getGeoJson = gpxContent => {
let jsonData
if (gpxContent) {
const gpx = new DOMParser().parseFromString(gpxContent, 'text/xml')
jsonData = togeojson.gpx(gpx)
}
return { jsonData }
}
export const getDateWithTZ = (date, tz) => {
if (!date) {
return ''
}
const dt = DateTime.fromISO(format(date)).setZone(tz)
return parse(dt.toFormat('yyyy-MM-dd HH:mm:ss'))
}
export const formatActivityDate = (
dateTime,
dateFormat = null,
timeFormat = null,
) => {
if (!dateFormat) {
dateFormat = 'DD/MM/YYYY'
}
if (!timeFormat) {
timeFormat = 'HH:mm'
}
return {
activity_date: dateTime ? format(dateTime, dateFormat) : null,
activity_time: dateTime ? format(dateTime, timeFormat) : null,
}
}
export const recordsLabels = [
{ record_type: 'AS', label: 'Avg speed' },
{ record_type: 'FD', label: 'Farest distance' },
{ record_type: 'LD', label: 'Longest duration' },
{ record_type: 'MS', label: 'Max speed' },
]
export const formatRecord = (record, tz) => {
let value, recordType = null
switch (record.record_type) {
case 'AS':
case 'MS':
value = `${record.value} km/h`
break
case 'FD':
value = `${record.value} km`
break
default: // 'LD'
value = record.value // eslint-disable-line prefer-destructuring
}
[recordType] = recordsLabels.filter(r => r.record_type === record.record_type)
return {
activity_date: formatActivityDate(
getDateWithTZ(record.activity_date, tz)
).activity_date,
activity_id: record.activity_id,
id: record.id,
record_type: recordType.label,
value: value,
}
}
export const formatDuration = (totalSeconds, formatWithDay = false) => {
let days = '0'
if (formatWithDay) {
days = String(Math.floor(totalSeconds / 86400))
totalSeconds %= 86400
}
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
totalSeconds %= 3600
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0')
const seconds = String(totalSeconds % 60).padStart(2, '0')
if (formatWithDay) {
return `${
days === '0' ? '' : `${days}d:`
}${
hours === '00' ? '' : `${hours}h:`
}${minutes}m:${seconds}s`
}
return `${hours === '00' ? '' : `${hours}:`}${minutes}:${seconds}`
}
export const formatChartData = chartData => {
for (let i = 0; i < chartData.length; i++) {
chartData[i].time = new Date(chartData[i].time).getTime()
chartData[i].duration = formatDuration(chartData[i].duration)
}
return chartData
}
const xAxisFormats = [
{ duration: 'week', dateFormat: 'YYYY-MM-DD', xAxis: 'DD/MM' },
{ duration: 'month', dateFormat: 'YYYY-MM', xAxis: 'MM/YYYY' },
{ duration: 'year', dateFormat: 'YYYY', xAxis: 'YYYY' },
]
const dateIncrement = (duration, day) => {
switch (duration) {
case 'week':
return addDays(day, 7)
case 'year':
return addYears(day, 1)
case 'month':
default:
return addMonths(day, 1)
}
}
const startDate = (duration, day) => {
switch (duration) {
case 'week':
return startOfWeek(day)
case 'year':
return startOfYear(day)
case 'month':
default:
return startOfMonth(day)
}
}
export const formatStats = (
stats, sports, params
) => {
const nbActivitiesStats = []
const distanceStats = []
const durationStats = []
for (let day = startDate(params.duration, params.start);
day <= params.end;
day = dateIncrement(params.duration, day)
) {
const [xAxisFormat] = xAxisFormats.filter(
x => x.duration === params.duration
)
const date = format(day, xAxisFormat.dateFormat)
const xAxis = format(day, xAxisFormat.xAxis)
const dataNbActivities = { date: xAxis }
const dataDistance = { date: xAxis }
const dataDuration = { date: xAxis }
if (stats[date]) {
Object.keys(stats[date]).map(sportId => {
const sportLabel = sports.filter(s => s.id === +sportId)[0].label
dataNbActivities[sportLabel] = stats[date][sportId].nb_activities
dataDistance[sportLabel] = stats[date][sportId].total_distance
dataDuration[sportLabel] = stats[date][sportId].total_duration
return null
})
}
nbActivitiesStats.push(dataNbActivities)
distanceStats.push(dataDistance)
durationStats.push(dataDuration)
}
return {
activities: nbActivitiesStats,
distance: distanceStats,
duration: durationStats
}
}

View File

@ -0,0 +1,74 @@
import { format } from 'date-fns'
import togeojson from '@mapbox/togeojson'
import { getDateWithTZ } from './index'
export const activityColors = [
'#55a8a3',
'#98C3A9',
'#D0838A',
'#ECC77E',
'#926692',
'#929292',
'#428bca',
]
export const recordsLabels = [
{ record_type: 'AS', label: 'Avg speed' },
{ record_type: 'FD', label: 'Farest distance' },
{ record_type: 'LD', label: 'Longest duration' },
{ record_type: 'MS', label: 'Max speed' },
]
export const getGeoJson = gpxContent => {
let jsonData
if (gpxContent) {
const gpx = new DOMParser().parseFromString(gpxContent, 'text/xml')
jsonData = togeojson.gpx(gpx)
}
return { jsonData }
}
export const formatActivityDate = (
dateTime,
dateFormat = null,
timeFormat = null,
) => {
if (!dateFormat) {
dateFormat = 'DD/MM/YYYY'
}
if (!timeFormat) {
timeFormat = 'HH:mm'
}
return {
activity_date: dateTime ? format(dateTime, dateFormat) : null,
activity_time: dateTime ? format(dateTime, timeFormat) : null,
}
}
export const formatRecord = (record, tz) => {
let value
switch (record.record_type) {
case 'AS':
case 'MS':
value = `${record.value} km/h`
break
case 'FD':
value = `${record.value} km`
break
default: // 'LD'
value = record.value // eslint-disable-line prefer-destructuring
}
const [recordType] = recordsLabels.filter(
r => r.record_type === record.record_type
)
return {
activity_date: formatActivityDate(
getDateWithTZ(record.activity_date, tz)
).activity_date,
activity_id: record.activity_id,
id: record.id,
record_type: recordType.label,
value: value,
}
}

View File

@ -0,0 +1,55 @@
import { format, parse } from 'date-fns'
import { DateTime } from 'luxon'
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
export const thunderforestApiKey = `${
process.env.REACT_APP_THUNDERFOREST_API_KEY
}`
export const gpxLimit = `${process.env.REACT_APP_GPX_LIMIT_IMPORT}`
export const isLoggedIn = () => !!window.localStorage.authToken
export const generateIds = arr => {
let i = 0
return arr.map(val => {
const obj = { id: i, value: val }
i++
return obj
})
}
export const createRequest = params => {
const headers = {}
if (!params.noAuthorization) {
headers.Authorization = `Bearer ${
window.localStorage.getItem('authToken')}`
}
if (params.type) {
headers['Content-Type'] = params.type
}
const requestParams = {
method: params.method,
headers: headers,
}
if (params.type === 'application/json' && params.body) {
requestParams.body = JSON.stringify(params.body)
} else if (params.body) {
requestParams.body = params.body
}
const request = new Request(params.url, requestParams)
return fetch(request)
.then(response => params.method === 'DELETE'
? response
: response.json())
.catch(error => error)
}
export const getDateWithTZ = (date, tz) => {
if (!date) {
return ''
}
const dt = DateTime.fromISO(format(date)).setZone(tz)
return parse(dt.toFormat('yyyy-MM-dd HH:mm:ss'))
}

View File

@ -0,0 +1,108 @@
import {
addDays,
addMonths,
addYears, format, startOfMonth,
startOfWeek,
startOfYear
} from 'date-fns'
const xAxisFormats = [
{ duration: 'week', dateFormat: 'YYYY-MM-DD', xAxis: 'DD/MM' },
{ duration: 'month', dateFormat: 'YYYY-MM', xAxis: 'MM/YYYY' },
{ duration: 'year', dateFormat: 'YYYY', xAxis: 'YYYY' },
]
export const formatDuration = (totalSeconds, formatWithDay = false) => {
let days = '0'
if (formatWithDay) {
days = String(Math.floor(totalSeconds / 86400))
totalSeconds %= 86400
}
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
totalSeconds %= 3600
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0')
const seconds = String(totalSeconds % 60).padStart(2, '0')
if (formatWithDay) {
return `${
days === '0' ? '' : `${days}d:`
}${
hours === '00' ? '' : `${hours}h:`
}${minutes}m:${seconds}s`
}
return `${hours === '00' ? '' : `${hours}:`}${minutes}:${seconds}`
}
export const formatChartData = chartData => {
for (let i = 0; i < chartData.length; i++) {
chartData[i].time = new Date(chartData[i].time).getTime()
chartData[i].duration = formatDuration(chartData[i].duration)
}
return chartData
}
const dateIncrement = (duration, day) => {
switch (duration) {
case 'week':
return addDays(day, 7)
case 'year':
return addYears(day, 1)
case 'month':
default:
return addMonths(day, 1)
}
}
const startDate = (duration, day) => {
switch (duration) {
case 'week':
return startOfWeek(day)
case 'year':
return startOfYear(day)
case 'month':
default:
return startOfMonth(day)
}
}
export const formatStats = (
stats, sports, params
) => {
const nbActivitiesStats = []
const distanceStats = []
const durationStats = []
for (let day = startDate(params.duration, params.start);
day <= params.end;
day = dateIncrement(params.duration, day)
) {
const [xAxisFormat] = xAxisFormats.filter(
x => x.duration === params.duration
)
const date = format(day, xAxisFormat.dateFormat)
const xAxis = format(day, xAxisFormat.xAxis)
const dataNbActivities = { date: xAxis }
const dataDistance = { date: xAxis }
const dataDuration = { date: xAxis }
if (stats[date]) {
Object.keys(stats[date]).map(sportId => {
const sportLabel = sports.filter(s => s.id === +sportId)[0].label
dataNbActivities[sportLabel] = stats[date][sportId].nb_activities
dataDistance[sportLabel] = stats[date][sportId].total_distance
dataDuration[sportLabel] = stats[date][sportId].total_duration
return null
})
}
nbActivitiesStats.push(dataNbActivities)
distanceStats.push(dataDistance)
durationStats.push(dataDuration)
}
return {
activities: nbActivitiesStats,
distance: distanceStats,
duration: durationStats
}
}