From c5672c94f2e6e81908e5734f8d899284048ec85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Glasn=C3=A1k?= Date: Mon, 26 Jan 2026 13:12:26 +0100 Subject: [PATCH] refactor(timeline): updated modal for adding timeblock to correctly display other (draft) timeblocks when editing subtask draft --- .../plugins/tm@_utils/useEmployeeTimeline.ts | 116 +++++++++++------- .../_components/a-task-timeblocks.vue | 27 +++- .../employees/a-employee-timeblock.vue | 13 +- .../task/_components/employees/a-employee.vue | 3 + .../task/_components/z-task-readonly-info.vue | 1 + 5 files changed, 110 insertions(+), 50 deletions(-) diff --git a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts index 5ecbb7a..8a6333e 100644 --- a/app/src/plugins/tm@_utils/useEmployeeTimeline.ts +++ b/app/src/plugins/tm@_utils/useEmployeeTimeline.ts @@ -25,7 +25,7 @@ export function useEmployeeTimeline( currentTimeblock: Ref, otherLocalTimeblocks?: Ref, editingBlockerId?: Ref, - showDraftTasks?: Ref, + draftTimeblocks?: Ref, ) { // Fetch employee's tasks for the selected date const { @@ -64,61 +64,60 @@ export function useEmployeeTimeline( }), }); - const { - data: conceptTasksData, - isLoading: conceptTasksLoading, - error: conceptTasksError, - refetch: conceptTasksRefetch, - } = useQuery({ - queryKey: computed(() => [ - 'concept-employee-timeline', - unref(employee)?.id, - unref(date) ? moment(unref(date)).format('YYYY-MM-DD') : null, - ]), - queryFn: async () => { - const emp = unref(employee); - const d = unref(date); + // const { + // data: conceptTasksData, + // isLoading: conceptTasksLoading, + // error: conceptTasksError, + // refetch: conceptTasksRefetch, + // } = useQuery({ + // queryKey: computed(() => [ + // 'concept-employee-timeline', + // unref(employee)?.id, + // unref(date) ? moment(unref(date)).format('YYYY-MM-DD') : null, + // ]), + // queryFn: async () => { + // const emp = unref(employee); + // const d = unref(date); - if (!emp?.id || !d) { - return null; - } + // if (!emp?.id || !d) { + // return null; + // } - const dateStr = moment(d).format('YYYY-MM-DD'); - const queryParams = new URLSearchParams({ - start: dateStr, - end: dateStr, - employee_id: emp.id.toString(), - type: 'draft', - }).toString(); + // const dateStr = moment(d).format('YYYY-MM-DD'); + // const queryParams = new URLSearchParams({ + // start: dateStr, + // end: dateStr, + // employee_id: emp.id.toString(), + // type: 'draft', + // }).toString(); - const response = await axios.get_auth(`v1/subtasks?${queryParams}`); - return response; - }, - enabled: computed(() => { - const emp = unref(employee); - const d = unref(date); - return !!(emp?.id && d); - }), - }); + // const response = await axios.get_auth(`v1/subtasks?${queryParams}`); + // return response; + // }, + // enabled: computed(() => { + // const emp = unref(employee); + // const d = unref(date); + // return !!(emp?.id && d); + // }), + // }); // Generate timeline tasks from fetched data const timelineTasks = computed(() => { const data = unref(tasksData); - const conceptData = unref(conceptTasksData); + // const conceptData = unref(conceptTasksData); const emp = unref(employee); const d = unref(date); const excludeBlockerId = editingBlockerId ? unref(editingBlockerId) : null; - const includeDrafts = showDraftTasks ? unref(showDraftTasks) : false; + const draftTbs = draftTimeblocks ? unref(draftTimeblocks) : []; - if (!data || !conceptData || !emp || !d) { + if (!data || !emp || !d) { return []; } const subtasks = [ ...(data.subtasks || []), ...(data.blockers || []), - // Only include concept tasks if showDraftTasks is true - ...(includeDrafts ? (conceptData.subtasks || []) : []), + // ...(conceptData.subtasks || []), ]; const selectedDateStr = moment(d).format('YYYY-MM-DD'); @@ -221,6 +220,39 @@ export function useEmployeeTimeline( } }); + // Process draft timeblocks from other employees in the same draft subtask + draftTbs.forEach((timeblock: any) => { + // Skip if timeblock doesn't have required fields + if (!timeblock.start || !timeblock.end) { + return; + } + + const timeblockDateStr = moment(timeblock.start).format('YYYY-MM-DD'); + + // Only include timeblocks for the selected date + if (timeblockDateStr === selectedDateStr) { + const task = { + id: null, // Draft timeblock doesn't have a subtask ID + start: new Date(timeblock.start), + end: new Date(timeblock.end), + master_task: null, + date: new Date(timeblock.start), + project_code: null, + subtask: null, + is_draft: true, + blocker: undefined, + type: 'draft_timeblock', + _timeblock_id: timeblock.id || null, + _timeblock_is_finished: timeblock.is_finished || false, + _real_start: new Date(timeblock.start), + _real_end: new Date(timeblock.end), + _employee_id: timeblock.employee_id, + }; + + tasks.push(task); + } + }); + return tasks; } catch (error) { console.error('Error generating timeline tasks:', error); @@ -328,9 +360,9 @@ export function useEmployeeTimeline( error, refetch, - conceptTasksLoading, - conceptTasksError, - conceptTasksRefetch, + // conceptTasksLoading, + // conceptTasksError, + // conceptTasksRefetch, // Timeline data timelineTasks, diff --git a/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue b/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue index 94c561e..5a9febf 100644 --- a/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue +++ b/app/src/plugins/tm@modal/task/_components/_components/a-task-timeblocks.vue @@ -117,7 +117,7 @@ :employee="employee" :deletable="false" :index="0" - :show-draft-tasks="!!subtask.is_draft" + :draft-timeblocks="draftTimeblocks" /> @@ -215,6 +215,11 @@ export default { type: Object, required: true, }, + + isDraft: { + type: Boolean, + default: false, + }, }, emits: ['update-subtask'], @@ -261,6 +266,26 @@ export default { ); }, + // Get draft timeblocks for the current employee in backend format for timeline + draftTimeblocks() { + if (!this.isDraft) { + return []; + } + + // Convert timeblocks from internal format to backend format for timeline + return this.timeblocks + .filter((tb) => tb.date && tb.starts_at_time && tb.ends_at_time) + .map((tb) => { + const dateStr = moment(tb.date).format('YYYY-MM-DD'); + return { + ...tb, + start: `${dateStr} ${tb.starts_at_time}:00`, + end: `${dateStr} ${tb.ends_at_time}:00`, + employee_id: this.subtask.employee_id, + }; + }); + }, + // Hour width based on screen size hourWidth() { return window.innerWidth <= 768 diff --git a/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue b/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue index 1c73a2d..6c31a79 100644 --- a/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue +++ b/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock.vue @@ -77,7 +77,7 @@ interface Props { currentIndex?: number; deletable?: boolean; index?: number; - showDraftTasks?: boolean; // Whether to show draft/concept tasks on timeline + draftTimeblocks?: TimeblockData[]; // Draft timeblocks from all employees in draft subtask } const props = withDefaults(defineProps(), { @@ -88,7 +88,7 @@ const props = withDefaults(defineProps(), { currentIndex: 0, deletable: false, index: 0, - showDraftTasks: false, + draftTimeblocks: () => [], }); const emit = defineEmits<{ @@ -240,7 +240,7 @@ const otherTimeblocksForSameDate = computed(() => { // Use timeline composable const editingBlockerIdRef = computed(() => props.editingBlockerId); -const showDraftTasksRef = computed(() => props.showDraftTasks); +const draftTimeblocksRef = computed(() => props.draftTimeblocks); const { isLoading, @@ -249,14 +249,13 @@ const { freeTime: _freeTime, timeblockDuration, refetch, - conceptTasksRefetch, } = useEmployeeTimeline( employeeRef, dateRef, timeblockRef, otherTimeblocksForSameDate, editingBlockerIdRef, - showDraftTasksRef, + draftTimeblocksRef, ); // Force refetch when date changes @@ -265,14 +264,14 @@ watch( () => { if (props.modelValue.date && props.employee?.id) { refetch(); - conceptTasksRefetch(); + // conceptTasksRefetch(); } }, ); onMounted(() => { refetch(); - conceptTasksRefetch(); + // conceptTasksRefetch(); }); // All tasks from timelineTasks are already filtered for the selected date diff --git a/app/src/plugins/tm@modal/task/_components/employees/a-employee.vue b/app/src/plugins/tm@modal/task/_components/employees/a-employee.vue index cf8f9a7..fea7278 100644 --- a/app/src/plugins/tm@modal/task/_components/employees/a-employee.vue +++ b/app/src/plugins/tm@modal/task/_components/employees/a-employee.vue @@ -19,6 +19,7 @@ :all-employee-timeblocks="timeblocks" :all-timeblocks="allTimeblocksForCurrentEmployee" :deleted-timeblock-ids="deletedTimeblockIds" + :draft-timeblocks="draftTimeblocks" :current-index="index" :deletable="timeblocks.length > 1" :index="index" @@ -46,6 +47,7 @@ interface Props { timeblocks?: TimeblockData[]; allTimeblocks?: TimeblockData[]; // All timeblocks from all employees deletedTimeblockIds?: number[]; // IDs of saved timeblocks that have been deleted + draftTimeblocks?: TimeblockData[]; // Draft timeblocks from all employees in draft subtask customFilter?: CustomFilterFunction; hasBackground?: boolean; required?: boolean; @@ -55,6 +57,7 @@ const props = withDefaults(defineProps(), { timeblocks: () => [], allTimeblocks: () => [], deletedTimeblockIds: () => [], + draftTimeblocks: () => [], customFilter: undefined, hasBackground: true, required: false, diff --git a/app/src/plugins/tm@modal/task/_components/z-task-readonly-info.vue b/app/src/plugins/tm@modal/task/_components/z-task-readonly-info.vue index 608d20b..b82b924 100644 --- a/app/src/plugins/tm@modal/task/_components/z-task-readonly-info.vue +++ b/app/src/plugins/tm@modal/task/_components/z-task-readonly-info.vue @@ -24,6 +24,7 @@