135 lines
3.6 KiB
JavaScript
135 lines
3.6 KiB
JavaScript
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
|
import { getData } from '../data';
|
|
import { Gantt, Editor, defaultEditorItems, ContextMenu } from '../../src';
|
|
import './GanttSummariesProgress.css';
|
|
|
|
function GanttSummariesProgress({ skinSettings }) {
|
|
const data = useMemo(() => getData(), []);
|
|
const tasks = data.tasks;
|
|
|
|
const [api, setApi] = useState();
|
|
|
|
const [items, setItems] = useState(defaultEditorItems);
|
|
|
|
const dayDiff = (next, prev) => {
|
|
const d = (next - prev) / 1000 / 60 / 60 / 24;
|
|
return Math.ceil(Math.abs(d));
|
|
};
|
|
|
|
/**
|
|
* The formula of calculation is ∑d*p / ∑d , where "d" is task duration in days,
|
|
* "p" is the task progress and "∑" stands for a sum of all loaded tasks
|
|
*/
|
|
function getSummaryProgress(id) {
|
|
const [totalProgress, totalDuration] = collectProgressFromKids(id);
|
|
const res = totalProgress / totalDuration;
|
|
return isNaN(res) ? 0 : Math.round(res);
|
|
}
|
|
|
|
function collectProgressFromKids(id) {
|
|
let totalProgress = 0,
|
|
totalDuration = 0;
|
|
const kids = api.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];
|
|
}
|
|
|
|
function recalcSummaryProgress(id, self) {
|
|
const { tasks } = api.getState();
|
|
const task = api.getTask(id);
|
|
|
|
if (task.type != 'milestone') {
|
|
const summary =
|
|
self && task.type === 'summary' ? id : tasks.getSummaryId(id);
|
|
|
|
if (summary) {
|
|
const progress = getSummaryProgress(summary);
|
|
api.exec('update-task', {
|
|
id: summary,
|
|
task: { progress },
|
|
});
|
|
}
|
|
}
|
|
}
|
|
const recalcRef = useRef(null);
|
|
recalcRef.current = recalcSummaryProgress;
|
|
|
|
const init = useCallback((api) => {
|
|
setApi(api);
|
|
// auto progress calculations
|
|
api.on('add-task', ({ id }) => {
|
|
recalcRef.current(id);
|
|
});
|
|
api.on('update-task', ({ id }) => {
|
|
recalcRef.current(id);
|
|
});
|
|
|
|
api.on('delete-task', ({ source }) => {
|
|
recalcRef.current(source, true);
|
|
});
|
|
api.on('copy-task', ({ id }) => {
|
|
recalcRef.current(id);
|
|
});
|
|
api.on('move-task', ({ id, source, inProgress }) => {
|
|
if (inProgress) return;
|
|
|
|
if (api.getTask(id).parent != source) recalcRef.current(source, true);
|
|
recalcRef.current(id);
|
|
});
|
|
|
|
// disabling progress slider for summary tasks
|
|
api.on('show-editor', ({ id }) => {
|
|
if (id) {
|
|
const type = api.getTask(id).type;
|
|
setItems(
|
|
defaultEditorItems.map((ed) => ({
|
|
...ed,
|
|
...(ed.key == 'progress' && {
|
|
config: { disabled: type === 'summary' },
|
|
}),
|
|
})),
|
|
);
|
|
}
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!api) return;
|
|
api.getState().tasks.forEach((task) => {
|
|
recalcSummaryProgress(task.id, true);
|
|
});
|
|
}, [api]);
|
|
|
|
return (
|
|
<div className="wx-OeNgRLo4 wrapper">
|
|
<ContextMenu api={api}>
|
|
<div className="wx-OeNgRLo4 gt-cell">
|
|
<Gantt
|
|
{...skinSettings}
|
|
init={init}
|
|
tasks={tasks}
|
|
links={data.links}
|
|
scales={data.scales}
|
|
cellWidth={30}
|
|
/>
|
|
</div>
|
|
</ContextMenu>
|
|
{api && <Editor api={api} items={items} />}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default GanttSummariesProgress;
|