Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Chip, Typography } from '@mui/material';
import { useTheme } from '@mui/system';
import { CSSProperties, DragEvent, MouseEvent, useEffect, useState } from 'react';
import { CSSProperties, DragEvent, MouseEvent, useCallback, useEffect, useRef, useState } from 'react';
import useMeasure from 'react-use-measure';
import { addDaysToDate } from 'shared';
import { GanttChange, GanttTask, GANTT_CHART_CELL_SIZE } from '../../../../../utils/gantt.utils';
Expand All @@ -15,6 +15,10 @@ import {
import { ArcherElement } from 'react-archer';
import { v4 as uuidv4 } from 'uuid';

const CELL_SIZE_PX = 38 + 2; // 38px cell + 2px for borders (1px each side)
const GAP_SIZE_PX = 10; // empirically determined, see note above
const WIDTH_PER_DAY = 7.2; //width per day to use for resizing calculations, kind of arbitrary,

interface GanttTaskBarEditProps<T> {
days: Date[];
task: GanttTask<T>;
Expand All @@ -33,18 +37,19 @@ export const GanttTaskBarEditView = <T,>({
onAddTaskPressed
}: GanttTaskBarEditProps<T>) => {
const theme = useTheme();
const [startX, setStartX] = useState<number | null>(null);
const [showDropPoints, setShowDropPoints] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const [width, setWidth] = useState(0); // current width of component, will change on resize
const [width, setWidth] = useState(0);
const [correctWidth, setCorrectWidth] = useState(0);
const [measureRef, bounds] = useMeasure();
const widthPerDay = 7.2; //width per day to use for resizing calculations, kind of arbitrary,
const hasMeasuredRef = useRef(false);
const boxRef = useRef<HTMLDivElement | null>(null);

const taskBarDisplayStyles: CSSProperties = {
gridColumnStart: getStartCol(task.start),
gridColumnEnd: getEndCol(task.end),
height: '2rem',
width: task.root ? 'unset' : width === 0 ? `unset` : `${width}px`,
width: task.root ? 'unset' : correctWidth > 0 ? `${correctWidth}px` : 'auto',
border: `1px solid ${isResizing ? theme.palette.text.primary : theme.palette.divider}`,
borderRadius: '0.25rem',
backgroundColor: task.styles ? task.styles.backgroundColor : theme.palette.background.paper,
Expand All @@ -67,48 +72,61 @@ export const GanttTaskBarEditView = <T,>({
right: '-10'
};

const getCorrectWidth = useCallback((rawWidth: number) => {
const newEventLengthInDays = floorToMultipleOf7(rawWidth / WIDTH_PER_DAY);
const displayWeeks = newEventLengthInDays / 7 + 1;
return displayWeeks * CELL_SIZE_PX + (displayWeeks - 1) * GAP_SIZE_PX;
}, []);
Comment on lines +75 to +79
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useCallback hook is missing widthPerDay in its dependency array. Since getCorrectWidth references widthPerDay (line 73), it should be included in the dependencies. However, since widthPerDay is a constant defined in the component, consider moving it outside the component as a module-level constant to avoid this issue and improve performance.

Copilot uses AI. Check for mistakes.

useEffect(() => {
if (bounds.width !== 0 && width === 0) {
if (!hasMeasuredRef.current && bounds.width > 0) {
setWidth(bounds.width);
setCorrectWidth(getCorrectWidth(bounds.width));
hasMeasuredRef.current = true;
}
}, [bounds, width]);
}, [bounds.width, getCorrectWidth]);

// used to make sure that any changes to the start and end dates are made in multiples of 7
const roundToMultipleOf7 = (num: number) => {
return Math.round(num / 7) * 7;
const floorToMultipleOf7 = (num: number) => {
return Math.floor(num / 7) * 7;
};

const getDistanceFromLeft = (clientX: number) => {
if (!boxRef.current) return 0;
const rect = boxRef.current.getBoundingClientRect();
return clientX - rect.left;
};
Comment thread
Santiordon marked this conversation as resolved.

const handleMouseDown = (e: MouseEvent<HTMLElement>) => {
const bar = (e.currentTarget as HTMLElement).closest('[data-gantt-bar]');
if (!bar) return;

boxRef.current = (e.currentTarget as HTMLElement).closest('[data-gantt-bar]') as HTMLDivElement;
Comment thread
Santiordon marked this conversation as resolved.
setIsResizing(true);
setStartX(e.clientX);
};

const handleMouseMove = (e: MouseEvent<HTMLElement>) => {
if (isResizing) {
const currentX = e.clientX;
const deltaX = currentX - startX!;
setWidth(Math.max(100, width + deltaX));
setStartX(currentX);
}
if (!isResizing) return;

const newWidth = Math.max(100, getDistanceFromLeft(e.clientX));

setWidth(newWidth); // sync render
setCorrectWidth(getCorrectWidth(newWidth));
};

const handleMouseUp = () => {
if (isResizing) {
setIsResizing(false);
// Use change in width to calculate new length
const newEventLengthInDays = roundToMultipleOf7(width / widthPerDay);
// The gantt chart tasks are inclusive (their width includes the full width of their start and end date)
const newEventLengthInDays = floorToMultipleOf7(width / WIDTH_PER_DAY);
const displayWeeks = newEventLengthInDays / 7 + 1;
// We need these magic pixel numbers to dynamically calculate the correct width of the task to keep it in sync with the stored end date
const correctWidth = displayWeeks * 38 + (displayWeeks - 1) * 10;
const newEndDate = addDaysToDate(task.start, newEventLengthInDays);
const correctWidth = displayWeeks * 40 + (displayWeeks - 1) * 10;
setWidth(correctWidth);
createChange({
id: uuidv4(),
element: task.element,
type: 'change-end-date',
originalEnd: task.end,
newEnd: newEndDate
newEnd: addDaysToDate(task.start, newEventLengthInDays)
});
}
};
Expand All @@ -124,7 +142,7 @@ export const GanttTaskBarEditView = <T,>({
e.preventDefault();
};
const onDrop = (day: Date) => {
const days = roundToMultipleOf7(differenceInDays(day, task.start));
const days = floorToMultipleOf7(differenceInDays(day, task.start));
createChange({ id: uuidv4(), element: task.element, type: 'shift-by-days', days });
};

Expand Down Expand Up @@ -156,7 +174,7 @@ export const GanttTaskBarEditView = <T,>({
};
})}
>
<div ref={measureRef} style={taskBarDisplayStyles}>
<div data-gantt-bar ref={measureRef} style={taskBarDisplayStyles}>
<Box sx={webKitBoxContainerStyles()}>
<Box draggable={!task.root} onDrag={onDragStart} onDragEnd={onDragEnd} sx={webKitBoxStyles()}>
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
Expand Down
Loading