refactor(task-modals): updated timeblocks to new local dat format

This commit is contained in:
Matej Glasnák committed 2025-10-14 18:42:07 +02:00
1 parent eaf83bf0cc
commit 0035251080
8 files changed
+1054 -592

No files matched your search

@@ -1,167 +1,241 @@
<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>
@@ -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,
};
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> </script>
@@ -1,82 +1,135 @@
<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>
@@ -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,
} );
}
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> </script>
+28 -2
View File
@@ -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>