refactor(task-modal): enhance timeblock handling and improve date formatting utilities

This commit is contained in:
Matej Glasnák committed 2025-10-14 18:04:00 +02:00
1 parent 61fc55d313
commit eaf83bf0cc
2 files changed
+270 -134

No files matched your search

+26 -13
View File
@@ -1,32 +1,45 @@
import moment from 'moment'
import moment from 'moment';
export default {
enrichEmployees(employees) {
return employees.map(employee => ({
return employees.map((employee) => ({
...employee,
_fullName: `${ employee.name } ${ employee.surname }`,
_initials: employee.name.charAt(0) + employee.surname.charAt(0)
}))
_fullName: `${employee.name} ${employee.surname}`,
_initials: employee.name.charAt(0) + employee.surname.charAt(0),
}));
},
formatDateTime(date) {
moment(date)
return moment(date).format('DD.MM.yyyy, HH:mm')
moment(date);
return moment(date).format('DD.MM.yyyy, HH:mm');
},
formatTimeblockDateTime(timeblock) {
// Handle new format with separate 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 old format
if (timeblock.start) {
return this.formatDateTime(timeblock.start);
}
return '';
},
formatDateTimeForBackend(date) {
return moment(date).format('YYYY-MM-DD HH:mm:ss')
return moment(date).format('YYYY-MM-DD HH:mm:ss');
},
formatDate(date) {
return moment(date).format('DD.MM.yyyy')
return moment(date).format('DD.MM.yyyy');
},
isBefore(date, compareTo = null) {
return moment(date).isBefore(moment(compareTo))
return moment(date).isBefore(moment(compareTo));
},
isAfter(date, compareTo = moment()) {
return moment(date).isAfter(moment(compareTo))
}
}
return moment(date).isAfter(moment(compareTo));
},
};
@@ -12,14 +12,49 @@
<v-col cols="7">
<v-sheet class="py-3">
<div class="d-flex align-center mb-3" v-if="!isOffice365Task">
<v-icon @click="showModal = true" class="add-date cursor-pointer mr-2" icon="mdi-plus" size="medium" />
<v-icon
@click="showModal = true"
class="add-date cursor-pointer mr-2"
icon="mdi-plus"
size="medium"
/>
<p class="task-badge rounded-pill">Pridať záznam</p>
</div>
<div v-for="(timeblock, index) in timeblocks" :key="timeblock.id" class="d-flex align-center mb-3">
<v-icon v-if="!isOffice365Task" @click.stop="subtask.id ? deleteTimeblock(timeblock) : deleteTimeblockWithoutId(index)" class="circle-border cursor-pointer mr-2" icon="mdi-minus" size="medium" />
<p class="task-badge rounded-pill" :class="{'strike-through': timeblock.is_finished}">{{ tmUtils.formatDateTime(timeblock.start) }} - {{ tmUtils.formatDateTime(timeblock.end) }}</p>
<v-icon v-if="!isOffice365Task" v-show="subtask.id" @click.stop="toggleTimeblockIsFinished(timeblock)" class="circle-border cursor-pointer ml-2" :class="{'completed': timeblock.is_finished}" icon="mdi-check" size="medium" />
<div
v-for="(timeblock, index) in timeblocks"
:key="timeblock.id"
class="d-flex align-center mb-3"
>
<v-icon
v-if="!isOffice365Task"
@click.stop="
subtask.id
? deleteTimeblock(timeblock)
: deleteTimeblockWithoutId(index)
"
class="circle-border cursor-pointer mr-2"
icon="mdi-minus"
size="medium"
/>
<p
class="task-badge rounded-pill"
:class="{ 'strike-through': timeblock.is_finished }"
>
{{ tmUtils.formatTimeblockDateTime(timeblock) }} -
{{
timeblock.ends_at_time || tmUtils.formatDateTime(timeblock.end)
}}
</p>
<v-icon
v-if="!isOffice365Task"
v-show="subtask.id"
@click.stop="toggleTimeblockIsFinished(timeblock)"
class="circle-border cursor-pointer ml-2"
:class="{ completed: timeblock.is_finished }"
icon="mdi-check"
size="medium"
/>
</div>
</v-sheet>
</v-col>
@@ -29,13 +64,54 @@
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Pridať dátum</h1>
<v-icon @click.stop="closeModal()" icon="mdi-close" size="large"></v-icon>
<v-icon
@click.stop="closeModal()"
icon="mdi-close"
size="large"
></v-icon>
</div>
<v-card-actions class="mt-10 d-flex flex-column modal-content">
<Datepicker v-model="form.date" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum" :auto-apply="true" :enable-time-picker="false" class="mb-5 modal-input" :class="{error: v$.form.date.$errors.length}" />
<v-text-field v-model="form.starts_at_time" type="time" variant="solo" bg-color="white" :hint="v$.form.starts_at_time.$errors ? errorUtils.parseErrors(v$.form.starts_at_time.$errors) : null" :class="{ error: v$.form.starts_at_time.$errors.length }" class="mb-1 modal-input" label="Začiatok" />
<v-text-field v-model="form.ends_at_time" type="time" variant="solo" bg-color="white" :hint="v$.form.ends_at_time.$errors ? errorUtils.parseErrors(v$.form.ends_at_time.$errors) : null" :class="{ error: v$.form.ends_at_time.$errors.length }" class="modal-input" label="Koniec" />
<Datepicker
v-model="form.date"
format="dd.MM.yyyy"
locale="sk"
select-text="Vybrať"
cancel-text="Zrušiť"
placeholder="Dátum"
:auto-apply="true"
:enable-time-picker="false"
class="mb-5 modal-input"
:class="{ error: v$.form.date.$errors.length }"
/>
<v-text-field
v-model="form.starts_at_time"
type="time"
variant="solo"
bg-color="white"
:hint="
v$.form.starts_at_time.$errors
? errorUtils.parseErrors(v$.form.starts_at_time.$errors)
: null
"
:class="{ error: v$.form.starts_at_time.$errors.length }"
class="mb-1 modal-input"
label="Začiatok"
/>
<v-text-field
v-model="form.ends_at_time"
type="time"
variant="solo"
bg-color="white"
:hint="
v$.form.ends_at_time.$errors
? errorUtils.parseErrors(v$.form.ends_at_time.$errors)
: null
"
:class="{ error: v$.form.ends_at_time.$errors.length }"
class="modal-input"
label="Koniec"
/>
</v-card-actions>
<v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
@@ -44,7 +120,10 @@
<v-dialog v-model="visible" max-width="750" persistent>
<v-card>
<div class="d-flex justify-space-between align-center">
<h1 class="text-red">Ste si istý, že chcete pridať timeblock po deadline <br> <b class="text-black">z mastertasku {{ subtask?.project_code }} ?</b></h1>
<h1 class="text-red">
Ste si istý, že chcete pridať timeblock po deadline <br />
<b class="text-black">z mastertasku {{ subtask?.project_code }} ?</b>
</h1>
<v-icon
@click.stop="visible = false"
@@ -53,44 +132,51 @@
class="mb-15"
></v-icon>
</div>
<v-card-actions class="mt-10 d-flex justify-start align-center mobile-view">
<v-btn class="button primary half" variant="flat" @click.stop="confirmSubmit()">ÁNO</v-btn>
<v-btn class="button primary half outline" @click.stop="visible = false">ZRUŠIŤ</v-btn>
<v-card-actions
class="mt-10 d-flex justify-start align-center mobile-view"
>
<v-btn
class="button primary half"
variant="flat"
@click.stop="confirmSubmit()"
>ÁNO</v-btn
>
<v-btn class="button primary half outline" @click.stop="visible = false"
>ZRUŠIŤ</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import tmUtils from '@/plugins/tm@_utils/tm.utils'
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import Datepicker from '@vuepic/vue-datepicker'
import moment from 'moment'
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin'
import { mapGetters } from 'vuex'
import tmUtils from '@/plugins/tm@_utils/tm.utils';
import errorUtils from '@/plugins/lib@auth/_utils/error.utils';
import useVuelidate from '@vuelidate/core';
import { required } from '@vuelidate/validators';
import Datepicker from '@vuepic/vue-datepicker';
import moment from 'moment';
import modalMixin from '@/plugins/tm@modal/_mixins/modal.mixin';
import { mapGetters } from 'vuex';
export default {
mixins: [ modalMixin ],
mixins: [modalMixin],
emits: ['update-subtask'],
components: {
Datepicker
Datepicker,
},
props: {
timeblocks: {
type: Array,
required: true
required: true,
},
subtask: {
type: Object,
required: true
}
required: true,
},
},
data() {
@@ -103,9 +189,9 @@ export default {
form: {
date: null,
starts_at_time: '09:00',
ends_at_time: '16:00'
}
}
ends_at_time: '16:00',
},
};
},
validations() {
@@ -113,194 +199,231 @@ export default {
form: {
date: { required },
starts_at_time: { required },
ends_at_time: { required }
}
}
ends_at_time: { required },
},
};
},
computed: {
...mapGetters('tm', ['mastertasks']),
isOffice365Task() {
return this.subtask.type == 'office365'
}
return this.subtask.type == 'office365';
},
},
methods: {
async submit() {
if (!await this.v$.$validate())
return
if (!(await this.v$.$validate())) return;
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()
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) { subtask.timeblocks = [] }
if (!subtask?.timesheets) { subtask.timesheets = [] }
if (!subtask?.is_finished) { subtask.is_finished = 0 }
if (!subtask?.timeblocks) {
subtask.timeblocks = [];
}
if (!subtask?.timesheets) {
subtask.timesheets = [];
}
if (!subtask?.is_finished) {
subtask.is_finished = 0;
}
subtask.timeblocks.push({
end: tmUtils.formatDateTimeForBackend(end),
id: null,
is_finished: 0,
start: tmUtils.formatDateTimeForBackend(start)
})
start: tmUtils.formatDateTimeForBackend(start),
});
if(subtask.deadline) subtask.deadline = tmUtils.formatDateTimeForBackend(this._mergeDeadlineTimeDate())
if (subtask.deadline)
subtask.deadline = tmUtils.formatDateTimeForBackend(
this._mergeDeadlineTimeDate(),
);
const isAfterDeadline = this.isAfterDeadline()
const isAfterDeadline = this.isAfterDeadline();
if (isAfterDeadline) {
this.visible = true
this.visible = true;
} else {
if (!subtask.id) {
this.$emit('update-unfinished-subtask', subtask)
this.$emit('update-unfinished-subtask', subtask);
} else {
this._update(subtask)
this._update(subtask);
}
}
},
async confirmSubmit() {
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()
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) { subtask.timeblocks = [] }
if (!subtask?.timesheets) { subtask.timesheets = [] }
if (!subtask?.is_finished) { subtask.is_finished = 0 }
if (!subtask?.timeblocks) {
subtask.timeblocks = [];
}
if (!subtask?.timesheets) {
subtask.timesheets = [];
}
if (!subtask?.is_finished) {
subtask.is_finished = 0;
}
subtask.timeblocks.push({
end: tmUtils.formatDateTimeForBackend(end),
id: null,
is_finished: 0,
start: tmUtils.formatDateTimeForBackend(start)
})
start: tmUtils.formatDateTimeForBackend(start),
});
if(subtask.deadline) subtask.deadline = tmUtils.formatDateTimeForBackend(this._mergeDeadlineTimeDate())
if (subtask.deadline)
subtask.deadline = tmUtils.formatDateTimeForBackend(
this._mergeDeadlineTimeDate(),
);
if (!subtask.id) {
this.$emit('update-unfinished-subtask', subtask)
this.closeModal()
return
this.$emit('update-unfinished-subtask', subtask);
this.closeModal();
return;
}
this.visible = false
this._update(subtask)
this.visible = false;
this._update(subtask);
},
async toggleTimeblockIsFinished(originalTimeblock) {
// TODO: Pridat odpracovany cas modal?
console.log('toggleTimeblockIsFinished 5', this.subtask)
const subtask = JSON.parse(JSON.stringify(this.subtask))
const timeblock = JSON.parse(JSON.stringify(originalTimeblock))
console.log('toggleTimeblockIsFinished 5', this.subtask);
const subtask = JSON.parse(JSON.stringify(this.subtask));
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 {
const {data: newTimeblock} = await this.$axios.put_auth(`v1/timeblocks/${timeblock.id}`, timeblock)
const { data: newTimeblock } = await this.$axios.put_auth(
`v1/timeblocks/${timeblock.id}`,
timeblock,
);
subtask.timeblocks = subtask.timeblocks.map(originalTimeblock => {
if (originalTimeblock.id == newTimeblock.id)
return newTimeblock
subtask.timeblocks = subtask.timeblocks.map((originalTimeblock) => {
if (originalTimeblock.id == newTimeblock.id) return newTimeblock;
return originalTimeblock
})
return originalTimeblock;
});
this.$events.emit('on-subtask-edited', subtask)
this.$emit('update-subtask', subtask)
this.$events.emit('on-subtask-edited', subtask);
this.$emit('update-subtask', subtask);
if (timeblock.is_finished)
this.$events.emit('show-task-add-time-modal', { subtask })
this.$events.emit('show-task-add-time-modal', { subtask });
} catch (error) {
this.$toast.error(error.message || error)
this.$toast.error(error.message || error);
if (process.env.NODE_ENV === 'development')
console.log('[a-task-timeblocks.vue] Error: ', error)
console.log('[a-task-timeblocks.vue] Error: ', error);
}
},
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)
if(subtask.deadline) subtask.deadline = tmUtils.formatDateTimeForBackend(this._mergeDeadlineTimeDate())
subtask.timeblocks = subtask.timeblocks.filter(
(timeblock) => timeblock.id != id,
);
if (subtask.deadline)
subtask.deadline = tmUtils.formatDateTimeForBackend(
this._mergeDeadlineTimeDate(),
);
this._update(subtask)
this._update(subtask);
},
deleteTimeblockWithoutId(i) {
this.$emit('delete-timeblock')
this.$emit('delete-timeblock');
},
async _update(data) {
try {
const timesheets = data.timesheets.map((timesheet) => {
delete timesheet.employee_name
return timesheet
})
delete timesheet.employee_name;
return timesheet;
});
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${data.id}`, {...data, timesheets})
const { subtask } = await this.$axios.put_auth(
`v1/subtasks/${data.id}`,
{ ...data, timesheets },
);
this.closeModal()
this.$events.emit('on-subtask-edited', subtask)
this.closeModal();
this.$events.emit('on-subtask-edited', subtask);
} catch (error) {
this.$toast.error(error.message || error)
this.$toast.error(error.message || error);
if (process.env.NODE_ENV === 'development')
console.log('[a-task-timeblocks.vue] Error: ', error)
console.log('[a-task-timeblocks.vue] Error: ', error);
}
},
isAfterDeadline() {
const deadline = this.getDeadline(this.subtask)
const deadline = this.getDeadline(this.subtask);
if (!deadline)
return
if (!deadline) return;
const mDate = moment(this.form.date)
const mDeadline = moment(deadline)
const mDate = moment(this.form.date);
const mDeadline = moment(deadline);
const [hours, minutes] = this.form.ends_at_time.split(':').map(Number)
const [hours, minutes] = this.form.ends_at_time.split(':').map(Number);
mDate.hours(hours)
mDate.minutes(minutes)
mDate.hours(hours);
mDate.minutes(minutes);
return mDate.isAfter(mDeadline)
return mDate.isAfter(mDeadline);
},
getDeadline(subtask) {
if (subtask?.deadline)
return subtask.deadline
if (subtask?.deadline) return subtask.deadline;
return subtask.master_task_deadline
return subtask.master_task_deadline;
},
_mergeDeadlineTimeDate() {
const date = new Date(this.subtask.deadline)
const timeParts = this.subtask._deadline_time.split(':')
const date = new Date(this.subtask.deadline);
const timeParts = this.subtask._deadline_time.split(':');
date.setHours(parseInt(timeParts[0]))
date.setMinutes(parseInt(timeParts[1]))
date.setHours(parseInt(timeParts[0]));
date.setMinutes(parseInt(timeParts[1]));
return date
return date;
},
closeModal() {
this.form = {
date: null,
starts_at_time: '09:00',
ends_at_time: '16:00'
}
ends_at_time: '16:00',
};
this.showModal = false
this.$nextTick(() => this.v$.$reset())
}
}
}
this.showModal = false;
this.$nextTick(() => this.v$.$reset());
},
},
};
</script>
<style lang="sass">