-
{{ event.subtask.project_code }}
-
{{ event.subtask.master_task?.name || event.subtask.master_task }}
+
+
+
+
{{ event.title }}
+
+
+
+
+
+
+ {{ event.blocker.name }}
+
+
+
+ {{ event.subtask.project_code }}
+ {{ event.subtask.master_task?.name || event.subtask.master_task }}
+
+
{{ event.initials }}
-
{{ event.start.toLocaleDateString('sk-SK') }}
+
{{ event.date?.toLocaleDateString('sk-SK') }}
-
+
+
+
+
-
-
-
-
{{ event.title }}
+
+
+
+
+
@@ -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 @@
+
+
+
+
+
Ste si istý, že chcete zmazať {{ blocker.name }}?
+
+
+
+
+ ÁNO
+ ZRUŠIŤ
+
+
+
+
+
+
\ 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 @@
-
-
+