as the title says, i can't figure out what's the cause when scrolling horizontally or vertically. it triggers a jittering or bounce. maybe someone can share if this is an issue with the package itself or with how i implement the gantt. see video below
https://streamable.com/oxa3ao
"use client";
import { useMemo } from "react";
import { Gantt, WillowDark, IGanttColumn } from "@svar-ui/react-gantt";
import "@svar-ui/react-gantt/all.css";
import { Box } from "@mui/material";
import type { TaskTimelineResponse, TimelineTask } from "../_types/task-management";
// Define Task type for Gantt
export interface Task {
id: number;
text: string;
start: Date;
end: Date;
duration: number;
progress: number;
type: "critical" | "high" | "medium" | "low" | "informative" | "summary" | "normal";
parent?: number;
priority?: string; // for custom column
}
// Priority colors
const priorityColors: Record<string, string> = {
critical: "#d32f2f",
high: "#f44336",
medium: "#ffa000",
low: "#1976d2",
informative: "#9e9e9e",
};
interface TaskTimelineProps {
apiData: TaskTimelineResponse;
}
export default function TaskTimeline({ apiData }: TaskTimelineProps) {
// Memoize mapped tasks to prevent rerenders that break internal scroll
const tasks: Task[] = useMemo(() => {
if (!apiData?.timeline) return [];
const result: Task[] = [];
apiData.timeline.forEach((t: TimelineTask) => {
const task: Task = {
id: t.id,
text: t.task_name,
start: new Date(t.start_date),
end: new Date(t.end_date),
duration: Math.ceil(
(new Date(t.end_date).getTime() - new Date(t.start_date).getTime()) /
(1000 * 60 * 60 * 24)
),
progress: t.status === "completed" ? 100 : 0,
type: t.priority.toLowerCase() as Task["type"],
parent: undefined,
priority: t.priority,
};
// Assign parent if subtask
if (t.is_subtask) {
const lastParent = result
.slice()
.reverse()
.find((r) => !apiData.timeline.find((tt) => tt.id === r.id)?.is_subtask);
task.parent = lastParent?.id;
}
result.push(task);
});
return result;
}, [apiData.timeline]);
// Memoize links for stability
const links = useMemo(() => {
return tasks
.map((t, i) => {
if (i === 0) return null;
return { id: i, source: tasks[i - 1].id, target: t.id, type: "e2e" };
})
.filter(Boolean) as any[];
}, [tasks]);
// Memoize columns
const columns: IGanttColumn[] = useMemo(
() => [
{ id: "text", header: "Task Name", flexgrow: 1, align: "left" },
{ id: "priority", header: "Priority", flexgrow: 1, align: "center" },
],
[]
);
// Memoize scales
const scales = useMemo(
() => [
{ unit: "month", step: 1, format: "MMMM yyyy" },
{ unit: "day", step: 1, format: "d" },
],
[]
);
return (
<WillowDark>
<Box
sx={{
height: 400, // fixed height
width: "100%",
}}
>
<Gantt
tasks={tasks}
links={links}
columns={columns}
scales={scales}
taskTemplate={({ data }) => {
const type = data.type as keyof typeof priorityColors;
return (
<div
style={{
width: "100%",
height: "100%",
backgroundColor: type ? priorityColors[type] : "#555",
color: "#fff",
paddingLeft: 4,
display: "flex",
alignItems: "center",
}}
>
{data.text}
</div>
);
}}
/>
</Box>
</WillowDark>
);
}