Client - edit a workout
This commit is contained in:
67
fittrackee_client/src/components/Common/CustomTextArea.vue
Normal file
67
fittrackee_client/src/components/Common/CustomTextArea.vue
Normal file
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="custom-textarea">
|
||||
<textarea
|
||||
:id="name"
|
||||
:name="name"
|
||||
:maxLenght="charLimit"
|
||||
v-model="text"
|
||||
@input="updateText"
|
||||
/>
|
||||
<div class="remaining-chars">
|
||||
{{ t('workouts.REMAINING_CHARS') }}: {{ text.length }}/{{ charLimit }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomTextarea',
|
||||
props: {
|
||||
charLimit: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
},
|
||||
input: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['updateValue'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n()
|
||||
let text = ref('')
|
||||
|
||||
function updateText(event: Event & { target: HTMLInputElement }) {
|
||||
emit('updateValue', event.target.value)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.input,
|
||||
(value) => {
|
||||
text.value = value
|
||||
}
|
||||
)
|
||||
|
||||
return { t, text, updateText }
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base.scss';
|
||||
.custom-textarea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.remaining-chars {
|
||||
font-size: 0.8em;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -23,6 +23,16 @@
|
||||
<div class="workout-title-date">
|
||||
<div class="workout-title" v-if="workoutObject.type === 'WORKOUT'">
|
||||
{{ workoutObject.title }}
|
||||
<i
|
||||
class="fa fa-edit"
|
||||
aria-hidden="true"
|
||||
@click="
|
||||
$router.push({
|
||||
name: 'EditWorkout',
|
||||
params: { workoutId: workoutObject.workoutId },
|
||||
})
|
||||
"
|
||||
/>
|
||||
<i
|
||||
class="fa fa-trash"
|
||||
aria-hidden="true"
|
||||
@ -135,6 +145,10 @@
|
||||
.workout-link {
|
||||
padding-left: $default-padding;
|
||||
}
|
||||
|
||||
.fa {
|
||||
padding: 0 $default-padding * 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
195
fittrackee_client/src/components/Workout/WorkoutEdition.vue
Normal file
195
fittrackee_client/src/components/Workout/WorkoutEdition.vue
Normal file
@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div id="workout-edition">
|
||||
<Card :without-title="false">
|
||||
<template #title>{{ t('workouts.EDIT_WORKOUT') }}</template>
|
||||
<template #content>
|
||||
<div id="workout-form">
|
||||
<form @submit.prevent="updateWorkout">
|
||||
<div class="form-items">
|
||||
<div class="form-item">
|
||||
<label> {{ t('workouts.SPORT', 1) }}: </label>
|
||||
<select id="sport" v-model="workoutDataObject.sport_id">
|
||||
<option
|
||||
v-for="sport in translatedSports"
|
||||
:value="sport.id"
|
||||
:key="sport.id"
|
||||
>
|
||||
{{ sport.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="title"> {{ t('workouts.TITLE') }}: </label>
|
||||
<input
|
||||
id="title"
|
||||
name="title"
|
||||
type="text"
|
||||
v-model="workoutDataObject.title"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label> {{ t('workouts.NOTES') }}: </label>
|
||||
<CustomTextArea
|
||||
name="notes"
|
||||
:input="workoutDataObject.notes"
|
||||
@updateValue="updateNotes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ErrorMessage :message="errorMessages" v-if="errorMessages" />
|
||||
<div class="form-buttons">
|
||||
<button class="confirm" type="submit">
|
||||
{{ t('buttons.SUBMIT') }}
|
||||
</button>
|
||||
<button
|
||||
class="cancel"
|
||||
@click="
|
||||
$router.push({
|
||||
name: 'Workout',
|
||||
params: { workoutId: workout.id },
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ t('buttons.CANCEL') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
ComputedRef,
|
||||
PropType,
|
||||
defineComponent,
|
||||
computed,
|
||||
reactive,
|
||||
watch,
|
||||
onUnmounted,
|
||||
} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
import CustomTextArea from '@/components/Common/CustomTextArea.vue'
|
||||
import ErrorMessage from '@/components/Common/ErrorMessage.vue'
|
||||
import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants'
|
||||
import { ISport } from '@/types/sports'
|
||||
import { IWorkout } from '@/types/workouts'
|
||||
import { useStore } from '@/use/useStore'
|
||||
import { translateSports } from '@/utils/sports'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AddOrEditWorkout',
|
||||
components: {
|
||||
Card,
|
||||
CustomTextArea,
|
||||
ErrorMessage,
|
||||
},
|
||||
props: {
|
||||
sports: {
|
||||
type: Object as PropType<ISport[]>,
|
||||
required: true,
|
||||
},
|
||||
workout: {
|
||||
type: Object as PropType<IWorkout>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const translatedSports: ComputedRef<ISport[]> = computed(() =>
|
||||
translateSports(props.sports, t)
|
||||
)
|
||||
const errorMessages: ComputedRef<string | string[] | null> = computed(
|
||||
() => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES]
|
||||
)
|
||||
const workoutForm = reactive({
|
||||
sport_id: 0,
|
||||
title: '',
|
||||
notes: '',
|
||||
})
|
||||
|
||||
function updateNotes(value: string) {
|
||||
workoutForm.notes = value
|
||||
}
|
||||
function updateWorkout() {
|
||||
if (props.workout) {
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.EDIT_WORKOUT, {
|
||||
workoutId: props.workout.id,
|
||||
data: workoutForm,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.workout,
|
||||
async (newWorkout: IWorkout | undefined) => {
|
||||
if (newWorkout && newWorkout.id) {
|
||||
workoutForm.sport_id = newWorkout.sport_id
|
||||
workoutForm.title = newWorkout.title
|
||||
workoutForm.notes = newWorkout.notes
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onUnmounted(() => store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES))
|
||||
|
||||
return {
|
||||
errorMessages,
|
||||
t,
|
||||
translatedSports,
|
||||
workoutDataObject: workoutForm,
|
||||
updateNotes,
|
||||
updateWorkout,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base';
|
||||
|
||||
#workout-edition {
|
||||
margin: 25% auto;
|
||||
width: 700px;
|
||||
|
||||
::v-deep(.card) {
|
||||
.card-title {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.card-content {
|
||||
.form-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: $default-padding;
|
||||
|
||||
label {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
button {
|
||||
margin: $default-padding * 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-limit) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user