Client - fix type errors

This commit is contained in:
Sam 2023-11-11 20:16:18 +01:00
parent b6d6a91549
commit 473d0aca53
48 changed files with 228 additions and 118 deletions

View File

@ -28,6 +28,9 @@ jobs:
- name: Lint
working-directory: ${{env.working-directory}}
run: yarn lint
- name: Type check
working-directory: ${{env.working-directory}}
run: yarn type-check
- name: Tests
working-directory: ${{env.working-directory}}
run: yarn test:unit

View File

@ -37,8 +37,9 @@
import NoConfig from '@/components/NoConfig.vue'
import { ROOT_STORE } from '@/store/constants'
import type { TAppConfig } from '@/types/application'
import type { TLanguage } from '@/types/locales'
import { useStore } from '@/use/useStore'
import { localeFromLanguage } from '@/utils/locales'
import { isLanguageSupported } from '@/utils/locales'
const store = useStore()
@ -82,10 +83,10 @@
}, 300)
}
function initLanguage() {
let language = 'en'
let language: TLanguage = 'en'
try {
const navigatorLanguage = navigator.language.split('-')[0]
if (navigatorLanguage in localeFromLanguage) {
if (isLanguageSupported(navigatorLanguage)) {
language = navigatorLanguage
}
} catch (e) {

View File

@ -37,7 +37,7 @@
const { appStatistics } = toRefs(props)
const uploadDirSize = computed(() =>
getReadableFileSize(appStatistics.value.uploads_dir_size, false)
getReadableFileSize(appStatistics.value.uploads_dir_size)
)
</script>

View File

@ -33,8 +33,8 @@
const text = ref('')
function updateText(event: Event & { target: HTMLInputElement }) {
emit('updateValue', event.target.value)
function updateText(event: Event) {
emit('updateValue', (event.target as HTMLInputElement).value)
}
watch(

View File

@ -60,8 +60,12 @@
const { order_by, query, sort, message } = toRefs(props)
const perPage = [10, 25, 50, 100]
function onSelectUpdate(event: Event & { target: HTMLInputElement }) {
emit('updateSelect', event.target.id, event.target.value)
function onSelectUpdate(event: Event) {
emit(
'updateSelect',
(event.target as HTMLInputElement).id,
(event.target as HTMLInputElement).value
)
}
</script>

View File

@ -26,7 +26,7 @@
</template>
<script setup lang="ts">
import { inject, toRefs, withDefaults } from 'vue'
import { inject, toRefs } from 'vue'
import CyclingSport from '@/components/Common/Images/SportImage/CyclingSport.vue'
import CyclingTransport from '@/components/Common/Images/SportImage/CyclingTransport.vue'
@ -55,5 +55,5 @@
})
const { color, sportLabel, title } = toRefs(props)
const sportColors = inject('sportColors')
const sportColors = inject('sportColors') as Record<string, string>
</script>

View File

@ -50,7 +50,7 @@
strongMessage?: string | null
}
const props = withDefaults(defineProps<Props>(), {
strongMessage: () => null,
strongMessage: () => '',
})
const emit = defineEmits(['cancelAction', 'confirmAction'])
@ -63,7 +63,7 @@
)
let confirmButton: HTMLElement | null = null
let cancelButton: HTMLElement | null = null
let previousFocusedElement: Element | null = null
let previousFocusedElement: HTMLInputElement | null = null
function focusTrap(e: KeyboardEvent) {
if (e.key === 'Tab' || e.keyCode === 9) {
@ -77,7 +77,7 @@
}
onMounted(() => {
previousFocusedElement = document.activeElement
previousFocusedElement = document.activeElement as HTMLInputElement | null
cancelButton = document.getElementById('cancel-button')
confirmButton = document.getElementById('confirm-button')
if (cancelButton) {

View File

@ -50,6 +50,7 @@
<script setup lang="ts">
import { toRefs } from 'vue'
import type { LocationQuery } from 'vue-router'
import type { IPagination, TPaginationPayload } from '@/types/api'
import type { IOauth2ClientsPayload } from '@/types/oauth'
@ -65,13 +66,10 @@
const { pagination, path, query } = toRefs(props)
function getQuery(
page: number,
cursor?: number
): TPaginationPayload | IOauth2ClientsPayload {
function getQuery(page: number, cursor?: number): LocationQuery {
const newQuery = Object.assign({}, query.value)
newQuery.page = cursor ? page + cursor : page
return newQuery
return newQuery as LocationQuery
}
</script>

View File

@ -62,8 +62,8 @@
function togglePassword() {
showPassword.value = !showPassword.value
}
function updatePassword(event: Event & { target: HTMLInputElement }) {
emit('updatePassword', event.target.value)
function updatePassword(event: Event) {
emit('updatePassword', (event.target as HTMLInputElement).value)
}
function invalidPassword() {
emit('passwordError')

View File

@ -163,12 +163,9 @@
params: apiParams,
})
}
function updateDisplayData(
event: Event & {
target: HTMLInputElement & { name: TStatisticsDatasetKeys }
}
) {
displayedData.value = event.target.name
function updateDisplayData(event: Event) {
displayedData.value = (event.target as HTMLInputElement)
.name as TStatisticsDatasetKeys
}
function getApiParams(
chartParams: IStatisticsDateParams,

View File

@ -40,14 +40,14 @@
import NoWorkouts from '@/components/Workouts/NoWorkouts.vue'
import { WORKOUTS_STORE } from '@/store/constants'
import type { ISport } from '@/types/sports'
import type { IUserProfile } from '@/types/user'
import type { IAuthUserProfile } from '@/types/user'
import type { IWorkout } from '@/types/workouts'
import { useStore } from '@/use/useStore'
import { defaultOrder } from '@/utils/workouts'
interface Props {
sports: ISport[]
user: IUserProfile
user: IAuthUserProfile
}
const props = defineProps<Props>()

View File

@ -16,7 +16,7 @@
}
const props = defineProps<Props>()
const days = []
const days: Date[] = []
for (let i = 0; i < 7; i++) {
days.push(addDays(props.startDate, i))
}

View File

@ -20,7 +20,9 @@
.filter((record) =>
displayHARecord ? true : record.record_type !== 'HA'
)
.map((record) => ` ${$t(`workouts.RECORD_${record.record_type}`)}`)
.map(
(record) => ` ${$t(`workouts.RECORD_${record.record_type}`)}`
)[0]
"
/>
</sup>

View File

@ -20,6 +20,7 @@
:sports="sports"
:datasets="chartDatasets"
:colors="colors"
:displayHARecord="displayHARecord"
/>
</div>
</div>
@ -30,6 +31,7 @@
:sports="sports"
:datasets="chartDatasets"
:colors="colors"
:displayHARecord="displayHARecord"
/>
</div>
</div>

View File

@ -14,6 +14,7 @@
<CalendarWorkout
v-for="(workout, index) in workouts"
:key="index"
:displayHARecord="displayHARecord"
:workout="workout"
:sportLabel="getSportLabel(workout, sports)"
:sportColor="getSportColor(workout, sports)"
@ -37,13 +38,14 @@
datasets: Record<number, Record<string, number>>
sports: ISport[]
workouts: IWorkout[]
displayHARecord: boolean
}
const props = defineProps<Props>()
const { colors, datasets, sports, workouts } = toRefs(props)
const isHidden = ref(true)
function togglePane(event: Event & { target: HTMLElement }) {
function togglePane(event: Event) {
event.stopPropagation()
isHidden.value = !isHidden.value
}

View File

@ -21,11 +21,11 @@
import StatChart from '@/components/Common/StatsChart/index.vue'
import type { ISport } from '@/types/sports'
import type { IUserProfile } from '@/types/user'
import type { IAuthUserProfile } from '@/types/user'
interface Props {
sports: ISport[]
user: IUserProfile
user: IAuthUserProfile
}
const props = defineProps<Props>()

View File

@ -35,11 +35,11 @@
import { toRefs } from 'vue'
import { useI18n } from 'vue-i18n'
import type { ICardRecord, IRecord, IRecordsBySports } from '@/types/workouts'
import type { ICardRecord, IRecord, IRecordsBySport } from '@/types/workouts'
import { sortRecords } from '@/utils/records'
interface Props {
records: IRecordsBySports
records: IRecordsBySport
sportTranslatedLabel: string
}
const props = defineProps<Props>()

View File

@ -99,6 +99,7 @@
import UserPicture from '@/components/User/UserPicture.vue'
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
import type { IDropdownOption } from '@/types/forms'
import type { TLanguage } from '@/types/locales'
import type { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { availableLanguages } from '@/utils/locales'
@ -130,7 +131,7 @@
function updateLanguage(option: IDropdownOption) {
store.dispatch(
ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE,
option.value.toString()
option.value as TLanguage
)
}
function logout() {

View File

@ -39,7 +39,7 @@
const { t } = useI18n()
const sportColors: Record<string, string> | undefined = inject('sportColors')
const sportColors = inject('sportColors') as Record<string, string>
const { selectedSportIds } = toRefs(props)
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(props.userSports, t)

View File

@ -125,6 +125,7 @@
import { AUTH_USER_STORE, ROOT_STORE, USERS_STORE } from '@/store/constants'
import type { TAppConfig } from '@/types/application'
import type { TLanguage } from '@/types/locales'
import type { IAuthUserProfile, IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { formatDate, getDateFormat } from '@/utils/dates'
@ -141,7 +142,7 @@
const store = useStore()
const { user, fromAdmin } = toRefs(props)
const language: ComputedRef<string> = computed(
const language: ComputedRef<TLanguage> = computed(
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
)
const authUser: ComputedRef<IAuthUserProfile> = computed(

View File

@ -60,6 +60,7 @@
import type { ComputedRef } from 'vue'
import { ROOT_STORE } from '@/store/constants'
import type { TLanguage } from '@/types/locales'
import type { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { getDateFormat } from '@/utils/dates'
@ -72,7 +73,7 @@
const store = useStore()
const appLanguage: ComputedRef<string> = computed(
const appLanguage: ComputedRef<TLanguage> = computed(
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
)
const userLanguage = computed(() =>

View File

@ -60,16 +60,16 @@
isOpen.value = false
emit('updateTimezone', value)
}
function onEnter(event: Event & { target: HTMLInputElement }) {
function onEnter(event: Event) {
event.preventDefault()
if (tzList.value?.firstElementChild?.innerHTML) {
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
}
}
function openDropdown(event: Event & { target: HTMLInputElement }) {
function openDropdown(event: Event) {
event.preventDefault()
isOpen.value = true
timezone.value = event.target.value.trim()
timezone.value = (event.target as HTMLInputElement).value.trim()
}
watch(

View File

@ -87,7 +87,7 @@
>
<i class="fa fa-download" aria-hidden="true" />
{{ $t('user.EXPORT_REQUEST.DOWNLOAD_ARCHIVE') }}
({{ getReadableFileSize(exportRequest.file_size) }})
({{ getReadableFileSizeAsText(exportRequest.file_size) }})
</span>
<span v-else>
{{ $t(`user.EXPORT_REQUEST.STATUS.${exportRequest.status}`) }}
@ -126,7 +126,7 @@
} from '@/types/user'
import { useStore } from '@/use/useStore'
import { formatDate } from '@/utils/dates'
import { getReadableFileSize } from '@/utils/files'
import { getReadableFileSizeAsText } from '@/utils/files'
interface Props {
user: IAuthUserProfile

View File

@ -41,7 +41,7 @@
import type { TAppConfig } from '@/types/application'
import type { IUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { getReadableFileSize } from '@/utils/files'
import { getReadableFileSizeAsText } from '@/utils/files'
interface Props {
user: IUserProfile
@ -58,16 +58,18 @@
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
)
const fileSizeLimit = appConfig.value.max_single_file_size
? getReadableFileSize(appConfig.value.max_single_file_size)
? getReadableFileSizeAsText(appConfig.value.max_single_file_size)
: ''
const pictureFile: Ref<File | null> = ref(null)
function deleteUserPicture() {
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_PICTURE)
}
function updatePictureFile(event: Event & { target: HTMLInputElement }) {
if (event.target.files) {
pictureFile.value = event.target.files[0]
function updatePictureFile(event: Event) {
if ((event.target as HTMLInputElement).files !== null) {
pictureFile.value = (
(event.target as HTMLInputElement).files as FileList
)[0]
}
}
function updateUserPicture() {

View File

@ -185,10 +185,12 @@
const userForm: IUserPreferencesPayload = reactive({
display_ascent: true,
imperial_units: false,
language: '',
language: 'en',
timezone: 'Europe/Paris',
date_format: 'dd/MM/yyyy',
weekm: false,
start_elevation_at_zero: false,
use_raw_gpx_speed: false,
})
const weekStart = [
{

View File

@ -104,6 +104,7 @@
client_name: '',
client_uri: '',
client_description: '',
description: '',
redirect_uri: '',
})
const scopes: string[] = reactive([])

View File

@ -122,8 +122,8 @@
import { useRoute } from 'vue-router'
import { OAUTH2_STORE, ROOT_STORE } from '@/store/constants'
import { IOAuth2Client } from '@/types/oauth'
import { IAuthUserProfile } from '@/types/user'
import type { IOAuth2Client } from '@/types/oauth'
import type { IAuthUserProfile } from '@/types/user'
import { useStore } from '@/use/useStore'
import { formatDate } from '@/utils/dates'
@ -145,7 +145,7 @@
() => store.getters[OAUTH2_STORE.GETTERS.REVOCATION_SUCCESSFUL]
)
const displayModal: Ref<boolean> = ref(false)
const messageToDisplay: Ref<string | null> = ref(null)
const messageToDisplay: Ref<string> = ref('')
const idCopied: Ref<boolean> = ref(false)
const secretCopied: Ref<boolean> = ref(false)
const clipboardSupport: Ref<boolean> = ref(false)
@ -176,7 +176,7 @@
function updateDisplayModal(value: boolean) {
displayModal.value = value
if (!value) {
messageToDisplay.value = null
messageToDisplay.value = ''
}
}
function confirmAction(clientId: number) {

View File

@ -186,7 +186,7 @@
const { isEdition, user } = toRefs(props)
const defaultColor = '#838383'
const sportColors: Record<string, string> | undefined = inject('sportColors')
const sportColors = inject('sportColors') as Record<string, string>
const sports: ComputedRef<ISport[]> = computed(
() => store.getters[SPORTS_STORE.GETTERS.SPORTS]
)
@ -223,14 +223,16 @@
function isSportInEdition(sportId: number) {
return sportPayload.sport_id === sportId
}
function updateColor(event: Event & { target: HTMLInputElement }) {
sportPayload.color = event.target.value
function updateColor(event: Event) {
sportPayload.color = (event.target as HTMLInputElement).value
}
function updateThreshold(event: Event & { target: HTMLInputElement }) {
sportPayload.stopped_speed_threshold = parseFloat(event.target.value)
function updateThreshold(event: Event) {
sportPayload.stopped_speed_threshold = parseFloat(
(event.target as HTMLInputElement).value
)
}
function updateIsActive(event: Event & { target: HTMLInputElement }) {
sportPayload.is_active = event.target.checked
function updateIsActive(event: Event) {
sportPayload.is_active = (event.target as HTMLInputElement).checked
}
function resetSportPayload() {
sportPayload.sport_id = 0

View File

@ -73,7 +73,7 @@
>
<div class="img">
<SportImage
v-if="sport.label"
v-if="sport?.label"
:sport-label="sport.label"
:color="sport.color"
/>
@ -147,16 +147,16 @@
import UserPicture from '@/components/User/UserPicture.vue'
import { ROOT_STORE } from '@/store/constants'
import type { ISport } from '@/types/sports'
import type { IUserProfile } from '@/types/user'
import type { IAuthUserProfile } from '@/types/user'
import type { IWorkout } from '@/types/workouts'
import { useStore } from '@/use/useStore'
import { formatDate } from '@/utils/dates'
interface Props {
user: IUserProfile
user: IAuthUserProfile
useImperialUnits: boolean
workout?: IWorkout
sport?: ISport
sport?: ISport | null
}
const props = withDefaults(defineProps<Props>(), {
workout: () => ({}) as IWorkout,

View File

@ -24,7 +24,7 @@
notes?: string | null
}
const props = withDefaults(defineProps<Props>(), {
notes: () => null,
notes: () => '',
})
const { notes } = toRefs(props)

View File

@ -11,6 +11,7 @@
<Card>
<template #title>
<WorkoutCardTitle
v-if="sport"
:sport="sport"
:workoutObject="workoutObject"
@displayModal="updateDisplayModal(true)"
@ -84,7 +85,7 @@
? props.sports.find(
(sport) => sport.id === props.workoutData.workout.sport_id
)
: {}
: ({} as ISport)
)
const workoutObject = computed(() =>
getWorkoutObject(workout.value, segment.value)

View File

@ -281,12 +281,12 @@
import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants'
import type { TAppConfig } from '@/types/application'
import type { ISport } from '@/types/sports'
import type { ISport, ITranslatedSport } from '@/types/sports'
import type { IAuthUserProfile } from '@/types/user'
import type { IWorkout, IWorkoutForm } from '@/types/workouts'
import { useStore } from '@/use/useStore'
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
import { getReadableFileSize } from '@/utils/files'
import { getReadableFileSizeAsText } from '@/utils/files'
import { translateSports } from '@/utils/sports'
import { convertDistance } from '@/utils/units'
@ -308,7 +308,7 @@
const router = useRouter()
const { authUser, workout, isCreation, loading } = toRefs(props)
const translatedSports: ComputedRef<ISport[]> = computed(() =>
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(
props.sports,
t,
@ -320,11 +320,11 @@
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
)
const fileSizeLimit = appConfig.value.max_single_file_size
? getReadableFileSize(appConfig.value.max_single_file_size)
? getReadableFileSizeAsText(appConfig.value.max_single_file_size)
: ''
const gpx_limit_import = appConfig.value.gpx_limit_import
const zipSizeLimit = appConfig.value.max_zip_file_size
? getReadableFileSize(appConfig.value.max_zip_file_size)
? getReadableFileSizeAsText(appConfig.value.max_zip_file_size)
: ''
const errorMessages: ComputedRef<string | string[] | null> = computed(
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
@ -369,9 +369,9 @@
withGpx.value = !withGpx.value
formErrors.value = false
}
function updateFile(event: Event & { target: HTMLInputElement }) {
if (event.target.files) {
gpxFile = event.target.files[0]
function updateFile(event: Event) {
if ((event.target as HTMLInputElement).files) {
gpxFile = ((event.target as HTMLInputElement).files as FileList)[0]
}
}
function formatWorkoutForm(workout: IWorkout) {

View File

@ -192,7 +192,7 @@
import { useRoute, useRouter } from 'vue-router'
import type { LocationQuery } from 'vue-router'
import type { ISport } from '@/types/sports'
import type { ISport, ITranslatedSport } from '@/types/sports'
import type { IAuthUserProfile } from '@/types/user'
import { translateSports } from '@/utils/sports'
import { units } from '@/utils/units'
@ -214,7 +214,7 @@
const toUnit = authUser.value.imperial_units
? units['km'].defaultTarget
: 'km'
const translatedSports: ComputedRef<ISport[]> = computed(() =>
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
translateSports(props.sports, t)
)
let params: LocationQuery = Object.assign({}, route.query)
@ -226,11 +226,13 @@
}
})
function handleFilterChange(event: Event & { target: HTMLInputElement }) {
if (event.target.value === '') {
delete params[event.target.name]
function handleFilterChange(event: Event) {
const name = (event.target as HTMLInputElement).name
const value = (event.target as HTMLInputElement).value
if (value === '') {
delete params[name]
} else {
params[event.target.name] = event.target.value
params[name] = value
}
}
function onFilter() {

View File

@ -5,7 +5,7 @@
<span class="total-label">
{{ $t('common.TOTAL').toLowerCase() }}:
</span>
<span v-if="pagination.total !== null">
<span v-if="pagination.total">
{{ pagination.total }}
{{ $t('workouts.WORKOUT', pagination.total) }}
</span>
@ -47,7 +47,7 @@
<SportImage
v-if="sports.length > 0"
:title="
sports.find((s) => s.id === workout.sport_id)
sports.filter((s) => s.id === workout.sport_id)[0]
.translatedLabel
"
:sport-label="getSportLabel(workout, sports)"

View File

@ -6,6 +6,7 @@ import router from '@/router'
import { ROOT_STORE } from '@/store/constants'
import type { IRootActions, IRootState } from '@/store/modules/root/types'
import type { TAppConfigForm } from '@/types/application'
import type { TLanguage } from '@/types/locales'
import { handleError } from '@/utils'
const { locale } = createI18n.global
@ -91,7 +92,7 @@ export const actions: ActionTree<IRootState, IRootState> & IRootActions = {
},
[ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE](
context: ActionContext<IRootState, IRootState>,
language: string
language: TLanguage
): void {
document.querySelector('html')?.setAttribute('lang', language)
context.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, language)

View File

@ -3,6 +3,7 @@ import type { MutationTree } from 'vuex'
import { ROOT_STORE } from '@/store/constants'
import type { IRootState, TRootMutations } from '@/store/modules/root/types'
import type { TAppConfig, IAppStatistics } from '@/types/application'
import type { TLanguage } from '@/types/locales'
import { localeFromLanguage } from '@/utils/locales'
export const mutations: MutationTree<IRootState> & TRootMutations = {
@ -40,7 +41,7 @@ export const mutations: MutationTree<IRootState> & TRootMutations = {
) {
state.application.statistics = statistics
},
[ROOT_STORE.MUTATIONS.UPDATE_LANG](state: IRootState, language: string) {
[ROOT_STORE.MUTATIONS.UPDATE_LANG](state: IRootState, language: TLanguage) {
state.language = language
state.locale = localeFromLanguage[language]
},

View File

@ -13,10 +13,11 @@ import type {
IAppStatistics,
TAppConfigForm,
} from '@/types/application'
import type { TLanguage } from '@/types/locales'
export interface IRootState {
root: boolean
language: string
language: TLanguage
locale: Locale
errorMessages: string | string[] | null
application: IApplication
@ -39,7 +40,7 @@ export interface IRootActions {
): void
[ROOT_STORE.ACTIONS.UPDATE_APPLICATION_LANGUAGE](
context: ActionContext<IRootState, IRootState>,
langauge: string
language: TLanguage
): void
}
@ -54,7 +55,7 @@ export interface IRootGetters {
state: IRootState
): string | string[] | null
[ROOT_STORE.GETTERS.LANGUAGE](state: IRootState): string
[ROOT_STORE.GETTERS.LANGUAGE](state: IRootState): TLanguage
[ROOT_STORE.GETTERS.LOCALE](state: IRootState): Locale
}
@ -81,7 +82,7 @@ export type TRootMutations<S = IRootState> = {
state: S,
statistics: IAppStatistics
): void
[ROOT_STORE.MUTATIONS.UPDATE_LANG](state: S, language: string): void
[ROOT_STORE.MUTATIONS.UPDATE_LANG](state: S, language: TLanguage): void
}
export type TRootStoreModule<S = IRootState> = Omit<

View File

@ -37,3 +37,8 @@ export type TAppConfigForm = {
max_zip_file_size: number
privacy_policy: string
}
export interface IFileSize {
size: string
suffix: string
}

View File

@ -0,0 +1,10 @@
export type TLanguage =
| 'en'
| 'de'
| 'es'
| 'fr'
| 'gl'
| 'it'
| 'nb'
| 'nl'
| 'pl'

View File

@ -1,5 +1,6 @@
import type { LocationQueryValue } from 'vue-router'
import type { TLanguage } from '@/types/locales'
import type { IRecord } from '@/types/workouts'
export interface IUserProfile {
@ -30,7 +31,7 @@ export interface IAuthUserProfile extends IUserProfile {
imperial_units: boolean
start_elevation_at_zero: boolean
use_raw_gpx_speed: boolean
language: string | null
language: TLanguage | null
timezone: string
date_format: string
weekm: boolean
@ -68,7 +69,7 @@ export interface IUserPreferencesPayload {
start_elevation_at_zero: boolean
use_raw_gpx_speed: boolean
imperial_units: boolean
language: string
language: TLanguage
timezone: string
date_format: string
weekm: boolean

View File

@ -34,10 +34,10 @@ export interface IRecord {
}
export interface IRecordsBySport {
[key: string]: string | Record<string, string | number>[] | null
[key: string]: string | IRecord[] | null
label: string
color: string | null
records: Record<string, string | number>[]
records: IRecord[]
}
export interface IRecordsBySports {
@ -59,8 +59,8 @@ export interface IWorkout {
bounds: number[]
creation_date: string
descent: number | null
distance: number | null
duration: string | null
distance: number
duration: string
id: string
map: string | null
max_alt: number | null

View File

@ -12,6 +12,7 @@ import {
import { utcToZonedTime } from 'date-fns-tz'
import createI18n from '@/i18n'
import type { TLanguage } from '@/types/locales'
import { localeFromLanguage } from '@/utils/locales'
const { locale } = createI18n.global
@ -114,7 +115,7 @@ export const formatDate = (
timezone: string,
dateFormat: string,
withTime = true,
language: string | null = null,
language: TLanguage | null = null,
withSeconds = false
): string => {
if (!language) {
@ -131,9 +132,9 @@ export const formatDate = (
export const availableDateFormatOptions = (
inputDate: string,
timezone: string,
language: string | null = null
language: TLanguage | null = null
) => {
const l: string = language ? language : locale.value
const l: TLanguage = language ? language : locale.value
const options: Record<string, string>[] = []
availableDateFormats.map((df) => {
const dateFormat = getDateFormat(df, l)

View File

@ -1,16 +1,23 @@
import type { IFileSize } from '@/types/application'
const suffixes = ['bytes', 'KB', 'MB', 'GB', 'TB']
export const getReadableFileSize = (
fileSize: number,
asText = true
): string | Record<string, string> => {
const i = Math.floor(Math.log(fileSize) / Math.log(1024))
export const getReadableFileSize = (fileSize: number): IFileSize => {
if (!fileSize) {
return asText ? '0 bytes' : { size: '0', suffix: 'bytes' }
return { size: '0', suffix: 'bytes' }
}
const i = Math.floor(Math.log(fileSize) / Math.log(1024))
const size = (fileSize / Math.pow(1024, i)).toFixed(1)
const suffix = suffixes[i]
return asText ? `${size}${suffix}` : { size, suffix }
return { size, suffix }
}
export const getReadableFileSizeAsText = (fileSize: number): string => {
if (!fileSize) {
return '0 bytes'
}
const readableFileSize = getReadableFileSize(fileSize)
return `${readableFileSize.size}${readableFileSize.suffix}`
}
export const getFileSizeInMB = (fileSize: number): number => {

View File

@ -2,20 +2,37 @@ import type { Locale } from 'date-fns'
import { de, enUS, es, fr, gl, it, nb, nl, pl } from 'date-fns/locale'
import createI18n from '@/i18n'
import type { TLanguage } from '@/types/locales'
export const localeFromLanguage: Record<string, Locale> = {
export const isLanguageSupported = (
language: string
): language is TLanguage => {
return (
language === 'de' ||
language === 'en' ||
language === 'es' ||
language === 'fr' ||
language === 'gl' ||
language === 'it' ||
language === 'nb' ||
language === 'nl' ||
language === 'pl'
)
}
export const localeFromLanguage: Record<TLanguage, Locale> = {
de: de,
en: enUS,
es: es,
fr: fr,
gl: gl,
it: it,
pl: pl,
nb: nb,
nl: nl,
pl: pl,
}
export const languageLabels: Record<string, string> = {
export const languageLabels: Record<TLanguage, string> = {
de: 'Deutsch',
en: 'English',
es: 'Español',

View File

@ -12,7 +12,7 @@ export const formatRecord = (
tz: string,
useImperialUnits: boolean,
date_format: string
): Record<string, string | number> => {
): IRecord => {
const distanceUnitFrom: TUnit = 'km'
const distanceUnitTo: TUnit = useImperialUnits
? units[distanceUnitFrom].defaultTarget
@ -57,11 +57,13 @@ export const formatRecord = (
)
}
return {
workout_date: formatDate(record.workout_date, tz, date_format, false),
workout_id: record.workout_id,
id: record.id,
record_type: record.record_type,
sport_id: record.sport_id,
value: value,
user: record.user,
workout_date: formatDate(record.workout_date, tz, date_format, false),
workout_id: record.workout_id,
}
}

View File

@ -27,7 +27,7 @@
const router = useRouter()
const { action } = toRefs(props)
const token = computed(() => route.query.token)
const token = computed(() => route.query.token as string)
onBeforeMount(() => {
if (props.action === 'reset' && !token.value) {

View File

@ -1,8 +1,12 @@
import { describe, it, expect } from 'vitest'
import { getFileSizeInMB, getReadableFileSize } from '@/utils/files'
import {
getFileSizeInMB,
getReadableFileSize,
getReadableFileSizeAsText,
} from '@/utils/files'
describe('getReadableFileSize (as text)', () => {
describe('getReadableFileSizeAsText', () => {
const testsParams = [
{
description: 'returns 0 bytes if provided file size is 0',
@ -23,7 +27,7 @@ describe('getReadableFileSize (as text)', () => {
testsParams.map((testParams) => {
it(testParams.description, () => {
expect(getReadableFileSize(testParams.inputFileSize, true)).toStrictEqual(
expect(getReadableFileSizeAsText(testParams.inputFileSize)).toStrictEqual(
testParams.expectedReadableFileSize
)
})
@ -51,9 +55,9 @@ describe('getReadableFileSize (as object)', () => {
testsParams.map((testParams) => {
it(testParams.description, () => {
expect(
getReadableFileSize(testParams.inputFileSize, false)
).toStrictEqual(testParams.expectedReadableFileSize)
expect(getReadableFileSize(testParams.inputFileSize)).toStrictEqual(
testParams.expectedReadableFileSize
)
})
})
})

View File

@ -24,6 +24,8 @@ describe('formatRecord', () => {
expected: {
id: 9,
record_type: 'AS',
sport_id: 1,
user: 'admin',
value: '18 km/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -47,6 +49,8 @@ describe('formatRecord', () => {
expected: {
id: 10,
record_type: 'FD',
sport_id: 1,
user: 'admin',
value: '18 km',
workout_date: '2019/07/08',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -70,6 +74,8 @@ describe('formatRecord', () => {
expected: {
id: 11,
record_type: 'LD',
sport_id: 1,
user: 'admin',
value: '1:01:00',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -93,6 +99,8 @@ describe('formatRecord', () => {
expected: {
id: 12,
record_type: 'MS',
sport_id: 1,
user: 'admin',
value: '18 km/h',
workout_date: '08/07/2019',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -116,6 +124,8 @@ describe('formatRecord', () => {
expected: {
id: 13,
record_type: 'HA',
sport_id: 1,
user: 'admin',
value: '100 m',
workout_date: 'Jul. 7th, 2019',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -156,6 +166,8 @@ describe('formatRecord after conversion', () => {
expected: {
id: 9,
record_type: 'AS',
sport_id: 1,
user: 'admin',
value: '11.18 mi/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -179,6 +191,8 @@ describe('formatRecord after conversion', () => {
expected: {
id: 10,
record_type: 'FD',
sport_id: 1,
user: 'admin',
value: '11.185 mi',
workout_date: '2019/08/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -202,6 +216,8 @@ describe('formatRecord after conversion', () => {
expected: {
id: 11,
record_type: 'LD',
sport_id: 1,
user: 'admin',
value: '1:01:00',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -225,6 +241,8 @@ describe('formatRecord after conversion', () => {
expected: {
id: 12,
record_type: 'MS',
sport_id: 1,
user: 'admin',
value: '11.18 mi/h',
workout_date: '2019/08/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -248,6 +266,8 @@ describe('formatRecord after conversion', () => {
expected: {
id: 13,
record_type: 'HA',
sport_id: 1,
user: 'admin',
value: '328.08 ft',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -327,6 +347,8 @@ describe('getRecordsBySports', () => {
{
id: 9,
record_type: 'AS',
sport_id: 1,
user: 'admin',
value: '18 km/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -378,6 +400,8 @@ describe('getRecordsBySports', () => {
{
id: 9,
record_type: 'AS',
sport_id: 1,
user: 'admin',
value: '18 km/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -385,6 +409,8 @@ describe('getRecordsBySports', () => {
{
id: 12,
record_type: 'MS',
sport_id: 1,
user: 'admin',
value: '18 km/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -398,6 +424,8 @@ describe('getRecordsBySports', () => {
{
id: 10,
record_type: 'FD',
sport_id: 2,
user: 'admin',
value: '18 km',
workout_date: '2019/08/07',
workout_id: 'n6JcLPQt3QtZWFfiSnYm4C',
@ -459,6 +487,8 @@ describe('getRecordsBySports after conversion', () => {
{
id: 9,
record_type: 'AS',
sport_id: 1,
user: 'admin',
value: '11.18 mi/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -510,6 +540,8 @@ describe('getRecordsBySports after conversion', () => {
{
id: 9,
record_type: 'AS',
sport_id: 1,
user: 'admin',
value: '11.18 mi/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -517,6 +549,8 @@ describe('getRecordsBySports after conversion', () => {
{
id: 12,
record_type: 'MS',
sport_id: 1,
user: 'admin',
value: '11.18 mi/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
@ -530,6 +564,8 @@ describe('getRecordsBySports after conversion', () => {
{
id: 10,
record_type: 'FD',
sport_id: 2,
user: 'admin',
value: '11.185 mi',
workout_date: '2019/08/07',
workout_id: 'n6JcLPQt3QtZWFfiSnYm4C',
@ -600,6 +636,8 @@ describe('getRecordsBySports with HA record', () => {
{
id: 9,
record_type: 'AS',
sport_id: 1,
user: 'admin',
value: '18 km/h',
workout_date: '2019/07/07',
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',