mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
Merge branch 'main' of bitbucket.org:studiotem/jandl-taskmanager-app
This commit is contained in:
commit
0260ebeb38
23 files changed
+267
-122
No files matched your search
@@ -13,6 +13,7 @@
|
||||
<Z-delete-blocker-modal ref="z-delete-blocker-modal" @on-blocker-deleted="onBlockerDeleted" />
|
||||
<Z-delete-master-task-modal ref="z-delete-master-task-modal" @on-mastertask-deleted="onMasterTaskDeleted" />
|
||||
<Z-complete-task-modal ref="z-complete-task-modal" @on-subtask-edited="onTaskEdited" />
|
||||
<Z-complete-master-task-modal ref="z-complete-master-task-modal" @master-task-edited="onMasterTaskEdited" />
|
||||
<Z-task-add-time-modal ref="z-task-add-time-modal" />
|
||||
<Z-blocker-modal ref="z-blocker-modal" />
|
||||
|
||||
@@ -28,6 +29,7 @@ import ZDeleteTaskModal from '@/plugins/tm@modal/task/z-delete-task-modal.vue'
|
||||
import ZTaskModal from '@/plugins/tm@modal/task/z-task-modal.vue'
|
||||
import ZDeleteBlockerModal from '@/plugins/tm@modal/blocker/z-delete-blocker-modal.vue'
|
||||
import ZCompleteTaskModal from '@/plugins/tm@modal/task/z-complete-task-modal.vue'
|
||||
import ZCompleteMasterTaskModal from '@/plugins/tm@modal/master-task/z-complete-master-task-modal.vue'
|
||||
import ZTaskAddTimeModal from '@/plugins/tm@modal/task/z-task-add-time-modal.vue'
|
||||
import ZBlockerModal from '@/plugins/tm@modal/blocker/z-blocker-modal.vue'
|
||||
import ZDeleteMasterTaskModal from '@/plugins/tm@modal/master-task/z-delete-master-task-modal.vue'
|
||||
@@ -48,7 +50,8 @@ export default {
|
||||
ZBlockerModal,
|
||||
ZDeleteMasterTaskModal,
|
||||
ZDeleteTeamMemberModal,
|
||||
ZShowMenuModal
|
||||
ZShowMenuModal,
|
||||
ZCompleteMasterTaskModal
|
||||
},
|
||||
|
||||
mounted() {
|
||||
@@ -100,6 +103,10 @@ export default {
|
||||
this.$refs['z-complete-task-modal'].showModal({ subtask })
|
||||
})
|
||||
|
||||
this.$events.on('show-complete-master-task-modal', ({ mastertask }) => {
|
||||
this.$refs['z-complete-master-task-modal'].showModal({ mastertask })
|
||||
})
|
||||
|
||||
this.$events.on('show-blocker-modal', ({blocker = {}}) => {
|
||||
this.$refs['z-blocker-modal'].showModal({blocker})
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
:class="{ active: isActive(page.pageName) }"
|
||||
@click="$router.push({ name: page.pageName }); closeMenu()"> {{ page.title }}
|
||||
<span
|
||||
v-if="page.pageName == 'Overview - Unscheduled tasks' && unscheduledSubtasks"
|
||||
v-if="page.pageName == 'Overview - Unscheduled tasks' && unscheduledSubtasks?.length >= 1"
|
||||
class="mb-4 text-center rounded-circle badge">{{ unscheduledSubtasks?.length }}</span>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
@@ -10,8 +10,8 @@ export default {
|
||||
const cleanedItem = normalizedItem.replace(pattern, '');
|
||||
const cleanedInput = normalizedInput.replace(pattern, '');
|
||||
|
||||
// Check if the cleaned item starts with the cleaned input
|
||||
return cleanedItem.toLowerCase().includes(cleanedInput.toLowerCase());
|
||||
// Check if the cleaned item starts with the cleaned input and return empty value for dropdown if present
|
||||
return cleanedItem.toLowerCase().includes(cleanedInput.toLowerCase()) || item === '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ export default {
|
||||
return moment(date).format('YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
|
||||
formatDate(date) {
|
||||
return moment(date).format('DD.MM.yyyy')
|
||||
},
|
||||
|
||||
isBefore(date, compareTo = null) {
|
||||
return moment(date).isBefore(moment(compareTo))
|
||||
|
||||
@@ -82,6 +82,11 @@
|
||||
</div>
|
||||
<small class="event-initials-date">{{ event.date?.toLocaleDateString('sk-SK') }}</small>
|
||||
</div>
|
||||
|
||||
<div v-if="event.subtask?.deadline" class="d-flex align-center">
|
||||
<v-icon icon="mdi-skull-outline" size="small" />
|
||||
<small class="ml-1" :class="`${tmUtils.isAfter(event.subtask?.deadline) ? 'text-black' : 'text-red-darken-4'}`">{{ tmUtils.formatDate(event.subtask?.deadline) }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="event-footer d-flex">
|
||||
@@ -144,7 +149,8 @@ export default {
|
||||
}
|
||||
},
|
||||
focusedEventId: null,
|
||||
timeCellHeight: 100
|
||||
timeCellHeight: 100,
|
||||
tmUtils
|
||||
}
|
||||
},
|
||||
|
||||
@@ -437,6 +443,7 @@ export default {
|
||||
|
||||
.custom-event-body
|
||||
padding: 20px 10px
|
||||
width: 100%
|
||||
|
||||
.event-content
|
||||
&.is-blocker
|
||||
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
margin-top: 5px
|
||||
display: -webkit-box
|
||||
-webkit-line-clamp: 3
|
||||
-webkit-box-orient: vertical
|
||||
-webkit-box-orient: vertical
|
||||
overflow: hidden
|
||||
&:after
|
||||
background: unset
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<v-row class="px-10 py-8 subheader">
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.projectId" class="subheader-input" :items="[...projectsOptions]" item-title="code" item-value="id" label="Projekt" variant="outlined" @update:search="projectCodeSearch = $event" />
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.projectId" class="subheader-input" no-data-text="reset" :items="[{id: null, code: ''}, ...projectsOptions]" item-title="code" item-value="id" label="Projekt" variant="outlined" @update:search="projectCodeSearch = $event" />
|
||||
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.teamId" class="subheader-input" :items="[...teams]" item-title="name" item-value="id" label="Tím" variant="outlined" />
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.teamId" class="subheader-input" :items="[{id: null, name: ''}, ...teams]" item-title="name" item-value="id" label="Tím" variant="outlined" />
|
||||
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.employeeId" class="subheader-input" :items="[...enrichedEmployees]" item-title="_fullName" item-value="id" label="Zamestnanec" variant="outlined" />
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.employeeId" class="subheader-input" :items="[{id: null, _fullName: ''}, ...enrichedEmployees]" item-title="_fullName" item-value="id" label="Zamestnanec" variant="outlined" />
|
||||
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.createdById" class="subheader-input" :items="[...enrichedEmployees]" label="Vytvoril" variant="outlined" item-title="_fullName" item-value="id" />
|
||||
<v-autocomplete :custom-filter="customFilter" :match-any="true" v-model="filter.createdById" class="subheader-input" :items="[{id: null, _fullName: ''}, ...enrichedEmployees]" label="Vytvoril" variant="outlined" item-title="_fullName" item-value="id" />
|
||||
|
||||
<v-select v-model="filter.month" class="subheader-input" :items="months" label="Mesiac" variant="outlined" />
|
||||
|
||||
@@ -106,6 +106,7 @@ export default {
|
||||
top: 0
|
||||
z-index: 9999
|
||||
background: white
|
||||
flex: none !important
|
||||
|
||||
.subheader-input
|
||||
max-width: 250px !important
|
||||
|
||||
@@ -33,7 +33,32 @@ export default {
|
||||
if (!this.mastertasks)
|
||||
return null
|
||||
|
||||
return this.mastertasks.map(event => {
|
||||
// return this.mastertasks.map(event => {
|
||||
|
||||
// const startOfDay = new Date(event.starts_at);
|
||||
// startOfDay.setHours(0, 0, 0, 0); // Set to 00:00:00.000
|
||||
|
||||
// const endOfDay = new Date(event.deadline);
|
||||
// endOfDay.setHours(23, 59, 59, 999); // Set to 23:59:59.999
|
||||
|
||||
// console.log({
|
||||
// start: startOfDay,
|
||||
// end: endOfDay,
|
||||
// })
|
||||
|
||||
// return {
|
||||
// id: event.id,
|
||||
// title: `${event.project_code} - ${event.name}`,
|
||||
// title_html: `${event.project_code} - <b>${event.name}</b>`,
|
||||
// start: startOfDay,
|
||||
// end: endOfDay,
|
||||
// is_finished: event.is_finished,
|
||||
// project_code: event.project_code,
|
||||
// backgroundColor: `${event.color}`,
|
||||
// borderColor: event.color
|
||||
// };
|
||||
// });
|
||||
return this.mastertasks.map(event => {
|
||||
return {
|
||||
id: event.id,
|
||||
title: `${event.project_code} - ${event.name}`,
|
||||
@@ -94,5 +119,5 @@ export default {
|
||||
|
||||
<style lang="sass" scoped>
|
||||
.non-projects
|
||||
height: 27rem !important
|
||||
height: 100vw !important
|
||||
</style>
|
||||
@@ -34,9 +34,7 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters('auth', [
|
||||
'user'
|
||||
]),
|
||||
...mapGetters('auth', ['user']),
|
||||
|
||||
splitDays() {
|
||||
const days = []
|
||||
@@ -87,7 +85,7 @@ export default {
|
||||
} else if (date.isSame(moment(), 'days')) {
|
||||
// Today
|
||||
const elapsedMinutes = now.diff(now.clone().startOf('day'), 'minutes')
|
||||
const workStart = this.user.work_start * 60 > elapsedMinutes ? this.user.work_start : elapsedMinutes
|
||||
const workStart = this.user.work_start * 60 > elapsedMinutes ? this.user.work_start : elapsedMinutes / 60
|
||||
const todayCapacityMinutes = (this.user.work_end - workStart) * 60
|
||||
unscheduledTime = todayCapacityMinutes - schedulesCapacityBySplit[i + 1] / 60 < 0 ? 0 : todayCapacityMinutes - schedulesCapacityBySplit[i + 1]
|
||||
} else {
|
||||
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
nonWorkingHours.push({
|
||||
title: "Non working hours",
|
||||
start: dayStart,
|
||||
end: this._setMinutesToDate(employee.work_start * 60),
|
||||
end: this._setMinutesToDate((employee.work_start + 1.3) * 60),
|
||||
split: index + 1,
|
||||
type: 'non-working-hours',
|
||||
draggable: false
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
|
||||
nonWorkingHours.push({
|
||||
title: "Non working hours",
|
||||
start: this._setMinutesToDate(employee.work_end * 60),
|
||||
start: this._setMinutesToDate((employee.work_end + 1.3) * 60),
|
||||
end: dayEnd,
|
||||
split: index + 1,
|
||||
type: 'non-working-hours',
|
||||
|
||||
@@ -62,7 +62,7 @@ export default {
|
||||
this.$events.off('on-blocker-deleted')
|
||||
},
|
||||
|
||||
methods: {
|
||||
methods: {
|
||||
async onFilterChanged(filter) {
|
||||
this.filter = filter
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<W-loadable :content="files" emptyMessage="Žiadne prílohy neboli nahraté">
|
||||
<div v-for="(file, index) in files" :key="file.id" class="d-flex align-center flex-wrap" :class="[index === 0 ? 'ml-0' : 'ml-2']">
|
||||
<v-chip class="mt-2">
|
||||
<p @click="handleDownload(index)" class="cursor-pointer">{{ file.name }}</p>
|
||||
<p @click="downloadFile(file)" class="cursor-pointer">{{ file.name }}</p>
|
||||
<v-icon class="mx-1" icon="mdi-paperclip"></v-icon>
|
||||
<p>{{ (file.file_size / (1024 * 1024)).toFixed(2) }} MB</p>
|
||||
</v-chip>
|
||||
<div v-if="showDownload" class="d-flex align-center">
|
||||
<div v-if="file.isLoading" class="d-flex align-center">
|
||||
<img class="gif-loader" :src="require('@/plugins/app/_img/spinner.gif')" alt="Loading...">
|
||||
<p class="ml-2">Súbor sa sťahuje, počkajte prosím</p>
|
||||
</div>
|
||||
@@ -18,28 +18,36 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as FileSaver from "file-saver"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
files: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
|
||||
downloadFile: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
|
||||
showDownload: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleDownload(index) {
|
||||
this.downloadFile(index)
|
||||
data() {
|
||||
return {
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async downloadFile(file) {
|
||||
file.isLoading = true // Show loader
|
||||
|
||||
try {
|
||||
const response = await fetch(file.path)
|
||||
const blob = await response.blob()
|
||||
FileSaver.saveAs(blob, file.name)
|
||||
} catch (error) {
|
||||
this.$toast.error(error)
|
||||
} finally {
|
||||
file.isLoading = false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
<Z-remaining-time :hours="mastertask.duration - mastertask.time_worked" />
|
||||
<Z-spent-time :hours="mastertask.time_worked" :is-exceeded="mastertask.time_worked > mastertask.duration" />
|
||||
<Z-description :description="mastertask.description" />
|
||||
<Z-files :files="mastertask.files" :download-file="downloadFile" :showDownload="showDownload" />
|
||||
<Z-files :files="mastertask.files" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as FileSaver from "file-saver"
|
||||
|
||||
import ZCreatedBy from '../../_components/read-only/z-created-by.vue'
|
||||
import ZDeadline from '../../_components/read-only/z-deadline.vue'
|
||||
import ZDescription from '../../_components/read-only/z-description.vue'
|
||||
@@ -41,31 +39,6 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showDownload: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async downloadFile(id) {
|
||||
this.showDownload = true // Show loader
|
||||
|
||||
const file = this.mastertask.files[id]
|
||||
|
||||
// Iterate over each file in the task
|
||||
const fileName = file.name
|
||||
|
||||
fetch(file.path)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
FileSaver.saveAs(blob, fileName)
|
||||
this.showDownload = false
|
||||
})
|
||||
.catch(error => this.$toast.error(error))
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<v-dialog v-model="visible" max-width="750" persistent>
|
||||
<v-card>
|
||||
<div class="d-flex justify-space-between align-center">
|
||||
<h1>Ste si istý, že chcete {{ mastertask.is_finished ? 'znovu otvoriť' : 'dokončiť' }} <b>{{ mastertask?.name }}</b>?</h1>
|
||||
<v-icon
|
||||
@click.stop="visible = false"
|
||||
icon="mdi-close"
|
||||
size="large"
|
||||
></v-icon>
|
||||
</div>
|
||||
<v-card-actions class="mt-10 d-flex justify-start align-center mobile-view">
|
||||
<v-btn class="button primary half" variant="flat" @click="complete()">ÁNO</v-btn>
|
||||
<v-btn class="button primary half outline" @click.stop="visible = false">ZRUŠIŤ</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
||||
|
||||
export default {
|
||||
mixins: [ modalMixin ],
|
||||
emits: ['close', 'on-master-task-edited'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
mastertask: null
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async showModal({ mastertask }) {
|
||||
this.mastertask = mastertask
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
async complete() {
|
||||
try {
|
||||
const tempMastertask = {...this.mastertask, is_finished: this.mastertask.is_finished ? 0 : 1, duration: this.mastertask.duration }
|
||||
const { mastertask } = await this.$axios.put_auth(`v1/mastertasks/${tempMastertask.id}`, tempMastertask)
|
||||
|
||||
this.visible = false
|
||||
this.$emit('on-master-task-edited', mastertask)
|
||||
} catch (error) {
|
||||
this.$toast.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
<div class="splitted-inputs">
|
||||
<!-- TODO: Bialko validacie -->
|
||||
<Datepicker v-model="mastertask.deadline" :auto-apply="true" @focus="onDateFocus" :min-date="new Date()" class="modal-input left" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum"
|
||||
<Datepicker v-model="mastertask.deadline" :auto-apply="true" @focus="onDateFocus" :min-date="new Date()" class="modal-input left" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum"
|
||||
:class="{ error: v$.mastertask.deadline.$errors?.length }" :validations="v$.mastertask.deadline" />
|
||||
|
||||
<v-text-field class="modal-input right" variant="solo" v-model="mastertask._deadline_time" label="Čas" append-inner-icon="mdi-clock-outline" type="time" @focus="onInputFocus"
|
||||
<v-text-field class="modal-input right" variant="solo" v-model="mastertask._deadline_time" label="Čas" append-inner-icon="mdi-clock-outline" type="time" @focus="onInputFocus"
|
||||
:class="{ error: v$.mastertask._deadline_time.$errors.length }" :hint="v$.mastertask._deadline_time.$errors ? errorUtils.parseErrors(v$.mastertask._deadline_time.$errors) : null" />
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<p class="employee-select-avatar d-flex justify-center align-center">
|
||||
{{ employee?.name.charAt(0) + employee?.surname.charAt(0) }}
|
||||
</p>
|
||||
|
||||
|
||||
<v-icon @click.stop="removeEmployee(employee)" class="remove-input" icon="mdi-close" size="small"></v-icon>
|
||||
</v-text-field>
|
||||
</template>
|
||||
@@ -70,7 +70,7 @@
|
||||
<A-mastertask-description :mastertask="mastertask" />
|
||||
|
||||
<div class="d-flex justify-start align-center mt-8">
|
||||
<v-btn class="button primary" @click="toggleIsFinished()">{{ mastertask.is_finished === 1 ? 'Otvoriť mastertask' : 'Ukončiť mastertask' }}</v-btn>
|
||||
<v-btn class="button primary" @click="toggleIsFinished(mastertask)">{{ mastertask.is_finished ? 'Otvoriť mastertask' : 'Ukončiť mastertask' }}</v-btn>
|
||||
</div>
|
||||
|
||||
<A-tasks-table :subtasks="mastertask.subtasks" />
|
||||
@@ -150,12 +150,12 @@ export default {
|
||||
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,
|
||||
...subtask,
|
||||
_initials: employee ? employee.name.charAt(0) + employee.surname.charAt(0) : '?',
|
||||
_fullName: employee ? `${employee.name} ${employee.surname}` : ''
|
||||
}
|
||||
@@ -167,7 +167,7 @@ export default {
|
||||
const quillEditorRequired = (value) => {
|
||||
if (!value || !value.length || !value.replace(/<[^>]*>/g, '').length)
|
||||
return false
|
||||
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -220,12 +220,12 @@ export default {
|
||||
|
||||
methods: {
|
||||
removeEmployee({ id }) {
|
||||
const isEmployeeInTeam = this.mastertask.team?.employees.find(employee => employee.id == id)
|
||||
// const isEmployeeInTeam = this.mastertask.team?.employees.find(employee => employee.id == id)
|
||||
|
||||
if (isEmployeeInTeam) {
|
||||
this.mastertask.team_id = null
|
||||
this.mastertask.team = null
|
||||
}
|
||||
// if (isEmployeeInTeam) {
|
||||
// this.mastertask.team_id = null
|
||||
// this.mastertask.team = null
|
||||
// }
|
||||
|
||||
this.mastertask.employees = this.mastertask.employees.filter(employee => employee.id != id)
|
||||
},
|
||||
@@ -264,9 +264,8 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
toggleIsFinished() {
|
||||
this.mastertask.is_finished = this.mastertask.is_finished === 0 ? 1 : 0
|
||||
this.save()
|
||||
async toggleIsFinished(mastertask) {
|
||||
this.$events.emit('show-complete-master-task-modal', { mastertask })
|
||||
},
|
||||
|
||||
async save() {
|
||||
|
||||
@@ -15,11 +15,6 @@
|
||||
{{ employee.name.charAt(0) + employee.surname.charAt(0) }}
|
||||
</p>
|
||||
<p class="ml-2" v-html="`${employee.name} ${employee.surname}`" />
|
||||
<!-- <v-tooltip text="Vymazať priradeného zamestnanca" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon v-bind="props" class="ml-2 cursor-pointer" icon="mdi-close" />
|
||||
</template>
|
||||
</v-tooltip> -->
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -32,6 +27,19 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// methods: {
|
||||
// async deleteEmployee() {
|
||||
// try {
|
||||
// const tempSubtask = {...this.subtask, employee_id: null, deadline: moment(this.subtask.deadline).format('YYYY-MM-DD HH:mm:ss') }
|
||||
// const { subtask } = await this.$axios.put_auth(`v1/subtasks/${tempSubtask.id}`, tempSubtask)
|
||||
// // employee_id
|
||||
// this.$emit('on-subtask-edited', subtask)
|
||||
// } catch (error) {
|
||||
// this.$toast.error(error)
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<v-row no-gutters class="align-center">
|
||||
<v-col cols="5">
|
||||
<v-sheet class="py-3">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon icon="mdi-calendar-week" size="large"></v-icon>
|
||||
<p class="mx-3">Deadline</p>
|
||||
</div>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
<v-col cols="7">
|
||||
<v-sheet class="py-3">
|
||||
<p v-if="deadline" :class="`${tmUtils.isAfter(deadline) ? 'text-black' : 'text-red-darken-4'}`">{{ tmUtils.formatDate(deadline) }}</p>
|
||||
<p v-else>Tento task nema určený deadline</p>
|
||||
</v-sheet>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tmUtils from '@/plugins/tm@_utils/tm.utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
deadline: {
|
||||
type: [String, Object, null],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tmUtils
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -181,7 +181,6 @@ export default {
|
||||
this.closeModal()
|
||||
this.$events.emit('on-subtask-edited', subtask)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.$toast.error(error.message || error)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<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 :isFinished="subtask.is_finished" />
|
||||
<A-task-deadline :deadline="subtask.deadline" />
|
||||
<A-task-timeblocks :timeblocks="subtask.timeblocks" :subtask="subtask" @update-subtask="$emit('update-subtask', $event)" />
|
||||
<Z-created-by :employee-id="subtask.created_by" />
|
||||
<A-task-assigned-employee :employee="subtask._employee" />
|
||||
@@ -8,12 +9,10 @@
|
||||
<A-task-remaining-time :hours="subtask.duration - subtask.spent_time" />
|
||||
<A-task-spent-time :hours="subtask.spent_time" :subtask="subtask" />
|
||||
<A-task-description :description="subtask.description" :mastertask-description="subtask.master_task_description" />
|
||||
<Z-files :files="subtask.files" :download-file="downloadFile" :showDownload="showDownload" />
|
||||
<Z-files :files="subtask.files" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as FileSaver from "file-saver"
|
||||
|
||||
import AMastertaskName from './_components/a-mastertask-name.vue'
|
||||
import AMastertaskStatus from './_components/a-mastertask-status.vue'
|
||||
import ATaskTimeblocks from './_components/a-task-timeblocks.vue'
|
||||
@@ -22,6 +21,7 @@ import ATaskAssignedEmployee from './_components/a-task-assigned-employee.vue
|
||||
import ATaskPlannedTime from './_components/a-task-planned-time.vue'
|
||||
import ATaskSpentTime from './_components/a-task-spent-time.vue'
|
||||
import ATaskRemainingTime from './_components/a-task-remaining-time.vue'
|
||||
import ATaskDeadline from './_components/a-task-deadline.vue'
|
||||
import ATaskDescription from './_components/a-task-description.vue'
|
||||
import ZFiles from '@/plugins/tm@modal/_components/read-only/z-files.vue'
|
||||
|
||||
@@ -38,7 +38,8 @@ export default {
|
||||
ATaskPlannedTime,
|
||||
ATaskSpentTime,
|
||||
ATaskRemainingTime,
|
||||
ATaskDescription
|
||||
ATaskDescription,
|
||||
ATaskDeadline
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -46,30 +47,6 @@ export default {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showDownload: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async downloadFile(id) {
|
||||
this.showDownload = true // Show loader
|
||||
|
||||
// Iterate over each file in the task
|
||||
const file = this.subtask.files[id]
|
||||
const fileName = file.name
|
||||
|
||||
fetch(file.path)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
FileSaver.saveAs(blob, fileName)
|
||||
this.showDownload = false
|
||||
})
|
||||
.catch(error => this.$toast.error(error))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -268,7 +268,6 @@ export default {
|
||||
|
||||
this.closeModal()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
this.$toast.error(error)
|
||||
} finally {
|
||||
this.isSaving = true
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
<v-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Kód projektu</th>
|
||||
<th @click="getSelectedTimeblockDeadline" class="text-left">Kód projektu</th>
|
||||
<th class="text-left">Názov</th>
|
||||
<th class="text-left">Deadline</th>
|
||||
<th class="text-left">Najbližší neuzavretý</th>
|
||||
<th class="text-left">Naplánované</th>
|
||||
<th class="text-left">Zostávajúce</th>
|
||||
<th class="text-left">Akcia</th>
|
||||
@@ -14,6 +16,13 @@
|
||||
<tr class="cursor-pointer" v-for="subtask in subtasks" :key="subtask.id" @click.stop="editTask(subtask, false)">
|
||||
<td>{{ subtask.project_code }}</td>
|
||||
<td>{{ subtask.title ?? subtask.master_task?.name ?? subtask.master_task }}</td>
|
||||
<td v-if="subtask.deadline">{{ getDeadline(subtask) }}</td>
|
||||
<v-tooltip text="Deadline z Mastertasku" location="end">
|
||||
<template v-slot:activator="{ props }">
|
||||
<td v-if="!subtask.deadline" v-bind="props">{{ getDeadline(subtask) }}*</td>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<td>{{ displayTimeblock(subtask) }}</td>
|
||||
<td>{{ subtask.duration }} h</td>
|
||||
<td>{{ (subtask.duration - subtask.spent_time).toFixed(2) }} h</td>
|
||||
<td class="task-action">
|
||||
@@ -31,6 +40,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
subtasks: {
|
||||
@@ -38,6 +50,10 @@ export default {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters('tm', ['mastertasks'])
|
||||
},
|
||||
|
||||
methods: {
|
||||
editTask(subtask, editMode = false) {
|
||||
@@ -46,6 +62,34 @@ export default {
|
||||
|
||||
deleteTask(subtask) {
|
||||
this.$events.emit('show-delete-task-modal', {subtask})
|
||||
},
|
||||
|
||||
getDeadline(subtask) {
|
||||
if(subtask.deadline) return moment(subtask.deadline).format('DD.MM.yyyy')
|
||||
else if(!this.mastertasks) return ''
|
||||
const masterTask = this.mastertasks.find((mt) => mt.id === subtask.master_task_id)
|
||||
return moment(masterTask).format('DD.MM.yyyy') ? moment(masterTask.deadline).format('DD.MM.yyyy') : ''
|
||||
},
|
||||
|
||||
getSelectedTimeblockDeadline(timeblocks) {
|
||||
const currentDate = moment()
|
||||
|
||||
const unfinishedTimeblocks = timeblocks.filter(tb => tb.is_finished === 0)
|
||||
if (unfinishedTimeblocks.length > 0) return moment(unfinishedTimeblocks[0].end).format('DD.MM.yyyy, HH:mm')
|
||||
|
||||
const upcomingTimeblocks = timeblocks
|
||||
.filter(tb => moment(tb.end).isAfter(currentDate))
|
||||
.sort((a, b) => moment(a.end).diff(moment(b.end)))
|
||||
|
||||
if (upcomingTimeblocks.length > 0 && upcomingTimeblocks.is_finished === 0) return moment(upcomingTimeblocks[0].end).format('DD.MM.yyyy, HH:mm')
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
displayTimeblock(subtask) {
|
||||
const timeblockDeadline = this.getSelectedTimeblockDeadline(subtask.timeblocks)
|
||||
|
||||
return timeblockDeadline !== '' ? timeblockDeadline : 'Žiadny termín'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
|
||||
<td>{{ subtask.project_code }}</td>
|
||||
<td>{{ subtask.title ?? subtask.master_task?.name ?? subtask.master_task }}</td>
|
||||
<td>{{ getDeadline(subtask) }}</td>
|
||||
<td v-if="subtask?.deadline">{{ getDeadline(subtask) }}</td>
|
||||
<v-tooltip text="Deadline z Mastertasku" location="end">
|
||||
<template v-slot:activator="{ props }">
|
||||
<td v-if="!subtask.deadline" v-bind="props">{{ getDeadline(subtask) }}*</td>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<td>{{ subtask.spent_time }} h</td>
|
||||
<td class="task-action">
|
||||
<div class="d-flex justify-start">
|
||||
|
||||
@@ -81,11 +81,13 @@ export class I18n {
|
||||
(response) => {
|
||||
return response
|
||||
},
|
||||
// fixnut aby logger nezaznamenaval chybu ak padne requet kvoli loggeru
|
||||
(error) => {
|
||||
axios.post('https://logger.wezeo.dev/cms/api/data/new', {
|
||||
data: JSON.stringify(error)
|
||||
})
|
||||
|
||||
if(error.response) {
|
||||
axios.post('https://logger.wezeo.dev/cms/api/data/new', {
|
||||
data: JSON.stringify(error)
|
||||
})
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user