gantt-lib 0.136.0 → 0.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/dist/core/scheduling/index.d.mts +1 -1
- package/dist/core/scheduling/index.d.ts +1 -1
- package/dist/{index-D99kbU4p.d.mts → index-DheG4h0O.d.mts} +60 -1
- package/dist/{index-D99kbU4p.d.ts → index-DheG4h0O.d.ts} +60 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +243 -3
- package/dist/index.d.ts +243 -3
- package/dist/index.js +2049 -1038
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2037 -1038
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +175 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,6 +33,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
33
33
|
// src/index.ts
|
|
34
34
|
var index_exports = {};
|
|
35
35
|
__export(index_exports, {
|
|
36
|
+
ACTIVITY_LANES_PADDING: () => ACTIVITY_LANES_PADDING,
|
|
37
|
+
ACTIVITY_LANE_BAR_HEIGHT: () => ACTIVITY_LANE_BAR_HEIGHT,
|
|
38
|
+
ACTIVITY_LANE_STEP: () => ACTIVITY_LANE_STEP,
|
|
36
39
|
Button: () => Button,
|
|
37
40
|
Calendar: () => Calendar,
|
|
38
41
|
DAY_MS: () => DAY_MS,
|
|
@@ -54,6 +57,10 @@ __export(index_exports, {
|
|
|
54
57
|
TaskRow: () => TaskRow_default,
|
|
55
58
|
TimeScaleHeader: () => TimeScaleHeader_default,
|
|
56
59
|
TodayIndicator: () => TodayIndicator_default,
|
|
60
|
+
activityChainMinStartDelta: () => activityChainMinStartDelta,
|
|
61
|
+
activityLanesExtraHeight: () => activityLanesExtraHeight,
|
|
62
|
+
activitySpanMs: () => activitySpanMs,
|
|
63
|
+
activityStartConstraint: () => activityStartConstraint,
|
|
57
64
|
addBusinessDays: () => addBusinessDays2,
|
|
58
65
|
alignToWorkingDay: () => alignToWorkingDay,
|
|
59
66
|
and: () => and,
|
|
@@ -76,6 +83,7 @@ __export(index_exports, {
|
|
|
76
83
|
cascadeByLinks: () => cascadeByLinks,
|
|
77
84
|
clampDateRangeForIncomingFS: () => clampDateRangeForIncomingFS,
|
|
78
85
|
clampTaskRangeForIncomingFS: () => clampTaskRangeForIncomingFS,
|
|
86
|
+
computeActivityLanes: () => computeActivityLanes,
|
|
79
87
|
computeCriticalPath: () => computeCriticalPath,
|
|
80
88
|
computeLagFromDates: () => computeLagFromDates,
|
|
81
89
|
computeParentDates: () => computeParentDates,
|
|
@@ -130,10 +138,12 @@ __export(index_exports, {
|
|
|
130
138
|
normalizeUTCDate: () => normalizeUTCDate,
|
|
131
139
|
not: () => not,
|
|
132
140
|
or: () => or,
|
|
141
|
+
packIntervals: () => packIntervals,
|
|
133
142
|
parseDateOnly: () => parseDateOnly,
|
|
134
143
|
parseUTCDate: () => parseUTCDate,
|
|
135
144
|
pixelsToDate: () => pixelsToDate,
|
|
136
145
|
progressInRange: () => progressInRange,
|
|
146
|
+
pushActivityChain: () => pushActivityChain,
|
|
137
147
|
recalculateIncomingLags: () => recalculateIncomingLags,
|
|
138
148
|
recalculateProjectSchedule: () => recalculateProjectSchedule,
|
|
139
149
|
recalculateTaskFromDependencies: () => recalculateTaskFromDependencies,
|
|
@@ -143,6 +153,8 @@ __export(index_exports, {
|
|
|
143
153
|
resolveDateRangeFromPixels: () => resolveDateRangeFromPixels,
|
|
144
154
|
resolveTaskHorizontalGeometry: () => resolveTaskHorizontalGeometry,
|
|
145
155
|
scaleTaskSubtreeDuration: () => scaleTaskSubtreeDuration,
|
|
156
|
+
shiftActivityChain: () => shiftActivityChain,
|
|
157
|
+
shiftActivityDate: () => shiftActivityDate,
|
|
146
158
|
shiftBusinessDayOffset: () => shiftBusinessDayOffset,
|
|
147
159
|
subtractBusinessDays: () => subtractBusinessDays2,
|
|
148
160
|
universalCascade: () => universalCascade,
|
|
@@ -611,644 +623,965 @@ function subtractBusinessDays2(endDate, businessDays, weekendPredicate) {
|
|
|
611
623
|
return result.toISOString().split("T")[0];
|
|
612
624
|
}
|
|
613
625
|
|
|
614
|
-
// src/
|
|
615
|
-
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
function getDependencyLag(dep) {
|
|
621
|
-
return Number.isFinite(dep.lag) ? dep.lag : 0;
|
|
622
|
-
}
|
|
623
|
-
function normalizeTaskDependencyLags(task) {
|
|
624
|
-
return task;
|
|
625
|
-
}
|
|
626
|
-
function normalizeDependencyLag(linkType, lag, predecessorStart, predecessorEnd, businessDays = false, weekendPredicate, successorType) {
|
|
627
|
-
if (linkType !== "FS") {
|
|
628
|
-
return lag;
|
|
629
|
-
}
|
|
630
|
-
const predecessorDuration = getTaskDuration(
|
|
631
|
-
predecessorStart,
|
|
632
|
-
predecessorEnd,
|
|
633
|
-
businessDays,
|
|
634
|
-
weekendPredicate
|
|
626
|
+
// src/utils/geometry.ts
|
|
627
|
+
var getUTCDayDifference = (date1, date2) => {
|
|
628
|
+
const ms1 = Date.UTC(
|
|
629
|
+
date1.getUTCFullYear(),
|
|
630
|
+
date1.getUTCMonth(),
|
|
631
|
+
date1.getUTCDate()
|
|
635
632
|
);
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
633
|
+
const ms2 = Date.UTC(
|
|
634
|
+
date2.getUTCFullYear(),
|
|
635
|
+
date2.getUTCMonth(),
|
|
636
|
+
date2.getUTCDate()
|
|
637
|
+
);
|
|
638
|
+
return Math.round((ms1 - ms2) / (1e3 * 60 * 60 * 24));
|
|
639
|
+
};
|
|
640
|
+
var calculateTaskBar = (taskStartDate, taskEndDate, monthStart, dayWidth) => {
|
|
641
|
+
const startOffset = getUTCDayDifference(taskStartDate, monthStart);
|
|
642
|
+
const duration = getUTCDayDifference(taskEndDate, taskStartDate);
|
|
643
|
+
const left = Math.round(startOffset * dayWidth);
|
|
644
|
+
const width = Math.round((duration + 1) * dayWidth);
|
|
645
|
+
return { left, width };
|
|
646
|
+
};
|
|
647
|
+
var calculateMilestoneGeometry = (taskDate, monthStart, dayWidth, size = 14) => {
|
|
648
|
+
const { left, width } = calculateTaskBar(taskDate, taskDate, monthStart, dayWidth);
|
|
649
|
+
const centerX = Math.round(left + width / 2);
|
|
650
|
+
const halfSize = Math.round(size / 2);
|
|
651
|
+
return {
|
|
652
|
+
centerX,
|
|
653
|
+
left: centerX - halfSize,
|
|
654
|
+
right: centerX + halfSize,
|
|
655
|
+
size
|
|
656
|
+
};
|
|
657
|
+
};
|
|
658
|
+
var calculateMilestoneConnectionBounds = (dayLeft, dayWidth, size = 14) => {
|
|
659
|
+
const halfDiagonal = Math.round(size / Math.SQRT2);
|
|
660
|
+
const centerX = Math.round(dayLeft + dayWidth / 2);
|
|
661
|
+
return {
|
|
662
|
+
left: centerX - halfDiagonal,
|
|
663
|
+
right: centerX + halfDiagonal
|
|
664
|
+
};
|
|
665
|
+
};
|
|
666
|
+
var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
|
|
667
|
+
const startDate = new Date(task.startDate);
|
|
668
|
+
const endDate = new Date(task.endDate);
|
|
669
|
+
if (task.type === "milestone") {
|
|
670
|
+
const size = 14;
|
|
671
|
+
if (override) {
|
|
672
|
+
const bounds2 = calculateMilestoneConnectionBounds(override.left, dayWidth, size);
|
|
673
|
+
return {
|
|
674
|
+
...bounds2,
|
|
675
|
+
centerX: Math.round(override.left + dayWidth / 2)
|
|
676
|
+
};
|
|
662
677
|
}
|
|
678
|
+
const bar2 = calculateTaskBar(startDate, startDate, monthStart, dayWidth);
|
|
679
|
+
const bounds = calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
|
|
680
|
+
return {
|
|
681
|
+
...bounds,
|
|
682
|
+
centerX: Math.round(bar2.left + dayWidth / 2)
|
|
683
|
+
};
|
|
663
684
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
linkType,
|
|
671
|
-
businessOffset - (successorType === "milestone" ? 0 : 1),
|
|
672
|
-
predStart,
|
|
673
|
-
predEnd,
|
|
674
|
-
businessDays,
|
|
675
|
-
weekendPredicate,
|
|
676
|
-
successorType
|
|
677
|
-
);
|
|
678
|
-
case "SS":
|
|
679
|
-
return businessOffset;
|
|
680
|
-
case "FF":
|
|
681
|
-
return businessOffset;
|
|
682
|
-
case "SF":
|
|
683
|
-
return businessOffset + 1;
|
|
685
|
+
if (override) {
|
|
686
|
+
return {
|
|
687
|
+
left: override.left,
|
|
688
|
+
right: override.left + override.width,
|
|
689
|
+
centerX: Math.round(override.left + override.width / 2)
|
|
690
|
+
};
|
|
684
691
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
692
|
+
const bar = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
|
|
693
|
+
return {
|
|
694
|
+
left: bar.left,
|
|
695
|
+
right: bar.left + bar.width,
|
|
696
|
+
centerX: Math.round(bar.left + bar.width / 2)
|
|
697
|
+
};
|
|
698
|
+
};
|
|
699
|
+
var pixelsToDate = (pixels, monthStart, dayWidth) => {
|
|
700
|
+
const days = Math.round(pixels / dayWidth);
|
|
701
|
+
return new Date(Date.UTC(
|
|
702
|
+
monthStart.getUTCFullYear(),
|
|
703
|
+
monthStart.getUTCMonth(),
|
|
704
|
+
monthStart.getUTCDate() + days
|
|
705
|
+
));
|
|
706
|
+
};
|
|
707
|
+
var calculateGridWidth = (daysInMonth, dayWidth) => {
|
|
708
|
+
return Math.round(daysInMonth * dayWidth);
|
|
709
|
+
};
|
|
710
|
+
var MIN_EDGE_ZONE_PX = 8;
|
|
711
|
+
var MIN_MOVE_ZONE_PX = 16;
|
|
712
|
+
var detectEdgeZone = (clientX, taskBarElement, edgeZoneWidth = 12) => {
|
|
713
|
+
const rect = taskBarElement.getBoundingClientRect();
|
|
714
|
+
const relativeX = Math.round(clientX - rect.left);
|
|
715
|
+
const width = Math.round(rect.width);
|
|
716
|
+
const effectiveZone = Math.max(
|
|
717
|
+
MIN_EDGE_ZONE_PX,
|
|
718
|
+
Math.min(edgeZoneWidth, (width - MIN_MOVE_ZONE_PX) / 2)
|
|
695
719
|
);
|
|
696
|
-
if (
|
|
697
|
-
|
|
698
|
-
case "FS":
|
|
699
|
-
return new Date(predecessorEnd.getTime() + (normalizedLag + (successorType === "milestone" ? 0 : 1)) * DAY_MS);
|
|
700
|
-
case "SS":
|
|
701
|
-
return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
|
|
702
|
-
case "FF":
|
|
703
|
-
return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
|
|
704
|
-
case "SF":
|
|
705
|
-
return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
|
|
709
|
-
let offset;
|
|
710
|
-
switch (linkType) {
|
|
711
|
-
case "FS":
|
|
712
|
-
offset = normalizedLag + (successorType === "milestone" ? 0 : 1);
|
|
713
|
-
break;
|
|
714
|
-
case "SS":
|
|
715
|
-
offset = normalizedLag;
|
|
716
|
-
break;
|
|
717
|
-
case "FF":
|
|
718
|
-
offset = normalizedLag;
|
|
719
|
-
break;
|
|
720
|
-
case "SF":
|
|
721
|
-
offset = normalizedLag - 1;
|
|
722
|
-
break;
|
|
720
|
+
if (relativeX >= 0 && relativeX <= effectiveZone) {
|
|
721
|
+
return "left";
|
|
723
722
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
// src/core/scheduling/criticalPath.ts
|
|
728
|
-
function computeCriticalPath(tasks, options = {}) {
|
|
729
|
-
const { businessDays = false, weekendPredicate } = options;
|
|
730
|
-
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
731
|
-
const childIds = /* @__PURE__ */ new Set();
|
|
732
|
-
for (const task of tasks) {
|
|
733
|
-
if (task.parentId) childIds.add(task.parentId);
|
|
723
|
+
if (relativeX >= width - effectiveZone && relativeX <= width) {
|
|
724
|
+
return "right";
|
|
734
725
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
726
|
+
return "move";
|
|
727
|
+
};
|
|
728
|
+
var getCursorForPosition = (position) => {
|
|
729
|
+
switch (position) {
|
|
730
|
+
case "left":
|
|
731
|
+
case "right":
|
|
732
|
+
return "ew-resize";
|
|
733
|
+
case "move":
|
|
734
|
+
return "grab";
|
|
735
|
+
default:
|
|
736
|
+
return "default";
|
|
746
737
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
return Math.round((targetDate.getTime() - projectStartDate.getTime()) / DAY_MS);
|
|
758
|
-
};
|
|
759
|
-
const scheduledStart = /* @__PURE__ */ new Map();
|
|
760
|
-
for (const leaf of leafTasks) {
|
|
761
|
-
scheduledStart.set(leaf.id, offsetFromProjectStart(leaf.startDate));
|
|
738
|
+
};
|
|
739
|
+
var calculateGridLines = (dateRange, dayWidth) => {
|
|
740
|
+
const lines = [];
|
|
741
|
+
for (let i = 0; i < dateRange.length; i++) {
|
|
742
|
+
const date = dateRange[i];
|
|
743
|
+
const x = Math.round(i * dayWidth);
|
|
744
|
+
const isMonthStart = i === 0 ? false : date.getUTCDate() === 1;
|
|
745
|
+
const isWeekStart = date.getUTCDay() === 1;
|
|
746
|
+
lines.push({ x, isMonthStart, isWeekStart });
|
|
762
747
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
seenEdges.add(edgeKey);
|
|
770
|
-
const outEdges = succByPred.get(predecessorId) ?? [];
|
|
771
|
-
outEdges.push({ succId, lag, type });
|
|
772
|
-
succByPred.set(predecessorId, outEdges);
|
|
773
|
-
const inEdges = predBySucc.get(succId) ?? [];
|
|
774
|
-
inEdges.push({ predId: predecessorId, lag, type });
|
|
775
|
-
predBySucc.set(succId, inEdges);
|
|
776
|
-
};
|
|
777
|
-
for (const succ of leafTasks) {
|
|
778
|
-
for (const dep of succ.dependencies ?? []) {
|
|
779
|
-
const lag = getDependencyLag(dep);
|
|
780
|
-
if (leafIds.has(dep.taskId)) {
|
|
781
|
-
addEdge(dep.taskId, succ.id, lag, dep.type);
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
const descendants = leafIdsByAncestor.get(dep.taskId);
|
|
785
|
-
if (!descendants || descendants.size === 0) continue;
|
|
786
|
-
for (const leafId of descendants) {
|
|
787
|
-
if (leafId === succ.id) continue;
|
|
788
|
-
addEdge(leafId, succ.id, lag, dep.type);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
748
|
+
if (dateRange.length > 0) {
|
|
749
|
+
lines.push({
|
|
750
|
+
x: Math.round(dateRange.length * dayWidth),
|
|
751
|
+
isMonthStart: false,
|
|
752
|
+
isWeekStart: false
|
|
753
|
+
});
|
|
791
754
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
755
|
+
return lines;
|
|
756
|
+
};
|
|
757
|
+
var calculateWeekendBlocks = (dateRange, dayWidth, isCustomWeekend) => {
|
|
758
|
+
const blocks = [];
|
|
759
|
+
let inWeekend = false;
|
|
760
|
+
let weekendStartIndex = -1;
|
|
761
|
+
for (let i = 0; i < dateRange.length; i++) {
|
|
762
|
+
const date = dateRange[i];
|
|
763
|
+
const isWeekend3 = isCustomWeekend ? isCustomWeekend(date) : date.getUTCDay() === 0 || date.getUTCDay() === 6;
|
|
764
|
+
if (isWeekend3 && !inWeekend) {
|
|
765
|
+
inWeekend = true;
|
|
766
|
+
weekendStartIndex = i;
|
|
767
|
+
} else if (!isWeekend3 && inWeekend) {
|
|
768
|
+
inWeekend = false;
|
|
769
|
+
const left = Math.round(weekendStartIndex * dayWidth);
|
|
770
|
+
const width = Math.round((i - weekendStartIndex) * dayWidth);
|
|
771
|
+
blocks.push({ left, width });
|
|
797
772
|
}
|
|
798
773
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
while (queue.length > 0) {
|
|
804
|
-
const current = queue.shift();
|
|
805
|
-
order.push(current);
|
|
806
|
-
for (const { succId } of succByPred.get(current) ?? []) {
|
|
807
|
-
const next = (indegree.get(succId) ?? 0) - 1;
|
|
808
|
-
indegree.set(succId, next);
|
|
809
|
-
if (next === 0 && !enqueued.has(succId)) {
|
|
810
|
-
enqueued.add(succId);
|
|
811
|
-
queue.push(succId);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
774
|
+
if (inWeekend && weekendStartIndex >= 0) {
|
|
775
|
+
const left = Math.round(weekendStartIndex * dayWidth);
|
|
776
|
+
const width = Math.round((dateRange.length - weekendStartIndex) * dayWidth);
|
|
777
|
+
blocks.push({ left, width });
|
|
814
778
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
);
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
779
|
+
return blocks;
|
|
780
|
+
};
|
|
781
|
+
var calculateBezierPath = (from, to) => {
|
|
782
|
+
const verticalDistance = Math.abs(to.y - from.y);
|
|
783
|
+
const cpOffset = Math.max(verticalDistance * 0.5, 20);
|
|
784
|
+
if (from.y === to.y) {
|
|
785
|
+
const arcHeight = 20;
|
|
786
|
+
const midX = (from.x + to.x) / 2;
|
|
787
|
+
return `M ${from.x} ${from.y} Q ${midX} ${from.y - arcHeight} ${to.x} ${to.y}`;
|
|
788
|
+
}
|
|
789
|
+
const cp1x = from.x;
|
|
790
|
+
const cp1y = from.y + (to.y > from.y ? cpOffset : -cpOffset);
|
|
791
|
+
const cp2x = to.x;
|
|
792
|
+
const cp2y = to.y - (to.y > from.y ? cpOffset : -cpOffset);
|
|
793
|
+
return `M ${Math.round(from.x)} ${Math.round(from.y)} C ${Math.round(cp1x)} ${Math.round(cp1y)}, ${Math.round(cp2x)} ${Math.round(cp2y)}, ${Math.round(to.x)} ${Math.round(to.y)}`;
|
|
794
|
+
};
|
|
795
|
+
var calculateDependencyPath = (from, to, arrivesFromRight) => {
|
|
796
|
+
const fx = Math.round(from.x);
|
|
797
|
+
const fy = Math.round(from.y);
|
|
798
|
+
const tx = Math.round(to.x);
|
|
799
|
+
const ty = Math.round(to.y);
|
|
800
|
+
if (fy === ty) {
|
|
801
|
+
return `M ${fx} ${fy} H ${tx}`;
|
|
802
|
+
}
|
|
803
|
+
if (fx === tx) {
|
|
804
|
+
return `M ${fx} ${fy} V ${ty}`;
|
|
805
|
+
}
|
|
806
|
+
const C = 2;
|
|
807
|
+
const goingDown = ty > fy;
|
|
808
|
+
const dirY = goingDown ? 1 : -1;
|
|
809
|
+
if (arrivesFromRight) {
|
|
810
|
+
const goingRight = tx >= fx;
|
|
811
|
+
const dirX = goingRight ? 1 : -1;
|
|
812
|
+
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
813
|
+
return [
|
|
814
|
+
`M ${fx} ${fy}`,
|
|
815
|
+
`H ${tx - dirX * C}`,
|
|
816
|
+
`L ${tx} ${fy + dirY * C}`,
|
|
817
|
+
`V ${ty}`
|
|
818
|
+
].join(" ");
|
|
846
819
|
}
|
|
847
|
-
|
|
848
|
-
|
|
820
|
+
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
821
|
+
} else {
|
|
822
|
+
const goingRight = tx >= fx;
|
|
823
|
+
const dirX = goingRight ? 1 : -1;
|
|
824
|
+
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
825
|
+
return [
|
|
826
|
+
`M ${fx} ${fy}`,
|
|
827
|
+
`H ${tx - dirX * C}`,
|
|
828
|
+
`L ${tx} ${fy + dirY * C}`,
|
|
829
|
+
`V ${ty}`
|
|
830
|
+
].join(" ");
|
|
831
|
+
}
|
|
832
|
+
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
849
833
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
834
|
+
};
|
|
835
|
+
var calculateWeekGridLines = (dateRange, dayWidth) => {
|
|
836
|
+
const blocks = getWeekBlocks(dateRange);
|
|
837
|
+
const lines = [];
|
|
838
|
+
let currentDayIndex = 0;
|
|
839
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
840
|
+
const block = blocks[i];
|
|
841
|
+
currentDayIndex += block.days;
|
|
842
|
+
const blockEndMonth = dateRange[currentDayIndex - 1];
|
|
843
|
+
const nextBlockMonth = currentDayIndex < dateRange.length ? dateRange[currentDayIndex] : null;
|
|
844
|
+
const isMonthBoundary = nextBlockMonth && blockEndMonth.getUTCMonth() !== nextBlockMonth.getUTCMonth();
|
|
845
|
+
lines.push({
|
|
846
|
+
x: Math.round(currentDayIndex * dayWidth),
|
|
847
|
+
isMonthStart: isMonthBoundary || false
|
|
848
|
+
});
|
|
859
849
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
function extendCriticalIdsToParents(criticalLeafIds, tasks) {
|
|
863
|
-
const result = new Set(criticalLeafIds);
|
|
864
|
-
const parentByChild = /* @__PURE__ */ new Map();
|
|
865
|
-
for (const task of tasks) {
|
|
866
|
-
if (task.parentId) parentByChild.set(task.id, task.parentId);
|
|
850
|
+
if (lines.length > 0 && lines[lines.length - 1].x === Math.round(dateRange.length * dayWidth)) {
|
|
851
|
+
lines.pop();
|
|
867
852
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
853
|
+
return lines;
|
|
854
|
+
};
|
|
855
|
+
var calculateMonthGridLines = (dateRange, dayWidth) => {
|
|
856
|
+
const lines = [];
|
|
857
|
+
for (let i = 1; i < dateRange.length; i++) {
|
|
858
|
+
const date = dateRange[i];
|
|
859
|
+
if (date.getUTCDate() === 1) {
|
|
860
|
+
const x = Math.round(i * dayWidth);
|
|
861
|
+
const isYearBoundary = date.getUTCMonth() === 0;
|
|
862
|
+
lines.push({ x, isMonthStart: isYearBoundary });
|
|
875
863
|
}
|
|
876
864
|
}
|
|
877
|
-
return
|
|
878
|
-
}
|
|
865
|
+
return lines;
|
|
866
|
+
};
|
|
867
|
+
var calculateOrthogonalPath = (from, to) => {
|
|
868
|
+
const fx = Math.round(from.x);
|
|
869
|
+
const fy = Math.round(from.y);
|
|
870
|
+
const tx = Math.round(to.x);
|
|
871
|
+
const ty = Math.round(to.y);
|
|
872
|
+
if (fy === ty) {
|
|
873
|
+
return `M ${fx} ${fy} H ${tx}`;
|
|
874
|
+
}
|
|
875
|
+
const C = 2;
|
|
876
|
+
const goingDown = ty > fy;
|
|
877
|
+
const goingRight = tx >= fx;
|
|
878
|
+
const dirY = goingDown ? 1 : -1;
|
|
879
|
+
const dirX = goingRight ? 1 : -1;
|
|
880
|
+
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
881
|
+
return [
|
|
882
|
+
`M ${fx} ${fy}`,
|
|
883
|
+
`H ${tx - dirX * C}`,
|
|
884
|
+
`L ${tx} ${fy + dirY * C}`,
|
|
885
|
+
`V ${ty}`
|
|
886
|
+
].join(" ");
|
|
887
|
+
}
|
|
888
|
+
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
889
|
+
};
|
|
879
890
|
|
|
880
|
-
// src/
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
891
|
+
// src/utils/activities.ts
|
|
892
|
+
var ACTIVITY_LANE_STEP = 28;
|
|
893
|
+
var ACTIVITY_LANE_BAR_HEIGHT = 24;
|
|
894
|
+
var ACTIVITY_LANES_PADDING = 2;
|
|
895
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
896
|
+
function packIntervals(intervals) {
|
|
897
|
+
const sorted = [...intervals].sort((a, b) => a.start - b.start);
|
|
898
|
+
const laneEnds = [];
|
|
899
|
+
return sorted.map((item) => {
|
|
900
|
+
let lane = 0;
|
|
901
|
+
while (lane < laneEnds.length && item.start < laneEnds[lane]) {
|
|
902
|
+
lane += 1;
|
|
903
|
+
}
|
|
904
|
+
laneEnds[lane] = item.end;
|
|
905
|
+
return { ...item, lane };
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
function computeActivityLanes(activities, monthStart, dayWidth) {
|
|
909
|
+
if (activities.length === 0) {
|
|
910
|
+
return { segments: [], laneCount: 0 };
|
|
911
|
+
}
|
|
912
|
+
const packed = packIntervals(activities.map((activity) => {
|
|
913
|
+
const bar = calculateTaskBar(parseUTCDate(activity.startDate), parseUTCDate(activity.endDate), monthStart, dayWidth);
|
|
884
914
|
return {
|
|
885
|
-
|
|
886
|
-
|
|
915
|
+
id: activity.id,
|
|
916
|
+
name: activity.name,
|
|
917
|
+
color: activity.color,
|
|
918
|
+
lag: activity.lag,
|
|
919
|
+
start: bar.left,
|
|
920
|
+
end: bar.left + bar.width,
|
|
921
|
+
left: bar.left,
|
|
922
|
+
width: bar.width
|
|
887
923
|
};
|
|
888
|
-
}
|
|
889
|
-
const DAY_MS6 = 24 * 60 * 60 * 1e3;
|
|
924
|
+
}));
|
|
890
925
|
return {
|
|
891
|
-
|
|
892
|
-
|
|
926
|
+
segments: packed.map(({ id, name, color, lag, lane, left, width }) => ({ id, name, color, lag, lane, left, width })),
|
|
927
|
+
laneCount: Math.max(...packed.map((item) => item.lane + 1))
|
|
893
928
|
};
|
|
894
929
|
}
|
|
895
|
-
function
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
end: normalizedEnd
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
const DAY_MS6 = 24 * 60 * 60 * 1e3;
|
|
930
|
+
function activityLanesExtraHeight(laneCount) {
|
|
931
|
+
if (laneCount <= 1) return 0;
|
|
932
|
+
return laneCount * ACTIVITY_LANE_STEP + ACTIVITY_LANES_PADDING * 2;
|
|
933
|
+
}
|
|
934
|
+
function activitySpanMs(activity) {
|
|
904
935
|
return {
|
|
905
|
-
start:
|
|
906
|
-
end:
|
|
936
|
+
start: parseUTCDate(activity.startDate).getTime(),
|
|
937
|
+
end: parseUTCDate(activity.endDate).getTime() + DAY_MS2
|
|
907
938
|
};
|
|
908
939
|
}
|
|
909
|
-
function
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
|
|
913
|
-
businessDays,
|
|
914
|
-
weekendPredicate,
|
|
915
|
-
snapDirection
|
|
916
|
-
);
|
|
940
|
+
function shiftActivityDate(value, days) {
|
|
941
|
+
const shifted = new Date(parseUTCDate(value).getTime() + days * DAY_MS2);
|
|
942
|
+
return typeof value === "string" ? shifted.toISOString().slice(0, 10) : shifted;
|
|
917
943
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
944
|
+
var CHAIN_KEY_SEPARATOR = "\0";
|
|
945
|
+
var chainKey = (taskId, activityId) => `${taskId}${CHAIN_KEY_SEPARATOR}${activityId}`;
|
|
946
|
+
var toDayNumber = (value) => Math.round(parseUTCDate(value).getTime() / DAY_MS2);
|
|
947
|
+
function buildChainGraph(orderedTasks) {
|
|
948
|
+
const successors = /* @__PURE__ */ new Map();
|
|
949
|
+
const predecessors = /* @__PURE__ */ new Map();
|
|
950
|
+
const info = /* @__PURE__ */ new Map();
|
|
951
|
+
const link = (from, to, lag) => {
|
|
952
|
+
const list = successors.get(from);
|
|
953
|
+
if (list) {
|
|
954
|
+
if (!list.some((edge) => edge.key === to)) list.push({ key: to, lag });
|
|
955
|
+
} else {
|
|
956
|
+
successors.set(from, [{ key: to, lag }]);
|
|
926
957
|
}
|
|
927
|
-
const
|
|
928
|
-
if (
|
|
929
|
-
|
|
958
|
+
const back = predecessors.get(to);
|
|
959
|
+
if (back) {
|
|
960
|
+
if (!back.some((edge) => edge.key === from)) back.push({ key: from, lag });
|
|
961
|
+
} else {
|
|
962
|
+
predecessors.set(to, [{ key: from, lag }]);
|
|
930
963
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
task.
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
964
|
+
};
|
|
965
|
+
const flagged = orderedTasks.filter((task) => Boolean(task.activityChain) && Array.isArray(task.activities) && task.activities.length > 0);
|
|
966
|
+
flagged.forEach((task, floorIdx) => {
|
|
967
|
+
task.activities.forEach((activity, workIdx) => {
|
|
968
|
+
info.set(chainKey(task.id, activity.id), {
|
|
969
|
+
taskId: task.id,
|
|
970
|
+
activityId: activity.id,
|
|
971
|
+
startDay: toDayNumber(activity.startDate),
|
|
972
|
+
durationDays: Math.max(1, toDayNumber(activity.endDate) - toDayNumber(activity.startDate) + 1),
|
|
973
|
+
floorIdx,
|
|
974
|
+
workIdx
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
for (let i = 0; i < task.activities.length - 1; i += 1) {
|
|
978
|
+
link(chainKey(task.id, task.activities[i].id), chainKey(task.id, task.activities[i + 1].id), task.activities[i + 1].lag ?? 0);
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
for (let f = 0; f < flagged.length - 1; f += 1) {
|
|
982
|
+
const current = flagged[f];
|
|
983
|
+
const next = flagged[f + 1];
|
|
984
|
+
for (const activity of current.activities) {
|
|
985
|
+
const twin = next.activities.find((candidate) => candidate.id === activity.id || candidate.name === activity.name);
|
|
986
|
+
if (twin) link(chainKey(current.id, activity.id), chainKey(next.id, twin.id), 0);
|
|
949
987
|
}
|
|
950
988
|
}
|
|
951
|
-
|
|
952
|
-
|
|
989
|
+
return { successors, predecessors, info };
|
|
990
|
+
}
|
|
991
|
+
function collectDownstream(graph, startKey) {
|
|
992
|
+
const visited = /* @__PURE__ */ new Set();
|
|
993
|
+
const queue = [...(graph.successors.get(startKey) ?? []).map((edge) => edge.key)];
|
|
994
|
+
while (queue.length > 0) {
|
|
995
|
+
const key = queue.shift();
|
|
996
|
+
if (visited.has(key)) continue;
|
|
997
|
+
visited.add(key);
|
|
998
|
+
queue.push(...(graph.successors.get(key) ?? []).map((edge) => edge.key));
|
|
953
999
|
}
|
|
954
|
-
return
|
|
955
|
-
minAllowedStart,
|
|
956
|
-
getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
|
|
957
|
-
businessDays,
|
|
958
|
-
weekendPredicate
|
|
959
|
-
);
|
|
1000
|
+
return visited;
|
|
960
1001
|
}
|
|
961
|
-
function
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const
|
|
965
|
-
if (!
|
|
966
|
-
|
|
1002
|
+
function shiftsFromDeltas(orderedTasks, deltasByTask) {
|
|
1003
|
+
const shifts = [];
|
|
1004
|
+
for (const task of orderedTasks) {
|
|
1005
|
+
const deltas = deltasByTask.get(task.id);
|
|
1006
|
+
if (!deltas || deltas.size === 0) continue;
|
|
1007
|
+
shifts.push({
|
|
1008
|
+
taskId: task.id,
|
|
1009
|
+
shiftedIds: new Set(deltas.keys()),
|
|
1010
|
+
deltas,
|
|
1011
|
+
activities: (task.activities ?? []).map((activity) => deltas.has(activity.id) ? {
|
|
1012
|
+
...activity,
|
|
1013
|
+
startDate: shiftActivityDate(activity.startDate, deltas.get(activity.id)),
|
|
1014
|
+
endDate: shiftActivityDate(activity.endDate, deltas.get(activity.id))
|
|
1015
|
+
} : activity)
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
return shifts;
|
|
1019
|
+
}
|
|
1020
|
+
function shiftActivityChain(orderedTasks, draggedTaskId, draggedActivityId, deltaDays) {
|
|
1021
|
+
if (deltaDays === 0) return [];
|
|
1022
|
+
const graph = buildChainGraph(orderedTasks);
|
|
1023
|
+
const shifted = collectDownstream(graph, chainKey(draggedTaskId, draggedActivityId));
|
|
1024
|
+
if (shifted.size === 0) return [];
|
|
1025
|
+
const deltasByTask = /* @__PURE__ */ new Map();
|
|
1026
|
+
for (const key of shifted) {
|
|
1027
|
+
const node = graph.info.get(key);
|
|
1028
|
+
const set = deltasByTask.get(node.taskId);
|
|
1029
|
+
if (set) set.set(node.activityId, deltaDays);
|
|
1030
|
+
else deltasByTask.set(node.taskId, /* @__PURE__ */ new Map([[node.activityId, deltaDays]]));
|
|
1031
|
+
}
|
|
1032
|
+
return shiftsFromDeltas(orderedTasks, deltasByTask);
|
|
1033
|
+
}
|
|
1034
|
+
function activityStartConstraint(orderedTasks, taskId, activityId) {
|
|
1035
|
+
const graph = buildChainGraph(orderedTasks);
|
|
1036
|
+
const key = chainKey(taskId, activityId);
|
|
1037
|
+
const node = graph.info.get(key);
|
|
1038
|
+
if (!node) return null;
|
|
1039
|
+
const candidates = [];
|
|
1040
|
+
let minStart = Number.NEGATIVE_INFINITY;
|
|
1041
|
+
for (const pred of graph.predecessors.get(key) ?? []) {
|
|
1042
|
+
const predNode = graph.info.get(pred.key);
|
|
1043
|
+
if (!predNode) continue;
|
|
1044
|
+
const min = predNode.startDay + predNode.durationDays + pred.lag;
|
|
1045
|
+
candidates.push({ taskId: predNode.taskId, activityId: predNode.activityId, min, lag: pred.lag });
|
|
1046
|
+
if (min > minStart) minStart = min;
|
|
1047
|
+
}
|
|
1048
|
+
if (!Number.isFinite(minStart)) return null;
|
|
1049
|
+
const blockers = candidates.filter((candidate) => candidate.min === minStart);
|
|
1050
|
+
return {
|
|
1051
|
+
minStartDay: minStart,
|
|
1052
|
+
minStartDelta: minStart - node.startDay,
|
|
1053
|
+
blockers: blockers.map(({ taskId: blockerTaskId, activityId: blockerActivityId }) => ({
|
|
1054
|
+
taskId: blockerTaskId,
|
|
1055
|
+
activityId: blockerActivityId
|
|
1056
|
+
})),
|
|
1057
|
+
lag: Math.max(0, ...blockers.map((blocker) => blocker.lag))
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function activityChainMinStartDelta(orderedTasks, taskId, activityId) {
|
|
1061
|
+
return activityStartConstraint(orderedTasks, taskId, activityId)?.minStartDelta ?? null;
|
|
1062
|
+
}
|
|
1063
|
+
function pushActivityChain(orderedTasks, draggedTaskId, draggedActivityId, requestedDeltaDays) {
|
|
1064
|
+
const graph = buildChainGraph(orderedTasks);
|
|
1065
|
+
const draggedKey = chainKey(draggedTaskId, draggedActivityId);
|
|
1066
|
+
if (!graph.info.has(draggedKey)) {
|
|
1067
|
+
return { shifts: [], draggedDeltaDays: requestedDeltaDays };
|
|
1068
|
+
}
|
|
1069
|
+
const downstream = collectDownstream(graph, draggedKey);
|
|
1070
|
+
if (downstream.size === 0) {
|
|
1071
|
+
return { shifts: [], draggedDeltaDays: requestedDeltaDays };
|
|
1072
|
+
}
|
|
1073
|
+
const newStart = /* @__PURE__ */ new Map();
|
|
1074
|
+
const dragged = graph.info.get(draggedKey);
|
|
1075
|
+
newStart.set(draggedKey, dragged.startDay + requestedDeltaDays);
|
|
1076
|
+
const endOfDay = (key) => {
|
|
1077
|
+
const node = graph.info.get(key);
|
|
1078
|
+
return (newStart.get(key) ?? node.startDay) + node.durationDays;
|
|
1079
|
+
};
|
|
1080
|
+
const order = [...downstream].map((key) => ({ key, node: graph.info.get(key) })).sort((a, b) => a.node.workIdx - b.node.workIdx || a.node.floorIdx - b.node.floorIdx);
|
|
1081
|
+
for (const { key, node } of order) {
|
|
1082
|
+
let earliest = Number.NEGATIVE_INFINITY;
|
|
1083
|
+
let oldEarliest = Number.NEGATIVE_INFINITY;
|
|
1084
|
+
for (const pred of graph.predecessors.get(key) ?? []) {
|
|
1085
|
+
const predNode = graph.info.get(pred.key);
|
|
1086
|
+
earliest = Math.max(earliest, endOfDay(pred.key) + pred.lag);
|
|
1087
|
+
oldEarliest = Math.max(oldEarliest, predNode.startDay + predNode.durationDays + pred.lag);
|
|
1088
|
+
}
|
|
1089
|
+
let nextStart;
|
|
1090
|
+
if (earliest > node.startDay) {
|
|
1091
|
+
nextStart = earliest;
|
|
1092
|
+
} else if (earliest < oldEarliest) {
|
|
1093
|
+
nextStart = earliest;
|
|
1094
|
+
} else {
|
|
1095
|
+
nextStart = node.startDay;
|
|
967
1096
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
task.type
|
|
981
|
-
);
|
|
982
|
-
return { ...dep, lag: nextLag };
|
|
983
|
-
});
|
|
1097
|
+
newStart.set(key, nextStart);
|
|
1098
|
+
}
|
|
1099
|
+
const deltasByTask = /* @__PURE__ */ new Map();
|
|
1100
|
+
for (const key of downstream) {
|
|
1101
|
+
const node = graph.info.get(key);
|
|
1102
|
+
const delta = (newStart.get(key) ?? node.startDay) - node.startDay;
|
|
1103
|
+
if (delta === 0) continue;
|
|
1104
|
+
const set = deltasByTask.get(node.taskId);
|
|
1105
|
+
if (set) set.set(node.activityId, delta);
|
|
1106
|
+
else deltasByTask.set(node.taskId, /* @__PURE__ */ new Map([[node.activityId, delta]]));
|
|
1107
|
+
}
|
|
1108
|
+
return { shifts: shiftsFromDeltas(orderedTasks, deltasByTask), draggedDeltaDays: requestedDeltaDays };
|
|
984
1109
|
}
|
|
985
1110
|
|
|
986
|
-
// src/core/scheduling/
|
|
987
|
-
function
|
|
988
|
-
|
|
989
|
-
|
|
1111
|
+
// src/core/scheduling/dependencies.ts
|
|
1112
|
+
function normalizePredecessorDates(predecessor, parseDateFn) {
|
|
1113
|
+
const predStart = parseDateFn(predecessor.startDate);
|
|
1114
|
+
const predEnd = predecessor.type === "milestone" ? predStart : parseDateFn(predecessor.endDate);
|
|
1115
|
+
return { predStart, predEnd };
|
|
1116
|
+
}
|
|
1117
|
+
function getDependencyLag(dep) {
|
|
1118
|
+
return Number.isFinite(dep.lag) ? dep.lag : 0;
|
|
1119
|
+
}
|
|
1120
|
+
function normalizeTaskDependencyLags(task) {
|
|
1121
|
+
return task;
|
|
1122
|
+
}
|
|
1123
|
+
function normalizeDependencyLag(linkType, lag, predecessorStart, predecessorEnd, businessDays = false, weekendPredicate, successorType) {
|
|
1124
|
+
if (linkType !== "FS") {
|
|
1125
|
+
return lag;
|
|
990
1126
|
}
|
|
991
|
-
|
|
1127
|
+
const predecessorDuration = getTaskDuration(
|
|
1128
|
+
predecessorStart,
|
|
1129
|
+
predecessorEnd,
|
|
1130
|
+
businessDays,
|
|
1131
|
+
weekendPredicate
|
|
1132
|
+
);
|
|
1133
|
+
const minimumLag = successorType === "milestone" ? -Math.max(0, predecessorDuration - 1) : -predecessorDuration;
|
|
1134
|
+
return Math.max(minimumLag, lag);
|
|
992
1135
|
}
|
|
993
|
-
function
|
|
994
|
-
const
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1136
|
+
function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate, successorType) {
|
|
1137
|
+
const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
|
|
1138
|
+
const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
|
|
1139
|
+
const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
|
|
1140
|
+
const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
|
|
1141
|
+
if (!businessDays || !weekendPredicate) {
|
|
1142
|
+
switch (linkType) {
|
|
1143
|
+
case "FS":
|
|
1144
|
+
return normalizeDependencyLag(
|
|
1145
|
+
linkType,
|
|
1146
|
+
Math.round((sS - pE) / DAY_MS) - (successorType === "milestone" ? 0 : 1),
|
|
1147
|
+
predStart,
|
|
1148
|
+
predEnd,
|
|
1149
|
+
businessDays,
|
|
1150
|
+
weekendPredicate,
|
|
1151
|
+
successorType
|
|
1152
|
+
);
|
|
1153
|
+
case "SS":
|
|
1154
|
+
return Math.round((sS - pS) / DAY_MS);
|
|
1155
|
+
case "FF":
|
|
1156
|
+
return Math.round((sE - pE) / DAY_MS);
|
|
1157
|
+
case "SF":
|
|
1158
|
+
return Math.round((sE - pS) / DAY_MS) + 1;
|
|
1003
1159
|
}
|
|
1004
|
-
if (!task.dependencies) continue;
|
|
1005
|
-
task.dependencies.forEach((dependency, dependencyIndex) => {
|
|
1006
|
-
const dependents = dependentsByPredecessorId.get(dependency.taskId) ?? [];
|
|
1007
|
-
dependents.push({ task, dependencyIndex });
|
|
1008
|
-
dependentsByPredecessorId.set(dependency.taskId, dependents);
|
|
1009
|
-
});
|
|
1010
1160
|
}
|
|
1011
|
-
|
|
1161
|
+
const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
|
|
1162
|
+
const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
|
|
1163
|
+
const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
|
|
1164
|
+
switch (linkType) {
|
|
1165
|
+
case "FS":
|
|
1166
|
+
return normalizeDependencyLag(
|
|
1167
|
+
linkType,
|
|
1168
|
+
businessOffset - (successorType === "milestone" ? 0 : 1),
|
|
1169
|
+
predStart,
|
|
1170
|
+
predEnd,
|
|
1171
|
+
businessDays,
|
|
1172
|
+
weekendPredicate,
|
|
1173
|
+
successorType
|
|
1174
|
+
);
|
|
1175
|
+
case "SS":
|
|
1176
|
+
return businessOffset;
|
|
1177
|
+
case "FF":
|
|
1178
|
+
return businessOffset;
|
|
1179
|
+
case "SF":
|
|
1180
|
+
return businessOffset + 1;
|
|
1181
|
+
}
|
|
1012
1182
|
}
|
|
1013
|
-
function
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1183
|
+
function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate, successorType) {
|
|
1184
|
+
const normalizedLag = normalizeDependencyLag(
|
|
1185
|
+
linkType,
|
|
1186
|
+
lag,
|
|
1187
|
+
predecessorStart,
|
|
1188
|
+
predecessorEnd,
|
|
1189
|
+
businessDays,
|
|
1190
|
+
weekendPredicate,
|
|
1191
|
+
successorType
|
|
1192
|
+
);
|
|
1193
|
+
if (!businessDays || !weekendPredicate) {
|
|
1194
|
+
switch (linkType) {
|
|
1195
|
+
case "FS":
|
|
1196
|
+
return new Date(predecessorEnd.getTime() + (normalizedLag + (successorType === "milestone" ? 0 : 1)) * DAY_MS);
|
|
1197
|
+
case "SS":
|
|
1198
|
+
return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
|
|
1199
|
+
case "FF":
|
|
1200
|
+
return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
|
|
1201
|
+
case "SF":
|
|
1202
|
+
return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
|
|
1035
1203
|
}
|
|
1036
1204
|
}
|
|
1037
|
-
|
|
1205
|
+
const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
|
|
1206
|
+
let offset;
|
|
1207
|
+
switch (linkType) {
|
|
1208
|
+
case "FS":
|
|
1209
|
+
offset = normalizedLag + (successorType === "milestone" ? 0 : 1);
|
|
1210
|
+
break;
|
|
1211
|
+
case "SS":
|
|
1212
|
+
offset = normalizedLag;
|
|
1213
|
+
break;
|
|
1214
|
+
case "FF":
|
|
1215
|
+
offset = normalizedLag;
|
|
1216
|
+
break;
|
|
1217
|
+
case "SF":
|
|
1218
|
+
offset = normalizedLag - 1;
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
1038
1222
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const
|
|
1044
|
-
const
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const parentOrigEnd = new Date(parentOrig.endDate);
|
|
1059
|
-
const parentStartDelta = predStart.getTime() - parentOrigStart.getTime();
|
|
1060
|
-
const parentEndDelta = predEnd.getTime() - parentOrigEnd.getTime();
|
|
1061
|
-
const newChildStart = new Date(origStart.getTime() + parentStartDelta);
|
|
1062
|
-
const newChildEnd = new Date(origEnd.getTime() + parentEndDelta);
|
|
1063
|
-
visited.add(child.id);
|
|
1064
|
-
updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
|
|
1065
|
-
result.push(normalizeTaskDependencyLags({
|
|
1066
|
-
...child,
|
|
1067
|
-
startDate: newChildStart.toISOString().split("T")[0],
|
|
1068
|
-
endDate: newChildEnd.toISOString().split("T")[0]
|
|
1069
|
-
}));
|
|
1070
|
-
queue.push(child.id);
|
|
1071
|
-
}
|
|
1223
|
+
|
|
1224
|
+
// src/core/scheduling/criticalPath.ts
|
|
1225
|
+
function computeCriticalPath(tasks, options = {}) {
|
|
1226
|
+
const { businessDays = false, weekendPredicate } = options;
|
|
1227
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
1228
|
+
const childIds = /* @__PURE__ */ new Set();
|
|
1229
|
+
for (const task of tasks) {
|
|
1230
|
+
if (task.parentId) childIds.add(task.parentId);
|
|
1231
|
+
}
|
|
1232
|
+
const leafTasks = tasks.filter((task) => !childIds.has(task.id));
|
|
1233
|
+
const leafIds = new Set(leafTasks.map((task) => task.id));
|
|
1234
|
+
const leafIdsByAncestor = /* @__PURE__ */ new Map();
|
|
1235
|
+
for (const leaf of leafTasks) {
|
|
1236
|
+
let ancestor = leaf.parentId;
|
|
1237
|
+
while (ancestor) {
|
|
1238
|
+
const set = leafIdsByAncestor.get(ancestor) ?? /* @__PURE__ */ new Set();
|
|
1239
|
+
set.add(leaf.id);
|
|
1240
|
+
leafIdsByAncestor.set(ancestor, set);
|
|
1241
|
+
ancestor = taskById.get(ancestor)?.parentId;
|
|
1072
1242
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1243
|
+
}
|
|
1244
|
+
const durationOf = (task) => getTaskDuration(task.startDate, task.endDate, businessDays, weekendPredicate);
|
|
1245
|
+
const projectStartDate = leafTasks.reduce((earliest, task) => {
|
|
1246
|
+
const taskStart = parseDateOnly(task.startDate);
|
|
1247
|
+
return taskStart.getTime() < earliest.getTime() ? taskStart : earliest;
|
|
1248
|
+
}, parseDateOnly(leafTasks[0]?.startDate ?? /* @__PURE__ */ new Date()));
|
|
1249
|
+
const offsetFromProjectStart = (date) => {
|
|
1250
|
+
const targetDate = parseDateOnly(date);
|
|
1251
|
+
if (businessDays && weekendPredicate) {
|
|
1252
|
+
return getBusinessDayOffset(projectStartDate, targetDate, weekendPredicate);
|
|
1253
|
+
}
|
|
1254
|
+
return Math.round((targetDate.getTime() - projectStartDate.getTime()) / DAY_MS);
|
|
1255
|
+
};
|
|
1256
|
+
const scheduledStart = /* @__PURE__ */ new Map();
|
|
1257
|
+
for (const leaf of leafTasks) {
|
|
1258
|
+
scheduledStart.set(leaf.id, offsetFromProjectStart(leaf.startDate));
|
|
1259
|
+
}
|
|
1260
|
+
const succByPred = /* @__PURE__ */ new Map();
|
|
1261
|
+
const predBySucc = /* @__PURE__ */ new Map();
|
|
1262
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
1263
|
+
const addEdge = (predecessorId, succId, lag, type) => {
|
|
1264
|
+
const edgeKey = `${predecessorId}|${succId}|${type}`;
|
|
1265
|
+
if (seenEdges.has(edgeKey)) return;
|
|
1266
|
+
seenEdges.add(edgeKey);
|
|
1267
|
+
const outEdges = succByPred.get(predecessorId) ?? [];
|
|
1268
|
+
outEdges.push({ succId, lag, type });
|
|
1269
|
+
succByPred.set(predecessorId, outEdges);
|
|
1270
|
+
const inEdges = predBySucc.get(succId) ?? [];
|
|
1271
|
+
inEdges.push({ predId: predecessorId, lag, type });
|
|
1272
|
+
predBySucc.set(succId, inEdges);
|
|
1273
|
+
};
|
|
1274
|
+
for (const succ of leafTasks) {
|
|
1275
|
+
for (const dep of succ.dependencies ?? []) {
|
|
1276
|
+
const lag = getDependencyLag(dep);
|
|
1277
|
+
if (leafIds.has(dep.taskId)) {
|
|
1278
|
+
addEdge(dep.taskId, succ.id, lag, dep.type);
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
const descendants = leafIdsByAncestor.get(dep.taskId);
|
|
1282
|
+
if (!descendants || descendants.size === 0) continue;
|
|
1283
|
+
for (const leafId of descendants) {
|
|
1284
|
+
if (leafId === succ.id) continue;
|
|
1285
|
+
addEdge(leafId, succ.id, lag, dep.type);
|
|
1106
1286
|
}
|
|
1107
|
-
visited.add(task.id);
|
|
1108
|
-
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
1109
|
-
result.push(normalizeTaskDependencyLags({
|
|
1110
|
-
...task,
|
|
1111
|
-
startDate: newSuccStart.toISOString().split("T")[0],
|
|
1112
|
-
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
1113
|
-
}));
|
|
1114
|
-
queue.push(task.id);
|
|
1115
1287
|
}
|
|
1116
1288
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
);
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
const queue = [...initialChain];
|
|
1289
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
1290
|
+
for (const leaf of leafTasks) indegree.set(leaf.id, 0);
|
|
1291
|
+
for (const edges of succByPred.values()) {
|
|
1292
|
+
for (const { succId } of edges) {
|
|
1293
|
+
indegree.set(succId, (indegree.get(succId) ?? 0) + 1);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
const order = [];
|
|
1297
|
+
const enqueued = /* @__PURE__ */ new Set();
|
|
1298
|
+
const queue = leafTasks.filter((leaf) => (indegree.get(leaf.id) ?? 0) === 0).map((leaf) => leaf.id);
|
|
1299
|
+
for (const id of queue) enqueued.add(id);
|
|
1129
1300
|
while (queue.length > 0) {
|
|
1130
1301
|
const current = queue.shift();
|
|
1131
|
-
|
|
1132
|
-
for (const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1302
|
+
order.push(current);
|
|
1303
|
+
for (const { succId } of succByPred.get(current) ?? []) {
|
|
1304
|
+
const next = (indegree.get(succId) ?? 0) - 1;
|
|
1305
|
+
indegree.set(succId, next);
|
|
1306
|
+
if (next === 0 && !enqueued.has(succId)) {
|
|
1307
|
+
enqueued.add(succId);
|
|
1308
|
+
queue.push(succId);
|
|
1137
1309
|
}
|
|
1138
1310
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1311
|
+
}
|
|
1312
|
+
const projectFinish = Math.max(
|
|
1313
|
+
0,
|
|
1314
|
+
...leafTasks.map((leaf) => (scheduledStart.get(leaf.id) ?? 0) + durationOf(leaf))
|
|
1315
|
+
);
|
|
1316
|
+
const LF = /* @__PURE__ */ new Map();
|
|
1317
|
+
const LS = /* @__PURE__ */ new Map();
|
|
1318
|
+
for (const leaf of leafTasks) LF.set(leaf.id, projectFinish);
|
|
1319
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
1320
|
+
const id = order[i];
|
|
1321
|
+
const task = taskById.get(id);
|
|
1322
|
+
const durPred = durationOf(task);
|
|
1323
|
+
const outEdges = succByPred.get(id) ?? [];
|
|
1324
|
+
let lf = projectFinish;
|
|
1325
|
+
if (outEdges.length > 0) {
|
|
1326
|
+
const candidates = outEdges.map(({ succId, lag, type }) => {
|
|
1327
|
+
const succLs = LS.get(succId) ?? projectFinish;
|
|
1328
|
+
const durSucc = durationOf(taskById.get(succId));
|
|
1329
|
+
switch (type) {
|
|
1330
|
+
case "FS":
|
|
1331
|
+
return succLs - lag;
|
|
1332
|
+
case "SS":
|
|
1333
|
+
return succLs - lag + durPred;
|
|
1334
|
+
case "FF":
|
|
1335
|
+
return succLs + durSucc - lag;
|
|
1336
|
+
case "SF":
|
|
1337
|
+
return succLs + durSucc - lag + durPred;
|
|
1338
|
+
default:
|
|
1339
|
+
return projectFinish;
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
lf = Math.min(projectFinish, ...candidates);
|
|
1146
1343
|
}
|
|
1344
|
+
LF.set(id, lf);
|
|
1345
|
+
LS.set(id, lf - durPred);
|
|
1147
1346
|
}
|
|
1148
|
-
|
|
1347
|
+
const critical = /* @__PURE__ */ new Set();
|
|
1348
|
+
const ordered = new Set(order);
|
|
1349
|
+
for (const leaf of leafTasks) {
|
|
1350
|
+
if (!ordered.has(leaf.id)) continue;
|
|
1351
|
+
const hasGraphEdge = (succByPred.get(leaf.id)?.length ?? 0) > 0 || (predBySucc.get(leaf.id)?.length ?? 0) > 0;
|
|
1352
|
+
if (!hasGraphEdge && (leaf.dependencies?.length ?? 0) > 0) continue;
|
|
1353
|
+
const es = scheduledStart.get(leaf.id) ?? 0;
|
|
1354
|
+
const ls = LS.get(leaf.id) ?? 0;
|
|
1355
|
+
if (ls - es <= 0) critical.add(leaf.id);
|
|
1356
|
+
}
|
|
1357
|
+
return critical;
|
|
1149
1358
|
}
|
|
1150
|
-
function
|
|
1151
|
-
const
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
endDate: newEnd.toISOString().split("T")[0]
|
|
1156
|
-
});
|
|
1157
|
-
if (!movedTask.parentId && !childrenByParentId.has(movedTask.id) && !dependentsByPredecessorId.has(movedTask.id)) {
|
|
1158
|
-
return [normalizedMovedTask];
|
|
1359
|
+
function extendCriticalIdsToParents(criticalLeafIds, tasks) {
|
|
1360
|
+
const result = new Set(criticalLeafIds);
|
|
1361
|
+
const parentByChild = /* @__PURE__ */ new Map();
|
|
1362
|
+
for (const task of tasks) {
|
|
1363
|
+
if (task.parentId) parentByChild.set(task.id, task.parentId);
|
|
1159
1364
|
}
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1365
|
+
const stack = Array.from(criticalLeafIds);
|
|
1366
|
+
while (stack.length > 0) {
|
|
1367
|
+
const id = stack.pop();
|
|
1368
|
+
const parentId = parentByChild.get(id);
|
|
1369
|
+
if (parentId && !result.has(parentId)) {
|
|
1370
|
+
result.add(parentId);
|
|
1371
|
+
stack.push(parentId);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return result;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// src/core/scheduling/commands.ts
|
|
1378
|
+
function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
1379
|
+
const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
|
|
1380
|
+
if (businessDays && weekendPredicate) {
|
|
1381
|
+
return {
|
|
1382
|
+
start: normalizedStart,
|
|
1383
|
+
end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
1387
|
+
return {
|
|
1388
|
+
start: normalizedStart,
|
|
1389
|
+
end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS7)
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
|
|
1393
|
+
const normalizedEnd = businessDays && weekendPredicate ? alignToWorkingDay(endDate, snapDirection, weekendPredicate) : normalizeUTCDate(endDate);
|
|
1394
|
+
if (businessDays && weekendPredicate) {
|
|
1395
|
+
return {
|
|
1396
|
+
start: parseDateOnly(subtractBusinessDays(normalizedEnd, duration, weekendPredicate)),
|
|
1397
|
+
end: normalizedEnd
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
1401
|
+
return {
|
|
1402
|
+
start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS7),
|
|
1403
|
+
end: normalizedEnd
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
1407
|
+
return buildTaskRangeFromStart(
|
|
1408
|
+
proposedStart,
|
|
1409
|
+
getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
|
|
1410
|
+
businessDays,
|
|
1411
|
+
weekendPredicate,
|
|
1412
|
+
snapDirection
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate, taskById) {
|
|
1416
|
+
if (!task.dependencies?.length) {
|
|
1417
|
+
return { start: proposedStart, end: proposedEnd };
|
|
1418
|
+
}
|
|
1419
|
+
let minAllowedStart = null;
|
|
1420
|
+
for (const dep of task.dependencies) {
|
|
1421
|
+
if (dep.type !== "FS") {
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
1425
|
+
if (!predecessor) {
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(predecessor, parseDateOnly);
|
|
1429
|
+
const predecessorDuration = getTaskDuration(
|
|
1430
|
+
predecessorStart,
|
|
1431
|
+
predecessorEnd,
|
|
1432
|
+
businessDays,
|
|
1433
|
+
weekendPredicate
|
|
1434
|
+
);
|
|
1435
|
+
const candidateMinStart = calculateSuccessorDate(
|
|
1436
|
+
predecessorStart,
|
|
1437
|
+
predecessorEnd,
|
|
1438
|
+
"FS",
|
|
1439
|
+
-predecessorDuration,
|
|
1440
|
+
businessDays,
|
|
1441
|
+
weekendPredicate,
|
|
1442
|
+
task.type
|
|
1443
|
+
);
|
|
1444
|
+
if (!minAllowedStart || candidateMinStart.getTime() > minAllowedStart.getTime()) {
|
|
1445
|
+
minAllowedStart = candidateMinStart;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
if (!minAllowedStart || proposedStart.getTime() >= minAllowedStart.getTime()) {
|
|
1449
|
+
return { start: proposedStart, end: proposedEnd };
|
|
1450
|
+
}
|
|
1451
|
+
return buildTaskRangeFromStart(
|
|
1452
|
+
minAllowedStart,
|
|
1453
|
+
getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
|
|
1454
|
+
businessDays,
|
|
1455
|
+
weekendPredicate
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate, taskById) {
|
|
1459
|
+
if (!task.dependencies) return [];
|
|
1460
|
+
return task.dependencies.map((dep) => {
|
|
1461
|
+
const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
1462
|
+
if (!predecessor) {
|
|
1463
|
+
return { ...dep, lag: getDependencyLag(dep) };
|
|
1464
|
+
}
|
|
1465
|
+
const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(
|
|
1466
|
+
predecessor,
|
|
1467
|
+
(d) => new Date(d instanceof Date ? d.getTime() : `${String(d).split("T")[0]}T00:00:00.000Z`)
|
|
1468
|
+
);
|
|
1469
|
+
const nextLag = computeLagFromDates(
|
|
1470
|
+
dep.type,
|
|
1471
|
+
predecessorStart,
|
|
1472
|
+
predecessorEnd,
|
|
1473
|
+
newStartDate,
|
|
1474
|
+
newEndDate,
|
|
1475
|
+
businessDays,
|
|
1476
|
+
weekendPredicate,
|
|
1477
|
+
task.type
|
|
1478
|
+
);
|
|
1479
|
+
return { ...dep, lag: nextLag };
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// src/core/scheduling/cascade.ts
|
|
1484
|
+
function parseCascadeDateInput(date) {
|
|
1485
|
+
if (date instanceof Date) {
|
|
1486
|
+
return normalizeUTCDate(date);
|
|
1487
|
+
}
|
|
1488
|
+
return normalizeUTCDate(/* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`));
|
|
1489
|
+
}
|
|
1490
|
+
function createCascadeContext(allTasks) {
|
|
1491
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
1492
|
+
const dependentsByPredecessorId = /* @__PURE__ */ new Map();
|
|
1493
|
+
const taskById = /* @__PURE__ */ new Map();
|
|
1494
|
+
for (const task of allTasks) {
|
|
1495
|
+
taskById.set(task.id, task);
|
|
1496
|
+
if (task.parentId) {
|
|
1497
|
+
const children = childrenByParentId.get(task.parentId) ?? [];
|
|
1498
|
+
children.push(task);
|
|
1499
|
+
childrenByParentId.set(task.parentId, children);
|
|
1500
|
+
}
|
|
1501
|
+
if (!task.dependencies) continue;
|
|
1502
|
+
task.dependencies.forEach((dependency, dependencyIndex) => {
|
|
1503
|
+
const dependents = dependentsByPredecessorId.get(dependency.taskId) ?? [];
|
|
1504
|
+
dependents.push({ task, dependencyIndex });
|
|
1505
|
+
dependentsByPredecessorId.set(dependency.taskId, dependents);
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
return { childrenByParentId, dependentsByPredecessorId, taskById };
|
|
1509
|
+
}
|
|
1510
|
+
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
1511
|
+
const { dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
|
|
1512
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1513
|
+
const queue = [draggedTaskId];
|
|
1514
|
+
const chain = [];
|
|
1515
|
+
visited.add(draggedTaskId);
|
|
1516
|
+
while (queue.length > 0) {
|
|
1517
|
+
const current = queue.shift();
|
|
1518
|
+
const successors = dependentsByPredecessorId.get(current) ?? [];
|
|
1519
|
+
for (const { task } of successors) {
|
|
1520
|
+
if (!linkTypes.includes(task.dependencies?.find((dependency) => dependency.taskId === current)?.type ?? "FS")) {
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
const sid = task.id;
|
|
1524
|
+
if (!visited.has(sid)) {
|
|
1525
|
+
visited.add(sid);
|
|
1526
|
+
const successorTask = taskById.get(sid);
|
|
1527
|
+
if (successorTask) {
|
|
1528
|
+
chain.push(successorTask);
|
|
1529
|
+
queue.push(sid);
|
|
1205
1530
|
}
|
|
1206
|
-
updatedDates.set(child.id, { start: childNewStart, end: childNewEnd });
|
|
1207
|
-
childShifted.add(child.id);
|
|
1208
|
-
queue.push([child.id, "child-delta"]);
|
|
1209
|
-
resultMap.set(child.id, normalizeTaskDependencyLags({
|
|
1210
|
-
...child,
|
|
1211
|
-
startDate: childNewStart.toISOString().split("T")[0],
|
|
1212
|
-
endDate: childNewEnd.toISOString().split("T")[0]
|
|
1213
|
-
}));
|
|
1214
1531
|
}
|
|
1215
1532
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1533
|
+
}
|
|
1534
|
+
return chain;
|
|
1535
|
+
}
|
|
1536
|
+
function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
|
|
1537
|
+
const { childrenByParentId, dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
|
|
1538
|
+
const updatedDates = /* @__PURE__ */ new Map();
|
|
1539
|
+
updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
|
|
1540
|
+
const result = [];
|
|
1541
|
+
const queue = [movedTaskId];
|
|
1542
|
+
const visited = /* @__PURE__ */ new Set([movedTaskId]);
|
|
1543
|
+
while (queue.length > 0) {
|
|
1544
|
+
const currentId = queue.shift();
|
|
1545
|
+
const { start: predStart, end: predEnd } = updatedDates.get(currentId);
|
|
1546
|
+
if (!skipChildCascade) {
|
|
1547
|
+
const children = childrenByParentId.get(currentId) ?? [];
|
|
1548
|
+
for (const child of children) {
|
|
1549
|
+
if (visited.has(child.id) || child.locked) continue;
|
|
1550
|
+
const origStart = new Date(child.startDate);
|
|
1551
|
+
const origEnd = new Date(child.endDate);
|
|
1552
|
+
const durationMs = origEnd.getTime() - origStart.getTime();
|
|
1553
|
+
const parentOrig = taskById.get(currentId);
|
|
1554
|
+
const parentOrigStart = new Date(parentOrig.startDate);
|
|
1555
|
+
const parentOrigEnd = new Date(parentOrig.endDate);
|
|
1556
|
+
const parentStartDelta = predStart.getTime() - parentOrigStart.getTime();
|
|
1557
|
+
const parentEndDelta = predEnd.getTime() - parentOrigEnd.getTime();
|
|
1558
|
+
const newChildStart = new Date(origStart.getTime() + parentStartDelta);
|
|
1559
|
+
const newChildEnd = new Date(origEnd.getTime() + parentEndDelta);
|
|
1560
|
+
visited.add(child.id);
|
|
1561
|
+
updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
|
|
1562
|
+
result.push(normalizeTaskDependencyLags({
|
|
1563
|
+
...child,
|
|
1564
|
+
startDate: newChildStart.toISOString().split("T")[0],
|
|
1565
|
+
endDate: newChildEnd.toISOString().split("T")[0]
|
|
1566
|
+
}));
|
|
1567
|
+
queue.push(child.id);
|
|
1237
1568
|
}
|
|
1238
1569
|
}
|
|
1239
1570
|
const dependents = dependentsByPredecessorId.get(currentId) ?? [];
|
|
1240
1571
|
for (const { task, dependencyIndex } of dependents) {
|
|
1241
|
-
if (task.
|
|
1242
|
-
if (childShifted.has(task.id)) continue;
|
|
1572
|
+
if (visited.has(task.id) || !task.dependencies || task.locked) continue;
|
|
1243
1573
|
const dep = task.dependencies[dependencyIndex];
|
|
1244
1574
|
if (!dep) continue;
|
|
1245
|
-
const
|
|
1246
|
-
const
|
|
1575
|
+
const orig = taskById.get(task.id);
|
|
1576
|
+
const origStart = new Date(orig.startDate);
|
|
1577
|
+
const origEnd = new Date(orig.endDate);
|
|
1578
|
+
const duration = getTaskDuration(origStart, origEnd);
|
|
1579
|
+
const currentTask = taskById.get(currentId);
|
|
1247
1580
|
const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
|
|
1248
1581
|
{
|
|
1249
|
-
startDate:
|
|
1250
|
-
endDate:
|
|
1251
|
-
type:
|
|
1582
|
+
startDate: predStart,
|
|
1583
|
+
endDate: predEnd,
|
|
1584
|
+
type: currentTask.type
|
|
1252
1585
|
},
|
|
1253
1586
|
parseCascadeDateInput
|
|
1254
1587
|
);
|
|
@@ -1257,8 +1590,172 @@ function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays =
|
|
|
1257
1590
|
normalizedPredEnd,
|
|
1258
1591
|
dep.type,
|
|
1259
1592
|
getDependencyLag(dep),
|
|
1260
|
-
|
|
1261
|
-
|
|
1593
|
+
false,
|
|
1594
|
+
void 0,
|
|
1595
|
+
task.type
|
|
1596
|
+
);
|
|
1597
|
+
let newSuccStart;
|
|
1598
|
+
let newSuccEnd;
|
|
1599
|
+
if (dep.type === "FS" || dep.type === "SS") {
|
|
1600
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
|
|
1601
|
+
} else {
|
|
1602
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
|
|
1603
|
+
}
|
|
1604
|
+
visited.add(task.id);
|
|
1605
|
+
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
1606
|
+
result.push(normalizeTaskDependencyLags({
|
|
1607
|
+
...task,
|
|
1608
|
+
startDate: newSuccStart.toISOString().split("T")[0],
|
|
1609
|
+
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
1610
|
+
}));
|
|
1611
|
+
queue.push(task.id);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
return result;
|
|
1615
|
+
}
|
|
1616
|
+
function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
|
|
1617
|
+
const { childrenByParentId, dependentsByPredecessorId } = createCascadeContext(allTasks);
|
|
1618
|
+
const directChildren = childrenByParentId.get(changedTaskId) ?? [];
|
|
1619
|
+
const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
|
|
1620
|
+
const initialChain = [...directChildren, ...directSuccessors].filter(
|
|
1621
|
+
(task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
|
|
1622
|
+
);
|
|
1623
|
+
const chain = [...initialChain];
|
|
1624
|
+
const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
|
|
1625
|
+
const queue = [...initialChain];
|
|
1626
|
+
while (queue.length > 0) {
|
|
1627
|
+
const current = queue.shift();
|
|
1628
|
+
const children = childrenByParentId.get(current.id) ?? [];
|
|
1629
|
+
for (const child of children) {
|
|
1630
|
+
if (!visited.has(child.id)) {
|
|
1631
|
+
visited.add(child.id);
|
|
1632
|
+
chain.push(child);
|
|
1633
|
+
queue.push(child);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
const successors = dependentsByPredecessorId.get(current.id) ?? [];
|
|
1637
|
+
for (const { task: successor } of successors) {
|
|
1638
|
+
if (!visited.has(successor.id)) {
|
|
1639
|
+
visited.add(successor.id);
|
|
1640
|
+
chain.push(successor);
|
|
1641
|
+
queue.push(successor);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
return chain;
|
|
1646
|
+
}
|
|
1647
|
+
function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate, context = createCascadeContext(allTasks)) {
|
|
1648
|
+
const { childrenByParentId, dependentsByPredecessorId, taskById } = context;
|
|
1649
|
+
const normalizedMovedTask = normalizeTaskDependencyLags({
|
|
1650
|
+
...movedTask,
|
|
1651
|
+
startDate: newStart.toISOString().split("T")[0],
|
|
1652
|
+
endDate: newEnd.toISOString().split("T")[0]
|
|
1653
|
+
});
|
|
1654
|
+
if (!movedTask.parentId && !childrenByParentId.has(movedTask.id) && !dependentsByPredecessorId.has(movedTask.id)) {
|
|
1655
|
+
return [normalizedMovedTask];
|
|
1656
|
+
}
|
|
1657
|
+
const updatedDates = /* @__PURE__ */ new Map();
|
|
1658
|
+
updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
|
|
1659
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
1660
|
+
resultMap.set(movedTask.id, normalizedMovedTask);
|
|
1661
|
+
const queue = [[movedTask.id, "direct"]];
|
|
1662
|
+
const childShifted = /* @__PURE__ */ new Set();
|
|
1663
|
+
let iterations = 0;
|
|
1664
|
+
const MAX_ITERATIONS = allTasks.length * 3;
|
|
1665
|
+
while (queue.length > 0 && iterations < MAX_ITERATIONS) {
|
|
1666
|
+
iterations++;
|
|
1667
|
+
const [currentId, arrivalMode] = queue.shift();
|
|
1668
|
+
const { start: currStart, end: currEnd } = updatedDates.get(currentId);
|
|
1669
|
+
const currentOriginal = taskById.get(currentId);
|
|
1670
|
+
if (arrivalMode !== "parent-recalc") {
|
|
1671
|
+
const children = childrenByParentId.get(currentId) ?? [];
|
|
1672
|
+
for (const child of children) {
|
|
1673
|
+
if (childShifted.has(child.id) || child.locked) continue;
|
|
1674
|
+
const parentOrigStart = new Date(currentOriginal.startDate);
|
|
1675
|
+
const parentOrigEnd = new Date(currentOriginal.endDate);
|
|
1676
|
+
const childOrigStart = new Date(child.startDate);
|
|
1677
|
+
const childOrigEnd = new Date(child.endDate);
|
|
1678
|
+
const startDeltaMs = currStart.getTime() - parentOrigStart.getTime();
|
|
1679
|
+
const endDeltaMs = currEnd.getTime() - parentOrigEnd.getTime();
|
|
1680
|
+
let childNewStart;
|
|
1681
|
+
let childNewEnd;
|
|
1682
|
+
if (businessDays && weekendPredicate) {
|
|
1683
|
+
const proposedStart = new Date(childOrigStart.getTime() + startDeltaMs);
|
|
1684
|
+
const snapDirection = currStart.getTime() >= parentOrigStart.getTime() ? 1 : -1;
|
|
1685
|
+
const movedRange = moveTaskRange(
|
|
1686
|
+
child.startDate,
|
|
1687
|
+
child.endDate,
|
|
1688
|
+
proposedStart,
|
|
1689
|
+
true,
|
|
1690
|
+
weekendPredicate,
|
|
1691
|
+
snapDirection
|
|
1692
|
+
);
|
|
1693
|
+
childNewStart = movedRange.start;
|
|
1694
|
+
childNewEnd = movedRange.end;
|
|
1695
|
+
} else {
|
|
1696
|
+
childNewStart = new Date(childOrigStart.getTime() + startDeltaMs);
|
|
1697
|
+
childNewEnd = new Date(childOrigEnd.getTime() + endDeltaMs);
|
|
1698
|
+
}
|
|
1699
|
+
const prev = updatedDates.get(child.id);
|
|
1700
|
+
if (prev && prev.start.getTime() === childNewStart.getTime() && prev.end.getTime() === childNewEnd.getTime()) {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
updatedDates.set(child.id, { start: childNewStart, end: childNewEnd });
|
|
1704
|
+
childShifted.add(child.id);
|
|
1705
|
+
queue.push([child.id, "child-delta"]);
|
|
1706
|
+
resultMap.set(child.id, normalizeTaskDependencyLags({
|
|
1707
|
+
...child,
|
|
1708
|
+
startDate: childNewStart.toISOString().split("T")[0],
|
|
1709
|
+
endDate: childNewEnd.toISOString().split("T")[0]
|
|
1710
|
+
}));
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
const parentId = currentOriginal.parentId;
|
|
1714
|
+
if (parentId && parentId !== movedTask.id) {
|
|
1715
|
+
const parent = taskById.get(parentId);
|
|
1716
|
+
if (parent && !parent.locked) {
|
|
1717
|
+
const siblings = childrenByParentId.get(parentId) ?? [];
|
|
1718
|
+
const siblingPositions = siblings.map((sib) => {
|
|
1719
|
+
if (updatedDates.has(sib.id)) return updatedDates.get(sib.id);
|
|
1720
|
+
return { start: new Date(sib.startDate), end: new Date(sib.endDate) };
|
|
1721
|
+
});
|
|
1722
|
+
const minStart = new Date(Math.min(...siblingPositions.map((p) => p.start.getTime())));
|
|
1723
|
+
const maxEnd = new Date(Math.max(...siblingPositions.map((p) => p.end.getTime())));
|
|
1724
|
+
const prev = updatedDates.get(parentId);
|
|
1725
|
+
if (!prev || prev.start.getTime() !== minStart.getTime() || prev.end.getTime() !== maxEnd.getTime()) {
|
|
1726
|
+
updatedDates.set(parentId, { start: minStart, end: maxEnd });
|
|
1727
|
+
queue.push([parentId, "parent-recalc"]);
|
|
1728
|
+
resultMap.set(parentId, normalizeTaskDependencyLags({
|
|
1729
|
+
...parent,
|
|
1730
|
+
startDate: minStart.toISOString().split("T")[0],
|
|
1731
|
+
endDate: maxEnd.toISOString().split("T")[0]
|
|
1732
|
+
}));
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
const dependents = dependentsByPredecessorId.get(currentId) ?? [];
|
|
1737
|
+
for (const { task, dependencyIndex } of dependents) {
|
|
1738
|
+
if (task.locked || !task.dependencies) continue;
|
|
1739
|
+
if (childShifted.has(task.id)) continue;
|
|
1740
|
+
const dep = task.dependencies[dependencyIndex];
|
|
1741
|
+
if (!dep) continue;
|
|
1742
|
+
const origStart = new Date(task.startDate);
|
|
1743
|
+
const origEnd = new Date(task.endDate);
|
|
1744
|
+
const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
|
|
1745
|
+
{
|
|
1746
|
+
startDate: currStart,
|
|
1747
|
+
endDate: currEnd,
|
|
1748
|
+
type: currentOriginal.type
|
|
1749
|
+
},
|
|
1750
|
+
parseCascadeDateInput
|
|
1751
|
+
);
|
|
1752
|
+
const constraintDate = calculateSuccessorDate(
|
|
1753
|
+
normalizedPredStart,
|
|
1754
|
+
normalizedPredEnd,
|
|
1755
|
+
dep.type,
|
|
1756
|
+
getDependencyLag(dep),
|
|
1757
|
+
businessDays,
|
|
1758
|
+
weekendPredicate,
|
|
1262
1759
|
task.type
|
|
1263
1760
|
);
|
|
1264
1761
|
let succNewStart;
|
|
@@ -1324,13 +1821,13 @@ function computeParentProgress(parentId, tasks) {
|
|
|
1324
1821
|
if (children.length === 0) {
|
|
1325
1822
|
return 0;
|
|
1326
1823
|
}
|
|
1327
|
-
const
|
|
1824
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
1328
1825
|
let totalWeight = 0;
|
|
1329
1826
|
let weightedSum = 0;
|
|
1330
1827
|
for (const child of children) {
|
|
1331
1828
|
const start = new Date(child.startDate).getTime();
|
|
1332
1829
|
const end = new Date(child.endDate).getTime();
|
|
1333
|
-
const duration = (end - start +
|
|
1830
|
+
const duration = (end - start + DAY_MS7) / DAY_MS7;
|
|
1334
1831
|
const progress = child.progress ?? 0;
|
|
1335
1832
|
totalWeight += duration;
|
|
1336
1833
|
weightedSum += duration * progress;
|
|
@@ -3047,7 +3544,7 @@ function normalizeHierarchyTasks(tasks) {
|
|
|
3047
3544
|
children.push(index);
|
|
3048
3545
|
childrenByParent.set(parentId, children);
|
|
3049
3546
|
}
|
|
3050
|
-
const
|
|
3547
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
3051
3548
|
for (let index = orderedTasks.length - 1; index >= 0; index--) {
|
|
3052
3549
|
const children = childrenByParent.get(orderedTasks[index].id);
|
|
3053
3550
|
if (!children?.length) continue;
|
|
@@ -3061,7 +3558,7 @@ function normalizeHierarchyTasks(tasks) {
|
|
|
3061
3558
|
const end = new Date(child.endDate).getTime();
|
|
3062
3559
|
minStart = Math.min(minStart, start);
|
|
3063
3560
|
maxEnd = Math.max(maxEnd, end);
|
|
3064
|
-
const duration = (end - start +
|
|
3561
|
+
const duration = (end - start + DAY_MS7) / DAY_MS7;
|
|
3065
3562
|
totalWeight += duration;
|
|
3066
3563
|
weightedProgress += duration * (child.progress ?? 0);
|
|
3067
3564
|
}
|
|
@@ -3277,301 +3774,36 @@ var TimeScaleHeader = ({
|
|
|
3277
3774
|
"div",
|
|
3278
3775
|
{
|
|
3279
3776
|
className: `gantt-tsh-dayCell ${isWeekendDay ? "gantt-tsh-weekendDay" : ""} ${isTodayDate ? "gantt-tsh-today" : ""} ${marker && !isTodayDate ? "gantt-tsh-markerDay" : ""} ${tooltipText ? "gantt-tsh-dayCell-tooltipTrigger" : ""}`,
|
|
3280
|
-
style: markerColor && !isTodayDate ? {
|
|
3281
|
-
["--gantt-marker-day-color"]: markerColor
|
|
3282
|
-
} : void 0,
|
|
3283
|
-
"aria-label": tooltipText,
|
|
3284
|
-
onMouseEnter: () => {
|
|
3285
|
-
if (!tooltipText) return;
|
|
3286
|
-
onTimelineHover?.({
|
|
3287
|
-
label: tooltipText,
|
|
3288
|
-
left: index * dayWidth,
|
|
3289
|
-
color: isTodayDate ? "#dc2626" : markerColor || "var(--gantt-timeline-marker-color, #dc2626)"
|
|
3290
|
-
});
|
|
3291
|
-
},
|
|
3292
|
-
onMouseLeave: onTimelineHoverEnd,
|
|
3293
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: (0, import_date_fns.format)(day, "d") })
|
|
3294
|
-
},
|
|
3295
|
-
`day-${index}`
|
|
3296
|
-
);
|
|
3297
|
-
})
|
|
3298
|
-
)
|
|
3299
|
-
}
|
|
3300
|
-
)
|
|
3301
|
-
]
|
|
3302
|
-
}
|
|
3303
|
-
);
|
|
3304
|
-
};
|
|
3305
|
-
var TimeScaleHeader_default = TimeScaleHeader;
|
|
3306
|
-
|
|
3307
|
-
// src/components/TaskRow/TaskRow.tsx
|
|
3308
|
-
var import_react3 = __toESM(require("react"));
|
|
3309
|
-
var import_react_dom = require("react-dom");
|
|
3310
|
-
|
|
3311
|
-
// src/utils/geometry.ts
|
|
3312
|
-
var getUTCDayDifference = (date1, date2) => {
|
|
3313
|
-
const ms1 = Date.UTC(
|
|
3314
|
-
date1.getUTCFullYear(),
|
|
3315
|
-
date1.getUTCMonth(),
|
|
3316
|
-
date1.getUTCDate()
|
|
3317
|
-
);
|
|
3318
|
-
const ms2 = Date.UTC(
|
|
3319
|
-
date2.getUTCFullYear(),
|
|
3320
|
-
date2.getUTCMonth(),
|
|
3321
|
-
date2.getUTCDate()
|
|
3322
|
-
);
|
|
3323
|
-
return Math.round((ms1 - ms2) / (1e3 * 60 * 60 * 24));
|
|
3324
|
-
};
|
|
3325
|
-
var calculateTaskBar = (taskStartDate, taskEndDate, monthStart, dayWidth) => {
|
|
3326
|
-
const startOffset = getUTCDayDifference(taskStartDate, monthStart);
|
|
3327
|
-
const duration = getUTCDayDifference(taskEndDate, taskStartDate);
|
|
3328
|
-
const left = Math.round(startOffset * dayWidth);
|
|
3329
|
-
const width = Math.round((duration + 1) * dayWidth);
|
|
3330
|
-
return { left, width };
|
|
3331
|
-
};
|
|
3332
|
-
var calculateMilestoneGeometry = (taskDate, monthStart, dayWidth, size = 14) => {
|
|
3333
|
-
const { left, width } = calculateTaskBar(taskDate, taskDate, monthStart, dayWidth);
|
|
3334
|
-
const centerX = Math.round(left + width / 2);
|
|
3335
|
-
const halfSize = Math.round(size / 2);
|
|
3336
|
-
return {
|
|
3337
|
-
centerX,
|
|
3338
|
-
left: centerX - halfSize,
|
|
3339
|
-
right: centerX + halfSize,
|
|
3340
|
-
size
|
|
3341
|
-
};
|
|
3342
|
-
};
|
|
3343
|
-
var calculateMilestoneConnectionBounds = (dayLeft, dayWidth, size = 14) => {
|
|
3344
|
-
const halfDiagonal = Math.round(size / Math.SQRT2);
|
|
3345
|
-
const centerX = Math.round(dayLeft + dayWidth / 2);
|
|
3346
|
-
return {
|
|
3347
|
-
left: centerX - halfDiagonal,
|
|
3348
|
-
right: centerX + halfDiagonal
|
|
3349
|
-
};
|
|
3350
|
-
};
|
|
3351
|
-
var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
|
|
3352
|
-
const startDate = new Date(task.startDate);
|
|
3353
|
-
const endDate = new Date(task.endDate);
|
|
3354
|
-
if (task.type === "milestone") {
|
|
3355
|
-
const size = 14;
|
|
3356
|
-
if (override) {
|
|
3357
|
-
const bounds2 = calculateMilestoneConnectionBounds(override.left, dayWidth, size);
|
|
3358
|
-
return {
|
|
3359
|
-
...bounds2,
|
|
3360
|
-
centerX: Math.round(override.left + dayWidth / 2)
|
|
3361
|
-
};
|
|
3362
|
-
}
|
|
3363
|
-
const bar2 = calculateTaskBar(startDate, startDate, monthStart, dayWidth);
|
|
3364
|
-
const bounds = calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
|
|
3365
|
-
return {
|
|
3366
|
-
...bounds,
|
|
3367
|
-
centerX: Math.round(bar2.left + dayWidth / 2)
|
|
3368
|
-
};
|
|
3369
|
-
}
|
|
3370
|
-
if (override) {
|
|
3371
|
-
return {
|
|
3372
|
-
left: override.left,
|
|
3373
|
-
right: override.left + override.width,
|
|
3374
|
-
centerX: Math.round(override.left + override.width / 2)
|
|
3375
|
-
};
|
|
3376
|
-
}
|
|
3377
|
-
const bar = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
|
|
3378
|
-
return {
|
|
3379
|
-
left: bar.left,
|
|
3380
|
-
right: bar.left + bar.width,
|
|
3381
|
-
centerX: Math.round(bar.left + bar.width / 2)
|
|
3382
|
-
};
|
|
3383
|
-
};
|
|
3384
|
-
var pixelsToDate = (pixels, monthStart, dayWidth) => {
|
|
3385
|
-
const days = Math.round(pixels / dayWidth);
|
|
3386
|
-
return new Date(Date.UTC(
|
|
3387
|
-
monthStart.getUTCFullYear(),
|
|
3388
|
-
monthStart.getUTCMonth(),
|
|
3389
|
-
monthStart.getUTCDate() + days
|
|
3390
|
-
));
|
|
3391
|
-
};
|
|
3392
|
-
var calculateGridWidth = (daysInMonth, dayWidth) => {
|
|
3393
|
-
return Math.round(daysInMonth * dayWidth);
|
|
3394
|
-
};
|
|
3395
|
-
var MIN_EDGE_ZONE_PX = 8;
|
|
3396
|
-
var MIN_MOVE_ZONE_PX = 16;
|
|
3397
|
-
var detectEdgeZone = (clientX, taskBarElement, edgeZoneWidth = 12) => {
|
|
3398
|
-
const rect = taskBarElement.getBoundingClientRect();
|
|
3399
|
-
const relativeX = Math.round(clientX - rect.left);
|
|
3400
|
-
const width = Math.round(rect.width);
|
|
3401
|
-
const effectiveZone = Math.max(
|
|
3402
|
-
MIN_EDGE_ZONE_PX,
|
|
3403
|
-
Math.min(edgeZoneWidth, (width - MIN_MOVE_ZONE_PX) / 2)
|
|
3404
|
-
);
|
|
3405
|
-
if (relativeX >= 0 && relativeX <= effectiveZone) {
|
|
3406
|
-
return "left";
|
|
3407
|
-
}
|
|
3408
|
-
if (relativeX >= width - effectiveZone && relativeX <= width) {
|
|
3409
|
-
return "right";
|
|
3410
|
-
}
|
|
3411
|
-
return "move";
|
|
3412
|
-
};
|
|
3413
|
-
var getCursorForPosition = (position) => {
|
|
3414
|
-
switch (position) {
|
|
3415
|
-
case "left":
|
|
3416
|
-
case "right":
|
|
3417
|
-
return "ew-resize";
|
|
3418
|
-
case "move":
|
|
3419
|
-
return "grab";
|
|
3420
|
-
default:
|
|
3421
|
-
return "default";
|
|
3422
|
-
}
|
|
3423
|
-
};
|
|
3424
|
-
var calculateGridLines = (dateRange, dayWidth) => {
|
|
3425
|
-
const lines = [];
|
|
3426
|
-
for (let i = 0; i < dateRange.length; i++) {
|
|
3427
|
-
const date = dateRange[i];
|
|
3428
|
-
const x = Math.round(i * dayWidth);
|
|
3429
|
-
const isMonthStart = i === 0 ? false : date.getUTCDate() === 1;
|
|
3430
|
-
const isWeekStart = date.getUTCDay() === 1;
|
|
3431
|
-
lines.push({ x, isMonthStart, isWeekStart });
|
|
3432
|
-
}
|
|
3433
|
-
if (dateRange.length > 0) {
|
|
3434
|
-
lines.push({
|
|
3435
|
-
x: Math.round(dateRange.length * dayWidth),
|
|
3436
|
-
isMonthStart: false,
|
|
3437
|
-
isWeekStart: false
|
|
3438
|
-
});
|
|
3439
|
-
}
|
|
3440
|
-
return lines;
|
|
3441
|
-
};
|
|
3442
|
-
var calculateWeekendBlocks = (dateRange, dayWidth, isCustomWeekend) => {
|
|
3443
|
-
const blocks = [];
|
|
3444
|
-
let inWeekend = false;
|
|
3445
|
-
let weekendStartIndex = -1;
|
|
3446
|
-
for (let i = 0; i < dateRange.length; i++) {
|
|
3447
|
-
const date = dateRange[i];
|
|
3448
|
-
const isWeekend3 = isCustomWeekend ? isCustomWeekend(date) : date.getUTCDay() === 0 || date.getUTCDay() === 6;
|
|
3449
|
-
if (isWeekend3 && !inWeekend) {
|
|
3450
|
-
inWeekend = true;
|
|
3451
|
-
weekendStartIndex = i;
|
|
3452
|
-
} else if (!isWeekend3 && inWeekend) {
|
|
3453
|
-
inWeekend = false;
|
|
3454
|
-
const left = Math.round(weekendStartIndex * dayWidth);
|
|
3455
|
-
const width = Math.round((i - weekendStartIndex) * dayWidth);
|
|
3456
|
-
blocks.push({ left, width });
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
if (inWeekend && weekendStartIndex >= 0) {
|
|
3460
|
-
const left = Math.round(weekendStartIndex * dayWidth);
|
|
3461
|
-
const width = Math.round((dateRange.length - weekendStartIndex) * dayWidth);
|
|
3462
|
-
blocks.push({ left, width });
|
|
3463
|
-
}
|
|
3464
|
-
return blocks;
|
|
3465
|
-
};
|
|
3466
|
-
var calculateBezierPath = (from, to) => {
|
|
3467
|
-
const verticalDistance = Math.abs(to.y - from.y);
|
|
3468
|
-
const cpOffset = Math.max(verticalDistance * 0.5, 20);
|
|
3469
|
-
if (from.y === to.y) {
|
|
3470
|
-
const arcHeight = 20;
|
|
3471
|
-
const midX = (from.x + to.x) / 2;
|
|
3472
|
-
return `M ${from.x} ${from.y} Q ${midX} ${from.y - arcHeight} ${to.x} ${to.y}`;
|
|
3473
|
-
}
|
|
3474
|
-
const cp1x = from.x;
|
|
3475
|
-
const cp1y = from.y + (to.y > from.y ? cpOffset : -cpOffset);
|
|
3476
|
-
const cp2x = to.x;
|
|
3477
|
-
const cp2y = to.y - (to.y > from.y ? cpOffset : -cpOffset);
|
|
3478
|
-
return `M ${Math.round(from.x)} ${Math.round(from.y)} C ${Math.round(cp1x)} ${Math.round(cp1y)}, ${Math.round(cp2x)} ${Math.round(cp2y)}, ${Math.round(to.x)} ${Math.round(to.y)}`;
|
|
3479
|
-
};
|
|
3480
|
-
var calculateDependencyPath = (from, to, arrivesFromRight) => {
|
|
3481
|
-
const fx = Math.round(from.x);
|
|
3482
|
-
const fy = Math.round(from.y);
|
|
3483
|
-
const tx = Math.round(to.x);
|
|
3484
|
-
const ty = Math.round(to.y);
|
|
3485
|
-
if (fy === ty) {
|
|
3486
|
-
return `M ${fx} ${fy} H ${tx}`;
|
|
3487
|
-
}
|
|
3488
|
-
if (fx === tx) {
|
|
3489
|
-
return `M ${fx} ${fy} V ${ty}`;
|
|
3490
|
-
}
|
|
3491
|
-
const C = 2;
|
|
3492
|
-
const goingDown = ty > fy;
|
|
3493
|
-
const dirY = goingDown ? 1 : -1;
|
|
3494
|
-
if (arrivesFromRight) {
|
|
3495
|
-
const goingRight = tx >= fx;
|
|
3496
|
-
const dirX = goingRight ? 1 : -1;
|
|
3497
|
-
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
3498
|
-
return [
|
|
3499
|
-
`M ${fx} ${fy}`,
|
|
3500
|
-
`H ${tx - dirX * C}`,
|
|
3501
|
-
`L ${tx} ${fy + dirY * C}`,
|
|
3502
|
-
`V ${ty}`
|
|
3503
|
-
].join(" ");
|
|
3504
|
-
}
|
|
3505
|
-
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
3506
|
-
} else {
|
|
3507
|
-
const goingRight = tx >= fx;
|
|
3508
|
-
const dirX = goingRight ? 1 : -1;
|
|
3509
|
-
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
3510
|
-
return [
|
|
3511
|
-
`M ${fx} ${fy}`,
|
|
3512
|
-
`H ${tx - dirX * C}`,
|
|
3513
|
-
`L ${tx} ${fy + dirY * C}`,
|
|
3514
|
-
`V ${ty}`
|
|
3515
|
-
].join(" ");
|
|
3516
|
-
}
|
|
3517
|
-
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
3518
|
-
}
|
|
3519
|
-
};
|
|
3520
|
-
var calculateWeekGridLines = (dateRange, dayWidth) => {
|
|
3521
|
-
const blocks = getWeekBlocks(dateRange);
|
|
3522
|
-
const lines = [];
|
|
3523
|
-
let currentDayIndex = 0;
|
|
3524
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
3525
|
-
const block = blocks[i];
|
|
3526
|
-
currentDayIndex += block.days;
|
|
3527
|
-
const blockEndMonth = dateRange[currentDayIndex - 1];
|
|
3528
|
-
const nextBlockMonth = currentDayIndex < dateRange.length ? dateRange[currentDayIndex] : null;
|
|
3529
|
-
const isMonthBoundary = nextBlockMonth && blockEndMonth.getUTCMonth() !== nextBlockMonth.getUTCMonth();
|
|
3530
|
-
lines.push({
|
|
3531
|
-
x: Math.round(currentDayIndex * dayWidth),
|
|
3532
|
-
isMonthStart: isMonthBoundary || false
|
|
3533
|
-
});
|
|
3534
|
-
}
|
|
3535
|
-
if (lines.length > 0 && lines[lines.length - 1].x === Math.round(dateRange.length * dayWidth)) {
|
|
3536
|
-
lines.pop();
|
|
3537
|
-
}
|
|
3538
|
-
return lines;
|
|
3539
|
-
};
|
|
3540
|
-
var calculateMonthGridLines = (dateRange, dayWidth) => {
|
|
3541
|
-
const lines = [];
|
|
3542
|
-
for (let i = 1; i < dateRange.length; i++) {
|
|
3543
|
-
const date = dateRange[i];
|
|
3544
|
-
if (date.getUTCDate() === 1) {
|
|
3545
|
-
const x = Math.round(i * dayWidth);
|
|
3546
|
-
const isYearBoundary = date.getUTCMonth() === 0;
|
|
3547
|
-
lines.push({ x, isMonthStart: isYearBoundary });
|
|
3777
|
+
style: markerColor && !isTodayDate ? {
|
|
3778
|
+
["--gantt-marker-day-color"]: markerColor
|
|
3779
|
+
} : void 0,
|
|
3780
|
+
"aria-label": tooltipText,
|
|
3781
|
+
onMouseEnter: () => {
|
|
3782
|
+
if (!tooltipText) return;
|
|
3783
|
+
onTimelineHover?.({
|
|
3784
|
+
label: tooltipText,
|
|
3785
|
+
left: index * dayWidth,
|
|
3786
|
+
color: isTodayDate ? "#dc2626" : markerColor || "var(--gantt-timeline-marker-color, #dc2626)"
|
|
3787
|
+
});
|
|
3788
|
+
},
|
|
3789
|
+
onMouseLeave: onTimelineHoverEnd,
|
|
3790
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: (0, import_date_fns.format)(day, "d") })
|
|
3791
|
+
},
|
|
3792
|
+
`day-${index}`
|
|
3793
|
+
);
|
|
3794
|
+
})
|
|
3795
|
+
)
|
|
3796
|
+
}
|
|
3797
|
+
)
|
|
3798
|
+
]
|
|
3548
3799
|
}
|
|
3549
|
-
|
|
3550
|
-
return lines;
|
|
3551
|
-
};
|
|
3552
|
-
var calculateOrthogonalPath = (from, to) => {
|
|
3553
|
-
const fx = Math.round(from.x);
|
|
3554
|
-
const fy = Math.round(from.y);
|
|
3555
|
-
const tx = Math.round(to.x);
|
|
3556
|
-
const ty = Math.round(to.y);
|
|
3557
|
-
if (fy === ty) {
|
|
3558
|
-
return `M ${fx} ${fy} H ${tx}`;
|
|
3559
|
-
}
|
|
3560
|
-
const C = 2;
|
|
3561
|
-
const goingDown = ty > fy;
|
|
3562
|
-
const goingRight = tx >= fx;
|
|
3563
|
-
const dirY = goingDown ? 1 : -1;
|
|
3564
|
-
const dirX = goingRight ? 1 : -1;
|
|
3565
|
-
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
3566
|
-
return [
|
|
3567
|
-
`M ${fx} ${fy}`,
|
|
3568
|
-
`H ${tx - dirX * C}`,
|
|
3569
|
-
`L ${tx} ${fy + dirY * C}`,
|
|
3570
|
-
`V ${ty}`
|
|
3571
|
-
].join(" ");
|
|
3572
|
-
}
|
|
3573
|
-
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
3800
|
+
);
|
|
3574
3801
|
};
|
|
3802
|
+
var TimeScaleHeader_default = TimeScaleHeader;
|
|
3803
|
+
|
|
3804
|
+
// src/components/TaskRow/TaskRow.tsx
|
|
3805
|
+
var import_react3 = __toESM(require("react"));
|
|
3806
|
+
var import_react_dom = require("react-dom");
|
|
3575
3807
|
|
|
3576
3808
|
// src/utils/expired.ts
|
|
3577
3809
|
var isTaskExpired = (task, referenceDate = /* @__PURE__ */ new Date()) => {
|
|
@@ -4412,18 +4644,243 @@ var useTaskDrag = (options) => {
|
|
|
4412
4644
|
};
|
|
4413
4645
|
};
|
|
4414
4646
|
|
|
4647
|
+
// src/components/GanttChart/previewStore.ts
|
|
4648
|
+
function positionsEqual(left, right) {
|
|
4649
|
+
return left?.left === right?.left && left?.width === right?.width;
|
|
4650
|
+
}
|
|
4651
|
+
function createTaskPreviewPositionStore() {
|
|
4652
|
+
let positions = /* @__PURE__ */ new Map();
|
|
4653
|
+
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
4654
|
+
function notify(taskIds) {
|
|
4655
|
+
for (const taskId of taskIds) {
|
|
4656
|
+
const listeners = listenersByTaskId.get(taskId);
|
|
4657
|
+
if (!listeners) continue;
|
|
4658
|
+
for (const listener of listeners) {
|
|
4659
|
+
listener();
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
return {
|
|
4664
|
+
subscribeTask(taskId, listener) {
|
|
4665
|
+
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
4666
|
+
listeners.add(listener);
|
|
4667
|
+
listenersByTaskId.set(taskId, listeners);
|
|
4668
|
+
return () => {
|
|
4669
|
+
listeners.delete(listener);
|
|
4670
|
+
if (listeners.size === 0) {
|
|
4671
|
+
listenersByTaskId.delete(taskId);
|
|
4672
|
+
}
|
|
4673
|
+
};
|
|
4674
|
+
},
|
|
4675
|
+
getTaskPosition(taskId) {
|
|
4676
|
+
return positions.get(taskId);
|
|
4677
|
+
},
|
|
4678
|
+
setPositions(nextPositions) {
|
|
4679
|
+
const changedTaskIds = /* @__PURE__ */ new Set();
|
|
4680
|
+
const mergedPositions = /* @__PURE__ */ new Map();
|
|
4681
|
+
for (const [taskId, nextPosition] of nextPositions) {
|
|
4682
|
+
const currentPosition = positions.get(taskId);
|
|
4683
|
+
if (positionsEqual(currentPosition, nextPosition)) {
|
|
4684
|
+
if (currentPosition) {
|
|
4685
|
+
mergedPositions.set(taskId, currentPosition);
|
|
4686
|
+
}
|
|
4687
|
+
continue;
|
|
4688
|
+
}
|
|
4689
|
+
changedTaskIds.add(taskId);
|
|
4690
|
+
mergedPositions.set(taskId, {
|
|
4691
|
+
left: nextPosition.left,
|
|
4692
|
+
width: nextPosition.width
|
|
4693
|
+
});
|
|
4694
|
+
}
|
|
4695
|
+
for (const taskId of positions.keys()) {
|
|
4696
|
+
if (!nextPositions.has(taskId)) {
|
|
4697
|
+
changedTaskIds.add(taskId);
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
if (changedTaskIds.size === 0) {
|
|
4701
|
+
return;
|
|
4702
|
+
}
|
|
4703
|
+
positions = mergedPositions;
|
|
4704
|
+
notify(changedTaskIds);
|
|
4705
|
+
},
|
|
4706
|
+
clear() {
|
|
4707
|
+
if (positions.size === 0) {
|
|
4708
|
+
return;
|
|
4709
|
+
}
|
|
4710
|
+
const changedTaskIds = new Set(positions.keys());
|
|
4711
|
+
positions = /* @__PURE__ */ new Map();
|
|
4712
|
+
notify(changedTaskIds);
|
|
4713
|
+
}
|
|
4714
|
+
};
|
|
4715
|
+
}
|
|
4716
|
+
function createActivityPreviewStore() {
|
|
4717
|
+
let overridesByTask = /* @__PURE__ */ new Map();
|
|
4718
|
+
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
4719
|
+
function notify(taskIds) {
|
|
4720
|
+
for (const taskId of taskIds) {
|
|
4721
|
+
for (const listener of listenersByTaskId.get(taskId) ?? []) {
|
|
4722
|
+
listener();
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
return {
|
|
4727
|
+
subscribeTask(taskId, listener) {
|
|
4728
|
+
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
4729
|
+
listeners.add(listener);
|
|
4730
|
+
listenersByTaskId.set(taskId, listeners);
|
|
4731
|
+
return () => {
|
|
4732
|
+
listeners.delete(listener);
|
|
4733
|
+
if (listeners.size === 0) {
|
|
4734
|
+
listenersByTaskId.delete(taskId);
|
|
4735
|
+
}
|
|
4736
|
+
};
|
|
4737
|
+
},
|
|
4738
|
+
getTaskOverrides(taskId) {
|
|
4739
|
+
return overridesByTask.get(taskId);
|
|
4740
|
+
},
|
|
4741
|
+
setOverrides(nextOverrides) {
|
|
4742
|
+
const changedTaskIds = new Set(nextOverrides.keys());
|
|
4743
|
+
for (const taskId of overridesByTask.keys()) {
|
|
4744
|
+
changedTaskIds.add(taskId);
|
|
4745
|
+
}
|
|
4746
|
+
if ([...changedTaskIds].every((taskId) => {
|
|
4747
|
+
const current = overridesByTask.get(taskId);
|
|
4748
|
+
const next = nextOverrides.get(taskId);
|
|
4749
|
+
if (!current || !next || current.size !== next.size) return false;
|
|
4750
|
+
return [...next].every(([activityId, delta]) => current.get(activityId) === delta);
|
|
4751
|
+
})) {
|
|
4752
|
+
return;
|
|
4753
|
+
}
|
|
4754
|
+
overridesByTask = new Map([...nextOverrides].map(([taskId, deltas]) => [taskId, new Map(deltas)]));
|
|
4755
|
+
notify(changedTaskIds);
|
|
4756
|
+
},
|
|
4757
|
+
clear() {
|
|
4758
|
+
if (overridesByTask.size === 0) {
|
|
4759
|
+
return;
|
|
4760
|
+
}
|
|
4761
|
+
const changedTaskIds = new Set(overridesByTask.keys());
|
|
4762
|
+
overridesByTask = /* @__PURE__ */ new Map();
|
|
4763
|
+
notify(changedTaskIds);
|
|
4764
|
+
}
|
|
4765
|
+
};
|
|
4766
|
+
}
|
|
4767
|
+
function createActivityBlockStore() {
|
|
4768
|
+
let blockersByTask = /* @__PURE__ */ new Map();
|
|
4769
|
+
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
4770
|
+
function notify(taskIds) {
|
|
4771
|
+
for (const taskId of taskIds) {
|
|
4772
|
+
for (const listener of listenersByTaskId.get(taskId) ?? []) {
|
|
4773
|
+
listener();
|
|
4774
|
+
}
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
return {
|
|
4778
|
+
subscribeTask(taskId, listener) {
|
|
4779
|
+
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
4780
|
+
listeners.add(listener);
|
|
4781
|
+
listenersByTaskId.set(taskId, listeners);
|
|
4782
|
+
return () => {
|
|
4783
|
+
listeners.delete(listener);
|
|
4784
|
+
if (listeners.size === 0) {
|
|
4785
|
+
listenersByTaskId.delete(taskId);
|
|
4786
|
+
}
|
|
4787
|
+
};
|
|
4788
|
+
},
|
|
4789
|
+
getTaskBlockers(taskId) {
|
|
4790
|
+
return blockersByTask.get(taskId);
|
|
4791
|
+
},
|
|
4792
|
+
setBlockers(nextBlockers) {
|
|
4793
|
+
const changedTaskIds = new Set(nextBlockers.keys());
|
|
4794
|
+
for (const taskId of blockersByTask.keys()) {
|
|
4795
|
+
changedTaskIds.add(taskId);
|
|
4796
|
+
}
|
|
4797
|
+
if ([...changedTaskIds].every((taskId) => {
|
|
4798
|
+
const current = blockersByTask.get(taskId);
|
|
4799
|
+
const next = nextBlockers.get(taskId);
|
|
4800
|
+
if (!current || !next || current.size !== next.size) return false;
|
|
4801
|
+
return [...next].every((activityId) => current.has(activityId));
|
|
4802
|
+
})) {
|
|
4803
|
+
return;
|
|
4804
|
+
}
|
|
4805
|
+
blockersByTask = new Map([...nextBlockers].map(([taskId, ids]) => [taskId, new Set(ids)]));
|
|
4806
|
+
notify(changedTaskIds);
|
|
4807
|
+
},
|
|
4808
|
+
clear() {
|
|
4809
|
+
if (blockersByTask.size === 0) {
|
|
4810
|
+
return;
|
|
4811
|
+
}
|
|
4812
|
+
const changedTaskIds = new Set(blockersByTask.keys());
|
|
4813
|
+
blockersByTask = /* @__PURE__ */ new Map();
|
|
4814
|
+
notify(changedTaskIds);
|
|
4815
|
+
}
|
|
4816
|
+
};
|
|
4817
|
+
}
|
|
4818
|
+
var activityOwnerKey = (taskId, activityId) => `${taskId}\0${activityId}`;
|
|
4819
|
+
function createActivityDragOwner() {
|
|
4820
|
+
let ownerKey = null;
|
|
4821
|
+
return {
|
|
4822
|
+
get: () => ownerKey,
|
|
4823
|
+
set: (nextOwnerKey) => {
|
|
4824
|
+
ownerKey = nextOwnerKey;
|
|
4825
|
+
}
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
function createActivityActivationStore() {
|
|
4829
|
+
let activeKey = null;
|
|
4830
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4831
|
+
return {
|
|
4832
|
+
subscribe(listener) {
|
|
4833
|
+
listeners.add(listener);
|
|
4834
|
+
return () => {
|
|
4835
|
+
listeners.delete(listener);
|
|
4836
|
+
};
|
|
4837
|
+
},
|
|
4838
|
+
getActiveKey: () => activeKey,
|
|
4839
|
+
setActiveKey(nextKey) {
|
|
4840
|
+
if (activeKey === nextKey) return;
|
|
4841
|
+
activeKey = nextKey;
|
|
4842
|
+
for (const listener of listeners) listener();
|
|
4843
|
+
}
|
|
4844
|
+
};
|
|
4845
|
+
}
|
|
4846
|
+
|
|
4415
4847
|
// src/components/TaskRow/TaskRow.tsx
|
|
4416
4848
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
4849
|
+
var COMPOSITE_SEGMENT_CUT_MIN_PX = 10;
|
|
4850
|
+
var toIsoDay = (date) => date.toISOString().slice(0, 10);
|
|
4851
|
+
var shiftActivityDay = (value, days) => new Date(parseUTCDate(value).getTime() + days * DAY_MS);
|
|
4852
|
+
var activityDurationDays = (activity) => Math.max(1, Math.round((parseUTCDate(activity.endDate).getTime() - parseUTCDate(activity.startDate).getTime()) / DAY_MS) + 1);
|
|
4853
|
+
var ACTIVITY_TEXT_LUMINANCE_THRESHOLD = 0.55;
|
|
4854
|
+
var relativeLuminance = (hex) => {
|
|
4855
|
+
const channels = [1, 3, 5].map((index) => {
|
|
4856
|
+
const value = parseInt(hex.slice(index, index + 2), 16) / 255;
|
|
4857
|
+
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
4858
|
+
});
|
|
4859
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
4860
|
+
};
|
|
4861
|
+
var activityTextColor = (background) => {
|
|
4862
|
+
const lightText = "var(--gantt-task-bar-text-color, #ffffff)";
|
|
4863
|
+
if (!background) return lightText;
|
|
4864
|
+
const value = background.trim();
|
|
4865
|
+
if (value.startsWith("#")) {
|
|
4866
|
+
return relativeLuminance(value) > ACTIVITY_TEXT_LUMINANCE_THRESHOLD ? "#111111" : "#ffffff";
|
|
4867
|
+
}
|
|
4868
|
+
const oklch = /oklch\(\s*([0-9.]+)/i.exec(value);
|
|
4869
|
+
if (oklch) {
|
|
4870
|
+
return Number(oklch[1]) > 0.66 ? "#111111" : "#ffffff";
|
|
4871
|
+
}
|
|
4872
|
+
return lightText;
|
|
4873
|
+
};
|
|
4417
4874
|
var formatCompositePreviewDate = (date) => {
|
|
4418
4875
|
const month = new Intl.DateTimeFormat("ru-RU", { month: "short", timeZone: "UTC" }).format(date).replace(/\.$/, "");
|
|
4419
4876
|
const yearSuffix = date.getUTCFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? "" : ` ${String(date.getUTCFullYear()).slice(-2)}`;
|
|
4420
4877
|
return `${date.getUTCDate()} ${month}${yearSuffix}`;
|
|
4421
4878
|
};
|
|
4422
4879
|
var arePropsEqual = (prevProps, nextProps) => {
|
|
4423
|
-
return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.baselineStartDate === nextProps.task.baselineStartDate && prevProps.task.baselineEndDate === nextProps.task.baselineEndDate && prevProps.task.type === nextProps.task.type && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.previewPositionStore === nextProps.previewPositionStore && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.deferCascadePreview === nextProps.deferCascadePreview && prevProps.task.locked === nextProps.task.locked && prevProps.task.synced === nextProps.task.synced && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks && prevProps.isCritical === nextProps.isCritical && prevProps.showBaseline === nextProps.showBaseline && prevProps.onCompositeToggle === nextProps.onCompositeToggle && prevProps.compactDetail === nextProps.compactDetail && prevProps.compositeParentColor === nextProps.compositeParentColor && prevProps.compositeExpanded === nextProps.compositeExpanded && prevProps.isFilterMatch === nextProps.isFilterMatch && prevProps.businessDays === nextProps.businessDays && prevProps.customDays === nextProps.customDays && prevProps.isWeekend === nextProps.isWeekend && prevProps.disableTaskDrag === nextProps.disableTaskDrag && prevProps.disableDependencyEditing === nextProps.disableDependencyEditing && prevProps.isDependencyDragActive === nextProps.isDependencyDragActive && prevProps.viewMode === nextProps.viewMode && prevProps.showTaskDateLabels === nextProps.showTaskDateLabels && prevProps.showTaskNames === nextProps.showTaskNames;
|
|
4880
|
+
return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.baselineStartDate === nextProps.task.baselineStartDate && prevProps.task.baselineEndDate === nextProps.task.baselineEndDate && prevProps.task.type === nextProps.task.type && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.task.activities === nextProps.task.activities && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.previewPositionStore === nextProps.previewPositionStore && prevProps.activityPreviewStore === nextProps.activityPreviewStore && prevProps.activityBlockStore === nextProps.activityBlockStore && prevProps.activityDragOwner === nextProps.activityDragOwner && prevProps.activityActivationStore === nextProps.activityActivationStore && prevProps.activityClickToDrag === nextProps.activityClickToDrag && prevProps.activityActivationMode === nextProps.activityActivationMode && prevProps.showActivityLinks === nextProps.showActivityLinks && prevProps.showActivityLag === nextProps.showActivityLag && prevProps.horizontalWindow?.startPx === nextProps.horizontalWindow?.startPx && prevProps.horizontalWindow?.endPx === nextProps.horizontalWindow?.endPx && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.deferCascadePreview === nextProps.deferCascadePreview && prevProps.task.locked === nextProps.task.locked && prevProps.task.synced === nextProps.task.synced && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks && prevProps.isCritical === nextProps.isCritical && prevProps.showBaseline === nextProps.showBaseline && prevProps.onCompositeToggle === nextProps.onCompositeToggle && prevProps.compactDetail === nextProps.compactDetail && prevProps.compositeParentColor === nextProps.compositeParentColor && prevProps.compositeExpanded === nextProps.compositeExpanded && prevProps.isFilterMatch === nextProps.isFilterMatch && prevProps.businessDays === nextProps.businessDays && prevProps.customDays === nextProps.customDays && prevProps.isWeekend === nextProps.isWeekend && prevProps.disableTaskDrag === nextProps.disableTaskDrag && prevProps.disableDependencyEditing === nextProps.disableDependencyEditing && prevProps.isDependencyDragActive === nextProps.isDependencyDragActive && prevProps.viewMode === nextProps.viewMode && prevProps.showTaskDateLabels === nextProps.showTaskDateLabels && prevProps.showTaskNames === nextProps.showTaskNames && prevProps.showCompositeSegments === nextProps.showCompositeSegments;
|
|
4424
4881
|
};
|
|
4425
4882
|
var TaskRow = import_react3.default.memo(
|
|
4426
|
-
({ task, monthStart, dayWidth, rowHeight, onTasksChange, onScheduleIntent, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, deferCascadePreview = false, overridePosition, previewPositionStore, onCascadeProgress, onCascade, divider, highlightExpiredTasks, isCritical = false, showBaseline = false, isFilterMatch = false, businessDays, customDays, isWeekend: isWeekend3, disableTaskDrag = false, disableDependencyEditing = false, onDependencyPortPointerDown, isDependencyDragActive = false, viewMode = "day", showTaskDateLabels = true, showTaskNames = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
|
|
4883
|
+
({ task, monthStart, dayWidth, rowHeight, onTasksChange, onScheduleIntent, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, deferCascadePreview = false, overridePosition, previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore, activityClickToDrag = false, activityActivationMode = "none", showActivityLinks = true, showActivityLag = true, activityTooltip, horizontalWindow, onCascadeProgress, onCascade, divider, highlightExpiredTasks, isCritical = false, showBaseline = false, isFilterMatch = false, businessDays, customDays, isWeekend: isWeekend3, disableTaskDrag = false, disableDependencyEditing = false, onDependencyPortPointerDown, isDependencyDragActive = false, viewMode = "day", showTaskDateLabels = true, showTaskNames = true, showCompositeSegments: showCompositeSegmentsProp = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
|
|
4427
4884
|
const defaultParentBarColor = "#782FC4";
|
|
4428
4885
|
const [showCompositePreview, setShowCompositePreview] = (0, import_react3.useState)(false);
|
|
4429
4886
|
const [compositePreviewPosition, setCompositePreviewPosition] = (0, import_react3.useState)(null);
|
|
@@ -4594,35 +5051,380 @@ var TaskRow = import_react3.default.memo(
|
|
|
4594
5051
|
const centerX = Math.round(displayLeft + dayWidth / 2);
|
|
4595
5052
|
const halfSize = Math.round(milestoneGeometry.size / 2);
|
|
4596
5053
|
return {
|
|
4597
|
-
centerX,
|
|
4598
|
-
left: centerX - halfSize,
|
|
4599
|
-
right: centerX + halfSize,
|
|
4600
|
-
size: milestoneGeometry.size
|
|
5054
|
+
centerX,
|
|
5055
|
+
left: centerX - halfSize,
|
|
5056
|
+
right: centerX + halfSize,
|
|
5057
|
+
size: milestoneGeometry.size
|
|
5058
|
+
};
|
|
5059
|
+
}, [displayLeft, dayWidth, milestoneGeometry.size]);
|
|
5060
|
+
const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
|
|
5061
|
+
const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
|
|
5062
|
+
const compositeSegments = (0, import_react3.useMemo)(() => {
|
|
5063
|
+
if (!task.composite || width <= 0) return [];
|
|
5064
|
+
const laneEnds = [];
|
|
5065
|
+
const segments2 = compositeChildren.map((child) => {
|
|
5066
|
+
const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
|
|
5067
|
+
let lane = Math.max(0, laneEnds.length - 1);
|
|
5068
|
+
if (laneEnds.length > 0 && segment.left < laneEnds[lane]) {
|
|
5069
|
+
lane = laneEnds.length;
|
|
5070
|
+
}
|
|
5071
|
+
laneEnds[lane] = segment.left + segment.width;
|
|
5072
|
+
return {
|
|
5073
|
+
id: child.id,
|
|
5074
|
+
name: child.name,
|
|
5075
|
+
left: (segment.left - left) / width * 100,
|
|
5076
|
+
width: segment.width / width * 100,
|
|
5077
|
+
lane,
|
|
5078
|
+
pxLeft: segment.left - left,
|
|
5079
|
+
cut: false
|
|
5080
|
+
};
|
|
5081
|
+
});
|
|
5082
|
+
const lastCutByLane = [];
|
|
5083
|
+
for (const segment of [...segments2].sort((a, b) => a.pxLeft - b.pxLeft)) {
|
|
5084
|
+
segment.cut = segment.pxLeft >= 1 && segment.pxLeft - (lastCutByLane[segment.lane] ?? 0) >= COMPOSITE_SEGMENT_CUT_MIN_PX;
|
|
5085
|
+
if (segment.cut) lastCutByLane[segment.lane] = segment.pxLeft;
|
|
5086
|
+
}
|
|
5087
|
+
return segments2;
|
|
5088
|
+
}, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
|
|
5089
|
+
const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
|
|
5090
|
+
const shouldRenderCompositeSegments = showCompositeSegmentsProp && task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
|
|
5091
|
+
const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
|
|
5092
|
+
const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
|
|
5093
|
+
const activityLayout = (0, import_react3.useMemo)(
|
|
5094
|
+
() => task.activities && task.activities.length > 0 ? computeActivityLanes(task.activities, monthStart, dayWidth) : null,
|
|
5095
|
+
[task.activities, monthStart, dayWidth]
|
|
5096
|
+
);
|
|
5097
|
+
const renderActivities = activityLayout !== null && !compactDetail;
|
|
5098
|
+
const activityTopOffset = activityLayout ? Math.max(0, (rowHeight - activityLayout.laneCount * ACTIVITY_LANE_STEP) / 2) : 0;
|
|
5099
|
+
const activityDragEnabled = !task.locked && !disableTaskDrag;
|
|
5100
|
+
const [activityDrag, setActivityDrag] = (0, import_react3.useState)(null);
|
|
5101
|
+
const [activityTipId, setActivityTipId] = (0, import_react3.useState)(null);
|
|
5102
|
+
const [activityTipPosition, setActivityTipPosition] = (0, import_react3.useState)(null);
|
|
5103
|
+
const activityTipAnchor = (0, import_react3.useRef)(null);
|
|
5104
|
+
const activityDragStart = (0, import_react3.useRef)(null);
|
|
5105
|
+
const activityConstraintRef = (0, import_react3.useRef)(null);
|
|
5106
|
+
const [activityBlockedBy, setActivityBlockedBy] = (0, import_react3.useState)(null);
|
|
5107
|
+
const visibleActivitySegments = (0, import_react3.useMemo)(() => {
|
|
5108
|
+
if (!activityLayout) return [];
|
|
5109
|
+
const window2 = horizontalWindow;
|
|
5110
|
+
if (!window2) return activityLayout.segments;
|
|
5111
|
+
const draggedId = activityDrag?.id;
|
|
5112
|
+
return activityLayout.segments.filter((segment) => segment.id === draggedId || segment.left <= window2.endPx && segment.left + segment.width >= window2.startPx);
|
|
5113
|
+
}, [activityLayout, horizontalWindow, activityDrag?.id]);
|
|
5114
|
+
const subscribeActivityOverrides = (0, import_react3.useCallback)(
|
|
5115
|
+
(listener) => activityPreviewStore?.subscribeTask(task.id, listener) ?? (() => {
|
|
5116
|
+
}),
|
|
5117
|
+
[activityPreviewStore, task.id]
|
|
5118
|
+
);
|
|
5119
|
+
const getActivityOverridesSnapshot = (0, import_react3.useCallback)(
|
|
5120
|
+
() => activityPreviewStore?.getTaskOverrides(task.id),
|
|
5121
|
+
[activityPreviewStore, task.id]
|
|
5122
|
+
);
|
|
5123
|
+
const activityOverrides = (0, import_react3.useSyncExternalStore)(
|
|
5124
|
+
subscribeActivityOverrides,
|
|
5125
|
+
getActivityOverridesSnapshot,
|
|
5126
|
+
() => void 0
|
|
5127
|
+
);
|
|
5128
|
+
const subscribeActivityBlock = (0, import_react3.useCallback)(
|
|
5129
|
+
(listener) => activityBlockStore?.subscribeTask(task.id, listener) ?? (() => {
|
|
5130
|
+
}),
|
|
5131
|
+
[activityBlockStore, task.id]
|
|
5132
|
+
);
|
|
5133
|
+
const getActivityBlockSnapshot = (0, import_react3.useCallback)(
|
|
5134
|
+
() => activityBlockStore?.getTaskBlockers(task.id),
|
|
5135
|
+
[activityBlockStore, task.id]
|
|
5136
|
+
);
|
|
5137
|
+
const blockedActivityIds = (0, import_react3.useSyncExternalStore)(
|
|
5138
|
+
subscribeActivityBlock,
|
|
5139
|
+
getActivityBlockSnapshot,
|
|
5140
|
+
() => void 0
|
|
5141
|
+
);
|
|
5142
|
+
const subscribeActivation = (0, import_react3.useCallback)(
|
|
5143
|
+
(listener) => activityActivationStore?.subscribe(listener) ?? (() => {
|
|
5144
|
+
}),
|
|
5145
|
+
[activityActivationStore]
|
|
5146
|
+
);
|
|
5147
|
+
const getActivationSnapshot = (0, import_react3.useCallback)(
|
|
5148
|
+
() => activityActivationStore?.getActiveKey() ?? null,
|
|
5149
|
+
[activityActivationStore]
|
|
5150
|
+
);
|
|
5151
|
+
const activeActivityKey = (0, import_react3.useSyncExternalStore)(
|
|
5152
|
+
subscribeActivation,
|
|
5153
|
+
getActivationSnapshot,
|
|
5154
|
+
() => null
|
|
5155
|
+
);
|
|
5156
|
+
const activeWork = (0, import_react3.useMemo)(() => {
|
|
5157
|
+
if (!activeActivityKey) return null;
|
|
5158
|
+
const [activeTaskId, activeActivityId] = activeActivityKey.split("\0");
|
|
5159
|
+
const source = (allTasks ?? []).find((candidate) => candidate.id === activeTaskId);
|
|
5160
|
+
const activity = source?.activities?.find((candidate) => candidate.id === activeActivityId);
|
|
5161
|
+
return { id: activeActivityId, name: activity?.name ?? null };
|
|
5162
|
+
}, [activeActivityKey, allTasks]);
|
|
5163
|
+
const activationVisualEnabled = activityClickToDrag || activityActivationMode !== "none";
|
|
5164
|
+
const dimOthers = activityActivationMode === "dim";
|
|
5165
|
+
const highlightChain = activityActivationMode === "highlight";
|
|
5166
|
+
const chainEndDeltaDays = (mode, dayDelta, durationDays2) => {
|
|
5167
|
+
if (mode === "resize-left") return 0;
|
|
5168
|
+
if (mode === "resize-right") return Math.max(dayDelta, -(durationDays2 - 1));
|
|
5169
|
+
return dayDelta;
|
|
5170
|
+
};
|
|
5171
|
+
const publishChainPreview = (0, import_react3.useCallback)((draggedId, mode, dayDelta) => {
|
|
5172
|
+
if (!activityPreviewStore) return;
|
|
5173
|
+
if (!task.activityChain) {
|
|
5174
|
+
activityPreviewStore.clear();
|
|
5175
|
+
return;
|
|
5176
|
+
}
|
|
5177
|
+
let shifts;
|
|
5178
|
+
if (task.activityChain === "push") {
|
|
5179
|
+
let delta = dayDelta;
|
|
5180
|
+
if (mode !== "resize-right") {
|
|
5181
|
+
const minDelta = activityConstraintRef.current?.minStartDelta ?? null;
|
|
5182
|
+
if (minDelta !== null) delta = Math.max(delta, minDelta);
|
|
5183
|
+
}
|
|
5184
|
+
shifts = delta === 0 ? [] : pushActivityChain(allTasks ?? [], task.id, draggedId, delta).shifts;
|
|
5185
|
+
} else {
|
|
5186
|
+
const endDelta = chainEndDeltaDays(mode, dayDelta, activityDragStart.current?.durationDays ?? 1);
|
|
5187
|
+
shifts = endDelta === 0 ? [] : shiftActivityChain(allTasks ?? [], task.id, draggedId, endDelta);
|
|
5188
|
+
}
|
|
5189
|
+
if (shifts.length === 0) {
|
|
5190
|
+
activityPreviewStore.clear();
|
|
5191
|
+
return;
|
|
5192
|
+
}
|
|
5193
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
5194
|
+
for (const shift of shifts) {
|
|
5195
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
5196
|
+
for (const [id, d] of shift.deltas) {
|
|
5197
|
+
if (d !== 0) deltas.set(id, d);
|
|
5198
|
+
}
|
|
5199
|
+
if (deltas.size > 0) overrides.set(shift.taskId, deltas);
|
|
5200
|
+
}
|
|
5201
|
+
activityPreviewStore.setOverrides(overrides);
|
|
5202
|
+
}, [activityPreviewStore, allTasks, task.activityChain, task.id]);
|
|
5203
|
+
(0, import_react3.useEffect)(() => {
|
|
5204
|
+
if (!activityDrag) return;
|
|
5205
|
+
const startInfo = activityDragStart.current;
|
|
5206
|
+
if (!startInfo) return;
|
|
5207
|
+
const publishBlockers = (blocked) => {
|
|
5208
|
+
const constraint = activityConstraintRef.current;
|
|
5209
|
+
if (!activityBlockStore) return;
|
|
5210
|
+
if (!blocked || !constraint) {
|
|
5211
|
+
activityBlockStore.clear();
|
|
5212
|
+
setActivityBlockedBy((current) => current === null ? current : null);
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
const blockers = /* @__PURE__ */ new Map();
|
|
5216
|
+
const names = [];
|
|
5217
|
+
for (const blocker of constraint.blockers) {
|
|
5218
|
+
const set = blockers.get(blocker.taskId) ?? /* @__PURE__ */ new Set();
|
|
5219
|
+
set.add(blocker.activityId);
|
|
5220
|
+
blockers.set(blocker.taskId, set);
|
|
5221
|
+
const source = (allTasks ?? []).find((candidate) => candidate.id === blocker.taskId);
|
|
5222
|
+
const work = source?.activities?.find((activity) => activity.id === blocker.activityId);
|
|
5223
|
+
const workName = work?.name ?? source?.name ?? blocker.activityId;
|
|
5224
|
+
names.push(blocker.taskId === task.id ? workName : `${source?.name ?? blocker.taskId} \xB7 ${workName}`);
|
|
5225
|
+
}
|
|
5226
|
+
activityBlockStore.setBlockers(blockers);
|
|
5227
|
+
setActivityBlockedBy((current) => current && current.lag === constraint.lag && current.names.length === names.length && current.names.every((name, index) => name === names[index]) ? current : { names, lag: constraint.lag });
|
|
5228
|
+
};
|
|
5229
|
+
const onMove = (event) => {
|
|
5230
|
+
const rawDelta = Math.round((event.clientX - startInfo.x) / dayWidth);
|
|
5231
|
+
let requested = rawDelta;
|
|
5232
|
+
if (activityDrag.mode === "resize-left") requested = Math.min(rawDelta, startInfo.durationDays - 1);
|
|
5233
|
+
if (activityDrag.mode === "resize-right") requested = Math.max(rawDelta, -(startInfo.durationDays - 1));
|
|
5234
|
+
const constraint = activityConstraintRef.current;
|
|
5235
|
+
let clamped = requested;
|
|
5236
|
+
let blocked = false;
|
|
5237
|
+
if (constraint && activityDrag.mode !== "resize-right" && requested < constraint.minStartDelta) {
|
|
5238
|
+
clamped = constraint.minStartDelta;
|
|
5239
|
+
blocked = true;
|
|
5240
|
+
}
|
|
5241
|
+
setActivityDrag((current) => current ? { ...current, dayDelta: clamped } : current);
|
|
5242
|
+
publishBlockers(blocked);
|
|
5243
|
+
const anchor = activityTipAnchor.current;
|
|
5244
|
+
if (anchor) {
|
|
5245
|
+
const shiftPx = activityDrag.mode === "resize-right" ? 0 : clamped * dayWidth;
|
|
5246
|
+
setActivityTipPosition({
|
|
5247
|
+
left: Math.max(8, Math.min(anchor.left + shiftPx, window.innerWidth - 160)),
|
|
5248
|
+
top: anchor.top,
|
|
5249
|
+
below: anchor.below
|
|
5250
|
+
});
|
|
5251
|
+
}
|
|
5252
|
+
publishChainPreview(activityDrag.id, activityDrag.mode, clamped);
|
|
5253
|
+
};
|
|
5254
|
+
const onUp = () => {
|
|
5255
|
+
activityPreviewStore?.clear();
|
|
5256
|
+
activityBlockStore?.clear();
|
|
5257
|
+
activityDragOwner?.set(null);
|
|
5258
|
+
setActivityBlockedBy(null);
|
|
5259
|
+
setActivityTipId(null);
|
|
5260
|
+
activityTipAnchor.current = null;
|
|
5261
|
+
setActivityTipPosition(null);
|
|
5262
|
+
if (activityDrag.dayDelta !== 0) {
|
|
5263
|
+
const chainPush = task.activityChain === "push";
|
|
5264
|
+
let delta = activityDrag.dayDelta;
|
|
5265
|
+
if (chainPush && activityDrag.mode !== "resize-right") {
|
|
5266
|
+
const minDelta = activityConstraintRef.current?.minStartDelta ?? null;
|
|
5267
|
+
if (minDelta !== null) delta = Math.max(delta, minDelta);
|
|
5268
|
+
}
|
|
5269
|
+
const clampLeft = Math.min(delta, startInfo.durationDays - 1);
|
|
5270
|
+
const clampRight = Math.max(delta, -(startInfo.durationDays - 1));
|
|
5271
|
+
const updatedActivities = (task.activities ?? []).map((activity) => {
|
|
5272
|
+
if (activity.id !== activityDrag.id) return activity;
|
|
5273
|
+
if (activityDrag.mode === "move") {
|
|
5274
|
+
return {
|
|
5275
|
+
...activity,
|
|
5276
|
+
startDate: toIsoDay(shiftActivityDay(activity.startDate, delta)),
|
|
5277
|
+
endDate: toIsoDay(shiftActivityDay(activity.endDate, delta))
|
|
5278
|
+
};
|
|
5279
|
+
}
|
|
5280
|
+
if (activityDrag.mode === "resize-left") {
|
|
5281
|
+
return { ...activity, startDate: toIsoDay(shiftActivityDay(activity.startDate, clampLeft)) };
|
|
5282
|
+
}
|
|
5283
|
+
return { ...activity, endDate: toIsoDay(shiftActivityDay(activity.endDate, clampRight)) };
|
|
5284
|
+
});
|
|
5285
|
+
const chainShifts = chainPush ? delta !== 0 ? pushActivityChain(allTasks ?? [], task.id, activityDrag.id, delta).shifts : [] : (() => {
|
|
5286
|
+
const endDeltaDays = chainEndDeltaDays(activityDrag.mode, delta, startInfo.durationDays);
|
|
5287
|
+
return endDeltaDays !== 0 ? shiftActivityChain(allTasks ?? [], task.id, activityDrag.id, endDeltaDays) : [];
|
|
5288
|
+
})();
|
|
5289
|
+
const changedTasks = [];
|
|
5290
|
+
let hostHandled = false;
|
|
5291
|
+
for (const shift of chainShifts) {
|
|
5292
|
+
if (shift.taskId === task.id) {
|
|
5293
|
+
hostHandled = true;
|
|
5294
|
+
changedTasks.push({
|
|
5295
|
+
...normalizedTask,
|
|
5296
|
+
activities: shift.activities.map((activity) => activity.id === activityDrag.id ? updatedActivities.find((item) => item.id === activityDrag.id) ?? activity : activity)
|
|
5297
|
+
});
|
|
5298
|
+
} else {
|
|
5299
|
+
const source = (allTasks ?? []).find((candidate) => candidate.id === shift.taskId);
|
|
5300
|
+
changedTasks.push({ ...source ?? { id: shift.taskId }, activities: shift.activities });
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
if (!hostHandled) {
|
|
5304
|
+
changedTasks.unshift({ ...normalizedTask, activities: updatedActivities });
|
|
5305
|
+
}
|
|
5306
|
+
onTasksChange?.(changedTasks);
|
|
5307
|
+
}
|
|
5308
|
+
setActivityDrag(null);
|
|
5309
|
+
activityDragStart.current = null;
|
|
5310
|
+
activityConstraintRef.current = null;
|
|
5311
|
+
document.body.style.cursor = "";
|
|
5312
|
+
};
|
|
5313
|
+
document.body.style.cursor = activityDrag.mode === "move" ? "grabbing" : "ew-resize";
|
|
5314
|
+
window.addEventListener("mousemove", onMove);
|
|
5315
|
+
window.addEventListener("mouseup", onUp);
|
|
5316
|
+
return () => {
|
|
5317
|
+
window.removeEventListener("mousemove", onMove);
|
|
5318
|
+
window.removeEventListener("mouseup", onUp);
|
|
5319
|
+
document.body.style.cursor = "";
|
|
5320
|
+
};
|
|
5321
|
+
}, [activityDrag, activityPreviewStore, activityBlockStore, activityDragOwner, allTasks, dayWidth, normalizedTask, onTasksChange, publishChainPreview, task.activities]);
|
|
5322
|
+
const anchorActivityTip = (rect) => {
|
|
5323
|
+
const below = rect.top < 34;
|
|
5324
|
+
const anchor = {
|
|
5325
|
+
left: Math.max(8, Math.min(rect.left, window.innerWidth - 160)),
|
|
5326
|
+
// Вплотную к полосе (1px зазора), сверху или снизу при нехватке места.
|
|
5327
|
+
top: below ? rect.bottom + 1 : rect.top - 1,
|
|
5328
|
+
below
|
|
5329
|
+
};
|
|
5330
|
+
activityTipAnchor.current = anchor;
|
|
5331
|
+
return anchor;
|
|
5332
|
+
};
|
|
5333
|
+
const handleActivityPointerDown = (segment, mode) => (event) => {
|
|
5334
|
+
if (!activityDragEnabled) return;
|
|
5335
|
+
event.preventDefault();
|
|
5336
|
+
event.stopPropagation();
|
|
5337
|
+
activityDragStart.current = {
|
|
5338
|
+
x: event.clientX,
|
|
5339
|
+
durationDays: Math.max(1, Math.round(segment.width / dayWidth) - 1)
|
|
5340
|
+
};
|
|
5341
|
+
activityConstraintRef.current = task.activityChain === "push" && mode !== "resize-right" ? activityStartConstraint(allTasks ?? [], task.id, segment.id) : null;
|
|
5342
|
+
activityDragOwner?.set(activityOwnerKey(task.id, segment.id));
|
|
5343
|
+
setActivityBlockedBy(null);
|
|
5344
|
+
setActivityTipPosition(anchorActivityTip(event.currentTarget.getBoundingClientRect()));
|
|
5345
|
+
setActivityTipId(segment.id);
|
|
5346
|
+
setActivityDrag({ id: segment.id, mode, dayDelta: 0 });
|
|
5347
|
+
};
|
|
5348
|
+
const liveActivityGeometry = (segment) => {
|
|
5349
|
+
if (!activityDrag || activityDrag.id !== segment.id || activityDrag.dayDelta === 0) return segment;
|
|
5350
|
+
if (activityDrag.mode === "move") {
|
|
5351
|
+
return { left: segment.left + activityDrag.dayDelta * dayWidth, width: segment.width };
|
|
5352
|
+
}
|
|
5353
|
+
if (activityDrag.mode === "resize-left") {
|
|
5354
|
+
return { left: segment.left + activityDrag.dayDelta * dayWidth, width: segment.width - activityDrag.dayDelta * dayWidth };
|
|
5355
|
+
}
|
|
5356
|
+
return { left: segment.left, width: segment.width + activityDrag.dayDelta * dayWidth };
|
|
5357
|
+
};
|
|
5358
|
+
const activityLinks = (0, import_react3.useMemo)(() => {
|
|
5359
|
+
const gaps = [];
|
|
5360
|
+
const elbows = [];
|
|
5361
|
+
if (!showActivityLinks || !renderActivities || !activityLayout) return { gaps, elbows };
|
|
5362
|
+
const activities = task.activities ?? [];
|
|
5363
|
+
const segmentById = new Map(activityLayout.segments.map((segment) => [segment.id, segment]));
|
|
5364
|
+
const visibleIds = new Set(visibleActivitySegments.map((segment) => segment.id));
|
|
5365
|
+
const laneTopY = (lane) => activityTopOffset + lane * ACTIVITY_LANE_STEP + (ACTIVITY_LANE_STEP - ACTIVITY_LANE_BAR_HEIGHT) / 2;
|
|
5366
|
+
const liveRect = (segment) => {
|
|
5367
|
+
const geometry = liveActivityGeometry(segment);
|
|
5368
|
+
const chainDelta = activityOverrides?.get(segment.id) ?? 0;
|
|
5369
|
+
const left2 = geometry.left + (chainDelta !== 0 ? chainDelta * dayWidth : 0);
|
|
5370
|
+
return { left: left2, right: left2 + geometry.width };
|
|
5371
|
+
};
|
|
5372
|
+
for (let index = 0; index < activities.length - 1; index += 1) {
|
|
5373
|
+
const predecessor = segmentById.get(activities[index].id);
|
|
5374
|
+
const successor = segmentById.get(activities[index + 1].id);
|
|
5375
|
+
if (!predecessor || !successor) continue;
|
|
5376
|
+
if (!visibleIds.has(predecessor.id) || !visibleIds.has(successor.id)) continue;
|
|
5377
|
+
const lag = activities[index + 1].lag ?? 0;
|
|
5378
|
+
const predRect = liveRect(predecessor);
|
|
5379
|
+
const succRect = liveRect(successor);
|
|
5380
|
+
if (predecessor.lane === successor.lane && succRect.left >= predRect.right) {
|
|
5381
|
+
const width2 = succRect.left - predRect.right;
|
|
5382
|
+
if (width2 >= 1) {
|
|
5383
|
+
gaps.push({ key: `${predecessor.id}->${successor.id}`, left: predRect.right, width: width2, lane: predecessor.lane, lag });
|
|
5384
|
+
}
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
const predecessorLaneTop = laneTopY(predecessor.lane);
|
|
5388
|
+
const successorLaneTop = laneTopY(successor.lane);
|
|
5389
|
+
const sameLane = predecessor.lane === successor.lane;
|
|
5390
|
+
const reverseOrder = !sameLane && successorLaneTop < predecessorLaneTop;
|
|
5391
|
+
const fromY = reverseOrder ? predecessorLaneTop + 6 : predecessorLaneTop + ACTIVITY_LANE_BAR_HEIGHT - 6;
|
|
5392
|
+
const toY = reverseOrder ? successorLaneTop + ACTIVITY_LANE_BAR_HEIGHT - 6 : successorLaneTop + (sameLane ? ACTIVITY_LANE_STEP : 0) + 6;
|
|
5393
|
+
const fromX = predRect.right;
|
|
5394
|
+
const toX = succRect.left;
|
|
5395
|
+
elbows.push({
|
|
5396
|
+
key: `${predecessor.id}->${successor.id}`,
|
|
5397
|
+
d: calculateDependencyPath({ x: fromX, y: fromY }, { x: toX, y: toY }, false),
|
|
5398
|
+
lagX: toX - 14,
|
|
5399
|
+
lagY: reverseOrder ? fromY - 4 : fromY + 12,
|
|
5400
|
+
lag
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
return { gaps, elbows };
|
|
5404
|
+
}, [showActivityLinks, renderActivities, activityLayout, visibleActivitySegments, activityTopOffset, activityOverrides, activityDrag, dayWidth, task.activities]);
|
|
5405
|
+
const activityTipData = (0, import_react3.useMemo)(() => {
|
|
5406
|
+
if (!activityTipId || !task.activities) return null;
|
|
5407
|
+
const activity = task.activities.find((item) => item.id === activityTipId);
|
|
5408
|
+
if (!activity) return null;
|
|
5409
|
+
let startDate = parseUTCDate(activity.startDate);
|
|
5410
|
+
let endDate = parseUTCDate(activity.endDate);
|
|
5411
|
+
if (activityDrag && activityDrag.id === activity.id && activityDrag.dayDelta !== 0) {
|
|
5412
|
+
startDate = shiftActivityDay(startDate, activityDrag.dayDelta);
|
|
5413
|
+
endDate = shiftActivityDay(endDate, activityDrag.dayDelta);
|
|
5414
|
+
}
|
|
5415
|
+
return {
|
|
5416
|
+
floor: task.name,
|
|
5417
|
+
name: activity.name,
|
|
5418
|
+
fields: activity.tooltipFields ?? [],
|
|
5419
|
+
context: {
|
|
5420
|
+
task,
|
|
5421
|
+
activity,
|
|
5422
|
+
startDate,
|
|
5423
|
+
endDate,
|
|
5424
|
+
durationDays: activityDurationDays({ startDate, endDate })
|
|
5425
|
+
}
|
|
4601
5426
|
};
|
|
4602
|
-
}, [
|
|
4603
|
-
const visualLeft = milestone ? displayMilestoneGeometry.left : displayLeft;
|
|
4604
|
-
const visualWidth = milestone ? displayMilestoneGeometry.size : displayWidth;
|
|
4605
|
-
const compositeSegments = (0, import_react3.useMemo)(() => {
|
|
4606
|
-
if (!task.composite || width <= 0) return [];
|
|
4607
|
-
const laneEnds = [];
|
|
4608
|
-
return compositeChildren.map((child) => {
|
|
4609
|
-
const segment = calculateTaskBar(parseUTCDate(child.startDate), parseUTCDate(child.endDate), monthStart, dayWidth);
|
|
4610
|
-
let lane = laneEnds.findIndex((end) => end <= segment.left);
|
|
4611
|
-
if (lane < 0) lane = laneEnds.length;
|
|
4612
|
-
laneEnds[lane] = segment.left + segment.width;
|
|
4613
|
-
return {
|
|
4614
|
-
id: child.id,
|
|
4615
|
-
name: child.name,
|
|
4616
|
-
left: (segment.left - left) / width * 100,
|
|
4617
|
-
width: segment.width / width * 100,
|
|
4618
|
-
lane
|
|
4619
|
-
};
|
|
4620
|
-
});
|
|
4621
|
-
}, [task.composite, compositeChildren, monthStart, dayWidth, left, width, displayLeft]);
|
|
4622
|
-
const compositeLaneCount = Math.max(1, ...compositeSegments.map((segment) => segment.lane + 1));
|
|
4623
|
-
const showCompositeSegments = task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
|
|
4624
|
-
const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
|
|
4625
|
-
const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
|
|
5427
|
+
}, [activityDrag, activityTipId, task, task.activities, task.name]);
|
|
4626
5428
|
const currentStartDate = hasPreviewPosition ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
|
|
4627
5429
|
const currentEndDate = hasPreviewPosition ? milestone ? pixelsToDate(displayLeft, monthStart, dayWidth) : pixelsToDate(displayLeft + displayWidth - dayWidth, monthStart, dayWidth) : taskEndDate;
|
|
4628
5430
|
const dateRangeLabel = formatDateRangeLabel(currentStartDate, currentEndDate);
|
|
@@ -4655,7 +5457,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4655
5457
|
children: [
|
|
4656
5458
|
taskDivider === "top" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-divider gantt-tr-divider-top" }),
|
|
4657
5459
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-taskContainer", children: [
|
|
4658
|
-
shouldRenderBaseline && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5460
|
+
shouldRenderBaseline && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4659
5461
|
"div",
|
|
4660
5462
|
{
|
|
4661
5463
|
className: `gantt-tr-baseline ${isParent ? "gantt-tr-baseline-parent" : ""} ${milestone ? "gantt-tr-baseline-milestone" : ""}`,
|
|
@@ -4665,7 +5467,114 @@ var TaskRow = import_react3.default.memo(
|
|
|
4665
5467
|
}
|
|
4666
5468
|
}
|
|
4667
5469
|
),
|
|
4668
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5470
|
+
renderActivities && activityLayout && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-activityLanes", children: [
|
|
5471
|
+
activityLinks.elbows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { className: "gantt-tr-activityLinksSvg", width: "100%", height: "100%", "aria-hidden": "true", children: [
|
|
5472
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("marker", { id: "arrowhead", markerWidth: "8", markerHeight: "6", markerUnits: "userSpaceOnUse", refX: "7", refY: "3", orient: "auto", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("polygon", { points: "0 0, 8 3, 0 6", fill: "var(--gantt-dependency-line-color, #666666)" }) }) }),
|
|
5473
|
+
activityLinks.elbows.map((link) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
5474
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { className: "gantt-dependency-path", d: link.d, markerEnd: "url(#arrowhead)" }),
|
|
5475
|
+
showActivityLag && link.lag !== 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5476
|
+
"text",
|
|
5477
|
+
{
|
|
5478
|
+
className: "gantt-dependency-lag-label",
|
|
5479
|
+
x: link.lagX,
|
|
5480
|
+
y: link.lagY,
|
|
5481
|
+
textAnchor: "middle",
|
|
5482
|
+
fontSize: "10",
|
|
5483
|
+
fill: "var(--gantt-dependency-line-color, #666666)",
|
|
5484
|
+
children: link.lag > 0 ? `+${link.lag}` : `${link.lag}`
|
|
5485
|
+
}
|
|
5486
|
+
)
|
|
5487
|
+
] }, link.key))
|
|
5488
|
+
] }),
|
|
5489
|
+
visibleActivitySegments.map((segment) => {
|
|
5490
|
+
const live = liveActivityGeometry(segment);
|
|
5491
|
+
const chainDelta = activityOverrides?.get(segment.id) ?? 0;
|
|
5492
|
+
const liveLeft = live.left + (chainDelta !== 0 ? chainDelta * dayWidth : 0);
|
|
5493
|
+
const isDraggingActivity = activityDrag?.id === segment.id;
|
|
5494
|
+
const isBlockingActivity = blockedActivityIds?.has(segment.id) ?? false;
|
|
5495
|
+
const isAtLimit = isDraggingActivity && activityBlockedBy !== null;
|
|
5496
|
+
const isActiveActivity = activationVisualEnabled && activeActivityKey === activityOwnerKey(task.id, segment.id);
|
|
5497
|
+
const isSameWorkType = activeWork !== null && (segment.id === activeWork.id || activeWork.name !== null && segment.name === activeWork.name);
|
|
5498
|
+
const isDimmed = dimOthers && activeWork !== null && !isSameWorkType;
|
|
5499
|
+
const isChainHighlighted = highlightChain && activeWork !== null && isSameWorkType;
|
|
5500
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5501
|
+
"div",
|
|
5502
|
+
{
|
|
5503
|
+
"data-activity-id": segment.id,
|
|
5504
|
+
className: `gantt-tr-activityBar${isDraggingActivity ? " gantt-tr-activityBar-dragging" : ""}${isBlockingActivity ? " gantt-tr-activityBar-blocking" : ""}${isAtLimit ? " gantt-tr-activityBar-atLimit" : ""}${isActiveActivity ? " gantt-tr-activityBar-active" : ""}${isDimmed ? " gantt-tr-activityBar-dimmed" : ""}${isChainHighlighted ? " gantt-tr-activityBar-chain" : ""}${activityDragEnabled ? "" : " gantt-tr-activityBar-locked"}`,
|
|
5505
|
+
style: {
|
|
5506
|
+
left: `${liveLeft}px`,
|
|
5507
|
+
width: `${live.width}px`,
|
|
5508
|
+
top: `${activityTopOffset + segment.lane * ACTIVITY_LANE_STEP + (ACTIVITY_LANE_STEP - ACTIVITY_LANE_BAR_HEIGHT) / 2}px`,
|
|
5509
|
+
height: `${ACTIVITY_LANE_BAR_HEIGHT}px`,
|
|
5510
|
+
backgroundColor: segment.color || "var(--gantt-task-bar-default-color)"
|
|
5511
|
+
},
|
|
5512
|
+
onMouseDown: (event) => {
|
|
5513
|
+
if (!activityDragEnabled) return;
|
|
5514
|
+
const activationKey = activityOwnerKey(task.id, segment.id);
|
|
5515
|
+
const isActive = activityActivationStore?.getActiveKey() === activationKey;
|
|
5516
|
+
if (activationVisualEnabled && !isActive) {
|
|
5517
|
+
activityActivationStore?.setActiveKey(activationKey);
|
|
5518
|
+
}
|
|
5519
|
+
if (activityClickToDrag && !isActive) return;
|
|
5520
|
+
const target = event.target;
|
|
5521
|
+
const mode = target.closest(".gantt-tr-resizeHandleLeft") ? "resize-left" : target.closest(".gantt-tr-resizeHandleRight") ? "resize-right" : "move";
|
|
5522
|
+
handleActivityPointerDown(segment, mode)(event);
|
|
5523
|
+
},
|
|
5524
|
+
onMouseEnter: (event) => {
|
|
5525
|
+
if (activityDrag || activityDragOwner?.get()) return;
|
|
5526
|
+
setActivityTipPosition(anchorActivityTip(event.currentTarget.getBoundingClientRect()));
|
|
5527
|
+
setActivityTipId(segment.id);
|
|
5528
|
+
},
|
|
5529
|
+
onMouseLeave: () => {
|
|
5530
|
+
if (!activityDrag) {
|
|
5531
|
+
setActivityTipId((current) => current === segment.id ? null : current);
|
|
5532
|
+
activityTipAnchor.current = null;
|
|
5533
|
+
setActivityTipPosition(null);
|
|
5534
|
+
}
|
|
5535
|
+
},
|
|
5536
|
+
children: [
|
|
5537
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityName", style: { color: activityTextColor(segment.color) }, children: segment.name }),
|
|
5538
|
+
activityDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
5539
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
|
|
5540
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleRight" })
|
|
5541
|
+
] })
|
|
5542
|
+
]
|
|
5543
|
+
},
|
|
5544
|
+
segment.id
|
|
5545
|
+
);
|
|
5546
|
+
}),
|
|
5547
|
+
activityLinks.gaps.map((gap) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react3.default.Fragment, { children: [
|
|
5548
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5549
|
+
"span",
|
|
5550
|
+
{
|
|
5551
|
+
className: "gantt-tr-activityLink",
|
|
5552
|
+
"aria-hidden": "true",
|
|
5553
|
+
style: {
|
|
5554
|
+
left: `${gap.left}px`,
|
|
5555
|
+
width: `${gap.width}px`,
|
|
5556
|
+
top: `${activityTopOffset + gap.lane * ACTIVITY_LANE_STEP + ACTIVITY_LANE_STEP / 2}px`
|
|
5557
|
+
}
|
|
5558
|
+
}
|
|
5559
|
+
),
|
|
5560
|
+
showActivityLag && gap.lag > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5561
|
+
"span",
|
|
5562
|
+
{
|
|
5563
|
+
className: "gantt-tr-activityLag",
|
|
5564
|
+
title: `\u0417\u0430\u0437\u043E\u0440 ${gap.lag} \u0434`,
|
|
5565
|
+
style: {
|
|
5566
|
+
left: `${gap.left + gap.width - 14}px`,
|
|
5567
|
+
top: `${activityTopOffset + gap.lane * ACTIVITY_LANE_STEP + ACTIVITY_LANE_STEP / 2}px`
|
|
5568
|
+
},
|
|
5569
|
+
children: [
|
|
5570
|
+
"+",
|
|
5571
|
+
gap.lag
|
|
5572
|
+
]
|
|
5573
|
+
}
|
|
5574
|
+
)
|
|
5575
|
+
] }, gap.key))
|
|
5576
|
+
] }),
|
|
5577
|
+
!renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
4669
5578
|
"div",
|
|
4670
5579
|
{
|
|
4671
5580
|
"data-taskbar": true,
|
|
@@ -4749,19 +5658,33 @@ var TaskRow = import_react3.default.memo(
|
|
|
4749
5658
|
}
|
|
4750
5659
|
),
|
|
4751
5660
|
!milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
5661
|
+
shouldRenderCompositeSegments && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-compositeSegments", "aria-hidden": "true", children: [
|
|
5662
|
+
compositeSegments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5663
|
+
"span",
|
|
5664
|
+
{
|
|
5665
|
+
className: "gantt-tr-compositeSegment",
|
|
5666
|
+
style: {
|
|
5667
|
+
left: `${segment.left}%`,
|
|
5668
|
+
width: `${segment.width}%`,
|
|
5669
|
+
top: `${segment.lane / compositeLaneCount * 100}%`,
|
|
5670
|
+
height: `${100 / compositeLaneCount}%`
|
|
5671
|
+
}
|
|
5672
|
+
},
|
|
5673
|
+
segment.id
|
|
5674
|
+
)),
|
|
5675
|
+
compositeSegments.map((segment) => segment.cut ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5676
|
+
"span",
|
|
5677
|
+
{
|
|
5678
|
+
className: "gantt-tr-compositeSegmentCut",
|
|
5679
|
+
style: {
|
|
5680
|
+
left: `${segment.left}%`,
|
|
5681
|
+
top: `${segment.lane / compositeLaneCount * 100}%`,
|
|
5682
|
+
height: `${100 / compositeLaneCount}%`
|
|
5683
|
+
}
|
|
5684
|
+
},
|
|
5685
|
+
`${segment.id}-cut`
|
|
5686
|
+
) : null)
|
|
5687
|
+
] }),
|
|
4765
5688
|
task.composite && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-compositeHoverChevron", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: `gantt-tr-compositeChevron${compositeExpanded ? " gantt-tr-compositeChevron-open" : ""}`, viewBox: "0 0 20 20", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m7 4 6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
|
|
4766
5689
|
showDurationInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-taskDuration", children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
|
|
4767
5690
|
progressWidth > 0 && showProgressInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-progressText", children: [
|
|
@@ -4807,7 +5730,33 @@ var TaskRow = import_react3.default.memo(
|
|
|
4807
5730
|
),
|
|
4808
5731
|
document.body
|
|
4809
5732
|
),
|
|
4810
|
-
|
|
5733
|
+
activityTipData && activityTipPosition && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
|
|
5734
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5735
|
+
"div",
|
|
5736
|
+
{
|
|
5737
|
+
className: `gantt-tr-activityTip${activityTipPosition.below ? " gantt-tr-activityTip-below" : ""}`,
|
|
5738
|
+
style: { left: `${activityTipPosition.left}px`, top: `${activityTipPosition.top}px` },
|
|
5739
|
+
role: "tooltip",
|
|
5740
|
+
children: [
|
|
5741
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipName", children: activityTipData.floor }),
|
|
5742
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipDot", "aria-hidden": "true", children: "\xB7" }),
|
|
5743
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipWork", children: activityTipData.name }),
|
|
5744
|
+
activityTooltip && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-activityTipExtra", children: activityTooltip(activityTipData.context) }),
|
|
5745
|
+
activityTipData.fields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-activityTipFields", children: activityTipData.fields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-activityTipField", children: [
|
|
5746
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipFieldLabel", children: field.label }),
|
|
5747
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipFieldValue", children: field.value })
|
|
5748
|
+
] }, field.label)) }),
|
|
5749
|
+
activityBlockedBy && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-activityTipReason", children: [
|
|
5750
|
+
"\xB7 \u0411\u043B\u043E\u043A: ",
|
|
5751
|
+
activityBlockedBy.names.join(", "),
|
|
5752
|
+
activityBlockedBy.lag > 0 ? ` \xB7 \u0437\u0430\u0437\u043E\u0440 ${activityBlockedBy.lag} \u0434` : ""
|
|
5753
|
+
] })
|
|
5754
|
+
]
|
|
5755
|
+
}
|
|
5756
|
+
),
|
|
5757
|
+
document.body
|
|
5758
|
+
),
|
|
5759
|
+
!disableDependencyEditing && onDependencyPortPointerDown && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
4811
5760
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4812
5761
|
"button",
|
|
4813
5762
|
{
|
|
@@ -4837,7 +5786,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4837
5786
|
}
|
|
4838
5787
|
)
|
|
4839
5788
|
] }),
|
|
4840
|
-
showTaskDateLabels && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5789
|
+
showTaskDateLabels && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4841
5790
|
"div",
|
|
4842
5791
|
{
|
|
4843
5792
|
className: `gantt-tr-leftLabels ${task.locked ? "gantt-tr-leftLabels-locked" : ""}`,
|
|
@@ -4847,7 +5796,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4847
5796
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-dateLabel gantt-tr-dateLabelLeft", children: dateRangeLabel })
|
|
4848
5797
|
}
|
|
4849
5798
|
),
|
|
4850
|
-
task.locked && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5799
|
+
task.locked && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4851
5800
|
"svg",
|
|
4852
5801
|
{
|
|
4853
5802
|
className: "gantt-tr-lockIcon",
|
|
@@ -4868,7 +5817,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4868
5817
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM12 17c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" })
|
|
4869
5818
|
}
|
|
4870
5819
|
),
|
|
4871
|
-
!showDurationInside && !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5820
|
+
!showDurationInside && !milestone && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4872
5821
|
"span",
|
|
4873
5822
|
{
|
|
4874
5823
|
className: `gantt-tr-externalDuration gantt-tr-durationBeforeBar${compactDetail ? " gantt-tr-compositeDuration" : ""}`,
|
|
@@ -4876,7 +5825,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4876
5825
|
children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434`
|
|
4877
5826
|
}
|
|
4878
5827
|
),
|
|
4879
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5828
|
+
!renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
4880
5829
|
"div",
|
|
4881
5830
|
{
|
|
4882
5831
|
className: "gantt-tr-rightLabels",
|
|
@@ -5012,10 +5961,10 @@ var import_react6 = __toESM(require("react"));
|
|
|
5012
5961
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
5013
5962
|
var arePropsEqual2 = (prevProps, nextProps) => {
|
|
5014
5963
|
return prevProps.dayWidth === nextProps.dayWidth && prevProps.dateRange.length === nextProps.dateRange.length && prevProps.totalHeight === nextProps.totalHeight && // skip re-render only when totalHeight unchanged
|
|
5015
|
-
prevProps.viewMode === nextProps.viewMode && prevProps.isCustomWeekend === nextProps.isCustomWeekend;
|
|
5964
|
+
prevProps.viewMode === nextProps.viewMode && prevProps.isCustomWeekend === nextProps.isCustomWeekend && prevProps.showWeekendBlocks === nextProps.showWeekendBlocks && prevProps.showGridLines === nextProps.showGridLines && prevProps.horizontalWindow?.startPx === nextProps.horizontalWindow?.startPx && prevProps.horizontalWindow?.endPx === nextProps.horizontalWindow?.endPx;
|
|
5016
5965
|
};
|
|
5017
5966
|
var GridBackground = import_react6.default.memo(
|
|
5018
|
-
({ dateRange, dayWidth, totalHeight, viewMode = "day", isCustomWeekend, className, showWeekendBlocks = true, showGridLines = true }) => {
|
|
5967
|
+
({ dateRange, dayWidth, totalHeight, viewMode = "day", isCustomWeekend, className, showWeekendBlocks = true, showGridLines = true, horizontalWindow }) => {
|
|
5019
5968
|
const weekGridLines = (0, import_react6.useMemo)(() => {
|
|
5020
5969
|
if (viewMode !== "week") return [];
|
|
5021
5970
|
return calculateWeekGridLines(dateRange, dayWidth);
|
|
@@ -5032,6 +5981,23 @@ var GridBackground = import_react6.default.memo(
|
|
|
5032
5981
|
if (viewMode === "week" || viewMode === "month") return [];
|
|
5033
5982
|
return calculateWeekendBlocks(dateRange, dayWidth, isCustomWeekend);
|
|
5034
5983
|
}, [dateRange, dayWidth, viewMode, isCustomWeekend]);
|
|
5984
|
+
const inHorizontalWindow = (left, width = 1) => !horizontalWindow || left <= horizontalWindow.endPx && left + width >= horizontalWindow.startPx;
|
|
5985
|
+
const visibleWeekendBlocks = (0, import_react6.useMemo)(
|
|
5986
|
+
() => weekendBlocks.filter((block) => inHorizontalWindow(block.left, block.width)),
|
|
5987
|
+
[weekendBlocks, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
5988
|
+
);
|
|
5989
|
+
const visibleWeekGridLines = (0, import_react6.useMemo)(
|
|
5990
|
+
() => weekGridLines.filter((line) => inHorizontalWindow(line.x)),
|
|
5991
|
+
[weekGridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
5992
|
+
);
|
|
5993
|
+
const visibleMonthGridLines = (0, import_react6.useMemo)(
|
|
5994
|
+
() => monthGridLines.filter((line) => inHorizontalWindow(line.x)),
|
|
5995
|
+
[monthGridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
5996
|
+
);
|
|
5997
|
+
const visibleGridLines = (0, import_react6.useMemo)(
|
|
5998
|
+
() => gridLines.filter((line) => inHorizontalWindow(line.x)),
|
|
5999
|
+
[gridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
6000
|
+
);
|
|
5035
6001
|
const gridWidth = (0, import_react6.useMemo)(() => {
|
|
5036
6002
|
return Math.round(dateRange.length * dayWidth);
|
|
5037
6003
|
}, [dateRange.length, dayWidth]);
|
|
@@ -5044,7 +6010,7 @@ var GridBackground = import_react6.default.memo(
|
|
|
5044
6010
|
height: `${totalHeight}px`
|
|
5045
6011
|
},
|
|
5046
6012
|
children: [
|
|
5047
|
-
showWeekendBlocks &&
|
|
6013
|
+
showWeekendBlocks && visibleWeekendBlocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5048
6014
|
"div",
|
|
5049
6015
|
{
|
|
5050
6016
|
className: "gantt-gb-weekendBlock",
|
|
@@ -5057,7 +6023,7 @@ var GridBackground = import_react6.default.memo(
|
|
|
5057
6023
|
)),
|
|
5058
6024
|
showGridLines && (viewMode === "week" ? (
|
|
5059
6025
|
// Week-view: one line per week column boundary
|
|
5060
|
-
|
|
6026
|
+
visibleWeekGridLines.map((line, index) => {
|
|
5061
6027
|
const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : "gantt-gb-weekSeparator";
|
|
5062
6028
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5063
6029
|
"div",
|
|
@@ -5070,7 +6036,7 @@ var GridBackground = import_react6.default.memo(
|
|
|
5070
6036
|
})
|
|
5071
6037
|
) : viewMode === "month" ? (
|
|
5072
6038
|
// Month-view: thin line at each month boundary, thick at year boundary
|
|
5073
|
-
|
|
6039
|
+
visibleMonthGridLines.map((line, index) => {
|
|
5074
6040
|
const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : "gantt-gb-weekSeparator";
|
|
5075
6041
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5076
6042
|
"div",
|
|
@@ -5082,8 +6048,8 @@ var GridBackground = import_react6.default.memo(
|
|
|
5082
6048
|
);
|
|
5083
6049
|
})
|
|
5084
6050
|
) : (
|
|
5085
|
-
// Day-view:
|
|
5086
|
-
|
|
6051
|
+
// Day-view: render only the lines inside the horizontal window.
|
|
6052
|
+
visibleGridLines.map((line, index) => {
|
|
5087
6053
|
const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : line.isWeekStart ? "gantt-gb-weekSeparator" : "gantt-gb-dayLine";
|
|
5088
6054
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5089
6055
|
"div",
|
|
@@ -5195,7 +6161,8 @@ var DependencyLines = import_react7.default.memo(({
|
|
|
5195
6161
|
criticalTaskIds,
|
|
5196
6162
|
businessDays = true,
|
|
5197
6163
|
weekendPredicate,
|
|
5198
|
-
onDependencyClick
|
|
6164
|
+
onDependencyClick,
|
|
6165
|
+
horizontalWindow
|
|
5199
6166
|
}) => {
|
|
5200
6167
|
const [hoveredLineId, setHoveredLineId] = (0, import_react7.useState)(null);
|
|
5201
6168
|
const tasksForPositions = allTasks ?? tasks;
|
|
@@ -5359,6 +6326,10 @@ var DependencyLines = import_react7.default.memo(({
|
|
|
5359
6326
|
}
|
|
5360
6327
|
return lines2;
|
|
5361
6328
|
}, [tasks, allTasks, taskPositions, taskIndices, cycleInfo, collapsedParentIds, criticalTaskIds]);
|
|
6329
|
+
const visibleLines = (0, import_react7.useMemo)(() => {
|
|
6330
|
+
if (!horizontalWindow) return lines;
|
|
6331
|
+
return lines.filter((line) => Math.max(line.fromX, line.toX) >= horizontalWindow.startPx && Math.min(line.fromX, line.toX) <= horizontalWindow.endPx);
|
|
6332
|
+
}, [lines, horizontalWindow?.startPx, horizontalWindow?.endPx]);
|
|
5362
6333
|
const svgHeight = totalHeight ?? tasks.length * rowHeight;
|
|
5363
6334
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
5364
6335
|
"svg",
|
|
@@ -5466,7 +6437,7 @@ var DependencyLines = import_react7.default.memo(({
|
|
|
5466
6437
|
}
|
|
5467
6438
|
)
|
|
5468
6439
|
] }),
|
|
5469
|
-
|
|
6440
|
+
visibleLines.map(({ id, predecessorId, successorId, linkType, path, hasCycle, lag, fromX, toX, fromY, reverseOrder, isVirtual, isCritical }) => {
|
|
5470
6441
|
const isHovered = hoveredLineId === id;
|
|
5471
6442
|
const isSelected = selectedDep != null && id === `${selectedDep.predecessorId}-${selectedDep.successorId}-${selectedDep.linkType}`;
|
|
5472
6443
|
let pathClassName = "gantt-dependency-path";
|
|
@@ -6306,7 +7277,7 @@ var LINK_TYPE_LABELS = {
|
|
|
6306
7277
|
};
|
|
6307
7278
|
|
|
6308
7279
|
// src/components/TaskList/defaultTaskDates.ts
|
|
6309
|
-
var
|
|
7280
|
+
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
6310
7281
|
var DEFAULT_TASK_DURATION_DAYS = 5;
|
|
6311
7282
|
function toISODate(date) {
|
|
6312
7283
|
return date.toISOString().split("T")[0];
|
|
@@ -6335,25 +7306,25 @@ function buildDefaultTaskDateRange(startDate, {
|
|
|
6335
7306
|
}
|
|
6336
7307
|
return {
|
|
6337
7308
|
startDate: toISODate(start),
|
|
6338
|
-
endDate: toISODate(new Date(start.getTime() + (durationDays - 1) *
|
|
7309
|
+
endDate: toISODate(new Date(start.getTime() + (durationDays - 1) * DAY_MS3))
|
|
6339
7310
|
};
|
|
6340
7311
|
}
|
|
6341
7312
|
|
|
6342
7313
|
// src/components/TaskList/TaskListRow.tsx
|
|
6343
7314
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
6344
|
-
var
|
|
7315
|
+
var DAY_MS4 = 24 * 60 * 60 * 1e3;
|
|
6345
7316
|
var LINK_TYPE_ORDER = ["FS", "SS", "FF", "SF"];
|
|
6346
7317
|
var getInclusiveDurationDays = (startDate, endDate) => {
|
|
6347
7318
|
const start = parseUTCDate(startDate);
|
|
6348
7319
|
const end = parseUTCDate(endDate);
|
|
6349
7320
|
return Math.max(
|
|
6350
7321
|
1,
|
|
6351
|
-
Math.round((end.getTime() - start.getTime()) /
|
|
7322
|
+
Math.round((end.getTime() - start.getTime()) / DAY_MS4) + 1
|
|
6352
7323
|
);
|
|
6353
7324
|
};
|
|
6354
7325
|
var getEndDateFromDuration = (startDate, durationDays) => {
|
|
6355
7326
|
const start = parseUTCDate(startDate);
|
|
6356
|
-
return new Date(start.getTime() + (durationDays - 1) *
|
|
7327
|
+
return new Date(start.getTime() + (durationDays - 1) * DAY_MS4).toISOString().split("T")[0];
|
|
6357
7328
|
};
|
|
6358
7329
|
var TrashIcon = () => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
6359
7330
|
"svg",
|
|
@@ -10459,7 +11430,11 @@ var TaskList = ({
|
|
|
10459
11430
|
onActiveCustomCellChange: setActiveCustomCell,
|
|
10460
11431
|
taskListMenuCommands,
|
|
10461
11432
|
hideTaskListRowActions,
|
|
10462
|
-
rowClassName: [
|
|
11433
|
+
rowClassName: [
|
|
11434
|
+
task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "",
|
|
11435
|
+
(task.activities?.length ?? 0) > 0 && (rowHeights?.[index] ?? rowHeight) > rowHeight ? "gantt-tl-row-activities" : "",
|
|
11436
|
+
getTaskListRowClassName?.(task)
|
|
11437
|
+
].filter(Boolean).join(" "),
|
|
10463
11438
|
compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
|
|
10464
11439
|
getTaskListNamePrefixIcon,
|
|
10465
11440
|
taskDateChangeMode,
|
|
@@ -12437,7 +13412,7 @@ var import_react17 = require("react");
|
|
|
12437
13412
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
12438
13413
|
var AUTO_COLUMN_MIN_WIDTH = 72;
|
|
12439
13414
|
var AUTO_COLUMN_MAX_WIDTH = 180;
|
|
12440
|
-
var
|
|
13415
|
+
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
12441
13416
|
function joinClasses(...values) {
|
|
12442
13417
|
return values.filter(Boolean).join(" ");
|
|
12443
13418
|
}
|
|
@@ -12472,8 +13447,8 @@ function getOverlayWidthPercent(column, overlayDateMs) {
|
|
|
12472
13447
|
if (overlayDateMs >= endMs) {
|
|
12473
13448
|
return 100;
|
|
12474
13449
|
}
|
|
12475
|
-
const totalDays = Math.max(1, Math.round((endMs - startMs) /
|
|
12476
|
-
const elapsedDays = Math.min(totalDays, Math.max(0, Math.round((overlayDateMs - startMs) /
|
|
13450
|
+
const totalDays = Math.max(1, Math.round((endMs - startMs) / DAY_MS5) + 1);
|
|
13451
|
+
const elapsedDays = Math.min(totalDays, Math.max(0, Math.round((overlayDateMs - startMs) / DAY_MS5) + 1));
|
|
12477
13452
|
return elapsedDays / totalDays * 100;
|
|
12478
13453
|
}
|
|
12479
13454
|
function isOverlayDateInColumn(column, overlayDateMs) {
|
|
@@ -12803,7 +13778,7 @@ function TableMatrix({
|
|
|
12803
13778
|
// src/components/PlanFactMatrix/PlanFactMatrix.tsx
|
|
12804
13779
|
var import_react18 = __toESM(require("react"));
|
|
12805
13780
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
12806
|
-
var
|
|
13781
|
+
var DAY_MS6 = 24 * 60 * 60 * 1e3;
|
|
12807
13782
|
function joinClasses2(...values) {
|
|
12808
13783
|
return values.filter(Boolean).join(" ");
|
|
12809
13784
|
}
|
|
@@ -12833,8 +13808,8 @@ function getPlannedIndexRange(task, rangeStartMs, rangeLength) {
|
|
|
12833
13808
|
if (rangeLength <= 0) return null;
|
|
12834
13809
|
const start = parseUTCDate(task.startDate);
|
|
12835
13810
|
const end = parseUTCDate(task.endDate);
|
|
12836
|
-
const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) /
|
|
12837
|
-
const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) /
|
|
13811
|
+
const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) / DAY_MS6);
|
|
13812
|
+
const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) / DAY_MS6);
|
|
12838
13813
|
const clampedStartIndex = Math.max(0, startIndex);
|
|
12839
13814
|
const clampedEndIndex = Math.min(rangeLength - 1, endIndex);
|
|
12840
13815
|
if (clampedStartIndex > clampedEndIndex) return null;
|
|
@@ -14203,80 +15178,13 @@ async function printGanttChart({
|
|
|
14203
15178
|
}
|
|
14204
15179
|
}
|
|
14205
15180
|
|
|
14206
|
-
// src/components/GanttChart/previewStore.ts
|
|
14207
|
-
function positionsEqual(left, right) {
|
|
14208
|
-
return left?.left === right?.left && left?.width === right?.width;
|
|
14209
|
-
}
|
|
14210
|
-
function createTaskPreviewPositionStore() {
|
|
14211
|
-
let positions = /* @__PURE__ */ new Map();
|
|
14212
|
-
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
14213
|
-
function notify(taskIds) {
|
|
14214
|
-
for (const taskId of taskIds) {
|
|
14215
|
-
const listeners = listenersByTaskId.get(taskId);
|
|
14216
|
-
if (!listeners) continue;
|
|
14217
|
-
for (const listener of listeners) {
|
|
14218
|
-
listener();
|
|
14219
|
-
}
|
|
14220
|
-
}
|
|
14221
|
-
}
|
|
14222
|
-
return {
|
|
14223
|
-
subscribeTask(taskId, listener) {
|
|
14224
|
-
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
14225
|
-
listeners.add(listener);
|
|
14226
|
-
listenersByTaskId.set(taskId, listeners);
|
|
14227
|
-
return () => {
|
|
14228
|
-
listeners.delete(listener);
|
|
14229
|
-
if (listeners.size === 0) {
|
|
14230
|
-
listenersByTaskId.delete(taskId);
|
|
14231
|
-
}
|
|
14232
|
-
};
|
|
14233
|
-
},
|
|
14234
|
-
getTaskPosition(taskId) {
|
|
14235
|
-
return positions.get(taskId);
|
|
14236
|
-
},
|
|
14237
|
-
setPositions(nextPositions) {
|
|
14238
|
-
const changedTaskIds = /* @__PURE__ */ new Set();
|
|
14239
|
-
const mergedPositions = /* @__PURE__ */ new Map();
|
|
14240
|
-
for (const [taskId, nextPosition] of nextPositions) {
|
|
14241
|
-
const currentPosition = positions.get(taskId);
|
|
14242
|
-
if (positionsEqual(currentPosition, nextPosition)) {
|
|
14243
|
-
if (currentPosition) {
|
|
14244
|
-
mergedPositions.set(taskId, currentPosition);
|
|
14245
|
-
}
|
|
14246
|
-
continue;
|
|
14247
|
-
}
|
|
14248
|
-
changedTaskIds.add(taskId);
|
|
14249
|
-
mergedPositions.set(taskId, {
|
|
14250
|
-
left: nextPosition.left,
|
|
14251
|
-
width: nextPosition.width
|
|
14252
|
-
});
|
|
14253
|
-
}
|
|
14254
|
-
for (const taskId of positions.keys()) {
|
|
14255
|
-
if (!nextPositions.has(taskId)) {
|
|
14256
|
-
changedTaskIds.add(taskId);
|
|
14257
|
-
}
|
|
14258
|
-
}
|
|
14259
|
-
if (changedTaskIds.size === 0) {
|
|
14260
|
-
return;
|
|
14261
|
-
}
|
|
14262
|
-
positions = mergedPositions;
|
|
14263
|
-
notify(changedTaskIds);
|
|
14264
|
-
},
|
|
14265
|
-
clear() {
|
|
14266
|
-
if (positions.size === 0) {
|
|
14267
|
-
return;
|
|
14268
|
-
}
|
|
14269
|
-
const changedTaskIds = new Set(positions.keys());
|
|
14270
|
-
positions = /* @__PURE__ */ new Map();
|
|
14271
|
-
notify(changedTaskIds);
|
|
14272
|
-
}
|
|
14273
|
-
};
|
|
14274
|
-
}
|
|
14275
|
-
|
|
14276
15181
|
// src/components/GanttChart/GanttChart.tsx
|
|
14277
15182
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
14278
15183
|
var SCROLL_TO_ROW_CONTEXT_ROWS = 2;
|
|
14279
15184
|
var TASK_ROW_OVERSCAN = 8;
|
|
15185
|
+
var SCROLL_TOP_BUCKET_PX = 64;
|
|
15186
|
+
var HORIZONTAL_WINDOW_BUCKET_PX = 480;
|
|
15187
|
+
var HORIZONTAL_WINDOW_OVERSCAN_PX = 960;
|
|
14280
15188
|
var PLAN_FACT_COLUMN_OVERSCAN = 24;
|
|
14281
15189
|
var PLAN_FACT_COLUMN_WINDOW_STEP = 14;
|
|
14282
15190
|
var DEFAULT_INITIAL_VIEWPORT_HEIGHT = 768;
|
|
@@ -14469,6 +15377,12 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14469
15377
|
skeletonStartJitterDays = 0,
|
|
14470
15378
|
showTaskDateLabels = true,
|
|
14471
15379
|
showTaskNames = true,
|
|
15380
|
+
showCompositeSegments = true,
|
|
15381
|
+
activityClickToDrag = false,
|
|
15382
|
+
activityActivationMode = "none",
|
|
15383
|
+
showActivityLinks = true,
|
|
15384
|
+
showActivityLag = true,
|
|
15385
|
+
activityTooltip,
|
|
14472
15386
|
timelineMarkers,
|
|
14473
15387
|
additionalColumns,
|
|
14474
15388
|
hiddenTaskListColumns,
|
|
@@ -14502,17 +15416,41 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14502
15416
|
const clearSelectedTaskTimeoutRef = (0, import_react19.useRef)(null);
|
|
14503
15417
|
const hasAutoScrolledToTodayRef = (0, import_react19.useRef)(false);
|
|
14504
15418
|
const previewPositionStoreRef = (0, import_react19.useRef)(null);
|
|
15419
|
+
const activityPreviewStoreRef = (0, import_react19.useRef)(null);
|
|
15420
|
+
const activityBlockStoreRef = (0, import_react19.useRef)(null);
|
|
15421
|
+
const activityDragOwnerRef = (0, import_react19.useRef)(null);
|
|
15422
|
+
const activityActivationStoreRef = (0, import_react19.useRef)(null);
|
|
14505
15423
|
const renderedTaskIdsRef = (0, import_react19.useRef)(/* @__PURE__ */ new Set());
|
|
14506
15424
|
if (previewPositionStoreRef.current === null) {
|
|
14507
15425
|
previewPositionStoreRef.current = createTaskPreviewPositionStore();
|
|
14508
15426
|
}
|
|
15427
|
+
if (activityPreviewStoreRef.current === null) {
|
|
15428
|
+
activityPreviewStoreRef.current = createActivityPreviewStore();
|
|
15429
|
+
}
|
|
15430
|
+
if (activityBlockStoreRef.current === null) {
|
|
15431
|
+
activityBlockStoreRef.current = createActivityBlockStore();
|
|
15432
|
+
}
|
|
15433
|
+
if (activityDragOwnerRef.current === null) {
|
|
15434
|
+
activityDragOwnerRef.current = createActivityDragOwner();
|
|
15435
|
+
}
|
|
15436
|
+
if (activityActivationStoreRef.current === null) {
|
|
15437
|
+
activityActivationStoreRef.current = createActivityActivationStore();
|
|
15438
|
+
}
|
|
14509
15439
|
const previewPositionStore = previewPositionStoreRef.current;
|
|
15440
|
+
const activityPreviewStore = activityPreviewStoreRef.current;
|
|
15441
|
+
const activityBlockStore = activityBlockStoreRef.current;
|
|
15442
|
+
const activityDragOwner = activityDragOwnerRef.current;
|
|
15443
|
+
const activityActivationStore = activityActivationStoreRef.current;
|
|
14510
15444
|
const [selectedTaskId, setSelectedTaskId] = (0, import_react19.useState)(null);
|
|
14511
15445
|
const [taskListHasRightShadow, setTaskListHasRightShadow] = (0, import_react19.useState)(false);
|
|
14512
15446
|
const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = (0, import_react19.useState)("preserve-duration");
|
|
14513
15447
|
const [scrollViewport, setScrollViewport] = (0, import_react19.useState)(() => ({
|
|
14514
15448
|
scrollTop: 0,
|
|
14515
|
-
viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1)
|
|
15449
|
+
viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1),
|
|
15450
|
+
// Ширина видимой области графика и «бакетированный» горизонтальный скролл —
|
|
15451
|
+
// основа горизонтального окна рендера (см. horizontalWindow).
|
|
15452
|
+
viewportWidth: 0,
|
|
15453
|
+
chartScrollLeft: 0
|
|
14516
15454
|
}));
|
|
14517
15455
|
const [forceFullRenderForPrint, setForceFullRenderForPrint] = (0, import_react19.useState)(false);
|
|
14518
15456
|
const [planFactDateWindow, setPlanFactDateWindow] = (0, import_react19.useState)({ start: 0, end: 0 });
|
|
@@ -14690,8 +15628,8 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14690
15628
|
const resolvedRowContentLines = isPlanFactMode ? Math.max(2, Math.floor(rowContentLines)) : Math.max(1, Math.floor(rowContentLines));
|
|
14691
15629
|
const shouldFillParentRowsInTaskList = fillParentRowsInTaskList ?? isPlanFactMode;
|
|
14692
15630
|
const effectiveRowHeight = (0, import_react19.useMemo)(
|
|
14693
|
-
() => Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
|
|
14694
|
-
[resolvedRowContentLines, rowHeight]
|
|
15631
|
+
() => rowContentLines === 0 && !isPlanFactMode ? rowHeight : Math.max(rowHeight, 10 + resolvedRowContentLines * 18),
|
|
15632
|
+
[isPlanFactMode, resolvedRowContentLines, rowContentLines, rowHeight]
|
|
14695
15633
|
);
|
|
14696
15634
|
const normalizedTasks = (0, import_react19.useMemo)(() => {
|
|
14697
15635
|
const ordered = normalizeHierarchyTasks(tasks);
|
|
@@ -14750,14 +15688,21 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14750
15688
|
if (isPlanFactMode) {
|
|
14751
15689
|
return getPlanFactRangeTasks(normalizedTasks);
|
|
14752
15690
|
}
|
|
14753
|
-
|
|
14754
|
-
|
|
15691
|
+
const rangeTasks = [];
|
|
15692
|
+
for (const task of normalizedTasks) {
|
|
15693
|
+
if (showBaseline) {
|
|
15694
|
+
rangeTasks.push({
|
|
15695
|
+
startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
|
|
15696
|
+
endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
|
|
15697
|
+
});
|
|
15698
|
+
} else {
|
|
15699
|
+
rangeTasks.push(task);
|
|
15700
|
+
}
|
|
15701
|
+
for (const activity of task.activities ?? []) {
|
|
15702
|
+
rangeTasks.push({ startDate: activity.startDate, endDate: activity.endDate });
|
|
15703
|
+
}
|
|
14755
15704
|
}
|
|
14756
|
-
return
|
|
14757
|
-
...task,
|
|
14758
|
-
startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
|
|
14759
|
-
endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
|
|
14760
|
-
}));
|
|
15705
|
+
return rangeTasks;
|
|
14761
15706
|
}, [isPlanFactMode, normalizedTasks, showBaseline]);
|
|
14762
15707
|
const dateRange = (0, import_react19.useMemo)(
|
|
14763
15708
|
() => {
|
|
@@ -14802,6 +15747,25 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14802
15747
|
}
|
|
14803
15748
|
return tasks2;
|
|
14804
15749
|
}, [normalizedTasks, collapsedParentIds, filterMode, taskFilter, criticalPathMode, criticalTaskIds]);
|
|
15750
|
+
const monthStart = (0, import_react19.useMemo)(() => {
|
|
15751
|
+
if (dateRange.length === 0) {
|
|
15752
|
+
return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
|
|
15753
|
+
}
|
|
15754
|
+
const firstDay = dateRange[0];
|
|
15755
|
+
if (explicitDateRange && !isPlanFactMode) {
|
|
15756
|
+
return new Date(firstDay);
|
|
15757
|
+
}
|
|
15758
|
+
return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
|
|
15759
|
+
}, [dateRange, explicitDateRange, isPlanFactMode]);
|
|
15760
|
+
const activityLaneCounts = (0, import_react19.useMemo)(() => {
|
|
15761
|
+
const counts = /* @__PURE__ */ new Map();
|
|
15762
|
+
for (const task of visibleTasks) {
|
|
15763
|
+
if (task.activities && task.activities.length > 0) {
|
|
15764
|
+
counts.set(task.id, computeActivityLanes(task.activities, monthStart, dayWidth).laneCount);
|
|
15765
|
+
}
|
|
15766
|
+
}
|
|
15767
|
+
return counts;
|
|
15768
|
+
}, [visibleTasks, monthStart, dayWidth]);
|
|
14805
15769
|
const compositeRowLayout = (0, import_react19.useMemo)(() => {
|
|
14806
15770
|
const compositeIds = new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id));
|
|
14807
15771
|
const tops = [];
|
|
@@ -14809,12 +15773,12 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14809
15773
|
let top = 0;
|
|
14810
15774
|
for (const task of visibleTasks) {
|
|
14811
15775
|
tops.push(top);
|
|
14812
|
-
const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : effectiveRowHeight;
|
|
15776
|
+
const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : Math.max(effectiveRowHeight, activityLanesExtraHeight(activityLaneCounts.get(task.id) ?? 0));
|
|
14813
15777
|
heights.push(height);
|
|
14814
15778
|
top += height;
|
|
14815
15779
|
}
|
|
14816
15780
|
return { tops, heights, totalHeight: top };
|
|
14817
|
-
}, [normalizedTasks, visibleTasks, effectiveRowHeight]);
|
|
15781
|
+
}, [normalizedTasks, visibleTasks, effectiveRowHeight, activityLaneCounts]);
|
|
14818
15782
|
const compositeParentsById = (0, import_react19.useMemo)(
|
|
14819
15783
|
() => new Map(normalizedTasks.filter((task) => task.composite).map((task) => [task.id, task])),
|
|
14820
15784
|
[normalizedTasks]
|
|
@@ -14853,16 +15817,6 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14853
15817
|
}
|
|
14854
15818
|
return `calc(${containerHeight} - ${timelineHeaderHeight}px)`;
|
|
14855
15819
|
}, [containerHeight, isPlanFactMode, isTableMatrixMode, timelineHeaderHeight]);
|
|
14856
|
-
const monthStart = (0, import_react19.useMemo)(() => {
|
|
14857
|
-
if (dateRange.length === 0) {
|
|
14858
|
-
return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
|
|
14859
|
-
}
|
|
14860
|
-
const firstDay = dateRange[0];
|
|
14861
|
-
if (explicitDateRange && !isPlanFactMode) {
|
|
14862
|
-
return new Date(firstDay);
|
|
14863
|
-
}
|
|
14864
|
-
return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
|
|
14865
|
-
}, [dateRange, explicitDateRange, isPlanFactMode]);
|
|
14866
15820
|
const todayIndex = (0, import_react19.useMemo)(() => {
|
|
14867
15821
|
const today = getTodayLocalUtcDate();
|
|
14868
15822
|
return dateRange.findIndex((day) => day.getTime() === today.getTime());
|
|
@@ -14905,12 +15859,23 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14905
15859
|
const nextScrollTop = container.scrollTop;
|
|
14906
15860
|
const nextScrollLeft = container.scrollLeft;
|
|
14907
15861
|
const nextChartScrollLeft = Math.max(0, nextScrollLeft);
|
|
15862
|
+
const bucketedScrollTop = Math.floor(nextScrollTop / SCROLL_TOP_BUCKET_PX) * SCROLL_TOP_BUCKET_PX;
|
|
15863
|
+
const bucketedChartScrollLeft = Math.floor(nextChartScrollLeft / HORIZONTAL_WINDOW_BUCKET_PX) * HORIZONTAL_WINDOW_BUCKET_PX;
|
|
14908
15864
|
setTaskListHasRightShadow(
|
|
14909
15865
|
(previous) => previous === nextHasRightShadow ? previous : nextHasRightShadow
|
|
14910
15866
|
);
|
|
14911
|
-
setScrollViewport(
|
|
14912
|
-
|
|
14913
|
-
|
|
15867
|
+
setScrollViewport((previous) => {
|
|
15868
|
+
const resolvedViewportHeight = previous.viewportHeight > 0 && nextViewportHeight === 0 ? previous.viewportHeight : nextViewportHeight;
|
|
15869
|
+
if (previous.scrollTop === bucketedScrollTop && previous.viewportHeight === resolvedViewportHeight && previous.viewportWidth === nextViewportWidth && previous.chartScrollLeft === bucketedChartScrollLeft) {
|
|
15870
|
+
return previous;
|
|
15871
|
+
}
|
|
15872
|
+
return {
|
|
15873
|
+
scrollTop: bucketedScrollTop,
|
|
15874
|
+
viewportHeight: resolvedViewportHeight,
|
|
15875
|
+
viewportWidth: nextViewportWidth,
|
|
15876
|
+
chartScrollLeft: bucketedChartScrollLeft
|
|
15877
|
+
};
|
|
15878
|
+
});
|
|
14914
15879
|
setPlanFactDateWindow((previous) => {
|
|
14915
15880
|
if (!isPlanFactMode || dateRange.length === 0 || nextViewportWidth <= 0) {
|
|
14916
15881
|
return previous;
|
|
@@ -15026,11 +15991,15 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15026
15991
|
return;
|
|
15027
15992
|
}
|
|
15028
15993
|
previewPositionStore.clear();
|
|
15994
|
+
activityPreviewStore.clear();
|
|
15995
|
+
activityBlockStore.clear();
|
|
15996
|
+
activityDragOwner.set(null);
|
|
15997
|
+
activityActivationStore.setActiveKey(null);
|
|
15029
15998
|
setCascadeOverrides((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
15030
15999
|
setPreviewTasksById((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
15031
16000
|
setDraggedTaskOverride((current) => current === null ? current : null);
|
|
15032
16001
|
setDragGuideLines((current) => current === null ? current : null);
|
|
15033
|
-
}, [controlledScheduleIdentity, previewPositionStore]);
|
|
16002
|
+
}, [controlledScheduleIdentity, previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore]);
|
|
15034
16003
|
(0, import_react19.useEffect)(() => {
|
|
15035
16004
|
const result = validateDependencies(tasks);
|
|
15036
16005
|
setValidationResult(result);
|
|
@@ -15041,7 +16010,11 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15041
16010
|
window.clearTimeout(clearSelectedTaskTimeoutRef.current);
|
|
15042
16011
|
}
|
|
15043
16012
|
previewPositionStore.clear();
|
|
15044
|
-
|
|
16013
|
+
activityPreviewStore.clear();
|
|
16014
|
+
activityBlockStore.clear();
|
|
16015
|
+
activityDragOwner.set(null);
|
|
16016
|
+
activityActivationStore.setActiveKey(null);
|
|
16017
|
+
}, [previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore]);
|
|
15045
16018
|
const handleTaskChange = (0, import_react19.useCallback)((updatedTasks, durationEdit) => {
|
|
15046
16019
|
let updatedTask = updatedTasks[0];
|
|
15047
16020
|
if (!updatedTask) return;
|
|
@@ -15277,6 +16250,15 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15277
16250
|
}
|
|
15278
16251
|
return ids;
|
|
15279
16252
|
}, [draggedTaskOverride]);
|
|
16253
|
+
const horizontalWindow = (0, import_react19.useMemo)(() => {
|
|
16254
|
+
const width = scrollViewport.viewportWidth;
|
|
16255
|
+
if (width <= 0) return void 0;
|
|
16256
|
+
const left = scrollViewport.chartScrollLeft;
|
|
16257
|
+
return {
|
|
16258
|
+
startPx: Math.max(0, left - HORIZONTAL_WINDOW_OVERSCAN_PX),
|
|
16259
|
+
endPx: left + width + HORIZONTAL_WINDOW_OVERSCAN_PX
|
|
16260
|
+
};
|
|
16261
|
+
}, [scrollViewport.chartScrollLeft, scrollViewport.viewportWidth]);
|
|
15280
16262
|
const visibleTaskWindowIndices = (0, import_react19.useMemo)(() => {
|
|
15281
16263
|
const totalTasks = visibleTasks.length;
|
|
15282
16264
|
if (totalTasks === 0) {
|
|
@@ -15611,6 +16593,9 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15611
16593
|
const handlePanStart = (0, import_react19.useCallback)((e) => {
|
|
15612
16594
|
if (e.button !== 0) return;
|
|
15613
16595
|
const target = e.target;
|
|
16596
|
+
if (!target.closest(".gantt-tr-activityBar")) {
|
|
16597
|
+
activityActivationStore.setActiveKey(null);
|
|
16598
|
+
}
|
|
15614
16599
|
if (target.closest("[data-taskbar]")) return;
|
|
15615
16600
|
if (target.closest("input, button, textarea, [contenteditable]")) return;
|
|
15616
16601
|
if (target.closest(".gantt-tl-overlay")) return;
|
|
@@ -15631,7 +16616,7 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15631
16616
|
}
|
|
15632
16617
|
container.style.cursor = "grabbing";
|
|
15633
16618
|
e.preventDefault();
|
|
15634
|
-
}, []);
|
|
16619
|
+
}, [activityActivationStore]);
|
|
15635
16620
|
(0, import_react19.useEffect)(() => {
|
|
15636
16621
|
const flushPanMove = () => {
|
|
15637
16622
|
const pan = panStateRef.current;
|
|
@@ -15861,7 +16846,8 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15861
16846
|
dayWidth,
|
|
15862
16847
|
totalHeight: totalGridHeight,
|
|
15863
16848
|
viewMode,
|
|
15864
|
-
isCustomWeekend
|
|
16849
|
+
isCustomWeekend,
|
|
16850
|
+
horizontalWindow
|
|
15865
16851
|
}
|
|
15866
16852
|
),
|
|
15867
16853
|
todayInRange && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
@@ -15902,6 +16888,7 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15902
16888
|
selectedDep: selectedChip,
|
|
15903
16889
|
businessDays,
|
|
15904
16890
|
weekendPredicate: isCustomWeekend,
|
|
16891
|
+
horizontalWindow,
|
|
15905
16892
|
onDependencyClick: handleDependencyLineClick,
|
|
15906
16893
|
criticalTaskIds: criticalPathMode ? criticalTaskIds : void 0
|
|
15907
16894
|
}
|
|
@@ -16008,6 +16995,7 @@ function TaskGanttChartInner(props, ref) {
|
|
|
16008
16995
|
setDragGuideLines((current) => current === null ? current : null);
|
|
16009
16996
|
setDraggedTaskOverride((current) => current === null ? current : null);
|
|
16010
16997
|
previewPositionStore.clear();
|
|
16998
|
+
activityPreviewStore.clear();
|
|
16011
16999
|
setCascadeOverrides((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
16012
17000
|
setPreviewTasksById((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
16013
17001
|
}
|
|
@@ -16018,6 +17006,16 @@ function TaskGanttChartInner(props, ref) {
|
|
|
16018
17006
|
disableConstraints: disableConstraints ?? false,
|
|
16019
17007
|
deferCascadePreview: normalizedTasks.length >= 1e3,
|
|
16020
17008
|
previewPositionStore,
|
|
17009
|
+
activityPreviewStore,
|
|
17010
|
+
activityBlockStore,
|
|
17011
|
+
activityDragOwner,
|
|
17012
|
+
activityActivationStore,
|
|
17013
|
+
activityClickToDrag,
|
|
17014
|
+
activityActivationMode,
|
|
17015
|
+
showActivityLinks,
|
|
17016
|
+
showActivityLag,
|
|
17017
|
+
activityTooltip,
|
|
17018
|
+
horizontalWindow,
|
|
16021
17019
|
onCascadeProgress: handleCascadeProgress,
|
|
16022
17020
|
onCascade: handleCascade,
|
|
16023
17021
|
onScheduleIntent,
|
|
@@ -16035,6 +17033,7 @@ function TaskGanttChartInner(props, ref) {
|
|
|
16035
17033
|
viewMode,
|
|
16036
17034
|
showTaskDateLabels,
|
|
16037
17035
|
showTaskNames,
|
|
17036
|
+
showCompositeSegments,
|
|
16038
17037
|
onCompositeToggle: task.composite ? (source) => handleCompositeBarToggle(task.id, source) : void 0,
|
|
16039
17038
|
compositeExpanded: task.composite ? !collapsedParentIds.has(task.id) : void 0
|
|
16040
17039
|
}
|
|
@@ -16124,6 +17123,9 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16124
17123
|
};
|
|
16125
17124
|
// Annotate the CommonJS export names for ESM import in node:
|
|
16126
17125
|
0 && (module.exports = {
|
|
17126
|
+
ACTIVITY_LANES_PADDING,
|
|
17127
|
+
ACTIVITY_LANE_BAR_HEIGHT,
|
|
17128
|
+
ACTIVITY_LANE_STEP,
|
|
16127
17129
|
Button,
|
|
16128
17130
|
Calendar,
|
|
16129
17131
|
DAY_MS,
|
|
@@ -16145,6 +17147,10 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16145
17147
|
TaskRow,
|
|
16146
17148
|
TimeScaleHeader,
|
|
16147
17149
|
TodayIndicator,
|
|
17150
|
+
activityChainMinStartDelta,
|
|
17151
|
+
activityLanesExtraHeight,
|
|
17152
|
+
activitySpanMs,
|
|
17153
|
+
activityStartConstraint,
|
|
16148
17154
|
addBusinessDays,
|
|
16149
17155
|
alignToWorkingDay,
|
|
16150
17156
|
and,
|
|
@@ -16167,6 +17173,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16167
17173
|
cascadeByLinks,
|
|
16168
17174
|
clampDateRangeForIncomingFS,
|
|
16169
17175
|
clampTaskRangeForIncomingFS,
|
|
17176
|
+
computeActivityLanes,
|
|
16170
17177
|
computeCriticalPath,
|
|
16171
17178
|
computeLagFromDates,
|
|
16172
17179
|
computeParentDates,
|
|
@@ -16221,10 +17228,12 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16221
17228
|
normalizeUTCDate,
|
|
16222
17229
|
not,
|
|
16223
17230
|
or,
|
|
17231
|
+
packIntervals,
|
|
16224
17232
|
parseDateOnly,
|
|
16225
17233
|
parseUTCDate,
|
|
16226
17234
|
pixelsToDate,
|
|
16227
17235
|
progressInRange,
|
|
17236
|
+
pushActivityChain,
|
|
16228
17237
|
recalculateIncomingLags,
|
|
16229
17238
|
recalculateProjectSchedule,
|
|
16230
17239
|
recalculateTaskFromDependencies,
|
|
@@ -16234,6 +17243,8 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16234
17243
|
resolveDateRangeFromPixels,
|
|
16235
17244
|
resolveTaskHorizontalGeometry,
|
|
16236
17245
|
scaleTaskSubtreeDuration,
|
|
17246
|
+
shiftActivityChain,
|
|
17247
|
+
shiftActivityDate,
|
|
16237
17248
|
shiftBusinessDayOffset,
|
|
16238
17249
|
subtractBusinessDays,
|
|
16239
17250
|
universalCascade,
|