From f3b4c0a862c4d6d0a631c68e5c2c54a37c5739cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Glasn=C3=A1k?= Date: Wed, 14 Jan 2026 04:02:31 +0100 Subject: [PATCH] refactor(timeline): fixed ux for creating time blocks over existing ones on the timeline --- .../a-employee-timeblock-timeline.vue | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock-timeline.vue b/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock-timeline.vue index ca69dc4..ba7abaf 100644 --- a/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock-timeline.vue +++ b/app/src/plugins/tm@modal/task/_components/employees/a-employee-timeblock-timeline.vue @@ -17,10 +17,7 @@ :hour-width="hourWidth" /> -
+
{ // Update cursor style based on work hours if (timelineScroll.value) { - timelineScroll.value.style.cursor = isInWorkHours ? 'crosshair' : 'not-allowed'; + timelineScroll.value.style.cursor = isInWorkHours + ? 'crosshair' + : 'not-allowed'; } - // Don't show preview if over existing block (using relative position) - if (isOverExistingBlock(x)) { - showPreview.value = false; - return; - } + // Allow preview to show even over existing blocks + // This allows creating conflicting timeblocks // Convert to time and snap const snappedStart = snappedTime; @@ -493,24 +489,16 @@ const handleTimelineMouseDown = (event: MouseEvent) => { // Get the target element const target = event.target as HTMLElement; - // Don't interfere with resize handles on current timeblock + // Only allow resize handles to work - don't interfere with them if (target.classList.contains('resize-handle')) { return; } - // Check if clicking on current timeblock (allow resize handles to work) - if (target.closest('.current-timeblock')) { - return; - } + // Allow clicking anywhere else, including over the current timeblock, + // to create/redraw the block at the cursor position - const rect = timelineScroll.value.getBoundingClientRect(); - const scrollLeft = timelineScroll.value.scrollLeft; - const x = event.clientX - rect.left + scrollLeft; - - // Don't start creating if over existing block - if (isOverExistingBlock(x)) { - return; - } + // Allow creating blocks even over existing blocks (scheduled or other timeblocks) + // This enables creating conflicting timeblocks // Check if cursor is in work hours (cursor line must be visible) if (!showCursor.value || !showPreview.value || !previewBlock.value) { @@ -521,7 +509,7 @@ const handleTimelineMouseDown = (event: MouseEvent) => { dragState.value.isStretching = true; dragState.value.startX = event.clientX; dragState.value.initialStart = cursorPosition.value; - + // Calculate initial end time (1 hour later, capped at work_end) let initialEnd = moment(cursorPosition.value).add(1, 'hour').toDate(); const endHour = initialEnd.getHours() + initialEnd.getMinutes() / 60; @@ -535,7 +523,7 @@ const handleTimelineMouseDown = (event: MouseEvent) => { }) .toDate(); } - + dragState.value.initialEnd = initialEnd; // Set initial preview block (already has 1 hour duration from preview) @@ -622,7 +610,7 @@ const handleStretchEnd = () => { // Only emit if we have a valid block (start != end) const startTime = previewBlock.value.start.getTime(); const endTime = previewBlock.value.end.getTime(); - + if (endTime > startTime) { // Emit the new timeblock emit('timeblockUpdated', {