feature(task-modal): added ability to edit timeblocks both on create and edit

This commit is contained in:
Matej Glasnák committed 2025-10-14 01:02:36 +02:00
1 parent 80d0e57351
commit 056b27b016
2 files changed
+119 -71

No files matched your search

@@ -27,7 +27,6 @@
<v-text-field
:value="timeblock.starts_at_time"
@input="updateStartTime"
@focus="onInputFocus"
:class="{ error: v$?.timeblock.starts_at_time.$errors.length }"
type="time"
variant="solo"
@@ -37,7 +36,6 @@
<v-text-field
:value="timeblock.ends_at_time"
@input="updateEndTime"
@focus="onInputFocus"
:class="{ error: v$?.timeblock.ends_at_time.$errors.length }"
type="time"
variant="solo"
@@ -109,20 +107,20 @@ export default {
async openDatepicker(ref) {
this.$refs[ref].openMenu();
},
updateDate(date) {
const timeblock = { ...this.timeblock };
if (date) timeblock.date = date;
this.$emit('updateTimeblock', timeblock);
const updated = { ...this.timeblock, date };
this.$emit('updateTimeblock', updated);
},
updateStartTime(event) {
const timeblock = { ...this.timeblock };
timeblock.starts_at_time = event.target.value;
this.$emit('updateTimeblock', timeblock);
const updated = { ...this.timeblock, starts_at_time: event.target.value };
this.$emit('updateTimeblock', updated);
},
updateEndTime(event) {
const timeblock = { ...this.timeblock };
timeblock.ends_at_time = event.target.value;
this.$emit('updateTimeblock', timeblock);
const updated = { ...this.timeblock, ends_at_time: event.target.value };
this.$emit('updateTimeblock', updated);
},
},
};
@@ -158,4 +156,3 @@ export default {
width: 100% !important
pointer-events: none
</style>
e
+110 -59
View File
@@ -234,70 +234,22 @@
</div>
</template>
<template v-if="!subtask.id">
<div class="timeblocks w-100">
<A-timeblock
v-for="(timeblock, index) in timeblocks"
v-for="(timeblock, index) in editableTimeblocks"
:key="index"
@updateTimeblock="(val) => updateTimeblock(val, index)"
@removeTimeblock="removeTimeblock(index)"
:timeblock="timeblock"
:deletable="index > 0"
editable
:editable="true"
:ref="`timetable-${index}`"
:prevent-input-focus="preventInputFocus"
:on-input-focus="onInputFocus"
:on-date-open="onDateOpen"
/>
<div class="end-button w-100">
<button
@click="
timeblocks.push({
date: null,
starts_at_time: null,
ends_at_time: null,
})
"
>
Pridať časový blok
</button>
</div>
</div>
</template>
<div
v-else
v-for="(timeblock, index) in subtask.timeblocks"
:key="index"
class="timeblock w-100"
>
<Datepicker
:model-value="timeblock.start.substring(0, 10)"
format="dd.MM.yyyy"
locale="sk"
select-text="Vybrať"
cancel-text="Zrušiť"
placeholder="Dátum"
:auto-apply="true"
:enable-time-picker="false"
readonly
class="mb-5 modal-input"
/>
<div class="splitted-time-inputs w-100">
<v-text-field
:value="timeblock.start.substring(11, 16)"
type="time"
variant="solo"
bg-color="white"
label="Začiatok"
/>
<v-text-field
:value="timeblock.end.substring(11, 16)"
type="time"
variant="solo"
bg-color="white"
label="Koniec"
/>
<button @click="addTimeblock">Pridať časový blok</button>
</div>
</div>
@@ -459,6 +411,27 @@ export default {
_fullName: `${mastertask.name} [${mastertask.project_code}]`,
}));
},
// Unified timeblocks for both create and update modes
editableTimeblocks: {
get() {
if (this.subtask?.id) {
// Update mode: use subtask.timeblocks (from BE)
return this.subtask.timeblocks || [];
}
// Create mode: use this.timeblocks (local state)
return this.timeblocks;
},
set(value) {
if (this.subtask?.id) {
// Update mode: update subtask.timeblocks (Vue 3 reactivity)
this.subtask.timeblocks = value;
} else {
// Create mode: update this.timeblocks
this.timeblocks = value;
}
},
},
},
validations() {
@@ -520,11 +493,42 @@ export default {
},
updateTimeblock(val, index) {
if (this.subtask?.id) {
// Update mode: direct assignment (Vue 3 reactivity)
this.subtask.timeblocks[index] = val;
} else {
// Create mode: direct assignment
this.timeblocks[index] = val;
}
},
addTimeblock() {
const newTimeblock = {
date: null,
starts_at_time: null,
ends_at_time: null,
};
if (this.subtask?.id) {
// Update mode
if (!this.subtask.timeblocks) {
this.subtask.timeblocks = [];
}
this.subtask.timeblocks.push(newTimeblock);
} else {
// Create mode
this.timeblocks.push(newTimeblock);
}
},
removeTimeblock(index) {
if (this.subtask?.id) {
// Update mode
this.subtask.timeblocks.splice(index, 1);
} else {
// Create mode
this.timeblocks.splice(index, 1);
}
},
async showModal({ subtask, editMode = false, mastertask = {} }) {
@@ -549,6 +553,35 @@ export default {
`v1/subtasks/${subtask.id}`,
);
// 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;
if (!data.deadline) {
this.editMode = editMode || !data.id;
return (this.subtask = this._enrichTask(data));
@@ -624,9 +657,7 @@ export default {
async validateAllFields() {
const employeesValid = await this._validateEmployees();
const timeblocksValid = this.subtask?.id
? true
: await this._validateTimeblocks();
const timeblocksValid = await this._validateTimeblocks();
if (!(await this.v$.$validate())) return;
if (!employeesValid || !timeblocksValid) return;
@@ -661,9 +692,22 @@ export default {
return timesheet;
});
// Convert timeblocks from internal format to BE format for update
const timeblocks = (this.subtask.timeblocks || [])
.filter((timeblock) => timeblock.date) // Only send timeblocks with dates
.map((timeblock) => {
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.patch_auth_form(
`v1/subtasks/${this.subtask.id}`,
{ ...this.subtask, timesheets },
{ ...this.subtask, timesheets, timeblocks },
);
localStorage.removeItem('subtaskData');
@@ -809,12 +853,19 @@ export default {
},
async _validateTimeblocks() {
let invalid = false;
for (let i = 0; i < this.timeblocks.length; i++) {
let valid = true;
const timeblocks = this.editableTimeblocks;
for (let i = 0; i < timeblocks.length; i++) {
const ref = this.$refs[`timetable-${i}`];
invalid = (await ref[0].v$.$validate()) || invalid;
if (ref && ref[0]) {
const isValid = await ref[0].v$.$validate();
if (!isValid) {
valid = false;
}
return invalid;
}
}
return valid;
},
_enrichTask(subtask) {