Client - use <script setup> in components
This commit is contained in:
@ -82,13 +82,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
withDefaults,
|
||||
onBeforeMount,
|
||||
} from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -98,64 +97,55 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getFileSizeInMB } from '@/utils/files'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminApplication',
|
||||
props: {
|
||||
appConfig: {
|
||||
type: Object as PropType<TAppConfig>,
|
||||
required: true,
|
||||
},
|
||||
edition: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const appData: TAppConfigForm = reactive({
|
||||
max_users: 0,
|
||||
max_single_file_size: 0,
|
||||
max_zip_file_size: 0,
|
||||
gpx_limit_import: 0,
|
||||
})
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.appConfig) {
|
||||
updateForm(props.appConfig)
|
||||
}
|
||||
})
|
||||
|
||||
function updateForm(appConfig: TAppConfig) {
|
||||
Object.keys(appData).map((key) => {
|
||||
;['max_single_file_size', 'max_zip_file_size'].includes(key)
|
||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = getFileSizeInMB(appConfig[key]))
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = appConfig[key])
|
||||
})
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
updateForm(props.appConfig)
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
router.push('/admin/application')
|
||||
}
|
||||
function onSubmit() {
|
||||
const formData: TAppConfigForm = Object.assign({}, appData)
|
||||
formData.max_single_file_size *= 1048576
|
||||
formData.max_zip_file_size *= 1048576
|
||||
store.dispatch(ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG, formData)
|
||||
}
|
||||
|
||||
return { appData, errorMessages, onCancel, onSubmit }
|
||||
},
|
||||
interface Props {
|
||||
appConfig: TAppConfig
|
||||
edition?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
edition: false,
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const appData: TAppConfigForm = reactive({
|
||||
max_users: 0,
|
||||
max_single_file_size: 0,
|
||||
max_zip_file_size: 0,
|
||||
gpx_limit_import: 0,
|
||||
})
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (props.appConfig) {
|
||||
updateForm(props.appConfig)
|
||||
}
|
||||
})
|
||||
|
||||
function updateForm(appConfig: TAppConfig) {
|
||||
Object.keys(appData).map((key) => {
|
||||
;['max_single_file_size', 'max_zip_file_size'].includes(key)
|
||||
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = getFileSizeInMB(appConfig[key]))
|
||||
: // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(appData[key] = appConfig[key])
|
||||
})
|
||||
}
|
||||
function onCancel() {
|
||||
updateForm(props.appConfig)
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
router.push('/admin/application')
|
||||
}
|
||||
function onSubmit() {
|
||||
const formData: TAppConfigForm = Object.assign({}, appData)
|
||||
formData.max_single_file_size *= 1048576
|
||||
formData.max_zip_file_size *= 1048576
|
||||
store.dispatch(ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG, formData)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<Card>
|
||||
<template #title>{{ $t('admin.ADMINISTRATION') }}</template>
|
||||
<template #content>
|
||||
<AppStatsCards :app-statistics="appStatistics" />
|
||||
<AppStatsCards :appStatistics="appStatistics" />
|
||||
<div class="admin-menu description-list">
|
||||
<dl>
|
||||
<dt>
|
||||
@ -46,32 +46,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, capitalize, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { capitalize, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import AppStatsCards from '@/components/Administration/AppStatsCards.vue'
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
import { IAppStatistics, TAppConfig } from '@/types/application'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminMenu',
|
||||
components: {
|
||||
AppStatsCards,
|
||||
Card,
|
||||
},
|
||||
props: {
|
||||
appConfig: {
|
||||
type: Object as PropType<TAppConfig>,
|
||||
required: true,
|
||||
},
|
||||
appStatistics: {
|
||||
type: Object as PropType<IAppStatistics>,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return { capitalize }
|
||||
},
|
||||
interface Props {
|
||||
appConfig: TAppConfig
|
||||
appStatistics?: IAppStatistics
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
appStatistics: () => ({} as IAppStatistics),
|
||||
})
|
||||
|
||||
const { appConfig, appStatistics } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -82,8 +82,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { ROOT_STORE, SPORTS_STORE } from '@/store/constants'
|
||||
@ -91,28 +91,22 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminSports',
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(store.getters[SPORTS_STORE.GETTERS.SPORTS], t)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
function updateSportStatus(id: number, isActive: boolean) {
|
||||
store.dispatch(SPORTS_STORE.ACTIONS.UPDATE_SPORTS, {
|
||||
id,
|
||||
isActive,
|
||||
})
|
||||
}
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(store.getters[SPORTS_STORE.GETTERS.SPORTS], t)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
return { errorMessages, translatedSports, updateSportStatus }
|
||||
},
|
||||
})
|
||||
function updateSportStatus(id: number, isActive: boolean) {
|
||||
store.dispatch(SPORTS_STORE.ACTIONS.UPDATE_SPORTS, {
|
||||
id,
|
||||
isActive,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -115,12 +115,11 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
watch,
|
||||
capitalize,
|
||||
@ -139,89 +138,63 @@
|
||||
import { getQuery, sortList } from '@/utils/api'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminUsers',
|
||||
components: {
|
||||
FilterSelects,
|
||||
Pagination,
|
||||
UserPicture,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const orderByList: string[] = [
|
||||
'admin',
|
||||
'created_at',
|
||||
'username',
|
||||
'workouts_count',
|
||||
]
|
||||
const defaultOrderBy = 'created_at'
|
||||
let query: TPaginationPayload = reactive(
|
||||
getQuery(route.query, orderByList, defaultOrderBy)
|
||||
)
|
||||
const orderByList: string[] = [
|
||||
'admin',
|
||||
'created_at',
|
||||
'username',
|
||||
'workouts_count',
|
||||
]
|
||||
const defaultOrderBy = 'created_at'
|
||||
let query: TPaginationPayload = reactive(
|
||||
getQuery(route.query, orderByList, defaultOrderBy)
|
||||
)
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const users: ComputedRef<IUserProfile[]> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS]
|
||||
)
|
||||
const pagination: ComputedRef<IPagination> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS_PAGINATION]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const users: ComputedRef<IUserProfile[]> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS]
|
||||
)
|
||||
const pagination: ComputedRef<IPagination> = computed(
|
||||
() => store.getters[USERS_STORE.GETTERS.USERS_PAGINATION]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
onBeforeMount(() => loadUsers(query))
|
||||
|
||||
function loadUsers(queryParams: TPaginationPayload) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.GET_USERS, queryParams)
|
||||
}
|
||||
function updateUser(username: string, admin: boolean) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
admin,
|
||||
})
|
||||
}
|
||||
function reloadUsers(queryParam: string, queryValue: string) {
|
||||
query[queryParam] = queryValue
|
||||
if (queryParam === 'per_page') {
|
||||
query.page = 1
|
||||
}
|
||||
router.push({ path: '/admin/users', query })
|
||||
}
|
||||
function loadUsers(queryParams: TPaginationPayload) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.GET_USERS, queryParams)
|
||||
}
|
||||
function updateUser(username: string, admin: boolean) {
|
||||
store.dispatch(USERS_STORE.ACTIONS.UPDATE_USER, {
|
||||
username,
|
||||
admin,
|
||||
})
|
||||
}
|
||||
function reloadUsers(queryParam: string, queryValue: string) {
|
||||
query[queryParam] = queryValue
|
||||
if (queryParam === 'per_page') {
|
||||
query.page = 1
|
||||
}
|
||||
router.push({ path: '/admin/users', query })
|
||||
}
|
||||
|
||||
onBeforeMount(() => loadUsers(query))
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery: LocationQuery) => {
|
||||
query = getQuery(newQuery, orderByList, defaultOrderBy, { query })
|
||||
loadUsers(query)
|
||||
}
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USERS)
|
||||
})
|
||||
|
||||
return {
|
||||
authUser,
|
||||
errorMessages,
|
||||
orderByList,
|
||||
pagination,
|
||||
query,
|
||||
sortList,
|
||||
users,
|
||||
capitalize,
|
||||
format,
|
||||
getDateWithTZ,
|
||||
reloadUsers,
|
||||
updateUser,
|
||||
}
|
||||
},
|
||||
onUnmounted(() => {
|
||||
store.dispatch(USERS_STORE.ACTIONS.EMPTY_USERS)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery: LocationQuery) => {
|
||||
query = getQuery(newQuery, orderByList, defaultOrderBy, { query })
|
||||
loadUsers(query)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -23,45 +23,34 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, computed } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, withDefaults } from 'vue'
|
||||
|
||||
import StatCard from '@/components/Common/StatCard.vue'
|
||||
import { IAppStatistics } from '@/types/application'
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserStatsCards',
|
||||
components: {
|
||||
StatCard,
|
||||
},
|
||||
props: {
|
||||
appStatistics: {
|
||||
type: Object as PropType<IAppStatistics>,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
uploadDirSize: computed(() =>
|
||||
props.appStatistics.uploads_dir_size
|
||||
? getReadableFileSize(props.appStatistics.uploads_dir_size, false)
|
||||
: { size: 0, suffix: 'bytes' }
|
||||
),
|
||||
usersCount: computed(() =>
|
||||
props.appStatistics.users ? props.appStatistics.users : 0
|
||||
),
|
||||
sportsCount: computed(() =>
|
||||
props.appStatistics.sports ? props.appStatistics.sports : 0
|
||||
),
|
||||
workoutCount: computed(() =>
|
||||
props.appStatistics.workouts ? props.appStatistics.workouts : 0
|
||||
),
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
appStatistics?: IAppStatistics
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
appStatistics: () => ({} as IAppStatistics),
|
||||
})
|
||||
|
||||
const uploadDirSize = computed(() =>
|
||||
props.appStatistics.uploads_dir_size
|
||||
? getReadableFileSize(props.appStatistics.uploads_dir_size, false)
|
||||
: { size: 0, suffix: 'bytes' }
|
||||
)
|
||||
const usersCount = computed(() =>
|
||||
props.appStatistics.users ? props.appStatistics.users : 0
|
||||
)
|
||||
const sportsCount = computed(() =>
|
||||
props.appStatistics.sports ? props.appStatistics.sports : 0
|
||||
)
|
||||
const workoutCount = computed(() =>
|
||||
props.appStatistics.workouts ? props.appStatistics.workouts : 0
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -1,19 +1,13 @@
|
||||
<template>
|
||||
<div id="about">
|
||||
<div id="bike">
|
||||
<img class="bike-img" :src="'/img/bike.svg'" alt="mountain bike" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'About',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/base';
|
||||
|
||||
#about {
|
||||
#bike {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
|
@ -4,15 +4,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AlertMessage',
|
||||
props: {
|
||||
message: String,
|
||||
},
|
||||
})
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
const props = defineProps<{
|
||||
message: string
|
||||
}>()
|
||||
const { message } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -9,13 +9,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'Card',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@/scss/base';
|
||||
|
||||
|
@ -14,47 +14,35 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomTextArea',
|
||||
props: {
|
||||
charLimit: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
input: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateValue'],
|
||||
setup(props, { emit }) {
|
||||
let text = ref('')
|
||||
|
||||
function updateText(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateValue', event.target.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
text.value = value
|
||||
}
|
||||
)
|
||||
|
||||
return { text, updateText }
|
||||
},
|
||||
interface Props {
|
||||
name: string
|
||||
charLimit?: number
|
||||
disabled?: boolean
|
||||
input?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
charLimit: 500,
|
||||
disabled: false,
|
||||
input: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['updateValue'])
|
||||
|
||||
let text = ref('')
|
||||
|
||||
function updateText(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateValue', event.target.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
text.value = value
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -17,53 +17,37 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, ref, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { IDropdownOption, TDropdownOptions } from '@/types/forms'
|
||||
interface Props {
|
||||
options: TDropdownOptions
|
||||
selected: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Dropdown',
|
||||
props: {
|
||||
options: {
|
||||
type: Object as PropType<TDropdownOptions>,
|
||||
required: true,
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
selected: (option: IDropdownOption) => option,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const route = useRoute()
|
||||
let isOpen = ref(false)
|
||||
let dropdownOptions = props.options.map((option) => option)
|
||||
|
||||
function toggleDropdown() {
|
||||
isOpen.value = !isOpen.value
|
||||
}
|
||||
function updateSelected(option: IDropdownOption) {
|
||||
emit('selected', option)
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => (isOpen.value = false)
|
||||
)
|
||||
|
||||
return {
|
||||
dropdownOptions,
|
||||
isOpen,
|
||||
toggleDropdown,
|
||||
updateSelected,
|
||||
}
|
||||
},
|
||||
const emit = defineEmits({
|
||||
selected: (option: IDropdownOption) => option,
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
let isOpen = ref(false)
|
||||
let dropdownOptions = props.options.map((option) => option)
|
||||
|
||||
function toggleDropdown() {
|
||||
isOpen.value = !isOpen.value
|
||||
}
|
||||
function updateSelected(option: IDropdownOption) {
|
||||
emit('selected', option)
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => (isOpen.value = false)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -10,28 +10,19 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Error',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
default: '/',
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
title: string
|
||||
message: string
|
||||
buttonText: string
|
||||
path?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
path: '/',
|
||||
})
|
||||
const { buttonText, title, message, path } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -9,15 +9,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ErrorMessage',
|
||||
props: {
|
||||
message: [String, Array],
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
message: string | string[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const { message } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -34,7 +34,7 @@
|
||||
:value="query.per_page"
|
||||
@change="onSelectUpdate"
|
||||
>
|
||||
<option v-for="nb in per_page" :value="nb" :key="nb">
|
||||
<option v-for="nb in perPage" :value="nb" :key="nb">
|
||||
{{ nb }}
|
||||
</option>
|
||||
</select>
|
||||
@ -42,43 +42,27 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { TPaginationPayload } from '@/types/api'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FilterSelects',
|
||||
props: {
|
||||
order_by: {
|
||||
type: Object as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
query: {
|
||||
type: Object as PropType<TPaginationPayload>,
|
||||
required: true,
|
||||
},
|
||||
sort: {
|
||||
type: Object as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateSelect'],
|
||||
setup(props, { emit }) {
|
||||
function onSelectUpdate(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateSelect', event.target.id, event.target.value)
|
||||
}
|
||||
interface Props {
|
||||
order_by: string[]
|
||||
query: TPaginationPayload
|
||||
sort: string[]
|
||||
message: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
return {
|
||||
per_page: [10, 25, 50, 100],
|
||||
onSelectUpdate,
|
||||
}
|
||||
},
|
||||
})
|
||||
const emit = defineEmits(['updateSelect'])
|
||||
|
||||
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)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -20,8 +20,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { inject, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import CyclingSport from '@/components/Common/Images/SportImage/CyclingSport.vue'
|
||||
import CyclingTransport from '@/components/Common/Images/SportImage/CyclingTransport.vue'
|
||||
@ -35,33 +35,14 @@
|
||||
import Trail from '@/components/Common/Images/SportImage/Trail.vue'
|
||||
import Walking from '@/components/Common/Images/SportImage/Walking.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SportImage',
|
||||
components: {
|
||||
CyclingSport,
|
||||
CyclingTransport,
|
||||
Hiking,
|
||||
MountainBiking,
|
||||
MountainBikingElectric,
|
||||
Rowing,
|
||||
Running,
|
||||
SkiingAlpine,
|
||||
SkiingCrossCountry,
|
||||
Trail,
|
||||
Walking,
|
||||
},
|
||||
props: {
|
||||
sportLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return { sportColors: inject('sportColors') }
|
||||
},
|
||||
interface Props {
|
||||
sportLabel: string
|
||||
title?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
})
|
||||
|
||||
const { sportLabel, title } = toRefs(props)
|
||||
const sportColors = inject('sportColors')
|
||||
</script>
|
||||
|
@ -2,13 +2,6 @@
|
||||
<div class="loader" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'Loader',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/base';
|
||||
.loader {
|
||||
|
@ -31,38 +31,30 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent, onUnmounted } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, toRefs, withDefaults, onUnmounted } from 'vue'
|
||||
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Modal',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
strongMessage: {
|
||||
type: String || null,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['cancelAction', 'confirmAction'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore()
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
return { errorMessages, emit }
|
||||
},
|
||||
interface Props {
|
||||
title: string
|
||||
message: string
|
||||
strongMessage?: string | null
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
strongMessage: () => null,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['cancelAction', 'confirmAction'])
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { title, message, strongMessage } = toRefs(props)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -6,21 +6,15 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
import Error from '@/components/Common/Error.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NotFound',
|
||||
components: {
|
||||
Error,
|
||||
},
|
||||
props: {
|
||||
target: {
|
||||
type: String,
|
||||
default: 'PAGE',
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
target?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
target: 'PAGE',
|
||||
})
|
||||
const { target } = toRefs(props)
|
||||
</script>
|
||||
|
@ -42,37 +42,27 @@
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IPagination, TPaginationPayload } from '@/types/api'
|
||||
import { IPagination } from '@/types/api'
|
||||
import { TWorkoutsPayload } from '@/types/workouts'
|
||||
import { rangePagination } from '@/utils/api'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
pagination: {
|
||||
type: Object as PropType<IPagination>,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
query: {
|
||||
type: Object as PropType<TPaginationPayload>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
function getQuery(page: number, cursor?: number): TPaginationPayload {
|
||||
const newQuery = Object.assign({}, props.query)
|
||||
newQuery.page = cursor ? page + cursor : page
|
||||
return newQuery
|
||||
}
|
||||
return { rangePagination, getQuery }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
pagination: IPagination
|
||||
path: string
|
||||
query: TWorkoutsPayload
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { pagination, path, query } = toRefs(props)
|
||||
|
||||
function getQuery(page: number, cursor?: number): TWorkoutsPayload {
|
||||
const newQuery = Object.assign({}, query.value)
|
||||
newQuery.page = cursor ? page + cursor : page
|
||||
return newQuery
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -12,26 +12,16 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StatCard',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
icon: string
|
||||
text: string
|
||||
value: string | number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const { icon, text, value } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -22,29 +22,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { getApiUrl } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StaticMap',
|
||||
props: {
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: true,
|
||||
},
|
||||
displayHover: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const imageUrl = `${getApiUrl()}workouts/map/${props.workout.map}`
|
||||
return { imageUrl }
|
||||
},
|
||||
interface Props {
|
||||
workout: IWorkout
|
||||
displayHover?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
displayHover: false,
|
||||
})
|
||||
const { displayHover } = toRefs(props)
|
||||
const imageUrl = `${getApiUrl()}workouts/map/${props.workout.map}`
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -30,15 +30,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onBeforeMount,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, ref, onBeforeMount, toRefs } from 'vue'
|
||||
|
||||
import WorkoutCard from '@/components/Workout/WorkoutCard.vue'
|
||||
import NoWorkouts from '@/components/Workouts/NoWorkouts.vue'
|
||||
@ -49,65 +42,44 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { defaultOrder } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Timeline',
|
||||
components: {
|
||||
NoWorkouts,
|
||||
WorkoutCard,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
let page = ref(1)
|
||||
const per_page = 5
|
||||
const initWorkoutsCount =
|
||||
props.user.nb_workouts >= per_page ? per_page : props.user.nb_workouts
|
||||
onBeforeMount(() => loadWorkouts())
|
||||
const store = useStore()
|
||||
|
||||
const workouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.TIMELINE_WORKOUTS]
|
||||
)
|
||||
const moreWorkoutsExist: ComputedRef<boolean> = computed(() =>
|
||||
workouts.value.length > 0
|
||||
? workouts.value[workouts.value.length - 1].previous_workout !== null
|
||||
: false
|
||||
)
|
||||
const { sports, user } = toRefs(props)
|
||||
let page = ref(1)
|
||||
const per_page = 5
|
||||
const initWorkoutsCount =
|
||||
props.user.nb_workouts >= per_page ? per_page : props.user.nb_workouts
|
||||
onBeforeMount(() => loadWorkouts())
|
||||
const workouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.TIMELINE_WORKOUTS]
|
||||
)
|
||||
const moreWorkoutsExist: ComputedRef<boolean> = computed(() =>
|
||||
workouts.value.length > 0
|
||||
? workouts.value[workouts.value.length - 1].previous_workout !== null
|
||||
: false
|
||||
)
|
||||
|
||||
function loadWorkouts() {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
function loadMoreWorkouts() {
|
||||
page.value += 1
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_MORE_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
initWorkoutsCount,
|
||||
moreWorkoutsExist,
|
||||
per_page,
|
||||
workouts,
|
||||
loadMoreWorkouts,
|
||||
}
|
||||
},
|
||||
})
|
||||
function loadWorkouts() {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
function loadMoreWorkouts() {
|
||||
page.value += 1
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_MORE_TIMELINE_WORKOUTS, {
|
||||
page: page.value,
|
||||
per_page,
|
||||
...defaultOrder,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -23,105 +23,71 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { addDays, format, isSameDay, isSameMonth, isToday } from 'date-fns'
|
||||
import {
|
||||
PropType,
|
||||
Ref,
|
||||
defineComponent,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
import { Ref, ref, toRefs, watch, onMounted } from 'vue'
|
||||
|
||||
import CalendarWorkouts from '@/components/Dashboard/UserCalendar/CalendarWorkouts.vue'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarCells',
|
||||
components: {
|
||||
CalendarWorkouts,
|
||||
},
|
||||
props: {
|
||||
currentDay: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
endDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
startDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
weekStartingMonday: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
workouts: {
|
||||
type: Object as PropType<IWorkout[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const rows: Ref<Date[][]> = ref([])
|
||||
let { startDate, endDate, weekStartingMonday } = toRefs(props)
|
||||
interface Props {
|
||||
currentDay: Date
|
||||
endDate: Date
|
||||
sports: ISport[]
|
||||
startDate: Date
|
||||
timezone: string
|
||||
weekStartingMonday: boolean
|
||||
workouts: IWorkout[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onMounted(() => getDays())
|
||||
const {
|
||||
currentDay,
|
||||
endDate,
|
||||
sports,
|
||||
startDate,
|
||||
timezone,
|
||||
weekStartingMonday,
|
||||
workouts,
|
||||
} = toRefs(props)
|
||||
const rows: Ref<Date[][]> = ref([])
|
||||
|
||||
function getDays() {
|
||||
rows.value = []
|
||||
let day = startDate.value
|
||||
while (day <= endDate.value) {
|
||||
const days: Date[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(day)
|
||||
day = addDays(day, 1)
|
||||
}
|
||||
rows.value.push(days)
|
||||
}
|
||||
onMounted(() => getDays())
|
||||
|
||||
function getDays() {
|
||||
rows.value = []
|
||||
let day = startDate.value
|
||||
while (day <= endDate.value) {
|
||||
const days: Date[] = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(day)
|
||||
day = addDays(day, 1)
|
||||
}
|
||||
rows.value.push(days)
|
||||
}
|
||||
}
|
||||
function isWeekEnd(day: number): boolean {
|
||||
return weekStartingMonday.value
|
||||
? [5, 6].includes(day)
|
||||
: [0, 6].includes(day)
|
||||
}
|
||||
function filterWorkouts(day: Date, workouts: IWorkout[]) {
|
||||
if (workouts) {
|
||||
return workouts
|
||||
.filter((workout) =>
|
||||
isSameDay(getDateWithTZ(workout.workout_date, timezone), day)
|
||||
)
|
||||
.reverse()
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function isWeekEnd(day: number): boolean {
|
||||
return weekStartingMonday.value
|
||||
? [5, 6].includes(day)
|
||||
: [0, 6].includes(day)
|
||||
}
|
||||
|
||||
function filterWorkouts(day: Date, workouts: IWorkout[]) {
|
||||
if (workouts) {
|
||||
return workouts
|
||||
.filter((workout) =>
|
||||
isSameDay(
|
||||
getDateWithTZ(workout.workout_date, props.timezone),
|
||||
day
|
||||
)
|
||||
)
|
||||
.reverse()
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentDay,
|
||||
() => getDays()
|
||||
)
|
||||
|
||||
return { rows, format, isSameMonth, isToday, isWeekEnd, filterWorkouts }
|
||||
},
|
||||
})
|
||||
watch(
|
||||
() => props.currentDay,
|
||||
() => getDays()
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -6,30 +6,19 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format, addDays } from 'date-fns'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarDays',
|
||||
props: {
|
||||
startDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
localeOptions: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const days = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(addDays(props.startDate, i))
|
||||
}
|
||||
return { days, addDays, format }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
startDate: Date
|
||||
localeOptions: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const days = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
days.push(addDays(props.startDate, i))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -20,27 +20,19 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import { defineComponent } from 'vue'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarHeader',
|
||||
props: {
|
||||
day: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
localeOptions: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['displayNextMonth', 'displayPreviousMonth'],
|
||||
setup(props, { emit }) {
|
||||
return { emit, format }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
day: Date
|
||||
localeOptions: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['displayNextMonth', 'displayPreviousMonth'])
|
||||
|
||||
const { day, localeOptions } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -13,7 +13,7 @@
|
||||
aria-hidden="true"
|
||||
:title="
|
||||
workout.records.map(
|
||||
(record) => ` ${t(`workouts.RECORD_${record.record_type}`)}`
|
||||
(record) => ` ${$t(`workouts.RECORD_${record.record_type}`)}`
|
||||
)
|
||||
"
|
||||
/>
|
||||
@ -21,29 +21,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
interface Props {
|
||||
workout: IWorkout
|
||||
sportLabel: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarWorkout',
|
||||
props: {
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: true,
|
||||
},
|
||||
sportLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
return { t }
|
||||
},
|
||||
})
|
||||
const { workout, sportLabel } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from 'vue'
|
||||
|
||||
import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue'
|
||||
import CalendarWorkoutsChart from '@/components/Dashboard/UserCalendar/CalendarWorkoutsChart.vue'
|
||||
@ -44,31 +44,16 @@
|
||||
import { getSportLabel, sportIdColors } from '@/utils/sports'
|
||||
import { getDonutDatasets } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarWorkouts',
|
||||
components: {
|
||||
CalendarWorkout,
|
||||
CalendarWorkoutsChart,
|
||||
},
|
||||
props: {
|
||||
workouts: {
|
||||
type: Object as PropType<IWorkout[]>,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
chartDatasets: computed(() => getDonutDatasets(props.workouts)),
|
||||
colors: computed(() => sportIdColors(props.sports)),
|
||||
displayedWorkoutCount: 6,
|
||||
getSportLabel,
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
workouts: IWorkout[]
|
||||
sports: ISport[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workouts, sports } = toRefs(props)
|
||||
const chartDatasets = computed(() => getDonutDatasets(props.workouts))
|
||||
const colors = computed(() => sportIdColors(props.sports))
|
||||
const displayedWorkoutCount = 6
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -22,8 +22,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, ref } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref, toRefs } from 'vue'
|
||||
|
||||
import CalendarWorkout from '@/components/Dashboard/UserCalendar/CalendarWorkout.vue'
|
||||
import DonutChart from '@/components/Dashboard/UserCalendar/DonutChart.vue'
|
||||
@ -31,39 +31,21 @@
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { getSportLabel } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CalendarWorkoutsChart',
|
||||
components: {
|
||||
CalendarWorkout,
|
||||
DonutChart,
|
||||
},
|
||||
props: {
|
||||
colors: {
|
||||
type: Object as PropType<Record<number, string>>,
|
||||
required: true,
|
||||
},
|
||||
datasets: {
|
||||
type: Object as PropType<Record<number, Record<string, number>>>,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
workouts: {
|
||||
type: Object as PropType<IWorkout[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const isHidden = ref(true)
|
||||
function togglePane(event: Event & { target: HTMLElement }) {
|
||||
event.stopPropagation()
|
||||
isHidden.value = !isHidden.value
|
||||
}
|
||||
return { isHidden, getSportLabel, togglePane }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
colors: Record<number, string>
|
||||
datasets: Record<number, Record<string, number>>
|
||||
sports: ISport[]
|
||||
workouts: IWorkout[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { colors, datasets, sports, workouts } = toRefs(props)
|
||||
const isHidden = ref(true)
|
||||
|
||||
function togglePane(event: Event & { target: HTMLElement }) {
|
||||
event.stopPropagation()
|
||||
isHidden.value = !isHidden.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -22,52 +22,34 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DonutChart',
|
||||
props: {
|
||||
colors: {
|
||||
type: Object as PropType<Record<number, string>>,
|
||||
required: true,
|
||||
},
|
||||
datasets: {
|
||||
type: Object as PropType<Record<number, Record<string, number>>>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
let angleOffset = -90
|
||||
const cx = 16
|
||||
const cy = 16
|
||||
const radius = 14
|
||||
const circumference = 2 * Math.PI * radius
|
||||
interface Props {
|
||||
colors: Record<number, string>
|
||||
datasets: Record<number, Record<string, number>>
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
function calculateStrokeDashOffset(
|
||||
percentage: number,
|
||||
circumference: number
|
||||
): number {
|
||||
return circumference - percentage * circumference
|
||||
}
|
||||
function returnCircleTransformValue(
|
||||
index: number,
|
||||
percentage: number
|
||||
): string {
|
||||
const rotation = `rotate(${angleOffset}, ${cx}, ${cy})`
|
||||
angleOffset = percentage * 360 + angleOffset
|
||||
return rotation
|
||||
}
|
||||
const { colors, datasets } = toRefs(props)
|
||||
let angleOffset = -90
|
||||
const cx = 16
|
||||
const cy = 16
|
||||
const radius = 14
|
||||
const circumference = 2 * Math.PI * radius
|
||||
|
||||
return {
|
||||
angleOffset,
|
||||
circumference,
|
||||
cx,
|
||||
cy,
|
||||
radius,
|
||||
calculateStrokeDashOffset,
|
||||
returnCircleTransformValue,
|
||||
}
|
||||
},
|
||||
})
|
||||
function calculateStrokeDashOffset(
|
||||
percentage: number,
|
||||
circumference: number
|
||||
): number {
|
||||
return circumference - percentage * circumference
|
||||
}
|
||||
function returnCircleTransformValue(
|
||||
index: number,
|
||||
percentage: number
|
||||
): string {
|
||||
const rotation = `rotate(${angleOffset}, ${cx}, ${cy})`
|
||||
angleOffset = percentage * 360 + angleOffset
|
||||
return rotation
|
||||
}
|
||||
</script>
|
||||
|
@ -21,16 +21,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { addMonths, format, subMonths } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
onBeforeMount,
|
||||
} from 'vue'
|
||||
import { ComputedRef, computed, ref, toRefs, onBeforeMount } from 'vue'
|
||||
|
||||
import CalendarCells from '@/components/Dashboard/UserCalendar/CalendarCells.vue'
|
||||
import CalendarDays from '@/components/Dashboard/UserCalendar/CalendarDays.vue'
|
||||
@ -43,70 +36,43 @@
|
||||
import { getCalendarStartAndEnd } from '@/utils/dates'
|
||||
import { defaultOrder } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserCalendar',
|
||||
components: {
|
||||
CalendarCells,
|
||||
CalendarDays,
|
||||
CalendarHeader,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onBeforeMount(() => getCalendarWorkouts())
|
||||
const store = useStore()
|
||||
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
let day = ref(new Date())
|
||||
let calendarDates = ref(
|
||||
getCalendarStartAndEnd(day.value, props.user.weekm)
|
||||
)
|
||||
const calendarWorkouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.CALENDAR_WORKOUTS]
|
||||
)
|
||||
const { sports, user } = toRefs(props)
|
||||
const dateFormat = 'yyyy-MM-dd'
|
||||
let day = ref(new Date())
|
||||
let calendarDates = ref(getCalendarStartAndEnd(day.value, props.user.weekm))
|
||||
const calendarWorkouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.CALENDAR_WORKOUTS]
|
||||
)
|
||||
|
||||
function getCalendarWorkouts() {
|
||||
calendarDates.value = getCalendarStartAndEnd(
|
||||
day.value,
|
||||
props.user.weekm
|
||||
)
|
||||
const apiParams: TWorkoutsPayload = {
|
||||
from: format(calendarDates.value.start, dateFormat),
|
||||
to: format(calendarDates.value.end, dateFormat),
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
...defaultOrder,
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_CALENDAR_WORKOUTS, apiParams)
|
||||
}
|
||||
onBeforeMount(() => getCalendarWorkouts())
|
||||
|
||||
function displayNextMonth() {
|
||||
day.value = addMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
function displayPreviousMonth() {
|
||||
day.value = subMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
|
||||
return {
|
||||
day,
|
||||
calendarDates,
|
||||
calendarWorkouts,
|
||||
displayNextMonth,
|
||||
displayPreviousMonth,
|
||||
}
|
||||
},
|
||||
})
|
||||
function getCalendarWorkouts() {
|
||||
calendarDates.value = getCalendarStartAndEnd(day.value, props.user.weekm)
|
||||
const apiParams: TWorkoutsPayload = {
|
||||
from: format(calendarDates.value.start, dateFormat),
|
||||
to: format(calendarDates.value.end, dateFormat),
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
...defaultOrder,
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_CALENDAR_WORKOUTS, apiParams)
|
||||
}
|
||||
function displayNextMonth() {
|
||||
day.value = addMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
function displayPreviousMonth() {
|
||||
day.value = subMonths(day.value, 1)
|
||||
getCalendarWorkouts()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -15,41 +15,28 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { endOfMonth, startOfMonth } from 'date-fns'
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import StatChart from '@/components/Common/StatsChart/index.vue'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserMonthStats',
|
||||
components: {
|
||||
StatChart,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const date = new Date()
|
||||
return {
|
||||
chartParams: {
|
||||
duration: 'week',
|
||||
start: startOfMonth(date),
|
||||
end: endOfMonth(date),
|
||||
},
|
||||
selectedSportIds: props.sports.map((sport) => sport.id),
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { sports, user } = toRefs(props)
|
||||
const date = new Date()
|
||||
const chartParams = {
|
||||
duration: 'week',
|
||||
start: startOfMonth(date),
|
||||
end: endOfMonth(date),
|
||||
}
|
||||
const selectedSportIds = props.sports.map((sport) => sport.id)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -7,9 +7,9 @@
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="record" v-for="record in records.records" :key="record.id">
|
||||
<span class="record-type">{{
|
||||
t(`workouts.RECORD_${record.record_type}`)
|
||||
}}</span>
|
||||
<span class="record-type">
|
||||
{{ $t(`workouts.RECORD_${record.record_type}`) }}
|
||||
</span>
|
||||
<span class="record-value">{{ record.value }}</span>
|
||||
<span class="record-date">
|
||||
<router-link
|
||||
@ -17,8 +17,9 @@
|
||||
name: 'Workout',
|
||||
params: { workoutId: record.workout_id },
|
||||
}"
|
||||
>{{ record.workout_date }}</router-link
|
||||
>
|
||||
{{ record.workout_date }}
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -26,29 +27,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IRecord } from '@/types/workouts'
|
||||
import { IRecordsBySports } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RecordsCard',
|
||||
props: {
|
||||
records: {
|
||||
type: Object as PropType<IRecord[]>,
|
||||
required: true,
|
||||
},
|
||||
sportTranslatedLabel: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
return { t }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
records: IRecordsBySports
|
||||
sportTranslatedLabel: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { records, sportTranslatedLabel } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -18,8 +18,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import RecordsCard from '@/components/Dashboard/UserRecords/RecordsCard.vue'
|
||||
@ -28,33 +28,21 @@
|
||||
import { getRecordsBySports } from '@/utils/records'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserRecords',
|
||||
components: {
|
||||
RecordsCard,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const recordsBySport = computed(() =>
|
||||
getRecordsBySports(
|
||||
props.user.records,
|
||||
translateSports(props.sports, t),
|
||||
props.user.timezone
|
||||
)
|
||||
)
|
||||
return { recordsBySport }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const recordsBySport = computed(() =>
|
||||
getRecordsBySports(
|
||||
props.user.records,
|
||||
translateSports(props.sports, t),
|
||||
props.user.timezone
|
||||
)
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -12,8 +12,8 @@
|
||||
/>
|
||||
<StatCard
|
||||
icon="clock-o"
|
||||
:value="total_duration.days"
|
||||
:text="total_duration.duration"
|
||||
:value="totalDuration.days"
|
||||
:text="totalDuration.duration"
|
||||
/>
|
||||
<StatCard
|
||||
icon="tags"
|
||||
@ -23,49 +23,40 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, PropType, defineComponent, computed } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import StatCard from '@/components/Common/StatCard.vue'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserStatsCards',
|
||||
components: {
|
||||
StatCard,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const total_duration: ComputedRef<string> = computed(
|
||||
() => props.user.total_duration
|
||||
)
|
||||
const { t } = useI18n()
|
||||
|
||||
function get_duration(total_duration: ComputedRef<string>) {
|
||||
const duration = total_duration.value.match(/day/g)
|
||||
? total_duration.value.split(', ')[1]
|
||||
: total_duration.value
|
||||
return {
|
||||
days: total_duration.value.match(/day/g)
|
||||
? `${total_duration.value.split(' ')[0]} ${
|
||||
total_duration.value.match(/days/g)
|
||||
? t('common.DAY', 2)
|
||||
: t('common.DAY', 1)
|
||||
}`
|
||||
: `0 ${t('common.DAY', 2)},`,
|
||||
duration: `${duration.split(':')[0]}h ${duration.split(':')[1]}min`,
|
||||
}
|
||||
}
|
||||
const { user } = toRefs(props)
|
||||
const userTotalDuration: ComputedRef<string> = computed(
|
||||
() => props.user.total_duration
|
||||
)
|
||||
const totalDuration = computed(() => get_duration(userTotalDuration))
|
||||
|
||||
return { total_duration: computed(() => get_duration(total_duration)) }
|
||||
},
|
||||
})
|
||||
function get_duration(total_duration: ComputedRef<string>) {
|
||||
const duration = total_duration.value.match(/day/g)
|
||||
? total_duration.value.split(', ')[1]
|
||||
: total_duration.value
|
||||
return {
|
||||
days: total_duration.value.match(/day/g)
|
||||
? `${total_duration.value.split(' ')[0]} ${
|
||||
total_duration.value.match(/days/g)
|
||||
? t('common.DAY', 2)
|
||||
: t('common.DAY', 1)
|
||||
}`
|
||||
: `0 ${t('common.DAY', 2)},`,
|
||||
duration: `${duration.split(':')[0]}h ${duration.split(':')[1]}min`,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -37,18 +37,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Footer',
|
||||
props: {
|
||||
version: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
version: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { version } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -77,8 +77,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent, ref, capitalize } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, ref, capitalize } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
@ -86,68 +86,39 @@
|
||||
import { IDropdownOption } from '@/types/forms'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getApiUrl } from '@/utils'
|
||||
import { availableLanguages } from '@/utils/locales'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NavBar',
|
||||
components: {
|
||||
UserPicture,
|
||||
},
|
||||
emits: ['menuInteraction'],
|
||||
setup(props, { emit }) {
|
||||
const { locale } = useI18n()
|
||||
const store = useStore()
|
||||
const emit = defineEmits(['menuInteraction'])
|
||||
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const isAuthenticated: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED]
|
||||
)
|
||||
const authUserPictureUrl: ComputedRef<string> = computed(() =>
|
||||
isAuthenticated.value && authUser.value.picture
|
||||
? `${getApiUrl()}/users/${
|
||||
authUser.value.username
|
||||
}/picture?${Date.now()}`
|
||||
: ''
|
||||
)
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
let isMenuOpen = ref(false)
|
||||
const { locale } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
function openMenu() {
|
||||
isMenuOpen.value = true
|
||||
emit('menuInteraction', true)
|
||||
}
|
||||
function closeMenu() {
|
||||
isMenuOpen.value = false
|
||||
emit('menuInteraction', false)
|
||||
}
|
||||
function updateLanguage(option: IDropdownOption) {
|
||||
locale.value = option.value.toString()
|
||||
store.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, option.value)
|
||||
}
|
||||
function logout() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
|
||||
}
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const isAuthenticated: ComputedRef<boolean> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.IS_AUTHENTICATED]
|
||||
)
|
||||
const language: ComputedRef<string> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LANGUAGE]
|
||||
)
|
||||
let isMenuOpen = ref(false)
|
||||
|
||||
return {
|
||||
availableLanguages,
|
||||
authUser,
|
||||
authUserPictureUrl,
|
||||
isAuthenticated,
|
||||
isMenuOpen,
|
||||
language,
|
||||
capitalize,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
updateLanguage,
|
||||
logout,
|
||||
}
|
||||
},
|
||||
})
|
||||
function openMenu() {
|
||||
isMenuOpen.value = true
|
||||
emit('menuInteraction', true)
|
||||
}
|
||||
function closeMenu() {
|
||||
isMenuOpen.value = false
|
||||
emit('menuInteraction', false)
|
||||
}
|
||||
function updateLanguage(option: IDropdownOption) {
|
||||
locale.value = option.value.toString()
|
||||
store.commit(ROOT_STORE.MUTATIONS.UPDATE_LANG, option.value)
|
||||
}
|
||||
function logout() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGOUT)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -28,19 +28,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NoConfig',
|
||||
setup() {
|
||||
const { t } = useI18n()
|
||||
return { t }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~@/scss/base';
|
||||
|
||||
|
@ -37,29 +37,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'StatsMenu',
|
||||
emits: ['arrowClick', 'timeFrameUpdate'],
|
||||
setup(props, { emit }) {
|
||||
let selectedTimeFrame = ref('month')
|
||||
const timeFrames = ['week', 'month', 'year']
|
||||
const emit = defineEmits(['arrowClick', 'timeFrameUpdate'])
|
||||
|
||||
function onUpdateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
emit('timeFrameUpdate', timeFrame)
|
||||
}
|
||||
let selectedTimeFrame = ref('month')
|
||||
const timeFrames = ['week', 'month', 'year']
|
||||
|
||||
return {
|
||||
selectedTimeFrame,
|
||||
timeFrames,
|
||||
onUpdateTimeFrame,
|
||||
emit,
|
||||
}
|
||||
},
|
||||
})
|
||||
function onUpdateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
emit('timeFrameUpdate', timeFrame)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -19,43 +19,34 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, PropType, computed, defineComponent, inject } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, inject, withDefaults, toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { ISport, ITranslatedSport } from '@/types/sports'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SportsMenu',
|
||||
props: {
|
||||
selectedSportIds: {
|
||||
type: Array as PropType<number[]>,
|
||||
default: () => [],
|
||||
},
|
||||
userSports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['selectedSportIdsUpdate'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.userSports, t)
|
||||
)
|
||||
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
emit('selectedSportIdsUpdate', sportId)
|
||||
}
|
||||
|
||||
return {
|
||||
sportColors: inject('sportColors'),
|
||||
translatedSports,
|
||||
updateSelectedSportIds,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
userSports: ISport[]
|
||||
selectedSportIds?: number[]
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
selectedSportIds: () => [],
|
||||
})
|
||||
|
||||
const emit = defineEmits(['selectedSportIdsUpdate'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const sportColors = inject('sportColors')
|
||||
const { selectedSportIds } = toRefs(props)
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.userSports, t)
|
||||
)
|
||||
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
emit('selectedSportIdsUpdate', sportId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -19,16 +19,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, Ref, computed, ref, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import StatChart from '@/components/Common/StatsChart/index.vue'
|
||||
@ -40,81 +32,57 @@
|
||||
import { translateSports } from '@/utils/sports'
|
||||
import { getStatsDateParams, updateChartParams } from '@/utils/statistics'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Statistics',
|
||||
components: {
|
||||
SportsMenu,
|
||||
StatChart,
|
||||
StatsMenu,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
let selectedTimeFrame = ref('month')
|
||||
const timeFrames = ['week', 'month', 'year']
|
||||
const chartParams: Ref<IStatisticsDateParams> = ref(
|
||||
getChartParams(selectedTimeFrame.value)
|
||||
)
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const selectedSportIds: Ref<number[]> = ref(getSports(props.sports))
|
||||
interface Props {
|
||||
sports: ISport[]
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
function updateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
chartParams.value = getChartParams(selectedTimeFrame.value)
|
||||
}
|
||||
function getChartParams(timeFrame: string): IStatisticsDateParams {
|
||||
return getStatsDateParams(new Date(), timeFrame, props.user.weekm)
|
||||
}
|
||||
function handleOnClickArrows(backward: boolean) {
|
||||
chartParams.value = updateChartParams(
|
||||
chartParams.value,
|
||||
backward,
|
||||
props.user.weekm
|
||||
)
|
||||
}
|
||||
function getSports(sports: ISport[]) {
|
||||
return sports.map((sport) => sport.id)
|
||||
}
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
if (selectedSportIds.value.includes(sportId)) {
|
||||
selectedSportIds.value = selectedSportIds.value.filter(
|
||||
(id) => id !== sportId
|
||||
)
|
||||
} else {
|
||||
selectedSportIds.value.push(sportId)
|
||||
}
|
||||
}
|
||||
const { t } = useI18n()
|
||||
|
||||
watch(
|
||||
() => props.sports,
|
||||
(newSports) => {
|
||||
selectedSportIds.value = getSports(newSports)
|
||||
}
|
||||
const { sports, user } = toRefs(props)
|
||||
let selectedTimeFrame = ref('month')
|
||||
const chartParams: Ref<IStatisticsDateParams> = ref(
|
||||
getChartParams(selectedTimeFrame.value)
|
||||
)
|
||||
const translatedSports: ComputedRef<ITranslatedSport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const selectedSportIds: Ref<number[]> = ref(getSports(props.sports))
|
||||
|
||||
function updateTimeFrame(timeFrame: string) {
|
||||
selectedTimeFrame.value = timeFrame
|
||||
chartParams.value = getChartParams(selectedTimeFrame.value)
|
||||
}
|
||||
function getChartParams(timeFrame: string): IStatisticsDateParams {
|
||||
return getStatsDateParams(new Date(), timeFrame, props.user.weekm)
|
||||
}
|
||||
function handleOnClickArrows(backward: boolean) {
|
||||
chartParams.value = updateChartParams(
|
||||
chartParams.value,
|
||||
backward,
|
||||
props.user.weekm
|
||||
)
|
||||
}
|
||||
function getSports(sports: ISport[]) {
|
||||
return sports.map((sport) => sport.id)
|
||||
}
|
||||
function updateSelectedSportIds(sportId: number) {
|
||||
if (selectedSportIds.value.includes(sportId)) {
|
||||
selectedSportIds.value = selectedSportIds.value.filter(
|
||||
(id) => id !== sportId
|
||||
)
|
||||
} else {
|
||||
selectedSportIds.value.push(sportId)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
chartParams,
|
||||
selectedTimeFrame,
|
||||
timeFrames,
|
||||
translatedSports,
|
||||
selectedSportIds,
|
||||
handleOnClickArrows,
|
||||
updateSelectedSportIds,
|
||||
updateTimeFrame,
|
||||
}
|
||||
},
|
||||
})
|
||||
watch(
|
||||
() => props.sports,
|
||||
(newSports) => {
|
||||
selectedSportIds.value = getSports(newSports)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -15,25 +15,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import EmailSent from '@/components/Common/Images/EmailSent.vue'
|
||||
import Password from '@/components/Common/Images/Password.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PasswordActionDone',
|
||||
components: {
|
||||
EmailSent,
|
||||
Password,
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
action: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { action } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -9,27 +9,20 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
import UserAuthForm from '@/components/User/UserAuthForm.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PasswordResetForm',
|
||||
components: {
|
||||
UserAuthForm,
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
action: string
|
||||
token?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
token: '',
|
||||
})
|
||||
|
||||
const { action, token } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -27,24 +27,18 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfileDisplay',
|
||||
components: {
|
||||
UserPicture,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { user } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -43,67 +43,46 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import { ComputedRef, Ref, computed, ref, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserInfos',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
fromAdmin: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const birthDate = computed(() =>
|
||||
props.user.birth_date
|
||||
? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
|
||||
: ''
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteUserAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
|
||||
return {
|
||||
authUser,
|
||||
birthDate,
|
||||
displayModal,
|
||||
registrationDate,
|
||||
deleteUserAccount,
|
||||
updateDisplayModal,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
fromAdmin?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
fromAdmin: false,
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user, fromAdmin } = toRefs(props)
|
||||
const authUser: ComputedRef<IUserProfile> = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.AUTH_USER_PROFILE]
|
||||
)
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const birthDate = computed(() =>
|
||||
props.user.birth_date
|
||||
? format(new Date(props.user.birth_date), 'dd/MM/yyyy')
|
||||
: ''
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteUserAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -17,35 +17,26 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { languageLabels } from '@/utils/locales'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPreferences',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const language = computed(() =>
|
||||
props.user.language
|
||||
? languageLabels[props.user.language]
|
||||
: languageLabels['en']
|
||||
)
|
||||
const fistDayOfWeek = computed(() =>
|
||||
props.user.weekm ? 'MONDAY' : 'SUNDAY'
|
||||
)
|
||||
const timezone = computed(() =>
|
||||
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
||||
)
|
||||
return { fistDayOfWeek, language, timezone }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const language = computed(() =>
|
||||
props.user.language
|
||||
? languageLabels[props.user.language]
|
||||
: languageLabels['en']
|
||||
)
|
||||
const fistDayOfWeek = computed(() => (props.user.weekm ? 'MONDAY' : 'SUNDAY'))
|
||||
const timezone = computed(() =>
|
||||
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -8,35 +8,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import UserHeader from '@/components/User/ProfileDisplay/UserHeader.vue'
|
||||
import UserProfileTabs from '@/components/User/UserProfileTabs.vue'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfileDisplay',
|
||||
components: {
|
||||
UserHeader,
|
||||
UserProfileTabs,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
tab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
tabs: ['PROFILE', 'PREFERENCES'],
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
tab: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { user, tab } = toRefs(props)
|
||||
const tabs = ['PROFILE', 'PREFERENCES']
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -27,74 +27,56 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Ref, defineComponent, ref, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref, toRefs, watch, withDefaults } from 'vue'
|
||||
|
||||
import { timeZones } from '@/utils/timezone'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TimezoneDropdown',
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
input: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateTimezone'],
|
||||
setup(props, { emit }) {
|
||||
const timezone: Ref<string> = ref(props.input)
|
||||
const isOpen: Ref<boolean> = ref(false)
|
||||
const tzList: Ref<HTMLInputElement | null> = ref(null)
|
||||
const focusItemIndex: Ref<number> = ref(0)
|
||||
|
||||
function matchTimezone(t: string): RegExpMatchArray | null {
|
||||
return t.toLowerCase().match(timezone.value.toLowerCase())
|
||||
}
|
||||
function onMouseOver(index: number) {
|
||||
focusItemIndex.value = index
|
||||
}
|
||||
function onUpdateTimezone(value: string) {
|
||||
timezone.value = value
|
||||
isOpen.value = false
|
||||
emit('updateTimezone', value)
|
||||
}
|
||||
function onEnter(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
if (tzList.value?.firstElementChild?.innerHTML) {
|
||||
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
|
||||
}
|
||||
}
|
||||
function openDropdown(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
isOpen.value = true
|
||||
timezone.value = event.target.value.trim()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
timezone.value = value
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
focusItemIndex,
|
||||
isOpen,
|
||||
timezone,
|
||||
timeZones,
|
||||
tzList,
|
||||
matchTimezone,
|
||||
onEnter,
|
||||
onMouseOver,
|
||||
onUpdateTimezone,
|
||||
openDropdown,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
input: string
|
||||
disabled?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['updateTimezone'])
|
||||
|
||||
const { input, disabled } = toRefs(props)
|
||||
const timezone: Ref<string> = ref(props.input)
|
||||
const isOpen: Ref<boolean> = ref(false)
|
||||
const tzList: Ref<HTMLInputElement | null> = ref(null)
|
||||
const focusItemIndex: Ref<number> = ref(0)
|
||||
|
||||
function matchTimezone(t: string): RegExpMatchArray | null {
|
||||
return t.toLowerCase().match(timezone.value.toLowerCase())
|
||||
}
|
||||
function onMouseOver(index: number) {
|
||||
focusItemIndex.value = index
|
||||
}
|
||||
function onUpdateTimezone(value: string) {
|
||||
timezone.value = value
|
||||
isOpen.value = false
|
||||
emit('updateTimezone', value)
|
||||
}
|
||||
function onEnter(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
if (tzList.value?.firstElementChild?.innerHTML) {
|
||||
onUpdateTimezone(tzList.value?.firstElementChild?.innerHTML)
|
||||
}
|
||||
}
|
||||
function openDropdown(event: Event & { target: HTMLInputElement }) {
|
||||
event.preventDefault()
|
||||
isOpen.value = true
|
||||
timezone.value = event.target.value.trim()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
timezone.value = value
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -93,16 +93,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
|
||||
@ -110,79 +109,63 @@
|
||||
import { IUserProfile, IUserPayload } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserInfosEdition',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const userForm: IUserPayload = reactive({
|
||||
password: '',
|
||||
password_conf: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
birth_date: '',
|
||||
location: '',
|
||||
bio: '',
|
||||
})
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
const store = useStore()
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.first_name = user.first_name ? user.first_name : ''
|
||||
userForm.last_name = user.last_name ? user.last_name : ''
|
||||
userForm.birth_date = user.birth_date
|
||||
? format(new Date(user.birth_date), 'yyyy-MM-dd')
|
||||
: ''
|
||||
userForm.location = user.location ? user.location : ''
|
||||
userForm.bio = user.bio ? user.bio : ''
|
||||
}
|
||||
function updateBio(value: string) {
|
||||
userForm.bio = value
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
|
||||
return {
|
||||
displayModal,
|
||||
errorMessages,
|
||||
loading,
|
||||
registrationDate,
|
||||
userForm,
|
||||
deleteAccount,
|
||||
updateBio,
|
||||
updateDisplayModal,
|
||||
updateProfile,
|
||||
}
|
||||
},
|
||||
const { user } = toRefs(props)
|
||||
const userForm: IUserPayload = reactive({
|
||||
password: '',
|
||||
password_conf: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
birth_date: '',
|
||||
location: '',
|
||||
bio: '',
|
||||
})
|
||||
const registrationDate = computed(() =>
|
||||
props.user.created_at
|
||||
? format(new Date(props.user.created_at), 'dd/MM/yyyy HH:mm')
|
||||
: ''
|
||||
)
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.first_name = user.first_name ? user.first_name : ''
|
||||
userForm.last_name = user.last_name ? user.last_name : ''
|
||||
userForm.birth_date = user.birth_date
|
||||
? format(new Date(user.birth_date), 'yyyy-MM-dd')
|
||||
: ''
|
||||
userForm.location = user.location ? user.location : ''
|
||||
userForm.bio = user.bio ? user.bio : ''
|
||||
}
|
||||
function updateBio(value: string) {
|
||||
userForm.bio = value
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PROFILE, userForm)
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteAccount(username: string) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.DELETE_ACCOUNT, { username })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -32,15 +32,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
defineComponent,
|
||||
computed,
|
||||
ref,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, Ref, computed, ref, toRefs } from 'vue'
|
||||
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -49,56 +42,40 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPictureEdition',
|
||||
components: {
|
||||
UserPicture,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
let pictureFile: Ref<File | null> = ref(null)
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
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 updateUserPicture() {
|
||||
if (pictureFile.value) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE, {
|
||||
picture: pictureFile.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
const store = useStore()
|
||||
|
||||
return {
|
||||
errorMessages,
|
||||
fileSizeLimit,
|
||||
pictureFile,
|
||||
deleteUserPicture,
|
||||
updateUserPicture,
|
||||
updatePictureFile,
|
||||
}
|
||||
},
|
||||
})
|
||||
const { user } = toRefs(props)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(appConfig.value.max_single_file_size)
|
||||
: ''
|
||||
let 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 updateUserPicture() {
|
||||
if (pictureFile.value) {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PICTURE, {
|
||||
picture: pictureFile.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -51,15 +51,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
computed,
|
||||
defineComponent,
|
||||
reactive,
|
||||
onMounted,
|
||||
} from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, reactive, onMounted } from 'vue'
|
||||
|
||||
import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue'
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -67,71 +60,50 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { availableLanguages } from '@/utils/locales'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPreferencesEdition',
|
||||
components: {
|
||||
TimezoneDropdown,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
const userForm: IUserPreferencesPayload = reactive({
|
||||
language: '',
|
||||
timezone: 'Europe/Paris',
|
||||
weekm: false,
|
||||
})
|
||||
const weekStart = [
|
||||
{
|
||||
label: 'MONDAY',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'SUNDAY',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
const store = useStore()
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.language = user.language ? user.language : 'en'
|
||||
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
||||
userForm.weekm = user.weekm ? user.weekm : false
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES,
|
||||
userForm
|
||||
)
|
||||
}
|
||||
function updateTZ(value: string) {
|
||||
userForm.timezone = value
|
||||
}
|
||||
|
||||
return {
|
||||
availableLanguages,
|
||||
errorMessages,
|
||||
loading,
|
||||
userForm,
|
||||
weekStart,
|
||||
updateProfile,
|
||||
updateTZ,
|
||||
}
|
||||
},
|
||||
const userForm: IUserPreferencesPayload = reactive({
|
||||
language: '',
|
||||
timezone: 'Europe/Paris',
|
||||
weekm: false,
|
||||
})
|
||||
const weekStart = [
|
||||
{
|
||||
label: 'MONDAY',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'SUNDAY',
|
||||
value: false,
|
||||
},
|
||||
]
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (props.user) {
|
||||
updateUserForm(props.user)
|
||||
}
|
||||
})
|
||||
|
||||
function updateUserForm(user: IUserProfile) {
|
||||
userForm.language = user.language ? user.language : 'en'
|
||||
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
||||
userForm.weekm = user.weekm ? user.weekm : false
|
||||
}
|
||||
function updateProfile() {
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.UPDATE_USER_PREFERENCES, userForm)
|
||||
}
|
||||
function updateTZ(value: string) {
|
||||
userForm.timezone = value
|
||||
}
|
||||
</script>
|
||||
|
@ -17,37 +17,25 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from 'vue'
|
||||
|
||||
import UserProfileTabs from '@/components/User/UserProfileTabs.vue'
|
||||
import { AUTH_USER_STORE } from '@/store/constants'
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProfileEdition',
|
||||
components: {
|
||||
UserProfileTabs,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
tab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
return {
|
||||
loading: computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
),
|
||||
tabs: ['PROFILE', 'PICTURE', 'PREFERENCES'],
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
tab: string
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user, tab } = toRefs(props)
|
||||
const tabs = ['PROFILE', 'PICTURE', 'PREFERENCES']
|
||||
const loading = computed(
|
||||
() => store.getters[AUTH_USER_STORE.GETTERS.USER_LOADING]
|
||||
)
|
||||
</script>
|
||||
|
@ -86,8 +86,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ComputedRef, computed, defineComponent, reactive, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
computed,
|
||||
reactive,
|
||||
toRefs,
|
||||
watch,
|
||||
withDefaults,
|
||||
} from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||
@ -95,104 +102,90 @@
|
||||
import { ILoginRegisterFormData } from '@/types/user'
|
||||
import { useStore } from '@/use/useStore'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserAuthForm',
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const formData: ILoginRegisterFormData = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_conf: '',
|
||||
})
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const buttonText: ComputedRef<string> = computed(() =>
|
||||
getButtonText(props.action)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const registration_disabled: ComputedRef<boolean> = computed(
|
||||
() =>
|
||||
props.action === 'register' &&
|
||||
!appConfig.value.is_registration_enabled
|
||||
)
|
||||
|
||||
function getButtonText(action: string): string {
|
||||
switch (action) {
|
||||
case 'reset-request':
|
||||
case 'reset':
|
||||
return 'buttons.SUBMIT'
|
||||
default:
|
||||
return `buttons.${props.action.toUpperCase()}`
|
||||
}
|
||||
}
|
||||
function onSubmit(actionType: string) {
|
||||
switch (actionType) {
|
||||
case 'reset':
|
||||
if (!props.token) {
|
||||
return store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
'user.INVALID_TOKEN'
|
||||
)
|
||||
}
|
||||
return store.dispatch(AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
|
||||
password: formData.password,
|
||||
password_conf: formData.password_conf,
|
||||
token: props.token,
|
||||
})
|
||||
case 'reset-request':
|
||||
return store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST,
|
||||
{
|
||||
email: formData.email,
|
||||
}
|
||||
)
|
||||
default:
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
|
||||
actionType,
|
||||
formData,
|
||||
redirectUrl: route.query.from,
|
||||
})
|
||||
}
|
||||
}
|
||||
function resetFormData() {
|
||||
formData.username = ''
|
||||
formData.email = ''
|
||||
formData.password = ''
|
||||
formData.password_conf = ''
|
||||
}
|
||||
watch(
|
||||
() => route.path,
|
||||
async () => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
resetFormData()
|
||||
}
|
||||
)
|
||||
return {
|
||||
appConfig,
|
||||
buttonText,
|
||||
errorMessages,
|
||||
formData,
|
||||
registration_disabled,
|
||||
onSubmit,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
action: string
|
||||
token?: string
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
token: '',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const { action } = toRefs(props)
|
||||
const formData: ILoginRegisterFormData = reactive({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
password_conf: '',
|
||||
})
|
||||
const buttonText: ComputedRef<string> = computed(() =>
|
||||
getButtonText(props.action)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const registration_disabled: ComputedRef<boolean> = computed(
|
||||
() =>
|
||||
props.action === 'register' && !appConfig.value.is_registration_enabled
|
||||
)
|
||||
|
||||
function getButtonText(action: string): string {
|
||||
switch (action) {
|
||||
case 'reset-request':
|
||||
case 'reset':
|
||||
return 'buttons.SUBMIT'
|
||||
default:
|
||||
return `buttons.${props.action.toUpperCase()}`
|
||||
}
|
||||
}
|
||||
function onSubmit(actionType: string) {
|
||||
switch (actionType) {
|
||||
case 'reset':
|
||||
if (!props.token) {
|
||||
return store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
'user.INVALID_TOKEN'
|
||||
)
|
||||
}
|
||||
return store.dispatch(AUTH_USER_STORE.ACTIONS.RESET_USER_PASSWORD, {
|
||||
password: formData.password,
|
||||
password_conf: formData.password_conf,
|
||||
token: props.token,
|
||||
})
|
||||
case 'reset-request':
|
||||
return store.dispatch(
|
||||
AUTH_USER_STORE.ACTIONS.SEND_PASSWORD_RESET_REQUEST,
|
||||
{
|
||||
email: formData.email,
|
||||
}
|
||||
)
|
||||
default:
|
||||
store.dispatch(AUTH_USER_STORE.ACTIONS.LOGIN_OR_REGISTER, {
|
||||
actionType,
|
||||
formData,
|
||||
redirectUrl: route.query.from,
|
||||
})
|
||||
}
|
||||
}
|
||||
function resetFormData() {
|
||||
formData.username = ''
|
||||
formData.email = ''
|
||||
formData.password = ''
|
||||
formData.password_conf = ''
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
async () => {
|
||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||
resetFormData()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
@ -12,30 +12,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { getApiUrl } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserPicture',
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
authUserPictureUrl: computed(() =>
|
||||
props.user.picture
|
||||
? `${getApiUrl()}users/${props.user.username}/picture`
|
||||
: ''
|
||||
),
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const authUserPictureUrl = computed(() =>
|
||||
props.user.picture
|
||||
? `${getApiUrl()}users/${props.user.username}/picture`
|
||||
: ''
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -18,44 +18,32 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserProfileTabs',
|
||||
props: {
|
||||
tabs: {
|
||||
type: Object as PropType<string[]>,
|
||||
required: true,
|
||||
},
|
||||
selectedTab: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
edition: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
function getPath(tab: string) {
|
||||
switch (tab) {
|
||||
case 'PICTURE':
|
||||
return '/profile/edit/picture'
|
||||
case 'PREFERENCES':
|
||||
return `/profile${props.edition ? '/edit' : ''}/preferences`
|
||||
default:
|
||||
case 'PROFILE':
|
||||
return `/profile${props.edition ? '/edit' : ''}`
|
||||
}
|
||||
}
|
||||
return { getPath }
|
||||
},
|
||||
interface Props {
|
||||
tabs: string[]
|
||||
selectedTab: string
|
||||
edition: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const { tabs, selectedTab, disabled } = toRefs(props)
|
||||
|
||||
function getPath(tab: string) {
|
||||
switch (tab) {
|
||||
case 'PICTURE':
|
||||
return '/profile/edit/picture'
|
||||
case 'PREFERENCES':
|
||||
return `/profile${props.edition ? '/edit' : ''}/preferences`
|
||||
default:
|
||||
case 'PROFILE':
|
||||
return `/profile${props.edition ? '/edit' : ''}`
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<router-link
|
||||
class="workout-title"
|
||||
v-if="workout"
|
||||
v-if="workout.id"
|
||||
:to="{
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
@ -27,7 +27,7 @@
|
||||
</router-link>
|
||||
<div
|
||||
class="workout-date"
|
||||
v-if="workout && user"
|
||||
v-if="workout.workout_date && user"
|
||||
:title="
|
||||
format(
|
||||
getDateWithTZ(workout.workout_date, user.timezone),
|
||||
@ -47,7 +47,7 @@
|
||||
class="workout-map"
|
||||
:class="{ 'no-cursor': !workout }"
|
||||
@click="
|
||||
workout
|
||||
workout.id
|
||||
? $router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
@ -66,11 +66,16 @@
|
||||
class="workout-data"
|
||||
:class="{ 'without-gpx': workout && !workout.with_gpx }"
|
||||
@click="
|
||||
$router.push({ name: 'Workout', params: { workoutId: workout.id } })
|
||||
workout.id
|
||||
? $router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
})
|
||||
: null
|
||||
"
|
||||
>
|
||||
<div class="img">
|
||||
<SportImage v-if="sport" :sport-label="sport.label" />
|
||||
<SportImage v-if="sport.label" :sport-label="sport.label" />
|
||||
</div>
|
||||
<div class="data">
|
||||
<i class="fa fa-clock-o" aria-hidden="true" />
|
||||
@ -103,9 +108,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { Locale, format, formatDistance } from 'date-fns'
|
||||
import { PropType, defineComponent, ComputedRef, computed } from 'vue'
|
||||
import { ComputedRef, computed, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import StaticMap from '@/components/Common/StaticMap.vue'
|
||||
import UserPicture from '@/components/User/UserPicture.vue'
|
||||
@ -116,39 +121,22 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutCard',
|
||||
components: {
|
||||
StaticMap,
|
||||
UserPicture,
|
||||
},
|
||||
props: {
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: false,
|
||||
},
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
sport: {
|
||||
type: Object as PropType<ISport>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const locale: ComputedRef<Locale> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
|
||||
)
|
||||
return {
|
||||
format,
|
||||
formatDistance,
|
||||
getDateWithTZ,
|
||||
locale,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
workout?: IWorkout
|
||||
sport?: ISport
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
workout: () => ({} as IWorkout),
|
||||
sport: () => ({} as ISport),
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const { user, workout, sport } = toRefs(props)
|
||||
const locale: ComputedRef<Locale> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.LOCALE]
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -36,9 +36,9 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ChartData, ChartOptions } from 'chart.js'
|
||||
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
|
||||
import { ComputedRef, computed, ref } from 'vue'
|
||||
import { LineChart, useLineChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@ -50,160 +50,142 @@
|
||||
} from '@/types/workouts'
|
||||
import { getDatasets } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutChart',
|
||||
components: {
|
||||
LineChart,
|
||||
},
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
workoutData: {
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
required: true,
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
workoutData: IWorkoutData
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['getCoordinates'])
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.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 coordinates: ComputedRef<TCoordinates[]> = computed(
|
||||
() => datasets.value.coordinates
|
||||
)
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
},
|
||||
},
|
||||
emits: ['getCoordinates'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workoutData.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 coordinates: ComputedRef<TCoordinates[]> = computed(
|
||||
() => datasets.value.coordinates
|
||||
)
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
scales: {
|
||||
[displayDistance.value ? 'xDistance' : 'xDuration']: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: formatDuration(value)
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
[displayDistance.value ? 'xDistance' : 'xDuration']: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: formatDuration(value)
|
||||
},
|
||||
},
|
||||
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)',
|
||||
},
|
||||
},
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
title: {
|
||||
display: true,
|
||||
text: displayDistance.value
|
||||
? t('workouts.DISTANCE') + ' (km)'
|
||||
: t('workouts.DURATION'),
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
pointStyle: 'circle',
|
||||
pointRadius: 0,
|
||||
},
|
||||
},
|
||||
ySpeed: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const label = ` ${context.dataset.label}: ${context.formattedValue}`
|
||||
return context.dataset.yAxisID === 'yElevation'
|
||||
? label + ' m'
|
||||
: label + ' km/h'
|
||||
},
|
||||
title: function (tooltipItems) {
|
||||
if (tooltipItems.length > 0) {
|
||||
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
|
||||
}
|
||||
return tooltipItems.length === 0
|
||||
? ''
|
||||
: displayDistance.value
|
||||
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
|
||||
: `${t('workouts.DURATION')}: ${formatDuration(
|
||||
tooltipItems[0].label.replace(',', '')
|
||||
)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.SPEED') + ' (km/h)',
|
||||
},
|
||||
}))
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
function formatDuration(duration: string | number): string {
|
||||
return new Date(+duration * 1000).toISOString().substr(11, 8)
|
||||
}
|
||||
function emitCoordinates(coordinates: TCoordinates) {
|
||||
emit('getCoordinates', coordinates)
|
||||
}
|
||||
function emitEmptyCoordinates() {
|
||||
emitCoordinates({ latitude: null, longitude: null })
|
||||
}
|
||||
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
return {
|
||||
displayDistance,
|
||||
lineChartProps,
|
||||
emitEmptyCoordinates,
|
||||
updateDisplayDistance,
|
||||
}
|
||||
},
|
||||
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,
|
||||
},
|
||||
tooltip: {
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
},
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const label = ` ${context.dataset.label}: ${context.formattedValue}`
|
||||
return context.dataset.yAxisID === 'yElevation'
|
||||
? label + ' m'
|
||||
: label + ' km/h'
|
||||
},
|
||||
title: function (tooltipItems) {
|
||||
if (tooltipItems.length > 0) {
|
||||
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
|
||||
}
|
||||
return tooltipItems.length === 0
|
||||
? ''
|
||||
: displayDistance.value
|
||||
? `${t('workouts.DISTANCE')}: ${tooltipItems[0].label} km`
|
||||
: `${t('workouts.DURATION')}: ${formatDuration(
|
||||
tooltipItems[0].label.replace(',', '')
|
||||
)}`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
function formatDuration(duration: string | number): string {
|
||||
return new Date(+duration * 1000).toISOString().substr(11, 8)
|
||||
}
|
||||
function emitCoordinates(coordinates: TCoordinates) {
|
||||
emit('getCoordinates', coordinates)
|
||||
}
|
||||
function emitEmptyCoordinates() {
|
||||
emitCoordinates({ latitude: null, longitude: null })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -80,29 +80,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
emits: ['displayModal'],
|
||||
setup(props, { emit }) {
|
||||
return { emit }
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
sport: ISport
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits(['displayModal'])
|
||||
|
||||
const { sport, workoutObject } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="workout-data">
|
||||
<i class="fa fa-clock-o" aria-hidden="true" />
|
||||
{{ $t('workouts.DURATION') }}: <span>{{ workoutObject.moving }}</span>
|
||||
<WorkoutRecord :workoutObject="workoutObject" record_type="LD" />
|
||||
<WorkoutRecord :workoutObject="workoutObject" recordType="LD" />
|
||||
<div v-if="withPause">
|
||||
({{ $t('workouts.PAUSES') }}: <span>{{ workoutObject.pauses }}</span> -
|
||||
{{ $t('workouts.TOTAL_DURATION') }}:
|
||||
@ -14,16 +14,16 @@
|
||||
<i class="fa fa-road" aria-hidden="true" />
|
||||
{{ $t('workouts.DISTANCE') }}:
|
||||
<span>{{ workoutObject.distance }} km</span>
|
||||
<WorkoutRecord :workoutObject="workoutObject" record_type="FD" />
|
||||
<WorkoutRecord :workoutObject="workoutObject" recordType="FD" />
|
||||
</div>
|
||||
<div class="workout-data">
|
||||
<i class="fa fa-tachometer" aria-hidden="true" />
|
||||
{{ $t('workouts.AVERAGE_SPEED') }}:
|
||||
<span>{{ workoutObject.aveSpeed }} km/h</span
|
||||
><WorkoutRecord :workoutObject="workoutObject" record_type="AS" /><br />
|
||||
><WorkoutRecord :workoutObject="workoutObject" recordType="AS" /><br />
|
||||
{{ $t('workouts.MAX_SPEED') }}:
|
||||
<span>{{ workoutObject.maxSpeed }} km/h</span>
|
||||
<WorkoutRecord :workoutObject="workoutObject" record_type="MS" />
|
||||
<WorkoutRecord :workoutObject="workoutObject" recordType="MS" />
|
||||
</div>
|
||||
<div
|
||||
class="workout-data"
|
||||
@ -52,35 +52,24 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from 'vue'
|
||||
|
||||
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: {
|
||||
WorkoutRecord,
|
||||
WorkoutWeather,
|
||||
},
|
||||
props: {
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
return {
|
||||
withPause: computed(
|
||||
() =>
|
||||
props.workoutObject.pauses !== '0:00:00' &&
|
||||
props.workoutObject.pauses !== null
|
||||
),
|
||||
}
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workoutObject } = toRefs(props)
|
||||
const withPause = computed(
|
||||
() =>
|
||||
props.workoutObject.pauses !== '0:00:00' &&
|
||||
props.workoutObject.pauses !== null
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -28,10 +28,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { gpx } from '@tmcw/togeojson'
|
||||
import { LGeoJson, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet'
|
||||
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
|
||||
import { ComputedRef, computed, ref, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import { ROOT_STORE } from '@/store/constants'
|
||||
import { TAppConfig } from '@/types/application'
|
||||
@ -40,90 +40,69 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { getApiUrl } from '@/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutMap',
|
||||
components: {
|
||||
LGeoJson,
|
||||
LMap,
|
||||
LMarker,
|
||||
LTileLayer,
|
||||
},
|
||||
props: {
|
||||
workoutData: {
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
},
|
||||
markerCoordinates: {
|
||||
type: Object as PropType<TCoordinates>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore()
|
||||
interface Props {
|
||||
workoutData: IWorkoutData
|
||||
markerCoordinates?: TCoordinates
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
markerCoordinates: () => ({} as TCoordinates),
|
||||
})
|
||||
|
||||
function getGeoJson(gpxContent: string): GeoJSONData {
|
||||
if (!gpxContent || gpxContent !== '') {
|
||||
try {
|
||||
const jsonData = gpx(
|
||||
new DOMParser().parseFromString(gpxContent, 'text/xml')
|
||||
)
|
||||
return { jsonData }
|
||||
} catch (e) {
|
||||
console.error('Invalid gpx content')
|
||||
return {}
|
||||
}
|
||||
}
|
||||
const store = useStore()
|
||||
|
||||
const { workoutData, markerCoordinates } = toRefs(props)
|
||||
const workoutMap = ref<null | {
|
||||
leafletObject: { fitBounds: (bounds: number[][]) => null }
|
||||
}>(null)
|
||||
const bounds = computed(() =>
|
||||
props.workoutData
|
||||
? [
|
||||
[
|
||||
props.workoutData.workout.bounds[0],
|
||||
props.workoutData.workout.bounds[1],
|
||||
],
|
||||
[
|
||||
props.workoutData.workout.bounds[2],
|
||||
props.workoutData.workout.bounds[3],
|
||||
],
|
||||
]
|
||||
: []
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const center = computed(() => getCenter(bounds))
|
||||
const geoJson = computed(() =>
|
||||
props.workoutData && props.workoutData.gpx
|
||||
? getGeoJson(props.workoutData.gpx)
|
||||
: {}
|
||||
)
|
||||
|
||||
function getGeoJson(gpxContent: string): GeoJSONData {
|
||||
if (!gpxContent || gpxContent !== '') {
|
||||
try {
|
||||
const jsonData = gpx(
|
||||
new DOMParser().parseFromString(gpxContent, 'text/xml')
|
||||
)
|
||||
return { jsonData }
|
||||
} catch (e) {
|
||||
console.error('Invalid gpx content')
|
||||
return {}
|
||||
}
|
||||
function getCenter(bounds: ComputedRef<number[][]>): number[] {
|
||||
return [
|
||||
(bounds.value[0][0] + bounds.value[1][0]) / 2,
|
||||
(bounds.value[0][1] + bounds.value[1][1]) / 2,
|
||||
]
|
||||
}
|
||||
function fitBounds(bounds: number[][]) {
|
||||
if (workoutMap.value?.leafletObject) {
|
||||
workoutMap.value?.leafletObject.fitBounds(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
const workoutMap = ref<null | {
|
||||
leafletObject: { fitBounds: (bounds: number[][]) => null }
|
||||
}>(null)
|
||||
const bounds = computed(() =>
|
||||
props.workoutData
|
||||
? [
|
||||
[
|
||||
props.workoutData.workout.bounds[0],
|
||||
props.workoutData.workout.bounds[1],
|
||||
],
|
||||
[
|
||||
props.workoutData.workout.bounds[2],
|
||||
props.workoutData.workout.bounds[3],
|
||||
],
|
||||
]
|
||||
: []
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const center = computed(() => getCenter(bounds))
|
||||
const geoJson = computed(() =>
|
||||
props.workoutData && props.workoutData.gpx
|
||||
? getGeoJson(props.workoutData.gpx)
|
||||
: {}
|
||||
)
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
bounds,
|
||||
center,
|
||||
geoJson,
|
||||
workoutMap,
|
||||
fitBounds,
|
||||
getApiUrl,
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
return {}
|
||||
}
|
||||
function getCenter(bounds: ComputedRef<number[][]>): number[] {
|
||||
return [
|
||||
(bounds.value[0][0] + bounds.value[1][0]) / 2,
|
||||
(bounds.value[0][1] + bounds.value[1][1]) / 2,
|
||||
]
|
||||
}
|
||||
function fitBounds(bounds: number[][]) {
|
||||
if (workoutMap.value?.leafletObject) {
|
||||
workoutMap.value?.leafletObject.fitBounds(bounds)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,7 +3,7 @@
|
||||
class="workout-record"
|
||||
v-if="
|
||||
workoutObject.records &&
|
||||
workoutObject.records.find((record) => record.record_type === record_type)
|
||||
workoutObject.records.find((record) => record.record_type === recordType)
|
||||
"
|
||||
>
|
||||
<sup>
|
||||
@ -12,24 +12,18 @@
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutRecord',
|
||||
props: {
|
||||
record_type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
recordType: string
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { recordType, workoutObject } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -97,20 +97,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkoutObject } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutWeather',
|
||||
props: {
|
||||
workoutObject: {
|
||||
type: Object as PropType<IWorkoutObject>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
workoutObject: IWorkoutObject
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { workoutObject } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -26,15 +26,15 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
withDefaults,
|
||||
} from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
@ -54,147 +54,122 @@
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutDetail',
|
||||
components: {
|
||||
WorkoutCardTitle,
|
||||
WorkoutData,
|
||||
WorkoutMap,
|
||||
},
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
displaySegment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
markerCoordinates: {
|
||||
type: Object as PropType<TCoordinates>,
|
||||
required: false,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
},
|
||||
workoutData: {
|
||||
type: Object as PropType<IWorkoutData>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteWorkout(workoutId: string) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.DELETE_WORKOUT, {
|
||||
workoutId: workoutId,
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
|
||||
watch(
|
||||
() => route.params.segmentId,
|
||||
async (newSegmentId) => {
|
||||
if (newSegmentId) {
|
||||
segmentId.value = +newSegmentId
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
sport: computed(() =>
|
||||
props.sports
|
||||
? props.sports.find(
|
||||
(sport) => sport.id === props.workoutData.workout.sport_id
|
||||
)
|
||||
: {}
|
||||
),
|
||||
workoutObject: computed(() =>
|
||||
getWorkoutObject(workout.value, segment.value)
|
||||
),
|
||||
displayModal,
|
||||
deleteWorkout,
|
||||
updateDisplayModal,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
displaySegment: boolean
|
||||
sports: ISport[]
|
||||
workoutData: IWorkoutData
|
||||
markerCoordinates?: TCoordinates
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
markerCoordinates: () => ({} as TCoordinates),
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
const { markerCoordinates, workoutData } = toRefs(props)
|
||||
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
|
||||
)
|
||||
let displayModal: Ref<boolean> = ref(false)
|
||||
const sport = computed(() =>
|
||||
props.sports
|
||||
? props.sports.find(
|
||||
(sport) => sport.id === props.workoutData.workout.sport_id
|
||||
)
|
||||
: {}
|
||||
)
|
||||
const workoutObject = computed(() =>
|
||||
getWorkoutObject(workout.value, segment.value)
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
}
|
||||
function deleteWorkout(workoutId: string) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.DELETE_WORKOUT, {
|
||||
workoutId: workoutId,
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.segmentId,
|
||||
async (newSegmentId) => {
|
||||
if (newSegmentId) {
|
||||
segmentId.value = +newSegmentId
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -31,9 +31,9 @@
|
||||
:disabled="loading"
|
||||
@click="updateWithGpx"
|
||||
/>
|
||||
<label for="withoutGpx">{{
|
||||
$t('workouts.WITHOUT_GPX')
|
||||
}}</label>
|
||||
<label for="withoutGpx">
|
||||
{{ $t('workouts.WITHOUT_GPX') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
@ -42,7 +42,7 @@
|
||||
id="sport"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.sport_id"
|
||||
v-model="workoutForm.sport_id"
|
||||
>
|
||||
<option
|
||||
v-for="sport in translatedSports.filter((s) => s.is_active)"
|
||||
@ -95,7 +95,7 @@
|
||||
type="text"
|
||||
:required="!isCreation"
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.title"
|
||||
v-model="workoutForm.title"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!withGpx">
|
||||
@ -109,7 +109,7 @@
|
||||
type="date"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDate"
|
||||
v-model="workoutForm.workoutDate"
|
||||
/>
|
||||
<input
|
||||
id="workout-time"
|
||||
@ -118,7 +118,7 @@
|
||||
type="time"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutTime"
|
||||
v-model="workoutForm.workoutTime"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -134,7 +134,7 @@
|
||||
pattern="^([0-9]*[0-9])$"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDurationHour"
|
||||
v-model="workoutForm.workoutDurationHour"
|
||||
/>
|
||||
:
|
||||
<input
|
||||
@ -146,7 +146,7 @@
|
||||
placeholder="MM"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDurationMinutes"
|
||||
v-model="workoutForm.workoutDurationMinutes"
|
||||
/>
|
||||
:
|
||||
<input
|
||||
@ -158,7 +158,7 @@
|
||||
placeholder="SS"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDurationSeconds"
|
||||
v-model="workoutForm.workoutDurationSeconds"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,7 +172,7 @@
|
||||
step="0.1"
|
||||
required
|
||||
:disabled="loading"
|
||||
v-model="workoutDataObject.workoutDistance"
|
||||
v-model="workoutForm.workoutDistance"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -180,7 +180,7 @@
|
||||
<label> {{ $t('workouts.NOTES') }}: </label>
|
||||
<CustomTextArea
|
||||
name="notes"
|
||||
:input="workoutDataObject.notes"
|
||||
:input="workoutForm.notes"
|
||||
:disabled="loading"
|
||||
@updateValue="updateNotes"
|
||||
/>
|
||||
@ -205,17 +205,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
defineComponent,
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
withDefaults,
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -230,189 +230,154 @@
|
||||
import { getReadableFileSize } from '@/utils/files'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutEdition',
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
isCreation: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.workout && props.workout.id) {
|
||||
formatWorkoutForm(props.workout)
|
||||
}
|
||||
})
|
||||
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(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)
|
||||
: ''
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const workoutForm = reactive({
|
||||
sport_id: '',
|
||||
title: '',
|
||||
notes: '',
|
||||
workoutDate: '',
|
||||
workoutTime: '',
|
||||
workoutDurationHour: '',
|
||||
workoutDurationMinutes: '',
|
||||
workoutDurationSeconds: '',
|
||||
workoutDistance: '',
|
||||
})
|
||||
let withGpx = ref(
|
||||
props.workout ? props.workout.with_gpx : props.isCreation
|
||||
)
|
||||
let gpxFile: File | null = null
|
||||
|
||||
function updateNotes(value: string) {
|
||||
workoutForm.notes = value
|
||||
}
|
||||
function updateWithGpx() {
|
||||
withGpx.value = !withGpx.value
|
||||
}
|
||||
function updateFile(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.files) {
|
||||
gpxFile = event.target.files[0]
|
||||
}
|
||||
}
|
||||
function formatWorkoutForm(workout: IWorkout) {
|
||||
workoutForm.sport_id = `${workout.sport_id}`
|
||||
workoutForm.title = workout.title
|
||||
workoutForm.notes = workout.notes
|
||||
if (!workout.with_gpx) {
|
||||
const workoutDateTime = formatWorkoutDate(
|
||||
getDateWithTZ(workout.workout_date, props.authUser.timezone),
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
const duration = workout.duration.split(':')
|
||||
workoutForm.workoutDistance = `${workout.distance}`
|
||||
workoutForm.workoutDate = workoutDateTime.workout_date
|
||||
workoutForm.workoutTime = workoutDateTime.workout_time
|
||||
workoutForm.workoutDurationHour = duration[0]
|
||||
workoutForm.workoutDurationMinutes = duration[1]
|
||||
workoutForm.workoutDurationSeconds = duration[2]
|
||||
}
|
||||
}
|
||||
function formatPayload(payload: IWorkoutForm) {
|
||||
payload.title = workoutForm.title
|
||||
payload.distance = +workoutForm.workoutDistance
|
||||
payload.duration =
|
||||
+workoutForm.workoutDurationHour * 3600 +
|
||||
+workoutForm.workoutDurationMinutes * 60 +
|
||||
+workoutForm.workoutDurationSeconds
|
||||
payload.workout_date = `${workoutForm.workoutDate} ${workoutForm.workoutTime}`
|
||||
}
|
||||
function updateWorkout() {
|
||||
const payload: IWorkoutForm = {
|
||||
sport_id: +workoutForm.sport_id,
|
||||
notes: workoutForm.notes,
|
||||
}
|
||||
if (props.workout) {
|
||||
if (props.workout.with_gpx) {
|
||||
payload.title = workoutForm.title
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT, {
|
||||
workoutId: props.workout.id,
|
||||
data: payload,
|
||||
})
|
||||
} else {
|
||||
if (withGpx.value) {
|
||||
if (!gpxFile) {
|
||||
const errorMessage = 'workouts.NO_FILE_PROVIDED'
|
||||
store.commit(
|
||||
ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES,
|
||||
errorMessage
|
||||
)
|
||||
return
|
||||
}
|
||||
payload.file = gpxFile
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT, payload)
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
store.dispatch(
|
||||
WORKOUTS_STORE.ACTIONS.ADD_WORKOUT_WITHOUT_GPX,
|
||||
payload
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
function onCancel() {
|
||||
if (props.workout) {
|
||||
router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: props.workout.id },
|
||||
})
|
||||
} else {
|
||||
router.go(-1)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.workout,
|
||||
async (
|
||||
newWorkout: IWorkout | undefined,
|
||||
previousWorkout: IWorkout | undefined
|
||||
) => {
|
||||
if (newWorkout !== previousWorkout && newWorkout && newWorkout.id) {
|
||||
formatWorkoutForm(newWorkout)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
|
||||
return {
|
||||
appConfig,
|
||||
errorMessages,
|
||||
fileSizeLimit,
|
||||
gpx_limit_import,
|
||||
translatedSports,
|
||||
withGpx,
|
||||
zipSizeLimit,
|
||||
workoutDataObject: workoutForm,
|
||||
onCancel,
|
||||
updateFile,
|
||||
updateNotes,
|
||||
updateWithGpx,
|
||||
updateWorkout,
|
||||
}
|
||||
},
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
sports: ISport[]
|
||||
isCreation?: boolean
|
||||
loading?: boolean
|
||||
workout?: IWorkout
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isCreation: false,
|
||||
loading: false,
|
||||
workout: () => ({} as IWorkout),
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const { workout, isCreation, loading } = toRefs(props)
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const appConfig: ComputedRef<TAppConfig> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
|
||||
)
|
||||
const fileSizeLimit = appConfig.value.max_single_file_size
|
||||
? getReadableFileSize(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)
|
||||
: ''
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const workoutForm = reactive({
|
||||
sport_id: '',
|
||||
title: '',
|
||||
notes: '',
|
||||
workoutDate: '',
|
||||
workoutTime: '',
|
||||
workoutDurationHour: '',
|
||||
workoutDurationMinutes: '',
|
||||
workoutDurationSeconds: '',
|
||||
workoutDistance: '',
|
||||
})
|
||||
let withGpx = ref(
|
||||
props.workout.id ? props.workout.with_gpx : props.isCreation
|
||||
)
|
||||
let gpxFile: File | null = null
|
||||
|
||||
onMounted(() => {
|
||||
if (props.workout.id) {
|
||||
formatWorkoutForm(props.workout)
|
||||
}
|
||||
})
|
||||
|
||||
function updateNotes(value: string) {
|
||||
workoutForm.notes = value
|
||||
}
|
||||
function updateWithGpx() {
|
||||
withGpx.value = !withGpx.value
|
||||
}
|
||||
function updateFile(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.files) {
|
||||
gpxFile = event.target.files[0]
|
||||
}
|
||||
}
|
||||
function formatWorkoutForm(workout: IWorkout) {
|
||||
workoutForm.sport_id = `${workout.sport_id}`
|
||||
workoutForm.title = workout.title
|
||||
workoutForm.notes = workout.notes
|
||||
if (!workout.with_gpx) {
|
||||
const workoutDateTime = formatWorkoutDate(
|
||||
getDateWithTZ(workout.workout_date, props.authUser.timezone),
|
||||
'yyyy-MM-dd'
|
||||
)
|
||||
const duration = workout.duration.split(':')
|
||||
workoutForm.workoutDistance = `${workout.distance}`
|
||||
workoutForm.workoutDate = workoutDateTime.workout_date
|
||||
workoutForm.workoutTime = workoutDateTime.workout_time
|
||||
workoutForm.workoutDurationHour = duration[0]
|
||||
workoutForm.workoutDurationMinutes = duration[1]
|
||||
workoutForm.workoutDurationSeconds = duration[2]
|
||||
}
|
||||
}
|
||||
function formatPayload(payload: IWorkoutForm) {
|
||||
payload.title = workoutForm.title
|
||||
payload.distance = +workoutForm.workoutDistance
|
||||
payload.duration =
|
||||
+workoutForm.workoutDurationHour * 3600 +
|
||||
+workoutForm.workoutDurationMinutes * 60 +
|
||||
+workoutForm.workoutDurationSeconds
|
||||
payload.workout_date = `${workoutForm.workoutDate} ${workoutForm.workoutTime}`
|
||||
}
|
||||
function updateWorkout() {
|
||||
const payload: IWorkoutForm = {
|
||||
sport_id: +workoutForm.sport_id,
|
||||
notes: workoutForm.notes,
|
||||
}
|
||||
if (props.workout.id) {
|
||||
if (props.workout.with_gpx) {
|
||||
payload.title = workoutForm.title
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
}
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT, {
|
||||
workoutId: props.workout.id,
|
||||
data: payload,
|
||||
})
|
||||
} else {
|
||||
if (withGpx.value) {
|
||||
if (!gpxFile) {
|
||||
const errorMessage = 'workouts.NO_FILE_PROVIDED'
|
||||
store.commit(ROOT_STORE.MUTATIONS.SET_ERROR_MESSAGES, errorMessage)
|
||||
return
|
||||
}
|
||||
payload.file = gpxFile
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT, payload)
|
||||
} else {
|
||||
formatPayload(payload)
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.ADD_WORKOUT_WITHOUT_GPX, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
function onCancel() {
|
||||
if (props.workout.id) {
|
||||
router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: props.workout.id },
|
||||
})
|
||||
} else {
|
||||
router.go(-1)
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
|
||||
watch(
|
||||
() => props.workout,
|
||||
async (
|
||||
newWorkout: IWorkout | undefined,
|
||||
previousWorkout: IWorkout | undefined
|
||||
) => {
|
||||
if (newWorkout !== previousWorkout && newWorkout && newWorkout.id) {
|
||||
formatWorkoutForm(newWorkout)
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -9,18 +9,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutNotes',
|
||||
props: {
|
||||
notes: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
interface Props {
|
||||
notes?: string | null
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
notes: () => null,
|
||||
})
|
||||
|
||||
const { notes } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -24,20 +24,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { toRefs } from 'vue'
|
||||
|
||||
import { IWorkoutSegment } from '@/types/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutSegments',
|
||||
props: {
|
||||
segments: {
|
||||
type: Object as PropType<IWorkoutSegment[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
interface Props {
|
||||
segments: IWorkoutSegment[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { segments } = toRefs(props)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -9,14 +9,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'NoWorkouts',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base.scss';
|
||||
.no-workouts {
|
||||
|
@ -159,8 +159,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, ComputedRef, defineComponent, PropType, watch } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, toRefs, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { LocationQuery, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
@ -168,58 +168,49 @@
|
||||
import { IUserProfile } from '@/types/user'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutsFilters',
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['filter'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
interface Props {
|
||||
authUser: IUserProfile
|
||||
sports: ISport[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
let params: LocationQuery = Object.assign({}, route.query)
|
||||
const emit = defineEmits(['filter'])
|
||||
|
||||
function handleFilterChange(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.value === '') {
|
||||
delete params[event.target.name]
|
||||
} else {
|
||||
params[event.target.name] = event.target.value
|
||||
}
|
||||
}
|
||||
function onFilter() {
|
||||
emit('filter')
|
||||
if ('page' in params) {
|
||||
params['page'] = '1'
|
||||
}
|
||||
router.push({ path: '/workouts', query: params })
|
||||
}
|
||||
function onClearFilter() {
|
||||
emit('filter')
|
||||
router.push({ path: '/workouts', query: {} })
|
||||
}
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery) => {
|
||||
params = Object.assign({}, newQuery)
|
||||
}
|
||||
)
|
||||
const { authUser } = toRefs(props)
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
let params: LocationQuery = Object.assign({}, route.query)
|
||||
|
||||
return { translatedSports, onClearFilter, onFilter, handleFilterChange }
|
||||
},
|
||||
})
|
||||
function handleFilterChange(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.value === '') {
|
||||
delete params[event.target.name]
|
||||
} else {
|
||||
params[event.target.name] = event.target.value
|
||||
}
|
||||
}
|
||||
function onFilter() {
|
||||
emit('filter')
|
||||
if ('page' in params) {
|
||||
params['page'] = '1'
|
||||
}
|
||||
router.push({ path: '/workouts', query: params })
|
||||
}
|
||||
function onClearFilter() {
|
||||
emit('filter')
|
||||
router.push({ path: '/workouts', query: {} })
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
(newQuery) => {
|
||||
params = Object.assign({}, newQuery)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -137,15 +137,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
capitalize,
|
||||
onBeforeMount,
|
||||
@ -166,103 +165,76 @@
|
||||
import { getDateWithTZ } from '@/utils/dates'
|
||||
import { defaultOrder } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutsList',
|
||||
components: {
|
||||
FilterSelects,
|
||||
NoWorkouts,
|
||||
Pagination,
|
||||
StaticMap,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object as PropType<IUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
sports: {
|
||||
type: Object as PropType<ITranslatedSport[]>,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
interface Props {
|
||||
user: IUserProfile
|
||||
sports: ITranslatedSport[]
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const orderByList: string[] = [
|
||||
'ave_speed',
|
||||
'distance',
|
||||
'duration',
|
||||
'workout_date',
|
||||
]
|
||||
const workouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.USER_WORKOUTS]
|
||||
)
|
||||
const pagination: ComputedRef<IPagination> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.WORKOUTS_PAGINATION]
|
||||
)
|
||||
let query: TWorkoutsPayload = getWorkoutsQuery(route.query)
|
||||
const hoverWorkoutId: Ref<string | null> = ref(null)
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadWorkouts(query)
|
||||
})
|
||||
const { user, sports } = toRefs(props)
|
||||
const orderByList: string[] = [
|
||||
'ave_speed',
|
||||
'distance',
|
||||
'duration',
|
||||
'workout_date',
|
||||
]
|
||||
const workouts: ComputedRef<IWorkout[]> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.USER_WORKOUTS]
|
||||
)
|
||||
const pagination: ComputedRef<IPagination> = computed(
|
||||
() => store.getters[WORKOUTS_STORE.GETTERS.WORKOUTS_PAGINATION]
|
||||
)
|
||||
let query: TWorkoutsPayload = getWorkoutsQuery(route.query)
|
||||
const hoverWorkoutId: Ref<string | null> = ref(null)
|
||||
|
||||
function loadWorkouts(payload: TWorkoutsPayload) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_USER_WORKOUTS, payload)
|
||||
}
|
||||
function reloadWorkouts(queryParam: string, queryValue: string) {
|
||||
const newQuery: LocationQuery = Object.assign({}, route.query)
|
||||
newQuery[queryParam] = queryValue
|
||||
if (queryParam === 'per_page') {
|
||||
newQuery['page'] = '1'
|
||||
}
|
||||
query = getWorkoutsQuery(newQuery)
|
||||
router.push({ path: '/workouts', query })
|
||||
}
|
||||
|
||||
function getWorkoutsQuery(newQuery: LocationQuery): TWorkoutsPayload {
|
||||
query = getQuery(newQuery, orderByList, defaultOrder.order_by, {
|
||||
defaultSort: defaultOrder.order,
|
||||
})
|
||||
Object.keys(newQuery)
|
||||
.filter((k) => workoutsPayloadKeys.includes(k))
|
||||
.map((k) => {
|
||||
if (typeof newQuery[k] === 'string') {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
query[k] = newQuery[k]
|
||||
}
|
||||
})
|
||||
return query
|
||||
}
|
||||
|
||||
function onHover(workoutId: string | null) {
|
||||
hoverWorkoutId.value = workoutId
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
async (newQuery) => {
|
||||
query = getWorkoutsQuery(newQuery)
|
||||
loadWorkouts(query)
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
query,
|
||||
hoverWorkoutId,
|
||||
orderByList,
|
||||
pagination,
|
||||
sortList,
|
||||
workouts,
|
||||
capitalize,
|
||||
format,
|
||||
getDateWithTZ,
|
||||
onHover,
|
||||
reloadWorkouts,
|
||||
}
|
||||
},
|
||||
onBeforeMount(() => {
|
||||
loadWorkouts(query)
|
||||
})
|
||||
|
||||
function loadWorkouts(payload: TWorkoutsPayload) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.GET_USER_WORKOUTS, payload)
|
||||
}
|
||||
function reloadWorkouts(queryParam: string, queryValue: string) {
|
||||
const newQuery: LocationQuery = Object.assign({}, route.query)
|
||||
newQuery[queryParam] = queryValue
|
||||
if (queryParam === 'per_page') {
|
||||
newQuery['page'] = '1'
|
||||
}
|
||||
query = getWorkoutsQuery(newQuery)
|
||||
router.push({ path: '/workouts', query })
|
||||
}
|
||||
|
||||
function getWorkoutsQuery(newQuery: LocationQuery): TWorkoutsPayload {
|
||||
query = getQuery(newQuery, orderByList, defaultOrder.order_by, {
|
||||
defaultSort: defaultOrder.order,
|
||||
})
|
||||
Object.keys(newQuery)
|
||||
.filter((k) => workoutsPayloadKeys.includes(k))
|
||||
.map((k) => {
|
||||
if (typeof newQuery[k] === 'string') {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
query[k] = newQuery[k]
|
||||
}
|
||||
})
|
||||
return query
|
||||
}
|
||||
|
||||
function onHover(workoutId: string | null) {
|
||||
hoverWorkoutId.value = workoutId
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.query,
|
||||
async (newQuery) => {
|
||||
query = getWorkoutsQuery(newQuery)
|
||||
loadWorkouts(query)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
Reference in New Issue
Block a user