Client - refactoring & display fix
This commit is contained in:
parent
b7b2eb0daf
commit
42c16a9680
@ -38,7 +38,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartData, ChartOptions } from 'chart.js'
|
||||
import { PropType, defineComponent, ref, ComputedRef, computed } from 'vue'
|
||||
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
|
||||
import { LineChart, useLineChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@ -70,6 +70,7 @@
|
||||
emits: ['getCoordinates'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.chartData, t)
|
||||
@ -90,6 +91,7 @@
|
||||
)
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
@ -221,7 +223,6 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
padding: 0 $default-padding;
|
||||
}
|
||||
@ -231,6 +232,15 @@
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-limit) {
|
||||
.card-content {
|
||||
padding: $default-padding 0;
|
||||
.no-data-cleaning {
|
||||
padding: 0 $default-padding * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div id="workout-card-title">
|
||||
<div
|
||||
class="workout-previous workout-arrow"
|
||||
:class="{ inactive: !workoutObject.previousUrl }"
|
||||
:title="
|
||||
workoutObject.previousUrl
|
||||
? t(`workouts.PREVIOUS_${workoutObject.type}`)
|
||||
: t(`workouts.NO_PREVIOUS_${workoutObject.type}`)
|
||||
"
|
||||
@click="
|
||||
workoutObject.previousUrl
|
||||
? $router.push(workoutObject.previousUrl)
|
||||
: null
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="workout-card-title">
|
||||
<div class="sport-img">
|
||||
<img alt="workout sport logo" :src="sport.img" />
|
||||
</div>
|
||||
<div class="workout-title-date">
|
||||
<div class="workout-title" v-if="workoutObject.type === 'WORKOUT'">
|
||||
{{ workoutObject.title }}
|
||||
</div>
|
||||
<div class="workout-title" v-else>
|
||||
{{ workoutObject.title }}
|
||||
<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">
|
||||
{{ workoutObject.workoutDate }} -
|
||||
{{ workoutObject.workoutTime }}
|
||||
<span class="workout-link">
|
||||
<router-link
|
||||
v-if="workoutObject.type === 'SEGMENT'"
|
||||
:to="{
|
||||
name: 'Workout',
|
||||
params: { workoutId: workoutObject.workoutId },
|
||||
}"
|
||||
>
|
||||
> {{ t('workouts.BACK_TO_WORKOUT') }}
|
||||
</router-link></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="workout-next workout-arrow"
|
||||
:class="{ inactive: !workoutObject.nextUrl }"
|
||||
:title="
|
||||
workoutObject.nextUrl
|
||||
? t(`workouts.NEXT_${workoutObject.type}`)
|
||||
: t(`workouts.NO_NEXT_${workoutObject.type}`)
|
||||
"
|
||||
@click="
|
||||
workoutObject.nextUrl ? $router.push(workoutObject.nextUrl) : null
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutCardTitle',
|
||||
props: {
|
||||
sport: {
|
||||
type: Object as PropType<ISport>,
|
||||
required: true,
|
||||
},
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
return { t }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base';
|
||||
|
||||
#workout-card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.workout-arrow {
|
||||
cursor: pointer;
|
||||
&.inactive {
|
||||
color: var(--disabled-color);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.workout-card-title {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
.sport-img {
|
||||
img {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
padding: 0 $default-padding;
|
||||
}
|
||||
}
|
||||
.workout-date {
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
}
|
||||
.workout-segment {
|
||||
font-weight: normal;
|
||||
}
|
||||
.workout-link {
|
||||
padding-left: $default-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -48,12 +48,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, computed } from 'vue'
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import WorkoutRecord from '@/components/Workout/WorkoutDetail/WorkoutRecord.vue'
|
||||
import WorkoutWeather from '@/components/Workout/WorkoutDetail/WorkoutWeather.vue'
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutData',
|
||||
components: {
|
||||
|
@ -1,34 +1,30 @@
|
||||
<template>
|
||||
<div id="workout-map">
|
||||
<div
|
||||
class="leaflet-container"
|
||||
v-if="
|
||||
workoutData.workout.with_gpx &&
|
||||
geoJson.jsonData &&
|
||||
center &&
|
||||
bounds.length === 2
|
||||
"
|
||||
>
|
||||
<LMap
|
||||
:zoom="options.zoom"
|
||||
:center="center"
|
||||
:bounds="bounds"
|
||||
ref="workoutMap"
|
||||
@ready="fitBounds(bounds)"
|
||||
>
|
||||
<LTileLayer
|
||||
:url="`${getApiUrl()}workouts/map_tile/{s}/{z}/{x}/{y}.png`"
|
||||
:attribution="appConfig.map_attribution"
|
||||
<div v-if="workoutData.loading" class="leaflet-container" />
|
||||
<div v-else>
|
||||
<div class="leaflet-container" v-if="workoutData.workout.with_gpx">
|
||||
<LMap
|
||||
v-if="geoJson.jsonData && center && bounds.length === 2"
|
||||
:zoom="options.zoom"
|
||||
:center="center"
|
||||
:bounds="bounds"
|
||||
/>
|
||||
<LGeoJson :geojson="geoJson.jsonData" />
|
||||
<LMarker
|
||||
v-if="markerCoordinates.latitude"
|
||||
:lat-lng="[markerCoordinates.latitude, markerCoordinates.longitude]"
|
||||
/>
|
||||
</LMap>
|
||||
ref="workoutMap"
|
||||
@ready="fitBounds(bounds)"
|
||||
>
|
||||
<LTileLayer
|
||||
:url="`${getApiUrl()}workouts/map_tile/{s}/{z}/{x}/{y}.png`"
|
||||
:attribution="appConfig.map_attribution"
|
||||
:bounds="bounds"
|
||||
/>
|
||||
<LGeoJson :geojson="geoJson.jsonData" />
|
||||
<LMarker
|
||||
v-if="markerCoordinates.latitude"
|
||||
:lat-lng="[markerCoordinates.latitude, markerCoordinates.longitude]"
|
||||
/>
|
||||
</LMap>
|
||||
</div>
|
||||
<div v-else class="no-map">{{ t('workouts.NO_MAP') }}</div>
|
||||
</div>
|
||||
<div v-else class="no-map">{{ t('workouts.NO_MAP') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -39,6 +35,7 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { IAppConfig } from '@/types/application'
|
||||
import { GeoJSONData } from '@/types/geojson'
|
||||
import { IWorkoutData, TCoordinates } from '@/types/workouts'
|
||||
import { useStore } from '@/use/useStore'
|
||||
@ -64,9 +61,7 @@
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const workoutMap = ref<null | {
|
||||
leafletObject: { fitBounds: (bounds: number[][]) => null }
|
||||
}>(null)
|
||||
|
||||
function getGeoJson(gpxContent: string): GeoJSONData {
|
||||
if (!gpxContent || gpxContent !== '') {
|
||||
try {
|
||||
@ -93,6 +88,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
const workoutMap = ref<null | {
|
||||
leafletObject: { fitBounds: (bounds: number[][]) => null }
|
||||
}>(null)
|
||||
const bounds = computed(() =>
|
||||
props.workoutData
|
||||
? [
|
||||
@ -107,7 +105,7 @@
|
||||
]
|
||||
: []
|
||||
)
|
||||
const appConfig = computed(
|
||||
const appConfig: ComputedRef<IAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
|
||||
|
@ -16,17 +16,28 @@
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutRecord',
|
||||
props: {
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
record_type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base';
|
||||
.workout-record {
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,66 +2,7 @@
|
||||
<div class="workout-detail">
|
||||
<Card :without-title="false">
|
||||
<template #title>
|
||||
<div
|
||||
class="workout-previous workout-arrow"
|
||||
:class="{ inactive: !workoutObject.previousUrl }"
|
||||
:title="
|
||||
workoutObject.previousUrl
|
||||
? t(`workouts.PREVIOUS_${workoutObject.type}`)
|
||||
: t(`workouts.NO_PREVIOUS_${workoutObject.type}`)
|
||||
"
|
||||
@click="
|
||||
workoutObject.previousUrl
|
||||
? $router.push(workoutObject.previousUrl)
|
||||
: null
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="workout-card-title">
|
||||
<div class="sport-img">
|
||||
<img alt="workout sport logo" :src="sport.img" />
|
||||
</div>
|
||||
<div class="workout-title-date">
|
||||
<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">
|
||||
{{ workoutObject.workoutDate }} -
|
||||
{{ workoutObject.workoutTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="workout-next workout-arrow"
|
||||
:class="{ inactive: !workoutObject.nextUrl }"
|
||||
:title="
|
||||
workoutObject.nextUrl
|
||||
? t(`workouts.NEXT_${workoutObject.type}`)
|
||||
: t(`workouts.NO_NEXT_${workoutObject.type}`)
|
||||
"
|
||||
@click="
|
||||
workoutObject.nextUrl ? $router.push(workoutObject.nextUrl) : null
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
</div>
|
||||
<WorkoutCardTitle :sport="sport" :workoutObject="workoutObject" />
|
||||
</template>
|
||||
<template #content>
|
||||
<WorkoutMap
|
||||
@ -79,8 +20,8 @@
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
defineComponent,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
@ -88,6 +29,7 @@
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
import WorkoutCardTitle from '@/components/Workout/WorkoutDetail/WorkoutCardTitle.vue'
|
||||
import WorkoutData from '@/components/Workout/WorkoutDetail/WorkoutData.vue'
|
||||
import WorkoutMap from '@/components/Workout/WorkoutDetail/WorkoutMap.vue'
|
||||
import { ISport } from '@/types/sports'
|
||||
@ -105,6 +47,7 @@
|
||||
name: 'WorkoutDetail',
|
||||
components: {
|
||||
Card,
|
||||
WorkoutCardTitle,
|
||||
WorkoutData,
|
||||
WorkoutMap,
|
||||
},
|
||||
@ -113,6 +56,10 @@
|
||||
type: Object as PropType<IAuthUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
displaySegment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
markerCoordinates: {
|
||||
type: Object as PropType<TCoordinates>,
|
||||
required: false,
|
||||
@ -124,10 +71,6 @@
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
required: true,
|
||||
},
|
||||
displaySegment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const route = useRoute()
|
||||
@ -239,38 +182,6 @@
|
||||
display: flex;
|
||||
::v-deep(.card) {
|
||||
width: 100%;
|
||||
.card-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.workout-arrow {
|
||||
cursor: pointer;
|
||||
&.inactive {
|
||||
color: var(--disabled-color);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.workout-card-title {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
.sport-img {
|
||||
img {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
padding: 0 $default-padding;
|
||||
}
|
||||
}
|
||||
.workout-date {
|
||||
font-size: 0.8em;
|
||||
font-weight: normal;
|
||||
}
|
||||
.workout-segment {
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -35,7 +35,6 @@
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base.scss';
|
||||
|
||||
#workout-note {
|
||||
::v-deep(.card-content) {
|
||||
font-style: italic;
|
||||
|
@ -25,7 +25,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
|
@ -3,6 +3,7 @@
|
||||
"ANALYSIS": "analysis",
|
||||
"ASCENT": "ascent",
|
||||
"AVERAGE_SPEED": "average speed",
|
||||
"BACK_TO_WORKOUT": "back to workout",
|
||||
"DESCENT": "descent",
|
||||
"DISTANCE": "distance",
|
||||
"DURATION": "duration",
|
||||
|
@ -3,6 +3,7 @@
|
||||
"ANALYSIS": "analyse",
|
||||
"ASCENT": "dénivelé positif",
|
||||
"AVERAGE_SPEED": "vitesse moyenne",
|
||||
"BACK_TO_WORKOUT": "revenir à la séance",
|
||||
"DESCENT": "dénivelé négatif",
|
||||
"DISTANCE": "distance",
|
||||
"DURATION": "durée",
|
||||
|
@ -1,15 +1,9 @@
|
||||
<template>
|
||||
<div id="workout">
|
||||
<div class="container">
|
||||
<div class="workout-loading" v-if="workoutData.loading">
|
||||
<div class="loading">
|
||||
<Loader />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="workout-container">
|
||||
<div class="workout-container" v-if="sports.length > 0">
|
||||
<div v-if="workoutData.workout.id">
|
||||
<WorkoutDetail
|
||||
v-if="sports.length > 0"
|
||||
:workoutData="workoutData"
|
||||
:sports="sports"
|
||||
:authUser="authUser"
|
||||
@ -28,14 +22,14 @@
|
||||
<WorkoutSegments
|
||||
v-if="!displaySegment && workoutData.workout.segments.length > 1"
|
||||
:segments="workoutData.workout.segments"
|
||||
></WorkoutSegments>
|
||||
/>
|
||||
<WorkoutNotes
|
||||
v-if="!displaySegment"
|
||||
:notes="workoutData.workout.notes"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<NotFound target="WORKOUT" />
|
||||
<NotFound v-if="!workoutData.loading" target="WORKOUT" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -55,9 +49,8 @@
|
||||
} from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import Loader from '@/components/Common/Loader.vue'
|
||||
import NotFound from '@/components/Common/NotFound.vue'
|
||||
import WorkoutChart from '@/components/Workout/WorkoutChart/index.vue'
|
||||
import WorkoutChart from '@/components/Workout/WorkoutChart.vue'
|
||||
import WorkoutDetail from '@/components/Workout/WorkoutDetail/index.vue'
|
||||
import WorkoutNotes from '@/components/Workout/WorkoutNotes.vue'
|
||||
import WorkoutSegments from '@/components/Workout/WorkoutSegments.vue'
|
||||
@ -69,7 +62,6 @@
|
||||
export default defineComponent({
|
||||
name: 'Workout',
|
||||
components: {
|
||||
Loader,
|
||||
NotFound,
|
||||
WorkoutChart,
|
||||
WorkoutDetail,
|
||||
@ -116,21 +108,25 @@
|
||||
watch(
|
||||
() => route.params.workoutId,
|
||||
async (newWorkoutId) => {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_WORKOUT_DATA, {
|
||||
workoutId: newWorkoutId,
|
||||
})
|
||||
if (newWorkoutId) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_WORKOUT_DATA, {
|
||||
workoutId: newWorkoutId,
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => route.params.segmentId,
|
||||
async (newSegmentId) => {
|
||||
const payload: IWorkoutPayload = {
|
||||
workoutId: route.params.workoutId,
|
||||
if (route.params.workoutId) {
|
||||
const payload: IWorkoutPayload = {
|
||||
workoutId: route.params.workoutId,
|
||||
}
|
||||
if (newSegmentId) {
|
||||
payload.segmentId = newSegmentId
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_WORKOUT_DATA, payload)
|
||||
}
|
||||
if (newSegmentId) {
|
||||
payload.segmentId = newSegmentId
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_WORKOUT_DATA, payload)
|
||||
}
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user