Merge branch 'main' of bitbucket.org:studiotem/jandl-taskmanager-app

This commit is contained in:
StudioTem committed 2023-04-12 08:50:24 +02:00
commit bbc476f5cd
17 files changed
+323 -265

No files matched your search

@@ -7,8 +7,9 @@
<td>{{ mastertask.name }}</td> <td>{{ mastertask.name }}</td>
<td class="task-action"> <td class="task-action">
<div class="d-flex justify-start"> <div class="d-flex justify-start">
<img class="pencil-icon" :src="require('@/plugins/app/_img/icons/pencil.svg')" alt="pencil-icon" @click.stop="openEditMasterTaskModal(mastertask)"> <img class="pencil-icon" :src="require('@/plugins/app/_img/icons/pencil.svg')" alt="pencil-icon" @click.stop="openEditMasterTaskModal(mastertask, true)">
<img class="trash-icon" :src="require('@/plugins/app/_img/icons/bin.svg')" alt="trash-icon" @click.stop="openDeleteMasterTaskModal(mastertask)"> <img class="pencil-icon ml-2" :src="require('@/plugins/app/_img/icons/eye.svg')" alt="eye-icon" @click.stop="openEditMasterTaskModal(mastertask, false)">
<img class="trash-icon ml-2" :src="require('@/plugins/app/_img/icons/bin.svg')" alt="trash-icon" @click.stop="openDeleteMasterTaskModal(mastertask)">
</div> </div>
</td> </td>
</tr> </tr>
@@ -36,8 +37,8 @@ export default {
this.$events.emit('show-delete-master-task-modal', { mastertask }) this.$events.emit('show-delete-master-task-modal', { mastertask })
}, },
openEditMasterTaskModal(mastertask) { openEditMasterTaskModal(mastertask, editMode) {
this.$events.emit('show-mastertask-modal', { mastertask, editMode: false }) this.$events.emit('show-mastertask-modal', { mastertask, editMode })
}, },
} }
} }
@@ -15,8 +15,9 @@
<div class="d-flex justify-start"> <div class="d-flex justify-start">
<img v-if="subtask.is_finished" class="pencil-icon is-finished" :src="require('@/plugins/app/_img/icons/checkmark.svg')" alt="checkmark-icon" @click.stop="clickCompleteTask(subtask)"> <img v-if="subtask.is_finished" class="pencil-icon is-finished" :src="require('@/plugins/app/_img/icons/checkmark.svg')" alt="checkmark-icon" @click.stop="clickCompleteTask(subtask)">
<img v-if="!subtask.is_finished" class="pencil-icon" :src="require('@/plugins/app/_img/icons/checkmark.svg')" alt="checkmark-icon" @click.stop="clickCompleteTask(subtask)"> <img v-if="!subtask.is_finished" class="pencil-icon" :src="require('@/plugins/app/_img/icons/checkmark.svg')" alt="checkmark-icon" @click.stop="clickCompleteTask(subtask)">
<img class="pencil-icon ml-2" :src="require('@/plugins/app/_img/icons/eye.svg')" alt="pencil-icon" @click.stop="openTaskModal(subtask)"> <img class="pencil-icon ml-2" :src="require('@/plugins/app/_img/icons/pencil.svg')" alt="pencil-icon" @click.stop="openTaskModal(subtask, true)">
<img class="trash-icon" :src="require('@/plugins/app/_img/icons/bin.svg')" alt="trash-icon" @click.stop="openDeleteTaskModal(subtask)"> <img class="pencil-icon ml-2" :src="require('@/plugins/app/_img/icons/eye.svg')" alt="eye-icon" @click.stop="openTaskModal(subtask, false)">
<img class="trash-icon ml-2" :src="require('@/plugins/app/_img/icons/bin.svg')" alt="trash-icon" @click.stop="openDeleteTaskModal(subtask)">
</div> </div>
</td> </td>
</tr> </tr>
@@ -61,8 +62,8 @@ export default {
this.$events.emit('show-delete-task-modal', { subtask }) this.$events.emit('show-delete-task-modal', { subtask })
}, },
openTaskModal(subtask) { openTaskModal(subtask, editMode) {
this.$events.emit('show-subtask-modal', { subtask, editMode: false }) this.$events.emit('show-subtask-modal', { subtask, editMode })
}, },
async clickCompleteTask(subtask) { async clickCompleteTask(subtask) {
+44 -13
View File
@@ -1,9 +1,14 @@
<template> <template>
<form> <div>
<A-search-result-mastertasks v-if="searchResults?.mastertasks" :mastertasks="searchResults.mastertasks"/> <p v-if="!this.searchResults" class="px-3 py-3">{{ this.search?.length ? $t('Searching...') : $t('Type something') }}</p>
<A-search-result-subtasks v-if="searchResults?.subtasks" :subtasks="searchResults.subtasks" /> <p v-else-if="this.searchResults.error" class="px-3">{{ this.searchResults.error }}</p>
<A-search-result-team v-if="searchResults?.teams" :teams="searchResults.teams" />
</form> <div v-else>
<A-search-result-mastertasks :mastertasks="searchResults.mastertasks"/>
<A-search-result-subtasks :subtasks="searchResults.subtasks" />
<A-search-result-team :teams="searchResults.teams" />
</div>
</div>
</template> </template>
<script> <script>
@@ -21,33 +26,59 @@ export default {
data() { data() {
return { return {
searchResults: {}, searchResults: null,
search: '' search: ''
} }
}, },
created() { created() {
this.$events.on('onSearch', (search) => { this.$events.on('onSearch', (search) => {
if (!search?.length)
return
this.search = search this.search = search
this.onSearch(search)
}) })
}, },
watch: { mounted() {
search(newVal) { this.$events.on('on-subtask-edited', () => {
this.updateSearchResults(newVal) this.onSearch(this.search, false)
} })
this.$events.on('on-subtask-deleted', () => {
this.onSearch(this.search, false)
})
this.$events.on('on-mastertask-edited', () => {
this.onSearch(this.search, false)
})
this.$events.on('on-mastertask-deleted', () => {
this.onSearch(this.search, false)
})
},
unmounted() {
this.$events.off('on-subtask-edited')
this.$events.off('on-subtask-deleted')
this.$events.off('on-mastertask-edited')
this.$events.off('on-mastertask-deleted')
}, },
methods: { methods: {
updateSearchResults: debounce(async function (search) { onSearch: debounce(async function (search, reset = true) {
try { try {
if (reset)
this.searchResults = null
const { mastertasks, subtasks, teams} = await this.$axios.get_auth(`v1/search?q=${search}`) const { mastertasks, subtasks, teams} = await this.$axios.get_auth(`v1/search?q=${search}`)
this.searchResults = {mastertasks: mastertasks.data, subtasks: subtasks.data, teams: teams.data} this.searchResults = {mastertasks: mastertasks.data, subtasks: subtasks.data, teams: teams.data}
console.log(this.searchResults)
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
this.searchResults = {error}
} }
}, 500) }, 300)
} }
} }
</script> </script>
@@ -26,10 +26,11 @@
<template v-slot:split-label="{ split }"> <template v-slot:split-label="{ split }">
<div class="w-100 h-100 d-flex flex-column"> <div class="w-100 h-100 d-flex flex-column">
<span v-if="split.date" class="d-flex align-center"> <span v-if="split.date" class="d-flex align-center">
<img v-if="getHeadingDate(split.date) == 'Dnes'" :src="require('@/plugins/app/_img/icons/home.svg')" class="mr-2"> <img v-if="split.isHomeoffice" :src="require('@/plugins/app/_img/icons/home.svg')" class="mr-2">
<p class="weekday-label">{{ getHeadingDate(split.date) }}</p> <p class="weekday-label">{{ getHeadingDate(split.date) }}</p>
</span> </span>
<span v-else class="d-flex align-center"> <span v-else class="d-flex align-center">
<img v-if="split.isHomeoffice" :src="require('@/plugins/app/_img/icons/home.svg')" class="mr-2">
<p class="weekday-label">{{ split.name }}</p> <p class="weekday-label">{{ split.name }}</p>
</span> </span>
@@ -64,16 +65,16 @@
</div> </div>
</div> </div>
<div v-else-if="event.blocker?.type != 'home_office'" class="custom-event-body w-100 h-100 d-flex flex-column justify-space-between" :class="{'short-event' : getEventLength(event) <= 9000000, 'mini-event' : getEventLength(event) <= 5400000, 'micro-event' : getEventLength(event) <= 3600000}" :style="{backgroundColor: event.color || event.subtask.color}"> <div v-else-if="['subtask', 'blocker', 'adhoc', 'vacation', 'free_day'].includes(event.type)" class="custom-event-body w-100 h-100 d-flex flex-column justify-space-between" :class="{'short-event' : getEventLength(event) <= 9000000, 'mini-event' : getEventLength(event) <= 5400000, 'micro-event' : getEventLength(event) <= 3600000}" :style="{backgroundColor: event.color || event.subtask.color}">
<div class="event-content" :class="event.type || 'subtask'"> <div class="event-content" :class="{'is-blocker': event.isBlocker}">
<template v-if="event.type == 'blocker'"> <template v-if="event.type == 'subtask'">
<h3>{{ event.blocker.name }}</h3>
</template>
<template v-else>
<small v-if="event.subtask.project_code" class="project-code">{{ event.subtask.project_code }}</small> <small v-if="event.subtask.project_code" class="project-code">{{ event.subtask.project_code }}</small>
<h3>{{ event.subtask.master_task?.name || event.subtask.master_task }}</h3> <h3>{{ event.subtask.master_task?.name || event.subtask.master_task }}</h3>
</template> </template>
<template v-else>
<h3>{{ event.blocker.name }}</h3>
</template>
<div class="d-flex align-center event-details event-initials-wrapper"> <div class="d-flex align-center event-details event-initials-wrapper">
<div class="event-initials d-flex justify-center align-center"> <div class="event-initials d-flex justify-center align-center">
@@ -84,12 +85,12 @@
</div> </div>
<div class="event-footer d-flex"> <div class="event-footer d-flex">
<template v-if="event.type == 'blocker'"> <template v-if="event.type == 'adhoc'">
<v-btn rounded class="event-footer-button me-2" @click.stop="editBlocker(event)"><img :src="require('@/plugins/app/_img/icons/pencil.svg')" alt=""></v-btn> <v-btn rounded class="event-footer-button me-2" @click.stop="editBlocker(event)"><img :src="require('@/plugins/app/_img/icons/pencil.svg')" alt=""></v-btn>
<v-btn rounded class="event-footer-button me-2" @click.stop="deleteBlocker(event)"><img :src="require('@/plugins/app/_img/icons/bin.svg')" alt=""></v-btn> <v-btn rounded class="event-footer-button me-2" @click.stop="deleteBlocker(event)"><img :src="require('@/plugins/app/_img/icons/bin.svg')" alt=""></v-btn>
</template> </template>
<template v-else> <template v-else-if="event.type == 'subtask'">
<v-btn rounded class="event-footer-button me-2" :class="{'completed': event.subtask?.is_finished}" @click.stop="completeTask(event)"><img :src="require('@/plugins/app/_img/icons/checkmark.svg')" alt=""></v-btn> <v-btn rounded class="event-footer-button me-2" :class="{'completed': event.subtask?.is_finished}" @click.stop="completeTask(event)"><img :src="require('@/plugins/app/_img/icons/checkmark.svg')" alt=""></v-btn>
<v-btn rounded class="event-footer-button me-2" @click.stop="editTask(event)"><img :src="require('@/plugins/app/_img/icons/eye.svg')" alt=""></v-btn> <v-btn rounded class="event-footer-button me-2" @click.stop="editTask(event)"><img :src="require('@/plugins/app/_img/icons/eye.svg')" alt=""></v-btn>
<v-btn rounded class="event-footer-button me-2" @click.stop="deleteTask(event)"><img :src="require('@/plugins/app/_img/icons/bin.svg')" alt=""></v-btn> <v-btn rounded class="event-footer-button me-2" @click.stop="deleteTask(event)"><img :src="require('@/plugins/app/_img/icons/bin.svg')" alt=""></v-btn>
@@ -209,11 +210,7 @@ export default {
}, },
taskClicked(subtask) { taskClicked(subtask) {
if (subtask.type == 'non-working-hours') return // console.log('task clicked')
// console.log("task", task)
if (subtask.end.getTime() - subtask.start.getTime() <= 3600000) {
// console.log("ShortTask")
}
}, },
eventFocused(event) { eventFocused(event) {
@@ -385,7 +382,7 @@ export default {
padding: 20px 10px padding: 20px 10px
.event-content .event-content
&.blocker &.is-blocker
* *
color: #ffffff !important color: #ffffff !important
@@ -3,10 +3,15 @@ import moment from 'moment'
export default { export default {
generateTimelineTasks(subtasks, date, employee) { generateTimelineTasks(subtasks, date, employee) {
return [ const { tasks, blockers, homeoffices } = this.generateTimelineEvents(subtasks, employee, date)
...this.generateTimelineEvents(subtasks, employee, date), const { nonWorkingHours } = this.generateTimelineNonWorkingHours(date, employee)
...this.generateTimelineNonWorkingHours(date, employee)
] return {
tasks,
blockers,
nonWorkingHours,
homeoffices
}
}, },
getUnscheduledTime(subtasks) { getUnscheduledTime(subtasks) {
@@ -21,19 +26,24 @@ export default {
}, },
generateTimelineEvents(subtasks, employee, date) { generateTimelineEvents(subtasks, employee, date) {
let events = [] let blockers = []
const tasks = []
subtasks.forEach(subtask => { subtasks.forEach(subtask => {
if (subtask.type == 'blocker') { if (['blocker', 'adhoc', 'home_office', 'vacation', 'free_day'].includes(subtask.type)) {
events = [...events, ...this._generateEventsFromBlocker(subtask, date)] blockers = [...blockers, ...this._generateEventsFromBlocker(subtask, date)]
} else { } else {
subtask.timeblocks?.forEach((timeblock) => { subtask.timeblocks?.forEach((timeblock) => {
events.push(this._composeTimelineEvent(subtask, employee, date, timeblock)) tasks.push(this._composeTimelineEvent(subtask, employee, date, timeblock))
}) })
} }
}) })
return events return {
tasks,
blockers: blockers.filter(blocker => blocker.type != 'home_office'),
homeoffices: blockers.filter(blocker => blocker.type == 'home_office')
}
}, },
_composeTimelineEvent(subtask, employee, date, timeblock) { _composeTimelineEvent(subtask, employee, date, timeblock) {
@@ -50,7 +60,10 @@ export default {
split: this._getSplitFromDate(date, timeblock.start), split: this._getSplitFromDate(date, timeblock.start),
project_code: subtask.project_code, project_code: subtask.project_code,
subtask: subtask, subtask: subtask,
_timeblock_id: timeblock.id type: 'subtask',
_timeblock_id: timeblock.id,
_real_start: new Date(timeblock.start),
_real_end: new Date(timeblock.end)
} }
}, },
@@ -58,8 +71,7 @@ export default {
const start = moment(blocker.start) const start = moment(blocker.start)
const end = moment(blocker.end) const end = moment(blocker.end)
const dates = start == end ? [moment(start).format('YYYY-MM-DD')] : this._getDatesInRange(start, end) const dates = start.isSame(end, 'date') ? [moment(start).format('YYYY-MM-DD')] : this._getDatesInRange(start, end)
const blockers = dates.map(d => { const blockers = dates.map(d => {
const diffInDays = differenceInCalendarDays(date, new Date(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 dStart = new Date(moment(d).set({hours: start.hours(), minutes: start.minutes()}).format('YYYY-MM-DD HH:mm'))
@@ -71,10 +83,13 @@ export default {
start: addDays(dStart, diffInDays), start: addDays(dStart, diffInDays),
end: addDays(dEnd, diffInDays), end: addDays(dEnd, diffInDays),
date: new Date(d), date: new Date(d),
type: 'blocker', type: blocker.type,
isBlocker: true,
initials: `${blocker.employee?.name[0]}${blocker.employee?.surname[0]}`, initials: `${blocker.employee?.name[0]}${blocker.employee?.surname[0]}`,
color: '#B00020', color: '#B00020',
draggable: false draggable: false,
_real_start: dStart,
_real_end: dEnd
} }
}) })
@@ -117,7 +132,7 @@ export default {
}) })
} }
return events return {nonWorkingHours: events}
}, },
_getSplitFromDate(date, start) { _getSplitFromDate(date, start) {
@@ -125,8 +140,12 @@ export default {
}, },
_getDifferenceInHours(date1, date2) { _getDifferenceInHours(date1, date2) {
let diff =(date1.getTime() - date2.getTime()) / 1000; return this._getDifferenceInMinutes(date1, date2) / 60
diff /= (60 * 60) },
_getDifferenceInMinutes(date1, date2) {
let diff = (date1.getTime() - date2.getTime()) / 1000
diff /= 60
return Math.abs(Math.round(diff)) return Math.abs(Math.round(diff))
}, },
+61 -10
View File
@@ -36,30 +36,78 @@ export default {
]), ]),
splitDays() { splitDays() {
const days = [] const days = []
const workingHours = {} const homeoffice = {}
const schedulesCapacityBySplit = {}
const now = moment()
const dailyCapacityMinutes = this.user.work_end - this.user.work_start
this.timelineData.tasks.forEach(task => { this.timelineData.tasks.forEach(task => {
if (!workingHours[task.split])
workingHours[task.split] = 0
if (task.type == 'non-working-hours' || task.subtask?.is_finished || task.blocker?.is_finished) if (task.type == 'non-working-hours' || task.subtask?.is_finished || task.blocker?.is_finished)
return return
workingHours[task.split] += timelineUtils._getDifferenceInHours(task.end, task.start) if (!schedulesCapacityBySplit[task.split])
schedulesCapacityBySplit[task.split] = 0
if (now.isAfter(moment(task._real_end))) {
// Ignore elapsed task
return
}
else if (now.isAfter(moment(task._real_start) && now.isBefore(moment(task._real_end)))) {
// Task in progress
schedulesCapacityBySplit[task.split] += timelineUtils._getDifferenceInMinutes(task._real_end, new Date())
}
else {
// Future task
schedulesCapacityBySplit[task.split] += timelineUtils._getDifferenceInMinutes(task._real_end, task._real_start)
}
})
schedulesCapacityBySplit[1] = 0 // Set YESTERDAY value to 0
this.timelineData.homeoffices.forEach(task => {
homeoffice[task.split] = true
}) })
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
if (!schedulesCapacityBySplit[i + 1])
schedulesCapacityBySplit[i + 1] = 0
let unscheduledTime
if (i == 0) {
// Yesterday
unscheduledTime = 0
} else if (i == 1) {
// Today
const elapsedMinutes = now.diff(now.clone().startOf('day'), 'minutes')
const workStart = this.user.work_start > elapsedMinutes ? this.user.workStart : elapsedMinutes
const todayCapacityMinutes = this.user.work_end - workStart
unscheduledTime = todayCapacityMinutes - schedulesCapacityBySplit[i + 1] < 0 ? 0 : todayCapacityMinutes - schedulesCapacityBySplit[i + 1]
} else {
// Future
unscheduledTime = dailyCapacityMinutes - schedulesCapacityBySplit[i + 1]
}
console.log(unscheduledTime)
days.push({ days.push({
id: i + 1, id: i + 1,
fullname: `${this.filter.employee.name} ${this.filter.employee.surname}`, fullname: `${this.filter.employee.name} ${this.filter.employee.surname}`,
initials: this.filter.employee.name.charAt(0).toUpperCase() + this.filter.employee.surname.charAt(0).toUpperCase(), initials: this.filter.employee.name.charAt(0).toUpperCase() + this.filter.employee.surname.charAt(0).toUpperCase(),
unscheduledTime: workingHours[i + 1], unscheduledTime: (unscheduledTime / 60).toFixed(1),
date: addDays(this.filter.start, i), date: addDays(this.filter.start, i),
isHomeoffice: homeoffice[i],
isWeekend: timelineUtils.isWeekend(addDays(this.filter.start, i)) isWeekend: timelineUtils.isWeekend(addDays(this.filter.start, i))
}) })
} }
console.log(days)
return days return days
}, },
@@ -74,11 +122,14 @@ export default {
if (!this.subtasks || !this.filter) if (!this.subtasks || !this.filter)
return null return null
const { tasks, blockers, nonWorkingHours, homeoffices } = timelineUtils.generateTimelineTasks(this.subtasks, new Date(this.filter.start), this.filter.employee)
return { return {
name: this.filter.employee.name, name: this.filter.employee.name,
surname: this.filter.employee.surname, surname: this.filter.employee.surname,
unscheduledTime: this.unscheduledTime, unscheduledTime: this.unscheduledTime,
tasks: timelineUtils.generateTimelineTasks(this.subtasks, new Date(this.filter.start), this.filter.employee) tasks: [...tasks, ...blockers, ...nonWorkingHours],
homeoffices
} }
} }
}, },
@@ -121,9 +172,9 @@ export default {
try { try {
const { subtasks, blockers } = await this.$axios.get_auth(`v1/subtasks?${queryParameters}`) const { subtasks, blockers } = await this.$axios.get_auth(`v1/subtasks?${queryParameters}`)
this.config.scrollHour = this.filter?.employee.work_start - 1 this.config.scrollHour = (this.filter?.employee.work_start / 60) - 1
this.config.date = this.filter.start this.config.date = this.filter.start
this.subtasks = [...subtasks, ...blockers.map(blocker => ({...blocker, type: 'blocker'}))] this.subtasks = [...subtasks, ...blockers]
} catch (error) { } catch (error) {
this.$toast.error('Nepodarilo sa nacitat data!') this.$toast.error('Nepodarilo sa nacitat data!')
} }
@@ -83,7 +83,8 @@ export default {
_updateFilters() { _updateFilters() {
this.filter.start = this.date this.filter.start = this.date
this.filter.end = addDays(this.date, 1) this.filter.end = this.date
// this.filter.end = addDays(this.date, 1)
this.selectedDate = String(this.filter.start?.toLocaleDateString('sk-SK')) this.selectedDate = String(this.filter.start?.toLocaleDateString('sk-SK'))
this._emitFilterChanged() this._emitFilterChanged()
@@ -9,21 +9,35 @@ export default {
if (!this.subtasks || !this.blockers) if (!this.subtasks || !this.blockers)
return return
const blockers = this._generateBlockers()
console.log([...this._generateNonWorking(), ...this._generateTasks(), ...blockers.filter(blocker => blocker.type != 'home_office')])
return { return {
unscheduledTime: 333, unscheduledTime: 333,
tasks: [...this._generateNonWorking(), ...this._generateTasks(), ...this._generateBlockers()] tasks: [...this._generateNonWorking(), ...this._generateTasks(), ...blockers.filter(blocker => blocker.type != 'home_office')],
homeoffices: [...blockers.filter(blocker => blocker.type == 'home_office')]
} }
}, },
splitDays() { splitDays() {
const days = [] const days = []
const homeoffice = {}
this.filter.team.employees.forEach((employee, index) => { this.filter.team.employees.forEach((employee, index) => {
let workingHours = 0 let workingHours = 0
this.calendarData.tasks.forEach(task => { this.calendarData.tasks.forEach(task => {
if (task?.subtask?.employee_id == employee.id) if ((task.subtask?.employee?.id != employee.id) && (task.blocker?.employee?.id != employee.id))
workingHours += timelineUtils._getDifferenceInHours(task.end, task.start) return
workingHours += timelineUtils._getDifferenceInHours(task.end, task.start)
})
this.calendarData.homeoffices.forEach(task => {
if (task.blocker.employee?.id != employee.id) return
homeoffice[task.split] = true
}) })
days.push({ days.push({
@@ -31,7 +45,8 @@ export default {
name: employee.name, name: employee.name,
fullname: `${employee.name} ${employee.surname}`, fullname: `${employee.name} ${employee.surname}`,
initials: employee.name.charAt(0).toUpperCase() + employee.surname.charAt(0).toUpperCase(), initials: employee.name.charAt(0).toUpperCase() + employee.surname.charAt(0).toUpperCase(),
unscheduledTime: workingHours unscheduledTime: workingHours,
isHomeoffice: homeoffice[index + 1] ? true : false
}) })
}) })
@@ -43,14 +58,14 @@ export default {
_generateNonWorking() { _generateNonWorking() {
const nonWorkingHours = [] const nonWorkingHours = []
const date = new Date(this.filter.start) const date = new Date(this.filter.start)
let earliestStartHour = null let earliestStartMinutes = null
this.calendarConfig.date = date this.calendarConfig.date = date
const { start: dayStart, end: dayEnd } = this._getDayStartEnd() const { start: dayStart, end: dayEnd } = this._getDayStartEnd()
this.filter.team.employees.forEach((employee, index) => { this.filter.team.employees.forEach((employee, index) => {
if (!earliestStartHour || employee.work_start < earliestStartHour) if (!earliestStartMinutes || (employee.work_start) < earliestStartMinutes)
earliestStartHour = employee.work_start earliestStartMinutes = employee.work_start
nonWorkingHours.push({ nonWorkingHours.push({
@@ -72,7 +87,7 @@ export default {
}) })
}) })
this.calendarConfig.scrollHour = earliestStartHour - 1 this.calendarConfig.scrollHour = (earliestStartMinutes) - 1
return nonWorkingHours return nonWorkingHours
}, },
@@ -107,7 +122,7 @@ export default {
initials: employee?.name?.charAt(0)?.toUpperCase() + employee?.surname?.charAt(0)?.toUpperCase(), initials: employee?.name?.charAt(0)?.toUpperCase() + employee?.surname?.charAt(0)?.toUpperCase(),
split: split, split: split,
project_code: subtask.project_code, project_code: subtask.project_code,
subtask: subtask, subtask,
_timeblock_id: timeblock.id _timeblock_id: timeblock.id
}) })
} }
@@ -136,7 +151,8 @@ export default {
split, split,
start: new Date(blocker.start), start: new Date(blocker.start),
end: new Date(blocker.end), end: new Date(blocker.end),
type: 'blocker', type: blocker.type,
isBlocker: true,
initials: `${employee?.name[0]}${employee?.surname[0]}`, initials: `${employee?.name[0]}${employee?.surname[0]}`,
color: '#B00020', color: '#B00020',
draggable: false, draggable: false,
@@ -1,7 +1,6 @@
<template> <template>
<div> <div>
<A-team-filter v-if="$store.getters['tm/isInitialized']" :unscheduledTime="calendarData?.unscheduledTime || 0" @filter-changed="onFilterChanged" /> <A-team-filter v-if="$store.getters['tm/isInitialized']" :unscheduledTime="calendarData?.unscheduledTime || 0" @filter-changed="onFilterChanged" />
<Z-timeline v-if="calendarData" :cal-data="calendarData" :cal-config="calendarConfig" :splits="splitDays" /> <Z-timeline v-if="calendarData" :cal-data="calendarData" :cal-config="calendarConfig" :splits="splitDays" />
</div> </div>
</template> </template>
@@ -1,5 +1,5 @@
<template> <template>
<v-row no-gutters class="align-center" v-if="employee"> <v-row no-gutters class="align-center">
<v-col cols="5"> <v-col cols="5">
<v-sheet class="py-3"> <v-sheet class="py-3">
<div class="d-flex align-center"> <div class="d-flex align-center">
@@ -12,9 +12,9 @@
<v-col cols="7"> <v-col cols="7">
<v-sheet class="py-3 d-flex align-center"> <v-sheet class="py-3 d-flex align-center">
<p class="employee-selected-avatar d-flex justify-center align-center"> <p class="employee-selected-avatar d-flex justify-center align-center">
{{ employee._initials }} {{ employee ? employee._initials : '?' }}
</p> </p>
<p class="ml-2">{{ employee._fullName }}</p> <p class="ml-2">{{ employee ? employee._fullName : $t('Unasigned') }}</p>
</v-sheet> </v-sheet>
</v-col> </v-col>
</v-row> </v-row>
@@ -3,34 +3,28 @@ export default {
employeesOptions() { employeesOptions() {
const selectedEmployees = [...this.mastertask.employees].map(employee => employee.id) const selectedEmployees = [...this.mastertask.employees].map(employee => employee.id)
const employees = this.enrichedEmployees.filter( return this.enrichedEmployees.filter(
(employee) => !selectedEmployees.includes(employee.id) (employee) => !selectedEmployees.includes(employee.id)
) || [] ) || []
},
return employees.map((employee) => { projectsOptions() {
return { // Added to "paginate" projects, because v-autocomplete was unable to handle so many projects at once
key: employee.id,
value: `${employee.name} ${employee.surname}`, const projects = this.projects.filter(project => project.code.includes(this.projectCodeSearch))
} return projects?.length ? projects.slice(0, 10) : this.projects.slice(0, 10)
})
} }
}, },
methods: { methods: {
onProjectSelected({ key }) { // onProjectSelected(id) {
const project = this.projects.find(project => project.id == key) // const project = this.projects.find(project => project.id == id)
this.mastertask.project_code = project.code // this.mastertask.project_code = project.code
this.mastertask.project_id = project.id // this.mastertask.project_id = project.id
}, // },
onMasterTaskSelected({ key }) { onEmployeeSelected(id) {
const mastertask = this.mastertasks.find(mastertask => mastertask.id == key) const employee = this.enrichedEmployees.find(employee => employee.id == id)
this.subtask.master_task = mastertask
this.subtask.project_code = mastertask.project_code
},
onEmployeeSelected({ key }) {
const employee = this.enrichedEmployees.find(employee => employee.id == key)
this.mastertask.employees.push(employee) this.mastertask.employees.push(employee)
this.showAddEmployeeInput = false this.showAddEmployeeInput = false
}, },
@@ -60,6 +54,6 @@ export default {
this.mastertask.team = this.teams.find(team => team.id == value) this.mastertask.team = this.teams.find(team => team.id == value)
this.mastertask.employees = this.mastertask.team.employees this.mastertask.employees = this.mastertask.team.employees
} },
} }
} }
@@ -8,17 +8,17 @@
</div> </div>
<v-card-actions class="d-flex flex-column modal-content"> <v-card-actions class="d-flex flex-column modal-content">
<Z-autocomplete label="Kód projektu" :options="projects.map((project) => ({ key: project.id, value: project.code }))" :validations="v$.mastertask.project_code" :clear-on-submit="false" :value="mastertask.project_code" :class="{ error: v$.mastertask.project_code.$errors.length }" class="modal-input" @option-selected="onProjectSelected" /> <v-autocomplete v-model="mastertask.project_id" :label="$t('Project code')" :class="{error: v$.mastertask.project_id.$errors.length}" :items="projectsOptions" item-title="code" item-value="id" variant="solo" class="modal-input" clearable @update:search="projectCodeSearch = $event" />
<v-text-field v-model="mastertask.name" @focus="onInputFocus" class="modal-input" label="Názov mastertasku" variant="solo" :class="{ error: v$.mastertask.name.$errors.length }" :hint="v$.mastertask.name.$errors ? errorUtils.parseErrors(v$.mastertask.name.$errors) : null"></v-text-field> <v-text-field v-model="mastertask.name" @focus="onInputFocus" class="modal-input" label="Názov mastertasku" variant="solo" :class="{ error: v$.mastertask.name.$errors.length }" :hint="v$.mastertask.name.$errors ? errorUtils.parseErrors(v$.mastertask.name.$errors) : null"></v-text-field>
<Datepicker v-model="mastertask.deadline" @focus="onDateFocus" class="modal-input" format="dd.MM.yyyy, HH:mm" locale="sk" :class="{ error: v$.mastertask.deadline.$errors.length }" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Deadline" /> <Datepicker v-model="mastertask.deadline" @focus="onDateFocus" :min-date="new Date()" class="modal-input" format="dd.MM.yyyy, HH:mm" locale="sk" :class="{ error: v$.mastertask.deadline.$errors.length }" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Deadline" />
<v-text-field v-model="mastertask.duration" @focus="onInputFocus" class="modal-input mt-5" variant="solo" type="number" disabled :label="`${mastertask.duration} h`"></v-text-field> <v-text-field v-model="mastertask.duration" @focus="onInputFocus" class="modal-input mt-5" variant="solo" type="number" disabled :label="`${mastertask.duration} h`"></v-text-field>
<v-select v-model="this.mastertask.team_id" @focus="onInputFocus" class="modal-input border-select" :class="{ error: v$.mastertask.team_id.$errors.length }" label="Tím" variant="solo" :items="teams" item-value="id" item-title="name"></v-select> <v-select v-model="mastertask.team_id" @focus="onInputFocus" class="modal-input border-select" :class="{ error: v$.mastertask.team_id.$errors.length }" label="Tím" variant="solo" :items="teams" item-value="id" item-title="name"></v-select>
<template v-if="!mastertask.id"> <template v-if="!mastertask.id && mastertask.team_id">
<v-text-field v-for="(employee, index) in mastertask.employees" :key="employee.id" @focus="onInputFocus" :class="{ error: !mastertask.employees[index].duration }" class="modal-input" :label="`${employee.name} ${employee.surname}`" variant="solo" placeholder="h" v-model="mastertask.employees[index].duration" type="number" min="1" @input="onEmployeeDurationChanged"> <v-text-field v-for="(employee, index) in mastertask.employees" :key="employee.id" @focus="onInputFocus" :class="{ error: !mastertask.employees[index].duration }" class="modal-input" :label="`${employee.name} ${employee.surname}`" variant="solo" placeholder="h" v-model="mastertask.employees[index].duration" type="number" min="1" @input="onEmployeeDurationChanged">
<p class="employee-select-avatar d-flex justify-center align-center"> <p class="employee-select-avatar d-flex justify-center align-center">
{{ employee.name.charAt(0) + employee.surname.charAt(0) }} {{ employee.name.charAt(0) + employee.surname.charAt(0) }}
@@ -26,7 +26,9 @@
</v-text-field> </v-text-field>
</template> </template>
<Z-autocomplete label="Vyhľadať zamestnanca" v-if="showAddEmployeeInput" :options="employeesOptions" class="modal-input" @option-selected="onEmployeeSelected" /> <v-autocomplete v-if="showAddEmployeeInput" :label="$t('Search for employee')" :class="{error: v$.mastertask.project_id.$errors.length}" :items="employeesOptions" item-title="_fullName" item-value="id" variant="solo" class="modal-input" clearable @update:model-value="onEmployeeSelected" />
<!-- <Z-autocomplete label="Vyhľadať zamestnanca" :options="employeesOptions" class="modal-input" @option-selected="onEmployeeSelected" /> -->
<v-btn class="w-100 mb-5 button secondary" :disabled="!mastertask.team?.id" @click="showAddEmployeeInput = true"> <v-icon class="mx-1" icon="mdi-plus" />Pridať zamestnanca </v-btn> <v-btn class="w-100 mb-5 button secondary" :disabled="!mastertask.team?.id" @click="showAddEmployeeInput = true"> <v-icon class="mx-1" icon="mdi-plus" />Pridať zamestnanca </v-btn>
@@ -79,11 +81,11 @@ import ZFilesUpload from '@/plugins/tm@_components/z-files-upload.vue'
import useVuelidate from '@vuelidate/core' import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators' import { required } from '@vuelidate/validators'
import errorUtils from '@/plugins/lib@auth/_utils/error.utils' import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import ZAutocomplete from '@/plugins/tm@_components/z-autocomplete.vue' // import ZAutocomplete from '@/plugins/tm@_components/z-autocomplete.vue'
import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus' import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus'
import moment from 'moment' import moment from 'moment'
import { QuillEditor } from '@vueup/vue-quill' import { QuillEditor } from '@vueup/vue-quill'
import tmUtils from '@/plugins/tm@_utils/tm.utils' // import tmUtils from '@/plugins/tm@_utils/tm.utils'
import '@vueup/vue-quill/dist/vue-quill.snow.css' import '@vueup/vue-quill/dist/vue-quill.snow.css'
@@ -94,7 +96,7 @@ import { mapGetters } from 'vuex'
export default { export default {
// MIXINS // MIXINS
// computed: employeesOptions // computed: employeesOptions, projectsOptions
// methods: onProjectSelected, onEmployeeSelected, onEmployeeDurationChanged // methods: onProjectSelected, onEmployeeSelected, onEmployeeDurationChanged
// watch: mastertask.team_id // watch: mastertask.team_id
@@ -106,7 +108,7 @@ export default {
ATasksTable, ATasksTable,
Datepicker, Datepicker,
ZFilesUpload, ZFilesUpload,
ZAutocomplete, // ZAutocomplete,
QuillEditor QuillEditor
}, },
@@ -119,6 +121,7 @@ export default {
mastertask: null, mastertask: null,
showAddEmployeeInput: false, showAddEmployeeInput: false,
date: null, date: null,
projectCodeSearch: null
} }
}, },
@@ -127,13 +130,29 @@ export default {
'teams', 'teams',
'projects', 'projects',
'enrichedEmployees' 'enrichedEmployees'
]) ]),
enrichedSubtasks() {
if (!this.mastertask?.subtasks)
return null
return this.mastertask.subtasks.map(subtask => {
const employee = this.enrichedEmployees.find(employee => employee.id == subtask.employee_id)
return {
...subtask,
_initials: employee ? employee.name.charAt(0) + employee.surname.charAt(0) : '?',
_fullName: employee ? `${employee.name} ${employee.surname}` : ''
}
})
}
}, },
validations() { validations() {
return { return {
mastertask: { mastertask: {
project_code: { required }, project_id: { required },
team_id: { required }, team_id: { required },
name: { required }, name: { required },
description: { required }, description: { required },
@@ -171,7 +190,8 @@ export default {
methods: { methods: {
async showModal({mastertask, editMode = false}) { async showModal({mastertask, editMode = false}) {
this.visible = true this.editMode = null
this.visible = true
this.mastertask = null this.mastertask = null
this.$nextTick(() => this.v$.$reset()) this.$nextTick(() => this.v$.$reset())
@@ -179,11 +199,12 @@ export default {
if (mastertask?.id) { if (mastertask?.id) {
this.mastertask = await this.$axios.get_auth_data(`v1/mastertasks/${mastertask.id}`) this.mastertask = await this.$axios.get_auth_data(`v1/mastertasks/${mastertask.id}`)
const project = this.projects.find((project) => project.code == this.mastertask?.project_code) // TODO zmazat ked zacne prichadzat project_id a team_id
const project = this.projects.find((project) => project.code == this.mastertask.project_code)
this.mastertask.team_id = this.mastertask.team?.id || null
this.mastertask.project_id = project.id this.mastertask.project_id = project.id
this.mastertask.project_code = project.code this.mastertask.team_id = this.mastertask.team?.id || null
// this.mastertask.project_code = project.code
} else { } else {
this.mastertask = { employees: [], duration: 0 } this.mastertask = { employees: [], duration: 0 }
} }
@@ -205,15 +226,6 @@ export default {
if (this.$refs['description']) if (this.$refs['description'])
this.mastertask.description = this.$refs['description'].getHTML() this.mastertask.description = this.$refs['description'].getHTML()
// this.mastertask.subtasks = this.mastertask.subtasks.map(subtask => {
// const employee = this.mastertask.employees.find(employee => employee.id == subtask.employee_id)
// subtask.duration = employee.duration
// return subtask
// })
// this.mastertask.project_code = null
try { try {
this.mastertask.deadline = moment(this.mastertask.deadline).format('YYYY-MM-DD HH:MM:SS') this.mastertask.deadline = moment(this.mastertask.deadline).format('YYYY-MM-DD HH:MM:SS')
@@ -22,11 +22,11 @@
<script> <script>
export default { export default {
props: { props: {
employee: { employee: {
type: Object, type: Object,
required: true required: true
} }
} }
} }
</script> </script>
@@ -1,9 +1,9 @@
<template> <template>
<A-mastertask-name :name="subtask.master_task?.name || task.master_task" :id="subtask.master_task.id" :color="subtask.master_task.color" @close-modal="$emit('close-modal')" /> <A-mastertask-name :name="subtask._master_task.name" :id="subtask.master_task_id" :color="subtask._master_task.color" @close-modal="$emit('close-modal')" />
<A-mastertask-status :status="subtask.is_finished" /> <A-mastertask-status :status="subtask.is_finished" />
<A-task-timeblocks :timeblocks="subtask.timeblocks" :subtask="subtask" /> <A-task-timeblocks :timeblocks="subtask.timeblocks" :subtask="subtask" />
<Z-created-by :employee-id="subtask.created_by" /> <Z-created-by :employee-id="subtask.created_by" />
<A-task-assigned-employee :employee="subtask.employee" /> <A-task-assigned-employee :employee="subtask._employee" />
<A-task-planned-time :hours="subtask.duration" /> <A-task-planned-time :hours="subtask.duration" />
<A-task-remaining-time :hours="subtask.duration - subtask.spent_time" /> <A-task-remaining-time :hours="subtask.duration - subtask.spent_time" />
<A-task-spent-time :hours="subtask.spent_time" :subtask="subtask" /> <A-task-spent-time :hours="subtask.spent_time" :subtask="subtask" />
+22 -55
View File
@@ -10,9 +10,10 @@
<v-card-actions class="d-flex flex-column modal-content"> <v-card-actions class="d-flex flex-column modal-content">
<v-text-field v-model="subtask.project_code" label="Kód projektu" :value="subtask.project_code" @focus="onInputFocus" class="modal-input" variant="solo" disabled></v-text-field> <v-text-field v-model="subtask.project_code" label="Kód projektu" :value="subtask.project_code" @focus="onInputFocus" class="modal-input" variant="solo" disabled></v-text-field>
<Z-autocomplete label="Master task" :options="mastertasks.map((mastertask) => ({ key: mastertask.id, value: mastertask.name }))" :validations="v$.subtask.master_task.id" :clear-on-submit="false" :value="subtask?.master_task?.name" :class="{ error: v$.subtask.master_task.id.$errors.length }" class="modal-input" @option-selected="onMasterTaskSelected" /> <v-autocomplete v-model="subtask.master_task_id" label="Master task" :class="{error: v$.subtask.master_task_id.$errors.length}" :items="mastertasks" item-title="name" item-value="id" variant="solo" class="modal-input" clearable />
<v-autocomplete v-model="subtask.employee_id" label="Priradený" :class="{error: v$.subtask.employee_id.$errors.length}" :items="enrichedEmployees" item-title="_fullName" item-value="id" variant="solo" class="modal-input" clearable />
<Z-autocomplete label="Priradený" :value="subtask?.employee ? `${subtask?.employee?.name} ${subtask?.employee?.surname}` : ''" :options="enrichedEmployees.map(employee => ({key: employee.id, value: employee._fullName}))" :clear-on-submit="false" :class="{ error: v$.subtask.employee.$errors.length }" class="modal-input" @option-selected="onEmployeeSelected" />
<v-text-field v-model="subtask.duration" @focus="onInputFocus" class="modal-input" variant="solo" type="number" label="Časový odhad" :class="{ error: v$.subtask.duration.$errors.length }" :hint="v$.subtask.duration.$errors ? errorUtils.parseErrors(v$.subtask.duration.$errors) : null" append-inner-icon="mdi-clock-outline"></v-text-field> <v-text-field v-model="subtask.duration" @focus="onInputFocus" class="modal-input" variant="solo" type="number" label="Časový odhad" :class="{ error: v$.subtask.duration.$errors.length }" :hint="v$.subtask.duration.$errors ? errorUtils.parseErrors(v$.subtask.duration.$errors) : null" append-inner-icon="mdi-clock-outline"></v-text-field>
<div class="w-100 modal-input"> <div class="w-100 modal-input">
@@ -28,8 +29,8 @@
<v-card v-else height="100%" class="d-flex flex-column scrolling"> <v-card v-else height="100%" class="d-flex flex-column scrolling">
<div class="d-flex justify-space-between align-center"> <div class="d-flex justify-space-between align-center">
<div> <div>
<p>{{ subtask.master_task?.project_code }}</p> <p>{{ subtask._master_task.project_code }}</p>
<h1>{{ subtask.master_task?.name }}</h1> <h1>{{ subtask._master_task.name }}</h1>
</div> </div>
<v-icon @click.stop="closeModal()" icon="mdi-close" size="large" class="pb-5"></v-icon> <v-icon @click.stop="closeModal()" icon="mdi-close" size="large" class="pb-5"></v-icon>
@@ -83,7 +84,7 @@ export default {
ZTaskReadonlyInfo, ZTaskReadonlyInfo,
ZFilesUpload, ZFilesUpload,
QuillEditor, QuillEditor,
ZAutocomplete // ZAutocomplete
}, },
data() { data() {
@@ -95,7 +96,6 @@ export default {
errorUtils, errorUtils,
v$: useVuelidate(), v$: useVuelidate(),
preventInputFocus: false, preventInputFocus: false,
mastertaskIdSelect: null,
showDatePicker: false, showDatePicker: false,
selectedNewDate: [] selectedNewDate: []
} }
@@ -105,24 +105,15 @@ export default {
...mapGetters('tm', [ ...mapGetters('tm', [
'mastertasks', 'mastertasks',
'enrichedEmployees' 'enrichedEmployees'
]), ])
filteredEmployees() {
if (!this.enrichedEmployees || !this.subtask?.master_task) return []
const selectedEmployees = this.subtask.master_task.employees?.map((employee) => employee.id) || []
return this.enrichedEmployees.filter((employee) => !selectedEmployees.includes(employee.id))
}
}, },
validations() { validations() {
return { return {
subtask: { subtask: {
master_task: { master_task_id: { required },
id: { required }
},
duration: { required }, duration: { required },
employee: { required } employee_id: { required }
} }
} }
}, },
@@ -153,7 +144,6 @@ export default {
this.subtask = this._enrichTask(data) this.subtask = this._enrichTask(data)
} else { } else {
this.subtask = { master_task: mastertask, timeblocks: [{start: tmUtils.formatDateTimeForBackend(moment()), end: tmUtils.formatDateTimeForBackend(moment())}] } this.subtask = { master_task: mastertask, timeblocks: [{start: tmUtils.formatDateTimeForBackend(moment()), end: tmUtils.formatDateTimeForBackend(moment())}] }
this.mastertaskIdSelect = mastertask.id
} }
this.editMode = editMode || !this.subtask.id this.editMode = editMode || !this.subtask.id
@@ -178,7 +168,7 @@ export default {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${this.subtask.id}`, this.subtask) const { subtask } = await this.$axios.put_auth(`v1/subtasks/${this.subtask.id}`, this.subtask)
this.$store.commit('tm/update_subtask', subtask) this.$store.commit('tm/update_subtask', subtask)
} else { } else {
const { subtask } = await this.$axios.post_auth(`v1/mastertasks/${this.subtask.master_task.id}/subtasks`, this.subtask) const { subtask } = await this.$axios.post_auth(`v1/mastertasks/${this.subtask.master_task_id}/subtasks`, this.subtask)
this.subtask.id = subtask.id this.subtask.id = subtask.id
this.$store.commit('tm/update_subtask', subtask) this.$store.commit('tm/update_subtask', subtask)
} }
@@ -190,17 +180,6 @@ export default {
} }
}, },
async addNewDate() {
if (this.selectedNewDate) {
this.subtask.timeblocks.push({
start: this.selectedNewDate[0],
end: this.selectedNewDate[1]
})
this.showDatePicker = false
this.selectedNewDate = []
}
},
async downloadFile(id) { async downloadFile(id) {
try { try {
const file = await this.$axios.get_auth_data(`v1/subtasks/${this.subtask.id}`, { responseType: "arraybuffer" }) const file = await this.$axios.get_auth_data(`v1/subtasks/${this.subtask.id}`, { responseType: "arraybuffer" })
@@ -210,10 +189,6 @@ export default {
} }
}, },
removeDate(index) {
this.subtask.timeblocks.splice(index, 1)
},
openDeleteTaskModal(subtask) { openDeleteTaskModal(subtask) {
this.$events.emit("show-delete-task-modal", { subtask }) this.$events.emit("show-delete-task-modal", { subtask })
}, },
@@ -226,35 +201,27 @@ export default {
this.subtask.files = files this.subtask.files = files
}, },
onEmployeeSelected({ key }) {
this.subtask.employee_id = key
},
_enrichTask(subtask) { _enrichTask(subtask) {
subtask.master_task = this.mastertasks.find((mastertask) => mastertask.id == subtask.master_task_id) return {
subtask.employee = this.enrichedEmployees.find((employee) => employee.id == subtask.employee_id) ...subtask,
subtask._created_by = this.enrichedEmployees.find((employee) => employee.id == subtask.created_by) _created_by: this.enrichedEmployees.find((employee) => employee.id == subtask.created_by),
this.mastertaskIdSelect = subtask.master_task?.id _employee: this.enrichedEmployees.find((employee) => employee.id == subtask.employee_id),
_master_task: this.mastertasks.find((mastertask) => mastertask.id == subtask.master_task_id)
return subtask }
} }
}, },
watch: { watch: {
mastertaskIdSelect(value) { 'subtask.employee_id'(value, oldValue) {
if (!value || !this.subtask) return if (!value || !oldValue) return
this.subtask.master_task = this.mastertasks.find((mastertask) => mastertask.id == value) this.subtask.timeblocks = []
this.subtask.project_code = this.subtask.master_task.project_code this.subtask._employee = this.enrichedEmployees.find((employee) => employee.id == this.subtask.employee_id)
}, },
"subtask.employee_id"(value, oldValue) { 'subtask.master_task_id'(value) {
if (!value) return if (!value) return
this.subtask._master_task = this.mastertasks.find((mastertask) => mastertask.id == this.subtask.master_task_id)
if (value && oldValue)
this.subtask.timeblocks = []
this.subtask.employee = this.enrichedEmployees.find((employee) => employee.id == value)
}, },
editMode() { editMode() {
+45 -76
View File
@@ -1,59 +1,33 @@
<template> <template>
<v-dialog v-model="visible" max-width="750" max-height="90%" height="700px"> <v-dialog v-model="visible" max-width="750" max-height="90%" height="700px">
<W-loadable :content="team && allEmployees" class="h-100"> <W-loadable :content="team" class="h-100">
<v-card height="100%" class="d-flex flex-column"> <v-card height="100%" class="d-flex flex-column">
<div class="d-flex justify-space-between align-center modal-header"> <div class="d-flex justify-space-between align-center modal-header">
<h1>{{ team.id ? 'Upraviť tím' : 'Vytvoriť tím' }}</h1> <h1>{{ team.id ? 'Upraviť tím' : 'Vytvoriť tím' }}</h1>
<v-icon <v-icon @click.stop="closeModal()" icon="mdi-close" size="large"></v-icon>
@click.stop="closeModal()"
icon="mdi-close"
size="large"
></v-icon>
</div> </div>
<v-card-actions class="d-flex flex-column modal-content"> <v-card-actions class="d-flex flex-column modal-content">
<v-text-field <v-text-field class="modal-input" label="Názov tímu" variant="solo"
class="modal-input" :class="{ error: v$.team.name.$errors.length }" :hint="
label="Názov tímu"
variant="solo"
:class="{ error: v$.team.name.$errors.length }"
:hint="
v$.team.name.$errors v$.team.name.$errors
? errorUtils.parseErrors(v$.team.name.$errors) ? errorUtils.parseErrors(v$.team.name.$errors)
: null : null
" " v-model="team.name"></v-text-field>
v-model="team.name"
></v-text-field>
<div <div class="relative w-100" v-for="(employee, index) in team.employees" :key="employee.id">
class="relative w-100" <v-text-field class="modal-input" disabled label="Zamestanec" variant="solo"
v-for="(employee, index) in team.employees" v-model="team.employees[index]._fullName">
:key="employee.id"
>
<v-text-field
class="modal-input"
disabled
label="Zamestanec"
variant="solo"
v-model="team.employees[index]._fullName"
>
</v-text-field> </v-text-field>
<v-icon <v-icon class="remove-input" @click.stop="team.employees.splice(index, 1)" icon="mdi-close"
class="remove-input" size="small"></v-icon>
@click.stop="team.employees.splice(index, 1)"
icon="mdi-close"
size="small"
></v-icon>
</div> </div>
<Z-autocomplete <v-autocomplete v-model="employeeSelect" label="Priradený"
label="Vyhľadať zamestnanca" :class="{ error: v$.team.employees.$errors.length }" :items="employeesOptions"
:options="employeesOptions" item-title="_fullName" item-value="id" variant="solo" class="modal-input" clearable
:validations="v$.team.employees" ref="autocomplete" />
class="modal-input"
@option-selected="employeeSelected"
/>
</v-card-actions> </v-card-actions>
<v-btn class="button primary block" @click="save()">{{ team.id ? 'UPRAVIŤ' : 'VYTVORIŤ' }}</v-btn> <v-btn class="button primary block" @click="save()">{{ team.id ? 'UPRAVIŤ' : 'VYTVORIŤ' }}</v-btn>
@@ -63,31 +37,41 @@
</template> </template>
<script> <script>
import ZAutocomplete from '@/plugins/tm@_components/z-autocomplete.vue' import useVuelidate from '@vuelidate/core'
import useVuelidate from '@vuelidate/core' import { required } from '@vuelidate/validators'
import { required } from '@vuelidate/validators' import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import errorUtils from '@/plugins/lib@auth/_utils/error.utils' import tmUtils from '@/plugins/tm@_utils/tm.utils'
import tmUtils from '@/plugins/tm@_utils/tm.utils' import { mapGetters } from 'vuex'
const atLeastOne = (value) => value.length const atLeastOne = (value) => value.length
export default { export default {
emits: ['close', 'update'], emits: ['close', 'update'],
components: {
ZAutocomplete,
},
data() { data() {
return { return {
visible: false, visible: false,
allEmployees: null,
v$: useVuelidate(), v$: useVuelidate(),
errorUtils, errorUtils,
employeeSelect: null,
team: null team: null
} }
}, },
computed: {
...mapGetters('tm', [
'enrichedEmployees'
]),
employeesOptions() {
const selectedEmployeesIds = this.team.employees.map(
(employee) => employee.id
)
return this.enrichedEmployees.filter(employee => !selectedEmployeesIds.includes(employee.id))
}
},
validations() { validations() {
return { return {
team: { team: {
@@ -98,20 +82,17 @@ export default {
}, },
methods: { methods: {
async showModal({ team = null}) { async showModal({ team = null }) {
this.visible = true this.visible = true
this.team = null this.team = null
this.$nextTick(() => this.v$.$reset()) this.$nextTick(() => this.v$.$reset())
try { try {
const allEmployees = await this.$axios.get_auth_data('v1/employees')
this.allEmployees = tmUtils.enrichEmployees(allEmployees)
if (team?.id) { if (team?.id) {
const t = await this.$axios.get_auth_data(`v1/teams/${team.id}`) const t = await this.$axios.get_auth_data(`v1/teams/${team.id}`)
this.team = this._enrichTeam(t) this.team = this._enrichTeam(t)
} else { } else {
this.team = {employees: []} this.team = { employees: [] }
} }
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
@@ -141,33 +122,21 @@ export default {
} }
}, },
employeeSelected(selectedOption) {
const employee = this.allEmployees.find((empl) => empl.id == selectedOption.key)
this.team.employees.push(employee)
},
_enrichTeam(team) { _enrichTeam(team) {
team.employees = tmUtils.enrichEmployees(team.employees) team.employees = tmUtils.enrichEmployees(team.employees)
return team return team
} }
}, },
computed: { watch: {
employeesOptions() { employeeSelect(employeeId) {
const selectedEmployeesIds = this.team.employees.map( if (!employeeId)
(employee) => employee.id return
)
const employees = this.allEmployees.filter( const employee = this.enrichedEmployees.find(employee => employee.id == employeeId)
(employee) => !selectedEmployeesIds.includes(employee.id) this.team.employees.push(employee)
) this.employeeSelect = null
this.$refs.autocomplete.search = ''
return employees.map((employee) => {
return {
key: employee.id,
value: `${employee.name} ${employee.surname}`,
}
})
} }
} }
} }
@@ -38,7 +38,7 @@ export default {
required: true required: true
} }
}, },
methods: { methods: {
editTask(subtask, editMode = false) { editTask(subtask, editMode = false) {
this.$events.emit('show-subtask-modal', {subtask, editMode}) this.$events.emit('show-subtask-modal', {subtask, editMode})