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

This commit is contained in:
StudioTem committed 2023-02-17 17:02:38 +01:00
commit eddd9eaafd
15 files changed
+335 -307

No files matched your search

+7 -25
View File
@@ -8,10 +8,10 @@
<Z-team-modal ref="z-team-modal" @update="onTeamUpdate" /> <Z-team-modal ref="z-team-modal" @update="onTeamUpdate" />
<Z-delete-team-modal ref="z-delete-team-modal" @update="onTeamUpdate" /> <Z-delete-team-modal ref="z-delete-team-modal" @update="onTeamUpdate" />
<Z-task-modal ref="z-task-modal" @on-task-edited="onTaskEdited" /> <Z-task-modal ref="z-task-modal" @on-subtask-edited="onTaskEdited" />
<Z-delete-task-modal ref="z-delete-task-modal" @on-task-deleted="onTaskDeleted" /> <Z-delete-task-modal ref="z-delete-task-modal" @on-task-deleted="onTaskDeleted" />
<Z-delete-master-task-modal ref="z-delete-master-task-modal" @on-master-task-deleted="onMasterTaskDeleted" /> <Z-delete-master-task-modal ref="z-delete-master-task-modal" @on-master-task-deleted="onMasterTaskDeleted" />
<Z-complete-task-modal ref="z-complete-task-modal" @task-completed="onTaskCompleted" /> <Z-complete-task-modal ref="z-complete-task-modal" @on-subtask-edited="onTaskEdited" />
<Z-task-add-time-modal ref="z-task-add-time-modal" /> <Z-task-add-time-modal ref="z-task-add-time-modal" />
<Z-blocker-modal ref="z-blocker-modal" /> <Z-blocker-modal ref="z-blocker-modal" />
@@ -42,21 +42,7 @@ export default {
ZDeleteMasterTaskModal ZDeleteMasterTaskModal
}, },
data() {
return {
loader: null
}
},
async created() {
await this.$store.dispatch('tm/initialize')
this.loader?.hide()
},
mounted() { mounted() {
this.loader = this.$loading.show()
this.$events.on('show-delete-team-modal', ({team}) => { this.$events.on('show-delete-team-modal', ({team}) => {
this.$refs['z-delete-team-modal'].showModal(team) this.$refs['z-delete-team-modal'].showModal(team)
}) })
@@ -89,16 +75,16 @@ export default {
this.$refs['z-master-task-modal'].showModal(masterTask, editMode) this.$refs['z-master-task-modal'].showModal(masterTask, editMode)
}) })
this.$events.on('show-complete-task-modal', ({task}) => { this.$events.on('show-complete-task-modal', ({ subtask }) => {
this.$refs['z-complete-task-modal'].showModal(task) this.$refs['z-complete-task-modal'].showModal({ subtask })
}) })
this.$events.on('show-blocker-modal', ({blocker}) => { this.$events.on('show-blocker-modal', ({blocker}) => {
this.$refs['z-blocker-modal'].showModal(blocker) this.$refs['z-blocker-modal'].showModal(blocker)
}) })
this.$events.on('show-task-add-time-modal', ({task}) => { this.$events.on('show-task-add-time-modal', ({subtask}) => {
this.$refs['z-task-add-time-modal'].showModal(task) this.$refs['z-task-add-time-modal'].showModal({ subtask })
}) })
}, },
@@ -111,12 +97,8 @@ export default {
this.$events.emit('on-task-deleted', task) this.$events.emit('on-task-deleted', task)
}, },
onTaskCompleted(task) {
this.$events.emit('on-task-completed', task)
},
onTaskEdited(task) { onTaskEdited(task) {
this.$events.emit('on-task-edited', task) this.$events.emit('on-subtask-edited', task)
}, },
onMasterTaskDeleted(masterTask){ onMasterTaskDeleted(masterTask){
@@ -20,6 +20,24 @@ export default {
components: { components: {
ASidemenu, ASidemenu,
AHeader AHeader
},
data() {
return {
loader: null
}
},
async mounted() {
this.loader = this.$loading.show()
try {
await this.$store.dispatch('tm/initialize')
} catch (error) {
this.$toast.error(error)
} finally {
this.loader?.hide()
}
} }
} }
</script> </script>
+4 -1
View File
@@ -86,4 +86,7 @@ input[type="time"]::-webkit-calendar-picker-indicator
.v-toast, .v-toast,
.v-toast__item .v-toast__item
z-index: 10000 !important z-index: 10000 !important
.vld-overlay .vld-background
opacity: 1
+1 -1
View File
@@ -60,7 +60,7 @@ export default {
state.unscheduledSubtasks = state.unscheduledSubtasks.map(st => st.id == subtask.id ? subtask : st) state.unscheduledSubtasks = state.unscheduledSubtasks.map(st => st.id == subtask.id ? subtask : st)
state.ongoingSubtasks = state.ongoingSubtasks.map(st => st.id == subtask.id ? subtask : st) state.ongoingSubtasks = state.ongoingSubtasks.map(st => st.id == subtask.id ? subtask : st)
state.mastertasks = state.mastertasks.map(mastertask => { state.mastertasks = state.mastertasks.map(mastertask => {
mastertask.subtasks = mastertask.subtasks.map(st => st.id == subtask.id ? subtask : st) mastertask.subtasks = mastertask.subtasks.map(st => st.id == subtask.id ? subtask : st)
return mastertask return mastertask
}) })
}, },
@@ -37,9 +37,9 @@
<p class="day-name" >{{ split.date.toLocaleDateString('en-EN', { weekday: 'long' }) }}</p> <p class="day-name" >{{ split.date.toLocaleDateString('en-EN', { weekday: 'long' }) }}</p>
</span> </span>
<div v-if="split.unscheduledTime && split.initials" class="unscheduled-time d-flex align-center text-left" :class="{'vuecal__cell-split--weekend': split.isWeekend}"> <div class="unscheduled-time d-flex align-center text-left" :class="{'vuecal__cell-split--weekend': split.isWeekend}">
<div class="unscheduled-time-initials d-flex align-center justify-center me-4"> <div class="unscheduled-time-initials d-flex align-center justify-center me-4">
<p>{{split.initials}}</p> <p>{{ split.initials }}</p>
</div> </div>
<div> <div>
<p class="mb-0 mt-0 unscheduled-time-name">{{split.name}}</p> <p class="mb-0 mt-0 unscheduled-time-name">{{split.name}}</p>
@@ -57,21 +57,22 @@
</template> </template>
<template v-slot:event="{ event }"> <template v-slot:event="{ event }">
{{ event }}
<!-- <div v-if="!event.nonWorking" @click.stop="editTask(event.id)" class="w-100 h-100 d-flex flex-column justify-space-between" :class="{'short-event' : getEventLenght(event) <= 9000000, 'mini-event' : getEventLenght(event) <= 5400000, 'micro-event' : getEventLenght(event) <= 3600000}"> --> <!-- <div v-if="!event.nonWorking" @click.stop="editTask(event.id)" class="w-100 h-100 d-flex flex-column justify-space-between" :class="{'short-event' : getEventLenght(event) <= 9000000, 'mini-event' : getEventLenght(event) <= 5400000, 'micro-event' : getEventLenght(event) <= 3600000}"> -->
<div v-if="!event.nonWorking" class="w-100 h-100 d-flex flex-column justify-space-between" :class="{'short-event' : getEventLenght(event) <= 9000000, 'mini-event' : getEventLenght(event) <= 5400000, 'micro-event' : getEventLenght(event) <= 3600000}"> <div v-if="!event.nonWorking" class="w-100 h-100 d-flex flex-column justify-space-between" :class="{'short-event' : getEventLenght(event) <= 9000000, 'mini-event' : getEventLenght(event) <= 5400000, 'micro-event' : getEventLenght(event) <= 3600000}">
<div class="event-content"> <div class="event-content">
<small v-if="(event.project_code && !event.is_blocker)" class="project-code">{{ event.project_code }}</small> <small v-if="(event.subtask.project_code && !event.is_blocker)" class="project-code">{{ event.subtask.project_code }}</small>
<h3>{{ event.title }}</h3> <h3>{{ event.subtask.master_task?.name || event.subtask.master_task }}</h3>
<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">
<small>{{ getUserInitials(event) }}</small> <small>{{ getUserInitials(event) }}</small>
</div> </div>
<small class="event-initials-date">{{ new Date(event.starts_at).toLocaleDateString('sk-SK') }}</small> <small class="event-initials-date">{{ event.start.toLocaleDateString('sk-SK') }}</small>
</div> </div>
</div> </div>
<div class="event-footer d-flex"> <div class="event-footer d-flex">
<v-btn rounded class="event-footer-button me-2" :class="{'completed': event.completed}" @click.stop="clickCompleteTask(event)"><img :src="require('./_img/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('./_img/checkmark.svg')" alt=""></v-btn>
<v-btn rounded class="event-footer-button me-2" @click.stop="editTask(event, true)"><img :src="require('./_img/pencil.svg')" alt=""></v-btn> <v-btn rounded class="event-footer-button me-2" @click.stop="editTask(event, true)"><img :src="require('./_img/pencil.svg')" alt=""></v-btn>
<v-btn rounded class="event-footer-button me-2" @click.stop="deleteTask(event)"><img :src="require('./_img/bin.svg')" alt=""></v-btn> <v-btn rounded class="event-footer-button me-2" @click.stop="deleteTask(event)"><img :src="require('./_img/bin.svg')" alt=""></v-btn>
</div> </div>
@@ -89,6 +90,9 @@
</template> </template>
<script> <script>
import { addDays } from 'date-fns'
import tmUtils from '@/plugins/tm@_utils/tm.utils'
export default { export default {
props: { props: {
calData: { calData: {
@@ -126,7 +130,7 @@ export default {
} }
}, },
focusedEventId: null, focusedEventId: null,
timeCellHeight: 100, timeCellHeight: 100
} }
}, },
@@ -136,10 +140,27 @@ export default {
scrollContainer.scrollLeft = 0 scrollContainer.scrollLeft = 0
}, },
async eventMoved(e) { async eventMoved({ event, newSplit }) {
this.focusedEventId = e.event.id const date = addDays(this.calConfig.date, newSplit - 1)
this.addTimeModal(e.event.id) const start = date.setHours(Math.round(event.startTimeMinutes / 60), Math.round(event.startTimeMinutes % 60), 0)
this.$emit('eventMoved', e) const end = date.setHours(Math.round(event.endTimeMinutes / 60), Math.round(event.endTimeMinutes % 60), 0)
const editedSubtask = {
...event.subtask,
timeblocks: [...event.subtask.timeblocks, {
id: event._timeblock_id,
start: tmUtils.formatDateTimeForBackend(start),
end: tmUtils.formatDateTimeForBackend(end)
}]
}
try {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${editedSubtask.id}`, editedSubtask)
this.$events.emit('on-subtask-edited', subtask)
this.$events.emit('show-task-add-time-modal', { subtask })
} catch (error) {
this.$toast.error(error.message || error)
}
}, },
onReady() { onReady() {
@@ -190,32 +211,14 @@ export default {
// console.log(`Focused: ${event.task_id}`) // console.log(`Focused: ${event.task_id}`)
}, },
async clickCompleteTask(event) { completeTask({ subtask }) {
if (!event.completed) { this.$events.emit('show-complete-task-modal', { subtask })
this.completeTask(event)
} else {
event.completed = !event.completed
try {
await this.$axios.patch_auth(`v1/subtasks/${event.id}`, event)
this.$emit('reopenTask', event)
} catch (error) {
this.$toast.error(error)
}
}
},
completeTask(task) {
this.$events.emit('show-complete-task-modal', {task})
}, },
editTask(task, editMode) { editTask(task, editMode) {
this.$events.emit('show-task-modal', {task, editMode}) this.$events.emit('show-task-modal', {task, editMode})
}, },
addTimeModal(task) {
this.$events.emit('show-task-add-time-modal', {task})
},
deleteTask(task) { deleteTask(task) {
this.$events.emit('show-delete-task-modal', {task}) this.$events.emit('show-delete-task-modal', {task})
} }
@@ -3,7 +3,7 @@
<v-select <v-select
v-model="selectedEmployee" v-model="selectedEmployee"
class="subheader-input" class="subheader-input"
:items="[null, ...employees]" :items="enrichedEmployees"
item-title="_fullName" item-title="_fullName"
item-value="id" item-value="id"
label="Zamestnanec" label="Zamestnanec"
@@ -23,8 +23,9 @@
<v-text-field <v-text-field
class="subheader-input disabled" class="subheader-input disabled"
label="Nenaplánované hodiny" label="Nenaplánované hodiny"
v-model="time"
variant="outlined" variant="outlined"
:disabled="true"
v-model="time"
></v-text-field> ></v-text-field>
</v-row> </v-row>
</template> </template>
@@ -34,76 +35,74 @@ import { addDays, subDays } from 'date-fns'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
export default { export default {
emits: ['filter-changed'],
props: { props: {
unscheduledTime: { unscheduledTime: {
type: String, type: Number,
default() { default: () => 0
return '0'
}
} }
}, },
data() { data() {
return { return {
employees: [], date: new Date(),
selectedDate: "", selectedEmployee: null,
selectedDate: null,
selectedEmployee: '', time: null,
date: new Date() filter: {
start: null,
end: null,
employee: null
}
} }
}, },
computed: { computed: {
...mapGetters('auth', ['user']), ...mapGetters('auth', ['user']),
...mapGetters('tm', ['enrichedEmployees']),
time() {
return this.unscheduledTime
}
}, },
created() { mounted() {
this._getData() this.selectedEmployee = this.user.id
// this.time = this.unscheduledTime
this.$nextTick(() => this._updateFilters())
}, },
methods: { methods: {
async _getData() {
await this._getEmployees()
this._getWeek(false)
this.$nextTick(() => {
this.$emit('dataChanged', {firstDay: this.firstDay, employeeId: this.selectedEmployee})
})
},
async _getEmployees() {
this.employees = await this.$axios.get_auth_data('v1/employees')
this.employees.forEach(employee => {
employee._fullName = `${employee.name} ${employee.surname}`
if (employee.id == this.user.id) this.selectedEmployee = employee
})
},
_getWeek(emitDataChanged = true) {
this.firstDay = subDays(this.date, 1)
this.lastDay = addDays(this.date, 5)
this.selectedDate = String(this.firstDay.toLocaleDateString('sk-SK') + ' - ' + this.lastDay.toLocaleDateString('sk-SK'))
if (emitDataChanged) this.$emit('dataChanged', {firstDay: this.firstDay, employeeId: this.selectedEmployee})
},
prevWeek() { prevWeek() {
this.date = subDays(this.date, 7) this.date = subDays(this.date, 7)
this._getWeek() this._updateFilters()
}, },
nextWeek() { nextWeek() {
this.date = addDays(this.date, 7) this.date = addDays(this.date, 7)
this._getWeek() this._updateFilters()
},
_updateFilters() {
this.filter.start = subDays(this.date, 1)
this.filter.end = addDays(this.date, 5)
this.selectedDate = String(this.filter.start?.toLocaleDateString('sk-SK') + ' - ' + this.filter.end?.toLocaleDateString('sk-SK'))
this._emitFilterChanged()
},
_emitFilterChanged() {
if (!this.filter.start || !this.filter.end || !this.filter.employee) return
this.$emit('filter-changed', this.filter)
} }
}, },
watch: { watch: {
selectedEmployee() { selectedEmployee(id) {
this.$emit('dataChanged', {firstDay: this.firstDay, employeeId: this.selectedEmployee}) this.filter.employee = this.enrichedEmployees.find(employee => employee.id == id)
this._emitFilterChanged()
},
unscheduledTime() {
this.time = this.unscheduledTime
} }
} }
} }
@@ -0,0 +1,84 @@
import { setHours, setMinutes, addDays, differenceInCalendarDays } from 'date-fns'
import moment from 'moment'
export default {
generateTimelineTasks(subtasks, date, employee) {
return [
...this.generateTimelineEvents(subtasks, employee, date),
...this.generateTimelineNonWorkingHours(date, employee)
]
},
getUnscheduledTime(subtasks) {
let unscheduledTime = 0
subtasks.forEach(subtask => unscheduledTime += (subtask.duration - subtask.spent_time))
return unscheduledTime
},
isWeekend(date) {
return moment(date).day() % 6 == 0
},
generateTimelineEvents(subtasks, employee, date) {
const events = []
subtasks.forEach(subtask => {
subtask.timeblocks?.forEach((timeblock) => {
events.push(this._composeTimelineEvent(subtask, employee, date, timeblock))
})
})
return events
},
_composeTimelineEvent(subtask, employee, date, timeblock) {
const diffInDays = differenceInCalendarDays(date, new Date(timeblock.start))
return {
id: subtask.id,
start: addDays(new Date(timeblock.start), diffInDays),
end: addDays(new Date(timeblock.end), diffInDays),
master_task: subtask.master_task,
name: employee.name,
surname: employee.surname,
date: new Date(timeblock.start),
isWeekend: this.isWeekend(timeblock.start),
initials: employee.name.charAt(0).toUpperCase() + employee.surname.charAt(0).toUpperCase(),
split: this._getSplitFromDate(date, timeblock.start),
project_code: subtask.project_code,
subtask: subtask,
_timeblock_id: timeblock.id
}
},
generateTimelineNonWorkingHours(date, employee) {
const nonWorkingHours = []
for (let i = 0; i < 7; i++) {
nonWorkingHours.push({
title: "Non working hours",
start: setMinutes(setHours(date, 0), 59),
end: setMinutes(setHours(date, employee.work_start), 0),
nonWorking: true,
split: i + 1,
draggable: false
})
nonWorkingHours.push({
title: "Non working hours",
start: setMinutes(setHours(date, employee.work_end), 0),
end: setMinutes(setHours(date, 23), 59),
nonWorking: true,
split: i + 1,
draggable: false
})
}
return nonWorkingHours
},
_getSplitFromDate(date, start) {
return differenceInCalendarDays(new Date(date), new Date(start)) * (-1) + 1
}
}
+77 -146
View File
@@ -1,17 +1,17 @@
<template> <template>
<div> <div>
<A-task-filter :unscheduledTime="unscheduledTime" @dataChanged="getTimelineData" /> <A-task-filter v-if="$store.getters['tm/isInitialized']" :unscheduledTime="timelineData?.unscheduledTime || 0" @filter-changed="onFilterChanged" />
<Z-timeline v-if="timelineData" :calData="timelineData" :calConfig="config" :splits="splitDays" />
<W-loadable :content="timelineData" class="w-loadable" empty-message="Nepodarilo sa nacitat data!">
<Z-timeline :calData="timelineData" :calConfig="config" :splits="splitDays" @reopenTask="toggleCompleteTask" />
</W-loadable>
</div> </div>
</template> </template>
<script> <script>
import ATaskFilter from './_components/a-tasks-filter.vue' import ATaskFilter from './_components/a-tasks-filter.vue'
import ZTimeline from '@/plugins/tm@dashboard/_components/z-timeline.vue' import ZTimeline from '@/plugins/tm@dashboard/_components/z-timeline.vue'
import { addDays, setHours, setMinutes, setSeconds, differenceInCalendarDays } from 'date-fns' import { addDays, differenceInCalendarDays } from 'date-fns'
import { mapGetters } from 'vuex'
import moment from 'moment'
import timelineUtils from './_utils/timeline.utils'
export default { export default {
components: { components: {
@@ -21,9 +21,8 @@ export default {
data() { data() {
return { return {
timelineData: null, filter: null,
splitDays: [], subtasks: null,
unscheduledTime: 0,
config: { config: {
date: new Date(), date: new Date(),
scrollHour: 7, scrollHour: 7,
@@ -31,150 +30,82 @@ export default {
} }
}, },
computed: {
...mapGetters('tm', [
'ongoingSubtasks'
]),
splitDays() {
const days = []
for (let i = 0; i < 7; i++) {
days.push({
id: i + 1,
name: this.filter.employee.name + " " + this.filter.employee.surname,
initials: this.filter.employee.name.charAt(0).toUpperCase() + this.filter.employee.surname.charAt(0).toUpperCase(),
unscheduledTime: this.timelineData.unscheduledTime,
date: addDays(this.filter.start, i),
isWeekend: timelineUtils.isWeekend(addDays(this.filter.start, i))
})
}
return days
},
unscheduledTime() {
if (!this.subtasks) return 0
return timelineUtils.getUnscheduledTime(this.subtasks)
},
timelineData() {
if (!this.subtasks || !this.filter)
return null
return {
name: this.filter.employee.name,
surname: this.filter.employee.surname,
unscheduledTime: this.unscheduledTime,
tasks: timelineUtils.generateTimelineTasks(this.subtasks, new Date(this.filter.start), this.filter.employee)
}
}
},
mounted() { mounted() {
this.$events.on('on-task-deleted', (task) => {
this.deleteTask(task)
})
this.$events.on('on-task-completed', (task) => { this.$events.on('on-subtask-edited', (subtask) => {
this.toggleCompleteTask(task) this.subtasks = this.subtasks.map(st => {
}) if (st.id == subtask.id)
return subtask
this.$events.on('on-task-edited', (task) => {
this.editTask(task) return st
})
}) })
}, },
methods: { methods: {
async getTimelineData(data) { async onFilterChanged(filter) {
this.config.date = data.firstDay this.filter = filter
const lastDay = addDays(data.firstDay, 6)
const urlQueryObject = {}
if (filter.start) urlQueryObject.start = moment(filter.start).format('YYYY-MM-DD')
if (filter.end) urlQueryObject.end = moment(filter.end).format('YYYY-MM-DD')
if (filter.employee) urlQueryObject.employee_id = filter.employee.id
const queryParameters = new URLSearchParams(urlQueryObject).toString()
try { try {
this.timelineData = await this.$axios.get_auth_data('v1/overview/tasks', {firstDay: data.firstDay, lastDay: lastDay, employeeId: data.employeeId}) const { subtasks } = await this.$axios.get_auth(`v1/subtasks?${queryParameters}`)
this.formatTimelineDataToTimelineFormat(data.firstDay) this.config.scrollHour = this.filter.employee.work_start - 1
this.generateDays(data.firstDay) this.subtasks = subtasks
this.generateNonWorkingHours(data.firstDay)
setTimeout(() => console.log(this.timelineData), 1000)
} catch (error) { } catch (error) {
this.timelineData = {error: error || 'Nepodarilo sa nacitat data!'} this.$toast.error('Nepodarilo sa nacitat data!')
console.error(error)
// console.log(this.$store.getters['auth/user'])
} }
}, }
generateDays(date) {
this.splitDays = []
for (let i = 0; i < 7; i++) {
this.splitDays.push({
id: i + 1,
name: this.timelineData.name + " " + this.timelineData.surname,
initials: this.timelineData.name.charAt(0).toUpperCase() + this.timelineData.surname.charAt(0).toUpperCase(),
unscheduledTime: Math.floor(this.timelineData.unscheduled_times[i].time / 60) + "," + String(this.timelineData.unscheduled_times[i].time % 60)[0],
date: addDays(date, i),
isWeekend: this.isWeekend(addDays(date, i))
})
}
},
isWeekend(date) {
if (date.getDay() == 6 || date.getDay() == 0) return true
return false
},
formatTimelineDataToTimelineFormat(date) {
// format events
this.timelineData.tasks.forEach(event => {
event.name = this.timelineData.name
event.surname = this.timelineData.surname
// convert all dates to one to show in day-splits
const startDate = new Date(event.starts_at)
const endDate = new Date(event.ends_at)
const diffInDays = differenceInCalendarDays(date, startDate)
event.start = addDays(startDate, diffInDays)
event.end = addDays(endDate, diffInDays)
event.split = diffInDays * (-1) + 1
})
// console.log(this.timelineData.tasks)
this.unscheduledTime = 0
this.timelineData.unscheduled_times.forEach(time => {
time.name = this.timelineData.name
time.surname = this.timelineData.surname
this.unscheduledTime += time.time
})
this.unscheduledTime = Math.floor(this.unscheduledTime / 60) + "," + String(this.unscheduledTime % 60)[0] + " h"
this.config.scrollHour = Number(this.timelineData.work_hours_from.split(':')[0]) - 1
},
generateNonWorkingHours(date) {
const nonWorkingHours = []
const start1 = { hour: 0, minutes: 0, seconds: 0}
const end1 = { hour: Number(this.timelineData.work_hours_from.split(':')[0]), minutes: Number(this.timelineData.work_hours_from.split(':')[1]) - 1, seconds: 0 }
const start2 = { hour: Number(this.timelineData.work_hours_to.split(':')[0]), minutes: Number(this.timelineData.work_hours_to.split(':')[1]) + 1, seconds: 0 }
const end2 = { hour: 23, minutes: 59, seconds: 59}
for (let i = 0; i < 7; i++) {
nonWorkingHours.push({
title: "Non working hours",
start: setSeconds(setMinutes(setHours(date, start1.hour), start1.minutes), start1.seconds),
end: setSeconds(setMinutes(setHours(date, end1.hour), end1.minutes), end1.seconds),
nonWorking: true,
split: i + 1,
draggable: false
})
nonWorkingHours.push({
title: "Non working hours",
start: setSeconds(setMinutes(setHours(date, start2.hour), start2.minutes), start2.seconds),
end: setSeconds(setMinutes(setHours(date, end2.hour), end2.minutes), end2.seconds),
nonWorking: true,
split: i + 1,
draggable: false
})
}
this.timelineData.tasks.push(...nonWorkingHours)
},
async onEventMoved(event) {
// console.log("drag", event)
const task = this.timelineData.tasks.find(task => task.id == event.event.id)
try {
const newStartDate = addDays(event.event.start, differenceInCalendarDays(this.splitDays.filter((split) => {return split.id == event.newSplit})[0].date, event.event.start))
const newEndDate = addDays(event.event.end, differenceInCalendarDays( newStartDate, event.event.start))
// await this.$axios.post_auth_data(`v1/overview/tasks/${event.event.id}`, {start: newStartDate, end: newEndDate})
task.start = newStartDate
task.end = newEndDate
task.split = event.newSplit
} catch(error) {
this.$toast.error(error)
task.start = new Date(event.originalEvent.start)
task.end = new Date(event.originalEvent.end)
task.split = event.oldSplit
}
},
deleteTask(task) {
const id = task.id
this.timelineData.tasks.splice(this.timelineData.tasks.findIndex(item => item.id === id), 1)
},
toggleCompleteTask(task) {
const taskInTasks = this.timelineData.tasks.find(e => e.id == task.id)
taskInTasks.completed = !taskInTasks.completed
},
editTask(task) {
const taskIndex = this.timelineData.tasks.findIndex(item => item.id === task.id)
this.timelineData.tasks[taskIndex] = task
},
} }
} }
</script> </script>
@@ -146,7 +146,7 @@ export default {
this.masterTask.subtasks = this.masterTask.subtasks.filter((subtask) => subtask.id != task.id) this.masterTask.subtasks = this.masterTask.subtasks.filter((subtask) => subtask.id != task.id)
}) })
this.$events.on('on-task-edited', (task) => { this.$events.on('on-subtask-edited', (task) => {
if (!this.masterTask?.subtasks) return if (!this.masterTask?.subtasks) return
if (this.masterTask.subtasks.find(subtask => subtask.id == task.id)) { if (this.masterTask.subtasks.find(subtask => subtask.id == task.id)) {
@@ -160,7 +160,7 @@ export default {
beforeUnmount() { beforeUnmount() {
this.$events.off('on-master-task-deleted') this.$events.off('on-master-task-deleted')
this.$events.off('on-task-deleted') this.$events.off('on-task-deleted')
this.$events.off('on-task-edited') this.$events.off('on-subtask-edited')
}, },
methods: { methods: {
@@ -119,7 +119,7 @@ export default {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${task.id}`, task) const { subtask } = await this.$axios.put_auth(`v1/subtasks/${task.id}`, task)
this.closeModal() this.closeModal()
this.$events.emit('on-task-edited', subtask) this.$events.emit('on-subtask-edited', subtask)
} catch (error) { } catch (error) {
this.$toast.error(error.message || error) this.$toast.error(error.message || error)
} }
@@ -125,7 +125,7 @@ export default {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${task.id}`, task) const { subtask } = await this.$axios.put_auth(`v1/subtasks/${task.id}`, task)
this.closeModal() this.closeModal()
this.$events.emit('on-task-edited', subtask) this.$events.emit('on-subtask-edited', subtask)
} catch (error) { } catch (error) {
this.$toast.error(error.message || error) this.$toast.error(error.message || error)
} }
@@ -2,7 +2,7 @@
<v-dialog v-model="visible" max-width="750"> <v-dialog v-model="visible" max-width="750">
<v-card> <v-card>
<div class="d-flex justify-space-between align-center"> <div class="d-flex justify-space-between align-center">
<h1>Ste si istý, že chcete {{ this.task?.id ? 'ukončiť' : 'obnoviť' }} task, z masterTasku <b>{{ task.master_task }}</b>?</h1> <h1>Ste si istý, že chcete {{ this.subtask.is_finished ? 'dokončiť' : 'otvoriť' }} task, z masterTasku <b>{{ subtask?.master_task }}</b>?</h1>
<v-icon <v-icon
@click.stop="visible = false" @click.stop="visible = false"
icon="mdi-close" icon="mdi-close"
@@ -19,27 +19,30 @@
<script> <script>
export default { export default {
emits: ['close', 'task-completed'], emits: ['close', 'on-subtask-edited'],
data() { data() {
return { return {
visible: false, visible: false,
task: null subtask: null
} }
}, },
methods: { methods: {
async showModal(task) { async showModal({ subtask }) {
this.task = task this.subtask = subtask
this.visible = true this.visible = true
}, },
async complete() { async complete() {
try { try {
const task = {...this.task, is_finished: true} const tempSubtask = {...this.subtask, is_finished: !this.subtask.is_finished}
await this.$axios.patch_auth(`v1/subtasks/${this.task.id}`, task) const { subtask } = await this.$axios.put_auth(`v1/subtasks/${tempSubtask.id}`, tempSubtask)
this.visible = false this.visible = false
this.$emit('task-completed', task)
this.$store.commit('tm/update_subtask', subtask)
this.$emit('on-subtask-edited', subtask)
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
} }
@@ -1,73 +1,77 @@
<template> <template>
<v-dialog v-model="show" max-width="750"> <v-dialog max-width="750" v-model="visible">
<v-card> <v-card>
<div class="d-flex justify-space-between align-center"> <div class="d-flex justify-space-between align-center">
<h1>Prosím zadajte odpracovaný čas</h1> <h1>Pridať odpracovaný čas</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="mt-10 d-flex justify-start align-center"> <v-card-actions class="mt-10 d-flex justify-start align-center">
<v-text-field <v-text-field v-model="form.hours" variant="solo" bg-color="white" type="number" :hint="v$.form.hours.$errors ? errorUtils.parseErrors(v$.form.hours.$errors) : null" :class="{ error: v$.form.hours.$errors.length }" class="mb-2" label="Odpracovaný čas" />
class="modal-input"
label="Odpracovaný čas"
variant="solo"
type="number"
placeholder="h"
append-inner-icon="mdi-timer-outline"
></v-text-field>
</v-card-actions> </v-card-actions>
<v-btn class="button primary mt-5">Potvrdiť</v-btn> <v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
</v-card> </v-card>
</v-dialog> </v-dialog>
</template> </template>
<script> <script>
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import useVuelidate from '@vuelidate/core'
import { required, numeric, minValue } from '@vuelidate/validators'
export default { export default {
emits: ['close'], emits: ['close', 'on-subtask-edited'],
data() { data() {
return { return {
visible: false v$: useVuelidate(),
errorUtils,
visible: false,
subtask: null,
form: {
hours: null
}
} }
}, },
computed: { validations() {
show: { return {
get() { form: {
return this.visible hours: {required, numeric, minValue: minValue(1)}
},
set(value) {
if (!value) this.$emit('close')
} }
}, }
}, },
methods: { methods: {
showModal() { async showModal({ subtask }) {
// Kravarik: Dorobit logiku this.subtask = subtask
// 1. Save na be
// 2. this.$events.emit('task-time-updated', task)
this.visible = true this.visible = true
}, },
async submit() {
if (!await this.v$.$validate())
return
this.subtask.timesheets.push({
id: null,
employee_id: this.subtask.employee_id,
time: typeof this.form.hours == 'string' ? parseInt(this.form.hours) : this.form.hours
})
try {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${this.subtask.id}`, this.subtask)
this.closeModal()
this.$events.emit('on-subtask-edited', subtask)
} catch (error) {
this.$toast.error(error.message || error)
}
},
closeModal() { closeModal() {
this.visible = false this.visible = false
this.$emit('close', this.blocker) this.v$.$reset()
this.blocker = null }
},
} }
} }
</script> </script>
<style lang="sass" scoped>
.button primary
height: 48px !important
h1
font-size: 34px
</style>
@@ -125,7 +125,7 @@ export default {
}, },
mounted() { mounted() {
this.$events.on("on-task-edited", (task) => { this.$events.on("on-subtask-edited", (task) => {
if (task?.id == this.task?.id) if (task?.id == this.task?.id)
this.task = this._enrichTask(task) this.task = this._enrichTask(task)
@@ -179,7 +179,7 @@ export default {
this.$store.commit('tm/update_subtask', subtask) this.$store.commit('tm/update_subtask', subtask)
} }
this.$events.emit("on-task-edited", this.task) this.$events.emit("on-subtask-edited", this.task)
this.closeModal() this.closeModal()
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
@@ -12,8 +12,9 @@
<script> <script>
import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue' import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import AUnscheduledTable from './_components/a-unscheduled-table.vue' import AUnscheduledTable from './_components/a-unscheduled-table.vue'
export default { export default {
components: { components: {
ZTasksSearch, ZTasksSearch,