Client - display segment

This commit is contained in:
Sam
2021-09-27 10:01:17 +02:00
parent de32c136b6
commit b7b2eb0daf
12 changed files with 413 additions and 110 deletions

View File

@ -2,44 +2,48 @@
<div id="workout-info">
<div class="workout-data">
<i class="fa fa-clock-o" aria-hidden="true" />
{{ t('workouts.DURATION') }}: <span>{{ workout.moving }}</span>
<WorkoutRecord :workout="workout" record_type="LD" />
{{ t('workouts.DURATION') }}: <span>{{ workoutObject.moving }}</span>
<WorkoutRecord :workoutObject="workoutObject" record_type="LD" />
<div v-if="withPause">
({{ t('workouts.PAUSES') }}: <span>{{ workout.pauses }}</span> -
{{ t('workouts.TOTAL_DURATION') }}: <span>{{ workout.duration }})</span>
({{ t('workouts.PAUSES') }}: <span>{{ workoutObject.pauses }}</span> -
{{ t('workouts.TOTAL_DURATION') }}:
<span>{{ workoutObject.duration }})</span>
</div>
</div>
<div class="workout-data">
<i class="fa fa-road" aria-hidden="true" />
{{ t('workouts.DISTANCE') }}: <span>{{ workout.distance }} km</span>
<WorkoutRecord :workout="workout" record_type="FD" />
{{ t('workouts.DISTANCE') }}: <span>{{ workoutObject.distance }} km</span>
<WorkoutRecord :workoutObject="workoutObject" record_type="FD" />
</div>
<div class="workout-data">
<i class="fa fa-tachometer" aria-hidden="true" />
{{ t('workouts.AVERAGE_SPEED') }}:
<span>{{ workout.ave_speed }} km/h</span
><WorkoutRecord :workout="workout" record_type="AS" /><br />
{{ t('workouts.MAX_SPEED') }}: <span>{{ workout.max_speed }} km/h</span>
<WorkoutRecord :workout="workout" record_type="MS" />
<span>{{ workoutObject.aveSpeed }} km/h</span
><WorkoutRecord :workoutObject="workoutObject" record_type="AS" /><br />
{{ t('workouts.MAX_SPEED') }}:
<span>{{ workoutObject.maxSpeed }} km/h</span>
<WorkoutRecord :workoutObject="workoutObject" record_type="MS" />
</div>
<div
class="workout-data"
v-if="workout.max_alt !== null && workout.min_alt !== null"
v-if="workoutObject.maxAlt !== null && workoutObject.minAlt !== null"
>
<img class="mountains" src="/img/misc/mountains.svg" />
{{ t('workouts.MIN_ALTITUDE') }}: <span>{{ workout.min_alt }} m</span
{{ t('workouts.MIN_ALTITUDE') }}: <span>{{ workoutObject.minAlt }} m</span
><br />
{{ t('workouts.MAX_ALTITUDE') }}: <span>{{ workout.max_alt }} m</span>
{{ t('workouts.MAX_ALTITUDE') }}:
<span>{{ workoutObject.maxAlt }} m</span>
</div>
<div
class="workout-data"
v-if="workout.ascent !== null && workout.descent !== null"
v-if="workoutObject.ascent !== null && workoutObject.descent !== null"
>
<i class="fa fa-location-arrow" aria-hidden="true" />
{{ t('workouts.ASCENT') }}: <span>{{ workout.ascent }} m</span><br />
{{ t('workouts.DESCENT') }}: <span>{{ workout.descent }} m</span>
{{ t('workouts.ASCENT') }}: <span>{{ workoutObject.ascent }} m</span
><br />
{{ t('workouts.DESCENT') }}: <span>{{ workoutObject.descent }} m</span>
</div>
<WorkoutWeather :workout="workout" />
<WorkoutWeather :workoutObject="workoutObject" />
</div>
</template>
@ -49,7 +53,7 @@
import WorkoutRecord from '@/components/Workout/WorkoutDetail/WorkoutRecord.vue'
import WorkoutWeather from '@/components/Workout/WorkoutDetail/WorkoutWeather.vue'
import { IWorkout } from '@/types/workouts'
import { IWorkoutObject } from '@/types/workouts'
export default defineComponent({
name: 'WorkoutData',
components: {
@ -57,8 +61,8 @@
WorkoutWeather,
},
props: {
workout: {
type: Object as PropType<IWorkout>,
workoutObject: {
type: Object as PropType<IWorkoutObject>,
required: true,
},
},
@ -67,7 +71,8 @@
return {
withPause: computed(
() =>
props.workout.pauses !== '0:00:00' && props.workout.pauses !== null
props.workoutObject.pauses !== '0:00:00' &&
props.workoutObject.pauses !== null
),
t,
}

View File

@ -2,8 +2,8 @@
<span
class="workout-record"
v-if="
workout.records &&
workout.records.find((record) => record.record_type === record_type)
workoutObject.records &&
workoutObject.records.find((record) => record.record_type === record_type)
"
>
<sup>
@ -15,12 +15,12 @@
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { IWorkout } from '@/types/workouts'
import { IWorkoutObject } from '@/types/workouts'
export default defineComponent({
name: 'WorkoutRecord',
props: {
workout: {
type: Object as PropType<IWorkout>,
workoutObject: {
type: Object as PropType<IWorkoutObject>,
required: true,
},
record_type: {

View File

@ -1,5 +1,8 @@
<template>
<div id="workout-weather" v-if="workout.weather_start && workout.weather_end">
<div
id="workout-weather"
v-if="workoutObject.weatherStart && workoutObject.weatherEnd"
>
<table class="weather-table">
<thead>
<tr>
@ -9,12 +12,16 @@
{{ t('workouts.START') }}
<img
class="weather-img"
:src="`/img/weather/${workout.weather_start.icon}.svg`"
:src="`/img/weather/${workoutObject.weatherStart.icon}.svg`"
:alt="
t(`workouts.WEATHER.DARK_SKY.${workout.weather_start.icon}`)
t(
`workouts.WEATHER.DARK_SKY.${workoutObject.weatherStart.icon}`
)
"
:title="
t(`workouts.WEATHER.DARK_SKY.${workout.weather_start.icon}`)
t(
`workouts.WEATHER.DARK_SKY.${workoutObject.weatherStart.icon}`
)
"
/>
</div>
@ -24,12 +31,16 @@
{{ t('workouts.END') }}
<img
class="weather-img"
:src="`/img/weather/${workout.weather_end.icon}.svg`"
:src="`/img/weather/${workoutObject.weatherEnd.icon}.svg`"
:alt="
t(`workouts.WEATHER.DARK_SKY.${workout.weather_end.icon}`)
t(
`workouts.WEATHER.DARK_SKY.${workoutObject.weatherEnd.icon}`
)
"
:title="
t(`workouts.WEATHER.DARK_SKY.${workout.weather_end.icon}`)
t(
`workouts.WEATHER.DARK_SKY.${workoutObject.weatherEnd.icon}`
)
"
/>
</div>
@ -46,8 +57,12 @@
:title="t(`workouts.WEATHER.TEMPERATURE`)"
/>
</td>
<td>{{ Number(workout.weather_start.temperature).toFixed(1) }}°C</td>
<td>{{ Number(workout.weather_end.temperature).toFixed(1) }}°C</td>
<td>
{{ Number(workoutObject.weatherStart.temperature).toFixed(1) }}°C
</td>
<td>
{{ Number(workoutObject.weatherEnd.temperature).toFixed(1) }}°C
</td>
</tr>
<tr>
<td>
@ -59,9 +74,11 @@
/>
</td>
<td>
{{ Number(workout.weather_start.humidity * 100).toFixed(1) }}%
{{ Number(workoutObject.weatherStart.humidity * 100).toFixed(1) }}%
</td>
<td>
{{ Number(workoutObject.weatherEnd.humidity * 100).toFixed(1) }}%
</td>
<td>{{ Number(workout.weather_end.humidity * 100).toFixed(1) }}%</td>
</tr>
<tr>
<td>
@ -72,8 +89,8 @@
:title="t(`workouts.WEATHER.WIND`)"
/>
</td>
<td>{{ Number(workout.weather_start.wind).toFixed(1) }}m/s</td>
<td>{{ Number(workout.weather_end.wind).toFixed(1) }}m/s</td>
<td>{{ Number(workoutObject.weatherStart.wind).toFixed(1) }}m/s</td>
<td>{{ Number(workoutObject.weatherEnd.wind).toFixed(1) }}m/s</td>
</tr>
</tbody>
</table>
@ -84,12 +101,12 @@
import { defineComponent, PropType } from 'vue'
import { useI18n } from 'vue-i18n'
import { IWorkout } from '@/types/workouts'
import { IWorkoutObject } from '@/types/workouts'
export default defineComponent({
name: 'WorkoutWeather',
props: {
workout: {
type: Object as PropType<IWorkout>,
workoutObject: {
type: Object as PropType<IWorkoutObject>,
required: true,
},
},

View File

@ -4,18 +4,15 @@
<template #title>
<div
class="workout-previous workout-arrow"
:class="{ inactive: !workoutData.workout.previous_workout }"
:class="{ inactive: !workoutObject.previousUrl }"
:title="
workoutData.workout.previous_workout
? t('workouts.PREVIOUS_WORKOUT')
: t('workouts.NO_PREVIOUS_WORKOUT')
workoutObject.previousUrl
? t(`workouts.PREVIOUS_${workoutObject.type}`)
: t(`workouts.NO_PREVIOUS_${workoutObject.type}`)
"
@click="
workoutData.workout.previous_workout
? $router.push({
name: 'Workout',
params: { workoutId: workoutData.workout.previous_workout },
})
workoutObject.previousUrl
? $router.push(workoutObject.previousUrl)
: null
"
>
@ -26,27 +23,41 @@
<img alt="workout sport logo" :src="sport.img" />
</div>
<div class="workout-title-date">
<div class="workout-title">{{ workoutData.workout.title }}</div>
<div class="workout-title" v-if="workoutObject.type === 'WORKOUT'">
{{ workoutObject.title }}
</div>
<div class="workout-title" v-else>
<router-link
:to="{
name: 'Workout',
params: { workoutId: workoutObject.workoutId },
}"
>
{{ workoutObject.title }}
</router-link>
<span class="workout-segment">
<i class="fa fa-map-marker" aria-hidden="true" />
{{ t('workouts.SEGMENT') }}
{{ workoutObject.segmentId + 1 }}
</span>
</div>
<div class="workout-date">
{{ workoutDate.workout_date }} - {{ workoutDate.workout_time }}
{{ workoutObject.workoutDate }} -
{{ workoutObject.workoutTime }}
</div>
</div>
</div>
<div
class="workout-next workout-arrow"
:class="{ inactive: !workoutData.workout.next_workout }"
:class="{ inactive: !workoutObject.nextUrl }"
:title="
workoutData.workout.next_workout
? t('workouts.NEXT_WORKOUT')
: t('workouts.NO_NEXT_WORKOUT')
workoutObject.nextUrl
? t(`workouts.NEXT_${workoutObject.type}`)
: t(`workouts.NO_NEXT_${workoutObject.type}`)
"
@click="
workoutData.workout.next_workout
? $router.push({
name: 'Workout',
params: { workoutId: workoutData.workout.next_workout },
})
: null
workoutObject.nextUrl ? $router.push(workoutObject.nextUrl) : null
"
>
<i class="fa fa-chevron-right" aria-hidden="true" />
@ -57,25 +68,37 @@
:workoutData="workoutData"
:markerCoordinates="markerCoordinates"
/>
<WorkoutData :workout="workoutData.workout" />
<WorkoutData :workoutObject="workoutObject" />
</template>
</Card>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent, computed, watch } from 'vue'
import {
ComputedRef,
PropType,
Ref,
defineComponent,
computed,
ref,
watch,
} from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import Card from '@/components/Common/Card.vue'
import WorkoutData from '@/components/Workout/WorkoutDetail/WorkoutData.vue'
import WorkoutMap from '@/components/Workout/WorkoutDetail/WorkoutMap.vue'
import { WORKOUTS_STORE } from '@/store/constants'
import { ISport } from '@/types/sports'
import { IAuthUserProfile } from '@/types/user'
import { IWorkoutData, TCoordinates } from '@/types/workouts'
import { useStore } from '@/use/useStore'
import {
IWorkout,
IWorkoutData,
IWorkoutObject,
IWorkoutSegment,
TCoordinates,
} from '@/types/workouts'
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
export default defineComponent({
@ -101,17 +124,98 @@
type: Object as PropType<IWorkoutData>,
required: true,
},
displaySegment: {
type: Boolean,
required: true,
},
},
setup(props) {
const route = useRoute()
const store = useStore()
const { t } = useI18n()
function getWorkoutObjectUrl(
workout: IWorkout,
displaySegment: boolean,
segmentId: number | null
): Record<string, string | null> {
const previousUrl =
displaySegment && segmentId && segmentId !== 1
? `/workouts/${workout.id}/segment/${segmentId - 1}`
: !displaySegment && workout.previous_workout
? `/workouts/${workout.previous_workout}`
: null
const nextUrl =
displaySegment && segmentId && segmentId < workout.segments.length
? `/workouts/${workout.id}/segment/${segmentId + 1}`
: !displaySegment && workout.next_workout
? `/workouts/${workout.next_workout}`
: null
return {
previousUrl,
nextUrl,
}
}
function getWorkoutObject(
workout: IWorkout,
segment: IWorkoutSegment | null
): IWorkoutObject {
const urls = getWorkoutObjectUrl(
workout,
props.displaySegment,
segmentId.value ? +segmentId.value : null
)
const workoutDate = formatWorkoutDate(
getDateWithTZ(
props.workoutData.workout.workout_date,
props.authUser.timezone
)
)
return {
ascent: segment ? segment.ascent : workout.ascent,
aveSpeed: segment ? segment.ave_speed : workout.ave_speed,
distance: segment ? segment.distance : workout.distance,
descent: segment ? segment.descent : workout.descent,
duration: segment ? segment.duration : workout.duration,
maxAlt: segment ? segment.max_alt : workout.max_alt,
maxSpeed: segment ? segment.max_speed : workout.max_speed,
minAlt: segment ? segment.min_alt : workout.min_alt,
moving: segment ? segment.moving : workout.moving,
nextUrl: urls.nextUrl,
pauses: segment ? segment.pauses : workout.pauses,
previousUrl: urls.previousUrl,
records: segment ? [] : workout.records,
segmentId: segment ? segment.segment_id : null,
title: workout.title,
type: props.displaySegment ? 'SEGMENT' : 'WORKOUT',
workoutDate: workoutDate.workout_date,
weatherEnd: segment ? null : workout.weather_end,
workoutId: workout.id,
weatherStart: segment ? null : workout.weather_start,
workoutTime: workoutDate.workout_time,
}
}
const workout: ComputedRef<IWorkout> = computed(
() => props.workoutData.workout
)
let segmentId: Ref<number | null> = ref(
route.params.workoutId ? +route.params.segmentId : null
)
const segment: ComputedRef<IWorkoutSegment | null> = computed(() =>
workout.value.segments.length > 0 && segmentId.value
? workout.value.segments[+segmentId.value - 1]
: null
)
watch(
() => route.params.workoutId,
async (newWorkoutId) => {
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_WORKOUT_DATA, newWorkoutId)
() => route.params.segmentId,
async (newSegmentId) => {
if (newSegmentId) {
segmentId.value = +newSegmentId
}
}
)
return {
sport: computed(() =>
props.sports
@ -120,13 +224,8 @@
)
: {}
),
workoutDate: computed(() =>
formatWorkoutDate(
getDateWithTZ(
props.workoutData.workout.workout_date,
props.authUser.timezone
)
)
workoutObject: computed(() =>
getWorkoutObject(workout.value, segment.value)
),
t,
}
@ -166,6 +265,10 @@
font-size: 0.8em;
font-weight: normal;
}
.workout-segment {
font-style: italic;
font-weight: normal;
}
}
}
.card-content {

View File

@ -0,0 +1,72 @@
<template>
<div id="workout-segments">
<Card :without-title="false">
<template #title>{{ t('workouts.SEGMENT', 2) }}</template>
<template #content>
<ul>
<li v-for="(segment, index) in segments" :key="segment.segment_id">
<router-link
:to="{
name: 'WorkoutSegment',
params: {
workoutId: segment.workout_id,
segmentId: index + 1,
},
}"
>{{ t('workouts.SEGMENT', 1) }} {{ index + 1 }}</router-link
>
({{ t('workouts.DISTANCE') }}: {{ segment.distance }} km,
{{ t('workouts.DURATION') }}: {{ segment.duration }})
</li>
</ul>
</template>
</Card>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { useI18n } from 'vue-i18n'
import Card from '@/components/Common/Card.vue'
import { IWorkoutSegment } from '@/types/workouts'
export default defineComponent({
name: 'WorkoutSegments',
components: {
Card,
},
props: {
segments: {
type: Object as PropType<IWorkoutSegment[]>,
required: true,
},
},
setup() {
const { t } = useI18n()
return { t }
},
})
</script>
<style lang="scss" scoped>
@import '~@/scss/base';
#workout-segments {
::v-deep(.card) {
.card-title {
text-transform: capitalize;
}
.card-content {
padding-bottom: 0;
padding-top: 0;
a {
font-weight: bold;
}
ul {
padding: 0 $default-padding;
list-style: square;
}
}
}
}
</style>