diff --git a/app/dist/js/index.js b/app/dist/js/index.js index 52f0c55..c5a4089 100644 --- a/app/dist/js/index.js +++ b/app/dist/js/index.js @@ -543,7 +543,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var cor /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var core_js_modules_es_json_stringify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/es.json.stringify.js */ \"./node_modules/core-js/modules/es.json.stringify.js\");\n/* harmony import */ var core_js_modules_es_json_stringify_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_json_stringify_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/esnext.iterator.constructor.js */ \"./node_modules/core-js/modules/esnext.iterator.constructor.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var core_js_modules_esnext_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! core-js/modules/esnext.iterator.filter.js */ \"./node_modules/core-js/modules/esnext.iterator.filter.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! core-js/modules/esnext.iterator.find.js */ \"./node_modules/core-js/modules/esnext.iterator.find.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! core-js/modules/esnext.iterator.for-each.js */ \"./node_modules/core-js/modules/esnext.iterator.for-each.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var core_js_modules_esnext_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! core-js/modules/esnext.iterator.map.js */ \"./node_modules/core-js/modules/esnext.iterator.map.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_map_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _plugins_tm_components_z_datepicker_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @/plugins/tm@_components/z-datepicker.vue */ \"./src/plugins/tm@_components/z-datepicker.vue\");\n/* harmony import */ var _plugins_tm_components_z_files_upload_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @/plugins/tm@_components/z-files-upload.vue */ \"./src/plugins/tm@_components/z-files-upload.vue\");\n/* harmony import */ var _vuelidate_core__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @vuelidate/core */ \"./node_modules/@vuelidate/core/dist/index.esm.js\");\n/* harmony import */ var _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @vuelidate/validators */ \"./node_modules/@vuelidate/validators/dist/index.esm.js\");\n/* harmony import */ var _plugins_lib_auth_utils_error_utils__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @/plugins/lib@auth/_utils/error.utils */ \"./src/plugins/lib@auth/_utils/error.utils.ts\");\n/* harmony import */ var _vueup_vue_quill__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @vueup/vue-quill */ \"./node_modules/@vueup/vue-quill/dist/vue-quill.esm-bundler.js\");\n/* harmony import */ var _master_task_mixins_selects_mixin__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../master-task/_mixins/selects.mixin */ \"./src/plugins/tm@modal/master-task/_mixins/selects.mixin.ts\");\n/* harmony import */ var _plugins_tm_utils_tm_utils__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! @/plugins/tm@_utils/tm.utils */ \"./src/plugins/tm@_utils/tm.utils.ts\");\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\");\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(moment__WEBPACK_IMPORTED_MODULE_15__);\n/* harmony import */ var _vueup_vue_quill_dist_vue_quill_snow_css__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! @vueup/vue-quill/dist/vue-quill.snow.css */ \"./node_modules/@vueup/vue-quill/dist/vue-quill.snow.css\");\n/* harmony import */ var _vueup_vue_quill_dist_vue_quill_snow_css__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(_vueup_vue_quill_dist_vue_quill_snow_css__WEBPACK_IMPORTED_MODULE_16__);\n/* harmony import */ var _plugins_tm_mixins_autocomplete_mixin__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! @/plugins/tm@_mixins/autocomplete.mixin */ \"./src/plugins/tm@_mixins/autocomplete.mixin.ts\");\n/* harmony import */ var _components_z_task_readonly_info_vue__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./_components/z-task-readonly-info.vue */ \"./src/plugins/tm@modal/task/_components/z-task-readonly-info.vue\");\n/* harmony import */ var _components_employees_a_employee_vue__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./_components/employees/a-employee.vue */ \"./src/plugins/tm@modal/task/_components/employees/a-employee.vue\");\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm-bundler.js\");\n/* harmony import */ var _plugins_tm_modal_mixins_modal_mixin__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! @/plugins/tm@modal/_mixins/modal.mixin */ \"./src/plugins/tm@modal/_mixins/modal.mixin.ts\");\n/* harmony import */ var _plugins_tm_modal_components_a_resize_handle_vue__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! @/plugins/tm@modal/_components/a-resize-handle.vue */ \"./src/plugins/tm@modal/_components/a-resize-handle.vue\");\n/* harmony import */ var _plugins_tm_modal_components_a_reset_size_button_vue__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! @/plugins/tm@modal/_components/a-reset-size-button.vue */ \"./src/plugins/tm@modal/_components/a-reset-size-button.vue\");\n/* harmony import */ var _plugins_tm_modal_components_a_close_button_vue__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! @/plugins/tm@modal/_components/a-close-button.vue */ \"./src/plugins/tm@modal/_components/a-close-button.vue\");\n/* harmony import */ var _z_remove_employee_modal_vue__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./z-remove-employee-modal.vue */ \"./src/plugins/tm@modal/task/z-remove-employee-modal.vue\");\n/* harmony import */ var _z_remove_timeblock_modal_vue__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./z-remove-timeblock-modal.vue */ \"./src/plugins/tm@modal/task/z-remove-timeblock-modal.vue\");\n/* provided dependency */ var console = __webpack_require__(/*! ./node_modules/console-browserify/index.js */ \"./node_modules/console-browserify/index.js\");\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n ZTaskReadonlyInfo: _components_z_task_readonly_info_vue__WEBPACK_IMPORTED_MODULE_18__[\"default\"],\n AEmployee: _components_employees_a_employee_vue__WEBPACK_IMPORTED_MODULE_19__[\"default\"],\n ZFilesUpload: _plugins_tm_components_z_files_upload_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n ZDatepicker: _plugins_tm_components_z_datepicker_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n // ATaskTimeblocks,\n QuillEditor: _vueup_vue_quill__WEBPACK_IMPORTED_MODULE_12__.QuillEditor,\n AResizeHandle: _plugins_tm_modal_components_a_resize_handle_vue__WEBPACK_IMPORTED_MODULE_22__[\"default\"],\n AResetSizeButton: _plugins_tm_modal_components_a_reset_size_button_vue__WEBPACK_IMPORTED_MODULE_23__[\"default\"],\n ACloseButton: _plugins_tm_modal_components_a_close_button_vue__WEBPACK_IMPORTED_MODULE_24__[\"default\"],\n ZRemoveEmployeeModal: _z_remove_employee_modal_vue__WEBPACK_IMPORTED_MODULE_25__[\"default\"],\n ZRemoveTimeblockModal: _z_remove_timeblock_modal_vue__WEBPACK_IMPORTED_MODULE_26__[\"default\"]\n },\n mixins: [_master_task_mixins_selects_mixin__WEBPACK_IMPORTED_MODULE_13__[\"default\"], _plugins_tm_mixins_autocomplete_mixin__WEBPACK_IMPORTED_MODULE_17__[\"default\"], _plugins_tm_modal_mixins_modal_mixin__WEBPACK_IMPORTED_MODULE_21__[\"default\"]],\n emits: ['close', 'refresh'],\n data() {\n return {\n tmUtils: _plugins_tm_utils_tm_utils__WEBPACK_IMPORTED_MODULE_14__[\"default\"],\n editMode: false,\n visible: false,\n subtask: null,\n isSaving: false,\n errorUtils: _plugins_lib_auth_utils_error_utils__WEBPACK_IMPORTED_MODULE_11__[\"default\"],\n v$: (0,_vuelidate_core__WEBPACK_IMPORTED_MODULE_9__[\"default\"])(),\n showDatePicker: false,\n selectedNewDate: [],\n timeblocks: [],\n wasFormValidationTriggered: false,\n modalName: 'z-task-modal',\n employeeRefs: [],\n pendingRemoveEmployeeId: undefined,\n pendingRemoveEmployeeIndex: undefined\n };\n },\n computed: {\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_20__.mapGetters)('tm', ['mastertasks', 'enrichedEmployees']),\n enrichedMastertasks() {\n return this.mastertasks.map(mastertask => ({\n ...mastertask,\n _fullName: `${mastertask.name} [${mastertask.project_code}]`\n }));\n },\n editModeEmployee: {\n get() {\n return {\n id: this.subtask?.employee_id || null,\n duration: this.subtask?.duration || null\n };\n },\n set(value) {\n if (this.subtask) {\n this.subtask.employee_id = value.id;\n this.subtask.duration = value.duration;\n }\n }\n }\n },\n validations() {\n return {\n subtask: {\n master_task_id: {\n required: _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__.required\n },\n deadline: {\n required: _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__.required\n },\n _deadline_time: {\n required: _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__.required\n }\n }\n };\n },\n watch: {\n 'subtask.employee_id'(value, oldValue) {\n if (!value || !oldValue) return;\n this.subtask.timeblocks = [];\n this.subtask._employee = this.enrichedEmployees.find(employee => employee.id == this.subtask.employee_id);\n },\n 'subtask.employees': {\n handler() {\n if (!this.subtask?.employees) return;\n const lastEmployee = this.subtask.employees[this.subtask.employees.length - 1];\n if (lastEmployee?.id || lastEmployee?.duration) this.subtask.employees.push({});\n },\n deep: true\n },\n 'subtask.master_task_id'(value) {\n if (!value) return;\n this.subtask._master_task = this.mastertasks.find(mastertask => mastertask.id == this.subtask.master_task_id);\n this.subtask.project_code = this.subtask._master_task?.project_code || null;\n },\n editMode() {\n this.$nextTick(() => {\n this.$refs['description']?.setHTML(this.subtask.description || '');\n });\n },\n subtask: {\n handler(val) {\n if (this.editMode && !this.subtask?.id) {\n const employees = val?.employees?.filter(employee => employee?.id || employee?.duration) || [{}];\n const subtaskData = {\n ...val,\n employees: employees && employees.length > 0 ? employees : [{}]\n };\n localStorage.setItem('subtaskData', JSON.stringify(subtaskData));\n }\n },\n deep: true\n },\n timeblocks: {\n handler(val) {\n if (this.editMode && !this.subtask?.id) {\n // Save timeblocks to localStorage for new tasks\n localStorage.setItem('subtaskTimeblocks', JSON.stringify(val));\n }\n },\n deep: true\n }\n },\n mounted() {\n // Handle remove timeblock modal\n this.$events.on('show-remove-timeblock-modal', ({\n onConfirm\n }) => {\n this.$refs.removeTimeblockModal.showModal({\n onConfirm\n });\n });\n this.$events.on('add-timeblock', timeblock => {\n if (!this.subtask?.timeblocks) {\n this.subtask.timeblocks = [];\n }\n if (!this.subtask?.timesheets) {\n this.subtask.timesheets = [];\n }\n if (!this.subtask?.is_finished) {\n this.subtask.is_finished = 0;\n }\n this.subtask.timeblocks.push(timeblock);\n });\n this.$events.on('on-subtask-edited', subtask => {\n if (subtask?.id !== this.subtask?.id) return;\n const data = JSON.parse(JSON.stringify(subtask));\n const splittedDeadline = data.deadline?.split(' ') || [];\n data.deadline = splittedDeadline[0];\n data._deadline_time = splittedDeadline[1];\n\n // Convert BE format timeblocks to internal format\n const convertedTimeblocks = [];\n if (data.timeblocks && data.timeblocks.length > 0) {\n data.timeblocks.forEach(timeblock => {\n // If already in internal format, keep as is\n if (timeblock.date) {\n convertedTimeblocks.push({\n ...timeblock,\n employee_id: data.employee_id // Add employee_id\n });\n return;\n }\n\n // Convert from BE format to internal format\n if (timeblock.start && timeblock.end) {\n const startMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.start, 'YYYY-MM-DD HH:mm:ss');\n const endMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.end, 'YYYY-MM-DD HH:mm:ss');\n convertedTimeblocks.push({\n id: timeblock.id || null,\n date: startMoment.toDate(),\n starts_at_time: startMoment.format('HH:mm'),\n ends_at_time: endMoment.format('HH:mm'),\n is_finished: timeblock.is_finished || 0,\n employee_id: data.employee_id // Add employee_id\n });\n }\n });\n }\n // Store in component's timeblocks array\n this.timeblocks = convertedTimeblocks;\n this.subtask = this._enrichTask(data);\n\n // TODO: Remove? riesil som neodstranovanie jedineho zanamu v trvani a odpracovanom case\n this.$forceUpdate();\n });\n this.$events.on('on-subtask-deleted', subtask => {\n if (subtask?.id == this.subtask?.id) this.closeModal();\n });\n },\n methods: {\n toggleEditMode() {\n if (this.subtask.type == 'office365') return this.$events.emit('show-unable-to-edit-office-task-modal', {\n mode: 'edit'\n });\n this.editMode = !this.editMode;\n },\n // Set refs for employee components in create mode\n setEmployeeRef(el, index) {\n if (el) {\n this.employeeRefs[index] = el;\n }\n },\n openDatepicker(ref) {\n this.$refs[ref].openMenu();\n },\n // Helper: Get employee by ID\n getEmployeeById(employeeId) {\n return this.enrichedEmployees.find(emp => emp.id === employeeId);\n },\n // Helper: Get employee full name\n getEmployeeFullName(employeeId) {\n const employee = this.getEmployeeById(employeeId);\n return employee ? `${employee.name} ${employee.surname}` : '';\n },\n // Helper: Get valid employees (with id and duration) in create mode\n getValidEmployees() {\n if (!this.subtask?.employees) return [];\n return this.subtask.employees.filter(emp => emp.id && emp.duration);\n },\n // Helper: Get timeblocks for specific employee\n getEmployeeTimeblocks(employeeId) {\n if (!this.timeblocks) return [];\n return this.timeblocks.filter(tb => tb.employee_id === employeeId);\n },\n updateEmployeeTimeblocks(updatedTimeblocks, employeeId) {\n // Remove old timeblocks for this employee\n const otherTimeblocks = this.timeblocks.filter(tb => tb.employee_id !== employeeId);\n // Add updated timeblocks with employee_id\n const timeblocksWithEmployeeId = updatedTimeblocks.map(tb => ({\n ...tb,\n employee_id: employeeId\n }));\n this.timeblocks = [...otherTimeblocks, ...timeblocksWithEmployeeId];\n },\n updateTimeblock(val, index, employeeId) {\n const employeeTimeblocks = this.getEmployeeTimeblocks(employeeId);\n const timeblock = employeeTimeblocks[index];\n if (!timeblock) return;\n\n // Find the actual index in the full timeblocks array\n const actualIndex = this.timeblocks.indexOf(timeblock);\n if (actualIndex !== -1) {\n this.timeblocks[actualIndex] = {\n ...val,\n employee_id: employeeId\n };\n }\n },\n addTimeblock(employeeId, afterIndex = -1) {\n const newTimeblock = {\n date: new Date(),\n // Preset to current day\n starts_at_time: null,\n ends_at_time: null,\n employee_id: employeeId\n };\n\n // Get timeblocks for this employee\n const employeeTimeblocks = this.timeblocks.filter(tb => tb.employee_id === employeeId);\n if (afterIndex === -1 || afterIndex >= employeeTimeblocks.length - 1) {\n // Add at the end\n this.timeblocks.push(newTimeblock);\n } else {\n // Find the position in the full timeblocks array\n const timeblockToInsertAfter = employeeTimeblocks[afterIndex];\n const insertPosition = this.timeblocks.indexOf(timeblockToInsertAfter) + 1;\n this.timeblocks.splice(insertPosition, 0, newTimeblock);\n }\n },\n removeTimeblock(index, employeeId) {\n const employeeTimeblocks = this.getEmployeeTimeblocks(employeeId);\n const timeblock = employeeTimeblocks[index];\n if (!timeblock) return;\n\n // Find the actual index in the full timeblocks array\n const actualIndex = this.timeblocks.indexOf(timeblock);\n if (actualIndex !== -1) {\n this.timeblocks.splice(actualIndex, 1);\n }\n },\n // Handle remove employee - also removes their timeblocks\n handleRemoveEmployee() {\n this.pendingRemoveEmployeeId = this.subtask.employee_id;\n this.$refs.removeEmployeeModal.showModal();\n },\n removeEmployeeAtIndex(index) {\n this.pendingRemoveEmployeeIndex = index;\n this.$refs.removeEmployeeModal.showModal();\n },\n confirmRemoveEmployee() {\n // Handle edit mode (single employee removal)\n if (this.pendingRemoveEmployeeId !== undefined) {\n if (this.subtask.employee_id) {\n // Remove all timeblocks for this employee\n this.timeblocks = this.timeblocks.filter(tb => tb.employee_id !== this.subtask.employee_id);\n }\n\n // Clear employee data in edit mode\n this.subtask.employee_id = null;\n this.subtask.duration = null;\n this.pendingRemoveEmployeeId = undefined;\n }\n // Handle create mode (multiple employees removal)\n else if (this.pendingRemoveEmployeeIndex !== undefined) {\n const employee = this.subtask.employees[this.pendingRemoveEmployeeIndex];\n if (employee?.id) {\n // Remove all timeblocks for this employee\n this.timeblocks = this.timeblocks.filter(tb => tb.employee_id !== employee.id);\n }\n\n // Remove employee from array\n this.subtask.employees.splice(this.pendingRemoveEmployeeIndex, 1);\n this.pendingRemoveEmployeeIndex = undefined;\n }\n },\n async showModal({\n subtask,\n editMode = false,\n mastertask = {}\n }) {\n this.isSaving = false;\n this.subtask = null;\n this.wasFormValidationTriggered = false; // Reset validation trigger\n\n // CRITICAL: Prevent v-dialog autofocus BEFORE modal opens\n this.preventInputFocus = true;\n this.visible = true;\n this.timeblocks = [];\n this.employeeRefs = [];\n this.$nextTick(() => {\n this.v$.$reset();\n\n // Reset after modal is fully rendered and v-dialog autofocus has been blocked\n setTimeout(() => {\n this.preventInputFocus = false;\n }, 300);\n });\n try {\n if (subtask?.id) {\n const data = await this.$axios.get_auth_data(`v1/subtasks/${subtask.id}`);\n\n // Convert BE format timeblocks to internal format\n const convertedTimeblocks = [];\n if (data.timeblocks && data.timeblocks.length > 0) {\n data.timeblocks.forEach(timeblock => {\n // If already in internal format, keep as is\n if (timeblock.date) {\n convertedTimeblocks.push({\n ...timeblock,\n employee_id: data.employee_id // Add employee_id for edit mode\n });\n return;\n }\n\n // Convert from BE format to internal format\n if (timeblock.start && timeblock.end) {\n const startMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.start, 'YYYY-MM-DD HH:mm:ss');\n const endMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.end, 'YYYY-MM-DD HH:mm:ss');\n convertedTimeblocks.push({\n id: timeblock.id || null,\n date: startMoment.toDate(),\n starts_at_time: startMoment.format('HH:mm'),\n ends_at_time: endMoment.format('HH:mm'),\n is_finished: timeblock.is_finished || 0,\n employee_id: data.employee_id // Add employee_id for edit mode\n });\n }\n });\n }\n // Store timeblocks in the component's timeblocks array\n this.timeblocks = convertedTimeblocks;\n\n // Ensure at least one timeblock exists for edit mode\n if (data.employee_id && this.timeblocks.length === 0) {\n this.addTimeblock(data.employee_id);\n }\n if (!data.deadline) {\n this.editMode = editMode || !data.id;\n return this.subtask = this._enrichTask(data);\n }\n const splittedDeadline = data.deadline.split(' ');\n data.deadline = splittedDeadline[0];\n data._deadline_time = splittedDeadline[1];\n this.subtask = this._enrichTask(data);\n } else {\n const savedSubtask = localStorage.getItem('subtaskData');\n const savedTimeblocks = localStorage.getItem('subtaskTimeblocks');\n try {\n if (savedSubtask) {\n const parsedSubtask = JSON.parse(savedSubtask);\n this.subtask = parsedSubtask;\n // If the parsed result is not an object, reset to default\n if (typeof this.subtask !== 'object' || this.subtask === null) {\n throw new Error('Parsed subtask is not an object');\n }\n\n // Load timeblocks from localStorage if they exist\n if (savedTimeblocks) {\n try {\n const parsedTimeblocks = JSON.parse(savedTimeblocks);\n if (Array.isArray(parsedTimeblocks)) {\n this.timeblocks = parsedTimeblocks.map(tb => ({\n ...tb,\n date: tb.date ? new Date(tb.date) : null\n }));\n }\n } catch (e) {\n console.error('Error parsing saved timeblocks:', e);\n this.timeblocks = [];\n }\n }\n this.$nextTick(() => {\n this.$refs['description']?.setHTML(this.subtask.description || '');\n });\n } else {\n throw new Error('subtaskData is null or undefined');\n }\n } catch (error) {\n this.subtask = {\n master_task: mastertask,\n employees: [{}],\n _deadline_time: '17:00'\n };\n }\n if (subtask?.deadline && subtask._deadline_time && subtask.employee_id) {\n const employee = this.enrichedEmployees.find(employee => employee.id == subtask.employee_id);\n this.subtask.employees = [employee];\n this.subtask.deadline = subtask.deadline;\n this.subtask._deadline_time = subtask._deadline_time;\n }\n if (subtask?.master_task_id && subtask?.project_code) {\n this.subtask.master_task_id = subtask.master_task_id;\n this.subtask.project_code = subtask.project_code;\n }\n }\n this.editMode = editMode || !this.subtask.id;\n } catch (error) {\n this.$toast.error(this.$parseError(error));\n }\n },\n deleteTimeblock(i) {\n this.subtask.timeblocks.splice(i, 1);\n },\n closeModal() {\n this.visible = false;\n this.$router.replace({\n name: this.$route.name\n });\n },\n async validate(concept = false) {\n if (concept === false) {\n const employeesValid = await this._validateEmployees();\n const timeblocksValid = await this._validateTimeblocks();\n if (!(await this.v$.$validate())) return;\n if (!employeesValid || !timeblocksValid) return;\n return true;\n } else {\n this.v$.subtask.master_task_id.$touch();\n return this.v$.subtask.master_task_id.$errors.length === 0;\n }\n },\n async save(concept = false) {\n this.wasFormValidationTriggered = true;\n if (!(await this.validate(concept))) return;\n this.subtask.description = this.$refs['description'].getHTML();\n this._mergeDeadlineTimeDate();\n try {\n this.isSaving = true;\n\n // Concept tasks may not have a deadline time set\n this.subtask.deadline = this.subtask.deadline ? moment__WEBPACK_IMPORTED_MODULE_15___default()(this.subtask.deadline).format('YYYY-MM-DD HH:mm:SS') : null;\n if (this.subtask?.id) {\n const timesheets = this.subtask.timesheets.map(timesheet => {\n delete timesheet.employee_name;\n return timesheet;\n });\n\n // Convert timeblocks from internal format to BE format for update\n // Filter out completely empty timeblocks AND timeblocks without all required fields\n const timeblocks = this.timeblocks.filter(timeblock => timeblock.date && timeblock.starts_at_time && timeblock.ends_at_time && timeblock.employee_id === this.subtask.employee_id) // Only send complete timeblocks for this employee\n .map(timeblock => {\n const dateStr = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD');\n return {\n id: timeblock.id || null,\n start: `${dateStr} ${timeblock.starts_at_time}:00`,\n end: `${dateStr} ${timeblock.ends_at_time}:00`,\n is_finished: timeblock.is_finished || 0\n };\n });\n const payload = {\n ...this.subtask,\n timesheets,\n timeblocks\n };\n if (concept) {\n payload.is_draft = 1;\n } else {\n payload.is_draft = 0;\n }\n const {\n subtask\n } = await this.$axios.patch_auth_form(`v1/subtasks/${this.subtask.id}`, payload);\n localStorage.removeItem('subtaskData');\n localStorage.removeItem('subtaskTimeblocks');\n this.$store.commit('tm/update_subtask', subtask);\n this.$events.emit('on-subtask-edited', this.subtask);\n } else {\n // Filter out employees that don't have both id and duration\n const validEmployees = this.subtask.employees.filter(employee => employee.id && employee.duration);\n\n // Assign timeblocks to each employee based on employee_id\n // Filter out empty/incomplete timeblocks - only send fully filled timeblocks\n validEmployees.forEach(employee => {\n employee.timeblocks = this.timeblocks.filter(timeblock => timeblock.date && timeblock.starts_at_time && timeblock.ends_at_time && timeblock.employee_id === employee.id).map(timeblock => {\n return {\n start: `${moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD')} ${timeblock.starts_at_time}:00`,\n end: `${moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD')} ${timeblock.ends_at_time}:00`,\n id: null\n };\n });\n });\n const subtaskCopy = {\n ...this.subtask,\n employees: validEmployees\n };\n if (concept) {\n subtaskCopy.is_draft = true;\n subtaskCopy.employees = subtaskCopy.employees?.length > 0 ? subtaskCopy.employees : [{\n id: this.$store.getters['auth/user']?.id,\n duration: 0\n }];\n }\n delete subtaskCopy._master_task;\n const {\n subtasks\n } = await this.$axios.post_auth(`v1/mastertasks/${this.subtask.master_task_id}/subtasks`, subtaskCopy);\n localStorage.removeItem('subtaskData');\n localStorage.removeItem('subtaskTimeblocks');\n this.subtask.id = subtasks[0].id;\n subtasks.forEach(subtask => {\n this.$store.commit('tm/update_subtask', subtask);\n this.$events.emit('on-subtask-edited', this.subtask);\n });\n this.$events.emit('task-created');\n this.$events.emit('close-both-modals');\n }\n this.closeModal();\n } catch (error) {\n this.$toast.error(this.$parseError(error));\n } finally {\n this.isSaving = false;\n }\n },\n openDeleteTaskModal(subtask) {\n if (subtask.type == 'office365') return this.$events.emit('show-unable-to-edit-office-task-modal', {\n mode: 'delete'\n });\n this.$events.emit('show-delete-task-modal', {\n subtask\n });\n },\n showCompleteTaskModal() {\n this.$events.emit('show-complete-task-modal', {\n subtask: this.subtask\n });\n if (this.subtask.deadline) this.subtask.deadline = _plugins_tm_utils_tm_utils__WEBPACK_IMPORTED_MODULE_14__[\"default\"].formatDateTimeForBackend(this.subtask.deadline);\n },\n filesUploaded(files) {\n this.subtask.files = files;\n },\n updateSubtask(subtask) {\n this.subtask = subtask;\n },\n _mergeDeadlineTimeDate() {\n // Override for concept tasks which may not have a deadline time set (check for invalid date)\n if (isNaN(new Date(this.subtask.deadline).getTime())) {\n this.subtask.deadline = null;\n } else {\n const date = new Date(this.subtask.deadline);\n const timeParts = this.subtask._deadline_time.split(':');\n date.setHours(parseInt(timeParts[0]));\n date.setMinutes(parseInt(timeParts[1]));\n this.subtask.deadline = date;\n }\n },\n async _validateEmployees() {\n if (this.subtask.id) {\n // For existing subtasks, at least one employee is required\n const hasEmployee = !!(this.subtask.employee_id && this.subtask.duration);\n return hasEmployee;\n } else {\n let valid = true;\n\n // Validate ALL employee component refs (this will show errors on inputs)\n for (let i = 0; i < this.employeeRefs.length; i++) {\n const employeeRef = this.employeeRefs[i];\n\n // Validate employees that have any data (partial or complete)\n if (employeeRef && typeof employeeRef.validate === 'function') {\n if (!(await employeeRef.validate())) {\n valid = false;\n }\n }\n }\n return valid;\n }\n },\n async _validateTimeblocks() {\n // Date alone is valid. Validation only applies if start OR end time is filled.\n // If start OR end is filled, then ALL fields (date, start, end) are required.\n let valid = true;\n for (const timeblock of this.timeblocks) {\n const hasStart = !!timeblock.starts_at_time;\n const hasEnd = !!timeblock.ends_at_time;\n const hasAnyTime = hasStart || hasEnd;\n\n // If no time data (start/end), timeblock is valid (date alone is OK)\n if (!hasAnyTime) {\n continue;\n }\n\n // If start OR end is filled, ALL fields are required\n const hasDate = !!timeblock.date;\n if (!hasDate || !hasStart || !hasEnd) {\n valid = false;\n break;\n }\n\n // Validate that end time is after start time\n const dateStr = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD');\n const start = moment__WEBPACK_IMPORTED_MODULE_15___default()(`${dateStr} ${timeblock.starts_at_time}`, 'YYYY-MM-DD HH:mm');\n const end = moment__WEBPACK_IMPORTED_MODULE_15___default()(`${dateStr} ${timeblock.ends_at_time}`, 'YYYY-MM-DD HH:mm');\n if (end.isSameOrBefore(start)) {\n valid = false;\n break;\n }\n }\n return valid;\n },\n _enrichTask(subtask) {\n return {\n ...subtask,\n _created_by: this.enrichedEmployees.find(employee => employee.id == subtask.created_by),\n _employee: this.enrichedEmployees.find(employee => employee.id == subtask.employee_id),\n _master_task: this.mastertasks.find(mastertask => mastertask.id == subtask.master_task_id)\n };\n }\n }\n});\n\n//# sourceURL=webpack://jandl/./src/plugins/tm@modal/task/z-task-modal.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D\n}"); +eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var core_js_modules_es_json_stringify_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/es.json.stringify.js */ \"./node_modules/core-js/modules/es.json.stringify.js\");\n/* harmony import */ var core_js_modules_es_json_stringify_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_json_stringify_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/esnext.iterator.constructor.js */ \"./node_modules/core-js/modules/esnext.iterator.constructor.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var core_js_modules_esnext_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! core-js/modules/esnext.iterator.filter.js */ \"./node_modules/core-js/modules/esnext.iterator.filter.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_filter_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! core-js/modules/esnext.iterator.find.js */ \"./node_modules/core-js/modules/esnext.iterator.find.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! core-js/modules/esnext.iterator.for-each.js */ \"./node_modules/core-js/modules/esnext.iterator.for-each.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_for_each_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var core_js_modules_esnext_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! core-js/modules/esnext.iterator.map.js */ \"./node_modules/core-js/modules/esnext.iterator.map.js\");\n/* harmony import */ var core_js_modules_esnext_iterator_map_js__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_map_js__WEBPACK_IMPORTED_MODULE_6__);\n/* harmony import */ var _plugins_tm_components_z_datepicker_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! @/plugins/tm@_components/z-datepicker.vue */ \"./src/plugins/tm@_components/z-datepicker.vue\");\n/* harmony import */ var _plugins_tm_components_z_files_upload_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! @/plugins/tm@_components/z-files-upload.vue */ \"./src/plugins/tm@_components/z-files-upload.vue\");\n/* harmony import */ var _vuelidate_core__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! @vuelidate/core */ \"./node_modules/@vuelidate/core/dist/index.esm.js\");\n/* harmony import */ var _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! @vuelidate/validators */ \"./node_modules/@vuelidate/validators/dist/index.esm.js\");\n/* harmony import */ var _plugins_lib_auth_utils_error_utils__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! @/plugins/lib@auth/_utils/error.utils */ \"./src/plugins/lib@auth/_utils/error.utils.ts\");\n/* harmony import */ var _vueup_vue_quill__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! @vueup/vue-quill */ \"./node_modules/@vueup/vue-quill/dist/vue-quill.esm-bundler.js\");\n/* harmony import */ var _master_task_mixins_selects_mixin__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ../master-task/_mixins/selects.mixin */ \"./src/plugins/tm@modal/master-task/_mixins/selects.mixin.ts\");\n/* harmony import */ var _plugins_tm_utils_tm_utils__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! @/plugins/tm@_utils/tm.utils */ \"./src/plugins/tm@_utils/tm.utils.ts\");\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! moment */ \"./node_modules/moment/moment.js\");\n/* harmony import */ var moment__WEBPACK_IMPORTED_MODULE_15___default = /*#__PURE__*/__webpack_require__.n(moment__WEBPACK_IMPORTED_MODULE_15__);\n/* harmony import */ var _vueup_vue_quill_dist_vue_quill_snow_css__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! @vueup/vue-quill/dist/vue-quill.snow.css */ \"./node_modules/@vueup/vue-quill/dist/vue-quill.snow.css\");\n/* harmony import */ var _vueup_vue_quill_dist_vue_quill_snow_css__WEBPACK_IMPORTED_MODULE_16___default = /*#__PURE__*/__webpack_require__.n(_vueup_vue_quill_dist_vue_quill_snow_css__WEBPACK_IMPORTED_MODULE_16__);\n/* harmony import */ var _plugins_tm_mixins_autocomplete_mixin__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! @/plugins/tm@_mixins/autocomplete.mixin */ \"./src/plugins/tm@_mixins/autocomplete.mixin.ts\");\n/* harmony import */ var _components_z_task_readonly_info_vue__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./_components/z-task-readonly-info.vue */ \"./src/plugins/tm@modal/task/_components/z-task-readonly-info.vue\");\n/* harmony import */ var _components_employees_a_employee_vue__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./_components/employees/a-employee.vue */ \"./src/plugins/tm@modal/task/_components/employees/a-employee.vue\");\n/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(/*! vuex */ \"./node_modules/vuex/dist/vuex.esm-bundler.js\");\n/* harmony import */ var _plugins_tm_modal_mixins_modal_mixin__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(/*! @/plugins/tm@modal/_mixins/modal.mixin */ \"./src/plugins/tm@modal/_mixins/modal.mixin.ts\");\n/* harmony import */ var _plugins_tm_modal_components_a_resize_handle_vue__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(/*! @/plugins/tm@modal/_components/a-resize-handle.vue */ \"./src/plugins/tm@modal/_components/a-resize-handle.vue\");\n/* harmony import */ var _plugins_tm_modal_components_a_reset_size_button_vue__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(/*! @/plugins/tm@modal/_components/a-reset-size-button.vue */ \"./src/plugins/tm@modal/_components/a-reset-size-button.vue\");\n/* harmony import */ var _plugins_tm_modal_components_a_close_button_vue__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(/*! @/plugins/tm@modal/_components/a-close-button.vue */ \"./src/plugins/tm@modal/_components/a-close-button.vue\");\n/* harmony import */ var _z_remove_employee_modal_vue__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(/*! ./z-remove-employee-modal.vue */ \"./src/plugins/tm@modal/task/z-remove-employee-modal.vue\");\n/* harmony import */ var _z_remove_timeblock_modal_vue__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(/*! ./z-remove-timeblock-modal.vue */ \"./src/plugins/tm@modal/task/z-remove-timeblock-modal.vue\");\n/* provided dependency */ var console = __webpack_require__(/*! ./node_modules/console-browserify/index.js */ \"./node_modules/console-browserify/index.js\");\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n ZTaskReadonlyInfo: _components_z_task_readonly_info_vue__WEBPACK_IMPORTED_MODULE_18__[\"default\"],\n AEmployee: _components_employees_a_employee_vue__WEBPACK_IMPORTED_MODULE_19__[\"default\"],\n ZFilesUpload: _plugins_tm_components_z_files_upload_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n ZDatepicker: _plugins_tm_components_z_datepicker_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n // ATaskTimeblocks,\n QuillEditor: _vueup_vue_quill__WEBPACK_IMPORTED_MODULE_12__.QuillEditor,\n AResizeHandle: _plugins_tm_modal_components_a_resize_handle_vue__WEBPACK_IMPORTED_MODULE_22__[\"default\"],\n AResetSizeButton: _plugins_tm_modal_components_a_reset_size_button_vue__WEBPACK_IMPORTED_MODULE_23__[\"default\"],\n ACloseButton: _plugins_tm_modal_components_a_close_button_vue__WEBPACK_IMPORTED_MODULE_24__[\"default\"],\n ZRemoveEmployeeModal: _z_remove_employee_modal_vue__WEBPACK_IMPORTED_MODULE_25__[\"default\"],\n ZRemoveTimeblockModal: _z_remove_timeblock_modal_vue__WEBPACK_IMPORTED_MODULE_26__[\"default\"]\n },\n mixins: [_master_task_mixins_selects_mixin__WEBPACK_IMPORTED_MODULE_13__[\"default\"], _plugins_tm_mixins_autocomplete_mixin__WEBPACK_IMPORTED_MODULE_17__[\"default\"], _plugins_tm_modal_mixins_modal_mixin__WEBPACK_IMPORTED_MODULE_21__[\"default\"]],\n emits: ['close', 'refresh'],\n data() {\n return {\n tmUtils: _plugins_tm_utils_tm_utils__WEBPACK_IMPORTED_MODULE_14__[\"default\"],\n editMode: false,\n visible: false,\n subtask: null,\n isSaving: false,\n errorUtils: _plugins_lib_auth_utils_error_utils__WEBPACK_IMPORTED_MODULE_11__[\"default\"],\n v$: (0,_vuelidate_core__WEBPACK_IMPORTED_MODULE_9__[\"default\"])(),\n showDatePicker: false,\n selectedNewDate: [],\n timeblocks: [],\n wasFormValidationTriggered: false,\n modalName: 'z-task-modal',\n employeeRefs: [],\n pendingRemoveEmployeeId: undefined,\n pendingRemoveEmployeeIndex: undefined\n };\n },\n computed: {\n ...(0,vuex__WEBPACK_IMPORTED_MODULE_20__.mapGetters)('tm', ['mastertasks', 'enrichedEmployees']),\n enrichedMastertasks() {\n return this.mastertasks.map(mastertask => ({\n ...mastertask,\n _fullName: `${mastertask.name} [${mastertask.project_code}]`\n }));\n },\n editModeEmployee: {\n get() {\n return {\n id: this.subtask?.employee_id || null,\n duration: this.subtask?.duration || null\n };\n },\n set(value) {\n if (this.subtask) {\n this.subtask.employee_id = value.id;\n this.subtask.duration = value.duration;\n }\n }\n }\n },\n validations() {\n return {\n subtask: {\n master_task_id: {\n required: _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__.required\n },\n deadline: {\n required: _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__.required\n },\n _deadline_time: {\n required: _vuelidate_validators__WEBPACK_IMPORTED_MODULE_10__.required\n }\n }\n };\n },\n watch: {\n 'subtask.employee_id'(value, oldValue) {\n if (!value || !oldValue) return;\n this.subtask.timeblocks = [];\n this.subtask._employee = this.enrichedEmployees.find(employee => employee.id == this.subtask.employee_id);\n },\n 'subtask.employees': {\n handler() {\n if (!this.subtask?.employees) return;\n const lastEmployee = this.subtask.employees[this.subtask.employees.length - 1];\n if (lastEmployee?.id || lastEmployee?.duration) this.subtask.employees.push({});\n },\n deep: true\n },\n 'subtask.master_task_id'(value) {\n if (!value) return;\n this.subtask._master_task = this.mastertasks.find(mastertask => mastertask.id == this.subtask.master_task_id);\n this.subtask.project_code = this.subtask._master_task?.project_code || null;\n },\n editMode() {\n this.$nextTick(() => {\n this.$refs['description']?.setHTML(this.subtask.description || '');\n });\n },\n subtask: {\n handler(val) {\n if (this.editMode && !this.subtask?.id) {\n const employees = val?.employees?.filter(employee => employee?.id || employee?.duration) || [{}];\n const subtaskData = {\n ...val,\n employees: employees && employees.length > 0 ? employees : [{}]\n };\n localStorage.setItem('subtaskData', JSON.stringify(subtaskData));\n }\n },\n deep: true\n },\n timeblocks: {\n handler(val) {\n if (this.editMode && !this.subtask?.id) {\n // Save timeblocks to localStorage for new tasks\n localStorage.setItem('subtaskTimeblocks', JSON.stringify(val));\n }\n },\n deep: true\n }\n },\n mounted() {\n // Handle remove timeblock modal\n this.$events.on('show-remove-timeblock-modal', ({\n onConfirm\n }) => {\n this.$refs.removeTimeblockModal.showModal({\n onConfirm\n });\n });\n this.$events.on('add-timeblock', timeblock => {\n if (!this.subtask?.timeblocks) {\n this.subtask.timeblocks = [];\n }\n if (!this.subtask?.timesheets) {\n this.subtask.timesheets = [];\n }\n if (!this.subtask?.is_finished) {\n this.subtask.is_finished = 0;\n }\n this.subtask.timeblocks.push(timeblock);\n });\n this.$events.on('on-subtask-edited', subtask => {\n if (subtask?.id !== this.subtask?.id) return;\n const data = JSON.parse(JSON.stringify(subtask));\n const splittedDeadline = data.deadline?.split(' ') || [];\n data.deadline = splittedDeadline[0];\n data._deadline_time = splittedDeadline[1];\n\n // Convert BE format timeblocks to internal format\n const convertedTimeblocks = [];\n if (data.timeblocks && data.timeblocks.length > 0) {\n data.timeblocks.forEach(timeblock => {\n // If already in internal format, keep as is\n if (timeblock.date) {\n convertedTimeblocks.push({\n ...timeblock,\n employee_id: data.employee_id // Add employee_id\n });\n return;\n }\n\n // Convert from BE format to internal format\n if (timeblock.start && timeblock.end) {\n const startMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.start, 'YYYY-MM-DD HH:mm:ss');\n const endMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.end, 'YYYY-MM-DD HH:mm:ss');\n convertedTimeblocks.push({\n id: timeblock.id || null,\n date: startMoment.toDate(),\n starts_at_time: startMoment.format('HH:mm'),\n ends_at_time: endMoment.format('HH:mm'),\n is_finished: timeblock.is_finished || 0,\n employee_id: data.employee_id // Add employee_id\n });\n }\n });\n }\n // Store in component's timeblocks array\n this.timeblocks = convertedTimeblocks;\n this.subtask = this._enrichTask(data);\n\n // TODO: Remove? riesil som neodstranovanie jedineho zanamu v trvani a odpracovanom case\n this.$forceUpdate();\n });\n this.$events.on('on-subtask-deleted', subtask => {\n if (subtask?.id == this.subtask?.id) this.closeModal();\n });\n },\n methods: {\n toggleEditMode() {\n if (this.subtask.type == 'office365') return this.$events.emit('show-unable-to-edit-office-task-modal', {\n mode: 'edit'\n });\n this.editMode = !this.editMode;\n },\n // Set refs for employee components in create mode\n setEmployeeRef(el, index) {\n if (el) {\n this.employeeRefs[index] = el;\n }\n },\n openDatepicker(ref) {\n this.$refs[ref].openMenu();\n },\n // Helper: Get employee by ID\n getEmployeeById(employeeId) {\n return this.enrichedEmployees.find(emp => emp.id === employeeId);\n },\n // Helper: Get employee full name\n getEmployeeFullName(employeeId) {\n const employee = this.getEmployeeById(employeeId);\n return employee ? `${employee.name} ${employee.surname}` : '';\n },\n // Helper: Get valid employees (with id and duration) in create mode\n getValidEmployees() {\n if (!this.subtask?.employees) return [];\n return this.subtask.employees.filter(emp => emp.id && emp.duration);\n },\n // Helper: Get timeblocks for specific employee\n getEmployeeTimeblocks(employeeId) {\n if (!this.timeblocks) return [];\n return this.timeblocks.filter(tb => tb.employee_id === employeeId);\n },\n updateEmployeeTimeblocks(updatedTimeblocks, employeeId) {\n // Remove old timeblocks for this employee\n const otherTimeblocks = this.timeblocks.filter(tb => tb.employee_id !== employeeId);\n // Add updated timeblocks with employee_id\n const timeblocksWithEmployeeId = updatedTimeblocks.map(tb => ({\n ...tb,\n employee_id: employeeId\n }));\n this.timeblocks = [...otherTimeblocks, ...timeblocksWithEmployeeId];\n },\n updateTimeblock(val, index, employeeId) {\n const employeeTimeblocks = this.getEmployeeTimeblocks(employeeId);\n const timeblock = employeeTimeblocks[index];\n if (!timeblock) return;\n\n // Find the actual index in the full timeblocks array\n const actualIndex = this.timeblocks.indexOf(timeblock);\n if (actualIndex !== -1) {\n this.timeblocks[actualIndex] = {\n ...val,\n employee_id: employeeId\n };\n }\n },\n addTimeblock(employeeId, afterIndex = -1) {\n const newTimeblock = {\n date: new Date(),\n // Preset to current day\n starts_at_time: null,\n ends_at_time: null,\n employee_id: employeeId\n };\n\n // Get timeblocks for this employee\n const employeeTimeblocks = this.timeblocks.filter(tb => tb.employee_id === employeeId);\n if (afterIndex === -1 || afterIndex >= employeeTimeblocks.length - 1) {\n // Add at the end\n this.timeblocks.push(newTimeblock);\n } else {\n // Find the position in the full timeblocks array\n const timeblockToInsertAfter = employeeTimeblocks[afterIndex];\n const insertPosition = this.timeblocks.indexOf(timeblockToInsertAfter) + 1;\n this.timeblocks.splice(insertPosition, 0, newTimeblock);\n }\n },\n removeTimeblock(index, employeeId) {\n const employeeTimeblocks = this.getEmployeeTimeblocks(employeeId);\n const timeblock = employeeTimeblocks[index];\n if (!timeblock) return;\n\n // Find the actual index in the full timeblocks array\n const actualIndex = this.timeblocks.indexOf(timeblock);\n if (actualIndex !== -1) {\n this.timeblocks.splice(actualIndex, 1);\n }\n },\n // Handle remove employee - also removes their timeblocks\n handleRemoveEmployee() {\n this.pendingRemoveEmployeeId = this.subtask.employee_id;\n this.$refs.removeEmployeeModal.showModal();\n },\n removeEmployeeAtIndex(index) {\n this.pendingRemoveEmployeeIndex = index;\n this.$refs.removeEmployeeModal.showModal();\n },\n confirmRemoveEmployee() {\n // Handle edit mode (single employee removal)\n if (this.pendingRemoveEmployeeId !== undefined) {\n if (this.subtask.employee_id) {\n // Remove all timeblocks for this employee\n this.timeblocks = this.timeblocks.filter(tb => tb.employee_id !== this.subtask.employee_id);\n }\n\n // Clear employee data in edit mode\n this.subtask.employee_id = null;\n this.subtask.duration = null;\n this.pendingRemoveEmployeeId = undefined;\n }\n // Handle create mode (multiple employees removal)\n else if (this.pendingRemoveEmployeeIndex !== undefined) {\n const employee = this.subtask.employees[this.pendingRemoveEmployeeIndex];\n if (employee?.id) {\n // Remove all timeblocks for this employee\n this.timeblocks = this.timeblocks.filter(tb => tb.employee_id !== employee.id);\n }\n\n // Remove employee from array\n this.subtask.employees.splice(this.pendingRemoveEmployeeIndex, 1);\n this.pendingRemoveEmployeeIndex = undefined;\n }\n },\n async showModal({\n subtask,\n editMode = false,\n mastertask = {}\n }) {\n this.isSaving = false;\n this.subtask = null;\n this.wasFormValidationTriggered = false; // Reset validation trigger\n\n // CRITICAL: Prevent v-dialog autofocus BEFORE modal opens\n this.preventInputFocus = true;\n this.visible = true;\n this.timeblocks = [];\n this.employeeRefs = [];\n this.$nextTick(() => {\n this.v$.$reset();\n\n // Reset after modal is fully rendered and v-dialog autofocus has been blocked\n setTimeout(() => {\n this.preventInputFocus = false;\n }, 300);\n });\n try {\n if (subtask?.id) {\n const data = await this.$axios.get_auth_data(`v1/subtasks/${subtask.id}`);\n\n // Convert BE format timeblocks to internal format\n const convertedTimeblocks = [];\n if (data.timeblocks && data.timeblocks.length > 0) {\n data.timeblocks.forEach(timeblock => {\n // If already in internal format, keep as is\n if (timeblock.date) {\n convertedTimeblocks.push({\n ...timeblock,\n employee_id: data.employee_id // Add employee_id for edit mode\n });\n return;\n }\n\n // Convert from BE format to internal format\n if (timeblock.start && timeblock.end) {\n const startMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.start, 'YYYY-MM-DD HH:mm:ss');\n const endMoment = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.end, 'YYYY-MM-DD HH:mm:ss');\n convertedTimeblocks.push({\n id: timeblock.id || null,\n date: startMoment.toDate(),\n starts_at_time: startMoment.format('HH:mm'),\n ends_at_time: endMoment.format('HH:mm'),\n is_finished: timeblock.is_finished || 0,\n employee_id: data.employee_id // Add employee_id for edit mode\n });\n }\n });\n }\n // Store timeblocks in the component's timeblocks array\n this.timeblocks = convertedTimeblocks;\n\n // Ensure at least one timeblock exists for edit mode\n if (data.employee_id && this.timeblocks.length === 0) {\n this.addTimeblock(data.employee_id);\n }\n if (!data.deadline) {\n this.editMode = editMode || !data.id;\n return this.subtask = this._enrichTask(data);\n }\n const splittedDeadline = data.deadline.split(' ');\n data.deadline = splittedDeadline[0];\n data._deadline_time = splittedDeadline[1];\n this.subtask = this._enrichTask(data);\n } else {\n const savedSubtask = localStorage.getItem('subtaskData');\n const savedTimeblocks = localStorage.getItem('subtaskTimeblocks');\n try {\n if (savedSubtask) {\n const parsedSubtask = JSON.parse(savedSubtask);\n this.subtask = parsedSubtask;\n // If the parsed result is not an object, reset to default\n if (typeof this.subtask !== 'object' || this.subtask === null) {\n throw new Error('Parsed subtask is not an object');\n }\n\n // Load timeblocks from localStorage if they exist\n if (savedTimeblocks) {\n try {\n const parsedTimeblocks = JSON.parse(savedTimeblocks);\n if (Array.isArray(parsedTimeblocks)) {\n this.timeblocks = parsedTimeblocks.map(tb => ({\n ...tb,\n date: tb.date ? new Date(tb.date) : null\n }));\n }\n } catch (e) {\n console.error('Error parsing saved timeblocks:', e);\n this.timeblocks = [];\n }\n }\n this.$nextTick(() => {\n this.$refs['description']?.setHTML(this.subtask.description || '');\n });\n } else {\n throw new Error('subtaskData is null or undefined');\n }\n } catch (error) {\n this.subtask = {\n master_task: mastertask,\n employees: [{}],\n _deadline_time: '17:00'\n };\n }\n if (subtask?.deadline && subtask._deadline_time && subtask.employee_id) {\n const employee = this.enrichedEmployees.find(employee => employee.id == subtask.employee_id);\n this.subtask.employees = [employee];\n this.subtask.deadline = subtask.deadline;\n this.subtask._deadline_time = subtask._deadline_time;\n }\n if (subtask?.master_task_id && subtask?.project_code) {\n this.subtask.master_task_id = subtask.master_task_id;\n this.subtask.project_code = subtask.project_code;\n }\n }\n this.editMode = editMode || !this.subtask.id;\n } catch (error) {\n this.$toast.error(this.$parseError(error));\n }\n },\n deleteTimeblock(i) {\n this.subtask.timeblocks.splice(i, 1);\n },\n closeModal() {\n this.visible = false;\n this.$router.replace({\n name: this.$route.name\n });\n },\n async validate(concept = false) {\n if (concept === false) {\n const employeesValid = await this._validateEmployees();\n const timeblocksValid = await this._validateTimeblocks();\n if (!(await this.v$.$validate())) return;\n if (!employeesValid || !timeblocksValid) return;\n return true;\n } else {\n this.v$.subtask.master_task_id.$touch();\n return this.v$.subtask.master_task_id.$errors.length === 0;\n }\n },\n async save(concept = false) {\n this.v$.$reset();\n this.wasFormValidationTriggered = true;\n if (!(await this.validate(concept))) return;\n this.subtask.description = this.$refs['description'].getHTML();\n this._mergeDeadlineTimeDate();\n try {\n this.isSaving = true;\n\n // Concept tasks may not have a deadline time set\n this.subtask.deadline = this.subtask.deadline ? moment__WEBPACK_IMPORTED_MODULE_15___default()(this.subtask.deadline).format('YYYY-MM-DD HH:mm:SS') : null;\n if (this.subtask?.id) {\n const timesheets = this.subtask.timesheets.map(timesheet => {\n delete timesheet.employee_name;\n return timesheet;\n });\n\n // Convert timeblocks from internal format to BE format for update\n // Filter out completely empty timeblocks AND timeblocks without all required fields\n const timeblocks = this.timeblocks.filter(timeblock => timeblock.date && timeblock.starts_at_time && timeblock.ends_at_time && timeblock.employee_id === this.subtask.employee_id) // Only send complete timeblocks for this employee\n .map(timeblock => {\n const dateStr = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD');\n return {\n id: timeblock.id || null,\n start: `${dateStr} ${timeblock.starts_at_time}:00`,\n end: `${dateStr} ${timeblock.ends_at_time}:00`,\n is_finished: timeblock.is_finished || 0\n };\n });\n const payload = {\n ...this.subtask,\n timesheets,\n timeblocks\n };\n if (concept) {\n payload.is_draft = 1;\n } else {\n payload.is_draft = 0;\n }\n const {\n subtask\n } = await this.$axios.patch_auth_form(`v1/subtasks/${this.subtask.id}`, payload);\n localStorage.removeItem('subtaskData');\n localStorage.removeItem('subtaskTimeblocks');\n this.$store.commit('tm/update_subtask', subtask);\n this.$events.emit('on-subtask-edited', this.subtask);\n } else {\n // Filter out employees that don't have both id and duration\n const validEmployees = this.subtask.employees.filter(employee => employee.id && employee.duration);\n\n // Assign timeblocks to each employee based on employee_id\n // Filter out empty/incomplete timeblocks - only send fully filled timeblocks\n validEmployees.forEach(employee => {\n employee.timeblocks = this.timeblocks.filter(timeblock => timeblock.date && timeblock.starts_at_time && timeblock.ends_at_time && timeblock.employee_id === employee.id).map(timeblock => {\n return {\n start: `${moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD')} ${timeblock.starts_at_time}:00`,\n end: `${moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD')} ${timeblock.ends_at_time}:00`,\n id: null\n };\n });\n });\n const subtaskCopy = {\n ...this.subtask,\n employees: validEmployees\n };\n if (concept) {\n subtaskCopy.is_draft = true;\n subtaskCopy.employees = subtaskCopy.employees?.length > 0 ? subtaskCopy.employees : [{\n id: this.$store.getters['auth/user']?.id,\n duration: 0\n }];\n }\n delete subtaskCopy._master_task;\n const {\n subtasks\n } = await this.$axios.post_auth(`v1/mastertasks/${this.subtask.master_task_id}/subtasks`, subtaskCopy);\n localStorage.removeItem('subtaskData');\n localStorage.removeItem('subtaskTimeblocks');\n this.subtask.id = subtasks[0].id;\n subtasks.forEach(subtask => {\n this.$store.commit('tm/update_subtask', subtask);\n this.$events.emit('on-subtask-edited', this.subtask);\n });\n this.$events.emit('task-created');\n this.$events.emit('close-both-modals');\n }\n this.closeModal();\n } catch (error) {\n this.$toast.error(this.$parseError(error));\n } finally {\n this.isSaving = false;\n }\n },\n openDeleteTaskModal(subtask) {\n if (subtask.type == 'office365') return this.$events.emit('show-unable-to-edit-office-task-modal', {\n mode: 'delete'\n });\n this.$events.emit('show-delete-task-modal', {\n subtask\n });\n },\n showCompleteTaskModal() {\n this.$events.emit('show-complete-task-modal', {\n subtask: this.subtask\n });\n if (this.subtask.deadline) this.subtask.deadline = _plugins_tm_utils_tm_utils__WEBPACK_IMPORTED_MODULE_14__[\"default\"].formatDateTimeForBackend(this.subtask.deadline);\n },\n filesUploaded(files) {\n this.subtask.files = files;\n },\n updateSubtask(subtask) {\n this.subtask = subtask;\n },\n _mergeDeadlineTimeDate() {\n // Override for concept tasks which may not have a deadline time set (check for invalid date)\n if (isNaN(new Date(this.subtask.deadline).getTime())) {\n this.subtask.deadline = null;\n } else {\n const date = new Date(this.subtask.deadline);\n const timeParts = this.subtask._deadline_time.split(':');\n date.setHours(parseInt(timeParts[0]));\n date.setMinutes(parseInt(timeParts[1]));\n this.subtask.deadline = date;\n }\n },\n async _validateEmployees() {\n if (this.subtask.id) {\n // For existing subtasks, at least one employee is required\n const hasEmployee = !!(this.subtask.employee_id && this.subtask.duration);\n return hasEmployee;\n } else {\n let valid = true;\n\n // Validate ALL employee component refs (this will show errors on inputs)\n for (let i = 0; i < this.employeeRefs.length; i++) {\n const employeeRef = this.employeeRefs[i];\n\n // Validate employees that have any data (partial or complete)\n if (employeeRef && typeof employeeRef.validate === 'function') {\n if (!(await employeeRef.validate())) {\n valid = false;\n }\n }\n }\n return valid;\n }\n },\n async _validateTimeblocks() {\n // Date alone is valid. Validation only applies if start OR end time is filled.\n // If start OR end is filled, then ALL fields (date, start, end) are required.\n let valid = true;\n for (const timeblock of this.timeblocks) {\n const hasStart = !!timeblock.starts_at_time;\n const hasEnd = !!timeblock.ends_at_time;\n const hasAnyTime = hasStart || hasEnd;\n\n // If no time data (start/end), timeblock is valid (date alone is OK)\n if (!hasAnyTime) {\n continue;\n }\n\n // If start OR end is filled, ALL fields are required\n const hasDate = !!timeblock.date;\n if (!hasDate || !hasStart || !hasEnd) {\n valid = false;\n break;\n }\n\n // Validate that end time is after start time\n const dateStr = moment__WEBPACK_IMPORTED_MODULE_15___default()(timeblock.date).format('YYYY-MM-DD');\n const start = moment__WEBPACK_IMPORTED_MODULE_15___default()(`${dateStr} ${timeblock.starts_at_time}`, 'YYYY-MM-DD HH:mm');\n const end = moment__WEBPACK_IMPORTED_MODULE_15___default()(`${dateStr} ${timeblock.ends_at_time}`, 'YYYY-MM-DD HH:mm');\n if (end.isSameOrBefore(start)) {\n valid = false;\n break;\n }\n }\n return valid;\n },\n _enrichTask(subtask) {\n return {\n ...subtask,\n _created_by: this.enrichedEmployees.find(employee => employee.id == subtask.created_by),\n _employee: this.enrichedEmployees.find(employee => employee.id == subtask.employee_id),\n _master_task: this.mastertasks.find(mastertask => mastertask.id == subtask.master_task_id)\n };\n }\n }\n});\n\n//# sourceURL=webpack://jandl/./src/plugins/tm@modal/task/z-task-modal.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D\n}"); /***/ }), diff --git a/app/src/plugins/tm@modal/task/z-task-modal.vue b/app/src/plugins/tm@modal/task/z-task-modal.vue index 9d024f5..40b63f0 100644 --- a/app/src/plugins/tm@modal/task/z-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-task-modal.vue @@ -815,6 +815,8 @@ export default { }, async save(concept = false) { + this.v$.$reset(); + this.wasFormValidationTriggered = true; if (!(await this.validate(concept))) return;