Client - init workout chart (WIP)
This commit is contained in:
parent
146899c269
commit
c50e74143e
@ -13,6 +13,21 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
Chart,
|
||||
BarElement,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Legend,
|
||||
Title,
|
||||
Tooltip,
|
||||
Filler,
|
||||
BarController,
|
||||
CategoryScale,
|
||||
LineController,
|
||||
LinearScale,
|
||||
} from 'chart.js'
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||
import { computed, ComputedRef, defineComponent, onBeforeMount } from 'vue'
|
||||
|
||||
import Loader from '@/components/Common/Loader.vue'
|
||||
@ -23,6 +38,21 @@
|
||||
import { IAppConfig } from '@/types/application'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
Chart.register(
|
||||
BarElement,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Legend,
|
||||
Title,
|
||||
Tooltip,
|
||||
Filler,
|
||||
BarController,
|
||||
CategoryScale,
|
||||
LineController,
|
||||
LinearScale,
|
||||
ChartDataLabels
|
||||
)
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
components: {
|
||||
|
@ -5,25 +5,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
Chart,
|
||||
ChartData,
|
||||
ChartOptions,
|
||||
LayoutItem,
|
||||
registerables,
|
||||
} from 'chart.js'
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||
import { ChartData, ChartOptions, LayoutItem } from 'chart.js'
|
||||
import { ComputedRef, PropType, computed, defineComponent } from 'vue'
|
||||
import { BarChart, useBarChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { IChartDataset } from '@/types/chart'
|
||||
import { TDatasetKeys } from '@/types/statistics'
|
||||
import { TStatisticsDatasetKeys } from '@/types/statistics'
|
||||
import { formatTooltipValue } from '@/utils/tooltip'
|
||||
|
||||
Chart.register(...registerables)
|
||||
Chart.register(ChartDataLabels)
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Chart',
|
||||
components: {
|
||||
@ -39,7 +29,7 @@
|
||||
required: true,
|
||||
},
|
||||
displayedData: {
|
||||
type: String as PropType<TDatasetKeys>,
|
||||
type: String as PropType<TStatisticsDatasetKeys>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
@ -16,7 +16,7 @@
|
||||
import {
|
||||
IStatisticsChartData,
|
||||
IStatisticsDateParams,
|
||||
TDatasetKeys,
|
||||
TStatisticsDatasetKeys,
|
||||
TStatisticsFromApi,
|
||||
} from '@/types/statistics'
|
||||
import { formatStats } from '@/utils/statistics'
|
||||
@ -32,7 +32,7 @@
|
||||
required: true,
|
||||
},
|
||||
displayedData: {
|
||||
type: String as PropType<TDatasetKeys>,
|
||||
type: String as PropType<TStatisticsDatasetKeys>,
|
||||
required: true,
|
||||
},
|
||||
params: {
|
||||
|
188
fittrackee_client/src/components/Workout/WorkoutChart/index.vue
Normal file
188
fittrackee_client/src/components/Workout/WorkoutChart/index.vue
Normal file
@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div id="workout-chart">
|
||||
<Card :without-title="false">
|
||||
<template #title>{{ t('workouts.ANALYSIS') }} </template>
|
||||
<template #content>
|
||||
<div class="chart-radio">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="distance"
|
||||
:checked="displayDistance"
|
||||
@click="updateDisplayDistance"
|
||||
/>
|
||||
{{ t('workouts.DISTANCE') }}
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="duration"
|
||||
:checked="!displayDistance"
|
||||
@click="updateDisplayDistance"
|
||||
/>
|
||||
{{ t('workouts.DURATION') }}
|
||||
</label>
|
||||
</div>
|
||||
<LineChart v-bind="lineChartProps" class="line-chart" />
|
||||
<div class="no-data-cleaning">
|
||||
{{ t('workouts.NO_DATA_CLEANING') }}
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartData, ChartOptions } from 'chart.js'
|
||||
import { PropType, defineComponent, ref, ComputedRef, computed } from 'vue'
|
||||
import { LineChart, useLineChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { IWorkoutChartData, IWorkoutState } from '@/types/workouts'
|
||||
import { getDatasets } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutChart',
|
||||
components: {
|
||||
Card,
|
||||
LineChart,
|
||||
},
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IAuthUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
workout: {
|
||||
type: Object as PropType<IWorkoutState>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workout.chartData, t)
|
||||
)
|
||||
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
labels: displayDistance.value
|
||||
? datasets.value.distance_labels
|
||||
: datasets.value.duration_labels,
|
||||
datasets: JSON.parse(
|
||||
JSON.stringify([
|
||||
datasets.value.datasets.speed,
|
||||
datasets.value.datasets.elevation,
|
||||
])
|
||||
),
|
||||
}))
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: new Date(+value * 1000).toISOString().substr(11, 8)
|
||||
},
|
||||
},
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
title: {
|
||||
display: true,
|
||||
text: displayDistance.value
|
||||
? t('workouts.DISTANCE') + ' (km)'
|
||||
: t('workouts.DURATION'),
|
||||
},
|
||||
},
|
||||
ySpeed: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.SPEED') + ' (km/h)',
|
||||
},
|
||||
},
|
||||
yElevation: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.ELEVATION') + ' (m)',
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
pointStyle: 'circle',
|
||||
pointRadius: 0,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
return {
|
||||
displayDistance,
|
||||
lineChartProps,
|
||||
t,
|
||||
updateDisplayDistance,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base';
|
||||
#workout-chart {
|
||||
::v-deep(.card) {
|
||||
.card-title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.chart-radio {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
padding: 0 $default-padding;
|
||||
}
|
||||
}
|
||||
.no-data-cleaning {
|
||||
font-size: 0.85em;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,10 +1,12 @@
|
||||
{
|
||||
"ADD_WORKOUT": "Add workout",
|
||||
"ANALYSIS": "analysis",
|
||||
"ASCENT": "ascent",
|
||||
"AVERAGE_SPEED": "average speed",
|
||||
"DESCENT": "descent",
|
||||
"DISTANCE": "distance",
|
||||
"DURATION": "duration",
|
||||
"ELEVATION": "elevation",
|
||||
"END": "end",
|
||||
"KM": "km",
|
||||
"LATEST_WORKOUTS": "Latest workouts",
|
||||
@ -12,6 +14,7 @@
|
||||
"MAX_SPEED": "max. speed",
|
||||
"MIN_ALTITUDE": "min. altitude",
|
||||
"NEXT_WORKOUT": "Next workout",
|
||||
"NO_DATA_CLEANING": "data from gpx, without any cleaning",
|
||||
"NO_MAP": "No map",
|
||||
"NO_NEXT_WORKOUT": "No next workout",
|
||||
"NO_PREVIOUS_WORKOUT": "No previous workout",
|
||||
@ -24,6 +27,7 @@
|
||||
"RECORD_FD": "Farest distance",
|
||||
"RECORD_LD": "Longest duration",
|
||||
"RECORD_MS": "Max. speed",
|
||||
"SPEED": "speed",
|
||||
"SPORT": "sport | sports",
|
||||
"START": "start",
|
||||
"TOTAL_DURATION": "total duration",
|
||||
|
@ -1,10 +1,12 @@
|
||||
{
|
||||
"ADD_WORKOUT": "Ajouter une séance",
|
||||
"ANALYSIS": "analyse",
|
||||
"ASCENT": "dénivelé positif",
|
||||
"AVERAGE_SPEED": "vitesse moyenne",
|
||||
"DESCENT": "dénivelé négatif",
|
||||
"DISTANCE": "distance",
|
||||
"DURATION": "durée",
|
||||
"ELEVATION": "altitude",
|
||||
"END": "fin",
|
||||
"KM": "km",
|
||||
"LATEST_WORKOUTS": "Séances récentes",
|
||||
@ -12,6 +14,7 @@
|
||||
"MAX_SPEED": "vitesse max",
|
||||
"MIN_ALTITUDE": "altitude min",
|
||||
"NEXT_WORKOUT": "Séance suivante",
|
||||
"NO_DATA_CLEANING": "données issues du fichier gpx, sans correction",
|
||||
"NO_MAP": "Pas de carte",
|
||||
"NO_NEXT_WORKOUT": "Pas de séance suivante",
|
||||
"NO_PREVIOUS_WORKOUT": "Pas de séances précédente",
|
||||
@ -24,6 +27,7 @@
|
||||
"RECORD_FD": "Distance la + longue",
|
||||
"RECORD_LD": "Durée la + longue",
|
||||
"RECORD_MS": "Vitesse max.",
|
||||
"SPEED": "vitesse",
|
||||
"SPORT": "sport | sports",
|
||||
"START": "début",
|
||||
"TOTAL_DURATION": "durée totale",
|
||||
|
@ -64,6 +64,14 @@ export const actions: ActionTree<IWorkoutsState, IRootState> &
|
||||
res.data.data.workouts[0]
|
||||
)
|
||||
if (res.data.data.workouts[0].with_gpx) {
|
||||
authApi.get(`workouts/${workoutId}/chart_data`).then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
context.commit(
|
||||
WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_CHART_DATA,
|
||||
res.data.data.chart_data
|
||||
)
|
||||
}
|
||||
})
|
||||
authApi.get(`workouts/${workoutId}/gpx`).then((res) => {
|
||||
if (res.data.status === 'success') {
|
||||
context.commit(
|
||||
|
@ -17,5 +17,6 @@ export enum WorkoutsMutations {
|
||||
SET_USER_WORKOUTS = 'SET_USER_WORKOUTS',
|
||||
SET_WORKOUT = 'SET_WORKOUT',
|
||||
SET_WORKOUT_GPX = 'SET_WORKOUT_GPX',
|
||||
SET_WORKOUT_CHART_DATA = 'SET_WORKOUT_CHART_DATA',
|
||||
SET_WORKOUT_LOADING = 'SET_WORKOUT_LOADING',
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { MutationTree } from 'vuex'
|
||||
|
||||
import { WORKOUTS_STORE } from '@/store/constants'
|
||||
import { initialWorkoutValue } from '@/store/modules/workouts/state'
|
||||
import {
|
||||
IWorkoutsState,
|
||||
TWorkoutsMutations,
|
||||
} from '@/store/modules/workouts/types'
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { IWorkout, IWorkoutApiChartData } from '@/types/workouts'
|
||||
|
||||
export const mutations: MutationTree<IWorkoutsState> & TWorkoutsMutations = {
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_CALENDAR_WORKOUTS](
|
||||
@ -26,11 +27,11 @@ export const mutations: MutationTree<IWorkoutsState> & TWorkoutsMutations = {
|
||||
) {
|
||||
state.workout.workout = workout
|
||||
},
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_LOADING](
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_CHART_DATA](
|
||||
state: IWorkoutsState,
|
||||
loading: boolean
|
||||
chartData: IWorkoutApiChartData[]
|
||||
) {
|
||||
state.workout.loading = loading
|
||||
state.workout.chartData = chartData
|
||||
},
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_GPX](
|
||||
state: IWorkoutsState,
|
||||
@ -38,15 +39,17 @@ export const mutations: MutationTree<IWorkoutsState> & TWorkoutsMutations = {
|
||||
) {
|
||||
state.workout.gpx = gpx
|
||||
},
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_LOADING](
|
||||
state: IWorkoutsState,
|
||||
loading: boolean
|
||||
) {
|
||||
state.workout.loading = loading
|
||||
},
|
||||
[WORKOUTS_STORE.MUTATIONS.EMPTY_WORKOUTS](state: IWorkoutsState) {
|
||||
state.calendar_workouts = []
|
||||
state.user_workouts = []
|
||||
},
|
||||
[WORKOUTS_STORE.MUTATIONS.EMPTY_WORKOUT](state: IWorkoutsState) {
|
||||
state.workout = {
|
||||
gpx: '',
|
||||
loading: false,
|
||||
workout: <IWorkout>{},
|
||||
}
|
||||
state.workout = initialWorkoutValue
|
||||
},
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import { IWorkoutsState } from '@/store/modules/workouts/types'
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
|
||||
export const initialWorkoutValue = {
|
||||
gpx: '',
|
||||
loading: false,
|
||||
workout: <IWorkout>{},
|
||||
chartData: [],
|
||||
}
|
||||
|
||||
export const workoutsState: IWorkoutsState = {
|
||||
calendar_workouts: [],
|
||||
user_workouts: [],
|
||||
workout: {
|
||||
gpx: '',
|
||||
loading: false,
|
||||
workout: <IWorkout>{},
|
||||
},
|
||||
workout: initialWorkoutValue,
|
||||
}
|
||||
|
@ -7,7 +7,12 @@ import {
|
||||
|
||||
import { WORKOUTS_STORE } from '@/store/constants'
|
||||
import { IRootState } from '@/store/modules/root/types'
|
||||
import { IWorkout, IWorkoutsPayload, IWorkoutState } from '@/types/workouts'
|
||||
import {
|
||||
IWorkout,
|
||||
IWorkoutApiChartData,
|
||||
IWorkoutsPayload,
|
||||
IWorkoutState,
|
||||
} from '@/types/workouts'
|
||||
|
||||
export interface IWorkoutsState {
|
||||
user_workouts: IWorkout[]
|
||||
@ -46,6 +51,10 @@ export type TWorkoutsMutations<S = IWorkoutsState> = {
|
||||
workouts: IWorkout[]
|
||||
): void
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT](state: S, workout: IWorkout): void
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_CHART_DATA](
|
||||
state: S,
|
||||
chartDate: IWorkoutApiChartData[]
|
||||
): void
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_GPX](state: S, gpx: string): void
|
||||
[WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_LOADING](
|
||||
state: S,
|
||||
|
@ -1,5 +1,9 @@
|
||||
export interface IChartDataset {
|
||||
label: string
|
||||
backgroundColor: string[]
|
||||
borderColor?: string[]
|
||||
borderWidth?: number
|
||||
fill?: boolean
|
||||
data: number[]
|
||||
yAxisID?: string
|
||||
}
|
||||
|
@ -18,10 +18,13 @@ export interface IStatisticsDateParams {
|
||||
end: Date
|
||||
}
|
||||
|
||||
export type TDatasetKeys = 'nb_workouts' | 'total_duration' | 'total_distance'
|
||||
export type TStatisticsDatasetKeys =
|
||||
| 'nb_workouts'
|
||||
| 'total_duration'
|
||||
| 'total_distance'
|
||||
|
||||
export type TStatistics = {
|
||||
[key in TDatasetKeys]: number
|
||||
[key in TStatisticsDatasetKeys]: number
|
||||
}
|
||||
|
||||
export type TSportStatistics = {
|
||||
@ -33,7 +36,7 @@ export type TStatisticsFromApi = {
|
||||
}
|
||||
|
||||
export type TStatisticsDatasets = {
|
||||
[key in TDatasetKeys]: IChartDataset[]
|
||||
[key in TStatisticsDatasetKeys]: IChartDataset[]
|
||||
}
|
||||
|
||||
export interface IStatisticsChartData {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { IChartDataset } from '@/types/chart'
|
||||
|
||||
export interface IWorkoutSegment {
|
||||
ascent: number
|
||||
ave_speed: number
|
||||
@ -79,8 +81,31 @@ export interface IWorkoutsPayload {
|
||||
page?: number
|
||||
}
|
||||
|
||||
export interface IWorkoutApiChartData {
|
||||
distance: number
|
||||
duration: number
|
||||
elevation: number
|
||||
latitude: number
|
||||
longitude: number
|
||||
speed: number
|
||||
time: string
|
||||
}
|
||||
|
||||
export interface IWorkoutState {
|
||||
gpx: string
|
||||
loading: boolean
|
||||
workout: IWorkout
|
||||
chartData: IWorkoutApiChartData[]
|
||||
}
|
||||
|
||||
export type TWorkoutDatasetKeys = 'speed' | 'elevation'
|
||||
|
||||
export type TWorkoutDatasets = {
|
||||
[key in TWorkoutDatasetKeys]: IChartDataset
|
||||
}
|
||||
|
||||
export interface IWorkoutChartData {
|
||||
distance_labels: unknown[]
|
||||
duration_labels: unknown[]
|
||||
datasets: TWorkoutDatasets
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { ISport } from '@/types/sports'
|
||||
import {
|
||||
IStatisticsChartData,
|
||||
IStatisticsDateParams,
|
||||
TDatasetKeys,
|
||||
TStatisticsDatasetKeys,
|
||||
TStatisticsDatasets,
|
||||
TStatisticsFromApi,
|
||||
} from '@/types/statistics'
|
||||
@ -19,7 +19,7 @@ const dateFormats: Record<string, string> = {
|
||||
year: 'yyyy',
|
||||
}
|
||||
|
||||
export const datasetKeys: TDatasetKeys[] = [
|
||||
export const datasetKeys: TStatisticsDatasetKeys[] = [
|
||||
'nb_workouts',
|
||||
'total_duration',
|
||||
'total_distance',
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { TDatasetKeys } from '@/types/statistics'
|
||||
import { TStatisticsDatasetKeys } from '@/types/statistics'
|
||||
import { formatDuration } from '@/utils/duration'
|
||||
|
||||
export const formatTooltipValue = (
|
||||
displayedData: TDatasetKeys,
|
||||
displayedData: TStatisticsDatasetKeys,
|
||||
value: number,
|
||||
formatWithUnits = true
|
||||
): string => {
|
||||
|
41
fittrackee_client/src/utils/workouts.ts
Normal file
41
fittrackee_client/src/utils/workouts.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {
|
||||
IWorkoutApiChartData,
|
||||
IWorkoutChartData,
|
||||
TWorkoutDatasets,
|
||||
} from '@/types/workouts'
|
||||
|
||||
export const getDatasets = (
|
||||
chartData: IWorkoutApiChartData[],
|
||||
t: CallableFunction
|
||||
): IWorkoutChartData => {
|
||||
const datasets: TWorkoutDatasets = {
|
||||
speed: {
|
||||
label: t('workouts.SPEED'),
|
||||
backgroundColor: ['#FFFFFF'],
|
||||
borderColor: ['#8884d8'],
|
||||
borderWidth: 2,
|
||||
data: [],
|
||||
yAxisID: 'ySpeed',
|
||||
},
|
||||
elevation: {
|
||||
label: t('workouts.ELEVATION'),
|
||||
backgroundColor: ['#e5e5e5'],
|
||||
borderColor: ['#cccccc'],
|
||||
borderWidth: 1,
|
||||
fill: true,
|
||||
data: [],
|
||||
yAxisID: 'yElevation',
|
||||
},
|
||||
}
|
||||
const distance_labels: unknown[] = []
|
||||
const duration_labels: unknown[] = []
|
||||
|
||||
chartData.map((data) => {
|
||||
distance_labels.push(data.distance)
|
||||
duration_labels.push(data.duration)
|
||||
datasets.speed.data.push(data.speed)
|
||||
datasets.elevation.data.push(data.elevation)
|
||||
})
|
||||
|
||||
return { distance_labels, duration_labels, datasets }
|
||||
}
|
@ -14,6 +14,11 @@
|
||||
:sports="sports"
|
||||
:authUser="authUser"
|
||||
/>
|
||||
<WorkoutChart
|
||||
v-if="workout.chartData.length > 0"
|
||||
:workout="workout"
|
||||
:authUser="authUser"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<NotFound target="WORKOUT" />
|
||||
@ -35,6 +40,7 @@
|
||||
|
||||
import Loader from '@/components/Common/Loader.vue'
|
||||
import NotFound from '@/components/Common/NotFound.vue'
|
||||
import WorkoutChart from '@/components/Workout/WorkoutChart/index.vue'
|
||||
import WorkoutDetail from '@/components/Workout/WorkoutDetail/index.vue'
|
||||
import { SPORTS_STORE, USER_STORE, WORKOUTS_STORE } from '@/store/constants'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
@ -46,6 +52,7 @@
|
||||
components: {
|
||||
Loader,
|
||||
NotFound,
|
||||
WorkoutChart,
|
||||
WorkoutDetail,
|
||||
},
|
||||
setup() {
|
||||
@ -78,6 +85,7 @@
|
||||
@import '~@/scss/base';
|
||||
#workout {
|
||||
display: flex;
|
||||
margin-bottom: 45px;
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
|
109
fittrackee_client/tests/unit/utils/workouts.spec.ts
Normal file
109
fittrackee_client/tests/unit/utils/workouts.spec.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import createI18n from '@/i18n'
|
||||
import { getDatasets } from '@/utils/workouts'
|
||||
|
||||
const { t, locale } = createI18n.global
|
||||
|
||||
describe('getDatasets', () => {
|
||||
const testparams = [
|
||||
{
|
||||
description:
|
||||
'returns empty datasets when no chart data w/ french translation',
|
||||
inputParams: {
|
||||
charData: [],
|
||||
locale: 'fr',
|
||||
},
|
||||
expected: {
|
||||
distance_labels: [],
|
||||
duration_labels: [],
|
||||
datasets: {
|
||||
speed: {
|
||||
label: 'vitesse',
|
||||
backgroundColor: ['#FFFFFF'],
|
||||
borderColor: ['#8884d8'],
|
||||
borderWidth: 2,
|
||||
data: [],
|
||||
yAxisID: 'ySpeed',
|
||||
},
|
||||
elevation: {
|
||||
label: 'altitude',
|
||||
backgroundColor: ['#e5e5e5'],
|
||||
borderColor: ['#cccccc'],
|
||||
borderWidth: 1,
|
||||
fill: true,
|
||||
data: [],
|
||||
yAxisID: 'yElevation',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'returns datasets w/ english translation',
|
||||
inputParams: {
|
||||
charData: [
|
||||
{
|
||||
distance: 0,
|
||||
duration: 0,
|
||||
elevation: 83.6,
|
||||
latitude: 48.845574,
|
||||
longitude: 2.373723,
|
||||
speed: 2.89,
|
||||
time: 'Sun, 12 Sep 2021 13:29:24 GMT',
|
||||
},
|
||||
{
|
||||
distance: 0,
|
||||
duration: 1,
|
||||
elevation: 83.7,
|
||||
latitude: 48.845578,
|
||||
longitude: 2.373732,
|
||||
speed: 1.56,
|
||||
time: 'Sun, 12 Sep 2021 13:29:25 GMT',
|
||||
},
|
||||
{
|
||||
distance: 0.01,
|
||||
duration: 96,
|
||||
elevation: 84.3,
|
||||
latitude: 48.845591,
|
||||
longitude: 2.373811,
|
||||
speed: 14.73,
|
||||
time: 'Sun, 12 Sep 2021 13:31:00 GMT',
|
||||
},
|
||||
],
|
||||
locale: 'en',
|
||||
},
|
||||
expected: {
|
||||
distance_labels: [0, 0, 0.01],
|
||||
duration_labels: [0, 1, 96],
|
||||
datasets: {
|
||||
speed: {
|
||||
label: 'speed',
|
||||
backgroundColor: ['#FFFFFF'],
|
||||
borderColor: ['#8884d8'],
|
||||
borderWidth: 2,
|
||||
data: [2.89, 1.56, 14.73],
|
||||
yAxisID: 'ySpeed',
|
||||
},
|
||||
elevation: {
|
||||
label: 'elevation',
|
||||
backgroundColor: ['#e5e5e5'],
|
||||
borderColor: ['#cccccc'],
|
||||
borderWidth: 1,
|
||||
fill: true,
|
||||
data: [83.6, 83.7, 84.3],
|
||||
yAxisID: 'yElevation',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
testparams.map((testParams) => {
|
||||
it(testParams.description, () => {
|
||||
locale.value = testParams.inputParams.locale
|
||||
assert.deepEqual(
|
||||
getDatasets(testParams.inputParams.charData, t),
|
||||
testParams.expected
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user