Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

move description and notes edition in workout detail #627

Merged
merged 2 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions fittrackee/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<link rel="stylesheet" href="/static/css/fork-awesome.min.css"/>
<link rel="stylesheet" href="/static/css/leaflet.css"/>
<title>FitTrackee</title>
<script type="module" crossorigin src="/static/index-G_tff9EL.js"></script>
<script type="module" crossorigin src="/static/index-DpXacOXi.js"></script>
<link rel="modulepreload" crossorigin href="/static/charts-lWJH0bM4.js">
<link rel="modulepreload" crossorigin href="/static/maps-BFpqWvfo.js">
<link rel="stylesheet" crossorigin href="/static/css/maps-HupOsEJb.css">
<link rel="stylesheet" crossorigin href="/static/css/index-x-giI9Hy.css">
<link rel="stylesheet" crossorigin href="/static/css/index-Ctp890VV.css">
</head>
<body>
<div id="app"></div>
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions fittrackee_client/src/components/Common/CustomTextArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</template>

<script setup lang="ts">
import { ref, watch } from 'vue'
import { ref, toRefs, watch } from 'vue'

interface Props {
name: string
Expand All @@ -34,7 +34,8 @@

const emit = defineEmits(['updateValue'])

const text = ref('')
const { input } = toRefs(props)
const text = ref(input.value ? input.value : '')

function updateText(event: Event) {
emit('updateValue', (event.target as HTMLInputElement).value)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,155 @@
<template>
<div id="workout-content">
<Card>
<template #title>{{ $t(`workouts.${contentType}`) }}</template>
<template #content>
<span
:class="{ notes: contentType === 'NOTES' || !content }"
v-html="
displayedContent && displayedContent !== ''
? linkifyAndClean(displayedContent)
: $t(`workouts.NO_${contentType}`)
"
/>
<template #title>
{{ $t(`workouts.${contentType}`) }}
<button
class="read-more transparent"
v-if="displayReadMoreButton"
@click="readMore = !readMore"
class="transparent icon-button"
:aria-label="$t(`buttons.EDIT`)"
@click="editContent"
>
<i
:class="`fa fa-caret-${readMore ? 'up' : 'down'}`"
aria-hidden="true"
/>
{{ $t(`buttons.${readMore ? 'HIDE' : 'READ_MORE'}`) }}
<i v-if="!isEdition" class="fa fa-edit" aria-hidden="true" />
</button>
</template>
<template #content>
<template v-if="isEdition">
<form @submit.prevent="updateWorkoutContent">
<label :for="contentType.toLowerCase()" class="visually-hidden">
{{ $t(`workouts.${contentType}`) }}
</label>
<CustomTextArea
:name="contentType.toLowerCase()"
:input="content"
:disabled="loading"
:charLimit="contentType === 'NOTES' ? 500 : 10000"
:rows="contentType === 'NOTES' ? 2 : 5"
@updateValue="updateContent"
/>
<div class="form-buttons">
<button class="confirm" type="submit" :disabled="loading">
{{ $t('buttons.SUBMIT') }}
</button>
<button class="cancel" @click.prevent="onCancel">
{{ $t('buttons.CANCEL') }}
</button>
<div class="edition-loading" v-if="loading">
<div>
<i class="fa fa-spinner fa-pulse" aria-hidden="true" />
</div>
</div>
</div>
</form>
</template>
<template v-else>
<span
class="workout-content"
:class="{ notes: contentType === 'NOTES' || !content }"
v-html="
displayedContent && displayedContent !== ''
? linkifyAndClean(displayedContent)
: $t(`workouts.NO_${contentType}`)
"
/>
<button
class="read-more transparent"
v-if="displayReadMoreButton"
@click="readMore = !readMore"
>
<i
:class="`fa fa-caret-${readMore ? 'up' : 'down'}`"
aria-hidden="true"
/>
{{ $t(`buttons.${readMore ? 'HIDE' : 'READ_MORE'}`) }}
</button>
</template>
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
</template>
</Card>
</div>
</template>

<script setup lang="ts">
import { computed, toRefs, ref } from 'vue'
import { computed, toRefs, ref, watch } from 'vue'
import type { ComputedRef, Ref } from 'vue'

import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants'
import type { IEquipmentError } from '@/types/equipments'
import type { IWorkoutContentEdition } from '@/types/workouts'
import { useStore } from '@/use/useStore'
import { linkifyAndClean } from '@/utils/inputs'

interface Props {
contentType: 'DESCRIPTION' | 'NOTES'
content?: string | null
contentType: 'DESCRIPTION' | 'NOTES'
workoutId: string
}
const props = withDefaults(defineProps<Props>(), {
content: () => '',
})

const { content, contentType } = toRefs(props)
const store = useStore()

const { content, contentType, workoutId } = toRefs(props)

const READ_MORE_LIMIT = 1000
const displayReadMoreButton: ComputedRef<boolean> = computed(
() => content.value !== null && content.value.length > READ_MORE_LIMIT
)
const workoutContentEdition: ComputedRef<IWorkoutContentEdition> = computed(
() => store.getters[WORKOUTS_STORE.GETTERS.WORKOUT_CONTENT_EDITION]
)
const loading: ComputedRef<boolean> = computed(
() =>
workoutContentEdition.value.loading &&
workoutContentEdition.value.contentType === contentType.value
)
const readMore: Ref<boolean> = ref(false)
const displayedContent: ComputedRef<string | null> = computed(() =>
readMore.value ? content.value : getTruncatedText(content.value)
)
const isEdition: Ref<boolean> = ref(false)
const editedContent: Ref<string> = ref('')
const errorMessages: ComputedRef<string | string[] | IEquipmentError | null> =
computed(() =>
workoutContentEdition.value.contentType === contentType.value
? store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
: null
)

function getTruncatedText(text: string | null) {
if (text === null || text.length <= READ_MORE_LIMIT) {
return text
}
return text.slice(0, READ_MORE_LIMIT - 10) + '…'
}
function editContent() {
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
isEdition.value = true
editedContent.value = content.value ? content.value : ''
}
function updateContent(text: string) {
editedContent.value = text
}
function onCancel() {
isEdition.value = false
editedContent.value = content.value ? content.value : ''
}
function updateWorkoutContent() {
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT_CONTENT, {
workoutId: workoutId.value,
content: editedContent.value,
contentType: contentType.value,
})
}

watch(
() => loading.value,
(newValue) => {
if (!newValue) {
isEdition.value = false
}
}
)
</script>

<style lang="scss" scoped>
Expand All @@ -66,17 +158,36 @@
#workout-content {
::v-deep(.card-title) {
text-transform: capitalize;
.icon-button {
cursor: pointer;
padding: 0;
margin-left: $default-margin * 0.5;
}
}
::v-deep(.card-content) {
white-space: pre-wrap;
.workout-content {
white-space: pre-wrap;
}
.read-more {
font-size: 0.85em;
font-weight: bold;
padding: 0 10px;
}
.edition-loading {
display: flex;
align-items: center;
}
.notes {
font-style: italic;
}
.error-message {
margin: $default-margin 0;
}
.form-buttons {
display: flex;
margin-top: $default-margin * 0.5;
gap: $default-padding;
}
}
}
</style>
17 changes: 13 additions & 4 deletions fittrackee_client/src/components/Workout/WorkoutEdition.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<div
id="workout-edition"
class="center-card"
:class="{ 'center-form': workout && workout.with_gpx }"
:class="{
'center-form': workout && workout.with_gpx,
'with-margin': !isCreation,
}"
>
<Card>
<template #title>{{
Expand Down Expand Up @@ -275,8 +278,10 @@
</option>
</select>
</div>
<div class="form-item">
<label for="notes"> {{ $t('workouts.DESCRIPTION') }}: </label>
<div class="form-item" v-if="isCreation">
<label for="description">
{{ $t('workouts.DESCRIPTION') }}:
</label>
<CustomTextArea
name="description"
:input="workoutForm.description"
Expand All @@ -286,7 +291,7 @@
@updateValue="updateDescription"
/>
</div>
<div class="form-item">
<div class="form-item" v-if="isCreation">
<label for="notes"> {{ $t('workouts.NOTES') }}: </label>
<CustomTextArea
name="notes"
Expand Down Expand Up @@ -750,6 +755,10 @@
&.center-form {
margin: 50px auto;
}

&.with-margin {
margin-top: 0;
}
}

.errored {
Expand Down
31 changes: 31 additions & 0 deletions fittrackee_client/src/store/modules/workouts/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
} from '@/store/modules/workouts/types'
import type {
IWorkout,
IWorkoutContentPayload,
IWorkoutForm,
IWorkoutPayload,
TWorkoutsPayload,
Expand Down Expand Up @@ -176,6 +177,36 @@ export const actions: ActionTree<IWorkoutsState, IRootState> &
context.commit(WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_LOADING, false)
)
},
[WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT_CONTENT](
context: ActionContext<IWorkoutsState, IRootState>,
payload: IWorkoutContentPayload
): void {
context.commit(WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_CONTENT_LOADING, true)
context.commit(
WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_CONTENT_TYPE,
payload.contentType
)
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
const data = {
[payload.contentType === 'NOTES' ? 'notes' : 'description']:
payload.content,
}
authApi
.patch(`workouts/${payload.workoutId}`, data)
.then((res) => {
const workout: IWorkout = res.data.data.workouts[0]
context.commit(WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_CONTENT, workout)
})
.catch((error) => {
handleError(context, error)
})
.finally(() =>
context.commit(
WORKOUTS_STORE.MUTATIONS.SET_WORKOUT_CONTENT_LOADING,
false
)
)
},
[WORKOUTS_STORE.ACTIONS.ADD_WORKOUT](
context: ActionContext<IWorkoutsState, IRootState>,
payload: IWorkoutForm
Expand Down
5 changes: 5 additions & 0 deletions fittrackee_client/src/store/modules/workouts/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export enum WorkoutsActions {
ADD_WORKOUT_WITHOUT_GPX = 'ADD_WORKOUT_WITHOUT_GPX',
DELETE_WORKOUT = 'DELETE_WORKOUT',
EDIT_WORKOUT = 'EDIT_WORKOUT',
EDIT_WORKOUT_CONTENT = 'EDIT_WORKOUT_CONTENT',
GET_CALENDAR_WORKOUTS = 'GET_CALENDAR_WORKOUTS',
GET_USER_WORKOUTS = 'GET_USER_WORKOUTS',
GET_TIMELINE_WORKOUTS = 'GET_TIMELINE_WORKOUTS',
Expand All @@ -14,6 +15,7 @@ export enum WorkoutsGetters {
CALENDAR_WORKOUTS = 'CALENDAR_WORKOUTS',
TIMELINE_WORKOUTS = 'TIMELINE_WORKOUTS',
USER_WORKOUTS = 'USER_WORKOUTS',
WORKOUT_CONTENT_EDITION = 'WORKOUT_CONTENT_EDITION',
WORKOUT_DATA = 'WORKOUT_DATA',
WORKOUTS_PAGINATION = 'WORKOUTS_PAGINATION',
}
Expand All @@ -29,6 +31,9 @@ export enum WorkoutsMutations {
SET_WORKOUT = 'SET_WORKOUT',
SET_WORKOUT_GPX = 'SET_WORKOUT_GPX',
SET_WORKOUT_CHART_DATA = 'SET_WORKOUT_CHART_DATA',
SET_WORKOUT_CONTENT = 'SET_WORKOUT_CONTENT',
SET_WORKOUT_CONTENT_LOADING = 'SET_WORKOUT_CONTENT_LOADING',
SET_WORKOUT_CONTENT_TYPE = 'SET_WORKOUT_CONTENT_TYPE',
SET_WORKOUT_LOADING = 'SET_WORKOUT_LOADING',
SET_WORKOUTS_PAGINATION = 'SET_WORKOUTS_PAGINATION',
}
3 changes: 3 additions & 0 deletions fittrackee_client/src/store/modules/workouts/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const getters: GetterTree<IWorkoutsState, IRootState> &
[WORKOUTS_STORE.GETTERS.USER_WORKOUTS]: (state: IWorkoutsState) => {
return state.user_workouts
},
[WORKOUTS_STORE.GETTERS.WORKOUT_CONTENT_EDITION]: (state: IWorkoutsState) => {
return state.workoutContent
},
[WORKOUTS_STORE.GETTERS.WORKOUT_DATA]: (state: IWorkoutsState) => {
return state.workoutData
},
Expand Down
Loading