From 5731ce9c763e1f4de6a202173e2a995c69eb5dd7 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 2 Mar 2026 10:15:37 +0100 Subject: [PATCH] feat(gantt): wire relation arrows into GanttChart with toggle --- frontend/src/components/gantt/GanttChart.vue | 64 +++++++++++++++++-- .../components/project/views/ProjectGantt.vue | 9 +++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/gantt/GanttChart.vue b/frontend/src/components/gantt/GanttChart.vue index 642fe7792..99a39fa16 100644 --- a/frontend/src/components/gantt/GanttChart.vue +++ b/frontend/src/components/gantt/GanttChart.vue @@ -62,6 +62,13 @@ + @@ -76,6 +83,7 @@ import {useDayjsLanguageSync} from '@/i18n/useDayjsLanguageSync' import {getHexColor} from '@/models/task' import {buildGanttTaskTree, type GanttTaskTreeNode} from '@/helpers/ganttTaskTree' +import {buildRelationArrows, type GanttBarPosition, type GanttArrow} from '@/helpers/ganttRelationArrows' import type {ITask, ITaskPartialWithId} from '@/modelTypes/ITask' import type {DateISO} from '@/types/DateISO' @@ -87,18 +95,22 @@ import GanttRow from '@/components/gantt/GanttRow.vue' import GanttRowBars from '@/components/gantt/GanttRowBars.vue' import GanttVerticalGridLines from '@/components/gantt/GanttVerticalGridLines.vue' import GanttTimelineHeader from '@/components/gantt/GanttTimelineHeader.vue' +import GanttRelationArrows from '@/components/gantt/GanttRelationArrows.vue' import Loading from '@/components/misc/Loading.vue' import {MILLISECONDS_A_DAY} from '@/constants/date' import {roundToNaturalDayBoundary} from '@/helpers/time/roundToNaturalDayBoundary' -const props = defineProps<{ +const props = withDefaults(defineProps<{ isLoading: boolean, filters: GanttFilters, tasks: Map, defaultTaskStartDate: DateISO defaultTaskEndDate: DateISO -}>() + showRelationArrows?: boolean +}>(), { + showRelationArrows: false, +}) const emit = defineEmits<{ (e: 'update:task', task: ITaskPartialWithId): void @@ -180,8 +192,8 @@ const visibleNodes = computed(() => { return result }) -// Used in Task 8 for arrow re-routing when children are collapsed -const _hiddenToAncestor = computed(() => { +// Map hidden tasks to their visible ancestor for arrow re-routing +const hiddenToAncestor = computed(() => { const map = new Map() const hiddenParents = new Set() @@ -323,6 +335,50 @@ watch( {deep: true, immediate: true}, ) +// Compute bar positions for arrow rendering +const ROW_HEIGHT = 40 + +const barPositions = computed(() => { + const positions = new Map() + + ganttBars.value.forEach((rowBars, rowIndex) => { + for (const bar of rowBars) { + const taskId = Number(bar.id) + const x = computeBarX(bar.start) + const width = computeBarWidth(bar) + const y = rowIndex * ROW_HEIGHT + ROW_HEIGHT / 2 + + positions.set(taskId, {x, y, width, rowIndex}) + } + }) + + return positions +}) + +function computeBarX(date: Date): number { + const diff = Math.ceil( + (roundToNaturalDayBoundary(date, true).getTime() - dateFromDate.value.getTime()) / + MILLISECONDS_A_DAY, + ) + return diff * DAY_WIDTH_PIXELS +} + +function computeBarWidth(bar: GanttBarModel): number { + const diff = Math.ceil( + (roundToNaturalDayBoundary(bar.end).getTime() - roundToNaturalDayBoundary(bar.start, true).getTime()) / + MILLISECONDS_A_DAY, + ) + return diff * DAY_WIDTH_PIXELS +} + +// Compute relation arrows +const relationArrows = computed(() => { + if (!props.showRelationArrows) return [] + return buildRelationArrows(tasks.value, barPositions.value, hiddenToAncestor.value) +}) + +const totalHeight = computed(() => ganttRows.value.length * ROW_HEIGHT) + function updateGanttTask(id: string, newStart: Date, newEnd: Date) { const task = tasks.value.get(Number(id)) if (!task) return diff --git a/frontend/src/components/project/views/ProjectGantt.vue b/frontend/src/components/project/views/ProjectGantt.vue index dc613208a..5c37f6f12 100644 --- a/frontend/src/components/project/views/ProjectGantt.vue +++ b/frontend/src/components/project/views/ProjectGantt.vue @@ -38,6 +38,12 @@ > {{ $t('task.show.noDates') }} + + {{ $t('project.gantt.toggleRelationArrows') }} + @@ -53,6 +59,7 @@ :is-loading="isLoading" :default-task-start-date="defaultTaskStartDate" :default-task-end-date="defaultTaskEndDate" + :show-relation-arrows="showRelationArrows" @update:task="updateTask" />