Reputation: 1
I am currently working on a Gantt Chart with draggable tasks (tasks should update their date and align with the grid after the drag) , I am using React and Framer motion to handle drag operations, I build the Gantt grid and the tasks do drag but the problem is that after the drag ends, the tasks new date doesnt align with the expected date in the grid (think of it like the task add both the moved days and the dragged distance while it should add only the moved days)
I tried to update the task date during the drag but resulted in big performance issues and unexpected behaviour, so i just update after the drag end but it just doubles the moved days everytime
const AnimatedDiv = motion.div;
const GanttChart: React.FC<GanttChartProps> = ({ zoomLevel }) => {
const headerRef = useRef<HTMLDivElement>(null);
const bodyRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [isDraggingTask, setIsDraggingTask] = useState(false);
const [scrollStartX, setScrollStartX] = useState(0);
const [startDragColumn, setStartDragColumn] = useState(0);
const [endDragColumn, setEndDragColumn] = useState(0);
const initialDragPos = useRef(0);
const finalDragPos = useRef(0);
const [isOver, setIsOver] = useState<number>(0);
const [tasks, setTasks] = useState<GanttTaskProps[]>([
{
id: "1",
title: "Task 1",
startDate: new Date(2024, 8, 11),
endDate: new Date(2024, 8, 16),
assignee: {
name: "Mira Jane",
avatarUrl: "/assets/images/avatars/avatar1.png",
},
gridColStart: 0,
gridColEnd: 0,
}
]);
//gantt params
const startDate = addDays(new Date(), -30);
const totalDays = 365;
const dayColumnWidth = zoomLevel * 10;
const topRow = [];
const bottomRow: { label: string; width: number; date: Date }[] = [];
const gridTemplateRows = `repeat(${7}, auto)`;
//gantt task categories
//task update on drag
const handleMouseEnterGridItem = (index: number) => {
console.log(index);
setIsOver(index);
};
const updateTaskDates = (taskId: string, daysMoved: number) => {
console.log("updating task...");
setTasks((prevTasks) =>
prevTasks.map((task) =>
task.id === taskId
? {
...task,
startDate: addDays(task.startDate, daysMoved),
endDate: addDays(task.endDate, daysMoved),
}
: task
)
);
};
// Handles the start of dragging.
console.log(tasks);
const onDragStart = useCallback(
(
event: MouseEvent | PointerEvent | TouchEvent,
info: PanInfo,
task: GanttTaskProps
) => {
// Prevent the default behavior and prevent conflicts with other elements.
event.preventDefault();
setIsDraggingTask(true);
},
[] // No dependencies are needed here unless you update more variables inside this function.
);
const getGridItemEquivalentDate = (index: number) => {
console.log(new Date(bottomRow[index].date));
return new Date(bottomRow[index].date);
};
const onDragEnd = (
event: any,
info: PanInfo,
task: Partial<GanttTaskProps>
) => {
event.preventDefault();
const offsetX = info.offset.x;
// Convert the drag distance to days
const daysMoved = Math.round(offsetX / dayColumnWidth);
if (Math.abs(daysMoved) > 0) {
updateTaskDates(task.id ?? "1", daysMoved);
}
};
return (
<div className="max-w-6xl">
<div
ref={headerRef}
className="whitespace-nowrap border-b border-gray-300 w-full bg-gray-50"
>
<div
className="grid grid-cols-auto"
style={{
gridTemplateColumns: `repeat(${topRow.length}, auto)`,
}}
>
{topRow.map((item, index) => (
<AnimatedDiv
key={index}
className="text-center border-r border-gray-300 text-gray-600 font-semibold bg-gray-50"
style={{ width: `${item.width}px` }}
>
{item.label}
</AnimatedDiv>
))}
</div>
<div
className="grid grid-cols-auto"
style={{ gridTemplateColumns: `repeat(${bottomRow.length}, auto)` }}
>
{bottomRow.map((item, index) => (
<AnimatedDiv
key={index}
className="text-center bg-white text-gray-700 border-r border-gray-300 text-xs"
style={{ width: `${item.width}px` }}
>
{item.label}
</AnimatedDiv>
))}
</div>
</div>
<div
ref={bodyRef}
className="overflow-x-visible overflow-y-visible scroll-smooth whitespace-nowrap w-full"
onMouseDown={handleMouseDown}
style={{ cursor: isDragging ? "grabbing" : "grab" }}
>
<div
className="grid h-screen"
style={{
gridTemplateRows: gridTemplateRows,
gridTemplateColumns: `repeat(${bottomRow.length}, auto)`,
// Add border for debugging
}}
>
{bottomRow.map((i, index) => (
<div
style={{
gridRow: index + 1,
gridColumn: index + 1,
width: `${i.width}px`,
}}
></div>
))}
{Array(15)
.fill("1")
.map((i, rowIndex) =>
bottomRow.map((_, colIndex) => (
<div
onPointerEnter={() => handleMouseEnterGridItem(colIndex + 1)}
onMouseEnter={() => handleMouseEnterGridItem(colIndex + 1)}
key={`${rowIndex}-${colIndex}`}
className={`border-r-[1px] border-b-[1px] text-xs p-1 border-slate-300 ${
isOver === colIndex + 1 && "bg-slate-200"
}`}
style={{
gridColumn: colIndex + 1,
gridRow: rowIndex + 1,
height: "50px",
}}
>
{colIndex + 1}
</div>
))
)}
{tasks.map((task, index) => {
const gridColStart =
// Math.trunc(differenceInHours(task.startDate, startDate)/24)
Math.trunc(differenceInHours(task.startDate, startDate) / 24) + 1;
const gridColEnd =
Math.trunc(differenceInHours(task.endDate, startDate) / 24) + 2;
console.log({ gridColEnd, gridColStart });
return (
<GanttTask
onDrag={() => null}
onDragStart={onDragStart}
key={task.id}
assignee={task.assignee}
endDate={task.endDate}
startDate={task.startDate}
id={task.id}
title={task.title}
gridRow={index + 1}
onDragEnd={onDragEnd}
gridColStart={gridColStart}
gridColEnd={gridColEnd}
/>
);
})}
</div>
</div>
</div>
);
};
export default GanttChart;
Upvotes: 0
Views: 143