Merge branch 'dev' into oauth2
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fittrackee_client",
|
||||
"version": "0.6.10",
|
||||
"version": "0.6.11",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
@ -55,6 +55,11 @@
|
||||
function getSum(total: any, value: any): number {
|
||||
return getNumber(total) + getNumber(value)
|
||||
}
|
||||
function getUnit(displayedData: string) {
|
||||
return ['total_ascent', 'total_descent'].includes(displayedData)
|
||||
? 'm'
|
||||
: 'km'
|
||||
}
|
||||
const chartData: ComputedRef<ChartData<'bar'>> = computed(() => ({
|
||||
labels: props.labels,
|
||||
// workaround to avoid dataset modification
|
||||
@ -88,12 +93,13 @@
|
||||
props.displayedData,
|
||||
+value,
|
||||
props.useImperialUnits,
|
||||
false
|
||||
false,
|
||||
getUnit(props.displayedData)
|
||||
)
|
||||
},
|
||||
},
|
||||
afterFit: function (scale: LayoutItem) {
|
||||
scale.width = props.fullStats ? 75 : 60
|
||||
scale.width = props.fullStats ? 90 : 60
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -143,7 +149,8 @@
|
||||
props.displayedData,
|
||||
total,
|
||||
props.useImperialUnits,
|
||||
false
|
||||
false,
|
||||
getUnit(props.displayedData)
|
||||
)
|
||||
: null
|
||||
}
|
||||
@ -172,7 +179,9 @@
|
||||
label += formatTooltipValue(
|
||||
props.displayedData,
|
||||
context.parsed.y,
|
||||
props.useImperialUnits
|
||||
props.useImperialUnits,
|
||||
true,
|
||||
getUnit(props.displayedData)
|
||||
)
|
||||
}
|
||||
return label
|
||||
@ -190,7 +199,9 @@
|
||||
formatTooltipValue(
|
||||
props.displayedData,
|
||||
sum,
|
||||
props.useImperialUnits
|
||||
props.useImperialUnits,
|
||||
true,
|
||||
getUnit(props.displayedData)
|
||||
)
|
||||
)
|
||||
},
|
||||
|
@ -14,6 +14,7 @@
|
||||
<CalendarWorkouts
|
||||
:workouts="filterWorkouts(day, workouts)"
|
||||
:sports="sports"
|
||||
:displayHARecord="displayHARecord"
|
||||
/>
|
||||
<div class="calendar-cell-day">
|
||||
{{ format(day, 'd') }}
|
||||
@ -34,6 +35,7 @@
|
||||
|
||||
interface Props {
|
||||
currentDay: Date
|
||||
displayHARecord: boolean
|
||||
endDate: Date
|
||||
sports: ISport[]
|
||||
startDate: Date
|
||||
@ -45,6 +47,7 @@
|
||||
|
||||
const {
|
||||
currentDay,
|
||||
displayHARecord,
|
||||
endDate,
|
||||
sports,
|
||||
startDate,
|
||||
|
@ -16,9 +16,11 @@
|
||||
class="fa fa-trophy custom-fa-small"
|
||||
aria-hidden="true"
|
||||
:title="
|
||||
workout.records.map(
|
||||
(record) => ` ${$t(`workouts.RECORD_${record.record_type}`)}`
|
||||
)
|
||||
workout.records
|
||||
.filter((record) =>
|
||||
displayHARecord ? true : record.record_type !== 'HA'
|
||||
)
|
||||
.map((record) => ` ${$t(`workouts.RECORD_${record.record_type}`)}`)
|
||||
"
|
||||
/>
|
||||
</sup>
|
||||
@ -30,13 +32,14 @@
|
||||
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
interface Props {
|
||||
displayHARecord: boolean
|
||||
workout: IWorkout
|
||||
sportLabel: string
|
||||
sportColor: string | null
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workout, sportLabel, sportColor } = toRefs(props)
|
||||
const { displayHARecord, workout, sportLabel, sportColor } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -8,6 +8,7 @@
|
||||
<CalendarWorkout
|
||||
v-for="(workout, index) in workouts.slice(0, displayedWorkoutCount)"
|
||||
:key="index"
|
||||
:displayHARecord="displayHARecord"
|
||||
:workout="workout"
|
||||
:sportLabel="getSportLabel(workout, sports)"
|
||||
:sportColor="getSportColor(workout, sports)"
|
||||
@ -46,12 +47,13 @@
|
||||
import { getDonutDatasets } from '@/utils/workouts'
|
||||
|
||||
interface Props {
|
||||
displayHARecord: boolean
|
||||
workouts: IWorkout[]
|
||||
sports: ISport[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workouts, sports } = toRefs(props)
|
||||
const { displayHARecord, workouts, sports } = toRefs(props)
|
||||
const chartDatasets = computed(() => getDonutDatasets(props.workouts))
|
||||
const colors = computed(() => sportIdColors(props.sports))
|
||||
const displayedWorkoutCount = 6
|
||||
|
@ -13,6 +13,7 @@
|
||||
/>
|
||||
<CalendarCells
|
||||
:currentDay="day"
|
||||
:displayHARecord="user.display_ascent"
|
||||
:end-date="calendarDates.end"
|
||||
:sports="sports"
|
||||
:start-date="calendarDates.start"
|
||||
|
@ -42,7 +42,8 @@
|
||||
props.user.records,
|
||||
translateSports(props.sports, t),
|
||||
props.user.timezone,
|
||||
props.user.imperial_units
|
||||
props.user.imperial_units,
|
||||
props.user.display_ascent
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
@ -8,7 +8,13 @@
|
||||
<StatCard
|
||||
icon="road"
|
||||
:value="totalDistance"
|
||||
:text="unitTo === 'mi' ? 'miles' : unitTo"
|
||||
:text="distanceUnitTo === 'mi' ? 'miles' : distanceUnitTo"
|
||||
/>
|
||||
<StatCard
|
||||
v-if="user.display_ascent"
|
||||
icon="location-arrow"
|
||||
:value="totalAscent"
|
||||
:text="ascentUnitTo === 'ft' ? 'feet' : ascentUnitTo"
|
||||
/>
|
||||
<StatCard
|
||||
icon="clock-o"
|
||||
@ -16,6 +22,7 @@
|
||||
:text="totalDuration.duration"
|
||||
/>
|
||||
<StatCard
|
||||
v-if="!user.display_ascent"
|
||||
icon="tags"
|
||||
:value="user.nb_sports"
|
||||
:text="$t('workouts.SPORT', user.nb_sports)"
|
||||
@ -43,15 +50,23 @@
|
||||
() => props.user.total_duration
|
||||
)
|
||||
const totalDuration = computed(() => get_duration(userTotalDuration))
|
||||
const defaultUnitFrom: TUnit = 'km'
|
||||
const unitTo: TUnit = user.value.imperial_units
|
||||
? units[defaultUnitFrom].defaultTarget
|
||||
: defaultUnitFrom
|
||||
const distanceUnitFrom: TUnit = 'km'
|
||||
const distanceUnitTo: TUnit = user.value.imperial_units
|
||||
? units[distanceUnitFrom].defaultTarget
|
||||
: distanceUnitFrom
|
||||
const totalDistance: ComputedRef<number> = computed(() =>
|
||||
user.value.imperial_units
|
||||
? convertDistance(user.value.total_distance, defaultUnitFrom, unitTo, 2)
|
||||
: parseFloat(user.value.total_distance.toFixed(2))
|
||||
)
|
||||
? convertDistance(user.value.total_distance, distanceUnitFrom, distanceUnitTo, 2)
|
||||
: parseFloat(user.value.total_distance.toFixed(2)))
|
||||
const ascentUnitFrom: TUnit = 'm'
|
||||
const ascentUnitTo: TUnit = user.value.imperial_units
|
||||
? units[ascentUnitFrom].defaultTarget
|
||||
: ascentUnitFrom
|
||||
const totalAscent: ComputedRef<number> = computed(() =>
|
||||
user.value.imperial_units
|
||||
? convertDistance(user.value.total_ascent, ascentUnitFrom, ascentUnitTo, 2)
|
||||
: parseFloat(user.value.total_ascent.toFixed(2)))
|
||||
|
||||
|
||||
function get_duration(total_duration: ComputedRef<string>) {
|
||||
const duration = total_duration.value.match(/day/g)
|
||||
|
@ -15,6 +15,8 @@
|
||||
)
|
||||
}}
|
||||
</dd>
|
||||
<dt>{{ $t('user.PROFILE.ASCENT_DATA') }}:</dt>
|
||||
<dd>{{ $t(`common.${display_ascent}`) }}</dd>
|
||||
</dl>
|
||||
<div class="profile-buttons">
|
||||
<button @click="$router.push('/profile/edit/preferences')">
|
||||
@ -28,11 +30,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { languageLabels } from '@/utils/locales'
|
||||
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
user: IAuthUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
@ -45,4 +47,7 @@
|
||||
const timezone = computed(() =>
|
||||
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
||||
)
|
||||
const display_ascent = computed(() =>
|
||||
props.user.display_ascent ? 'DISPLAYED' : 'HIDDEN'
|
||||
)
|
||||
</script>
|
||||
|
@ -23,34 +23,66 @@
|
||||
@updateTimezone="updateTZ"
|
||||
/>
|
||||
</label>
|
||||
<label class="form-items">
|
||||
{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
|
||||
<select id="weekm" v-model="userForm.weekm" :disabled="loading">
|
||||
<option
|
||||
v-for="start in weekStart"
|
||||
:value="start.value"
|
||||
:key="start.value"
|
||||
>
|
||||
{{ $t(`user.PROFILE.${start.label}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="form-items">
|
||||
{{ $t('user.PROFILE.UNITS.LABEL') }}
|
||||
<select
|
||||
id="imperial_units"
|
||||
v-model="userForm.imperial_units"
|
||||
:disabled="loading"
|
||||
>
|
||||
<option
|
||||
v-for="unit in imperialUnits"
|
||||
:value="unit.value"
|
||||
:key="unit.value"
|
||||
>
|
||||
{{ $t(`user.PROFILE.UNITS.${unit.label}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="form-items form-checkboxes">
|
||||
<span class="checkboxes-label">
|
||||
{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
|
||||
</span>
|
||||
<div class="checkboxes">
|
||||
<label v-for="start in weekStart" :key="start.label">
|
||||
<input
|
||||
type="radio"
|
||||
:id="start.label"
|
||||
:name="start.label"
|
||||
:checked="start.value === userForm.weekm"
|
||||
:disabled="loading"
|
||||
@input="updateWeekM(start.value)"
|
||||
/>
|
||||
<span class="checkbox-label">
|
||||
{{ $t(`user.PROFILE.${start.label}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-items form-checkboxes">
|
||||
<span class="checkboxes-label">
|
||||
{{ $t('user.PROFILE.UNITS.LABEL') }}
|
||||
</span>
|
||||
<div class="checkboxes">
|
||||
<label v-for="unit in imperialUnits" :key="unit.label">
|
||||
<input
|
||||
type="radio"
|
||||
:id="unit.label"
|
||||
:name="unit.label"
|
||||
:checked="unit.value === userForm.imperial_units"
|
||||
:disabled="loading"
|
||||
@input="updateImperialUnit(unit.value)"
|
||||
/>
|
||||
<span class="checkbox-label">
|
||||
{{ $t(`user.PROFILE.UNITS.${unit.label}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-items form-checkboxes">
|
||||
<span class="checkboxes-label">
|
||||
{{ $t('user.PROFILE.ASCENT_DATA') }}
|
||||
</span>
|
||||
<div class="checkboxes">
|
||||
<label v-for="status in ascentData" :key="status.label">
|
||||
<input
|
||||
type="radio"
|
||||
:id="status.label"
|
||||
:name="status.label"
|
||||
:checked="status.value === userForm.display_ascent"
|
||||
:disabled="loading"
|
||||
@input="updateAscentDisplay(status.value)"
|
||||
/>
|
||||
<span class="checkbox-label">
|
||||
{{ $t(`common.${status.label}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-buttons">
|
||||
<button class="confirm" type="submit">
|
||||
{{ $t('buttons.SUBMIT') }}
|
||||
@ -72,40 +104,51 @@
|
||||
|
||||
import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
import { IUserProfile, IUserPreferencesPayload } from '@/types/user'
|
||||
import { IUserPreferencesPayload, IAuthUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { availableLanguages } from '@/utils/locales'
|
||||
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
user: IAuthUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const userForm: IUserPreferencesPayload = reactive({
|
||||
display_ascent: true,
|
||||
imperial_units: false,
|
||||
language: '',
|
||||
timezone: 'Europe/Paris',
|
||||
weekm: false,
|
||||
})
|
||||
const weekStart = [
|
||||
{
|
||||
label: 'MONDAY',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'SUNDAY',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: 'MONDAY',
|
||||
value: true,
|
||||
},
|
||||
]
|
||||
const imperialUnits = [
|
||||
{
|
||||
label: 'METRIC',
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: 'IMPERIAL',
|
||||
value: true,
|
||||
},
|
||||
]
|
||||
const ascentData = [
|
||||
{
|
||||
label: 'METRIC',
|
||||
label: 'DISPLAYED',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'HIDDEN',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
@ -122,7 +165,8 @@
|
||||
}
|
||||
})
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
function updateUserForm(user: IAuthUserProfile) {
|
||||
userForm.display_ascent = user.display_ascent
|
||||
userForm.imperial_units = user.imperial_units ? user.imperial_units : false
|
||||
userForm.language = user.language ? user.language : 'en'
|
||||
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
||||
@ -134,8 +178,43 @@
|
||||
function updateTZ(value: string) {
|
||||
userForm.timezone = value
|
||||
}
|
||||
function updateAscentDisplay(value: boolean) {
|
||||
userForm.display_ascent = value
|
||||
}
|
||||
function updateImperialUnit(value: boolean) {
|
||||
userForm.imperial_units = value
|
||||
}
|
||||
function updateWeekM(value: boolean) {
|
||||
userForm.weekm = value
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/vars.scss';
|
||||
#user-preferences-edition {
|
||||
.form-items {
|
||||
padding-top: $default-padding * 0.5;
|
||||
}
|
||||
|
||||
.form-checkboxes {
|
||||
.checkboxes-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
.checkboxes {
|
||||
display: flex;
|
||||
gap: $default-padding;
|
||||
flex-wrap: wrap;
|
||||
.checkbox-label {
|
||||
padding-left: $default-padding * 0.5;
|
||||
}
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -32,11 +32,13 @@ export const htmlLegendPlugin = {
|
||||
legendItems.forEach((item: LegendItem) => {
|
||||
const li = document.createElement('li')
|
||||
li.onclick = () => {
|
||||
chart.setDatasetVisibility(
|
||||
item.datasetIndex,
|
||||
!chart.isDatasetVisible(item.datasetIndex)
|
||||
)
|
||||
chart.update()
|
||||
if (item.datasetIndex) {
|
||||
chart.setDatasetVisibility(
|
||||
item.datasetIndex,
|
||||
!chart.isDatasetVisible(item.datasetIndex)
|
||||
)
|
||||
chart.update()
|
||||
}
|
||||
}
|
||||
|
||||
const checkBox = document.createElement('input')
|
||||
|
@ -84,7 +84,13 @@
|
||||
unitFrom="m"
|
||||
:strong="true"
|
||||
:useImperialUnits="useImperialUnits"
|
||||
/><br />
|
||||
/>
|
||||
<WorkoutRecord
|
||||
v-if="displayHARecord"
|
||||
:workoutObject="workoutObject"
|
||||
recordType="HA"
|
||||
/>
|
||||
<br />
|
||||
<span class="label"> {{ $t('workouts.DESCENT') }} </span>:
|
||||
<Distance
|
||||
:distance="workoutObject.descent"
|
||||
@ -110,10 +116,11 @@
|
||||
interface Props {
|
||||
workoutObject: IWorkoutObject
|
||||
useImperialUnits: boolean
|
||||
displayHARecord: boolean
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workoutObject, useImperialUnits } = toRefs(props)
|
||||
const { displayHARecord, workoutObject, useImperialUnits } = toRefs(props)
|
||||
const withPause = computed(
|
||||
() =>
|
||||
props.workoutObject.pauses !== '0:00:00' &&
|
||||
|
@ -23,6 +23,7 @@
|
||||
<WorkoutData
|
||||
:workoutObject="workoutObject"
|
||||
:useImperialUnits="authUser.imperial_units"
|
||||
:displayHARecord="authUser.display_ascent"
|
||||
/>
|
||||
</template>
|
||||
</Card>
|
||||
|
@ -3,9 +3,11 @@
|
||||
"CONFIRMATION": "Confirmation",
|
||||
"CONTACT": "contact",
|
||||
"DAY": "day | days",
|
||||
"DISPLAYED": "Displayed",
|
||||
"DOCUMENTATION": "documentation",
|
||||
"HOME": "Home",
|
||||
"HERE": "here",
|
||||
"HIDDEN": "Hidden",
|
||||
"SELECTS": {
|
||||
"ORDER_BY": {
|
||||
"LABEL": "order by"
|
||||
|
@ -48,6 +48,7 @@
|
||||
"PASSWORD_UPDATED": "Your password have been updated. Click {0} to log in.",
|
||||
"PROFILE": {
|
||||
"ACCOUNT_EDITION": "Account edition",
|
||||
"ASCENT_DATA": "Ascent-related data (records, total)",
|
||||
"BACK_TO_PROFILE": "Back to profile",
|
||||
"BIO": "Bio",
|
||||
"BIRTH_DATE": "Birth date",
|
||||
|
@ -43,6 +43,7 @@
|
||||
"RECORD": "record | records",
|
||||
"RECORD_AS": "Ave. speed",
|
||||
"RECORD_FD": "Farthest distance",
|
||||
"RECORD_HA": "Highest ascent",
|
||||
"RECORD_LD": "Longest duration",
|
||||
"RECORD_MS": "Max. speed",
|
||||
"REMAINING_CHARS": "remaining characters",
|
||||
|
@ -3,9 +3,11 @@
|
||||
"CONFIRMATION": "Confirmation",
|
||||
"CONTACT": "contact",
|
||||
"DAY": "jour | jours",
|
||||
"DISPLAYED": "Affiché",
|
||||
"DOCUMENTATION": "documentation (en)",
|
||||
"HOME": "Accueil",
|
||||
"HERE": "ici",
|
||||
"HIDDEN": "Masqué",
|
||||
"SELECTS": {
|
||||
"ORDER_BY": {
|
||||
"LABEL": "trier par "
|
||||
|
@ -48,6 +48,7 @@
|
||||
"PASSWORD_UPDATED": "Votre mot de passe a été mis à jour. Cliquez {0} pour vous connecter.",
|
||||
"PROFILE": {
|
||||
"ACCOUNT_EDITION": "Mise à jour du compte",
|
||||
"ASCENT_DATA": "Données relatives au dénivelé positif (records, total)",
|
||||
"BACK_TO_PROFILE": "Revenir au profil",
|
||||
"BIO": "Bio",
|
||||
"BIRTH_DATE": "Date de naissance",
|
||||
|
@ -43,6 +43,7 @@
|
||||
"RECORD": "record | records",
|
||||
"RECORD_AS": "Vitesse moy.",
|
||||
"RECORD_FD": "Distance la + longue",
|
||||
"RECORD_HA": "Dénivelé positif le + élevé",
|
||||
"RECORD_LD": "Durée la + longue",
|
||||
"RECORD_MS": "Vitesse max.",
|
||||
"REMAINING_CHARS": "nombre de caractères restants ",
|
||||
|
@ -24,6 +24,7 @@ export interface IUserProfile {
|
||||
}
|
||||
|
||||
export interface IAuthUserProfile extends IUserProfile {
|
||||
display_ascent: boolean
|
||||
imperial_units: boolean
|
||||
language: string | null
|
||||
timezone: string
|
||||
@ -58,6 +59,7 @@ export interface IAdminUserPayload {
|
||||
}
|
||||
|
||||
export interface IUserPreferencesPayload {
|
||||
display_ascent: boolean
|
||||
imperial_units: boolean
|
||||
language: string
|
||||
timezone: string
|
||||
|
@ -9,30 +9,47 @@ export const formatRecord = (
|
||||
tz: string,
|
||||
useImperialUnits: boolean
|
||||
): Record<string, string | number> => {
|
||||
const unitFrom: TUnit = 'km'
|
||||
const unitTo: TUnit = useImperialUnits
|
||||
? units[unitFrom].defaultTarget
|
||||
: unitFrom
|
||||
const distanceUnitFrom: TUnit = 'km'
|
||||
const distanceUnitTo: TUnit = useImperialUnits
|
||||
? units[distanceUnitFrom].defaultTarget
|
||||
: distanceUnitFrom
|
||||
const ascentUnitFrom: TUnit = 'm'
|
||||
const ascentUnitTo: TUnit = useImperialUnits
|
||||
? units[ascentUnitFrom].defaultTarget
|
||||
: ascentUnitFrom
|
||||
let value
|
||||
switch (record.record_type) {
|
||||
case 'AS':
|
||||
case 'MS':
|
||||
value = `${convertDistance(
|
||||
+record.value,
|
||||
unitFrom,
|
||||
unitTo,
|
||||
distanceUnitFrom,
|
||||
distanceUnitTo,
|
||||
2
|
||||
)} ${unitTo}/h`
|
||||
)} ${distanceUnitTo}/h`
|
||||
break
|
||||
case 'FD':
|
||||
value = `${convertDistance(+record.value, unitFrom, unitTo, 3)} ${unitTo}`
|
||||
value = `${convertDistance(
|
||||
+record.value,
|
||||
distanceUnitFrom,
|
||||
distanceUnitTo,
|
||||
3
|
||||
)} ${distanceUnitTo}`
|
||||
break
|
||||
case 'HA':
|
||||
value = `${convertDistance(
|
||||
+record.value,
|
||||
ascentUnitFrom,
|
||||
ascentUnitTo,
|
||||
2
|
||||
)} ${ascentUnitTo}`
|
||||
break
|
||||
case 'LD':
|
||||
value = record.value
|
||||
break
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid record type, expected: "AS", "FD", "LD", "MD", got: "${record.record_type}"`
|
||||
`Invalid record type, expected: "AS", "FD", "HA", "LD", "MD", got: "${record.record_type}"`
|
||||
)
|
||||
}
|
||||
return {
|
||||
@ -55,21 +72,24 @@ export const getRecordsBySports = (
|
||||
records: IRecord[],
|
||||
translatedSports: ITranslatedSport[],
|
||||
tz: string,
|
||||
useImperialUnits: boolean
|
||||
useImperialUnits: boolean,
|
||||
display_ascent: boolean
|
||||
): IRecordsBySports =>
|
||||
records.reduce((sportList: IRecordsBySports, record) => {
|
||||
const sport = translatedSports.find((s) => s.id === record.sport_id)
|
||||
if (sport && sport.label) {
|
||||
if (sportList[sport.translatedLabel] === void 0) {
|
||||
sportList[sport.translatedLabel] = {
|
||||
label: sport.label,
|
||||
color: sport.color,
|
||||
records: [],
|
||||
records
|
||||
.filter((r) => (display_ascent ? true : r.record_type !== 'HA'))
|
||||
.reduce((sportList: IRecordsBySports, record) => {
|
||||
const sport = translatedSports.find((s) => s.id === record.sport_id)
|
||||
if (sport && sport.label) {
|
||||
if (sportList[sport.translatedLabel] === void 0) {
|
||||
sportList[sport.translatedLabel] = {
|
||||
label: sport.label,
|
||||
color: sport.color,
|
||||
records: [],
|
||||
}
|
||||
}
|
||||
sportList[sport.translatedLabel].records.push(
|
||||
formatRecord(record, tz, useImperialUnits)
|
||||
)
|
||||
}
|
||||
sportList[sport.translatedLabel].records.push(
|
||||
formatRecord(record, tz, useImperialUnits)
|
||||
)
|
||||
}
|
||||
return sportList
|
||||
}, {})
|
||||
return sportList
|
||||
}, {})
|
||||
|
@ -6,9 +6,9 @@ export const formatTooltipValue = (
|
||||
displayedData: TStatisticsDatasetKeys,
|
||||
value: number,
|
||||
useImperialUnits: boolean,
|
||||
formatWithUnits = true
|
||||
formatWithUnits = true,
|
||||
unitFrom = 'km'
|
||||
): string => {
|
||||
const unitFrom = 'km'
|
||||
const unitTo = useImperialUnits ? units[unitFrom].defaultTarget : unitFrom
|
||||
switch (displayedData) {
|
||||
case 'average_speed':
|
||||
@ -16,10 +16,9 @@ export const formatTooltipValue = (
|
||||
case 'total_duration':
|
||||
return formatDuration(value, formatWithUnits)
|
||||
case 'total_distance':
|
||||
return `${value.toFixed(2)} ${unitTo}`
|
||||
case 'total_ascent':
|
||||
case 'total_descent':
|
||||
return `${(value / 1000).toFixed(2)} ${unitTo}`
|
||||
return `${value.toFixed(2)} ${unitTo}`
|
||||
default:
|
||||
return value.toString()
|
||||
}
|
||||
|
@ -94,6 +94,28 @@ describe('formatRecord', () => {
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "return formatted record for 'Highest ascent'",
|
||||
inputParams: {
|
||||
record: {
|
||||
id: 13,
|
||||
record_type: 'HA',
|
||||
sport_id: 1,
|
||||
user: 'admin',
|
||||
value: 100,
|
||||
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
timezone: 'Europe/Paris',
|
||||
},
|
||||
expected: {
|
||||
id: 13,
|
||||
record_type: 'HA',
|
||||
value: '100 m',
|
||||
workout_date: '2019/07/07',
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
},
|
||||
]
|
||||
testsParams.map((testParams) => {
|
||||
it(testParams.description, () => {
|
||||
@ -199,6 +221,28 @@ describe('formatRecord after conversion', () => {
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "return formatted record for 'Highest ascent'",
|
||||
inputParams: {
|
||||
record: {
|
||||
id: 13,
|
||||
record_type: 'HA',
|
||||
sport_id: 1,
|
||||
user: 'admin',
|
||||
value: 100,
|
||||
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
timezone: 'Europe/Paris',
|
||||
},
|
||||
expected: {
|
||||
id: 13,
|
||||
record_type: 'HA',
|
||||
value: '328.08 ft',
|
||||
workout_date: '2019/07/07',
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
},
|
||||
]
|
||||
testsParams.map((testParams) => {
|
||||
it(testParams.description, () => {
|
||||
@ -231,7 +275,7 @@ describe('formatRecord (invalid record type)', () => {
|
||||
false
|
||||
)
|
||||
).to.throw(
|
||||
'Invalid record type, expected: "AS", "FD", "LD", "MD", got: "M"'
|
||||
'Invalid record type, expected: "AS", "FD", "HA", "LD", "MD", got: "M"'
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -356,7 +400,8 @@ describe('getRecordsBySports', () => {
|
||||
testParams.input.records,
|
||||
translatedSports,
|
||||
testParams.input.tz,
|
||||
false
|
||||
false,
|
||||
true
|
||||
),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
@ -486,6 +531,7 @@ describe('getRecordsBySports after conversion', () => {
|
||||
testParams.input.records,
|
||||
translatedSports,
|
||||
testParams.input.tz,
|
||||
true,
|
||||
true
|
||||
),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
@ -495,3 +541,73 @@ describe('getRecordsBySports after conversion', () => {
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
describe('getRecordsBySports with HA record', () => {
|
||||
const testsParams = [
|
||||
{
|
||||
description: 'returns empty object if no records',
|
||||
input: {
|
||||
records: [],
|
||||
tz: 'Europe/Paris',
|
||||
},
|
||||
expected: {},
|
||||
},
|
||||
{
|
||||
description: 'returns records except HA record',
|
||||
input: {
|
||||
records: [
|
||||
{
|
||||
id: 9,
|
||||
record_type: 'AS',
|
||||
sport_id: 1,
|
||||
user: 'admin',
|
||||
value: 18,
|
||||
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
record_type: 'HA',
|
||||
sport_id: 1,
|
||||
user: 'admin',
|
||||
value: 235,
|
||||
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
],
|
||||
tz: 'Europe/Paris',
|
||||
},
|
||||
expected: {
|
||||
'Cycling (Sport)': {
|
||||
color: null,
|
||||
label: 'Cycling (Sport)',
|
||||
records: [
|
||||
{
|
||||
id: 9,
|
||||
record_type: 'AS',
|
||||
value: '18 km/h',
|
||||
workout_date: '2019/07/07',
|
||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
testsParams.map((testParams) =>
|
||||
it(testParams.description, () => {
|
||||
assert.deepEqual(
|
||||
getRecordsBySports(
|
||||
testParams.input.records,
|
||||
translatedSports,
|
||||
testParams.input.tz,
|
||||
false,
|
||||
false
|
||||
),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
testParams.expected
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
@ -30,16 +30,16 @@ describe('formatTooltipValue', () => {
|
||||
expectedResult: '30.00 km',
|
||||
},
|
||||
{
|
||||
description: 'returns 0.003 km if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_distance'
|
||||
description: 'returns 30 km if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_ascent'
|
||||
inputValue: 30,
|
||||
expectedResult: '0.03 km',
|
||||
expectedResult: '30.00 km',
|
||||
},
|
||||
{
|
||||
description: 'returns 0.003 km if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_distance'
|
||||
description: 'returns 30 km if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_descent'
|
||||
inputValue: 30,
|
||||
expectedResult: '0.03 km',
|
||||
expectedResult: '30.00 km',
|
||||
},
|
||||
]
|
||||
|
||||
@ -84,16 +84,16 @@ describe('formatTooltipValue after conversion to imperial units', () => {
|
||||
expectedResult: '30.00 mi',
|
||||
},
|
||||
{
|
||||
description: 'returns 0.03 mi if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_distance'
|
||||
description: 'returns 30 mi if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_ascent'
|
||||
inputValue: 30,
|
||||
expectedResult: '0.03 mi',
|
||||
expectedResult: '30.00 mi',
|
||||
},
|
||||
{
|
||||
description: 'returns 0.03 mi if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_distance'
|
||||
description: 'returns 30 mi if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_descent'
|
||||
inputValue: 30,
|
||||
expectedResult: '0.03 mi',
|
||||
expectedResult: '30.00 mi',
|
||||
},
|
||||
]
|
||||
|
||||
@ -111,6 +111,54 @@ describe('formatTooltipValue after conversion to imperial units', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatTooltipValue with unitFrom', () => {
|
||||
const testsParams = [
|
||||
{
|
||||
description: 'returns 30 km if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_ascent'
|
||||
inputValue: 30,
|
||||
expectedResult: '30.00 m',
|
||||
useImperialUnits: false,
|
||||
},
|
||||
{
|
||||
description: 'returns 30 km if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_descent'
|
||||
inputValue: 30,
|
||||
expectedResult: '30.00 m',
|
||||
useImperialUnits: false,
|
||||
},
|
||||
{
|
||||
description: 'returns 30 mi if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_ascent'
|
||||
inputValue: 30,
|
||||
expectedResult: '30.00 ft',
|
||||
useImperialUnits: true,
|
||||
},
|
||||
{
|
||||
description: 'returns 30 mi if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_descent'
|
||||
inputValue: 30,
|
||||
expectedResult: '30.00 ft',
|
||||
useImperialUnits: true,
|
||||
},
|
||||
]
|
||||
|
||||
testsParams.map((testParams) => {
|
||||
it(testParams.description, () => {
|
||||
assert.equal(
|
||||
formatTooltipValue(
|
||||
testParams.inputDisplayedData,
|
||||
testParams.inputValue,
|
||||
testParams.useImperialUnits,
|
||||
true,
|
||||
'm'
|
||||
),
|
||||
testParams.expectedResult
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatTooltipValue (formatWithUnits = false)', () => {
|
||||
const testsParams = [
|
||||
{
|
||||
@ -138,16 +186,16 @@ describe('formatTooltipValue (formatWithUnits = false)', () => {
|
||||
expectedResult: '30.00 km',
|
||||
},
|
||||
{
|
||||
description: 'returns 0.003 km if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_distance'
|
||||
description: 'returns 30 km if input is total ascent',
|
||||
inputDisplayedData: datasetKeys[4], // 'total_ascent'
|
||||
inputValue: 30,
|
||||
expectedResult: '0.03 km',
|
||||
expectedResult: '30.00 km',
|
||||
},
|
||||
{
|
||||
description: 'returns 0.003 km if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_distance'
|
||||
description: 'returns 30 km if input is total descent',
|
||||
inputDisplayedData: datasetKeys[5], // 'total_descent'
|
||||
inputValue: 30,
|
||||
expectedResult: '0.03 km',
|
||||
expectedResult: '30.00 km',
|
||||
},
|
||||
]
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user