diff --git a/app/src/plugins/app/_config/App.vue b/app/src/plugins/app/_config/App.vue index bb12b24..3f7d31e 100644 --- a/app/src/plugins/app/_config/App.vue +++ b/app/src/plugins/app/_config/App.vue @@ -10,6 +10,7 @@ + @@ -23,6 +24,7 @@ import ZDeleteTeamModal from '@/plugins/tm@modal/team/z-delete-team-modal.vue' import ZTeamModal from '@/plugins/tm@modal/team/z-team-modal.vue' import ZDeleteTaskModal from '@/plugins/tm@modal/task/z-delete-task-modal.vue' import ZTaskModal from '@/plugins/tm@modal/task/z-task-modal.vue' +import ZDeleteBlockerModal from '@/plugins/tm@modal/blocker/z-delete-blocker-modal.vue' import ZCompleteTaskModal from '@/plugins/tm@modal/task/z-complete-task-modal.vue' import ZTaskAddTimeModal from '@/plugins/tm@modal/task/z-task-add-time-modal.vue' import ZBlockerModal from '@/plugins/tm@modal/blocker/z-blocker-modal.vue' @@ -34,6 +36,7 @@ export default { ZDeleteTeamModal, ZTeamModal, ZDeleteTaskModal, + ZDeleteBlockerModal, ZTaskModal, ZMasterTaskModal, ZCompleteTaskModal, @@ -55,6 +58,10 @@ export default { this.$refs['z-delete-task-modal'].showModal({subtask}) }) + this.$events.on('show-delete-blocker-modal', ({blocker}) => { + this.$refs['z-delete-blocker-modal'].showModal({blocker}) + }) + this.$events.on('show-delete-master-task-modal', ({mastertask}) => { this.$refs['z-delete-master-task-modal'].showModal({mastertask}) }) @@ -111,6 +118,10 @@ export default { this.$events.emit('on-subtask-deleted', task) }, + onBlockerDeleted(task) { + this.$events.emit('on-blocker-deleted', task) + }, + onTaskEdited(task) { this.$events.emit('on-subtask-edited', task) }, diff --git a/app/src/plugins/app@vendor/vue-cal/event.vue b/app/src/plugins/app@vendor/vue-cal/event.vue index f352468..d3b62b1 100644 --- a/app/src/plugins/app@vendor/vue-cal/event.vue +++ b/app/src/plugins/app@vendor/vue-cal/event.vue @@ -157,7 +157,7 @@ export default { }, onDragStart(e) { - if (this.event.nonWorking) return + if (this.event.type == 'blocker' || this.event.type == 'non-working-hours') return this.event.draggingStatic = false this.dnd && this.dnd.eventDragStart(e, this.event); }, diff --git a/app/src/plugins/tm@dashboard/_components/z-timeline.vue b/app/src/plugins/tm@dashboard/_components/z-timeline.vue index 450d82e..3d86b06 100644 --- a/app/src/plugins/tm@dashboard/_components/z-timeline.vue +++ b/app/src/plugins/tm@dashboard/_components/z-timeline.vue @@ -57,30 +57,43 @@ @@ -196,7 +209,7 @@ export default { }, taskClicked(subtask) { - if (subtask.nonWorking) return + if (subtask.type == 'non-working-hours') return // console.log("task", task) if (subtask.end.getTime() - subtask.start.getTime() <= 3600000) { // console.log("ShortTask") @@ -212,13 +225,21 @@ export default { this.$events.emit('show-complete-task-modal', { subtask }) }, - editTask(subtask, editMode) { - this.$events.emit('show-subtask-modal', {subtask, editMode}) + editTask({subtask}) { + this.$events.emit('show-subtask-modal', {subtask}) }, - deleteTask(subtask) { + deleteTask({ subtask }) { this.$events.emit('show-delete-task-modal', {subtask}) - } + }, + + editBlocker({ blocker }) { + this.$events.emit('show-blocker-modal', {blocker}) + }, + + deleteBlocker({ blocker }) { + this.$events.emit('show-delete-blocker-modal', {blocker}) + }, } } @@ -363,6 +384,17 @@ export default { .custom-event-body padding: 20px 10px + .event-content + &.blocker + * + color: #ffffff !important + + .event-details .event-initials + border: 1px solid white + + .event-footer + border-top: 1px solid rgba(255, 255, 255, 0.19) + &:has(.completed) background: rgb(251, 251, 251) !important border: 1px solid #cccccc !important @@ -378,12 +410,11 @@ export default { left: 0 !important &:not(.vuecal__event--focus) - &:has(.short-event) - padding: 10px !important - - &:has(.micro-event) - padding: 5px 10px !important + &:has(.micro-event) .custom-event-body + padding: 5px !important + &:has(.short-event) .custom-event-body:not(.micro-event) + padding: 10px 5px !important .short-event .project-code display: none !important @@ -420,19 +451,6 @@ export default { .event-footer display: none !important - &.event-blocker - background: #B00020 !important - - h3, - .event-details * - color: white !important - - .event-details .event-initials - border: 1px solid white - - .event-footer - border-top: 1px solid rgba(255, 255, 255, 0.19) - .project-code margin: 0 0 20px display: block diff --git a/app/src/plugins/tm@dashboard/tasks/_utils/timeline.utils.ts b/app/src/plugins/tm@dashboard/tasks/_utils/timeline.utils.ts index 8f5e220..1a31d8a 100644 --- a/app/src/plugins/tm@dashboard/tasks/_utils/timeline.utils.ts +++ b/app/src/plugins/tm@dashboard/tasks/_utils/timeline.utils.ts @@ -21,13 +21,17 @@ export default { }, generateTimelineEvents(subtasks, employee, date) { - const events = [] + let events = [] subtasks.forEach(subtask => { - subtask.timeblocks?.forEach((timeblock) => { - events.push(this._composeTimelineEvent(subtask, employee, date, timeblock)) - }) - }) + if (subtask.type == 'blocker') { + events = [...events, ...this._generateEventsFromBlocker(subtask, date)] + } else { + subtask.timeblocks?.forEach((timeblock) => { + events.push(this._composeTimelineEvent(subtask, employee, date, timeblock)) + }) + } + }) return events }, @@ -50,30 +54,72 @@ export default { } }, + _generateEventsFromBlocker(blocker, date) { + const start = moment(blocker.start) + const end = moment(blocker.end) + + const dates = start == end ? [moment(start).format('YYYY-MM-DD')] : this._getDatesInRange(start, end) + + const blockers = dates.map(d => { + const diffInDays = differenceInCalendarDays(date, new Date(d)) + const dStart = new Date(moment(d).set({hours: start.hours(), minutes: start.minutes()}).format('YYYY-MM-DD HH:mm')) + const dEnd = new Date(moment(d).set({hours: end.hours(), minutes: end.minutes()}).format('YYYY-MM-DD HH:mm')) + + return { + blocker, + split: this._getSplitFromDate(date, d), + start: addDays(dStart, diffInDays), + end: addDays(dEnd, diffInDays), + date: new Date(d), + type: 'blocker', + initials: `${blocker.employee?.name[0]}${blocker.employee?.surname[0]}`, + color: '#B00020', + draggable: false + } + }) + + + + console.log(blockers) + + return blockers + }, + + _getDatesInRange(start, end) { + const dates = [] + + while (start <= end) { + dates.push(moment(start).format('YYYY-MM-DD')) + start.add(1, 'day') + } + + return dates + }, + generateTimelineNonWorkingHours(date, employee) { - const nonWorkingHours = [] + const events = [] for (let i = 0; i < 7; i++) { - nonWorkingHours.push({ + events.push({ title: "Non working hours", start: setMinutes(setHours(date, 0), 0), end: setMinutes(setHours(date, employee.work_start), 0), - nonWorking: true, + type: 'non-working-hours', split: i + 1, draggable: false }) - nonWorkingHours.push({ + events.push({ title: "Non working hours", start: setMinutes(setHours(date, employee.work_end), 0), end: setMinutes(setHours(date, 23), 59), - nonWorking: true, + type: 'non-working-hours', split: i + 1, draggable: false }) } - return nonWorkingHours + return events }, _getSplitFromDate(date, start) { diff --git a/app/src/plugins/tm@dashboard/tasks/tasks.vue b/app/src/plugins/tm@dashboard/tasks/tasks.vue index 60998c5..a3a43f1 100644 --- a/app/src/plugins/tm@dashboard/tasks/tasks.vue +++ b/app/src/plugins/tm@dashboard/tasks/tasks.vue @@ -43,7 +43,7 @@ export default { if (!workingHours[task.split]) workingHours[task.split] = 0 - if (task.nonWorking || task.subtask.is_finished) + if (task.type == 'non-working-hours' || task.subtask?.is_finished || task.blocker?.is_finished) return workingHours[task.split] += timelineUtils._getDifferenceInHours(task.end, task.start) @@ -88,9 +88,17 @@ export default { this.onFilterChanged(this.filter) }) + this.$events.on('on-blocker-edited', () => { + this.onFilterChanged(this.filter) + }) + this.$events.on('on-subtask-deleted', () => { this.onFilterChanged(this.filter) }) + + this.$events.on('on-blocker-deleted', () => { + this.onFilterChanged(this.filter) + }) }, unmounted() { @@ -110,10 +118,10 @@ export default { const queryParameters = new URLSearchParams(urlQueryObject).toString() try { - const { subtasks } = await this.$axios.get_auth(`v1/subtasks?${queryParameters}`) - this.config.scrollHour = this.filter?.employee.work_start - 1 - this.config.date = this.filter.start - this.subtasks = subtasks + const { subtasks, blockers } = await this.$axios.get_auth(`v1/subtasks?${queryParameters}`) + this.config.scrollHour = this.filter?.employee.work_start - 1 + this.config.date = this.filter.start + this.subtasks = [...subtasks, ...blockers.map(blocker => ({...blocker, type: 'blocker'}))] } catch (error) { console.log(error) this.$toast.error('Nepodarilo sa nacitat data!') diff --git a/app/src/plugins/tm@dashboard/teams/teams.vue b/app/src/plugins/tm@dashboard/teams/teams.vue index c2b944b..fcb0811 100644 --- a/app/src/plugins/tm@dashboard/teams/teams.vue +++ b/app/src/plugins/tm@dashboard/teams/teams.vue @@ -57,15 +57,7 @@ export default { timelineData() { if (!this.subtasks) - return - - // let unscheduledTime = 0 - // const tasks = [...this._generateNonWorking(), ...this._generateTasks()] - - // tasks.forEach(task => { - // if (!task.nonWorking) - // workingHours += timelineUtils._getDifferenceInHours(task.end, task.start) - // }) + return return { unscheduledTime: 333, @@ -147,7 +139,7 @@ export default { start: setMinutes(setHours(date, 0), 0), end: setMinutes(setHours(date, employee.work_start), 0), split: index + 1, - nonWorking: true, + type: 'non-working-hours', draggable: false }) @@ -156,7 +148,7 @@ export default { start: setMinutes(setHours(date, employee.work_end), 0), end: setMinutes(setHours(date, 23), 59), split: index + 1, - nonWorking: true, + type: 'non-working-hours', draggable: false }) }) diff --git a/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue b/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue index 5495286..4640ab6 100644 --- a/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue +++ b/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue @@ -27,18 +27,21 @@ > + + + {{ blocker.id ? 'UPRAVIŤ' : 'VYTVORIŤ'}} @@ -55,6 +58,7 @@ import errorUtils from '@/plugins/lib@auth/_utils/error.utils' import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus' import tmUtils from '@/plugins/tm@_utils/tm.utils' import { mapGetters } from 'vuex' +import moment from 'moment' export default { mixins: [VuetifyFixFocusMixin], @@ -71,7 +75,9 @@ export default { v$: useVuelidate(), errorUtils, blocker: null, - startEndDate: null + datepicker: null, + starts_at_time: null, + ends_at_time: null } }, @@ -92,22 +98,33 @@ export default { start: { required }, end: { required }, employee_id: { required } - } + }, + + starts_at_time: { required }, + ends_at_time: { required }, + datepicker: { required } } }, methods: { async showModal({ blocker }) { - this.visible = true - this.blocker = null - this.startEndDate = null + this.visible = true + this.blocker = null + this.datepicker = null + this.starts_at_time = null + this.ends_at_time = null + this.$nextTick(() => this.v$.$reset()) try { if (blocker?.id) { blocker = await this.$axios.get_auth_data(`v1/blockers/${blocker.id}`) blocker.employee_id = blocker.employee.id - this.startEndDate = [new Date(blocker.start), new Date(blocker.end)] + + this.starts_at_time = moment(blocker.start).format('HH:mm') + this.ends_at_time = moment(blocker.end).format('HH:mm') + this.datepicker = new Date(blocker.start) + this.blocker = blocker } else { this.blocker = {start: null} @@ -122,14 +139,18 @@ export default { return try { - if (this.blocker.id) - await this.$axios.put_auth(`v1/blockers/${this.blocker.id}`, this.blocker) - else - await this.$axios.post_auth('v1/blockers', this.blocker) + if (this.blocker.id) { + const { blocker } = await this.$axios.put_auth(`v1/blockers/${this.blocker.id}`, this.blocker) + this.blocker = blocker + } else { + const { blocker } = await this.$axios.post_auth('v1/blockers', this.blocker) + this.blocker = blocker + } + this.$events.emit("on-blocker-edited", this.blocker) this.closeModal() } catch (error) { - this.$toast(error) + this.$toast.error(error) } }, @@ -137,23 +158,31 @@ export default { this.$refs['datepicker'].clearValue() this.visible = false this.$emit('close', this.blocker) + }, + + _syncDatesTimes() { + if (!this.starts_at_time || !this.ends_at_time || !this.datepicker) + return + + const splittedStart = this.starts_at_time.split(':') + const splittedEnd = this.ends_at_time.split(':') + + this.blocker.start = moment(this.datepicker).set({hours: parseInt(splittedStart[0]), minutes: parseInt(splittedStart[1])}).format('YYYY-MM-DD HH:mm') + this.blocker.end = moment(this.datepicker).set({hours: parseInt(splittedEnd[0]), minutes: parseInt(splittedEnd[1])}).format('YYYY-MM-DD HH:mm') } }, watch: { - startEndDate(value) { - if (!value || !value[1]) - return + ends_at_time() { + this._syncDatesTimes() + }, - this.startEndDate[0] = this.startEndDate[0].setHours(this.user.work_start, 0, 0, 0) - this.startEndDate[1] = this.startEndDate[1].setHours(this.user.work_end, 0, 0, 0) + starts_at_time() { + this._syncDatesTimes() + }, - - this.blocker = { - ...this.blocker, - start: tmUtils.formatDateTimeForBackend(this.startEndDate[0]), - end: tmUtils.formatDateTimeForBackend(this.startEndDate[1]), - } + datepicker() { + this._syncDatesTimes() } } } diff --git a/app/src/plugins/tm@modal/blocker/z-delete-blocker-modal.vue b/app/src/plugins/tm@modal/blocker/z-delete-blocker-modal.vue new file mode 100644 index 0000000..18a6e02 --- /dev/null +++ b/app/src/plugins/tm@modal/blocker/z-delete-blocker-modal.vue @@ -0,0 +1,55 @@ + + + \ No newline at end of file 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 4e0093b..095e444 100644 --- a/app/src/plugins/tm@modal/task/z-task-modal.vue +++ b/app/src/plugins/tm@modal/task/z-task-modal.vue @@ -3,7 +3,7 @@ @@ -28,7 +28,11 @@
-

{{ subtask.project_code }}

+
+

{{ subtask.master_task?.project_code }}

+

{{ subtask.master_task?.name }}

+
+