feat(z-task-modal): enhance timeblock conversion logic and improve draft handling for employee-specific timeblocks

This commit is contained in:
Matej Glasnák committed 2026-01-08 02:28:03 +01:00
1 parent 81aaa293dd
commit 7a5f354912
4 files changed
+106 -106

No files matched your search

@@ -652,7 +652,7 @@ export default {
this._update(subtask); this._update(subtask);
}, },
async toggleTimeblockIsFinished(originalTimeblock, index) { async toggleTimeblockIsFinished(originalTimeblock, _index) {
// Only works for published subtasks with saved timeblocks // Only works for published subtasks with saved timeblocks
if (!originalTimeblock.id) { if (!originalTimeblock.id) {
return; return;
@@ -1,5 +1,8 @@
<template> <template>
<div class="timeblock"> <div
class="timeblock"
:class="{ 'timeblock--finished': modelValue.is_finished }"
>
<a-employee-timeblock-header <a-employee-timeblock-header
:model-value="modelValue" :model-value="modelValue"
:min-date="minDate" :min-date="minDate"
@@ -25,22 +28,27 @@
/> />
<!-- Timeline Visualization --> <!-- Timeline Visualization -->
<a-employee-timeblock-timeline <div
v-if="modelValue.date && employee" v-if="modelValue.date && employee"
ref="timelineRef" :class="{ 'timeline-wrapper--finished': modelValue.is_finished }"
:is-loading="isLoading" class="timeline-wrapper"
:employee="employee" >
:scheduled-blocks="scheduledBlocks" <a-employee-timeblock-timeline
:other-timeblocks="otherTimeblocksForSameDate" ref="timelineRef"
:current-timeblock-range="currentTimeblockRange" :is-loading="isLoading"
:collision="collision" :employee="employee"
:collision-segments="collisionSegments" :scheduled-blocks="scheduledBlocks"
:timeblock-duration="timeblockDuration" :other-timeblocks="otherTimeblocksForSameDate"
:start-hour="START_HOUR" :current-timeblock-range="currentTimeblockRange"
:end-hour="END_HOUR" :collision="collision"
:hour-width="hourWidth" :collision-segments="collisionSegments"
@timeblock-updated="handleTimeblockUpdated" :timeblock-duration="timeblockDuration"
/> :start-hour="START_HOUR"
:end-hour="END_HOUR"
:hour-width="hourWidth"
@timeblock-updated="handleTimeblockUpdated"
/>
</div>
</div> </div>
</template> </template>
@@ -487,4 +495,9 @@ watch(
border-radius: 8px border-radius: 8px
margin-bottom: 8px margin-bottom: 8px
overflow: hidden overflow: hidden
.timeblock--finished
opacity: 0.4
pointer-events: none
cursor: not-allowed
</style> </style>
@@ -11,6 +11,7 @@ export interface TimeblockData {
starts_at_time: string | null; starts_at_time: string | null;
ends_at_time: string | null; ends_at_time: string | null;
employee_id?: number | null; employee_id?: number | null;
is_finished?: number;
} }
export interface EmployeeFullData { export interface EmployeeFullData {
+75 -89
View File
@@ -480,35 +480,32 @@ export default {
data._deadline_time = splittedDeadline[1]; data._deadline_time = splittedDeadline[1];
// Convert BE format timeblocks to internal format // Convert BE format timeblocks to internal format
const convertedTimeblocks = []; if (data.is_draft && Array.isArray(data.employees)) {
if (data.timeblocks && data.timeblocks.length > 0) { // Handle draft subtasks with employees array
data.timeblocks.forEach((timeblock) => { const allDraftTimeblocks = [];
// If already in internal format, keep as is
if (timeblock.date) {
convertedTimeblocks.push({
...timeblock,
employee_id: data.employee_id, // Add employee_id
});
return;
}
// Convert from BE format to internal format data.employees = data.employees.map((emp) => {
if (timeblock.start && timeblock.end) { const convertedTimeblocks = (emp.timeblocks || [])
const startMoment = moment(timeblock.start, 'YYYY-MM-DD HH:mm:ss'); .map((tb) => this._convertTimeblockToInternal(tb, emp.id))
const endMoment = moment(timeblock.end, 'YYYY-MM-DD HH:mm:ss'); .filter(Boolean);
convertedTimeblocks.push({
id: timeblock.id || null, // Add to flat array for edit mode
date: startMoment.toDate(), allDraftTimeblocks.push(...convertedTimeblocks);
starts_at_time: startMoment.format('HH:mm'),
ends_at_time: endMoment.format('HH:mm'), return {
is_finished: timeblock.is_finished || 0, ...emp,
employee_id: data.employee_id, // Add employee_id timeblocks: convertedTimeblocks,
}); };
}
}); });
// Update flat timeblocks array for edit mode
this.timeblocks = allDraftTimeblocks;
} else {
// Handle published subtasks with root-level timeblocks
this.timeblocks = (data.timeblocks || [])
.map((tb) => this._convertTimeblockToInternal(tb, data.employee_id))
.filter(Boolean);
} }
// Store in component's timeblocks array
this.timeblocks = convertedTimeblocks;
this.subtask = this._enrichTask(data); this.subtask = this._enrichTask(data);
@@ -522,6 +519,38 @@ export default {
}, },
methods: { methods: {
/**
* Convert a timeblock from backend format to internal format
* @param {Object} timeblock - Timeblock in BE format (start/end) or internal format (date/times)
* @param {Number} employeeId - Employee ID to associate with the timeblock
* @returns {Object} Timeblock in internal format
*/
_convertTimeblockToInternal(timeblock, employeeId) {
// If already in internal format, return as is
if (timeblock.date) {
return {
...timeblock,
employee_id: employeeId,
};
}
// 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');
return {
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,
employee_id: employeeId,
};
}
return null;
},
customFilterWithEmployeeFilter(item, input, itemObject) { customFilterWithEmployeeFilter(item, input, itemObject) {
const shouldShowItem = this.customFilter(item, input); const shouldShowItem = this.customFilter(item, input);
@@ -749,84 +778,41 @@ export default {
); );
// Convert BE format timeblocks to internal format // 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,
employee_id: data.employee_id, // Add employee_id for edit mode
});
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,
employee_id: data.employee_id, // Add employee_id for edit mode
});
}
});
}
// Store timeblocks in the component's timeblocks array
this.timeblocks = convertedTimeblocks;
// If draft with employees data, parse timeblocks for each employee
if ( if (
data.is_draft && data.is_draft &&
data.employees && data.employees &&
Array.isArray(data.employees) Array.isArray(data.employees)
) { ) {
// Handle draft subtasks with employees array
const draftTimeblocks = []; const draftTimeblocks = [];
data.employees.forEach((employee) => { data.employees = data.employees.map((employee) => {
if (employee.timeblocks && Array.isArray(employee.timeblocks)) { const convertedTimeblocks = (employee.timeblocks || [])
employee.timeblocks.forEach((timeblock) => { .map((tb) => this._convertTimeblockToInternal(tb, employee.id))
// Convert BE format to internal format .filter(Boolean);
if (timeblock.start && timeblock.end) {
const startMoment = moment( draftTimeblocks.push(...convertedTimeblocks);
timeblock.start,
'YYYY-MM-DD HH:mm:ss', return {
); ...employee,
const endMoment = moment( timeblocks: convertedTimeblocks,
timeblock.end, };
'YYYY-MM-DD HH:mm:ss',
);
draftTimeblocks.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,
employee_id: employee.id, // Associate with employee
});
}
});
}
}); });
// Use draft timeblocks if they exist this.timeblocks = draftTimeblocks;
if (draftTimeblocks.length > 0) {
this.timeblocks = draftTimeblocks;
}
// Ensure employees array has proper structure for editing // Ensure employees array has proper structure for editing
// Check if last employee is not empty, then add empty slot
const lastEmployee = data.employees[data.employees.length - 1]; const lastEmployee = data.employees[data.employees.length - 1];
if (lastEmployee && (lastEmployee.id || lastEmployee.duration)) { if (lastEmployee && (lastEmployee.id || lastEmployee.duration)) {
data.employees.push({}); data.employees.push({});
} }
} else {
// Handle published subtasks with root-level timeblocks
this.timeblocks = (data.timeblocks || [])
.map((tb) =>
this._convertTimeblockToInternal(tb, data.employee_id),
)
.filter(Boolean);
} }
// Ensure at least one timeblock exists for edit mode (non-draft single employee) // Ensure at least one timeblock exists for edit mode (non-draft single employee)