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..63b79fd 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..0fe3275 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,76 @@ 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 = [] + + const { start: dayStart, end: dayEnd } = this._getDayStartEnd(date) 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, + start: dayStart, + end: this._setMinutesToDate(employee.work_start, date), + 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, + start: this._setMinutesToDate(employee.work_end, date), + end: dayEnd, + type: 'non-working-hours', split: i + 1, draggable: false }) } - return nonWorkingHours + console.log(events) + + return events }, _getSplitFromDate(date, start) { @@ -84,5 +134,18 @@ export default { let diff =(date1.getTime() - date2.getTime()) / 1000; diff /= (60 * 60) return Math.abs(Math.round(diff)) + }, + + _getDayStartEnd(date) { + return { + start: this._setMinutesToDate(0, date), + end: this._setMinutesToDate(1439, date) + } + }, + + _setMinutesToDate(minutes, date) { + const newDate = new Date(date) + newDate.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0) + return newDate } } \ No newline at end of file diff --git a/app/src/plugins/tm@dashboard/tasks/tasks.vue b/app/src/plugins/tm@dashboard/tasks/tasks.vue index 60998c5..fd5e668 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) @@ -91,11 +91,21 @@ export default { this.$events.on('on-subtask-deleted', () => { this.onFilterChanged(this.filter) }) + + this.$events.on('on-blocker-edited', () => { + this.onFilterChanged(this.filter) + }) + + this.$events.on('on-blocker-deleted', () => { + this.onFilterChanged(this.filter) + }) }, unmounted() { this.$events.off('on-subtask-edited') this.$events.off('on-subtask-deleted') + this.$events.off('on-blocker-edited') + this.$events.off('on-blocker-deleted') }, methods: { @@ -110,10 +120,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/_mixins/TeamsCalendar.mixin.ts b/app/src/plugins/tm@dashboard/teams/_mixins/TeamsCalendar.mixin.ts new file mode 100644 index 0000000..f63d6d6 --- /dev/null +++ b/app/src/plugins/tm@dashboard/teams/_mixins/TeamsCalendar.mixin.ts @@ -0,0 +1,162 @@ +import { setHours, setMinutes } from 'date-fns' +import moment from 'moment' +import timelineUtils from '@/plugins/tm@dashboard/tasks/_utils/timeline.utils' + + +export default { + computed: { + calendarData() { + if (!this.subtasks || !this.blockers) + return + + return { + unscheduledTime: 333, + tasks: [...this._generateNonWorking(), ...this._generateTasks(), ...this._generateBlockers()] + } + }, + + splitDays() { + const days = [] + + this.filter.team.employees.forEach((employee, index) => { + let workingHours = 0 + + this.calendarData.tasks.forEach(task => { + if (task?.subtask?.employee_id == employee.id) + workingHours += timelineUtils._getDifferenceInHours(task.end, task.start) + }) + + days.push({ + id: index + 1, + name: employee.name, + fullname: `${employee.name} ${employee.surname}`, + initials: employee.name.charAt(0).toUpperCase() + employee.surname.charAt(0).toUpperCase(), + unscheduledTime: workingHours + }) + }) + + return days + }, + }, + + methods: { + _generateNonWorking() { + const nonWorkingHours = [] + const date = new Date(this.filter.start) + let earliestStartHour = null + this.calendarConfig.date = date + + const { start: dayStart, end: dayEnd } = this._getDayStartEnd() + + this.filter.team.employees.forEach((employee, index) => { + if (!earliestStartHour || employee.work_start < earliestStartHour) + earliestStartHour = employee.work_start + + + nonWorkingHours.push({ + title: "Non working hours", + start: dayStart, + end: this._setMinutesToDate(employee.work_start), + split: index + 1, + type: 'non-working-hours', + draggable: false + }) + + nonWorkingHours.push({ + title: "Non working hours", + start: this._setMinutesToDate(employee.work_end), + end: dayEnd, + split: index + 1, + type: 'non-working-hours', + draggable: false + }) + }) + + this.calendarConfig.scrollHour = earliestStartHour - 1 + return nonWorkingHours + }, + + _generateTasks() { + const tasks = [] + const calendarDate = moment(this.filter.start).format('DD-MM-YYYY') + + this.subtasks.forEach(subtask => { + let split = null + let employee = null + + this.filter.team.employees.forEach((e, index) => { + if (e.id == subtask.employee_id) { + split = index + 1 + employee = e + } + }) + + subtask.timeblocks.forEach(timeblock => { + const formattedDate = moment(timeblock.start).format('DD-MM-YYYY') + + if (formattedDate == calendarDate) { + tasks.push({ + id: subtask.id, + start: new Date(timeblock.start), + end: new Date(timeblock.end), + master_task: subtask.master_task, + name: employee?.name, + surname: employee?.surname, + // date: new Date(timeblock.start), + isWeekend: moment(timeblock.start).day() % 6 == 0, + initials: employee?.name?.charAt(0)?.toUpperCase() + employee?.surname?.charAt(0)?.toUpperCase(), + split: split, + project_code: subtask.project_code, + subtask: subtask, + _timeblock_id: timeblock.id + }) + } + }) + }) + + return tasks + }, + + _generateBlockers() { + const blockers = [] + + this.blockers.forEach(blocker => { + let split = null + let employee = null + + this.filter.team.employees.forEach((e, index) => { + if (e.id == blocker.employee?.id) { + split = index + 1 + employee = e + } + }) + + blockers.push({ + blocker, + split, + start: new Date(blocker.start), + end: new Date(blocker.end), + type: 'blocker', + initials: `${employee?.name[0]}${employee?.surname[0]}`, + color: '#B00020', + draggable: false, + isWeekend: moment(blocker.start).day() % 6 == 0, + }) + }) + + return blockers + }, + + _getDayStartEnd() { + return { + start: this._setMinutesToDate(0), + end: this._setMinutesToDate(1439) + } + }, + + _setMinutesToDate(minutes, date = new Date(this.filter.start)) { + date.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0) + return date + } + } +} \ No newline at end of file diff --git a/app/src/plugins/tm@dashboard/teams/teams.vue b/app/src/plugins/tm@dashboard/teams/teams.vue index c2b944b..7600eaa 100644 --- a/app/src/plugins/tm@dashboard/teams/teams.vue +++ b/app/src/plugins/tm@dashboard/teams/teams.vue @@ -1,20 +1,20 @@ \ No newline at end of file + 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..6ffdd6c 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 @@ @@ -12,8 +12,7 @@ - - +