Merge branch 'dev' into elevation

This commit is contained in:
Sam
2022-07-23 17:55:33 +02:00
53 changed files with 657 additions and 125 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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