mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
[timeblocks]
This commit is contained in:
1 parent
cb44878dcb
commit
6a89b7bc47
5 files changed
+218
-17
No files matched your search
@@ -7,6 +7,12 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
setFocusEvent() {
|
||||||
|
document.querySelectorAll('.v-autocomplete input').forEach(input => {
|
||||||
|
input.addEventListener('focus', this.onInputFocus)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
onDateFocus($event) {
|
onDateFocus($event) {
|
||||||
if ($event == undefined) {
|
if ($event == undefined) {
|
||||||
this.preventInputFocus = true
|
this.preventInputFocus = true
|
||||||
|
|||||||
@@ -98,7 +98,10 @@ export default {
|
|||||||
this.starts_at_time = null
|
this.starts_at_time = null
|
||||||
this.ends_at_time = null
|
this.ends_at_time = null
|
||||||
|
|
||||||
this.$nextTick(() => this.v$.$reset())
|
this.$nextTick(() => {
|
||||||
|
this.setFocusEvent()
|
||||||
|
this.v$.$reset()
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (blocker?.id) {
|
if (blocker?.id) {
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ export default {
|
|||||||
this.visible = true
|
this.visible = true
|
||||||
this.mastertask = null
|
this.mastertask = null
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
this.setFocusEvent()
|
||||||
this.v$.$reset()
|
this.v$.$reset()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<div class="timeblock w-100">
|
||||||
|
<div v-if="deletable && editable" class="x-button">
|
||||||
|
<button @click="$emit('removeTimeblock')" class="x-button">x</button>
|
||||||
|
</div>
|
||||||
|
<Datepicker :model-value="timeblock.date" @focus="$emit('onDateFocus')" @update:model-value="updateDate" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum" :auto-apply="true" :enable-time-picker="false" class="mb-5 modal-input" />
|
||||||
|
<div class="splitted-time-inputs">
|
||||||
|
<v-text-field :value="timeblock.starts_at_time" @input="updateStartTime" @focus="onInputFocus" :class="{ error: v$?.timeblock.starts_at_time.$errors.length }" type="time" variant="solo" bg-color="white" label="Začiatok" />
|
||||||
|
<v-text-field :value="timeblock.ends_at_time" @input="updateEndTime" @focus="onInputFocus" :class="{ error: v$?.timeblock.ends_at_time.$errors.length }" type="time" variant="solo" bg-color="white" label="Koniec" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import useVuelidate from '@vuelidate/core'
|
||||||
|
import { requiredIf } from '@vuelidate/validators'
|
||||||
|
import Datepicker from '@vuepic/vue-datepicker'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Datepicker
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
props: {
|
||||||
|
timeblock: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
deletable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
v$: useVuelidate(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
validations() {
|
||||||
|
return {
|
||||||
|
timeblock: {
|
||||||
|
starts_at_time: { required: requiredIf(this.timeblock.date) },
|
||||||
|
ends_at_time: { required: requiredIf(this.timeblock.date) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onFocus() {
|
||||||
|
console.log('focus')
|
||||||
|
},
|
||||||
|
updateDate(date) {
|
||||||
|
const timeblock = { ...this.timeblock }
|
||||||
|
if (date) timeblock.date = date
|
||||||
|
this.$emit('updateTimeblock', timeblock)
|
||||||
|
},
|
||||||
|
updateStartTime(event) {
|
||||||
|
const timeblock = { ...this.timeblock }
|
||||||
|
timeblock.starts_at_time = event.target.value
|
||||||
|
this.$emit('updateTimeblock', timeblock)
|
||||||
|
},
|
||||||
|
updateEndTime(event) {
|
||||||
|
const timeblock = { ...this.timeblock }
|
||||||
|
timeblock.ends_at_time = event.target.value
|
||||||
|
this.$emit('updateTimeblock', timeblock)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
.timeblock
|
||||||
|
position: relative
|
||||||
|
border-top: 1px solid #e0e0e0
|
||||||
|
padding: 20px 0 5px 0
|
||||||
|
.splitted-time-inputs
|
||||||
|
display: flex !important
|
||||||
|
flex-direction: row !important
|
||||||
|
gap: 15px
|
||||||
|
margin-bottom: -11px
|
||||||
|
|
||||||
|
.x-button
|
||||||
|
display: flex
|
||||||
|
justify-content: flex-end
|
||||||
|
align-items: center
|
||||||
|
position: relative
|
||||||
|
bottom: 5px
|
||||||
|
|
||||||
|
button
|
||||||
|
margin-right: 5px
|
||||||
|
|
||||||
|
.error input
|
||||||
|
border: 1px solid red !important
|
||||||
|
</style>e
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<div class="splitted-inputs">
|
<div class="splitted-inputs">
|
||||||
<!-- TODO: Bialko validacie -->
|
<!-- TODO: Bialko validacie -->
|
||||||
<Datepicker v-model="subtask.deadline" :auto-apply="true" @focus="onDateFocus" :min-date="new Date()" class="modal-input left" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Deadline" :class="{ error: v$.subtask.deadline.$errors?.length }" :validations="v$.subtask.deadline" />
|
<Datepicker v-model="subtask.deadline" @focus="onDateFocus" :auto-apply="true" :min-date="new Date()" class="modal-input left" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Deadline" :class="{ error: v$.subtask.deadline.$errors?.length }" :validations="v$.subtask.deadline" />
|
||||||
|
|
||||||
<v-text-field class="modal-input right" variant="solo" v-model="subtask._deadline_time" label="Čas" append-inner-icon="mdi-clock-outline" type="time" @focus="onInputFocus" :class="{ error: v$.subtask._deadline_time.$errors.length }" :hint="v$.subtask._deadline_time.$errors ? errorUtils.parseErrors(v$.subtask._deadline_time.$errors) : null" />
|
<v-text-field class="modal-input right" variant="solo" v-model="subtask._deadline_time" label="Čas" append-inner-icon="mdi-clock-outline" type="time" @focus="onInputFocus" :class="{ error: v$.subtask._deadline_time.$errors.length }" :hint="v$.subtask._deadline_time.$errors ? errorUtils.parseErrors(v$.subtask._deadline_time.$errors) : null" />
|
||||||
</div>
|
</div>
|
||||||
@@ -62,11 +62,28 @@
|
|||||||
<!-- <div class="w-100">
|
<!-- <div class="w-100">
|
||||||
<Datepicker v-model="subtask.date" :class="{ error: v$.subtask.date.$errors.length }" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum" :auto-apply="true" :enable-time-picker="false" @focus="onDateFocus" class="mb-5 modal-input" />
|
<Datepicker v-model="subtask.date" :class="{ error: v$.subtask.date.$errors.length }" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum" :auto-apply="true" :enable-time-picker="false" @focus="onDateFocus" class="mb-5 modal-input" />
|
||||||
<div class="splitted-time-inputs">
|
<div class="splitted-time-inputs">
|
||||||
<v-text-field v-model="subtask.starts_at_time" :class="{ error: v$.subtask.starts_at_time.$errors.length }" type="time" variant="solo" bg-color="white" label="Začiatok" />
|
<v-text-field :class="{ error: v$.subtask.starts_at_time.$errors.length }" type="time" variant="solo" bg-color="white" label="Začiatok" />
|
||||||
<v-text-field v-model="subtask.ends_at_time" :class="{ error: v$.subtask.ends_at_time.$errors.length }" type="time" variant="solo" bg-color="white" label="Koniec" />
|
<v-text-field :class="{ error: v$.subtask.ends_at_time.$errors.length }" type="time" variant="solo" bg-color="white" label="Koniec" />
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
|
<template v-if="!subtask.id">
|
||||||
|
<div class="timeblocks w-100">
|
||||||
|
<A-timeblock v-for="(timeblock, index) in timeblocks" :key="index" @updateTimeblock="val => updateTimeblock(val, index)" @removeTimeblock="removeTimeblock(index)" @onDateFocus="onDateFocus" :timeblock="timeblock" :deletable="index > 0" editable :ref="`timetable-${index}`" />
|
||||||
|
<div class="end-button w-100">
|
||||||
|
<button @click="timeblocks.push({date: null, starts_at_time: null, ends_at_time: null})">Pridať časový blok</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-else v-for="(timeblock, index) in subtask.timeblocks" :key="index" class="timeblock w-100">
|
||||||
|
<Datepicker :model-value="timeblock.start.substring(0, 10)" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum" :auto-apply="true" :enable-time-picker="false" readonly class="mb-5 modal-input" />
|
||||||
|
<div class="splitted-time-inputs w-100">
|
||||||
|
<v-text-field :value="timeblock.start.substring(11, 16)" type="time" variant="solo" bg-color="white" label="Začiatok" />
|
||||||
|
<v-text-field :value="timeblock.end.substring(11, 16)" type="time" variant="solo" bg-color="white" label="Koniec" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="w-100 modal-input">
|
<div class="w-100 modal-input">
|
||||||
<QuillEditor ref="description" v-model="subtask.description" placeholder="Popis" theme="snow" class="rich-text-editor" @input="subtask.description = $event.srcElement.innerHTML" />
|
<QuillEditor ref="description" v-model="subtask.description" placeholder="Popis" theme="snow" class="rich-text-editor" @input="subtask.description = $event.srcElement.innerHTML" />
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +140,7 @@ import Datepicker from '@vuepic/vue-datepicker'
|
|||||||
import ZFilesUpload from '@/plugins/tm@_components/z-files-upload.vue'
|
import ZFilesUpload from '@/plugins/tm@_components/z-files-upload.vue'
|
||||||
import ATaskTimeblocks from '@/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue'
|
import ATaskTimeblocks from '@/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue'
|
||||||
import useVuelidate from '@vuelidate/core'
|
import useVuelidate from '@vuelidate/core'
|
||||||
import { required, requiredIf } from '@vuelidate/validators'
|
import { required, requiredIf, optional } from '@vuelidate/validators'
|
||||||
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
|
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
|
||||||
import { QuillEditor } from '@vueup/vue-quill'
|
import { QuillEditor } from '@vueup/vue-quill'
|
||||||
import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus'
|
import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus'
|
||||||
@@ -135,15 +152,19 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css'
|
|||||||
import AutocompleteMixin from '@/plugins/tm@_mixins/autocomplete.mixin'
|
import AutocompleteMixin from '@/plugins/tm@_mixins/autocomplete.mixin'
|
||||||
|
|
||||||
import ZTaskReadonlyInfo from './_components/z-task-readonly-info.vue'
|
import ZTaskReadonlyInfo from './_components/z-task-readonly-info.vue'
|
||||||
|
import ATimeblock from './_components/a-timeblock.vue'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['close', 'refresh'],
|
emits: ['close', 'refresh'],
|
||||||
mixins: [VuetifyFixFocusMixin, SelectsMixin, AutocompleteMixin, modalMixin],
|
mixins: [VuetifyFixFocusMixin, SelectsMixin, AutocompleteMixin, modalMixin],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ZTaskReadonlyInfo,
|
ZTaskReadonlyInfo,
|
||||||
|
ATimeblock,
|
||||||
ZFilesUpload,
|
ZFilesUpload,
|
||||||
Datepicker,
|
Datepicker,
|
||||||
// ATaskTimeblocks,
|
// ATaskTimeblocks,
|
||||||
@@ -160,9 +181,13 @@ export default {
|
|||||||
errorUtils,
|
errorUtils,
|
||||||
v$: useVuelidate(),
|
v$: useVuelidate(),
|
||||||
employeeValidations: [],
|
employeeValidations: [],
|
||||||
preventInputFocus: false,
|
|
||||||
showDatePicker: false,
|
showDatePicker: false,
|
||||||
selectedNewDate: []
|
selectedNewDate: [],
|
||||||
|
timeblocks: [{
|
||||||
|
date: null,
|
||||||
|
starts_at_time: null,
|
||||||
|
ends_at_time: null
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -176,9 +201,6 @@ export default {
|
|||||||
master_task_id: { required },
|
master_task_id: { required },
|
||||||
deadline: { required },
|
deadline: { required },
|
||||||
_deadline_time: { required },
|
_deadline_time: { required },
|
||||||
// starts_at_time: { requiredIf: requiredIf(this.subtask.date) },
|
|
||||||
// ends_at_time: { requiredIf: requiredIf(this.subtask.starts_at_time) }
|
|
||||||
// description: { required }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -210,11 +232,28 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
updateTimeblock(val, index) {
|
||||||
|
this.timeblocks[index] = val
|
||||||
|
},
|
||||||
|
|
||||||
|
removeTimeblock(index) {
|
||||||
|
this.timeblocks.splice(index, 1)
|
||||||
|
},
|
||||||
|
|
||||||
async showModal({ subtask, editMode = false, mastertask = {} }) {
|
async showModal({ subtask, editMode = false, mastertask = {} }) {
|
||||||
|
console.log(subtask)
|
||||||
this.isSaving = false
|
this.isSaving = false
|
||||||
this.subtask = null
|
this.subtask = null
|
||||||
this.visible = true
|
this.visible = true
|
||||||
this.$nextTick(() => this.v$.$reset())
|
this.timeblocks = [{
|
||||||
|
date: null,
|
||||||
|
starts_at_time: null,
|
||||||
|
ends_at_time: null
|
||||||
|
}]
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.v$.$reset()
|
||||||
|
this.setFocusEvent()
|
||||||
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (subtask?.id) {
|
if (subtask?.id) {
|
||||||
@@ -263,9 +302,10 @@ export default {
|
|||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
const employeesValid = await this._validateEmployees()
|
const employeesValid = await this._validateEmployees()
|
||||||
|
const timeblocksValid = this.subtask?.id ? true : await this._validateTimeblocks()
|
||||||
|
|
||||||
if (!(await this.v$.$validate())) return
|
if (!(await this.v$.$validate())) return
|
||||||
if (!employeesValid) return
|
if (!employeesValid || !timeblocksValid) return
|
||||||
|
|
||||||
this.subtask.description = this.$refs['description'].getHTML()
|
this.subtask.description = this.$refs['description'].getHTML()
|
||||||
this._mergeDeadlineTimeDate()
|
this._mergeDeadlineTimeDate()
|
||||||
@@ -286,7 +326,13 @@ export default {
|
|||||||
this.$store.commit('tm/update_subtask', subtask)
|
this.$store.commit('tm/update_subtask', subtask)
|
||||||
this.$events.emit('on-subtask-edited', this.subtask)
|
this.$events.emit('on-subtask-edited', this.subtask)
|
||||||
} else {
|
} else {
|
||||||
const { subtasks } = await this.$axios.post_auth(`v1/mastertasks/${this.subtask.master_task_id}/subtasks`, this.subtask)
|
this.subtask.employees.forEach(employee => {
|
||||||
|
if (employee.id)
|
||||||
|
employee.timeblocks = this.timeblocks.filter(timeblock => timeblock.date).map(timeblock => { return { start: `${moment(timeblock.date).format('YYYY-MM-DD')} ${timeblock.starts_at_time}:00`, end: `${moment(timeblock.date).format('YYYY-MM-DD')} ${timeblock.ends_at_time}:00`, id: null } })
|
||||||
|
})
|
||||||
|
const subtaskCopy = JSON.parse(JSON.stringify(this.subtask))
|
||||||
|
delete subtaskCopy._master_task
|
||||||
|
const { subtasks } = await this.$axios.post_auth(`v1/mastertasks/${this.subtask.master_task_id}/subtasks`, subtaskCopy)
|
||||||
this.subtask.id = subtasks[0].id
|
this.subtask.id = subtasks[0].id
|
||||||
|
|
||||||
subtasks.forEach((subtask) => {
|
subtasks.forEach((subtask) => {
|
||||||
@@ -364,6 +410,15 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async _validateTimeblocks() {
|
||||||
|
let invalid = false
|
||||||
|
for(let i = 0; i < this.timeblocks.length; i++) {
|
||||||
|
const ref = this.$refs[`timetable-${i}`]
|
||||||
|
invalid = await ref[0].v$.$validate() || invalid
|
||||||
|
}
|
||||||
|
return invalid
|
||||||
|
},
|
||||||
|
|
||||||
_enrichTask(subtask) {
|
_enrichTask(subtask) {
|
||||||
return {
|
return {
|
||||||
...subtask,
|
...subtask,
|
||||||
@@ -470,8 +525,41 @@ export default {
|
|||||||
.files
|
.files
|
||||||
margin-top: 104px !important
|
margin-top: 104px !important
|
||||||
|
|
||||||
// .splitted-time-inputs
|
.timeblocks
|
||||||
// display: flex !important
|
display: flex
|
||||||
// flex-direction: row !important
|
flex-direction: column
|
||||||
// gap: 15px
|
align-items: center
|
||||||
|
border-bottom: 1px solid #e0e0e0
|
||||||
|
|
||||||
|
.end-button
|
||||||
|
width: 100%
|
||||||
|
display: flex
|
||||||
|
justify-content: flex-end
|
||||||
|
align-items: center
|
||||||
|
border-top: 1px solid #e0e0e0
|
||||||
|
height: 70px
|
||||||
|
|
||||||
|
button
|
||||||
|
background: #7f39fb !important
|
||||||
|
border-radius: 4px !important
|
||||||
|
color: #fff !important
|
||||||
|
height: 50px
|
||||||
|
width: 203px
|
||||||
|
|
||||||
|
.splitted-time-inputs
|
||||||
|
display: flex !important
|
||||||
|
flex-direction: row !important
|
||||||
|
gap: 15px
|
||||||
|
margin-bottom: -11px
|
||||||
|
|
||||||
|
.timeblock
|
||||||
|
position: relative
|
||||||
|
border-top: 1px solid #e0e0e0
|
||||||
|
padding: 20px 0 10px 0
|
||||||
|
|
||||||
|
.splitted-time-inputs
|
||||||
|
display: flex !important
|
||||||
|
flex-direction: row !important
|
||||||
|
gap: 15px
|
||||||
|
margin-bottom: -11px
|
||||||
</style>
|
</style>
|
||||||
Reference in new issue
Block a user