feat(modals): replace datepicker and time fields with employee timeblock component for improved time management

This commit is contained in:
Matej Glasnák committed 2026-01-14 16:54:26 +01:00
1 parent fdb4452447
commit 1ce56c544c
2 files changed
+111 -81

No files matched your search

@@ -46,49 +46,14 @@
variant="solo" variant="solo"
class="modal-input" class="modal-input"
/> />
<!-- <Z-autocomplete label="Vyhľadať zamestnanca" :value="blocker?.employee ? `${blocker?.employee?.name} ${blocker?.employee?.surname}` : ''" :options="enrichedEmployees.map(employee => ({key: employee.id, value: employee._fullName}))" :clear-on-submit="false" :class="{ error: v$.blocker.employee.$errors.length }" class="modal-input" @option-selected="onEmployeeSelected" /> -->
<Z-datepicker <!-- Timeblock Component -->
v-model="datepicker" <a-employee-timeblock
class="modal-input mb-5" ref="employeeTimeblockRef"
:auto-apply="true" v-model="timeblockData"
format="dd.MM.yyyy" :employee="selectedEmployee"
locale="sk" :deletable="false"
:class="{ error: v$.datepicker.$errors?.length }" :index="0"
:validations="v$.datepicker"
select-text="Vybrať"
cancel-text="Zrušiť"
placeholder="Dátum"
label="Dátum"
:enable-time-picker="false"
/>
<v-text-field
v-model="starts_at_time"
type="time"
variant="solo"
bg-color="white"
:hint="
v$.starts_at_time.$errors
? errorUtils.parseErrors(v$.starts_at_time.$errors)
: null
"
:class="{ error: v$.starts_at_time.$errors.length }"
class="mb-1 modal-input"
label="Začiatok"
/>
<v-text-field
v-model="ends_at_time"
type="time"
variant="solo"
bg-color="white"
:hint="
v$.ends_at_time.$errors
? errorUtils.parseErrors(v$.ends_at_time.$errors)
: null
"
:class="{ error: v$.ends_at_time.$errors.length }"
class="modal-input"
label="Koniec"
/> />
</v-card-actions> </v-card-actions>
@@ -107,7 +72,6 @@
</template> </template>
<script> <script>
import ZDatepicker from '@/plugins/tm@_components/z-datepicker.vue';
import useVuelidate from '@vuelidate/core'; import useVuelidate from '@vuelidate/core';
import { required } from '@vuelidate/validators'; import { required } from '@vuelidate/validators';
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'; import errorUtils from '@/plugins/lib@auth/_utils/error.utils';
@@ -118,14 +82,14 @@ import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin';
import AResizeHandle from '@/plugins/tm@modal/_components/a-resize-handle.vue'; import AResizeHandle from '@/plugins/tm@modal/_components/a-resize-handle.vue';
import AResetSizeButton from '@/plugins/tm@modal/_components/a-reset-size-button.vue'; import AResetSizeButton from '@/plugins/tm@modal/_components/a-reset-size-button.vue';
import ACloseButton from '@/plugins/tm@modal/_components/a-close-button.vue'; import ACloseButton from '@/plugins/tm@modal/_components/a-close-button.vue';
import AEmployeeTimeblock from '@/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue';
export default { export default {
components: { components: {
ZDatepicker,
// ZAutocomplete
AResizeHandle, AResizeHandle,
AResetSizeButton, AResetSizeButton,
ACloseButton, ACloseButton,
AEmployeeTimeblock,
}, },
mixins: [AutocompleteMixin, modalMixin], mixins: [AutocompleteMixin, modalMixin],
@@ -137,9 +101,13 @@ export default {
v$: useVuelidate(), v$: useVuelidate(),
errorUtils, errorUtils,
blocker: null, blocker: null,
datepicker: null, timeblockData: {
starts_at_time: null, id: null,
ends_at_time: null, date: new Date(),
starts_at_time: null,
ends_at_time: null,
employee_id: null,
},
employee: null, employee: null,
modalName: 'z-blocker-modal', modalName: 'z-blocker-modal',
modalSize: 'large', modalSize: 'large',
@@ -150,6 +118,13 @@ export default {
...mapGetters('tm', ['enrichedEmployees']), ...mapGetters('tm', ['enrichedEmployees']),
...mapGetters('auth', ['user']), ...mapGetters('auth', ['user']),
selectedEmployee() {
if (!this.blocker?.employee_id) return null;
return this.enrichedEmployees.find(
(emp) => emp.id === this.blocker.employee_id,
);
},
}, },
validations() { validations() {
@@ -158,25 +133,27 @@ export default {
name: { required }, name: { required },
start: { required }, start: { required },
end: { required }, end: { required },
// employee: { required }
}, },
starts_at_time: { required },
ends_at_time: { required },
datepicker: { required },
}; };
}, },
watch: { watch: {
ends_at_time() { 'blocker.employee_id'(newEmployeeId) {
// Update timeblock employee_id when blocker employee changes
if (this.timeblockData) {
this.timeblockData.employee_id = newEmployeeId;
}
},
'timeblockData.date'() {
this._syncDatesTimes(); this._syncDatesTimes();
}, },
starts_at_time() { 'timeblockData.starts_at_time'() {
this._syncDatesTimes(); this._syncDatesTimes();
}, },
datepicker() { 'timeblockData.ends_at_time'() {
this._syncDatesTimes(); this._syncDatesTimes();
}, },
}, },
@@ -185,9 +162,13 @@ export default {
async showModal({ blocker }) { async showModal({ blocker }) {
this.visible = true; this.visible = true;
this.blocker = null; this.blocker = null;
this.datepicker = null; this.timeblockData = {
this.starts_at_time = null; id: null,
this.ends_at_time = null; date: new Date(),
starts_at_time: null,
ends_at_time: null,
employee_id: null,
};
this.$nextTick(() => { this.$nextTick(() => {
this.v$.$reset(); this.v$.$reset();
@@ -199,11 +180,18 @@ export default {
`v1/blockers/${blocker.id}`, `v1/blockers/${blocker.id}`,
); );
this.starts_at_time = moment(blocker.start).format('HH:mm'); const startMoment = moment(blocker.start);
this.ends_at_time = moment(blocker.end).format('HH:mm'); const endMoment = moment(blocker.end);
this.datepicker = new Date(blocker.start);
this.timeblockData = {
id: null,
date: startMoment.toDate(),
starts_at_time: startMoment.format('HH:mm'),
ends_at_time: endMoment.format('HH:mm'),
employee_id: blocker.employee.id,
};
blocker.employee_id = blocker.employee.id; blocker.employee_id = blocker.employee.id;
//fixnut validacie employee_id
this.blocker = blocker; this.blocker = blocker;
} else { } else {
this.blocker = { start: null, employee_id: this.user.id }; this.blocker = { start: null, employee_id: this.user.id };
@@ -213,9 +201,16 @@ export default {
blocker.datepicker && blocker.datepicker &&
blocker.employee_id blocker.employee_id
) { ) {
this.datepicker = blocker.datepicker; this.timeblockData = {
this.starts_at_time = blocker.starts_at_time; id: null,
date: blocker.datepicker,
starts_at_time: blocker.starts_at_time,
ends_at_time: blocker.ends_at_time,
employee_id: blocker.employee_id,
};
this.blocker.employee_id = blocker.employee_id; this.blocker.employee_id = blocker.employee_id;
} else {
this.timeblockData.employee_id = this.user.id;
} }
} }
} catch (error) { } catch (error) {
@@ -224,11 +219,13 @@ export default {
}, },
async save() { async save() {
if (!(await this.v$.$validate())) return; // Validate using the employee timeblock component's validation
const isTimeblockValid =
await this.$refs.employeeTimeblockRef?.validate();
if (!isTimeblockValid || !(await this.v$.$validate())) return;
try { try {
// this.blocker.employee_id = this.blocker.employee?.id
if (this.blocker.id) { if (this.blocker.id) {
const { blocker } = await this.$axios.put_auth( const { blocker } = await this.$axios.put_auth(
`v1/blockers/${this.blocker.id}`, `v1/blockers/${this.blocker.id}`,
@@ -253,29 +250,34 @@ export default {
closeModal() { closeModal() {
this.visible = false; this.visible = false;
this.timeblockData = {
id: null,
date: new Date(),
starts_at_time: null,
ends_at_time: null,
employee_id: null,
};
this.$emit('close', this.blocker); this.$emit('close', this.blocker);
}, },
onEmployeeSelected({ key }) {
this.blocker.employee = this.enrichedEmployees.find(
(employee) => employee.id == key,
);
},
_syncDatesTimes() { _syncDatesTimes() {
if (!this.starts_at_time || !this.ends_at_time || !this.datepicker) if (
!this.timeblockData.starts_at_time ||
!this.timeblockData.ends_at_time ||
!this.timeblockData.date
)
return; return;
const splittedStart = this.starts_at_time.split(':'); const splittedStart = this.timeblockData.starts_at_time.split(':');
const splittedEnd = this.ends_at_time.split(':'); const splittedEnd = this.timeblockData.ends_at_time.split(':');
this.blocker.start = moment(this.datepicker) this.blocker.start = moment(this.timeblockData.date)
.set({ .set({
hours: parseInt(splittedStart[0]), hours: parseInt(splittedStart[0]),
minutes: parseInt(splittedStart[1]), minutes: parseInt(splittedStart[1]),
}) })
.format('YYYY-MM-DD HH:mm'); .format('YYYY-MM-DD HH:mm');
this.blocker.end = moment(this.datepicker) this.blocker.end = moment(this.timeblockData.date)
.set({ .set({
hours: parseInt(splittedEnd[0]), hours: parseInt(splittedEnd[0]),
minutes: parseInt(splittedEnd[1]), minutes: parseInt(splittedEnd[1]),
@@ -340,3 +342,13 @@ function oo_cm() {
return v; return v;
} /*eslint unicorn/no-abusive-eslint-disable:,eslint-comments/disable-enable-pair:,eslint-comments/no-unlimited-disable:,eslint-comments/no-aggregating-enable:,eslint-comments/no-duplicate-disable:,eslint-comments/no-unused-disable:,eslint-comments/no-unused-enable:,*/ } /*eslint unicorn/no-abusive-eslint-disable:,eslint-comments/disable-enable-pair:,eslint-comments/no-unlimited-disable:,eslint-comments/no-aggregating-enable:,eslint-comments/no-duplicate-disable:,eslint-comments/no-unused-disable:,eslint-comments/no-unused-enable:,*/
</script> </script>
<style lang="sass" scoped>
::v-deep
// Hide employee select and add/remove buttons in timeblock component
.timeblock__header__top
display: none
.actions
display: none
</style>
+19 -1
View File
@@ -832,8 +832,16 @@ export default {
} }
if (!data.deadline) { if (!data.deadline) {
this.subtask = this._enrichTask(data);
this.editMode = editMode || !data.id; this.editMode = editMode || !data.id;
return (this.subtask = this._enrichTask(data));
// Set Quill editor content after subtask is loaded and editMode is set
this.$nextTick(() => {
if (this.$refs['description']) {
this.$refs['description'].setHTML(this.subtask.description || '');
}
});
return;
} }
const splittedDeadline = data.deadline.split(' '); const splittedDeadline = data.deadline.split(' ');
@@ -963,6 +971,16 @@ export default {
} }
} }
this.editMode = editMode || !this.subtask.id; this.editMode = editMode || !this.subtask.id;
// Set Quill editor content after editMode is set
// This ensures the description is loaded when opening in edit mode
if (this.editMode) {
this.$nextTick(() => {
if (this.$refs['description']) {
this.$refs['description'].setHTML(this.subtask?.description || '');
}
});
}
} catch (error) { } catch (error) {
this.$toast.error(this.$parseError(error)); this.$toast.error(this.$parseError(error));
} }