mirror of
https://bitbucket.org/studiotem/jandl-taskmanager-app.git
synced 2026-10-21 14:29:34 +00:00
feat(z-task-modal): implement draft handling for timeblocks and improve employee-specific timeblock management
This commit is contained in:
1 parent
1bca6cbfae
commit
81aaa293dd
3 files changed
+188
-25
No files matched your search
@@ -33,9 +33,9 @@
|
|||||||
icon="mdi-minus"
|
icon="mdi-minus"
|
||||||
size="medium"
|
size="medium"
|
||||||
@click.stop="
|
@click.stop="
|
||||||
subtask.id
|
timeblock.id
|
||||||
? deleteTimeblock(timeblock)
|
? deleteTimeblock(timeblock)
|
||||||
: deleteTimeblockWithoutId(index)
|
: deleteTimeblockByIndex(index)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
@@ -48,11 +48,17 @@
|
|||||||
<v-icon
|
<v-icon
|
||||||
v-if="!isOffice365Task"
|
v-if="!isOffice365Task"
|
||||||
v-show="subtask.id"
|
v-show="subtask.id"
|
||||||
class="circle-border cursor-pointer ml-2"
|
class="circle-border ml-2"
|
||||||
:class="{ completed: timeblock.is_finished }"
|
:class="{
|
||||||
|
completed: timeblock.is_finished,
|
||||||
|
'cursor-pointer': !subtask.is_draft,
|
||||||
|
'disabled-check': subtask.is_draft,
|
||||||
|
}"
|
||||||
icon="mdi-check"
|
icon="mdi-check"
|
||||||
size="medium"
|
size="medium"
|
||||||
@click.stop="toggleTimeblockIsFinished(timeblock)"
|
@click.stop="
|
||||||
|
!subtask.is_draft && toggleTimeblockIsFinished(timeblock, index)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -551,14 +557,29 @@ export default {
|
|||||||
subtask.is_finished = 0;
|
subtask.is_finished = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add timeblock in internal format from newTimeblock
|
const newTimeblockData = {
|
||||||
subtask.timeblocks.push({
|
|
||||||
id: null,
|
id: null,
|
||||||
date: this.newTimeblock.date,
|
date: this.newTimeblock.date,
|
||||||
starts_at_time: this.newTimeblock.starts_at_time,
|
starts_at_time: this.newTimeblock.starts_at_time,
|
||||||
ends_at_time: this.newTimeblock.ends_at_time,
|
ends_at_time: this.newTimeblock.ends_at_time,
|
||||||
is_finished: 0,
|
is_finished: 0,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// For drafts with employees array, add to the correct employee's timeblocks
|
||||||
|
if (subtask.is_draft && Array.isArray(subtask.employees)) {
|
||||||
|
const employeeIndex = subtask.employees.findIndex(
|
||||||
|
(emp) => emp.id == this.subtask.employee_id,
|
||||||
|
);
|
||||||
|
if (employeeIndex !== -1) {
|
||||||
|
if (!subtask.employees[employeeIndex].timeblocks) {
|
||||||
|
subtask.employees[employeeIndex].timeblocks = [];
|
||||||
|
}
|
||||||
|
subtask.employees[employeeIndex].timeblocks.push(newTimeblockData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For published subtasks, add to root timeblocks
|
||||||
|
subtask.timeblocks.push(newTimeblockData);
|
||||||
|
}
|
||||||
|
|
||||||
if (subtask.deadline)
|
if (subtask.deadline)
|
||||||
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
||||||
@@ -592,14 +613,29 @@ export default {
|
|||||||
subtask.is_finished = 0;
|
subtask.is_finished = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add timeblock in internal format from newTimeblock
|
const newTimeblockData = {
|
||||||
subtask.timeblocks.push({
|
|
||||||
id: null,
|
id: null,
|
||||||
date: this.newTimeblock.date,
|
date: this.newTimeblock.date,
|
||||||
starts_at_time: this.newTimeblock.starts_at_time,
|
starts_at_time: this.newTimeblock.starts_at_time,
|
||||||
ends_at_time: this.newTimeblock.ends_at_time,
|
ends_at_time: this.newTimeblock.ends_at_time,
|
||||||
is_finished: 0,
|
is_finished: 0,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// For drafts with employees array, add to the correct employee's timeblocks
|
||||||
|
if (subtask.is_draft && Array.isArray(subtask.employees)) {
|
||||||
|
const employeeIndex = subtask.employees.findIndex(
|
||||||
|
(emp) => emp.id == this.subtask.employee_id,
|
||||||
|
);
|
||||||
|
if (employeeIndex !== -1) {
|
||||||
|
if (!subtask.employees[employeeIndex].timeblocks) {
|
||||||
|
subtask.employees[employeeIndex].timeblocks = [];
|
||||||
|
}
|
||||||
|
subtask.employees[employeeIndex].timeblocks.push(newTimeblockData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For published subtasks, add to root timeblocks
|
||||||
|
subtask.timeblocks.push(newTimeblockData);
|
||||||
|
}
|
||||||
|
|
||||||
if (subtask.deadline)
|
if (subtask.deadline)
|
||||||
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
||||||
@@ -616,8 +652,12 @@ export default {
|
|||||||
this._update(subtask);
|
this._update(subtask);
|
||||||
},
|
},
|
||||||
|
|
||||||
async toggleTimeblockIsFinished(originalTimeblock) {
|
async toggleTimeblockIsFinished(originalTimeblock, index) {
|
||||||
// TODO: Pridat odpracovany cas modal?
|
// Only works for published subtasks with saved timeblocks
|
||||||
|
if (!originalTimeblock.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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));
|
||||||
|
|
||||||
@@ -657,12 +697,25 @@ export default {
|
|||||||
is_finished: newTimeblock.is_finished,
|
is_finished: newTimeblock.is_finished,
|
||||||
};
|
};
|
||||||
|
|
||||||
subtask.timeblocks = subtask.timeblocks.map((originalTimeblock) => {
|
// Update timeblock in correct location (employee's array for drafts, root for published)
|
||||||
if (originalTimeblock.id == convertedTimeblock.id)
|
if (subtask.is_draft && Array.isArray(subtask.employees)) {
|
||||||
return convertedTimeblock;
|
const employeeIndex = subtask.employees.findIndex(
|
||||||
|
(emp) => emp.id == this.subtask.employee_id,
|
||||||
return originalTimeblock;
|
);
|
||||||
|
if (employeeIndex !== -1) {
|
||||||
|
subtask.employees[employeeIndex].timeblocks = subtask.employees[
|
||||||
|
employeeIndex
|
||||||
|
].timeblocks.map((tb) => {
|
||||||
|
if (tb.id == convertedTimeblock.id) return convertedTimeblock;
|
||||||
|
return tb;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subtask.timeblocks = subtask.timeblocks.map((tb) => {
|
||||||
|
if (tb.id == convertedTimeblock.id) return convertedTimeblock;
|
||||||
|
return tb;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.$events.emit('on-subtask-edited', subtask);
|
this.$events.emit('on-subtask-edited', subtask);
|
||||||
this.$emit('update-subtask', subtask);
|
this.$emit('update-subtask', subtask);
|
||||||
@@ -677,9 +730,34 @@ export default {
|
|||||||
async deleteTimeblock({ id }) {
|
async deleteTimeblock({ id }) {
|
||||||
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
|
|
||||||
|
// Remove timeblock from correct location (employee's array for drafts, root for published)
|
||||||
|
if (subtask.is_draft && Array.isArray(subtask.employees)) {
|
||||||
|
// Find which employee this timeblock belongs to by searching through all employees
|
||||||
|
let found = false;
|
||||||
|
subtask.employees = subtask.employees.map((emp) => {
|
||||||
|
if (!emp.timeblocks) return emp;
|
||||||
|
|
||||||
|
// Check if this employee has the timeblock
|
||||||
|
const hasTimeblock = emp.timeblocks.some((tb) => tb.id == id);
|
||||||
|
if (hasTimeblock) {
|
||||||
|
found = true;
|
||||||
|
return {
|
||||||
|
...emp,
|
||||||
|
timeblocks: emp.timeblocks.filter((tb) => tb.id != id),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return emp;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
console.warn('Timeblock not found in any employee', id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
subtask.timeblocks = subtask.timeblocks.filter(
|
subtask.timeblocks = subtask.timeblocks.filter(
|
||||||
(timeblock) => timeblock.id != id,
|
(timeblock) => timeblock.id != id,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (subtask.deadline)
|
if (subtask.deadline)
|
||||||
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
||||||
this._mergeDeadlineTimeDate(),
|
this._mergeDeadlineTimeDate(),
|
||||||
@@ -688,8 +766,34 @@ export default {
|
|||||||
this._update(subtask);
|
this._update(subtask);
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTimeblockWithoutId(_index) {
|
async deleteTimeblockByIndex(index) {
|
||||||
this.$emit('delete-timeblock');
|
const subtask = JSON.parse(JSON.stringify(this.subtask));
|
||||||
|
|
||||||
|
// Remove timeblock by index from correct location
|
||||||
|
if (subtask.is_draft && Array.isArray(subtask.employees)) {
|
||||||
|
const employeeIndex = subtask.employees.findIndex(
|
||||||
|
(emp) => emp.id == this.subtask.employee_id,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
employeeIndex !== -1 &&
|
||||||
|
subtask.employees[employeeIndex].timeblocks
|
||||||
|
) {
|
||||||
|
subtask.employees[employeeIndex].timeblocks.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
subtask.timeblocks.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subtask.deadline)
|
||||||
|
subtask.deadline = tmUtils.formatDateTimeForBackend(
|
||||||
|
this._mergeDeadlineTimeDate(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!subtask.id) {
|
||||||
|
this.$emit('update-unfinished-subtask', subtask);
|
||||||
|
} else {
|
||||||
|
this._update(subtask);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async _update(data) {
|
async _update(data) {
|
||||||
@@ -699,7 +803,42 @@ export default {
|
|||||||
return timesheet;
|
return timesheet;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Convert timeblocks from internal format to BE format
|
const payload = { ...data, timesheets };
|
||||||
|
|
||||||
|
// For drafts with employees array, properly structure the employees with their timeblocks
|
||||||
|
if (data.is_draft && Array.isArray(data.employees)) {
|
||||||
|
const validEmployees = data.employees
|
||||||
|
.filter((emp) => emp.id)
|
||||||
|
.map((emp) => ({
|
||||||
|
id: emp.id,
|
||||||
|
duration: emp.duration,
|
||||||
|
// Convert employee timeblocks to BE format
|
||||||
|
timeblocks: (emp.timeblocks || [])
|
||||||
|
.filter((tb) => tb.date || tb.start)
|
||||||
|
.map((tb) => {
|
||||||
|
if (tb.start && !tb.date) {
|
||||||
|
return {
|
||||||
|
id: tb.id || null,
|
||||||
|
start: tb.start,
|
||||||
|
end: tb.end,
|
||||||
|
is_finished: tb.is_finished || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const dateStr = moment(tb.date).format('YYYY-MM-DD');
|
||||||
|
return {
|
||||||
|
id: tb.id || null,
|
||||||
|
start: `${dateStr} ${tb.starts_at_time}:00`,
|
||||||
|
end: `${dateStr} ${tb.ends_at_time}:00`,
|
||||||
|
is_finished: tb.is_finished || 0,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
payload.employees = validEmployees;
|
||||||
|
// For drafts, don't send root-level timeblocks
|
||||||
|
delete payload.timeblocks;
|
||||||
|
} else {
|
||||||
|
// For published subtasks, convert and send root-level timeblocks
|
||||||
const timeblocks = (data.timeblocks || [])
|
const timeblocks = (data.timeblocks || [])
|
||||||
.filter((timeblock) => {
|
.filter((timeblock) => {
|
||||||
// Filter timeblocks that have either old format (date) or new format (start)
|
// Filter timeblocks that have either old format (date) or new format (start)
|
||||||
@@ -726,9 +865,18 @@ export default {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
payload.timeblocks = timeblocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove UI-only enriched fields
|
||||||
|
delete payload._created_by;
|
||||||
|
delete payload._employee;
|
||||||
|
delete payload._master_task;
|
||||||
|
delete payload._deadline_time;
|
||||||
|
|
||||||
const { subtask } = await this.$axios.put_auth(
|
const { subtask } = await this.$axios.put_auth(
|
||||||
`v1/subtasks/${data.id}`,
|
`v1/subtasks/${data.id}`,
|
||||||
{ ...data, timesheets, timeblocks },
|
payload,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
@@ -792,6 +940,10 @@ export default {
|
|||||||
.completed
|
.completed
|
||||||
background: #06dece
|
background: #06dece
|
||||||
|
|
||||||
|
.disabled-check
|
||||||
|
opacity: 0.5
|
||||||
|
cursor: not-allowed
|
||||||
|
|
||||||
.existing-timeblock-timeline
|
.existing-timeblock-timeline
|
||||||
margin-top: 8px
|
margin-top: 8px
|
||||||
padding-left: 16px
|
padding-left: 16px
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ export default {
|
|||||||
...mapGetters('tm', ['enrichedEmployees']),
|
...mapGetters('tm', ['enrichedEmployees']),
|
||||||
|
|
||||||
normalizedEmployees() {
|
normalizedEmployees() {
|
||||||
// Draft with multiple employees
|
// Drafts always have employees array
|
||||||
if (this.subtask.is_draft && Array.isArray(this.subtask.employees)) {
|
if (this.subtask.is_draft) {
|
||||||
return this.subtask.employees
|
return this.subtask.employees
|
||||||
.filter((emp) => emp.id) // Filter out empty employee slots
|
.filter((emp) => emp.id) // Filter out empty employee slots
|
||||||
.map((emp) => ({
|
.map((emp) => ({
|
||||||
@@ -99,7 +99,7 @@ export default {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single employee (published subtask)
|
// Published subtask - single employee format
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: this.subtask.employee_id,
|
id: this.subtask.employee_id,
|
||||||
@@ -111,6 +111,11 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
hasMultipleEmployees() {
|
hasMultipleEmployees() {
|
||||||
|
// For drafts, always use multi-employee styling (even with 1 employee)
|
||||||
|
if (this.subtask.is_draft) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// For published subtasks, check actual count
|
||||||
return this.normalizedEmployees.length > 1;
|
return this.normalizedEmployees.length > 1;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ export default {
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async showModal({ subtask }) {
|
async showModal({ subtask }) {
|
||||||
|
// Prevent using this modal for drafts - they should be edited instead
|
||||||
|
if (subtask.is_draft) {
|
||||||
|
this.$toast.info('Pre koncepty použite režim úprav');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.subtask = subtask;
|
this.subtask = subtask;
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.form.hours = null;
|
this.form.hours = null;
|
||||||
|
|||||||
Reference in new issue
Block a user