Configuring summary tasks
Default functionality
A summary task is a group of tasks, milestones and other summary tasks (or other custom items created by a user). A summary task has the next default settings:
- The start and end dates of a summary task depend on child tasks. If a child task is rescheduled to start before the start date of a summary task, the summary task's start date changes. If a child task is rescheduled to end after a summary task's end date, the summary task's end date changes.
- Its start and end dates cannot be changed separately. That's why a summary task's bar cannot be resized. A user can drag a summary task only with its child tasks. If a summary task is shifted, all its child tasks will be moved too.
- The progress of summary tasks is set manually in UI and does not depend on the progress of its child tasks (please, see Change task progress)
- Tasks with child tasks are not converted into summary tasks automatically, a user can convert a task into another type manually ( see convert one task type into another)
The Gantt API allows configuring this default behavior.
Enabling the auto calculation of summary tasks' progress
By default, a user can set the progress of tasks only manually. The example below demonstrates how to enable the auto calculation of summary tasks' progress that will be based on the progress of its child tasks. The formula that is applied for calculation is ∑d*p / ∑d
, where "d" is task duration in days and "p" is the task progress, and "∑" stands for a sum of all loaded tasks.
import { useEffect, useRef } from "react";
import { getData } from "./data";
import { Gantt } from "wx-react-gantt";
const dayDiff = (next, prev) => {
const d = (next - prev) / 1000 / 60 / 60 / 24;
return Math.ceil(Math.abs(d));
};
const data = getData();
const GanttComponent = () => {
const tasks = data.tasks;
const gApiRef = useRef();
const getSummaryProgress = (id) => {
const [totalProgress, totalDuration] = collectProgressFromKids(id);
const res = totalProgress / totalDuration;
return isNaN(res) ? 0 : Math.round(res);
};
const collectProgressFromKids = (id) => {
let totalProgress = 0,
totalDuration = 0;
const kids = gApiRef.current.getTask(id).data;
kids?.forEach((kid) => {
let duration = 0;
if (kid.type !== "milestone" && kid.type !== "summary") {
duration = kid.duration || dayDiff(kid.end, kid.start);
totalDuration += duration;
totalProgress += duration * kid.progress;
}
const [p, d] = collectProgressFromKids(kid.id);
totalProgress += p;
totalDuration += d;
});
return [totalProgress, totalDuration];
};
const recalcSummaryProgress = (id, self) => {
const { tasks } = gApiRef.current.getState();
const task = gApiRef.current.getTask(id);
if (task.type !== "milestone") {
const summary = self && task.type === "summary" ? id : tasks.getSummaryId(id);
if (summary) {
const progress = getSummaryProgress(summary);
gApiRef.current.exec("update-task", {
id: summary,
task: { progress },
});
}
}
};
const init = (api) => {
gApiRef.current = api;
api.getState().tasks.forEach((task) => {
recalcSummaryProgress(task.id, true);
});
api.on("add-task", ({ id }) => {
recalcSummaryProgress(id);
});
api.on("update-task", ({ id }) => {
recalcSummaryProgress(id);
});
api.on("delete-task", ({ source }) => {
recalcSummaryProgress(source, true);
});
api.on("copy-task", ({ id }) => {
recalcSummaryProgress(id);
});
api.on("move-task", ({ id, source, inProgress }) => {
if (inProgress) return;
if (api.getTask(id).parent !== source) recalcSummaryProgress(source, true);
recalcSummaryProgress(id);
});
};
return (
<Gantt
init={init}
tasks={tasks}
links={data.links}
scales={data.scales}
cellWidth={30}
/>
);
};
export default GanttComponent;
Making parent tasks auto convert into summary tasks
By default, a user can convert other tasks into a summary task only manually. The example below shows how to make all parent tasks automatically convert into a summary task and vice versa (from "summary" to "task" in case no child tasks found in a task).
The getTask
method is used to identify if it's a parent task. The api.exec()
method triggers the update-task
action for a parent task and changes the task type to "summary". The same method is applied to change the task type into "task" in case the length of the data array is 0 (no child items found).
The getState()
method identifies the type of each task, and the api.on()
method listens to the add-task
, move-task
, and delete-task
actions to convert parent tasks to summary tasks and explicitly update summary tasks while loading data.
import { useEffect, useRef } from "react";
import { getData } from "./data";
import { Gantt } from "wx-react-gantt";
export default function SomeComponent({ skinSettings }) {
const data = getData();
const apiRef = useRef();
const toSummary = (id, self) => {
const task = apiRef.current.getTask(id);
if (!self) id = task.parent;
if (id && task.type !== "summary") {
apiRef.current.exec("update-task", {
id,
task: { type: "summary" },
});
}
};
const toTask = (id) => {
const obj = apiRef.current.getTask(id);
if (obj && !obj.data?.length) {
apiRef.current.exec("update-task", {
id,
task: { type: "task" },
});
}
};
useEffect(() => {
if (apiRef.current) {
// convert parent tasks to summary
// will load data and then explicitly update summary tasks
apiRef.current.getState().tasks.forEach((task) => {
if (task.data?.length) {
toSummary(task.id, true);
}
});
apiRef.current.on("add-task", (ev) => {
const { id, mode } = ev;
if (mode === "child") toSummary(id);
});
apiRef.current.on("move-task", (ev) => {
const { id, source, mode, inProgress } = ev;
if (inProgress) return;
if (mode === "child") toSummary(id);
else toTask(source);
});
apiRef.current.on("delete-task", (ev) => {
const { source } = ev;
toTask(source);
});
}
}, []);
return (
<Gantt
apiRef={apiRef}
tasks={data.tasks}
links={data.links}
scales={data.scales}
/>
);
}
Disabling the ability to drag the bar of a summary task
To disable the ability to drag a summary task's bar in UI, in the example below we apply the api.intercept()
method to modify the drag-task
action for the "summary" task type. To identify the task type, we apply the getTask
method.
import { useEffect, useRef } from "react";
import { getData } from "./data";
import { Gantt } from "wx-react-gantt";
function GanttComponent() {
const data = getData();
const apiRef = useRef();
useEffect(() => {
if (apiRef.current) {
apiRef.current.intercept("drag-task", ({ id, top }) => {
return !(typeof top === "undefined" && apiRef.current.getTask(id).type === "summary");
});
}
}, [apiRef.current]);
return (
<Gantt
apiRef={apiRef}
tasks={data.tasks}
links={data.links}
scales={data.scales}
/>
);
}
export default GanttComponent;