mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
refactor(task-modals): updated timeblocks to new local dat format
This commit is contained in:
1 parent
eaf83bf0cc
commit
0035251080
8 files changed
+1059
-597
No files matched your search
@@ -1,170 +1,244 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-row no-gutters>
|
<v-row no-gutters>
|
||||||
<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">
|
||||||
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
|
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
|
||||||
<p class="mx-3">Odpracovaný čas</p>
|
<p class="mx-3">Odpracovaný čas</p>
|
||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
<v-col cols="7">
|
<v-col cols="7">
|
||||||
<v-sheet class="py-3">
|
<v-sheet class="py-3">
|
||||||
<p>{{ hours }} h</p>
|
<p>{{ hours }} h</p>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
|
|
||||||
<v-sheet class="py-3">
|
<v-sheet class="py-3">
|
||||||
<div class="d-flex align-center mb-3">
|
<div class="d-flex align-center mb-3">
|
||||||
<v-icon @click="showModal = true" class="add-date cursor-pointer mr-2" icon="mdi-plus" size="medium" />
|
<v-icon
|
||||||
<p class="task-badge rounded-pill">Pridať záznam</p>
|
@click="showModal = true"
|
||||||
</div>
|
class="add-date cursor-pointer mr-2"
|
||||||
|
icon="mdi-plus"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
<p class="task-badge rounded-pill">Pridať záznam</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-if="subtask.timesheets.length">
|
<template v-if="subtask.timesheets.length">
|
||||||
<div v-for="timesheet in subtask.timesheets" :key="timesheet.id" class="d-flex align-center mb-3">
|
<div
|
||||||
<v-icon @click.stop="deleteTimesheet(timesheet)" class="circle-border cursor-pointer mr-2" icon="mdi-minus" size="medium" />
|
v-for="timesheet in subtask.timesheets"
|
||||||
|
:key="timesheet.id"
|
||||||
|
class="d-flex align-center mb-3"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
@click.stop="deleteTimesheet(timesheet)"
|
||||||
|
class="circle-border cursor-pointer mr-2"
|
||||||
|
icon="mdi-minus"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="d-flex align-center flex-start width-100">
|
<div class="d-flex align-center flex-start width-100">
|
||||||
<p class="task-badge rounded-pill">{{ timesheet.time }}</p>
|
<p class="task-badge rounded-pill">{{ timesheet.time }}</p>
|
||||||
<!-- <p class="ml-5">{{ timesheet.employee_name }} ({{ moment(timesheet.timestamp).format('D.M.YYYY') }})</p> -->
|
<!-- <p class="ml-5">{{ timesheet.employee_name }} ({{ moment(timesheet.timestamp).format('D.M.YYYY') }})</p> -->
|
||||||
<p class="ml-5">{{ timesheet.employee_name }} ({{ timesheet.timestamp }})</p>
|
<p class="ml-5">
|
||||||
</div>
|
{{ timesheet.employee_name }} ({{ timesheet.timestamp }})
|
||||||
</div>
|
</p>
|
||||||
</template>
|
</div>
|
||||||
</v-sheet>
|
</div>
|
||||||
</v-col>
|
</template>
|
||||||
</v-row>
|
</v-sheet>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
<v-dialog max-width="750" v-model="showModal" persistent>
|
<v-dialog max-width="750" v-model="showModal" persistent>
|
||||||
<v-card>
|
<v-card>
|
||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<h1>Pridať odpracovaný čas</h1>
|
<h1>Pridať odpracovaný čas</h1>
|
||||||
|
|
||||||
<v-icon @click.stop="closeModal()" icon="mdi-close" size="large"></v-icon>
|
<v-icon
|
||||||
</div>
|
@click.stop="closeModal()"
|
||||||
<v-card-actions class="mt-10 d-flex justify-start align-center">
|
icon="mdi-close"
|
||||||
<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" />
|
size="large"
|
||||||
</v-card-actions>
|
></v-icon>
|
||||||
<v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
|
</div>
|
||||||
</v-card>
|
<v-card-actions class="mt-10 d-flex justify-start align-center">
|
||||||
</v-dialog>
|
<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"
|
||||||
|
/>
|
||||||
|
</v-card-actions>
|
||||||
|
<v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment'
|
import moment from 'moment';
|
||||||
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
|
import errorUtils from '@/plugins/lib@auth/_utils/error.utils';
|
||||||
import useVuelidate from '@vuelidate/core'
|
import useVuelidate from '@vuelidate/core';
|
||||||
import { required, numeric, minValue } from '@vuelidate/validators'
|
import { required, numeric, minValue } from '@vuelidate/validators';
|
||||||
import tmUtils from '@/plugins/tm@_utils/tm.utils'
|
import tmUtils from '@/plugins/tm@_utils/tm.utils';
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex';
|
||||||
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [ modalMixin ],
|
mixins: [modalMixin],
|
||||||
props: {
|
props: {
|
||||||
hours: {
|
hours: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
subtask: {
|
subtask: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
moment,
|
moment,
|
||||||
v$: useVuelidate(),
|
v$: useVuelidate(),
|
||||||
errorUtils,
|
errorUtils,
|
||||||
showModal: false,
|
showModal: false,
|
||||||
form: {
|
form: {
|
||||||
hours: null
|
hours: null,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
validations() {
|
validations() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
hours: {required, numeric, minValue: minValue(0.1)}
|
hours: { required, numeric, minValue: minValue(0.1) },
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('auth', ['user'])
|
...mapGetters('auth', ['user']),
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async submit() {
|
async submit() {
|
||||||
if (!await this.v$.$validate())
|
if (!(await this.v$.$validate())) return;
|
||||||
return
|
|
||||||
|
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask))
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
|
|
||||||
subtask.timesheets.push({
|
subtask.timesheets.push({
|
||||||
id: null,
|
id: null,
|
||||||
employee_id: this.user.id,
|
employee_id: this.user.id,
|
||||||
time: typeof this.form.hours == 'string' ? parseFloat(this.form.hours) : this.form.hours
|
time:
|
||||||
})
|
typeof this.form.hours == 'string'
|
||||||
|
? parseFloat(this.form.hours)
|
||||||
|
: this.form.hours,
|
||||||
|
});
|
||||||
|
|
||||||
if(subtask.deadline) subtask.deadline = tmUtils.formatDateTimeForBackend(this._mergeDeadlineTimeDate())
|
if (subtask.deadline)
|
||||||
|
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
||||||
|
this._mergeDeadlineTimeDate(),
|
||||||
|
);
|
||||||
|
|
||||||
this._update(subtask)
|
this._update(subtask);
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteTimesheet({ id }) {
|
async deleteTimesheet({ id }) {
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask))
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
subtask.timesheets = subtask.timesheets.filter(timesheet => timesheet.id != id)
|
subtask.timesheets = subtask.timesheets.filter(
|
||||||
if(subtask.deadline) subtask.deadline = tmUtils.formatDateTimeForBackend(this._mergeDeadlineTimeDate())
|
(timesheet) => timesheet.id != id,
|
||||||
|
);
|
||||||
|
if (subtask.deadline)
|
||||||
|
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
||||||
|
this._mergeDeadlineTimeDate(),
|
||||||
|
);
|
||||||
|
|
||||||
this._update(subtask)
|
this._update(subtask);
|
||||||
},
|
},
|
||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.form = {
|
this.form = {
|
||||||
hours: null
|
hours: null,
|
||||||
}
|
};
|
||||||
|
|
||||||
this.showModal = false
|
this.showModal = false;
|
||||||
this.$nextTick(() => this.v$.$reset())
|
this.$nextTick(() => this.v$.$reset());
|
||||||
},
|
},
|
||||||
|
|
||||||
async _update(data) {
|
async _update(data) {
|
||||||
try {
|
try {
|
||||||
data.timesheets = data.timesheets.map((timesheet) => {
|
const timesheets = data.timesheets.map((timesheet) => {
|
||||||
delete timesheet.employee_name
|
delete timesheet.employee_name;
|
||||||
return timesheet
|
return timesheet;
|
||||||
})
|
});
|
||||||
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${data.id}`, data)
|
|
||||||
|
|
||||||
this.closeModal()
|
// Convert timeblocks from internal format to BE format
|
||||||
this.$events.emit('on-subtask-edited', subtask)
|
const timeblocks = (data.timeblocks || [])
|
||||||
} catch (error) {
|
.filter((timeblock) => {
|
||||||
this.$toast.error(error.message || error)
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
|
return timeblock.date || timeblock.start;
|
||||||
|
})
|
||||||
|
.map((timeblock) => {
|
||||||
|
// If already in BE format (has start field), return as is
|
||||||
|
if (timeblock.start && !timeblock.date) {
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: timeblock.start,
|
||||||
|
end: timeblock.end,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development')
|
// Convert from internal format to BE format
|
||||||
console.log('[a-task-spent-time.vue] Error: ', error)
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
}
|
return {
|
||||||
},
|
id: timeblock.id || null,
|
||||||
|
start: `${dateStr} ${timeblock.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${timeblock.ends_at_time}:00`,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
_mergeDeadlineTimeDate() {
|
const { subtask } = await this.$axios.put_auth(
|
||||||
const date = new Date(this.subtask.deadline)
|
`v1/subtasks/${data.id}`,
|
||||||
console.log('a-task-spent-time', this.subtask)
|
{ ...data, timesheets, timeblocks },
|
||||||
const timeParts = this.subtask._deadline_time.split(':')
|
);
|
||||||
|
|
||||||
date.setHours(parseInt(timeParts[0]))
|
this.closeModal();
|
||||||
date.setMinutes(parseInt(timeParts[1]))
|
this.$events.emit('on-subtask-edited', subtask);
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(error.message || error);
|
||||||
|
|
||||||
return date
|
if (process.env.NODE_ENV === 'development')
|
||||||
},
|
console.log('[a-task-spent-time.vue] Error: ', error);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
_mergeDeadlineTimeDate() {
|
||||||
|
const date = new Date(this.subtask.deadline);
|
||||||
|
console.log('a-task-spent-time', this.subtask);
|
||||||
|
const timeParts = this.subtask._deadline_time.split(':');
|
||||||
|
|
||||||
|
date.setHours(parseInt(timeParts[0]));
|
||||||
|
date.setMinutes(parseInt(timeParts[1]));
|
||||||
|
|
||||||
|
return date;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
.width-100
|
.width-100
|
||||||
width: 100%
|
width: 100%
|
||||||
</style>
|
</style>
|
||||||
@@ -41,10 +41,8 @@
|
|||||||
class="task-badge rounded-pill"
|
class="task-badge rounded-pill"
|
||||||
:class="{ 'strike-through': timeblock.is_finished }"
|
:class="{ 'strike-through': timeblock.is_finished }"
|
||||||
>
|
>
|
||||||
{{ tmUtils.formatTimeblockDateTime(timeblock) }} -
|
{{ formatTimeblockStart(timeblock) }} -
|
||||||
{{
|
{{ formatTimeblockEnd(timeblock) }}
|
||||||
timeblock.ends_at_time || tmUtils.formatDateTime(timeblock.end)
|
|
||||||
}}
|
|
||||||
</p>
|
</p>
|
||||||
<v-icon
|
<v-icon
|
||||||
v-if="!isOffice365Task"
|
v-if="!isOffice365Task"
|
||||||
@@ -212,20 +210,35 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
formatTimeblockStart(timeblock) {
|
||||||
|
// Handle internal format with date and time fields
|
||||||
|
if (timeblock.date && timeblock.starts_at_time) {
|
||||||
|
const dateStr = moment(timeblock.date).format('DD.MM.yyyy');
|
||||||
|
return `${dateStr}, ${timeblock.starts_at_time}`;
|
||||||
|
}
|
||||||
|
// Fallback to backend format
|
||||||
|
if (timeblock.start) {
|
||||||
|
return tmUtils.formatDateTime(timeblock.start);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
|
||||||
|
formatTimeblockEnd(timeblock) {
|
||||||
|
// Handle internal format with date and time fields
|
||||||
|
if (timeblock.date && timeblock.ends_at_time) {
|
||||||
|
return timeblock.ends_at_time;
|
||||||
|
}
|
||||||
|
// Fallback to backend format
|
||||||
|
if (timeblock.end) {
|
||||||
|
return tmUtils.formatDateTime(timeblock.end);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
if (!(await this.v$.$validate())) return;
|
if (!(await this.v$.$validate())) return;
|
||||||
|
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
const splittedStartsAtTime = this.form.starts_at_time.split(':');
|
|
||||||
const splittedEndsAtTime = this.form.ends_at_time.split(':');
|
|
||||||
const start = moment(this.form.date)
|
|
||||||
.hours(parseInt(splittedStartsAtTime[0]))
|
|
||||||
.minutes(parseInt(splittedStartsAtTime[1]))
|
|
||||||
.toISOString();
|
|
||||||
const end = moment(this.form.date)
|
|
||||||
.hours(parseInt(splittedEndsAtTime[0]))
|
|
||||||
.minutes(parseInt(splittedEndsAtTime[1]))
|
|
||||||
.toISOString();
|
|
||||||
|
|
||||||
if (!subtask?.timeblocks) {
|
if (!subtask?.timeblocks) {
|
||||||
subtask.timeblocks = [];
|
subtask.timeblocks = [];
|
||||||
@@ -237,11 +250,13 @@ export default {
|
|||||||
subtask.is_finished = 0;
|
subtask.is_finished = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add timeblock in internal format
|
||||||
subtask.timeblocks.push({
|
subtask.timeblocks.push({
|
||||||
end: tmUtils.formatDateTimeForBackend(end),
|
|
||||||
id: null,
|
id: null,
|
||||||
|
date: this.form.date,
|
||||||
|
starts_at_time: this.form.starts_at_time,
|
||||||
|
ends_at_time: this.form.ends_at_time,
|
||||||
is_finished: 0,
|
is_finished: 0,
|
||||||
start: tmUtils.formatDateTimeForBackend(start),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (subtask.deadline)
|
if (subtask.deadline)
|
||||||
@@ -256,6 +271,7 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
if (!subtask.id) {
|
if (!subtask.id) {
|
||||||
this.$emit('update-unfinished-subtask', subtask);
|
this.$emit('update-unfinished-subtask', subtask);
|
||||||
|
this.closeModal();
|
||||||
} else {
|
} else {
|
||||||
this._update(subtask);
|
this._update(subtask);
|
||||||
}
|
}
|
||||||
@@ -264,16 +280,6 @@ export default {
|
|||||||
|
|
||||||
async confirmSubmit() {
|
async confirmSubmit() {
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
const splittedStartsAtTime = this.form.starts_at_time.split(':');
|
|
||||||
const splittedEndsAtTime = this.form.ends_at_time.split(':');
|
|
||||||
const start = moment(this.form.date)
|
|
||||||
.hours(parseInt(splittedStartsAtTime[0]))
|
|
||||||
.minutes(parseInt(splittedStartsAtTime[1]))
|
|
||||||
.toISOString();
|
|
||||||
const end = moment(this.form.date)
|
|
||||||
.hours(parseInt(splittedEndsAtTime[0]))
|
|
||||||
.minutes(parseInt(splittedEndsAtTime[1]))
|
|
||||||
.toISOString();
|
|
||||||
|
|
||||||
if (!subtask?.timeblocks) {
|
if (!subtask?.timeblocks) {
|
||||||
subtask.timeblocks = [];
|
subtask.timeblocks = [];
|
||||||
@@ -285,11 +291,13 @@ export default {
|
|||||||
subtask.is_finished = 0;
|
subtask.is_finished = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add timeblock in internal format
|
||||||
subtask.timeblocks.push({
|
subtask.timeblocks.push({
|
||||||
end: tmUtils.formatDateTimeForBackend(end),
|
|
||||||
id: null,
|
id: null,
|
||||||
|
date: this.form.date,
|
||||||
|
starts_at_time: this.form.starts_at_time,
|
||||||
|
ends_at_time: this.form.ends_at_time,
|
||||||
is_finished: 0,
|
is_finished: 0,
|
||||||
start: tmUtils.formatDateTimeForBackend(start),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (subtask.deadline)
|
if (subtask.deadline)
|
||||||
@@ -309,20 +317,48 @@ export default {
|
|||||||
|
|
||||||
async toggleTimeblockIsFinished(originalTimeblock) {
|
async toggleTimeblockIsFinished(originalTimeblock) {
|
||||||
// TODO: Pridat odpracovany cas modal?
|
// TODO: Pridat odpracovany cas modal?
|
||||||
console.log('toggleTimeblockIsFinished 5', this.subtask);
|
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
const timeblock = JSON.parse(JSON.stringify(originalTimeblock));
|
const timeblock = JSON.parse(JSON.stringify(originalTimeblock));
|
||||||
|
|
||||||
timeblock.is_finished = timeblock.is_finished == 1 ? 0 : 1;
|
timeblock.is_finished = timeblock.is_finished == 1 ? 0 : 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Convert timeblock to backend format before sending
|
||||||
|
const timeblockForBackend = {
|
||||||
|
id: timeblock.id,
|
||||||
|
is_finished: timeblock.is_finished,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If timeblock is in internal format, convert it
|
||||||
|
if (timeblock.date) {
|
||||||
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
|
timeblockForBackend.start = `${dateStr} ${timeblock.starts_at_time}:00`;
|
||||||
|
timeblockForBackend.end = `${dateStr} ${timeblock.ends_at_time}:00`;
|
||||||
|
} else {
|
||||||
|
// Already in backend format
|
||||||
|
timeblockForBackend.start = timeblock.start;
|
||||||
|
timeblockForBackend.end = timeblock.end;
|
||||||
|
}
|
||||||
|
|
||||||
const { data: newTimeblock } = await this.$axios.put_auth(
|
const { data: newTimeblock } = await this.$axios.put_auth(
|
||||||
`v1/timeblocks/${timeblock.id}`,
|
`v1/timeblocks/${timeblock.id}`,
|
||||||
timeblock,
|
timeblockForBackend,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Convert returned timeblock from backend format to internal format
|
||||||
|
const startMoment = moment(newTimeblock.start, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
const endMoment = moment(newTimeblock.end, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
const convertedTimeblock = {
|
||||||
|
id: newTimeblock.id,
|
||||||
|
date: startMoment.toDate(),
|
||||||
|
starts_at_time: startMoment.format('HH:mm'),
|
||||||
|
ends_at_time: endMoment.format('HH:mm'),
|
||||||
|
is_finished: newTimeblock.is_finished,
|
||||||
|
};
|
||||||
|
|
||||||
subtask.timeblocks = subtask.timeblocks.map((originalTimeblock) => {
|
subtask.timeblocks = subtask.timeblocks.map((originalTimeblock) => {
|
||||||
if (originalTimeblock.id == newTimeblock.id) return newTimeblock;
|
if (originalTimeblock.id == convertedTimeblock.id)
|
||||||
|
return convertedTimeblock;
|
||||||
|
|
||||||
return originalTimeblock;
|
return originalTimeblock;
|
||||||
});
|
});
|
||||||
@@ -365,9 +401,36 @@ export default {
|
|||||||
return timesheet;
|
return timesheet;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Convert timeblocks from internal format to BE format
|
||||||
|
const timeblocks = (data.timeblocks || [])
|
||||||
|
.filter((timeblock) => {
|
||||||
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
|
return timeblock.date || timeblock.start;
|
||||||
|
})
|
||||||
|
.map((timeblock) => {
|
||||||
|
// If already in BE format (has start field), return as is
|
||||||
|
if (timeblock.start && !timeblock.date) {
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: timeblock.start,
|
||||||
|
end: timeblock.end,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from internal format to BE format
|
||||||
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: `${dateStr} ${timeblock.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${timeblock.ends_at_time}:00`,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const { subtask } = await this.$axios.put_auth(
|
const { subtask } = await this.$axios.put_auth(
|
||||||
`v1/subtasks/${data.id}`,
|
`v1/subtasks/${data.id}`,
|
||||||
{ ...data, timesheets },
|
{ ...data, timesheets, timeblocks },
|
||||||
);
|
);
|
||||||
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
|
|||||||
@@ -1,63 +1,114 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog v-model="visible" max-width="750" persistent>
|
<v-dialog v-model="visible" max-width="750" persistent>
|
||||||
<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 {{ subtask.is_finished ? 'znovu otvoriť' : 'dokončiť' }} sub task, z mastertasku <b>{{ subtask?.master_task?.name || subtask.master_task }}</b>?</h1>
|
<h1>
|
||||||
<v-icon
|
Ste si istý, že chcete
|
||||||
@click.stop="closeModal()"
|
{{ subtask.is_finished ? 'znovu otvoriť' : 'dokončiť' }} sub task, z
|
||||||
icon="mdi-close"
|
mastertasku
|
||||||
size="large"
|
<b>{{ subtask?.master_task?.name || subtask.master_task }}</b
|
||||||
></v-icon>
|
>?
|
||||||
</div>
|
</h1>
|
||||||
<v-card-actions class="mt-10 d-flex justify-start align-center mobile-view">
|
<v-icon
|
||||||
<v-btn class="button primary half" variant="flat" @click="complete()">ÁNO</v-btn>
|
@click.stop="closeModal()"
|
||||||
<v-btn class="button primary half outline" @click.stop="visible = false">ZRUŠIŤ</v-btn>
|
icon="mdi-close"
|
||||||
</v-card-actions>
|
size="large"
|
||||||
</v-card>
|
></v-icon>
|
||||||
</v-dialog>
|
</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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [ modalMixin ],
|
mixins: [modalMixin],
|
||||||
emits: ['close', 'on-subtask-edited'],
|
emits: ['close', 'on-subtask-edited'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
subtask: null
|
subtask: null,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async showModal({ subtask }) {
|
async showModal({ subtask }) {
|
||||||
this.subtask = subtask
|
this.subtask = subtask;
|
||||||
this.visible = true
|
this.visible = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
async complete() {
|
async complete() {
|
||||||
try {
|
try {
|
||||||
const tempSubtask = {...this.subtask, is_finished: this.subtask.is_finished ? 0 : 1, timesheets: this.subtask.timesheets.map((timesheet) => {
|
const timesheets = this.subtask.timesheets.map((timesheet) => {
|
||||||
delete timesheet.employee_name
|
delete timesheet.employee_name;
|
||||||
return timesheet
|
return timesheet;
|
||||||
})}
|
});
|
||||||
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${tempSubtask.id}`, tempSubtask)
|
|
||||||
|
|
||||||
this.visible = false
|
// Convert timeblocks from internal format to BE format
|
||||||
this.$emit('on-subtask-edited', subtask)
|
const timeblocks = (this.subtask.timeblocks || [])
|
||||||
} catch (error) {
|
.filter((timeblock) => {
|
||||||
this.$toast.error(this.$parseError(error))
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
|
return timeblock.date || timeblock.start;
|
||||||
|
})
|
||||||
|
.map((timeblock) => {
|
||||||
|
// If already in BE format (has start field), return as is
|
||||||
|
if (timeblock.start && !timeblock.date) {
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: timeblock.start,
|
||||||
|
end: timeblock.end,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development')
|
// Convert from internal format to BE format
|
||||||
console.log('[z-complete-task-modal.vue] Error: ', error)
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
}
|
return {
|
||||||
},
|
id: timeblock.id || null,
|
||||||
|
start: `${dateStr} ${timeblock.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${timeblock.ends_at_time}:00`,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
closeModal() {
|
const tempSubtask = {
|
||||||
this.visible = false
|
...this.subtask,
|
||||||
}
|
is_finished: this.subtask.is_finished ? 0 : 1,
|
||||||
}
|
timesheets,
|
||||||
}
|
timeblocks,
|
||||||
</script>
|
};
|
||||||
|
|
||||||
|
const { subtask } = await this.$axios.put_auth(
|
||||||
|
`v1/subtasks/${tempSubtask.id}`,
|
||||||
|
tempSubtask,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.visible = false;
|
||||||
|
this.$emit('on-subtask-edited', subtask);
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(this.$parseError(error));
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development')
|
||||||
|
console.log('[z-complete-task-modal.vue] Error: ', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,86 +1,139 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog v-model="visible" max-width="500" persistent>
|
<v-dialog v-model="visible" max-width="500" persistent>
|
||||||
<v-card>
|
<v-card>
|
||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<h4 class="mt-3">Vyberte časový úsek, ktorý chcete odstrániť</h4>
|
<h4 class="mt-3">Vyberte časový úsek, ktorý chcete odstrániť</h4>
|
||||||
|
|
||||||
<v-icon
|
<v-icon
|
||||||
@click.stop="closeModal()"
|
@click.stop="closeModal()"
|
||||||
icon="mdi-close"
|
icon="mdi-close"
|
||||||
size="large"
|
size="large"
|
||||||
></v-icon>
|
></v-icon>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(timeblock, index) in subtask.timeblocks" :key="timeblock.id" class="d-flex align-center justify-center mb-3 mt-3">
|
<div
|
||||||
<p class="task-badge rounded-pill" :class="{'strike-through': timeblock.is_finished}">{{ tmUtils.formatDateTime(timeblock.start) }} - {{ tmUtils.formatDateTime(timeblock.end) }}</p>
|
v-for="(timeblock, index) in subtask.timeblocks"
|
||||||
<v-icon @click.stop="subtask.id ? deleteTimeblock(timeblock) : deleteTimeblockWithoutId(index)" class="close-icon circle-border cursor-pointer ml-3 text-red" icon="mdi-close" size="medium" />
|
:key="timeblock.id"
|
||||||
</div>
|
class="d-flex align-center justify-center mb-3 mt-3"
|
||||||
</v-card>
|
>
|
||||||
</v-dialog>
|
<p
|
||||||
|
class="task-badge rounded-pill"
|
||||||
|
:class="{ 'strike-through': timeblock.is_finished }"
|
||||||
|
>
|
||||||
|
{{ tmUtils.formatDateTime(timeblock.start) }} -
|
||||||
|
{{ tmUtils.formatDateTime(timeblock.end) }}
|
||||||
|
</p>
|
||||||
|
<v-icon
|
||||||
|
@click.stop="
|
||||||
|
subtask.id
|
||||||
|
? deleteTimeblock(timeblock)
|
||||||
|
: deleteTimeblockWithoutId(index)
|
||||||
|
"
|
||||||
|
class="close-icon circle-border cursor-pointer ml-3 text-red"
|
||||||
|
icon="mdi-close"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin';
|
||||||
import tmUtils from '@/plugins/tm@_utils/tm.utils'
|
import tmUtils from '@/plugins/tm@_utils/tm.utils';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [ modalMixin ],
|
mixins: [modalMixin],
|
||||||
emits: ['on-subtask-edited'],
|
emits: ['on-subtask-edited'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
subtask: null,
|
subtask: null,
|
||||||
isDeleting: false,
|
isDeleting: false,
|
||||||
tmUtils
|
tmUtils,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async showModal({ subtask }) {
|
async showModal({ subtask }) {
|
||||||
this.subtask = subtask
|
this.subtask = subtask;
|
||||||
this.visible = true
|
this.visible = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.visible = false
|
this.visible = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteTimeblock({ id }) {
|
async deleteTimeblock({ id }) {
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask))
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
subtask.timeblocks = subtask.timeblocks.filter(timeblock => timeblock.id != id)
|
subtask.timeblocks = subtask.timeblocks.filter(
|
||||||
if(subtask.deadline) subtask.deadline = tmUtils.formatDateTimeForBackend(subtask.deadline)
|
(timeblock) => timeblock.id != id,
|
||||||
|
);
|
||||||
|
if (subtask.deadline)
|
||||||
|
subtask.deadline = tmUtils.formatDateTimeForBackend(subtask.deadline);
|
||||||
|
|
||||||
this._update(subtask)
|
this._update(subtask);
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTimeblockWithoutId(i) {
|
deleteTimeblockWithoutId(i) {
|
||||||
this.$emit('delete-timeblock')
|
this.$emit('delete-timeblock');
|
||||||
},
|
},
|
||||||
|
|
||||||
async _update(data) {
|
async _update(data) {
|
||||||
try {
|
try {
|
||||||
const timesheets = data.timesheets.map((timesheet) => {
|
const timesheets = data.timesheets.map((timesheet) => {
|
||||||
delete timesheet.employee_name
|
delete timesheet.employee_name;
|
||||||
return timesheet
|
return timesheet;
|
||||||
})
|
});
|
||||||
|
|
||||||
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${data.id}`, {...data, timesheets})
|
// Convert timeblocks from internal format to BE format
|
||||||
|
const timeblocks = (data.timeblocks || [])
|
||||||
|
.filter((timeblock) => {
|
||||||
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
|
return timeblock.date || timeblock.start;
|
||||||
|
})
|
||||||
|
.map((timeblock) => {
|
||||||
|
// If already in BE format (has start field), return as is
|
||||||
|
if (timeblock.start && !timeblock.date) {
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: timeblock.start,
|
||||||
|
end: timeblock.end,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.closeModal()
|
// Convert from internal format to BE format
|
||||||
this.$events.emit('on-subtask-edited', subtask)
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
} catch (error) {
|
return {
|
||||||
this.$toast.error(error.message || error)
|
id: timeblock.id || null,
|
||||||
|
start: `${dateStr} ${timeblock.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${timeblock.ends_at_time}:00`,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development')
|
const { subtask } = await this.$axios.put_auth(
|
||||||
console.log('[z-delete-timeblock-modal.vue] Error: ', error)
|
`v1/subtasks/${data.id}`,
|
||||||
}
|
{ ...data, timesheets, timeblocks },
|
||||||
},
|
);
|
||||||
}
|
|
||||||
}
|
this.closeModal();
|
||||||
|
this.$events.emit('on-subtask-edited', subtask);
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(error.message || error);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development')
|
||||||
|
console.log('[z-delete-timeblock-modal.vue] Error: ', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
.close-icon:hover
|
.close-icon:hover
|
||||||
background: red
|
background: red
|
||||||
color: #fff !important
|
color: #fff !important
|
||||||
</style>
|
</style>
|
||||||
@@ -1,95 +1,151 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog max-width="750" v-model="visible" persistent>
|
<v-dialog max-width="750" v-model="visible" persistent>
|
||||||
<v-card>
|
<v-card>
|
||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<h1>Pridať odpracovaný čas</h1>
|
<h1>Pridať odpracovaný čas</h1>
|
||||||
|
|
||||||
<v-icon @click.stop="closeModal()" icon="mdi-close" size="large"></v-icon>
|
<v-icon
|
||||||
</div>
|
@click.stop="closeModal()"
|
||||||
<v-card-actions class="mt-10 d-flex justify-start align-center">
|
icon="mdi-close"
|
||||||
<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" />
|
size="large"
|
||||||
</v-card-actions>
|
></v-icon>
|
||||||
<v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
|
</div>
|
||||||
</v-card>
|
<v-card-actions class="mt-10 d-flex justify-start align-center">
|
||||||
</v-dialog>
|
<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"
|
||||||
|
/>
|
||||||
|
</v-card-actions>
|
||||||
|
<v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
|
import errorUtils from '@/plugins/lib@auth/_utils/error.utils';
|
||||||
import useVuelidate from '@vuelidate/core'
|
import useVuelidate from '@vuelidate/core';
|
||||||
import { required, numeric, minValue } from '@vuelidate/validators'
|
import { required, numeric, minValue } from '@vuelidate/validators';
|
||||||
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin';
|
||||||
import tmUtils from '@/plugins/tm@_utils/tm.utils'
|
import tmUtils from '@/plugins/tm@_utils/tm.utils';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [ modalMixin ],
|
mixins: [modalMixin],
|
||||||
emits: ['close', 'on-subtask-edited'],
|
emits: ['close', 'on-subtask-edited'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
v$: useVuelidate(),
|
v$: useVuelidate(),
|
||||||
errorUtils,
|
errorUtils,
|
||||||
visible: false,
|
visible: false,
|
||||||
subtask: null,
|
subtask: null,
|
||||||
form: {
|
form: {
|
||||||
hours: null
|
hours: null,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
validations() {
|
validations() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
hours: {required, numeric, minValue: minValue(0.1)}
|
hours: { required, numeric, minValue: minValue(0.1) },
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async showModal({ subtask }) {
|
async showModal({ subtask }) {
|
||||||
this.subtask = subtask
|
this.subtask = subtask;
|
||||||
this.visible = true
|
this.visible = true;
|
||||||
this.form.hours = null
|
this.form.hours = null;
|
||||||
this.$nextTick(() => this.v$.$reset())
|
this.$nextTick(() => this.v$.$reset());
|
||||||
},
|
},
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
if (!await this.v$.$validate())
|
if (!(await this.v$.$validate())) return;
|
||||||
return
|
|
||||||
|
|
||||||
if (this.subtask.deadline) this.subtask.deadline = tmUtils.formatDateTimeForBackend(this.subtask.deadline)
|
if (this.subtask.deadline)
|
||||||
|
this.subtask.deadline = tmUtils.formatDateTimeForBackend(
|
||||||
|
this.subtask.deadline,
|
||||||
|
);
|
||||||
|
|
||||||
this.subtask.timesheets.push({
|
this.subtask.timesheets.push({
|
||||||
id: null,
|
id: null,
|
||||||
employee_id: this.subtask.employee_id,
|
employee_id: this.subtask.employee_id,
|
||||||
time: typeof this.form.hours == 'string' ? parseFloat(this.form.hours) : this.form.hours
|
time:
|
||||||
})
|
typeof this.form.hours == 'string'
|
||||||
|
? parseFloat(this.form.hours)
|
||||||
|
: this.form.hours,
|
||||||
|
});
|
||||||
|
|
||||||
const payload = {
|
const timesheets = this.subtask.timesheets.map((timesheet) => {
|
||||||
...this.subtask,
|
delete timesheet.employee_name;
|
||||||
timesheets: this.subtask.timesheets.map((timesheet) => {
|
return timesheet;
|
||||||
delete timesheet.employee_name
|
});
|
||||||
return timesheet
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Convert timeblocks from internal format to BE format
|
||||||
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${this.subtask.id}`, payload)
|
const timeblocks = (this.subtask.timeblocks || [])
|
||||||
|
.filter((timeblock) => {
|
||||||
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
|
return timeblock.date || timeblock.start;
|
||||||
|
})
|
||||||
|
.map((timeblock) => {
|
||||||
|
// If already in BE format (has start field), return as is
|
||||||
|
if (timeblock.start && !timeblock.date) {
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: timeblock.start,
|
||||||
|
end: timeblock.end,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.closeModal()
|
// Convert from internal format to BE format
|
||||||
this.$events.emit('on-subtask-edited', subtask)
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
} catch (error) {
|
return {
|
||||||
this.$toast.error(error.message || error)
|
id: timeblock.id || null,
|
||||||
|
start: `${dateStr} ${timeblock.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${timeblock.ends_at_time}:00`,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development')
|
const payload = {
|
||||||
console.log('[z-task-add-time-modal.vue] Error: ', error)
|
...this.subtask,
|
||||||
}
|
timesheets,
|
||||||
},
|
timeblocks,
|
||||||
|
};
|
||||||
|
|
||||||
closeModal() {
|
try {
|
||||||
this.visible = false
|
const { subtask } = await this.$axios.put_auth(
|
||||||
this.v$.$reset()
|
`v1/subtasks/${this.subtask.id}`,
|
||||||
}
|
payload,
|
||||||
}
|
);
|
||||||
}
|
|
||||||
</script>
|
this.closeModal();
|
||||||
|
this.$events.emit('on-subtask-edited', subtask);
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(error.message || error);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development')
|
||||||
|
console.log('[z-task-add-time-modal.vue] Error: ', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
closeModal() {
|
||||||
|
this.visible = false;
|
||||||
|
this.v$.$reset();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
:on-input-focus="onInputFocus"
|
:on-input-focus="onInputFocus"
|
||||||
:on-date-open="onDateOpen"
|
:on-date-open="onDateOpen"
|
||||||
/>
|
/>
|
||||||
<div class="end-button w-100">
|
<div class="end-button w-100" v-if="!subtask.id">
|
||||||
<button @click="addTimeblock">Pridať časový blok</button>
|
<button @click="addTimeblock">Pridať časový blok</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -466,10 +466,36 @@ export default {
|
|||||||
|
|
||||||
data.deadline = splittedDeadline[0];
|
data.deadline = splittedDeadline[0];
|
||||||
data._deadline_time = splittedDeadline[1];
|
data._deadline_time = splittedDeadline[1];
|
||||||
|
|
||||||
|
// Convert BE format timeblocks to internal format
|
||||||
|
const convertedTimeblocks = [];
|
||||||
|
if (data.timeblocks && data.timeblocks.length > 0) {
|
||||||
|
data.timeblocks.forEach((timeblock) => {
|
||||||
|
// If already in internal format, keep as is
|
||||||
|
if (timeblock.date) {
|
||||||
|
convertedTimeblocks.push(timeblock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from BE format to internal format
|
||||||
|
if (timeblock.start && timeblock.end) {
|
||||||
|
const startMoment = moment(timeblock.start, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
const endMoment = moment(timeblock.end, 'YYYY-MM-DD HH:mm:ss');
|
||||||
|
convertedTimeblocks.push({
|
||||||
|
id: timeblock.id || null,
|
||||||
|
date: startMoment.toDate(),
|
||||||
|
starts_at_time: startMoment.format('HH:mm'),
|
||||||
|
ends_at_time: endMoment.format('HH:mm'),
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
data.timeblocks = convertedTimeblocks;
|
||||||
|
|
||||||
this.subtask = this._enrichTask(data);
|
this.subtask = this._enrichTask(data);
|
||||||
|
|
||||||
// TODO: Remove? riesil som neodstranovanie jedineho zanamu v trvani a odpracovanom case
|
// TODO: Remove? riesil som neodstranovanie jedineho zanamu v trvani a odpracovanom case
|
||||||
console.log('2', this.subtask);
|
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,159 +1,226 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog v-model="visible" max-width="750" persistent>
|
<v-dialog v-model="visible" max-width="750" persistent>
|
||||||
<v-card>
|
<v-card>
|
||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<h1>Sub Task má nedokončené úlohy</h1>
|
<h1>Sub Task má nedokončené úlohy</h1>
|
||||||
|
|
||||||
<v-icon
|
<v-icon
|
||||||
@click.stop="closeModal()"
|
@click.stop="closeModal()"
|
||||||
icon="mdi-close"
|
icon="mdi-close"
|
||||||
size="large"
|
size="large"
|
||||||
></v-icon>
|
></v-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div v-for="(timeblock) in subtask?.timeblocks" :key="timeblock.id" class="d-flex align-center mb-3">
|
<div
|
||||||
<p class="task-badge rounded-pill" :class="{'strike-through': timeblock.is_finished}">{{ tmUtils.formatDateTime(timeblock.start) }} - {{ tmUtils.formatDateTime(timeblock.end) }}</p>
|
v-for="timeblock in subtask?.timeblocks"
|
||||||
<v-icon @click.stop="handleFinishTimeblock(timeblock)" class="finish-icon circle-border cursor-pointer ml-2" :class="{'disabled': loadingTimeblocks[timeblock.id] || loadingCloseAll, 'finished': timeblock.is_finished}" icon="mdi-check" size="medium" />
|
:key="timeblock.id"
|
||||||
</div>
|
class="d-flex align-center mb-3"
|
||||||
</div>
|
>
|
||||||
|
<p
|
||||||
|
class="task-badge rounded-pill"
|
||||||
|
:class="{ 'strike-through': timeblock.is_finished }"
|
||||||
|
>
|
||||||
|
{{ tmUtils.formatDateTime(timeblock.start) }} -
|
||||||
|
{{ tmUtils.formatDateTime(timeblock.end) }}
|
||||||
|
</p>
|
||||||
|
<v-icon
|
||||||
|
@click.stop="handleFinishTimeblock(timeblock)"
|
||||||
|
class="finish-icon circle-border cursor-pointer ml-2"
|
||||||
|
:class="{
|
||||||
|
disabled: loadingTimeblocks[timeblock.id] || loadingCloseAll,
|
||||||
|
finished: timeblock.is_finished,
|
||||||
|
}"
|
||||||
|
icon="mdi-check"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<v-btn @click="closeModal()" class="button secondary">Zrušiť</v-btn>
|
<v-btn @click="closeModal()" class="button secondary">Zrušiť</v-btn>
|
||||||
<v-btn @click="handleFinishAllTimeblocks()" :loading="loadingCloseAll" class="button primary">Uzatvoriť všetky</v-btn>
|
<v-btn
|
||||||
</div>
|
@click="handleFinishAllTimeblocks()"
|
||||||
</v-card>
|
:loading="loadingCloseAll"
|
||||||
</v-dialog>
|
class="button primary"
|
||||||
|
>Uzatvoriť všetky</v-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
|
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin';
|
||||||
import tmUtils from '@/plugins/tm@_utils/tm.utils'
|
import tmUtils from '@/plugins/tm@_utils/tm.utils';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [ modalMixin ],
|
mixins: [modalMixin],
|
||||||
emits: ['close', 'on-subtask-edited'],
|
emits: ['close', 'on-subtask-edited'],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'edit' // edit or delete
|
default: 'edit', // edit or delete
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tmUtils,
|
tmUtils,
|
||||||
visible: false,
|
visible: false,
|
||||||
subtask: null,
|
subtask: null,
|
||||||
loadingTimeblocks: {},
|
loadingTimeblocks: {},
|
||||||
loadingCloseAll: false
|
loadingCloseAll: false,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async showModal({ subtask }) {
|
async showModal({ subtask }) {
|
||||||
this.subtask = subtask
|
this.subtask = subtask;
|
||||||
this.visible = true
|
this.visible = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
async complete() {
|
async complete() {
|
||||||
console.log('complete')
|
console.log('complete');
|
||||||
},
|
},
|
||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.visible = false
|
this.visible = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleFinishTimeblock(timeblock) {
|
async handleFinishTimeblock(timeblock) {
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask))
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.loadingTimeblocks[timeblock.id] = true
|
this.loadingTimeblocks[timeblock.id] = true;
|
||||||
await this.$axios.put_auth(`v1/timeblocks/${timeblock.id}`, {...timeblock, is_finished: 1})
|
await this.$axios.put_auth(`v1/timeblocks/${timeblock.id}`, {
|
||||||
timeblock.is_finished = 1
|
...timeblock,
|
||||||
|
is_finished: 1,
|
||||||
|
});
|
||||||
|
timeblock.is_finished = 1;
|
||||||
|
|
||||||
const areAllTimeblocksFinished = subtask.timeblocks.every(timeblock => timeblock.is_finished)
|
const areAllTimeblocksFinished = subtask.timeblocks.every(
|
||||||
|
(timeblock) => timeblock.is_finished,
|
||||||
|
);
|
||||||
|
|
||||||
if (areAllTimeblocksFinished) {
|
if (areAllTimeblocksFinished) {
|
||||||
await this._finishSubtask()
|
await this._finishSubtask();
|
||||||
this.$store.dispatch('tm/removeOngoingSubtask', this.subtask.id)
|
this.$store.dispatch('tm/removeOngoingSubtask', this.subtask.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$events.emit('on-subtask-edited', subtask)
|
this.$events.emit('on-subtask-edited', subtask);
|
||||||
this.closeModal()
|
this.closeModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$toast.error(error.message || error)
|
this.$toast.error(error.message || error);
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development')
|
if (process.env.NODE_ENV === 'development')
|
||||||
console.log('[a-task-timeblocks.vue] Error: ', error)
|
console.log('[a-task-timeblocks.vue] Error: ', error);
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingTimeblocks[timeblock.id] = false
|
this.loadingTimeblocks[timeblock.id] = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleFinishAllTimeblocks() {
|
async handleFinishAllTimeblocks() {
|
||||||
this.loadingCloseAll = true
|
this.loadingCloseAll = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const promises = this.subtask.timeblocks.map(timeblock => {
|
const promises = this.subtask.timeblocks.map((timeblock) => {
|
||||||
return this.$axios.put_auth(`v1/timeblocks/${timeblock.id}`, {...timeblock, is_finished: 1})
|
return this.$axios.put_auth(`v1/timeblocks/${timeblock.id}`, {
|
||||||
})
|
...timeblock,
|
||||||
|
is_finished: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises);
|
||||||
|
|
||||||
this.subtask.timeblocks = this.subtask.timeblocks.map(timeblock => {
|
this.subtask.timeblocks = this.subtask.timeblocks.map((timeblock) => {
|
||||||
timeblock.is_finished = 1
|
timeblock.is_finished = 1;
|
||||||
return timeblock
|
return timeblock;
|
||||||
})
|
});
|
||||||
|
|
||||||
await this._finishSubtask()
|
await this._finishSubtask();
|
||||||
|
|
||||||
this.$store.dispatch('tm/removeOngoingSubtask', this.subtask.id)
|
this.$store.dispatch('tm/removeOngoingSubtask', this.subtask.id);
|
||||||
this.$events.emit('on-subtask-edited', this.subtask)
|
this.$events.emit('on-subtask-edited', this.subtask);
|
||||||
this.closeModal()
|
this.closeModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$toast.error(error.message || error)
|
this.$toast.error(error.message || error);
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingCloseAll = false
|
this.loadingCloseAll = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async _finishSubtask() {
|
async _finishSubtask() {
|
||||||
const tempSubtask = {...this.subtask, is_finished: 1, timesheets: this.subtask.timesheets.map((timesheet) => {
|
const timesheets = this.subtask.timesheets.map((timesheet) => {
|
||||||
delete timesheet.employee_name
|
delete timesheet.employee_name;
|
||||||
return timesheet
|
return timesheet;
|
||||||
})}
|
});
|
||||||
|
|
||||||
return this.$axios.put_auth(`v1/subtasks/${tempSubtask.id}`, tempSubtask)
|
// Convert timeblocks from internal format to BE format
|
||||||
}
|
const timeblocks = (this.subtask.timeblocks || [])
|
||||||
}
|
.filter((timeblock) => {
|
||||||
}
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
|
return timeblock.date || timeblock.start;
|
||||||
|
})
|
||||||
|
.map((timeblock) => {
|
||||||
|
// If already in BE format (has start field), return as is
|
||||||
|
if (timeblock.start && !timeblock.date) {
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: timeblock.start,
|
||||||
|
end: timeblock.end,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert from internal format to BE format
|
||||||
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: `${dateStr} ${timeblock.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${timeblock.ends_at_time}:00`,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const tempSubtask = {
|
||||||
|
...this.subtask,
|
||||||
|
is_finished: 1,
|
||||||
|
timesheets,
|
||||||
|
timeblocks,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.$axios.put_auth(`v1/subtasks/${tempSubtask.id}`, tempSubtask);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.card-content {
|
.card-content {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-actions {
|
.card-actions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.finish-icon {
|
.finish-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.finished {
|
&.finished {
|
||||||
background: #06dece;
|
background: #06dece;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,154 +1,226 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-table>
|
<v-table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th @click="getSelectedTimeblockDeadline" class="text-left">Kód projektu</th>
|
<th @click="getSelectedTimeblockDeadline" class="text-left">
|
||||||
<th class="text-left">Názov</th>
|
Kód projektu
|
||||||
<th class="text-left">Deadline</th>
|
</th>
|
||||||
<th class="text-left">Najbližší neuzavretý</th>
|
<th class="text-left">Názov</th>
|
||||||
<th class="text-left">Naplánované</th>
|
<th class="text-left">Deadline</th>
|
||||||
<th class="text-left">Zostávajúce</th>
|
<th class="text-left">Najbližší neuzavretý</th>
|
||||||
<th class="text-left">Akcia</th>
|
<th class="text-left">Naplánované</th>
|
||||||
</tr>
|
<th class="text-left">Zostávajúce</th>
|
||||||
</thead>
|
<th class="text-left">Akcia</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="cursor-pointer" v-for="subtask in subtasks" :key="subtask.id" @click.stop="editTask(subtask, false)">
|
<tr
|
||||||
<td>{{ subtask.project_code }}</td>
|
class="cursor-pointer"
|
||||||
<td>{{ subtask.title ?? subtask.master_task }}</td>
|
v-for="subtask in subtasks"
|
||||||
<td v-if="subtask.deadline">{{ getDeadline(subtask) }}</td>
|
:key="subtask.id"
|
||||||
<v-tooltip text="Deadline z Mastertasku" location="end">
|
@click.stop="editTask(subtask, false)"
|
||||||
<template v-slot:activator="{ props }">
|
>
|
||||||
<td v-if="!subtask.deadline" v-bind="props">{{ getDeadline(subtask) }}*</td>
|
<td>{{ subtask.project_code }}</td>
|
||||||
</template>
|
<td>{{ subtask.title ?? subtask.master_task }}</td>
|
||||||
</v-tooltip>
|
<td v-if="subtask.deadline">{{ getDeadline(subtask) }}</td>
|
||||||
<td>{{ displayTimeblock(subtask) }}</td>
|
<v-tooltip text="Deadline z Mastertasku" location="end">
|
||||||
<td>{{ subtask.duration }} h</td>
|
<template v-slot:activator="{ props }">
|
||||||
<td>{{ (subtask.duration - subtask.spent_time).toFixed(2) }} h</td>
|
<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">
|
<td class="task-action">
|
||||||
<div class="d-flex justify-start align-center">
|
<div class="d-flex justify-start align-center">
|
||||||
<img class="pencil-icon mr-3" :class="{'disabled': loadingSubtask[subtask.id]}" :src="checkmarkDouble" @click.stop="handleCompleteTask(subtask)">
|
<img
|
||||||
|
class="pencil-icon mr-3"
|
||||||
|
:class="{ disabled: loadingSubtask[subtask.id] }"
|
||||||
|
:src="checkmarkDouble"
|
||||||
|
@click.stop="handleCompleteTask(subtask)"
|
||||||
|
/>
|
||||||
|
|
||||||
<template v-if="subtask.type == 'office365'">
|
<template v-if="subtask.type == 'office365'">
|
||||||
<img class="pencil-icon" :src="eye" @click.stop="editTask(subtask, false)">
|
<img
|
||||||
<img class="office-icon" :src="office365" />
|
class="pencil-icon"
|
||||||
</template>
|
:src="eye"
|
||||||
|
@click.stop="editTask(subtask, false)"
|
||||||
|
/>
|
||||||
|
<img class="office-icon" :src="office365" />
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<img class="pencil-icon" :src="eye" @click.stop="editTask(subtask, false)">
|
<img
|
||||||
<img class="trash-icon" :src="bin" @click.stop="deleteTask(subtask)" />
|
class="pencil-icon"
|
||||||
</template>
|
:src="eye"
|
||||||
</div>
|
@click.stop="editTask(subtask, false)"
|
||||||
</td>
|
/>
|
||||||
</tr>
|
<img
|
||||||
</tbody>
|
class="trash-icon"
|
||||||
|
:src="bin"
|
||||||
|
@click.stop="deleteTask(subtask)"
|
||||||
</v-table>
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { bin, eye, office365, checkmarkDouble } from '@/plugins/app/_img'
|
import { bin, eye, office365, checkmarkDouble } from '@/plugins/app/_img';
|
||||||
|
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex';
|
||||||
import moment from 'moment'
|
import moment from 'moment';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
subtasks: {
|
subtasks: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
eye,
|
eye,
|
||||||
bin,
|
bin,
|
||||||
office365,
|
office365,
|
||||||
checkmarkDouble,
|
checkmarkDouble,
|
||||||
loadingSubtask: {}
|
loadingSubtask: {},
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('tm', ['mastertasks'])
|
...mapGetters('tm', ['mastertasks']),
|
||||||
},
|
},
|
||||||
|
|
||||||
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 });
|
||||||
},
|
},
|
||||||
|
|
||||||
async handleCompleteTask(subtask) {
|
async handleCompleteTask(subtask) {
|
||||||
const areAllTimeBlocksFinished = subtask.timeblocks.every(tb => tb.is_finished)
|
const areAllTimeBlocksFinished = subtask.timeblocks.every(
|
||||||
|
(tb) => tb.is_finished,
|
||||||
|
);
|
||||||
|
|
||||||
if (!areAllTimeBlocksFinished) {
|
if (!areAllTimeBlocksFinished) {
|
||||||
this.$events.emit('show-unable-to-complete-task-modal', { subtask })
|
this.$events.emit('show-unable-to-complete-task-modal', { subtask });
|
||||||
} else {
|
} else {
|
||||||
await this._finishSubtask(subtask)
|
await this._finishSubtask(subtask);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTask(subtask) {
|
deleteTask(subtask) {
|
||||||
if (subtask.type == 'office365')
|
if (subtask.type == 'office365')
|
||||||
return this.$events.emit('show-unable-to-edit-office-task-modal', { mode: 'delete' })
|
return this.$events.emit('show-unable-to-edit-office-task-modal', {
|
||||||
|
mode: 'delete',
|
||||||
|
});
|
||||||
|
|
||||||
this.$events.emit('show-delete-task-modal', {subtask})
|
this.$events.emit('show-delete-task-modal', { subtask });
|
||||||
},
|
},
|
||||||
|
|
||||||
getDeadline(subtask) {
|
getDeadline(subtask) {
|
||||||
if (subtask?.deadline)
|
if (subtask?.deadline)
|
||||||
return moment(subtask.deadline).format('DD.MM.yyyy')
|
return moment(subtask.deadline).format('DD.MM.yyyy');
|
||||||
|
|
||||||
return moment(subtask.master_task_deadline).format('DD.MM.yyyy')
|
return moment(subtask.master_task_deadline).format('DD.MM.yyyy');
|
||||||
},
|
},
|
||||||
|
|
||||||
getSelectedTimeblockDeadline(timeblocks) {
|
getSelectedTimeblockDeadline(timeblocks) {
|
||||||
const currentDate = moment()
|
const currentDate = moment();
|
||||||
|
|
||||||
const unfinishedTimeblocks = timeblocks.filter(tb => tb.is_finished === 0)
|
const unfinishedTimeblocks = timeblocks.filter(
|
||||||
if (unfinishedTimeblocks.length > 0) return moment(unfinishedTimeblocks[0].end).format('DD.MM.yyyy, HH:mm')
|
(tb) => tb.is_finished === 0,
|
||||||
|
);
|
||||||
|
if (unfinishedTimeblocks.length > 0)
|
||||||
|
return moment(unfinishedTimeblocks[0].end).format('DD.MM.yyyy, HH:mm');
|
||||||
|
|
||||||
const upcomingTimeblocks = timeblocks
|
const upcomingTimeblocks = timeblocks
|
||||||
.filter(tb => moment(tb.end).isAfter(currentDate))
|
.filter((tb) => moment(tb.end).isAfter(currentDate))
|
||||||
.sort((a, b) => moment(a.end).diff(moment(b.end)))
|
.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')
|
if (upcomingTimeblocks.length > 0 && upcomingTimeblocks.is_finished === 0)
|
||||||
|
return moment(upcomingTimeblocks[0].end).format('DD.MM.yyyy, HH:mm');
|
||||||
|
|
||||||
return ''
|
return '';
|
||||||
},
|
},
|
||||||
|
|
||||||
displayTimeblock(subtask) {
|
displayTimeblock(subtask) {
|
||||||
const timeblockDeadline = this.getSelectedTimeblockDeadline(subtask.timeblocks)
|
const timeblockDeadline = this.getSelectedTimeblockDeadline(
|
||||||
|
subtask.timeblocks,
|
||||||
|
);
|
||||||
|
|
||||||
return timeblockDeadline !== '' ? timeblockDeadline : 'Žiadny termín'
|
return timeblockDeadline !== '' ? timeblockDeadline : 'Žiadny termín';
|
||||||
},
|
},
|
||||||
|
|
||||||
async _finishSubtask(subtask) {
|
async _finishSubtask(subtask) {
|
||||||
const tempSubtask = {...subtask, is_finished: 1, timesheets: subtask?.timesheets.map((timesheet) => {
|
const timesheets =
|
||||||
delete timesheet.employee_name
|
subtask?.timesheets.map((timesheet) => {
|
||||||
return timesheet
|
delete timesheet.employee_name;
|
||||||
}) || []}
|
return timesheet;
|
||||||
|
}) || [];
|
||||||
|
|
||||||
try {
|
// Convert timeblocks from internal format to BE format
|
||||||
this.loadingSubtask[subtask.id] = true
|
const timeblocks = (subtask.timeblocks || [])
|
||||||
await this.$axios.put_auth(`v1/subtasks/${tempSubtask.id}`, tempSubtask)
|
.filter((timeblock) => {
|
||||||
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
|
return timeblock.date || timeblock.start;
|
||||||
|
})
|
||||||
|
.map((timeblock) => {
|
||||||
|
// If already in BE format (has start field), return as is
|
||||||
|
if (timeblock.start && !timeblock.date) {
|
||||||
|
return {
|
||||||
|
id: timeblock.id || null,
|
||||||
|
start: timeblock.start,
|
||||||
|
end: timeblock.end,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.$store.dispatch('tm/removeOngoingSubtask', subtask.id)
|
// Convert from internal format to BE format
|
||||||
this.$events.emit('on-subtask-edited', subtask)
|
const dateStr = moment(timeblock.date).format('YYYY-MM-DD');
|
||||||
} catch (error) {
|
return {
|
||||||
this.$toast.error(error.message || error)
|
id: timeblock.id || null,
|
||||||
|
start: `${dateStr} ${timeblock.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${timeblock.ends_at_time}:00`,
|
||||||
|
is_finished: timeblock.is_finished || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development')
|
const tempSubtask = {
|
||||||
console.log('[a-ongoing-table.vue] Error: ', error)
|
...subtask,
|
||||||
} finally {
|
is_finished: 1,
|
||||||
this.loadingSubtask[subtask.id] = false
|
timesheets,
|
||||||
}
|
timeblocks,
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
try {
|
||||||
|
this.loadingSubtask[subtask.id] = true;
|
||||||
|
await this.$axios.put_auth(
|
||||||
|
`v1/subtasks/${tempSubtask.id}`,
|
||||||
|
tempSubtask,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.$store.dispatch('tm/removeOngoingSubtask', subtask.id);
|
||||||
|
this.$events.emit('on-subtask-edited', subtask);
|
||||||
|
} catch (error) {
|
||||||
|
this.$toast.error(error.message || error);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development')
|
||||||
|
console.log('[a-ongoing-table.vue] Error: ', error);
|
||||||
|
} finally {
|
||||||
|
this.loadingSubtask[subtask.id] = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
|
|||||||
Reference in new issue
Block a user