gantt-lib 0.136.1 → 0.138.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/core/scheduling/index.js +40 -3
- package/dist/core/scheduling/index.js.map +1 -1
- package/dist/core/scheduling/index.mjs +40 -3
- package/dist/core/scheduling/index.mjs.map +1 -1
- package/dist/{index-D99kbU4p.d.mts → index-C3mZqSTf.d.mts} +67 -1
- package/dist/{index-D99kbU4p.d.ts → index-C3mZqSTf.d.ts} +67 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +258 -3
- package/dist/index.d.ts +258 -3
- package/dist/index.js +2389 -1331
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2376 -1331
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +176 -1
- 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,11 @@ __export(index_exports, {
|
|
|
54
57
|
TaskRow: () => TaskRow_default,
|
|
55
58
|
TimeScaleHeader: () => TimeScaleHeader_default,
|
|
56
59
|
TodayIndicator: () => TodayIndicator_default,
|
|
60
|
+
activityBounds: () => activityBounds,
|
|
61
|
+
activityChainMinStartDelta: () => activityChainMinStartDelta,
|
|
62
|
+
activityLanesExtraHeight: () => activityLanesExtraHeight,
|
|
63
|
+
activitySpanMs: () => activitySpanMs,
|
|
64
|
+
activityStartConstraint: () => activityStartConstraint,
|
|
57
65
|
addBusinessDays: () => addBusinessDays2,
|
|
58
66
|
alignToWorkingDay: () => alignToWorkingDay,
|
|
59
67
|
and: () => and,
|
|
@@ -76,6 +84,7 @@ __export(index_exports, {
|
|
|
76
84
|
cascadeByLinks: () => cascadeByLinks,
|
|
77
85
|
clampDateRangeForIncomingFS: () => clampDateRangeForIncomingFS,
|
|
78
86
|
clampTaskRangeForIncomingFS: () => clampTaskRangeForIncomingFS,
|
|
87
|
+
computeActivityLanes: () => computeActivityLanes,
|
|
79
88
|
computeCriticalPath: () => computeCriticalPath,
|
|
80
89
|
computeLagFromDates: () => computeLagFromDates,
|
|
81
90
|
computeParentDates: () => computeParentDates,
|
|
@@ -130,10 +139,12 @@ __export(index_exports, {
|
|
|
130
139
|
normalizeUTCDate: () => normalizeUTCDate,
|
|
131
140
|
not: () => not,
|
|
132
141
|
or: () => or,
|
|
142
|
+
packIntervals: () => packIntervals,
|
|
133
143
|
parseDateOnly: () => parseDateOnly,
|
|
134
144
|
parseUTCDate: () => parseUTCDate,
|
|
135
145
|
pixelsToDate: () => pixelsToDate,
|
|
136
146
|
progressInRange: () => progressInRange,
|
|
147
|
+
pushActivityChain: () => pushActivityChain,
|
|
137
148
|
recalculateIncomingLags: () => recalculateIncomingLags,
|
|
138
149
|
recalculateProjectSchedule: () => recalculateProjectSchedule,
|
|
139
150
|
recalculateTaskFromDependencies: () => recalculateTaskFromDependencies,
|
|
@@ -143,6 +154,8 @@ __export(index_exports, {
|
|
|
143
154
|
resolveDateRangeFromPixels: () => resolveDateRangeFromPixels,
|
|
144
155
|
resolveTaskHorizontalGeometry: () => resolveTaskHorizontalGeometry,
|
|
145
156
|
scaleTaskSubtreeDuration: () => scaleTaskSubtreeDuration,
|
|
157
|
+
shiftActivityChain: () => shiftActivityChain,
|
|
158
|
+
shiftActivityDate: () => shiftActivityDate,
|
|
146
159
|
shiftBusinessDayOffset: () => shiftBusinessDayOffset,
|
|
147
160
|
subtractBusinessDays: () => subtractBusinessDays2,
|
|
148
161
|
universalCascade: () => universalCascade,
|
|
@@ -611,949 +624,1487 @@ function subtractBusinessDays2(endDate, businessDays, weekendPredicate) {
|
|
|
611
624
|
return result.toISOString().split("T")[0];
|
|
612
625
|
}
|
|
613
626
|
|
|
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
|
|
627
|
+
// src/utils/geometry.ts
|
|
628
|
+
var getUTCDayDifference = (date1, date2) => {
|
|
629
|
+
const ms1 = Date.UTC(
|
|
630
|
+
date1.getUTCFullYear(),
|
|
631
|
+
date1.getUTCMonth(),
|
|
632
|
+
date1.getUTCDate()
|
|
635
633
|
);
|
|
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
|
-
|
|
634
|
+
const ms2 = Date.UTC(
|
|
635
|
+
date2.getUTCFullYear(),
|
|
636
|
+
date2.getUTCMonth(),
|
|
637
|
+
date2.getUTCDate()
|
|
638
|
+
);
|
|
639
|
+
return Math.round((ms1 - ms2) / (1e3 * 60 * 60 * 24));
|
|
640
|
+
};
|
|
641
|
+
var calculateTaskBar = (taskStartDate, taskEndDate, monthStart, dayWidth) => {
|
|
642
|
+
const startOffset = getUTCDayDifference(taskStartDate, monthStart);
|
|
643
|
+
const duration = getUTCDayDifference(taskEndDate, taskStartDate);
|
|
644
|
+
const left = Math.round(startOffset * dayWidth);
|
|
645
|
+
const width = Math.round((duration + 1) * dayWidth);
|
|
646
|
+
return { left, width };
|
|
647
|
+
};
|
|
648
|
+
var calculateMilestoneGeometry = (taskDate, monthStart, dayWidth, size = 14) => {
|
|
649
|
+
const { left, width } = calculateTaskBar(taskDate, taskDate, monthStart, dayWidth);
|
|
650
|
+
const centerX = Math.round(left + width / 2);
|
|
651
|
+
const halfSize = Math.round(size / 2);
|
|
652
|
+
return {
|
|
653
|
+
centerX,
|
|
654
|
+
left: centerX - halfSize,
|
|
655
|
+
right: centerX + halfSize,
|
|
656
|
+
size
|
|
657
|
+
};
|
|
658
|
+
};
|
|
659
|
+
var calculateMilestoneConnectionBounds = (dayLeft, dayWidth, size = 14) => {
|
|
660
|
+
const halfDiagonal = Math.round(size / Math.SQRT2);
|
|
661
|
+
const centerX = Math.round(dayLeft + dayWidth / 2);
|
|
662
|
+
return {
|
|
663
|
+
left: centerX - halfDiagonal,
|
|
664
|
+
right: centerX + halfDiagonal
|
|
665
|
+
};
|
|
666
|
+
};
|
|
667
|
+
var resolveTaskHorizontalGeometry = (task, monthStart, dayWidth, override) => {
|
|
668
|
+
const startDate = new Date(task.startDate);
|
|
669
|
+
const endDate = new Date(task.endDate);
|
|
670
|
+
if (task.type === "milestone") {
|
|
671
|
+
const size = 14;
|
|
672
|
+
if (override) {
|
|
673
|
+
const bounds2 = calculateMilestoneConnectionBounds(override.left, dayWidth, size);
|
|
674
|
+
return {
|
|
675
|
+
...bounds2,
|
|
676
|
+
centerX: Math.round(override.left + dayWidth / 2)
|
|
677
|
+
};
|
|
662
678
|
}
|
|
679
|
+
const bar2 = calculateTaskBar(startDate, startDate, monthStart, dayWidth);
|
|
680
|
+
const bounds = calculateMilestoneConnectionBounds(bar2.left, dayWidth, size);
|
|
681
|
+
return {
|
|
682
|
+
...bounds,
|
|
683
|
+
centerX: Math.round(bar2.left + dayWidth / 2)
|
|
684
|
+
};
|
|
663
685
|
}
|
|
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;
|
|
686
|
+
if (override) {
|
|
687
|
+
return {
|
|
688
|
+
left: override.left,
|
|
689
|
+
right: override.left + override.width,
|
|
690
|
+
centerX: Math.round(override.left + override.width / 2)
|
|
691
|
+
};
|
|
684
692
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
693
|
+
const bar = calculateTaskBar(startDate, endDate, monthStart, dayWidth);
|
|
694
|
+
return {
|
|
695
|
+
left: bar.left,
|
|
696
|
+
right: bar.left + bar.width,
|
|
697
|
+
centerX: Math.round(bar.left + bar.width / 2)
|
|
698
|
+
};
|
|
699
|
+
};
|
|
700
|
+
var pixelsToDate = (pixels, monthStart, dayWidth) => {
|
|
701
|
+
const days = Math.round(pixels / dayWidth);
|
|
702
|
+
return new Date(Date.UTC(
|
|
703
|
+
monthStart.getUTCFullYear(),
|
|
704
|
+
monthStart.getUTCMonth(),
|
|
705
|
+
monthStart.getUTCDate() + days
|
|
706
|
+
));
|
|
707
|
+
};
|
|
708
|
+
var calculateGridWidth = (daysInMonth, dayWidth) => {
|
|
709
|
+
return Math.round(daysInMonth * dayWidth);
|
|
710
|
+
};
|
|
711
|
+
var MIN_EDGE_ZONE_PX = 8;
|
|
712
|
+
var MIN_MOVE_ZONE_PX = 16;
|
|
713
|
+
var detectEdgeZone = (clientX, taskBarElement, edgeZoneWidth = 12) => {
|
|
714
|
+
const rect = taskBarElement.getBoundingClientRect();
|
|
715
|
+
const relativeX = Math.round(clientX - rect.left);
|
|
716
|
+
const width = Math.round(rect.width);
|
|
717
|
+
const effectiveZone = Math.max(
|
|
718
|
+
MIN_EDGE_ZONE_PX,
|
|
719
|
+
Math.min(edgeZoneWidth, (width - MIN_MOVE_ZONE_PX) / 2)
|
|
695
720
|
);
|
|
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;
|
|
721
|
+
if (relativeX >= 0 && relativeX <= effectiveZone) {
|
|
722
|
+
return "left";
|
|
723
723
|
}
|
|
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);
|
|
724
|
+
if (relativeX >= width - effectiveZone && relativeX <= width) {
|
|
725
|
+
return "right";
|
|
734
726
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
727
|
+
return "move";
|
|
728
|
+
};
|
|
729
|
+
var getCursorForPosition = (position) => {
|
|
730
|
+
switch (position) {
|
|
731
|
+
case "left":
|
|
732
|
+
case "right":
|
|
733
|
+
return "ew-resize";
|
|
734
|
+
case "move":
|
|
735
|
+
return "grab";
|
|
736
|
+
default:
|
|
737
|
+
return "default";
|
|
746
738
|
}
|
|
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));
|
|
739
|
+
};
|
|
740
|
+
var calculateGridLines = (dateRange, dayWidth) => {
|
|
741
|
+
const lines = [];
|
|
742
|
+
for (let i = 0; i < dateRange.length; i++) {
|
|
743
|
+
const date = dateRange[i];
|
|
744
|
+
const x = Math.round(i * dayWidth);
|
|
745
|
+
const isMonthStart = i === 0 ? false : date.getUTCDate() === 1;
|
|
746
|
+
const isWeekStart = date.getUTCDay() === 1;
|
|
747
|
+
lines.push({ x, isMonthStart, isWeekStart });
|
|
762
748
|
}
|
|
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
|
-
}
|
|
749
|
+
if (dateRange.length > 0) {
|
|
750
|
+
lines.push({
|
|
751
|
+
x: Math.round(dateRange.length * dayWidth),
|
|
752
|
+
isMonthStart: false,
|
|
753
|
+
isWeekStart: false
|
|
754
|
+
});
|
|
791
755
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
756
|
+
return lines;
|
|
757
|
+
};
|
|
758
|
+
var calculateWeekendBlocks = (dateRange, dayWidth, isCustomWeekend) => {
|
|
759
|
+
const blocks = [];
|
|
760
|
+
let inWeekend = false;
|
|
761
|
+
let weekendStartIndex = -1;
|
|
762
|
+
for (let i = 0; i < dateRange.length; i++) {
|
|
763
|
+
const date = dateRange[i];
|
|
764
|
+
const isWeekend3 = isCustomWeekend ? isCustomWeekend(date) : date.getUTCDay() === 0 || date.getUTCDay() === 6;
|
|
765
|
+
if (isWeekend3 && !inWeekend) {
|
|
766
|
+
inWeekend = true;
|
|
767
|
+
weekendStartIndex = i;
|
|
768
|
+
} else if (!isWeekend3 && inWeekend) {
|
|
769
|
+
inWeekend = false;
|
|
770
|
+
const left = Math.round(weekendStartIndex * dayWidth);
|
|
771
|
+
const width = Math.round((i - weekendStartIndex) * dayWidth);
|
|
772
|
+
blocks.push({ left, width });
|
|
797
773
|
}
|
|
798
774
|
}
|
|
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
|
-
}
|
|
775
|
+
if (inWeekend && weekendStartIndex >= 0) {
|
|
776
|
+
const left = Math.round(weekendStartIndex * dayWidth);
|
|
777
|
+
const width = Math.round((dateRange.length - weekendStartIndex) * dayWidth);
|
|
778
|
+
blocks.push({ left, width });
|
|
814
779
|
}
|
|
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
|
-
|
|
780
|
+
return blocks;
|
|
781
|
+
};
|
|
782
|
+
var calculateBezierPath = (from, to) => {
|
|
783
|
+
const verticalDistance = Math.abs(to.y - from.y);
|
|
784
|
+
const cpOffset = Math.max(verticalDistance * 0.5, 20);
|
|
785
|
+
if (from.y === to.y) {
|
|
786
|
+
const arcHeight = 20;
|
|
787
|
+
const midX = (from.x + to.x) / 2;
|
|
788
|
+
return `M ${from.x} ${from.y} Q ${midX} ${from.y - arcHeight} ${to.x} ${to.y}`;
|
|
789
|
+
}
|
|
790
|
+
const cp1x = from.x;
|
|
791
|
+
const cp1y = from.y + (to.y > from.y ? cpOffset : -cpOffset);
|
|
792
|
+
const cp2x = to.x;
|
|
793
|
+
const cp2y = to.y - (to.y > from.y ? cpOffset : -cpOffset);
|
|
794
|
+
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)}`;
|
|
795
|
+
};
|
|
796
|
+
var calculateDependencyPath = (from, to, arrivesFromRight) => {
|
|
797
|
+
const fx = Math.round(from.x);
|
|
798
|
+
const fy = Math.round(from.y);
|
|
799
|
+
const tx = Math.round(to.x);
|
|
800
|
+
const ty = Math.round(to.y);
|
|
801
|
+
if (fy === ty) {
|
|
802
|
+
return `M ${fx} ${fy} H ${tx}`;
|
|
803
|
+
}
|
|
804
|
+
if (fx === tx) {
|
|
805
|
+
return `M ${fx} ${fy} V ${ty}`;
|
|
806
|
+
}
|
|
807
|
+
const C = 2;
|
|
808
|
+
const goingDown = ty > fy;
|
|
809
|
+
const dirY = goingDown ? 1 : -1;
|
|
810
|
+
if (arrivesFromRight) {
|
|
811
|
+
const goingRight = tx >= fx;
|
|
812
|
+
const dirX = goingRight ? 1 : -1;
|
|
813
|
+
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
814
|
+
return [
|
|
815
|
+
`M ${fx} ${fy}`,
|
|
816
|
+
`H ${tx - dirX * C}`,
|
|
817
|
+
`L ${tx} ${fy + dirY * C}`,
|
|
818
|
+
`V ${ty}`
|
|
819
|
+
].join(" ");
|
|
846
820
|
}
|
|
847
|
-
|
|
848
|
-
|
|
821
|
+
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
822
|
+
} else {
|
|
823
|
+
const goingRight = tx >= fx;
|
|
824
|
+
const dirX = goingRight ? 1 : -1;
|
|
825
|
+
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
826
|
+
return [
|
|
827
|
+
`M ${fx} ${fy}`,
|
|
828
|
+
`H ${tx - dirX * C}`,
|
|
829
|
+
`L ${tx} ${fy + dirY * C}`,
|
|
830
|
+
`V ${ty}`
|
|
831
|
+
].join(" ");
|
|
832
|
+
}
|
|
833
|
+
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
849
834
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
835
|
+
};
|
|
836
|
+
var calculateWeekGridLines = (dateRange, dayWidth) => {
|
|
837
|
+
const blocks = getWeekBlocks(dateRange);
|
|
838
|
+
const lines = [];
|
|
839
|
+
let currentDayIndex = 0;
|
|
840
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
841
|
+
const block = blocks[i];
|
|
842
|
+
currentDayIndex += block.days;
|
|
843
|
+
const blockEndMonth = dateRange[currentDayIndex - 1];
|
|
844
|
+
const nextBlockMonth = currentDayIndex < dateRange.length ? dateRange[currentDayIndex] : null;
|
|
845
|
+
const isMonthBoundary = nextBlockMonth && blockEndMonth.getUTCMonth() !== nextBlockMonth.getUTCMonth();
|
|
846
|
+
lines.push({
|
|
847
|
+
x: Math.round(currentDayIndex * dayWidth),
|
|
848
|
+
isMonthStart: isMonthBoundary || false
|
|
849
|
+
});
|
|
859
850
|
}
|
|
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);
|
|
851
|
+
if (lines.length > 0 && lines[lines.length - 1].x === Math.round(dateRange.length * dayWidth)) {
|
|
852
|
+
lines.pop();
|
|
867
853
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
854
|
+
return lines;
|
|
855
|
+
};
|
|
856
|
+
var calculateMonthGridLines = (dateRange, dayWidth) => {
|
|
857
|
+
const lines = [];
|
|
858
|
+
for (let i = 1; i < dateRange.length; i++) {
|
|
859
|
+
const date = dateRange[i];
|
|
860
|
+
if (date.getUTCDate() === 1) {
|
|
861
|
+
const x = Math.round(i * dayWidth);
|
|
862
|
+
const isYearBoundary = date.getUTCMonth() === 0;
|
|
863
|
+
lines.push({ x, isMonthStart: isYearBoundary });
|
|
875
864
|
}
|
|
876
865
|
}
|
|
877
|
-
return
|
|
878
|
-
}
|
|
866
|
+
return lines;
|
|
867
|
+
};
|
|
868
|
+
var calculateOrthogonalPath = (from, to) => {
|
|
869
|
+
const fx = Math.round(from.x);
|
|
870
|
+
const fy = Math.round(from.y);
|
|
871
|
+
const tx = Math.round(to.x);
|
|
872
|
+
const ty = Math.round(to.y);
|
|
873
|
+
if (fy === ty) {
|
|
874
|
+
return `M ${fx} ${fy} H ${tx}`;
|
|
875
|
+
}
|
|
876
|
+
const C = 2;
|
|
877
|
+
const goingDown = ty > fy;
|
|
878
|
+
const goingRight = tx >= fx;
|
|
879
|
+
const dirY = goingDown ? 1 : -1;
|
|
880
|
+
const dirX = goingRight ? 1 : -1;
|
|
881
|
+
if (Math.abs(ty - fy) >= C && Math.abs(tx - fx) >= C) {
|
|
882
|
+
return [
|
|
883
|
+
`M ${fx} ${fy}`,
|
|
884
|
+
`H ${tx - dirX * C}`,
|
|
885
|
+
`L ${tx} ${fy + dirY * C}`,
|
|
886
|
+
`V ${ty}`
|
|
887
|
+
].join(" ");
|
|
888
|
+
}
|
|
889
|
+
return `M ${fx} ${fy} H ${tx} V ${ty}`;
|
|
890
|
+
};
|
|
879
891
|
|
|
880
|
-
// src/
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
892
|
+
// src/utils/activities.ts
|
|
893
|
+
var ACTIVITY_LANE_STEP = 28;
|
|
894
|
+
var ACTIVITY_LANE_BAR_HEIGHT = 24;
|
|
895
|
+
var ACTIVITY_LANES_PADDING = 2;
|
|
896
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
897
|
+
function packIntervals(intervals) {
|
|
898
|
+
const sorted = [...intervals].sort((a, b) => a.start - b.start);
|
|
899
|
+
const laneEnds = [];
|
|
900
|
+
return sorted.map((item) => {
|
|
901
|
+
let lane = 0;
|
|
902
|
+
while (lane < laneEnds.length && item.start < laneEnds[lane]) {
|
|
903
|
+
lane += 1;
|
|
904
|
+
}
|
|
905
|
+
laneEnds[lane] = item.end;
|
|
906
|
+
return { ...item, lane };
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
function computeActivityLanes(activities, monthStart, dayWidth) {
|
|
910
|
+
if (activities.length === 0) {
|
|
911
|
+
return { segments: [], laneCount: 0 };
|
|
912
|
+
}
|
|
913
|
+
const packed = packIntervals(activities.map((activity) => {
|
|
914
|
+
const bar = calculateTaskBar(parseUTCDate(activity.startDate), parseUTCDate(activity.endDate), monthStart, dayWidth);
|
|
884
915
|
return {
|
|
885
|
-
|
|
886
|
-
|
|
916
|
+
id: activity.id,
|
|
917
|
+
name: activity.name,
|
|
918
|
+
color: activity.color,
|
|
919
|
+
lag: activity.lag,
|
|
920
|
+
progress: activity.progress,
|
|
921
|
+
start: bar.left,
|
|
922
|
+
end: bar.left + bar.width,
|
|
923
|
+
left: bar.left,
|
|
924
|
+
width: bar.width
|
|
887
925
|
};
|
|
888
|
-
}
|
|
889
|
-
const DAY_MS6 = 24 * 60 * 60 * 1e3;
|
|
926
|
+
}));
|
|
890
927
|
return {
|
|
891
|
-
|
|
892
|
-
|
|
928
|
+
segments: packed.map(({ id, name, color, lag, progress, lane, left, width }) => ({ id, name, color, lag, progress, lane, left, width })),
|
|
929
|
+
laneCount: Math.max(...packed.map((item) => item.lane + 1))
|
|
893
930
|
};
|
|
894
931
|
}
|
|
895
|
-
function
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
932
|
+
function activityBounds(activities) {
|
|
933
|
+
if (!activities || activities.length === 0) return null;
|
|
934
|
+
let min = Number.POSITIVE_INFINITY;
|
|
935
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
936
|
+
for (const activity of activities) {
|
|
937
|
+
const start = parseUTCDate(activity.startDate).getTime();
|
|
938
|
+
const end = parseUTCDate(activity.endDate).getTime();
|
|
939
|
+
if (start < min) min = start;
|
|
940
|
+
if (end > max) max = end;
|
|
941
|
+
}
|
|
942
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) return null;
|
|
943
|
+
const toIso = (ms) => new Date(ms).toISOString().slice(0, 10);
|
|
944
|
+
return { startDate: toIso(min), endDate: toIso(max) };
|
|
945
|
+
}
|
|
946
|
+
function activityLanesExtraHeight(laneCount) {
|
|
947
|
+
if (laneCount <= 1) return 0;
|
|
948
|
+
return laneCount * ACTIVITY_LANE_STEP + ACTIVITY_LANES_PADDING * 2;
|
|
949
|
+
}
|
|
950
|
+
function activitySpanMs(activity) {
|
|
904
951
|
return {
|
|
905
|
-
start:
|
|
906
|
-
end:
|
|
952
|
+
start: parseUTCDate(activity.startDate).getTime(),
|
|
953
|
+
end: parseUTCDate(activity.endDate).getTime() + DAY_MS2
|
|
907
954
|
};
|
|
908
955
|
}
|
|
909
|
-
function
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
|
|
913
|
-
businessDays,
|
|
914
|
-
weekendPredicate,
|
|
915
|
-
snapDirection
|
|
916
|
-
);
|
|
956
|
+
function shiftActivityDate(value, days) {
|
|
957
|
+
const shifted = new Date(parseUTCDate(value).getTime() + days * DAY_MS2);
|
|
958
|
+
return typeof value === "string" ? shifted.toISOString().slice(0, 10) : shifted;
|
|
917
959
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
960
|
+
var CHAIN_KEY_SEPARATOR = "\0";
|
|
961
|
+
var chainKey = (taskId, activityId) => `${taskId}${CHAIN_KEY_SEPARATOR}${activityId}`;
|
|
962
|
+
var toDayNumber = (value) => Math.round(parseUTCDate(value).getTime() / DAY_MS2);
|
|
963
|
+
function buildChainGraph(orderedTasks) {
|
|
964
|
+
const successors = /* @__PURE__ */ new Map();
|
|
965
|
+
const predecessors = /* @__PURE__ */ new Map();
|
|
966
|
+
const info = /* @__PURE__ */ new Map();
|
|
967
|
+
const link = (from, to, lag) => {
|
|
968
|
+
const list = successors.get(from);
|
|
969
|
+
if (list) {
|
|
970
|
+
if (!list.some((edge) => edge.key === to)) list.push({ key: to, lag });
|
|
971
|
+
} else {
|
|
972
|
+
successors.set(from, [{ key: to, lag }]);
|
|
926
973
|
}
|
|
927
|
-
const
|
|
928
|
-
if (
|
|
929
|
-
|
|
974
|
+
const back = predecessors.get(to);
|
|
975
|
+
if (back) {
|
|
976
|
+
if (!back.some((edge) => edge.key === from)) back.push({ key: from, lag });
|
|
977
|
+
} else {
|
|
978
|
+
predecessors.set(to, [{ key: from, lag }]);
|
|
930
979
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
task.
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
980
|
+
};
|
|
981
|
+
const flagged = orderedTasks.filter((task) => Boolean(task.activityChain) && Array.isArray(task.activities) && task.activities.length > 0);
|
|
982
|
+
flagged.forEach((task, floorIdx) => {
|
|
983
|
+
task.activities.forEach((activity, workIdx) => {
|
|
984
|
+
info.set(chainKey(task.id, activity.id), {
|
|
985
|
+
taskId: task.id,
|
|
986
|
+
activityId: activity.id,
|
|
987
|
+
startDay: toDayNumber(activity.startDate),
|
|
988
|
+
durationDays: Math.max(1, toDayNumber(activity.endDate) - toDayNumber(activity.startDate) + 1),
|
|
989
|
+
floorIdx,
|
|
990
|
+
workIdx
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
for (let i = 0; i < task.activities.length - 1; i += 1) {
|
|
994
|
+
link(chainKey(task.id, task.activities[i].id), chainKey(task.id, task.activities[i + 1].id), task.activities[i + 1].lag ?? 0);
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
for (let f = 0; f < flagged.length - 1; f += 1) {
|
|
998
|
+
const current = flagged[f];
|
|
999
|
+
const next = flagged[f + 1];
|
|
1000
|
+
if (current.parentId !== next.parentId) continue;
|
|
1001
|
+
for (const activity of current.activities) {
|
|
1002
|
+
const twin = next.activities.find((candidate) => candidate.id === activity.id || candidate.name === activity.name);
|
|
1003
|
+
if (twin) link(chainKey(current.id, activity.id), chainKey(next.id, twin.id), 0);
|
|
949
1004
|
}
|
|
950
1005
|
}
|
|
951
|
-
|
|
952
|
-
|
|
1006
|
+
return { successors, predecessors, info };
|
|
1007
|
+
}
|
|
1008
|
+
function collectDownstream(graph, startKey) {
|
|
1009
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1010
|
+
const queue = [...(graph.successors.get(startKey) ?? []).map((edge) => edge.key)];
|
|
1011
|
+
while (queue.length > 0) {
|
|
1012
|
+
const key = queue.shift();
|
|
1013
|
+
if (visited.has(key)) continue;
|
|
1014
|
+
visited.add(key);
|
|
1015
|
+
queue.push(...(graph.successors.get(key) ?? []).map((edge) => edge.key));
|
|
953
1016
|
}
|
|
954
|
-
return
|
|
955
|
-
minAllowedStart,
|
|
956
|
-
getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
|
|
957
|
-
businessDays,
|
|
958
|
-
weekendPredicate
|
|
959
|
-
);
|
|
1017
|
+
return visited;
|
|
960
1018
|
}
|
|
961
|
-
function
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const
|
|
965
|
-
if (!
|
|
966
|
-
|
|
1019
|
+
function shiftsFromDeltas(orderedTasks, deltasByTask) {
|
|
1020
|
+
const shifts = [];
|
|
1021
|
+
for (const task of orderedTasks) {
|
|
1022
|
+
const deltas = deltasByTask.get(task.id);
|
|
1023
|
+
if (!deltas || deltas.size === 0) continue;
|
|
1024
|
+
shifts.push({
|
|
1025
|
+
taskId: task.id,
|
|
1026
|
+
shiftedIds: new Set(deltas.keys()),
|
|
1027
|
+
deltas,
|
|
1028
|
+
activities: (task.activities ?? []).map((activity) => deltas.has(activity.id) ? {
|
|
1029
|
+
...activity,
|
|
1030
|
+
startDate: shiftActivityDate(activity.startDate, deltas.get(activity.id)),
|
|
1031
|
+
endDate: shiftActivityDate(activity.endDate, deltas.get(activity.id))
|
|
1032
|
+
} : activity)
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
return shifts;
|
|
1036
|
+
}
|
|
1037
|
+
function shiftActivityChain(orderedTasks, draggedTaskId, draggedActivityId, deltaDays) {
|
|
1038
|
+
if (deltaDays === 0) return [];
|
|
1039
|
+
const graph = buildChainGraph(orderedTasks);
|
|
1040
|
+
const shifted = collectDownstream(graph, chainKey(draggedTaskId, draggedActivityId));
|
|
1041
|
+
if (shifted.size === 0) return [];
|
|
1042
|
+
const deltasByTask = /* @__PURE__ */ new Map();
|
|
1043
|
+
for (const key of shifted) {
|
|
1044
|
+
const node = graph.info.get(key);
|
|
1045
|
+
const set = deltasByTask.get(node.taskId);
|
|
1046
|
+
if (set) set.set(node.activityId, deltaDays);
|
|
1047
|
+
else deltasByTask.set(node.taskId, /* @__PURE__ */ new Map([[node.activityId, deltaDays]]));
|
|
1048
|
+
}
|
|
1049
|
+
return shiftsFromDeltas(orderedTasks, deltasByTask);
|
|
1050
|
+
}
|
|
1051
|
+
function activityStartConstraint(orderedTasks, taskId, activityId) {
|
|
1052
|
+
const graph = buildChainGraph(orderedTasks);
|
|
1053
|
+
const key = chainKey(taskId, activityId);
|
|
1054
|
+
const node = graph.info.get(key);
|
|
1055
|
+
if (!node) return null;
|
|
1056
|
+
const candidates = [];
|
|
1057
|
+
let minStart = Number.NEGATIVE_INFINITY;
|
|
1058
|
+
for (const pred of graph.predecessors.get(key) ?? []) {
|
|
1059
|
+
const predNode = graph.info.get(pred.key);
|
|
1060
|
+
if (!predNode) continue;
|
|
1061
|
+
const min = predNode.startDay + predNode.durationDays + pred.lag;
|
|
1062
|
+
candidates.push({ taskId: predNode.taskId, activityId: predNode.activityId, min, lag: pred.lag });
|
|
1063
|
+
if (min > minStart) minStart = min;
|
|
1064
|
+
}
|
|
1065
|
+
if (!Number.isFinite(minStart)) return null;
|
|
1066
|
+
const blockers = candidates.filter((candidate) => candidate.min === minStart);
|
|
1067
|
+
return {
|
|
1068
|
+
minStartDay: minStart,
|
|
1069
|
+
minStartDelta: minStart - node.startDay,
|
|
1070
|
+
blockers: blockers.map(({ taskId: blockerTaskId, activityId: blockerActivityId }) => ({
|
|
1071
|
+
taskId: blockerTaskId,
|
|
1072
|
+
activityId: blockerActivityId
|
|
1073
|
+
})),
|
|
1074
|
+
lag: Math.max(0, ...blockers.map((blocker) => blocker.lag))
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function activityChainMinStartDelta(orderedTasks, taskId, activityId) {
|
|
1078
|
+
return activityStartConstraint(orderedTasks, taskId, activityId)?.minStartDelta ?? null;
|
|
1079
|
+
}
|
|
1080
|
+
function pushActivityChain(orderedTasks, draggedTaskId, draggedActivityId, requestedDeltaDays) {
|
|
1081
|
+
const graph = buildChainGraph(orderedTasks);
|
|
1082
|
+
const draggedKey = chainKey(draggedTaskId, draggedActivityId);
|
|
1083
|
+
if (!graph.info.has(draggedKey)) {
|
|
1084
|
+
return { shifts: [], draggedDeltaDays: requestedDeltaDays };
|
|
1085
|
+
}
|
|
1086
|
+
const downstream = collectDownstream(graph, draggedKey);
|
|
1087
|
+
if (downstream.size === 0) {
|
|
1088
|
+
return { shifts: [], draggedDeltaDays: requestedDeltaDays };
|
|
1089
|
+
}
|
|
1090
|
+
const newStart = /* @__PURE__ */ new Map();
|
|
1091
|
+
const dragged = graph.info.get(draggedKey);
|
|
1092
|
+
newStart.set(draggedKey, dragged.startDay + requestedDeltaDays);
|
|
1093
|
+
const endOfDay = (key) => {
|
|
1094
|
+
const node = graph.info.get(key);
|
|
1095
|
+
return (newStart.get(key) ?? node.startDay) + node.durationDays;
|
|
1096
|
+
};
|
|
1097
|
+
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);
|
|
1098
|
+
for (const { key, node } of order) {
|
|
1099
|
+
let earliest = Number.NEGATIVE_INFINITY;
|
|
1100
|
+
let oldEarliest = Number.NEGATIVE_INFINITY;
|
|
1101
|
+
for (const pred of graph.predecessors.get(key) ?? []) {
|
|
1102
|
+
const predNode = graph.info.get(pred.key);
|
|
1103
|
+
earliest = Math.max(earliest, endOfDay(pred.key) + pred.lag);
|
|
1104
|
+
oldEarliest = Math.max(oldEarliest, predNode.startDay + predNode.durationDays + pred.lag);
|
|
1105
|
+
}
|
|
1106
|
+
let nextStart;
|
|
1107
|
+
if (earliest > node.startDay) {
|
|
1108
|
+
nextStart = earliest;
|
|
1109
|
+
} else if (earliest < oldEarliest) {
|
|
1110
|
+
nextStart = earliest;
|
|
1111
|
+
} else {
|
|
1112
|
+
nextStart = node.startDay;
|
|
967
1113
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
task.type
|
|
981
|
-
);
|
|
982
|
-
return { ...dep, lag: nextLag };
|
|
983
|
-
});
|
|
1114
|
+
newStart.set(key, nextStart);
|
|
1115
|
+
}
|
|
1116
|
+
const deltasByTask = /* @__PURE__ */ new Map();
|
|
1117
|
+
for (const key of downstream) {
|
|
1118
|
+
const node = graph.info.get(key);
|
|
1119
|
+
const delta = (newStart.get(key) ?? node.startDay) - node.startDay;
|
|
1120
|
+
if (delta === 0) continue;
|
|
1121
|
+
const set = deltasByTask.get(node.taskId);
|
|
1122
|
+
if (set) set.set(node.activityId, delta);
|
|
1123
|
+
else deltasByTask.set(node.taskId, /* @__PURE__ */ new Map([[node.activityId, delta]]));
|
|
1124
|
+
}
|
|
1125
|
+
return { shifts: shiftsFromDeltas(orderedTasks, deltasByTask), draggedDeltaDays: requestedDeltaDays };
|
|
984
1126
|
}
|
|
985
1127
|
|
|
986
|
-
// src/core/scheduling/
|
|
987
|
-
function
|
|
988
|
-
|
|
989
|
-
|
|
1128
|
+
// src/core/scheduling/dependencies.ts
|
|
1129
|
+
function normalizePredecessorDates(predecessor, parseDateFn) {
|
|
1130
|
+
const predStart = parseDateFn(predecessor.startDate);
|
|
1131
|
+
const predEnd = predecessor.type === "milestone" ? predStart : parseDateFn(predecessor.endDate);
|
|
1132
|
+
return { predStart, predEnd };
|
|
1133
|
+
}
|
|
1134
|
+
function getDependencyLag(dep) {
|
|
1135
|
+
return Number.isFinite(dep.lag) ? dep.lag : 0;
|
|
1136
|
+
}
|
|
1137
|
+
function normalizeTaskDependencyLags(task) {
|
|
1138
|
+
return task;
|
|
1139
|
+
}
|
|
1140
|
+
function normalizeDependencyLag(linkType, lag, predecessorStart, predecessorEnd, businessDays = false, weekendPredicate, successorType) {
|
|
1141
|
+
if (linkType !== "FS") {
|
|
1142
|
+
return lag;
|
|
990
1143
|
}
|
|
991
|
-
|
|
1144
|
+
const predecessorDuration = getTaskDuration(
|
|
1145
|
+
predecessorStart,
|
|
1146
|
+
predecessorEnd,
|
|
1147
|
+
businessDays,
|
|
1148
|
+
weekendPredicate
|
|
1149
|
+
);
|
|
1150
|
+
const minimumLag = successorType === "milestone" ? -Math.max(0, predecessorDuration - 1) : -predecessorDuration;
|
|
1151
|
+
return Math.max(minimumLag, lag);
|
|
992
1152
|
}
|
|
993
|
-
function
|
|
994
|
-
const
|
|
995
|
-
const
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1153
|
+
function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate, successorType) {
|
|
1154
|
+
const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
|
|
1155
|
+
const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
|
|
1156
|
+
const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
|
|
1157
|
+
const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
|
|
1158
|
+
if (!businessDays || !weekendPredicate) {
|
|
1159
|
+
switch (linkType) {
|
|
1160
|
+
case "FS":
|
|
1161
|
+
return normalizeDependencyLag(
|
|
1162
|
+
linkType,
|
|
1163
|
+
Math.round((sS - pE) / DAY_MS) - (successorType === "milestone" ? 0 : 1),
|
|
1164
|
+
predStart,
|
|
1165
|
+
predEnd,
|
|
1166
|
+
businessDays,
|
|
1167
|
+
weekendPredicate,
|
|
1168
|
+
successorType
|
|
1169
|
+
);
|
|
1170
|
+
case "SS":
|
|
1171
|
+
return Math.round((sS - pS) / DAY_MS);
|
|
1172
|
+
case "FF":
|
|
1173
|
+
return Math.round((sE - pE) / DAY_MS);
|
|
1174
|
+
case "SF":
|
|
1175
|
+
return Math.round((sE - pS) / DAY_MS) + 1;
|
|
1003
1176
|
}
|
|
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
1177
|
}
|
|
1011
|
-
|
|
1178
|
+
const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
|
|
1179
|
+
const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
|
|
1180
|
+
const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
|
|
1181
|
+
switch (linkType) {
|
|
1182
|
+
case "FS":
|
|
1183
|
+
return normalizeDependencyLag(
|
|
1184
|
+
linkType,
|
|
1185
|
+
businessOffset - (successorType === "milestone" ? 0 : 1),
|
|
1186
|
+
predStart,
|
|
1187
|
+
predEnd,
|
|
1188
|
+
businessDays,
|
|
1189
|
+
weekendPredicate,
|
|
1190
|
+
successorType
|
|
1191
|
+
);
|
|
1192
|
+
case "SS":
|
|
1193
|
+
return businessOffset;
|
|
1194
|
+
case "FF":
|
|
1195
|
+
return businessOffset;
|
|
1196
|
+
case "SF":
|
|
1197
|
+
return businessOffset + 1;
|
|
1198
|
+
}
|
|
1012
1199
|
}
|
|
1013
|
-
function
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1200
|
+
function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate, successorType) {
|
|
1201
|
+
const normalizedLag = normalizeDependencyLag(
|
|
1202
|
+
linkType,
|
|
1203
|
+
lag,
|
|
1204
|
+
predecessorStart,
|
|
1205
|
+
predecessorEnd,
|
|
1206
|
+
businessDays,
|
|
1207
|
+
weekendPredicate,
|
|
1208
|
+
successorType
|
|
1209
|
+
);
|
|
1210
|
+
if (!businessDays || !weekendPredicate) {
|
|
1211
|
+
switch (linkType) {
|
|
1212
|
+
case "FS":
|
|
1213
|
+
return new Date(predecessorEnd.getTime() + (normalizedLag + (successorType === "milestone" ? 0 : 1)) * DAY_MS);
|
|
1214
|
+
case "SS":
|
|
1215
|
+
return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
|
|
1216
|
+
case "FF":
|
|
1217
|
+
return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
|
|
1218
|
+
case "SF":
|
|
1219
|
+
return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
|
|
1035
1220
|
}
|
|
1036
1221
|
}
|
|
1037
|
-
|
|
1222
|
+
const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
|
|
1223
|
+
let offset;
|
|
1224
|
+
switch (linkType) {
|
|
1225
|
+
case "FS":
|
|
1226
|
+
offset = normalizedLag + (successorType === "milestone" ? 0 : 1);
|
|
1227
|
+
break;
|
|
1228
|
+
case "SS":
|
|
1229
|
+
offset = normalizedLag;
|
|
1230
|
+
break;
|
|
1231
|
+
case "FF":
|
|
1232
|
+
offset = normalizedLag;
|
|
1233
|
+
break;
|
|
1234
|
+
case "SF":
|
|
1235
|
+
offset = normalizedLag - 1;
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
1038
1239
|
}
|
|
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
|
-
}
|
|
1240
|
+
|
|
1241
|
+
// src/core/scheduling/criticalPath.ts
|
|
1242
|
+
function computeCriticalPath(tasks, options = {}) {
|
|
1243
|
+
const { businessDays = false, weekendPredicate } = options;
|
|
1244
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
1245
|
+
const childIds = /* @__PURE__ */ new Set();
|
|
1246
|
+
for (const task of tasks) {
|
|
1247
|
+
if (task.parentId) childIds.add(task.parentId);
|
|
1248
|
+
}
|
|
1249
|
+
const leafTasks = tasks.filter((task) => !childIds.has(task.id));
|
|
1250
|
+
const leafIds = new Set(leafTasks.map((task) => task.id));
|
|
1251
|
+
const leafIdsByAncestor = /* @__PURE__ */ new Map();
|
|
1252
|
+
for (const leaf of leafTasks) {
|
|
1253
|
+
let ancestor = leaf.parentId;
|
|
1254
|
+
while (ancestor) {
|
|
1255
|
+
const set = leafIdsByAncestor.get(ancestor) ?? /* @__PURE__ */ new Set();
|
|
1256
|
+
set.add(leaf.id);
|
|
1257
|
+
leafIdsByAncestor.set(ancestor, set);
|
|
1258
|
+
ancestor = taskById.get(ancestor)?.parentId;
|
|
1072
1259
|
}
|
|
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
|
-
|
|
1260
|
+
}
|
|
1261
|
+
const durationOf = (task) => getTaskDuration(task.startDate, task.endDate, businessDays, weekendPredicate);
|
|
1262
|
+
const projectStartDate = leafTasks.reduce((earliest, task) => {
|
|
1263
|
+
const taskStart = parseDateOnly(task.startDate);
|
|
1264
|
+
return taskStart.getTime() < earliest.getTime() ? taskStart : earliest;
|
|
1265
|
+
}, parseDateOnly(leafTasks[0]?.startDate ?? /* @__PURE__ */ new Date()));
|
|
1266
|
+
const offsetFromProjectStart = (date) => {
|
|
1267
|
+
const targetDate = parseDateOnly(date);
|
|
1268
|
+
if (businessDays && weekendPredicate) {
|
|
1269
|
+
return getBusinessDayOffset(projectStartDate, targetDate, weekendPredicate);
|
|
1270
|
+
}
|
|
1271
|
+
return Math.round((targetDate.getTime() - projectStartDate.getTime()) / DAY_MS);
|
|
1272
|
+
};
|
|
1273
|
+
const scheduledStart = /* @__PURE__ */ new Map();
|
|
1274
|
+
for (const leaf of leafTasks) {
|
|
1275
|
+
scheduledStart.set(leaf.id, offsetFromProjectStart(leaf.startDate));
|
|
1276
|
+
}
|
|
1277
|
+
const succByPred = /* @__PURE__ */ new Map();
|
|
1278
|
+
const predBySucc = /* @__PURE__ */ new Map();
|
|
1279
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
1280
|
+
const addEdge = (predecessorId, succId, lag, type) => {
|
|
1281
|
+
const edgeKey = `${predecessorId}|${succId}|${type}`;
|
|
1282
|
+
if (seenEdges.has(edgeKey)) return;
|
|
1283
|
+
seenEdges.add(edgeKey);
|
|
1284
|
+
const outEdges = succByPred.get(predecessorId) ?? [];
|
|
1285
|
+
outEdges.push({ succId, lag, type });
|
|
1286
|
+
succByPred.set(predecessorId, outEdges);
|
|
1287
|
+
const inEdges = predBySucc.get(succId) ?? [];
|
|
1288
|
+
inEdges.push({ predId: predecessorId, lag, type });
|
|
1289
|
+
predBySucc.set(succId, inEdges);
|
|
1290
|
+
};
|
|
1291
|
+
for (const succ of leafTasks) {
|
|
1292
|
+
for (const dep of succ.dependencies ?? []) {
|
|
1293
|
+
const lag = getDependencyLag(dep);
|
|
1294
|
+
if (leafIds.has(dep.taskId)) {
|
|
1295
|
+
addEdge(dep.taskId, succ.id, lag, dep.type);
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
const descendants = leafIdsByAncestor.get(dep.taskId);
|
|
1299
|
+
if (!descendants || descendants.size === 0) continue;
|
|
1300
|
+
for (const leafId of descendants) {
|
|
1301
|
+
if (leafId === succ.id) continue;
|
|
1302
|
+
addEdge(leafId, succ.id, lag, dep.type);
|
|
1106
1303
|
}
|
|
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
1304
|
}
|
|
1116
1305
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
);
|
|
1126
|
-
const
|
|
1127
|
-
const
|
|
1128
|
-
const queue = [...initialChain];
|
|
1306
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
1307
|
+
for (const leaf of leafTasks) indegree.set(leaf.id, 0);
|
|
1308
|
+
for (const edges of succByPred.values()) {
|
|
1309
|
+
for (const { succId } of edges) {
|
|
1310
|
+
indegree.set(succId, (indegree.get(succId) ?? 0) + 1);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
const order = [];
|
|
1314
|
+
const enqueued = /* @__PURE__ */ new Set();
|
|
1315
|
+
const queue = leafTasks.filter((leaf) => (indegree.get(leaf.id) ?? 0) === 0).map((leaf) => leaf.id);
|
|
1316
|
+
for (const id of queue) enqueued.add(id);
|
|
1129
1317
|
while (queue.length > 0) {
|
|
1130
1318
|
const current = queue.shift();
|
|
1131
|
-
|
|
1132
|
-
for (const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1319
|
+
order.push(current);
|
|
1320
|
+
for (const { succId } of succByPred.get(current) ?? []) {
|
|
1321
|
+
const next = (indegree.get(succId) ?? 0) - 1;
|
|
1322
|
+
indegree.set(succId, next);
|
|
1323
|
+
if (next === 0 && !enqueued.has(succId)) {
|
|
1324
|
+
enqueued.add(succId);
|
|
1325
|
+
queue.push(succId);
|
|
1137
1326
|
}
|
|
1138
1327
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1328
|
+
}
|
|
1329
|
+
const projectFinish = Math.max(
|
|
1330
|
+
0,
|
|
1331
|
+
...leafTasks.map((leaf) => (scheduledStart.get(leaf.id) ?? 0) + durationOf(leaf))
|
|
1332
|
+
);
|
|
1333
|
+
const LF = /* @__PURE__ */ new Map();
|
|
1334
|
+
const LS = /* @__PURE__ */ new Map();
|
|
1335
|
+
for (const leaf of leafTasks) LF.set(leaf.id, projectFinish);
|
|
1336
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
1337
|
+
const id = order[i];
|
|
1338
|
+
const task = taskById.get(id);
|
|
1339
|
+
const durPred = durationOf(task);
|
|
1340
|
+
const outEdges = succByPred.get(id) ?? [];
|
|
1341
|
+
let lf = projectFinish;
|
|
1342
|
+
if (outEdges.length > 0) {
|
|
1343
|
+
const candidates = outEdges.map(({ succId, lag, type }) => {
|
|
1344
|
+
const succLs = LS.get(succId) ?? projectFinish;
|
|
1345
|
+
const durSucc = durationOf(taskById.get(succId));
|
|
1346
|
+
switch (type) {
|
|
1347
|
+
case "FS":
|
|
1348
|
+
return succLs - lag;
|
|
1349
|
+
case "SS":
|
|
1350
|
+
return succLs - lag + durPred;
|
|
1351
|
+
case "FF":
|
|
1352
|
+
return succLs + durSucc - lag;
|
|
1353
|
+
case "SF":
|
|
1354
|
+
return succLs + durSucc - lag + durPred;
|
|
1355
|
+
default:
|
|
1356
|
+
return projectFinish;
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
lf = Math.min(projectFinish, ...candidates);
|
|
1146
1360
|
}
|
|
1361
|
+
LF.set(id, lf);
|
|
1362
|
+
LS.set(id, lf - durPred);
|
|
1147
1363
|
}
|
|
1148
|
-
|
|
1364
|
+
const critical = /* @__PURE__ */ new Set();
|
|
1365
|
+
const ordered = new Set(order);
|
|
1366
|
+
for (const leaf of leafTasks) {
|
|
1367
|
+
if (!ordered.has(leaf.id)) continue;
|
|
1368
|
+
const hasGraphEdge = (succByPred.get(leaf.id)?.length ?? 0) > 0 || (predBySucc.get(leaf.id)?.length ?? 0) > 0;
|
|
1369
|
+
if (!hasGraphEdge && (leaf.dependencies?.length ?? 0) > 0) continue;
|
|
1370
|
+
const es = scheduledStart.get(leaf.id) ?? 0;
|
|
1371
|
+
const ls = LS.get(leaf.id) ?? 0;
|
|
1372
|
+
if (ls - es <= 0) critical.add(leaf.id);
|
|
1373
|
+
}
|
|
1374
|
+
return critical;
|
|
1149
1375
|
}
|
|
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];
|
|
1376
|
+
function extendCriticalIdsToParents(criticalLeafIds, tasks) {
|
|
1377
|
+
const result = new Set(criticalLeafIds);
|
|
1378
|
+
const parentByChild = /* @__PURE__ */ new Map();
|
|
1379
|
+
for (const task of tasks) {
|
|
1380
|
+
if (task.parentId) parentByChild.set(task.id, task.parentId);
|
|
1159
1381
|
}
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const MAX_ITERATIONS = allTasks.length * 3;
|
|
1168
|
-
while (queue.length > 0 && iterations < MAX_ITERATIONS) {
|
|
1169
|
-
iterations++;
|
|
1170
|
-
const [currentId, arrivalMode] = queue.shift();
|
|
1171
|
-
const { start: currStart, end: currEnd } = updatedDates.get(currentId);
|
|
1172
|
-
const currentOriginal = taskById.get(currentId);
|
|
1173
|
-
if (arrivalMode !== "parent-recalc") {
|
|
1174
|
-
const children = childrenByParentId.get(currentId) ?? [];
|
|
1175
|
-
for (const child of children) {
|
|
1176
|
-
if (childShifted.has(child.id) || child.locked) continue;
|
|
1177
|
-
const parentOrigStart = new Date(currentOriginal.startDate);
|
|
1178
|
-
const parentOrigEnd = new Date(currentOriginal.endDate);
|
|
1179
|
-
const childOrigStart = new Date(child.startDate);
|
|
1180
|
-
const childOrigEnd = new Date(child.endDate);
|
|
1181
|
-
const startDeltaMs = currStart.getTime() - parentOrigStart.getTime();
|
|
1182
|
-
const endDeltaMs = currEnd.getTime() - parentOrigEnd.getTime();
|
|
1183
|
-
let childNewStart;
|
|
1184
|
-
let childNewEnd;
|
|
1185
|
-
if (businessDays && weekendPredicate) {
|
|
1186
|
-
const proposedStart = new Date(childOrigStart.getTime() + startDeltaMs);
|
|
1187
|
-
const snapDirection = currStart.getTime() >= parentOrigStart.getTime() ? 1 : -1;
|
|
1188
|
-
const movedRange = moveTaskRange(
|
|
1189
|
-
child.startDate,
|
|
1190
|
-
child.endDate,
|
|
1191
|
-
proposedStart,
|
|
1192
|
-
true,
|
|
1193
|
-
weekendPredicate,
|
|
1194
|
-
snapDirection
|
|
1195
|
-
);
|
|
1196
|
-
childNewStart = movedRange.start;
|
|
1197
|
-
childNewEnd = movedRange.end;
|
|
1198
|
-
} else {
|
|
1199
|
-
childNewStart = new Date(childOrigStart.getTime() + startDeltaMs);
|
|
1200
|
-
childNewEnd = new Date(childOrigEnd.getTime() + endDeltaMs);
|
|
1201
|
-
}
|
|
1202
|
-
const prev = updatedDates.get(child.id);
|
|
1203
|
-
if (prev && prev.start.getTime() === childNewStart.getTime() && prev.end.getTime() === childNewEnd.getTime()) {
|
|
1204
|
-
continue;
|
|
1205
|
-
}
|
|
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
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
const parentId = currentOriginal.parentId;
|
|
1217
|
-
if (parentId && parentId !== movedTask.id) {
|
|
1218
|
-
const parent = taskById.get(parentId);
|
|
1219
|
-
if (parent && !parent.locked) {
|
|
1220
|
-
const siblings = childrenByParentId.get(parentId) ?? [];
|
|
1221
|
-
const siblingPositions = siblings.map((sib) => {
|
|
1222
|
-
if (updatedDates.has(sib.id)) return updatedDates.get(sib.id);
|
|
1223
|
-
return { start: new Date(sib.startDate), end: new Date(sib.endDate) };
|
|
1224
|
-
});
|
|
1225
|
-
const minStart = new Date(Math.min(...siblingPositions.map((p) => p.start.getTime())));
|
|
1226
|
-
const maxEnd = new Date(Math.max(...siblingPositions.map((p) => p.end.getTime())));
|
|
1227
|
-
const prev = updatedDates.get(parentId);
|
|
1228
|
-
if (!prev || prev.start.getTime() !== minStart.getTime() || prev.end.getTime() !== maxEnd.getTime()) {
|
|
1229
|
-
updatedDates.set(parentId, { start: minStart, end: maxEnd });
|
|
1230
|
-
queue.push([parentId, "parent-recalc"]);
|
|
1231
|
-
resultMap.set(parentId, normalizeTaskDependencyLags({
|
|
1232
|
-
...parent,
|
|
1233
|
-
startDate: minStart.toISOString().split("T")[0],
|
|
1234
|
-
endDate: maxEnd.toISOString().split("T")[0]
|
|
1235
|
-
}));
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
const dependents = dependentsByPredecessorId.get(currentId) ?? [];
|
|
1240
|
-
for (const { task, dependencyIndex } of dependents) {
|
|
1241
|
-
if (task.locked || !task.dependencies) continue;
|
|
1242
|
-
if (childShifted.has(task.id)) continue;
|
|
1243
|
-
const dep = task.dependencies[dependencyIndex];
|
|
1244
|
-
if (!dep) continue;
|
|
1245
|
-
const origStart = new Date(task.startDate);
|
|
1246
|
-
const origEnd = new Date(task.endDate);
|
|
1247
|
-
const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
|
|
1248
|
-
{
|
|
1249
|
-
startDate: currStart,
|
|
1250
|
-
endDate: currEnd,
|
|
1251
|
-
type: currentOriginal.type
|
|
1252
|
-
},
|
|
1253
|
-
parseCascadeDateInput
|
|
1254
|
-
);
|
|
1255
|
-
const constraintDate = calculateSuccessorDate(
|
|
1256
|
-
normalizedPredStart,
|
|
1257
|
-
normalizedPredEnd,
|
|
1258
|
-
dep.type,
|
|
1259
|
-
getDependencyLag(dep),
|
|
1260
|
-
businessDays,
|
|
1261
|
-
weekendPredicate,
|
|
1262
|
-
task.type
|
|
1263
|
-
);
|
|
1264
|
-
let succNewStart;
|
|
1265
|
-
let succNewEnd;
|
|
1266
|
-
const duration = getTaskDuration(origStart, origEnd, businessDays, weekendPredicate);
|
|
1267
|
-
if (dep.type === "FS" || dep.type === "SS") {
|
|
1268
|
-
({ start: succNewStart, end: succNewEnd } = buildTaskRangeFromStart(
|
|
1269
|
-
constraintDate,
|
|
1270
|
-
duration,
|
|
1271
|
-
businessDays,
|
|
1272
|
-
weekendPredicate
|
|
1273
|
-
));
|
|
1274
|
-
} else {
|
|
1275
|
-
({ start: succNewStart, end: succNewEnd } = buildTaskRangeFromEnd(
|
|
1276
|
-
constraintDate,
|
|
1277
|
-
duration,
|
|
1278
|
-
businessDays,
|
|
1279
|
-
weekendPredicate
|
|
1280
|
-
));
|
|
1281
|
-
}
|
|
1282
|
-
const prev = updatedDates.get(task.id);
|
|
1283
|
-
if (prev && prev.start.getTime() === succNewStart.getTime() && prev.end.getTime() === succNewEnd.getTime()) {
|
|
1284
|
-
continue;
|
|
1285
|
-
}
|
|
1286
|
-
updatedDates.set(task.id, { start: succNewStart, end: succNewEnd });
|
|
1287
|
-
queue.push([task.id, "dependency"]);
|
|
1288
|
-
resultMap.set(task.id, normalizeTaskDependencyLags({
|
|
1289
|
-
...task,
|
|
1290
|
-
startDate: succNewStart.toISOString().split("T")[0],
|
|
1291
|
-
endDate: succNewEnd.toISOString().split("T")[0]
|
|
1292
|
-
}));
|
|
1382
|
+
const stack = Array.from(criticalLeafIds);
|
|
1383
|
+
while (stack.length > 0) {
|
|
1384
|
+
const id = stack.pop();
|
|
1385
|
+
const parentId = parentByChild.get(id);
|
|
1386
|
+
if (parentId && !result.has(parentId)) {
|
|
1387
|
+
result.add(parentId);
|
|
1388
|
+
stack.push(parentId);
|
|
1293
1389
|
}
|
|
1294
1390
|
}
|
|
1295
|
-
return
|
|
1391
|
+
return result;
|
|
1296
1392
|
}
|
|
1297
1393
|
|
|
1298
|
-
// src/core/scheduling/
|
|
1299
|
-
function
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1394
|
+
// src/core/scheduling/commands.ts
|
|
1395
|
+
function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
1396
|
+
const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
|
|
1397
|
+
if (businessDays && weekendPredicate) {
|
|
1398
|
+
return {
|
|
1399
|
+
start: normalizedStart,
|
|
1400
|
+
end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
1404
|
+
return {
|
|
1405
|
+
start: normalizedStart,
|
|
1406
|
+
end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS7)
|
|
1407
|
+
};
|
|
1304
1408
|
}
|
|
1305
|
-
function
|
|
1306
|
-
const
|
|
1307
|
-
if (
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1409
|
+
function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
|
|
1410
|
+
const normalizedEnd = businessDays && weekendPredicate ? alignToWorkingDay(endDate, snapDirection, weekendPredicate) : normalizeUTCDate(endDate);
|
|
1411
|
+
if (businessDays && weekendPredicate) {
|
|
1412
|
+
return {
|
|
1413
|
+
start: parseDateOnly(subtractBusinessDays(normalizedEnd, duration, weekendPredicate)),
|
|
1414
|
+
end: normalizedEnd
|
|
1415
|
+
};
|
|
1312
1416
|
}
|
|
1313
|
-
const
|
|
1314
|
-
const endDates = children.map((c) => new Date(c.endDate));
|
|
1315
|
-
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
1316
|
-
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
1417
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
1317
1418
|
return {
|
|
1318
|
-
|
|
1319
|
-
|
|
1419
|
+
start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS7),
|
|
1420
|
+
end: normalizedEnd
|
|
1320
1421
|
};
|
|
1321
1422
|
}
|
|
1322
|
-
function
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1423
|
+
function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
1424
|
+
return buildTaskRangeFromStart(
|
|
1425
|
+
proposedStart,
|
|
1426
|
+
getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
|
|
1427
|
+
businessDays,
|
|
1428
|
+
weekendPredicate,
|
|
1429
|
+
snapDirection
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate, taskById) {
|
|
1433
|
+
if (!task.dependencies?.length) {
|
|
1434
|
+
return { start: proposedStart, end: proposedEnd };
|
|
1326
1435
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1436
|
+
let minAllowedStart = null;
|
|
1437
|
+
for (const dep of task.dependencies) {
|
|
1438
|
+
if (dep.type !== "FS") {
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
1442
|
+
if (!predecessor) {
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(predecessor, parseDateOnly);
|
|
1446
|
+
const predecessorDuration = getTaskDuration(
|
|
1447
|
+
predecessorStart,
|
|
1448
|
+
predecessorEnd,
|
|
1449
|
+
businessDays,
|
|
1450
|
+
weekendPredicate
|
|
1451
|
+
);
|
|
1452
|
+
const candidateMinStart = calculateSuccessorDate(
|
|
1453
|
+
predecessorStart,
|
|
1454
|
+
predecessorEnd,
|
|
1455
|
+
"FS",
|
|
1456
|
+
-predecessorDuration,
|
|
1457
|
+
businessDays,
|
|
1458
|
+
weekendPredicate,
|
|
1459
|
+
task.type
|
|
1460
|
+
);
|
|
1461
|
+
if (!minAllowedStart || candidateMinStart.getTime() > minAllowedStart.getTime()) {
|
|
1462
|
+
minAllowedStart = candidateMinStart;
|
|
1463
|
+
}
|
|
1337
1464
|
}
|
|
1338
|
-
if (
|
|
1339
|
-
return
|
|
1465
|
+
if (!minAllowedStart || proposedStart.getTime() >= minAllowedStart.getTime()) {
|
|
1466
|
+
return { start: proposedStart, end: proposedEnd };
|
|
1340
1467
|
}
|
|
1341
|
-
return
|
|
1468
|
+
return buildTaskRangeFromStart(
|
|
1469
|
+
minAllowedStart,
|
|
1470
|
+
getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
|
|
1471
|
+
businessDays,
|
|
1472
|
+
weekendPredicate
|
|
1473
|
+
);
|
|
1342
1474
|
}
|
|
1343
|
-
function
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if (
|
|
1348
|
-
|
|
1349
|
-
const children = getChildren(taskId, tasks);
|
|
1350
|
-
for (const child of children) {
|
|
1351
|
-
descendants.push(child);
|
|
1352
|
-
collectChildren(child.id);
|
|
1475
|
+
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate, taskById) {
|
|
1476
|
+
if (!task.dependencies) return [];
|
|
1477
|
+
return task.dependencies.map((dep) => {
|
|
1478
|
+
const predecessor = taskById?.get(dep.taskId) ?? allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
1479
|
+
if (!predecessor) {
|
|
1480
|
+
return { ...dep, lag: getDependencyLag(dep) };
|
|
1353
1481
|
}
|
|
1482
|
+
const { predStart: predecessorStart, predEnd: predecessorEnd } = normalizePredecessorDates(
|
|
1483
|
+
predecessor,
|
|
1484
|
+
(d) => new Date(d instanceof Date ? d.getTime() : `${String(d).split("T")[0]}T00:00:00.000Z`)
|
|
1485
|
+
);
|
|
1486
|
+
const nextLag = computeLagFromDates(
|
|
1487
|
+
dep.type,
|
|
1488
|
+
predecessorStart,
|
|
1489
|
+
predecessorEnd,
|
|
1490
|
+
newStartDate,
|
|
1491
|
+
newEndDate,
|
|
1492
|
+
businessDays,
|
|
1493
|
+
weekendPredicate,
|
|
1494
|
+
task.type
|
|
1495
|
+
);
|
|
1496
|
+
return { ...dep, lag: nextLag };
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/core/scheduling/cascade.ts
|
|
1501
|
+
function parseCascadeDateInput(date) {
|
|
1502
|
+
if (date instanceof Date) {
|
|
1503
|
+
return normalizeUTCDate(date);
|
|
1354
1504
|
}
|
|
1355
|
-
|
|
1356
|
-
return descendants;
|
|
1505
|
+
return normalizeUTCDate(/* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`));
|
|
1357
1506
|
}
|
|
1358
|
-
function
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
return [task];
|
|
1507
|
+
function projectDayDelta(from, to, businessDays, weekendPredicate) {
|
|
1508
|
+
if (businessDays && weekendPredicate) {
|
|
1509
|
+
return getBusinessDayOffset(from, to, weekendPredicate);
|
|
1362
1510
|
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1511
|
+
return Math.round((to.getTime() - from.getTime()) / DAY_MS);
|
|
1512
|
+
}
|
|
1513
|
+
function shiftTaskActivities(activities, dayDelta, businessDays, weekendPredicate) {
|
|
1514
|
+
if (!activities || activities.length === 0 || dayDelta === 0) return void 0;
|
|
1515
|
+
const shiftDate = (value) => {
|
|
1516
|
+
const base = parseDateOnly(value);
|
|
1517
|
+
const shifted = businessDays && weekendPredicate ? shiftBusinessDayOffset(base, dayDelta, weekendPredicate) : new Date(base.getTime() + dayDelta * DAY_MS);
|
|
1518
|
+
return typeof value === "string" ? shifted.toISOString().slice(0, 10) : shifted;
|
|
1519
|
+
};
|
|
1520
|
+
return activities.map((activity) => ({
|
|
1521
|
+
...activity,
|
|
1522
|
+
startDate: shiftDate(activity.startDate),
|
|
1523
|
+
endDate: shiftDate(activity.endDate)
|
|
1524
|
+
}));
|
|
1525
|
+
}
|
|
1526
|
+
function shiftedActivitiesPatch(activities, dayDelta, businessDays, weekendPredicate) {
|
|
1527
|
+
const shifted = shiftTaskActivities(activities, dayDelta, businessDays, weekendPredicate);
|
|
1528
|
+
return shifted ? { activities: shifted } : {};
|
|
1529
|
+
}
|
|
1530
|
+
function createCascadeContext(allTasks) {
|
|
1531
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
1532
|
+
const dependentsByPredecessorId = /* @__PURE__ */ new Map();
|
|
1533
|
+
const taskById = /* @__PURE__ */ new Map();
|
|
1534
|
+
for (const task of allTasks) {
|
|
1535
|
+
taskById.set(task.id, task);
|
|
1536
|
+
if (task.parentId) {
|
|
1537
|
+
const children = childrenByParentId.get(task.parentId) ?? [];
|
|
1538
|
+
children.push(task);
|
|
1539
|
+
childrenByParentId.set(task.parentId, children);
|
|
1540
|
+
}
|
|
1541
|
+
if (!task.dependencies) continue;
|
|
1542
|
+
task.dependencies.forEach((dependency, dependencyIndex) => {
|
|
1543
|
+
const dependents = dependentsByPredecessorId.get(dependency.taskId) ?? [];
|
|
1544
|
+
dependents.push({ task, dependencyIndex });
|
|
1545
|
+
dependentsByPredecessorId.set(dependency.taskId, dependents);
|
|
1546
|
+
});
|
|
1366
1547
|
}
|
|
1367
|
-
return
|
|
1368
|
-
{ ...task, progress },
|
|
1369
|
-
...descendants.map((descendant) => ({ ...descendant, progress }))
|
|
1370
|
-
];
|
|
1548
|
+
return { childrenByParentId, dependentsByPredecessorId, taskById };
|
|
1371
1549
|
}
|
|
1372
|
-
function
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1550
|
+
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
1551
|
+
const { dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
|
|
1552
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1553
|
+
const queue = [draggedTaskId];
|
|
1554
|
+
const chain = [];
|
|
1555
|
+
visited.add(draggedTaskId);
|
|
1556
|
+
while (queue.length > 0) {
|
|
1557
|
+
const current = queue.shift();
|
|
1558
|
+
const successors = dependentsByPredecessorId.get(current) ?? [];
|
|
1559
|
+
for (const { task } of successors) {
|
|
1560
|
+
if (!linkTypes.includes(task.dependencies?.find((dependency) => dependency.taskId === current)?.type ?? "FS")) {
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
const sid = task.id;
|
|
1564
|
+
if (!visited.has(sid)) {
|
|
1565
|
+
visited.add(sid);
|
|
1566
|
+
const successorTask = taskById.get(sid);
|
|
1567
|
+
if (successorTask) {
|
|
1568
|
+
chain.push(successorTask);
|
|
1569
|
+
queue.push(sid);
|
|
1570
|
+
}
|
|
1383
1571
|
}
|
|
1384
1572
|
}
|
|
1385
1573
|
}
|
|
1386
|
-
return
|
|
1574
|
+
return chain;
|
|
1387
1575
|
}
|
|
1388
|
-
function
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1576
|
+
function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
|
|
1577
|
+
const { childrenByParentId, dependentsByPredecessorId, taskById } = createCascadeContext(allTasks);
|
|
1578
|
+
const updatedDates = /* @__PURE__ */ new Map();
|
|
1579
|
+
updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
|
|
1580
|
+
const result = [];
|
|
1581
|
+
const queue = [movedTaskId];
|
|
1582
|
+
const visited = /* @__PURE__ */ new Set([movedTaskId]);
|
|
1583
|
+
while (queue.length > 0) {
|
|
1584
|
+
const currentId = queue.shift();
|
|
1585
|
+
const { start: predStart, end: predEnd } = updatedDates.get(currentId);
|
|
1586
|
+
if (!skipChildCascade) {
|
|
1587
|
+
const children = childrenByParentId.get(currentId) ?? [];
|
|
1588
|
+
for (const child of children) {
|
|
1589
|
+
if (visited.has(child.id) || child.locked) continue;
|
|
1590
|
+
const origStart = new Date(child.startDate);
|
|
1591
|
+
const origEnd = new Date(child.endDate);
|
|
1592
|
+
const durationMs = origEnd.getTime() - origStart.getTime();
|
|
1593
|
+
const parentOrig = taskById.get(currentId);
|
|
1594
|
+
const parentOrigStart = new Date(parentOrig.startDate);
|
|
1595
|
+
const parentOrigEnd = new Date(parentOrig.endDate);
|
|
1596
|
+
const parentStartDelta = predStart.getTime() - parentOrigStart.getTime();
|
|
1597
|
+
const parentEndDelta = predEnd.getTime() - parentOrigEnd.getTime();
|
|
1598
|
+
const newChildStart = new Date(origStart.getTime() + parentStartDelta);
|
|
1599
|
+
const newChildEnd = new Date(origEnd.getTime() + parentEndDelta);
|
|
1600
|
+
visited.add(child.id);
|
|
1601
|
+
updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
|
|
1602
|
+
result.push(normalizeTaskDependencyLags({
|
|
1603
|
+
...child,
|
|
1604
|
+
startDate: newChildStart.toISOString().split("T")[0],
|
|
1605
|
+
endDate: newChildEnd.toISOString().split("T")[0],
|
|
1606
|
+
...shiftedActivitiesPatch(child.activities, Math.round(parentStartDelta / DAY_MS), false)
|
|
1607
|
+
}));
|
|
1608
|
+
queue.push(child.id);
|
|
1396
1609
|
}
|
|
1397
|
-
|
|
1610
|
+
}
|
|
1611
|
+
const dependents = dependentsByPredecessorId.get(currentId) ?? [];
|
|
1612
|
+
for (const { task, dependencyIndex } of dependents) {
|
|
1613
|
+
if (visited.has(task.id) || !task.dependencies || task.locked) continue;
|
|
1614
|
+
const dep = task.dependencies[dependencyIndex];
|
|
1615
|
+
if (!dep) continue;
|
|
1616
|
+
const orig = taskById.get(task.id);
|
|
1617
|
+
const origStart = new Date(orig.startDate);
|
|
1618
|
+
const origEnd = new Date(orig.endDate);
|
|
1619
|
+
const duration = getTaskDuration(origStart, origEnd);
|
|
1620
|
+
const currentTask = taskById.get(currentId);
|
|
1621
|
+
const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
|
|
1622
|
+
{
|
|
1623
|
+
startDate: predStart,
|
|
1624
|
+
endDate: predEnd,
|
|
1625
|
+
type: currentTask.type
|
|
1626
|
+
},
|
|
1627
|
+
parseCascadeDateInput
|
|
1628
|
+
);
|
|
1629
|
+
const constraintDate = calculateSuccessorDate(
|
|
1630
|
+
normalizedPredStart,
|
|
1631
|
+
normalizedPredEnd,
|
|
1632
|
+
dep.type,
|
|
1633
|
+
getDependencyLag(dep),
|
|
1634
|
+
false,
|
|
1635
|
+
void 0,
|
|
1636
|
+
task.type
|
|
1637
|
+
);
|
|
1638
|
+
let newSuccStart;
|
|
1639
|
+
let newSuccEnd;
|
|
1640
|
+
if (dep.type === "FS" || dep.type === "SS") {
|
|
1641
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
|
|
1642
|
+
} else {
|
|
1643
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
|
|
1644
|
+
}
|
|
1645
|
+
visited.add(task.id);
|
|
1646
|
+
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
1647
|
+
result.push(normalizeTaskDependencyLags({
|
|
1398
1648
|
...task,
|
|
1399
|
-
|
|
1400
|
-
|
|
1649
|
+
startDate: newSuccStart.toISOString().split("T")[0],
|
|
1650
|
+
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
1651
|
+
}));
|
|
1652
|
+
queue.push(task.id);
|
|
1401
1653
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
}
|
|
1405
|
-
function findParentId(taskId, tasks) {
|
|
1406
|
-
const task = tasks.find((t) => t.id === taskId);
|
|
1407
|
-
return task?.parentId;
|
|
1654
|
+
}
|
|
1655
|
+
return result;
|
|
1408
1656
|
}
|
|
1409
|
-
function
|
|
1410
|
-
const
|
|
1411
|
-
const
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1657
|
+
function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
|
|
1658
|
+
const { childrenByParentId, dependentsByPredecessorId } = createCascadeContext(allTasks);
|
|
1659
|
+
const directChildren = childrenByParentId.get(changedTaskId) ?? [];
|
|
1660
|
+
const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
|
|
1661
|
+
const initialChain = [...directChildren, ...directSuccessors].filter(
|
|
1662
|
+
(task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
|
|
1663
|
+
);
|
|
1664
|
+
const chain = [...initialChain];
|
|
1665
|
+
const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
|
|
1666
|
+
const queue = [...initialChain];
|
|
1667
|
+
while (queue.length > 0) {
|
|
1668
|
+
const current = queue.shift();
|
|
1669
|
+
const children = childrenByParentId.get(current.id) ?? [];
|
|
1670
|
+
for (const child of children) {
|
|
1671
|
+
if (!visited.has(child.id)) {
|
|
1672
|
+
visited.add(child.id);
|
|
1673
|
+
chain.push(child);
|
|
1674
|
+
queue.push(child);
|
|
1675
|
+
}
|
|
1416
1676
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1677
|
+
const successors = dependentsByPredecessorId.get(current.id) ?? [];
|
|
1678
|
+
for (const { task: successor } of successors) {
|
|
1679
|
+
if (!visited.has(successor.id)) {
|
|
1680
|
+
visited.add(successor.id);
|
|
1681
|
+
chain.push(successor);
|
|
1682
|
+
queue.push(successor);
|
|
1683
|
+
}
|
|
1419
1684
|
}
|
|
1420
|
-
visited.add(current.parentId);
|
|
1421
|
-
current = taskById.get(current.parentId);
|
|
1422
|
-
}
|
|
1423
|
-
return false;
|
|
1424
|
-
}
|
|
1425
|
-
function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
|
|
1426
|
-
if (taskId1 === taskId2) {
|
|
1427
|
-
return true;
|
|
1428
1685
|
}
|
|
1429
|
-
return
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
// src/core/scheduling/execute.ts
|
|
1433
|
-
function toIsoDate(date) {
|
|
1434
|
-
return date.toISOString().split("T")[0];
|
|
1435
|
-
}
|
|
1436
|
-
function createChangedResult(snapshot, nextTasks) {
|
|
1437
|
-
const originalById = new Map(snapshot.map((task) => [task.id, task]));
|
|
1438
|
-
const changedTasks = nextTasks.filter((task) => JSON.stringify(originalById.get(task.id)) !== JSON.stringify(task));
|
|
1439
|
-
return {
|
|
1440
|
-
changedTasks,
|
|
1441
|
-
changedIds: changedTasks.map((task) => task.id)
|
|
1442
|
-
};
|
|
1686
|
+
return chain;
|
|
1443
1687
|
}
|
|
1444
|
-
function
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
task.endDate,
|
|
1454
|
-
newStart,
|
|
1455
|
-
businessDays,
|
|
1456
|
-
weekendPredicate
|
|
1457
|
-
);
|
|
1458
|
-
const updatedDependencies = recalculateIncomingLags(
|
|
1459
|
-
task,
|
|
1460
|
-
newRange.start,
|
|
1461
|
-
newRange.end,
|
|
1462
|
-
snapshot,
|
|
1463
|
-
businessDays,
|
|
1464
|
-
weekendPredicate
|
|
1465
|
-
);
|
|
1466
|
-
const movedTask = {
|
|
1467
|
-
...task,
|
|
1468
|
-
startDate: newRange.start.toISOString().split("T")[0],
|
|
1469
|
-
endDate: newRange.end.toISOString().split("T")[0],
|
|
1470
|
-
dependencies: updatedDependencies
|
|
1471
|
-
};
|
|
1472
|
-
if (options?.skipCascade) {
|
|
1473
|
-
return {
|
|
1474
|
-
changedTasks: [movedTask],
|
|
1475
|
-
changedIds: [movedTask.id]
|
|
1476
|
-
};
|
|
1688
|
+
function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate, context = createCascadeContext(allTasks)) {
|
|
1689
|
+
const { childrenByParentId, dependentsByPredecessorId, taskById } = context;
|
|
1690
|
+
const normalizedMovedTask = normalizeTaskDependencyLags({
|
|
1691
|
+
...movedTask,
|
|
1692
|
+
startDate: newStart.toISOString().split("T")[0],
|
|
1693
|
+
endDate: newEnd.toISOString().split("T")[0]
|
|
1694
|
+
});
|
|
1695
|
+
if (!movedTask.parentId && !childrenByParentId.has(movedTask.id) && !dependentsByPredecessorId.has(movedTask.id)) {
|
|
1696
|
+
return [normalizedMovedTask];
|
|
1477
1697
|
}
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1480
|
-
newRange.start,
|
|
1481
|
-
newRange.end,
|
|
1482
|
-
snapshot,
|
|
1483
|
-
businessDays,
|
|
1484
|
-
weekendPredicate
|
|
1485
|
-
);
|
|
1698
|
+
const updatedDates = /* @__PURE__ */ new Map();
|
|
1699
|
+
updatedDates.set(movedTask.id, { start: newStart, end: newEnd });
|
|
1486
1700
|
const resultMap = /* @__PURE__ */ new Map();
|
|
1487
|
-
resultMap.set(movedTask.id,
|
|
1488
|
-
|
|
1489
|
-
|
|
1701
|
+
resultMap.set(movedTask.id, normalizedMovedTask);
|
|
1702
|
+
const queue = [[movedTask.id, "direct"]];
|
|
1703
|
+
const childShifted = /* @__PURE__ */ new Set();
|
|
1704
|
+
let iterations = 0;
|
|
1705
|
+
const MAX_ITERATIONS = allTasks.length * 3;
|
|
1706
|
+
while (queue.length > 0 && iterations < MAX_ITERATIONS) {
|
|
1707
|
+
iterations++;
|
|
1708
|
+
const [currentId, arrivalMode] = queue.shift();
|
|
1709
|
+
const { start: currStart, end: currEnd } = updatedDates.get(currentId);
|
|
1710
|
+
const currentOriginal = taskById.get(currentId);
|
|
1711
|
+
if (arrivalMode !== "parent-recalc") {
|
|
1712
|
+
const children = childrenByParentId.get(currentId) ?? [];
|
|
1713
|
+
for (const child of children) {
|
|
1714
|
+
if (childShifted.has(child.id) || child.locked) continue;
|
|
1715
|
+
const parentOrigStart = new Date(currentOriginal.startDate);
|
|
1716
|
+
const parentOrigEnd = new Date(currentOriginal.endDate);
|
|
1717
|
+
const childOrigStart = new Date(child.startDate);
|
|
1718
|
+
const childOrigEnd = new Date(child.endDate);
|
|
1719
|
+
const startDeltaMs = currStart.getTime() - parentOrigStart.getTime();
|
|
1720
|
+
const endDeltaMs = currEnd.getTime() - parentOrigEnd.getTime();
|
|
1721
|
+
let childNewStart;
|
|
1722
|
+
let childNewEnd;
|
|
1723
|
+
if (businessDays && weekendPredicate) {
|
|
1724
|
+
const proposedStart = new Date(childOrigStart.getTime() + startDeltaMs);
|
|
1725
|
+
const snapDirection = currStart.getTime() >= parentOrigStart.getTime() ? 1 : -1;
|
|
1726
|
+
const movedRange = moveTaskRange(
|
|
1727
|
+
child.startDate,
|
|
1728
|
+
child.endDate,
|
|
1729
|
+
proposedStart,
|
|
1730
|
+
true,
|
|
1731
|
+
weekendPredicate,
|
|
1732
|
+
snapDirection
|
|
1733
|
+
);
|
|
1734
|
+
childNewStart = movedRange.start;
|
|
1735
|
+
childNewEnd = movedRange.end;
|
|
1736
|
+
} else {
|
|
1737
|
+
childNewStart = new Date(childOrigStart.getTime() + startDeltaMs);
|
|
1738
|
+
childNewEnd = new Date(childOrigEnd.getTime() + endDeltaMs);
|
|
1739
|
+
}
|
|
1740
|
+
const prev = updatedDates.get(child.id);
|
|
1741
|
+
if (prev && prev.start.getTime() === childNewStart.getTime() && prev.end.getTime() === childNewEnd.getTime()) {
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
updatedDates.set(child.id, { start: childNewStart, end: childNewEnd });
|
|
1745
|
+
childShifted.add(child.id);
|
|
1746
|
+
queue.push([child.id, "child-delta"]);
|
|
1747
|
+
resultMap.set(child.id, normalizeTaskDependencyLags({
|
|
1748
|
+
...child,
|
|
1749
|
+
startDate: childNewStart.toISOString().split("T")[0],
|
|
1750
|
+
endDate: childNewEnd.toISOString().split("T")[0],
|
|
1751
|
+
// Дети едут параллельно: вместе с датами строки сдвигаются и её работы.
|
|
1752
|
+
...shiftedActivitiesPatch(
|
|
1753
|
+
child.activities,
|
|
1754
|
+
projectDayDelta(childOrigStart, childNewStart, businessDays, weekendPredicate),
|
|
1755
|
+
businessDays,
|
|
1756
|
+
weekendPredicate
|
|
1757
|
+
)
|
|
1758
|
+
}));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
const parentId = currentOriginal.parentId;
|
|
1762
|
+
if (parentId && parentId !== movedTask.id) {
|
|
1763
|
+
const parent = taskById.get(parentId);
|
|
1764
|
+
if (parent && !parent.locked) {
|
|
1765
|
+
const siblings = childrenByParentId.get(parentId) ?? [];
|
|
1766
|
+
const siblingPositions = siblings.map((sib) => {
|
|
1767
|
+
if (updatedDates.has(sib.id)) return updatedDates.get(sib.id);
|
|
1768
|
+
return { start: new Date(sib.startDate), end: new Date(sib.endDate) };
|
|
1769
|
+
});
|
|
1770
|
+
const minStart = new Date(Math.min(...siblingPositions.map((p) => p.start.getTime())));
|
|
1771
|
+
const maxEnd = new Date(Math.max(...siblingPositions.map((p) => p.end.getTime())));
|
|
1772
|
+
const prev = updatedDates.get(parentId);
|
|
1773
|
+
if (!prev || prev.start.getTime() !== minStart.getTime() || prev.end.getTime() !== maxEnd.getTime()) {
|
|
1774
|
+
updatedDates.set(parentId, { start: minStart, end: maxEnd });
|
|
1775
|
+
queue.push([parentId, "parent-recalc"]);
|
|
1776
|
+
resultMap.set(parentId, normalizeTaskDependencyLags({
|
|
1777
|
+
...parent,
|
|
1778
|
+
startDate: minStart.toISOString().split("T")[0],
|
|
1779
|
+
endDate: maxEnd.toISOString().split("T")[0]
|
|
1780
|
+
}));
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const dependents = dependentsByPredecessorId.get(currentId) ?? [];
|
|
1785
|
+
for (const { task, dependencyIndex } of dependents) {
|
|
1786
|
+
if (task.locked || !task.dependencies) continue;
|
|
1787
|
+
if (childShifted.has(task.id)) continue;
|
|
1788
|
+
const dep = task.dependencies[dependencyIndex];
|
|
1789
|
+
if (!dep) continue;
|
|
1790
|
+
const origStart = new Date(task.startDate);
|
|
1791
|
+
const origEnd = new Date(task.endDate);
|
|
1792
|
+
const { predStart: normalizedPredStart, predEnd: normalizedPredEnd } = normalizePredecessorDates(
|
|
1793
|
+
{
|
|
1794
|
+
startDate: currStart,
|
|
1795
|
+
endDate: currEnd,
|
|
1796
|
+
type: currentOriginal.type
|
|
1797
|
+
},
|
|
1798
|
+
parseCascadeDateInput
|
|
1799
|
+
);
|
|
1800
|
+
const constraintDate = calculateSuccessorDate(
|
|
1801
|
+
normalizedPredStart,
|
|
1802
|
+
normalizedPredEnd,
|
|
1803
|
+
dep.type,
|
|
1804
|
+
getDependencyLag(dep),
|
|
1805
|
+
businessDays,
|
|
1806
|
+
weekendPredicate,
|
|
1807
|
+
task.type
|
|
1808
|
+
);
|
|
1809
|
+
let succNewStart;
|
|
1810
|
+
let succNewEnd;
|
|
1811
|
+
const duration = getTaskDuration(origStart, origEnd, businessDays, weekendPredicate);
|
|
1812
|
+
if (dep.type === "FS" || dep.type === "SS") {
|
|
1813
|
+
({ start: succNewStart, end: succNewEnd } = buildTaskRangeFromStart(
|
|
1814
|
+
constraintDate,
|
|
1815
|
+
duration,
|
|
1816
|
+
businessDays,
|
|
1817
|
+
weekendPredicate
|
|
1818
|
+
));
|
|
1819
|
+
} else {
|
|
1820
|
+
({ start: succNewStart, end: succNewEnd } = buildTaskRangeFromEnd(
|
|
1821
|
+
constraintDate,
|
|
1822
|
+
duration,
|
|
1823
|
+
businessDays,
|
|
1824
|
+
weekendPredicate
|
|
1825
|
+
));
|
|
1826
|
+
}
|
|
1827
|
+
const prev = updatedDates.get(task.id);
|
|
1828
|
+
if (prev && prev.start.getTime() === succNewStart.getTime() && prev.end.getTime() === succNewEnd.getTime()) {
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
updatedDates.set(task.id, { start: succNewStart, end: succNewEnd });
|
|
1832
|
+
queue.push([task.id, "dependency"]);
|
|
1833
|
+
resultMap.set(task.id, normalizeTaskDependencyLags({
|
|
1834
|
+
...task,
|
|
1835
|
+
startDate: succNewStart.toISOString().split("T")[0],
|
|
1836
|
+
endDate: succNewEnd.toISOString().split("T")[0],
|
|
1837
|
+
...shiftedActivitiesPatch(
|
|
1838
|
+
task.activities,
|
|
1839
|
+
projectDayDelta(origStart, succNewStart, businessDays, weekendPredicate),
|
|
1840
|
+
businessDays,
|
|
1841
|
+
weekendPredicate
|
|
1842
|
+
)
|
|
1843
|
+
}));
|
|
1844
|
+
}
|
|
1490
1845
|
}
|
|
1491
|
-
|
|
1846
|
+
return Array.from(resultMap.values());
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
// src/core/scheduling/hierarchy.ts
|
|
1850
|
+
function getChildren(parentId, tasks) {
|
|
1851
|
+
return tasks.filter((t) => t.parentId === parentId);
|
|
1852
|
+
}
|
|
1853
|
+
function isTaskParent(taskId, tasks) {
|
|
1854
|
+
return tasks.some((t) => t.parentId === taskId);
|
|
1855
|
+
}
|
|
1856
|
+
function computeParentDates(parentId, tasks) {
|
|
1857
|
+
const children = getChildren(parentId, tasks);
|
|
1858
|
+
if (children.length === 0) {
|
|
1859
|
+
const parent = tasks.find((t) => t.id === parentId);
|
|
1860
|
+
const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
|
|
1861
|
+
const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
|
|
1862
|
+
return { startDate: start, endDate: end };
|
|
1863
|
+
}
|
|
1864
|
+
const startDates = children.map((c) => new Date(c.startDate));
|
|
1865
|
+
const endDates = children.map((c) => new Date(c.endDate));
|
|
1866
|
+
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
1867
|
+
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
1492
1868
|
return {
|
|
1493
|
-
|
|
1494
|
-
|
|
1869
|
+
startDate: new Date(minTime),
|
|
1870
|
+
endDate: new Date(maxTime)
|
|
1495
1871
|
};
|
|
1496
1872
|
}
|
|
1497
|
-
function
|
|
1498
|
-
const
|
|
1499
|
-
if (
|
|
1500
|
-
return
|
|
1501
|
-
}
|
|
1502
|
-
const businessDays = options?.businessDays ?? false;
|
|
1503
|
-
const weekendPredicate = options?.weekendPredicate;
|
|
1504
|
-
const originalStart = parseDateOnly(task.startDate);
|
|
1505
|
-
const originalEnd = parseDateOnly(task.endDate);
|
|
1506
|
-
let newRange;
|
|
1507
|
-
if (anchor === "start") {
|
|
1508
|
-
newRange = { start: originalStart, end: newDate };
|
|
1509
|
-
} else {
|
|
1510
|
-
newRange = { start: newDate, end: originalEnd };
|
|
1873
|
+
function computeParentProgress(parentId, tasks) {
|
|
1874
|
+
const children = getChildren(parentId, tasks);
|
|
1875
|
+
if (children.length === 0) {
|
|
1876
|
+
return 0;
|
|
1511
1877
|
}
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
startDate: newRange.start.toISOString().split("T")[0],
|
|
1523
|
-
endDate: newRange.end.toISOString().split("T")[0],
|
|
1524
|
-
dependencies: updatedDependencies
|
|
1525
|
-
};
|
|
1526
|
-
if (options?.skipCascade) {
|
|
1527
|
-
return {
|
|
1528
|
-
changedTasks: [resizedTask],
|
|
1529
|
-
changedIds: [resizedTask.id]
|
|
1530
|
-
};
|
|
1878
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
1879
|
+
let totalWeight = 0;
|
|
1880
|
+
let weightedSum = 0;
|
|
1881
|
+
for (const child of children) {
|
|
1882
|
+
const start = new Date(child.startDate).getTime();
|
|
1883
|
+
const end = new Date(child.endDate).getTime();
|
|
1884
|
+
const duration = (end - start + DAY_MS7) / DAY_MS7;
|
|
1885
|
+
const progress = child.progress ?? 0;
|
|
1886
|
+
totalWeight += duration;
|
|
1887
|
+
weightedSum += duration * progress;
|
|
1531
1888
|
}
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
newRange.start,
|
|
1535
|
-
newRange.end,
|
|
1536
|
-
snapshot,
|
|
1537
|
-
businessDays,
|
|
1538
|
-
weekendPredicate
|
|
1539
|
-
);
|
|
1540
|
-
const resultMap = /* @__PURE__ */ new Map();
|
|
1541
|
-
resultMap.set(resizedTask.id, resizedTask);
|
|
1542
|
-
for (const t of cascadeResult) {
|
|
1543
|
-
resultMap.set(t.id, t);
|
|
1889
|
+
if (totalWeight === 0) {
|
|
1890
|
+
return 0;
|
|
1544
1891
|
}
|
|
1545
|
-
|
|
1546
|
-
return {
|
|
1547
|
-
changedTasks,
|
|
1548
|
-
changedIds: changedTasks.map((t) => t.id)
|
|
1549
|
-
};
|
|
1892
|
+
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
1550
1893
|
}
|
|
1551
|
-
function
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1894
|
+
function getAllDescendants(parentId, tasks) {
|
|
1895
|
+
const descendants = [];
|
|
1896
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1897
|
+
function collectChildren(taskId) {
|
|
1898
|
+
if (visited.has(taskId)) return;
|
|
1899
|
+
visited.add(taskId);
|
|
1900
|
+
const children = getChildren(taskId, tasks);
|
|
1901
|
+
for (const child of children) {
|
|
1902
|
+
descendants.push(child);
|
|
1903
|
+
collectChildren(child.id);
|
|
1904
|
+
}
|
|
1555
1905
|
}
|
|
1556
|
-
|
|
1906
|
+
collectChildren(parentId);
|
|
1907
|
+
return descendants;
|
|
1908
|
+
}
|
|
1909
|
+
function cascadeTaskProgress(task, tasks) {
|
|
1910
|
+
const progress = task.progress;
|
|
1911
|
+
if (progress !== 0 && progress !== 100) {
|
|
1912
|
+
return [task];
|
|
1913
|
+
}
|
|
1914
|
+
const descendants = getAllDescendants(task.id, tasks);
|
|
1915
|
+
if (descendants.length === 0) {
|
|
1916
|
+
return [task];
|
|
1917
|
+
}
|
|
1918
|
+
return [
|
|
1919
|
+
{ ...task, progress },
|
|
1920
|
+
...descendants.map((descendant) => ({ ...descendant, progress }))
|
|
1921
|
+
];
|
|
1922
|
+
}
|
|
1923
|
+
function getAllDependencyEdges(tasks) {
|
|
1924
|
+
const edges = [];
|
|
1925
|
+
for (const task of tasks) {
|
|
1926
|
+
if (task.dependencies) {
|
|
1927
|
+
for (const dep of task.dependencies) {
|
|
1928
|
+
edges.push({
|
|
1929
|
+
predecessorId: dep.taskId,
|
|
1930
|
+
successorId: task.id,
|
|
1931
|
+
type: dep.type,
|
|
1932
|
+
lag: dep.lag ?? 0
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
return edges;
|
|
1938
|
+
}
|
|
1939
|
+
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
1940
|
+
return tasks.map((task) => {
|
|
1941
|
+
if (task.id === taskId1 || task.id === taskId2) {
|
|
1942
|
+
if (!task.dependencies) return task;
|
|
1943
|
+
const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
|
|
1944
|
+
const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
|
|
1945
|
+
if (filteredDependencies.length === task.dependencies.length) {
|
|
1946
|
+
return task;
|
|
1947
|
+
}
|
|
1948
|
+
return {
|
|
1949
|
+
...task,
|
|
1950
|
+
dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
return task;
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
function findParentId(taskId, tasks) {
|
|
1957
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
1958
|
+
return task?.parentId;
|
|
1959
|
+
}
|
|
1960
|
+
function isAncestorTask(ancestorId, taskId, tasks) {
|
|
1961
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
1962
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1963
|
+
let current = taskById.get(taskId);
|
|
1964
|
+
while (current?.parentId) {
|
|
1965
|
+
if (current.parentId === ancestorId) {
|
|
1966
|
+
return true;
|
|
1967
|
+
}
|
|
1968
|
+
if (visited.has(current.parentId)) {
|
|
1969
|
+
return false;
|
|
1970
|
+
}
|
|
1971
|
+
visited.add(current.parentId);
|
|
1972
|
+
current = taskById.get(current.parentId);
|
|
1973
|
+
}
|
|
1974
|
+
return false;
|
|
1975
|
+
}
|
|
1976
|
+
function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
|
|
1977
|
+
if (taskId1 === taskId2) {
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// src/core/scheduling/execute.ts
|
|
1984
|
+
function toIsoDate(date) {
|
|
1985
|
+
return date.toISOString().split("T")[0];
|
|
1986
|
+
}
|
|
1987
|
+
function createChangedResult(snapshot, nextTasks) {
|
|
1988
|
+
const originalById = new Map(snapshot.map((task) => [task.id, task]));
|
|
1989
|
+
const changedTasks = nextTasks.filter((task) => JSON.stringify(originalById.get(task.id)) !== JSON.stringify(task));
|
|
1990
|
+
return {
|
|
1991
|
+
changedTasks,
|
|
1992
|
+
changedIds: changedTasks.map((task) => task.id)
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function moveTaskWithCascade(taskId, newStart, snapshot, options) {
|
|
1996
|
+
const task = snapshot.find((t) => t.id === taskId);
|
|
1997
|
+
if (!task) {
|
|
1998
|
+
return { changedTasks: [], changedIds: [] };
|
|
1999
|
+
}
|
|
2000
|
+
const businessDays = options?.businessDays ?? false;
|
|
2001
|
+
const weekendPredicate = options?.weekendPredicate;
|
|
2002
|
+
const newRange = moveTaskRange(
|
|
2003
|
+
task.startDate,
|
|
2004
|
+
task.endDate,
|
|
2005
|
+
newStart,
|
|
2006
|
+
businessDays,
|
|
2007
|
+
weekendPredicate
|
|
2008
|
+
);
|
|
2009
|
+
const updatedDependencies = recalculateIncomingLags(
|
|
2010
|
+
task,
|
|
2011
|
+
newRange.start,
|
|
2012
|
+
newRange.end,
|
|
2013
|
+
snapshot,
|
|
2014
|
+
businessDays,
|
|
2015
|
+
weekendPredicate
|
|
2016
|
+
);
|
|
2017
|
+
const movedTask = {
|
|
2018
|
+
...task,
|
|
2019
|
+
startDate: newRange.start.toISOString().split("T")[0],
|
|
2020
|
+
endDate: newRange.end.toISOString().split("T")[0],
|
|
2021
|
+
dependencies: updatedDependencies
|
|
2022
|
+
};
|
|
2023
|
+
if (options?.skipCascade) {
|
|
2024
|
+
return {
|
|
2025
|
+
changedTasks: [movedTask],
|
|
2026
|
+
changedIds: [movedTask.id]
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
const cascadeResult = universalCascade(
|
|
2030
|
+
movedTask,
|
|
2031
|
+
newRange.start,
|
|
2032
|
+
newRange.end,
|
|
2033
|
+
snapshot,
|
|
2034
|
+
businessDays,
|
|
2035
|
+
weekendPredicate
|
|
2036
|
+
);
|
|
2037
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
2038
|
+
resultMap.set(movedTask.id, movedTask);
|
|
2039
|
+
for (const t of cascadeResult) {
|
|
2040
|
+
resultMap.set(t.id, t);
|
|
2041
|
+
}
|
|
2042
|
+
const changedTasks = Array.from(resultMap.values());
|
|
2043
|
+
return {
|
|
2044
|
+
changedTasks,
|
|
2045
|
+
changedIds: changedTasks.map((t) => t.id)
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
function resizeTaskWithCascade(taskId, anchor, newDate, snapshot, options) {
|
|
2049
|
+
const task = snapshot.find((t) => t.id === taskId);
|
|
2050
|
+
if (!task) {
|
|
2051
|
+
return { changedTasks: [], changedIds: [] };
|
|
2052
|
+
}
|
|
2053
|
+
const businessDays = options?.businessDays ?? false;
|
|
2054
|
+
const weekendPredicate = options?.weekendPredicate;
|
|
2055
|
+
const originalStart = parseDateOnly(task.startDate);
|
|
2056
|
+
const originalEnd = parseDateOnly(task.endDate);
|
|
2057
|
+
let newRange;
|
|
2058
|
+
if (anchor === "start") {
|
|
2059
|
+
newRange = { start: originalStart, end: newDate };
|
|
2060
|
+
} else {
|
|
2061
|
+
newRange = { start: newDate, end: originalEnd };
|
|
2062
|
+
}
|
|
2063
|
+
const updatedDependencies = recalculateIncomingLags(
|
|
2064
|
+
task,
|
|
2065
|
+
newRange.start,
|
|
2066
|
+
newRange.end,
|
|
2067
|
+
snapshot,
|
|
2068
|
+
businessDays,
|
|
2069
|
+
weekendPredicate
|
|
2070
|
+
);
|
|
2071
|
+
const resizedTask = {
|
|
2072
|
+
...task,
|
|
2073
|
+
startDate: newRange.start.toISOString().split("T")[0],
|
|
2074
|
+
endDate: newRange.end.toISOString().split("T")[0],
|
|
2075
|
+
dependencies: updatedDependencies
|
|
2076
|
+
};
|
|
2077
|
+
if (options?.skipCascade) {
|
|
2078
|
+
return {
|
|
2079
|
+
changedTasks: [resizedTask],
|
|
2080
|
+
changedIds: [resizedTask.id]
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
const cascadeResult = universalCascade(
|
|
2084
|
+
resizedTask,
|
|
2085
|
+
newRange.start,
|
|
2086
|
+
newRange.end,
|
|
2087
|
+
snapshot,
|
|
2088
|
+
businessDays,
|
|
2089
|
+
weekendPredicate
|
|
2090
|
+
);
|
|
2091
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
2092
|
+
resultMap.set(resizedTask.id, resizedTask);
|
|
2093
|
+
for (const t of cascadeResult) {
|
|
2094
|
+
resultMap.set(t.id, t);
|
|
2095
|
+
}
|
|
2096
|
+
const changedTasks = Array.from(resultMap.values());
|
|
2097
|
+
return {
|
|
2098
|
+
changedTasks,
|
|
2099
|
+
changedIds: changedTasks.map((t) => t.id)
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
function recalculateTaskFromDependencies(taskId, snapshot, options) {
|
|
2103
|
+
const task = snapshot.find((t) => t.id === taskId);
|
|
2104
|
+
if (!task) {
|
|
2105
|
+
return { changedTasks: [], changedIds: [] };
|
|
2106
|
+
}
|
|
2107
|
+
const businessDays = options?.businessDays ?? false;
|
|
1557
2108
|
const weekendPredicate = options?.weekendPredicate;
|
|
1558
2109
|
if (!task.dependencies || task.dependencies.length === 0) {
|
|
1559
2110
|
return {
|
|
@@ -3036,7 +3587,11 @@ function flattenHierarchy(tasks) {
|
|
|
3036
3587
|
}
|
|
3037
3588
|
function normalizeHierarchyTasks(tasks) {
|
|
3038
3589
|
const orderedTasks = flattenHierarchy(tasks).map((task) => {
|
|
3039
|
-
const
|
|
3590
|
+
const bounds = activityBounds(task.activities);
|
|
3591
|
+
const { startDate, endDate } = normalizeTaskDates(
|
|
3592
|
+
bounds?.startDate ?? task.startDate,
|
|
3593
|
+
bounds?.endDate ?? task.endDate
|
|
3594
|
+
);
|
|
3040
3595
|
return { ...task, startDate, endDate };
|
|
3041
3596
|
});
|
|
3042
3597
|
const childrenByParent = /* @__PURE__ */ new Map();
|
|
@@ -3047,7 +3602,7 @@ function normalizeHierarchyTasks(tasks) {
|
|
|
3047
3602
|
children.push(index);
|
|
3048
3603
|
childrenByParent.set(parentId, children);
|
|
3049
3604
|
}
|
|
3050
|
-
const
|
|
3605
|
+
const DAY_MS7 = 24 * 60 * 60 * 1e3;
|
|
3051
3606
|
for (let index = orderedTasks.length - 1; index >= 0; index--) {
|
|
3052
3607
|
const children = childrenByParent.get(orderedTasks[index].id);
|
|
3053
3608
|
if (!children?.length) continue;
|
|
@@ -3061,7 +3616,7 @@ function normalizeHierarchyTasks(tasks) {
|
|
|
3061
3616
|
const end = new Date(child.endDate).getTime();
|
|
3062
3617
|
minStart = Math.min(minStart, start);
|
|
3063
3618
|
maxEnd = Math.max(maxEnd, end);
|
|
3064
|
-
const duration = (end - start +
|
|
3619
|
+
const duration = (end - start + DAY_MS7) / DAY_MS7;
|
|
3065
3620
|
totalWeight += duration;
|
|
3066
3621
|
weightedProgress += duration * (child.progress ?? 0);
|
|
3067
3622
|
}
|
|
@@ -3217,361 +3772,96 @@ var TimeScaleHeader = ({
|
|
|
3217
3772
|
},
|
|
3218
3773
|
`month-${index}`
|
|
3219
3774
|
))
|
|
3220
|
-
)
|
|
3221
|
-
}
|
|
3222
|
-
),
|
|
3223
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3224
|
-
"div",
|
|
3225
|
-
{
|
|
3226
|
-
className: "gantt-tsh-dayRow",
|
|
3227
|
-
style: {
|
|
3228
|
-
height: `${rowHeight}px`,
|
|
3229
|
-
gridTemplateColumns: viewMode === "week" ? weekGridTemplate : viewMode === "month" ? monthGridTemplate : dayGridTemplate
|
|
3230
|
-
},
|
|
3231
|
-
children: viewMode === "week" ? (
|
|
3232
|
-
// Week-view row 2: week block start day numbers (variable width)
|
|
3233
|
-
weekBlocks.map((block, index) => {
|
|
3234
|
-
const prevBlock = weekBlocks[index - 1];
|
|
3235
|
-
const isMonthBoundary = index > 0 && prevBlock && prevBlock.startDate.getUTCMonth() !== block.startDate.getUTCMonth();
|
|
3236
|
-
const showDate = block.days === 7;
|
|
3237
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3238
|
-
"div",
|
|
3239
|
-
{
|
|
3240
|
-
className: "gantt-tsh-dayCell gantt-tsh-weekCell",
|
|
3241
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: showDate ? String(block.startDate.getUTCDate()).padStart(2, "0") : "" })
|
|
3242
|
-
},
|
|
3243
|
-
`week-${index}`
|
|
3244
|
-
);
|
|
3245
|
-
})
|
|
3246
|
-
) : viewMode === "month" ? (
|
|
3247
|
-
// Month-view row 2: one column per month, shows abbreviated month name
|
|
3248
|
-
monthBlocks.map((block, index) => {
|
|
3249
|
-
const MIN_DAYS_TO_SHOW_LABEL = 15;
|
|
3250
|
-
const showLabel = block.days >= MIN_DAYS_TO_SHOW_LABEL;
|
|
3251
|
-
const isYearBoundary = index > 0 && block.startDate.getUTCMonth() === 0;
|
|
3252
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3253
|
-
"div",
|
|
3254
|
-
{
|
|
3255
|
-
className: "gantt-tsh-dayCell gantt-tsh-weekCell",
|
|
3256
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: showLabel ? (() => {
|
|
3257
|
-
const s = block.startDate.toLocaleString("ru-RU", { month: "long", timeZone: "UTC" });
|
|
3258
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3259
|
-
})() : "" })
|
|
3260
|
-
},
|
|
3261
|
-
`mblock-${index}`
|
|
3262
|
-
);
|
|
3263
|
-
})
|
|
3264
|
-
) : (
|
|
3265
|
-
// Day-view row 2: individual day numbers (existing code)
|
|
3266
|
-
days.map((day, index) => {
|
|
3267
|
-
const isWeekendDay = isCustomWeekend ? isCustomWeekend(day) : day.getUTCDay() === 0 || day.getUTCDay() === 6;
|
|
3268
|
-
const prevDay = days[index - 1];
|
|
3269
|
-
const isMonthBoundary = index > 0 && prevDay && prevDay.getUTCMonth() !== day.getUTCMonth();
|
|
3270
|
-
const isTodayDate = day.getUTCFullYear() === today.getUTCFullYear() && day.getUTCMonth() === today.getUTCMonth() && day.getUTCDate() === today.getUTCDate();
|
|
3271
|
-
const markerKey = `${day.getUTCFullYear()}-${day.getUTCMonth()}-${day.getUTCDate()}`;
|
|
3272
|
-
const marker = markerByDayKey.get(markerKey);
|
|
3273
|
-
const markerColor = marker?.color;
|
|
3274
|
-
const formattedDate = `${String(day.getUTCDate()).padStart(2, "0")}.${String(day.getUTCMonth() + 1).padStart(2, "0")}.${String(day.getUTCFullYear()).slice(-2)}`;
|
|
3275
|
-
const tooltipText = isTodayDate ? `${formattedDate} \u2014 \u0421\u0435\u0433\u043E\u0434\u043D\u044F` : marker?.name ? `${formattedDate} \u2014 ${marker.name}` : void 0;
|
|
3276
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3277
|
-
"div",
|
|
3278
|
-
{
|
|
3279
|
-
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 });
|
|
3775
|
+
)
|
|
3776
|
+
}
|
|
3777
|
+
),
|
|
3778
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3779
|
+
"div",
|
|
3780
|
+
{
|
|
3781
|
+
className: "gantt-tsh-dayRow",
|
|
3782
|
+
style: {
|
|
3783
|
+
height: `${rowHeight}px`,
|
|
3784
|
+
gridTemplateColumns: viewMode === "week" ? weekGridTemplate : viewMode === "month" ? monthGridTemplate : dayGridTemplate
|
|
3785
|
+
},
|
|
3786
|
+
children: viewMode === "week" ? (
|
|
3787
|
+
// Week-view row 2: week block start day numbers (variable width)
|
|
3788
|
+
weekBlocks.map((block, index) => {
|
|
3789
|
+
const prevBlock = weekBlocks[index - 1];
|
|
3790
|
+
const isMonthBoundary = index > 0 && prevBlock && prevBlock.startDate.getUTCMonth() !== block.startDate.getUTCMonth();
|
|
3791
|
+
const showDate = block.days === 7;
|
|
3792
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3793
|
+
"div",
|
|
3794
|
+
{
|
|
3795
|
+
className: "gantt-tsh-dayCell gantt-tsh-weekCell",
|
|
3796
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: showDate ? String(block.startDate.getUTCDate()).padStart(2, "0") : "" })
|
|
3797
|
+
},
|
|
3798
|
+
`week-${index}`
|
|
3799
|
+
);
|
|
3800
|
+
})
|
|
3801
|
+
) : viewMode === "month" ? (
|
|
3802
|
+
// Month-view row 2: one column per month, shows abbreviated month name
|
|
3803
|
+
monthBlocks.map((block, index) => {
|
|
3804
|
+
const MIN_DAYS_TO_SHOW_LABEL = 15;
|
|
3805
|
+
const showLabel = block.days >= MIN_DAYS_TO_SHOW_LABEL;
|
|
3806
|
+
const isYearBoundary = index > 0 && block.startDate.getUTCMonth() === 0;
|
|
3807
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3808
|
+
"div",
|
|
3809
|
+
{
|
|
3810
|
+
className: "gantt-tsh-dayCell gantt-tsh-weekCell",
|
|
3811
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: showLabel ? (() => {
|
|
3812
|
+
const s = block.startDate.toLocaleString("ru-RU", { month: "long", timeZone: "UTC" });
|
|
3813
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3814
|
+
})() : "" })
|
|
3815
|
+
},
|
|
3816
|
+
`mblock-${index}`
|
|
3817
|
+
);
|
|
3818
|
+
})
|
|
3819
|
+
) : (
|
|
3820
|
+
// Day-view row 2: individual day numbers (existing code)
|
|
3821
|
+
days.map((day, index) => {
|
|
3822
|
+
const isWeekendDay = isCustomWeekend ? isCustomWeekend(day) : day.getUTCDay() === 0 || day.getUTCDay() === 6;
|
|
3823
|
+
const prevDay = days[index - 1];
|
|
3824
|
+
const isMonthBoundary = index > 0 && prevDay && prevDay.getUTCMonth() !== day.getUTCMonth();
|
|
3825
|
+
const isTodayDate = day.getUTCFullYear() === today.getUTCFullYear() && day.getUTCMonth() === today.getUTCMonth() && day.getUTCDate() === today.getUTCDate();
|
|
3826
|
+
const markerKey = `${day.getUTCFullYear()}-${day.getUTCMonth()}-${day.getUTCDate()}`;
|
|
3827
|
+
const marker = markerByDayKey.get(markerKey);
|
|
3828
|
+
const markerColor = marker?.color;
|
|
3829
|
+
const formattedDate = `${String(day.getUTCDate()).padStart(2, "0")}.${String(day.getUTCMonth() + 1).padStart(2, "0")}.${String(day.getUTCFullYear()).slice(-2)}`;
|
|
3830
|
+
const tooltipText = isTodayDate ? `${formattedDate} \u2014 \u0421\u0435\u0433\u043E\u0434\u043D\u044F` : marker?.name ? `${formattedDate} \u2014 ${marker.name}` : void 0;
|
|
3831
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
3832
|
+
"div",
|
|
3833
|
+
{
|
|
3834
|
+
className: `gantt-tsh-dayCell ${isWeekendDay ? "gantt-tsh-weekendDay" : ""} ${isTodayDate ? "gantt-tsh-today" : ""} ${marker && !isTodayDate ? "gantt-tsh-markerDay" : ""} ${tooltipText ? "gantt-tsh-dayCell-tooltipTrigger" : ""}`,
|
|
3835
|
+
style: markerColor && !isTodayDate ? {
|
|
3836
|
+
["--gantt-marker-day-color"]: markerColor
|
|
3837
|
+
} : void 0,
|
|
3838
|
+
"aria-label": tooltipText,
|
|
3839
|
+
onMouseEnter: () => {
|
|
3840
|
+
if (!tooltipText) return;
|
|
3841
|
+
onTimelineHover?.({
|
|
3842
|
+
label: tooltipText,
|
|
3843
|
+
left: index * dayWidth,
|
|
3844
|
+
color: isTodayDate ? "#dc2626" : markerColor || "var(--gantt-timeline-marker-color, #dc2626)"
|
|
3845
|
+
});
|
|
3846
|
+
},
|
|
3847
|
+
onMouseLeave: onTimelineHoverEnd,
|
|
3848
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "gantt-tsh-dayLabel", children: (0, import_date_fns.format)(day, "d") })
|
|
3849
|
+
},
|
|
3850
|
+
`day-${index}`
|
|
3851
|
+
);
|
|
3852
|
+
})
|
|
3853
|
+
)
|
|
3854
|
+
}
|
|
3855
|
+
)
|
|
3856
|
+
]
|
|
3548
3857
|
}
|
|
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}`;
|
|
3858
|
+
);
|
|
3574
3859
|
};
|
|
3860
|
+
var TimeScaleHeader_default = TimeScaleHeader;
|
|
3861
|
+
|
|
3862
|
+
// src/components/TaskRow/TaskRow.tsx
|
|
3863
|
+
var import_react3 = __toESM(require("react"));
|
|
3864
|
+
var import_react_dom = require("react-dom");
|
|
3575
3865
|
|
|
3576
3866
|
// src/utils/expired.ts
|
|
3577
3867
|
var isTaskExpired = (task, referenceDate = /* @__PURE__ */ new Date()) => {
|
|
@@ -4410,21 +4700,245 @@ var useTaskDrag = (options) => {
|
|
|
4410
4700
|
}
|
|
4411
4701
|
}
|
|
4412
4702
|
};
|
|
4413
|
-
};
|
|
4703
|
+
};
|
|
4704
|
+
|
|
4705
|
+
// src/components/GanttChart/previewStore.ts
|
|
4706
|
+
function positionsEqual(left, right) {
|
|
4707
|
+
return left?.left === right?.left && left?.width === right?.width;
|
|
4708
|
+
}
|
|
4709
|
+
function createTaskPreviewPositionStore() {
|
|
4710
|
+
let positions = /* @__PURE__ */ new Map();
|
|
4711
|
+
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
4712
|
+
function notify(taskIds) {
|
|
4713
|
+
for (const taskId of taskIds) {
|
|
4714
|
+
const listeners = listenersByTaskId.get(taskId);
|
|
4715
|
+
if (!listeners) continue;
|
|
4716
|
+
for (const listener of listeners) {
|
|
4717
|
+
listener();
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
return {
|
|
4722
|
+
subscribeTask(taskId, listener) {
|
|
4723
|
+
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
4724
|
+
listeners.add(listener);
|
|
4725
|
+
listenersByTaskId.set(taskId, listeners);
|
|
4726
|
+
return () => {
|
|
4727
|
+
listeners.delete(listener);
|
|
4728
|
+
if (listeners.size === 0) {
|
|
4729
|
+
listenersByTaskId.delete(taskId);
|
|
4730
|
+
}
|
|
4731
|
+
};
|
|
4732
|
+
},
|
|
4733
|
+
getTaskPosition(taskId) {
|
|
4734
|
+
return positions.get(taskId);
|
|
4735
|
+
},
|
|
4736
|
+
setPositions(nextPositions) {
|
|
4737
|
+
const changedTaskIds = /* @__PURE__ */ new Set();
|
|
4738
|
+
const mergedPositions = /* @__PURE__ */ new Map();
|
|
4739
|
+
for (const [taskId, nextPosition] of nextPositions) {
|
|
4740
|
+
const currentPosition = positions.get(taskId);
|
|
4741
|
+
if (positionsEqual(currentPosition, nextPosition)) {
|
|
4742
|
+
if (currentPosition) {
|
|
4743
|
+
mergedPositions.set(taskId, currentPosition);
|
|
4744
|
+
}
|
|
4745
|
+
continue;
|
|
4746
|
+
}
|
|
4747
|
+
changedTaskIds.add(taskId);
|
|
4748
|
+
mergedPositions.set(taskId, {
|
|
4749
|
+
left: nextPosition.left,
|
|
4750
|
+
width: nextPosition.width
|
|
4751
|
+
});
|
|
4752
|
+
}
|
|
4753
|
+
for (const taskId of positions.keys()) {
|
|
4754
|
+
if (!nextPositions.has(taskId)) {
|
|
4755
|
+
changedTaskIds.add(taskId);
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
if (changedTaskIds.size === 0) {
|
|
4759
|
+
return;
|
|
4760
|
+
}
|
|
4761
|
+
positions = mergedPositions;
|
|
4762
|
+
notify(changedTaskIds);
|
|
4763
|
+
},
|
|
4764
|
+
clear() {
|
|
4765
|
+
if (positions.size === 0) {
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4768
|
+
const changedTaskIds = new Set(positions.keys());
|
|
4769
|
+
positions = /* @__PURE__ */ new Map();
|
|
4770
|
+
notify(changedTaskIds);
|
|
4771
|
+
}
|
|
4772
|
+
};
|
|
4773
|
+
}
|
|
4774
|
+
function createActivityPreviewStore() {
|
|
4775
|
+
let overridesByTask = /* @__PURE__ */ new Map();
|
|
4776
|
+
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
4777
|
+
function notify(taskIds) {
|
|
4778
|
+
for (const taskId of taskIds) {
|
|
4779
|
+
for (const listener of listenersByTaskId.get(taskId) ?? []) {
|
|
4780
|
+
listener();
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
return {
|
|
4785
|
+
subscribeTask(taskId, listener) {
|
|
4786
|
+
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
4787
|
+
listeners.add(listener);
|
|
4788
|
+
listenersByTaskId.set(taskId, listeners);
|
|
4789
|
+
return () => {
|
|
4790
|
+
listeners.delete(listener);
|
|
4791
|
+
if (listeners.size === 0) {
|
|
4792
|
+
listenersByTaskId.delete(taskId);
|
|
4793
|
+
}
|
|
4794
|
+
};
|
|
4795
|
+
},
|
|
4796
|
+
getTaskOverrides(taskId) {
|
|
4797
|
+
return overridesByTask.get(taskId);
|
|
4798
|
+
},
|
|
4799
|
+
setOverrides(nextOverrides) {
|
|
4800
|
+
const changedTaskIds = new Set(nextOverrides.keys());
|
|
4801
|
+
for (const taskId of overridesByTask.keys()) {
|
|
4802
|
+
changedTaskIds.add(taskId);
|
|
4803
|
+
}
|
|
4804
|
+
if ([...changedTaskIds].every((taskId) => {
|
|
4805
|
+
const current = overridesByTask.get(taskId);
|
|
4806
|
+
const next = nextOverrides.get(taskId);
|
|
4807
|
+
if (!current || !next || current.size !== next.size) return false;
|
|
4808
|
+
return [...next].every(([activityId, delta]) => current.get(activityId) === delta);
|
|
4809
|
+
})) {
|
|
4810
|
+
return;
|
|
4811
|
+
}
|
|
4812
|
+
overridesByTask = new Map([...nextOverrides].map(([taskId, deltas]) => [taskId, new Map(deltas)]));
|
|
4813
|
+
notify(changedTaskIds);
|
|
4814
|
+
},
|
|
4815
|
+
clear() {
|
|
4816
|
+
if (overridesByTask.size === 0) {
|
|
4817
|
+
return;
|
|
4818
|
+
}
|
|
4819
|
+
const changedTaskIds = new Set(overridesByTask.keys());
|
|
4820
|
+
overridesByTask = /* @__PURE__ */ new Map();
|
|
4821
|
+
notify(changedTaskIds);
|
|
4822
|
+
}
|
|
4823
|
+
};
|
|
4824
|
+
}
|
|
4825
|
+
function createActivityBlockStore() {
|
|
4826
|
+
let blockersByTask = /* @__PURE__ */ new Map();
|
|
4827
|
+
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
4828
|
+
function notify(taskIds) {
|
|
4829
|
+
for (const taskId of taskIds) {
|
|
4830
|
+
for (const listener of listenersByTaskId.get(taskId) ?? []) {
|
|
4831
|
+
listener();
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
return {
|
|
4836
|
+
subscribeTask(taskId, listener) {
|
|
4837
|
+
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
4838
|
+
listeners.add(listener);
|
|
4839
|
+
listenersByTaskId.set(taskId, listeners);
|
|
4840
|
+
return () => {
|
|
4841
|
+
listeners.delete(listener);
|
|
4842
|
+
if (listeners.size === 0) {
|
|
4843
|
+
listenersByTaskId.delete(taskId);
|
|
4844
|
+
}
|
|
4845
|
+
};
|
|
4846
|
+
},
|
|
4847
|
+
getTaskBlockers(taskId) {
|
|
4848
|
+
return blockersByTask.get(taskId);
|
|
4849
|
+
},
|
|
4850
|
+
setBlockers(nextBlockers) {
|
|
4851
|
+
const changedTaskIds = new Set(nextBlockers.keys());
|
|
4852
|
+
for (const taskId of blockersByTask.keys()) {
|
|
4853
|
+
changedTaskIds.add(taskId);
|
|
4854
|
+
}
|
|
4855
|
+
if ([...changedTaskIds].every((taskId) => {
|
|
4856
|
+
const current = blockersByTask.get(taskId);
|
|
4857
|
+
const next = nextBlockers.get(taskId);
|
|
4858
|
+
if (!current || !next || current.size !== next.size) return false;
|
|
4859
|
+
return [...next].every((activityId) => current.has(activityId));
|
|
4860
|
+
})) {
|
|
4861
|
+
return;
|
|
4862
|
+
}
|
|
4863
|
+
blockersByTask = new Map([...nextBlockers].map(([taskId, ids]) => [taskId, new Set(ids)]));
|
|
4864
|
+
notify(changedTaskIds);
|
|
4865
|
+
},
|
|
4866
|
+
clear() {
|
|
4867
|
+
if (blockersByTask.size === 0) {
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
const changedTaskIds = new Set(blockersByTask.keys());
|
|
4871
|
+
blockersByTask = /* @__PURE__ */ new Map();
|
|
4872
|
+
notify(changedTaskIds);
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
}
|
|
4876
|
+
var activityOwnerKey = (taskId, activityId) => `${taskId}\0${activityId}`;
|
|
4877
|
+
function createActivityDragOwner() {
|
|
4878
|
+
let ownerKey = null;
|
|
4879
|
+
return {
|
|
4880
|
+
get: () => ownerKey,
|
|
4881
|
+
set: (nextOwnerKey) => {
|
|
4882
|
+
ownerKey = nextOwnerKey;
|
|
4883
|
+
}
|
|
4884
|
+
};
|
|
4885
|
+
}
|
|
4886
|
+
function createActivityActivationStore() {
|
|
4887
|
+
let activeKey = null;
|
|
4888
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4889
|
+
return {
|
|
4890
|
+
subscribe(listener) {
|
|
4891
|
+
listeners.add(listener);
|
|
4892
|
+
return () => {
|
|
4893
|
+
listeners.delete(listener);
|
|
4894
|
+
};
|
|
4895
|
+
},
|
|
4896
|
+
getActiveKey: () => activeKey,
|
|
4897
|
+
setActiveKey(nextKey) {
|
|
4898
|
+
if (activeKey === nextKey) return;
|
|
4899
|
+
activeKey = nextKey;
|
|
4900
|
+
for (const listener of listeners) listener();
|
|
4901
|
+
}
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4414
4904
|
|
|
4415
4905
|
// src/components/TaskRow/TaskRow.tsx
|
|
4416
4906
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
4417
4907
|
var COMPOSITE_SEGMENT_CUT_MIN_PX = 10;
|
|
4908
|
+
var toIsoDay = (date) => date.toISOString().slice(0, 10);
|
|
4909
|
+
var shiftActivityDay = (value, days) => new Date(parseUTCDate(value).getTime() + days * DAY_MS);
|
|
4910
|
+
var activityDurationDays = (activity) => Math.max(1, Math.round((parseUTCDate(activity.endDate).getTime() - parseUTCDate(activity.startDate).getTime()) / DAY_MS) + 1);
|
|
4911
|
+
var ACTIVITY_TEXT_LUMINANCE_THRESHOLD = 0.55;
|
|
4912
|
+
var relativeLuminance = (hex) => {
|
|
4913
|
+
const channels = [1, 3, 5].map((index) => {
|
|
4914
|
+
const value = parseInt(hex.slice(index, index + 2), 16) / 255;
|
|
4915
|
+
return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
|
|
4916
|
+
});
|
|
4917
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
4918
|
+
};
|
|
4919
|
+
var activityTextColor = (background) => {
|
|
4920
|
+
const lightText = "var(--gantt-task-bar-text-color, #ffffff)";
|
|
4921
|
+
if (!background) return lightText;
|
|
4922
|
+
const value = background.trim();
|
|
4923
|
+
if (value.startsWith("#")) {
|
|
4924
|
+
return relativeLuminance(value) > ACTIVITY_TEXT_LUMINANCE_THRESHOLD ? "#111111" : "#ffffff";
|
|
4925
|
+
}
|
|
4926
|
+
const oklch = /oklch\(\s*([0-9.]+)/i.exec(value);
|
|
4927
|
+
if (oklch) {
|
|
4928
|
+
return Number(oklch[1]) > 0.66 ? "#111111" : "#ffffff";
|
|
4929
|
+
}
|
|
4930
|
+
return lightText;
|
|
4931
|
+
};
|
|
4418
4932
|
var formatCompositePreviewDate = (date) => {
|
|
4419
4933
|
const month = new Intl.DateTimeFormat("ru-RU", { month: "short", timeZone: "UTC" }).format(date).replace(/\.$/, "");
|
|
4420
4934
|
const yearSuffix = date.getUTCFullYear() === (/* @__PURE__ */ new Date()).getFullYear() ? "" : ` ${String(date.getUTCFullYear()).slice(-2)}`;
|
|
4421
4935
|
return `${date.getUTCDate()} ${month}${yearSuffix}`;
|
|
4422
4936
|
};
|
|
4423
4937
|
var arePropsEqual = (prevProps, nextProps) => {
|
|
4424
|
-
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 && prevProps.showCompositeSegments === nextProps.showCompositeSegments;
|
|
4938
|
+
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;
|
|
4425
4939
|
};
|
|
4426
4940
|
var TaskRow = import_react3.default.memo(
|
|
4427
|
-
({ 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, showCompositeSegments: showCompositeSegmentsProp = true, onCompositeToggle, compactDetail = false, compositeParentColor, compositeExpanded = false }) => {
|
|
4941
|
+
({ 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 }) => {
|
|
4428
4942
|
const defaultParentBarColor = "#782FC4";
|
|
4429
4943
|
const [showCompositePreview, setShowCompositePreview] = (0, import_react3.useState)(false);
|
|
4430
4944
|
const [compositePreviewPosition, setCompositePreviewPosition] = (0, import_react3.useState)(null);
|
|
@@ -4634,6 +5148,347 @@ var TaskRow = import_react3.default.memo(
|
|
|
4634
5148
|
const shouldRenderCompositeSegments = showCompositeSegmentsProp && task.composite && compositeSegments.length > 0 && visualWidth / compositeSegments.length >= 22;
|
|
4635
5149
|
const shouldRenderBaseline = showBaseline && baselineGeometry !== null;
|
|
4636
5150
|
const hasPreviewPosition = isDragging || effectiveOverridePosition !== void 0;
|
|
5151
|
+
const activityLayout = (0, import_react3.useMemo)(
|
|
5152
|
+
() => task.activities && task.activities.length > 0 ? computeActivityLanes(task.activities, monthStart, dayWidth) : null,
|
|
5153
|
+
[task.activities, monthStart, dayWidth]
|
|
5154
|
+
);
|
|
5155
|
+
const renderActivities = activityLayout !== null && !compactDetail;
|
|
5156
|
+
const activityTopOffset = activityLayout ? Math.max(0, (rowHeight - activityLayout.laneCount * ACTIVITY_LANE_STEP) / 2) : 0;
|
|
5157
|
+
const activityDragEnabled = !task.locked && !disableTaskDrag;
|
|
5158
|
+
const [activityDrag, setActivityDrag] = (0, import_react3.useState)(null);
|
|
5159
|
+
const [activityTipId, setActivityTipId] = (0, import_react3.useState)(null);
|
|
5160
|
+
const [activityTipPosition, setActivityTipPosition] = (0, import_react3.useState)(null);
|
|
5161
|
+
const activityTipAnchor = (0, import_react3.useRef)(null);
|
|
5162
|
+
const activityDragStart = (0, import_react3.useRef)(null);
|
|
5163
|
+
const activityConstraintRef = (0, import_react3.useRef)(null);
|
|
5164
|
+
const [activityBlockedBy, setActivityBlockedBy] = (0, import_react3.useState)(null);
|
|
5165
|
+
const visibleActivitySegments = (0, import_react3.useMemo)(() => {
|
|
5166
|
+
if (!activityLayout) return [];
|
|
5167
|
+
const window2 = horizontalWindow;
|
|
5168
|
+
if (!window2) return activityLayout.segments;
|
|
5169
|
+
const draggedId = activityDrag?.id;
|
|
5170
|
+
return activityLayout.segments.filter((segment) => segment.id === draggedId || segment.left <= window2.endPx && segment.left + segment.width >= window2.startPx);
|
|
5171
|
+
}, [activityLayout, horizontalWindow, activityDrag?.id]);
|
|
5172
|
+
const subscribeActivityOverrides = (0, import_react3.useCallback)(
|
|
5173
|
+
(listener) => activityPreviewStore?.subscribeTask(task.id, listener) ?? (() => {
|
|
5174
|
+
}),
|
|
5175
|
+
[activityPreviewStore, task.id]
|
|
5176
|
+
);
|
|
5177
|
+
const getActivityOverridesSnapshot = (0, import_react3.useCallback)(
|
|
5178
|
+
() => activityPreviewStore?.getTaskOverrides(task.id),
|
|
5179
|
+
[activityPreviewStore, task.id]
|
|
5180
|
+
);
|
|
5181
|
+
const activityOverrides = (0, import_react3.useSyncExternalStore)(
|
|
5182
|
+
subscribeActivityOverrides,
|
|
5183
|
+
getActivityOverridesSnapshot,
|
|
5184
|
+
() => void 0
|
|
5185
|
+
);
|
|
5186
|
+
const subscribeActivityBlock = (0, import_react3.useCallback)(
|
|
5187
|
+
(listener) => activityBlockStore?.subscribeTask(task.id, listener) ?? (() => {
|
|
5188
|
+
}),
|
|
5189
|
+
[activityBlockStore, task.id]
|
|
5190
|
+
);
|
|
5191
|
+
const getActivityBlockSnapshot = (0, import_react3.useCallback)(
|
|
5192
|
+
() => activityBlockStore?.getTaskBlockers(task.id),
|
|
5193
|
+
[activityBlockStore, task.id]
|
|
5194
|
+
);
|
|
5195
|
+
const blockedActivityIds = (0, import_react3.useSyncExternalStore)(
|
|
5196
|
+
subscribeActivityBlock,
|
|
5197
|
+
getActivityBlockSnapshot,
|
|
5198
|
+
() => void 0
|
|
5199
|
+
);
|
|
5200
|
+
const subscribeActivation = (0, import_react3.useCallback)(
|
|
5201
|
+
(listener) => activityActivationStore?.subscribe(listener) ?? (() => {
|
|
5202
|
+
}),
|
|
5203
|
+
[activityActivationStore]
|
|
5204
|
+
);
|
|
5205
|
+
const getActivationSnapshot = (0, import_react3.useCallback)(
|
|
5206
|
+
() => activityActivationStore?.getActiveKey() ?? null,
|
|
5207
|
+
[activityActivationStore]
|
|
5208
|
+
);
|
|
5209
|
+
const activeActivityKey = (0, import_react3.useSyncExternalStore)(
|
|
5210
|
+
subscribeActivation,
|
|
5211
|
+
getActivationSnapshot,
|
|
5212
|
+
() => null
|
|
5213
|
+
);
|
|
5214
|
+
const activeWork = (0, import_react3.useMemo)(() => {
|
|
5215
|
+
if (!activeActivityKey) return null;
|
|
5216
|
+
const [activeTaskId, activeActivityId] = activeActivityKey.split("\0");
|
|
5217
|
+
const source = (allTasks ?? []).find((candidate) => candidate.id === activeTaskId);
|
|
5218
|
+
const activity = source?.activities?.find((candidate) => candidate.id === activeActivityId);
|
|
5219
|
+
return { id: activeActivityId, name: activity?.name ?? null };
|
|
5220
|
+
}, [activeActivityKey, allTasks]);
|
|
5221
|
+
const activationVisualEnabled = activityClickToDrag || activityActivationMode !== "none";
|
|
5222
|
+
const dimOthers = activityActivationMode === "dim";
|
|
5223
|
+
const highlightChain = activityActivationMode === "highlight";
|
|
5224
|
+
const chainEndDeltaDays = (mode, dayDelta, durationDays2) => {
|
|
5225
|
+
if (mode === "resize-left") return 0;
|
|
5226
|
+
if (mode === "resize-right") return Math.max(dayDelta, -(durationDays2 - 1));
|
|
5227
|
+
return dayDelta;
|
|
5228
|
+
};
|
|
5229
|
+
const publishChainPreview = (0, import_react3.useCallback)((draggedId, mode, dayDelta) => {
|
|
5230
|
+
if (!activityPreviewStore) return;
|
|
5231
|
+
if (!task.activityChain) {
|
|
5232
|
+
activityPreviewStore.clear();
|
|
5233
|
+
return;
|
|
5234
|
+
}
|
|
5235
|
+
let shifts;
|
|
5236
|
+
if (task.activityChain === "push") {
|
|
5237
|
+
let delta = dayDelta;
|
|
5238
|
+
if (mode !== "resize-right") {
|
|
5239
|
+
const minDelta = activityConstraintRef.current?.minStartDelta ?? null;
|
|
5240
|
+
if (minDelta !== null) delta = Math.max(delta, minDelta);
|
|
5241
|
+
}
|
|
5242
|
+
shifts = delta === 0 ? [] : pushActivityChain(allTasks ?? [], task.id, draggedId, delta).shifts;
|
|
5243
|
+
} else {
|
|
5244
|
+
const endDelta = chainEndDeltaDays(mode, dayDelta, activityDragStart.current?.durationDays ?? 1);
|
|
5245
|
+
shifts = endDelta === 0 ? [] : shiftActivityChain(allTasks ?? [], task.id, draggedId, endDelta);
|
|
5246
|
+
}
|
|
5247
|
+
if (shifts.length === 0) {
|
|
5248
|
+
activityPreviewStore.clear();
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
5252
|
+
for (const shift of shifts) {
|
|
5253
|
+
const deltas = /* @__PURE__ */ new Map();
|
|
5254
|
+
for (const [id, d] of shift.deltas) {
|
|
5255
|
+
if (d !== 0) deltas.set(id, d);
|
|
5256
|
+
}
|
|
5257
|
+
if (deltas.size > 0) overrides.set(shift.taskId, deltas);
|
|
5258
|
+
}
|
|
5259
|
+
activityPreviewStore.setOverrides(overrides);
|
|
5260
|
+
}, [activityPreviewStore, allTasks, task.activityChain, task.id]);
|
|
5261
|
+
(0, import_react3.useEffect)(() => {
|
|
5262
|
+
if (!activityDrag) return;
|
|
5263
|
+
const startInfo = activityDragStart.current;
|
|
5264
|
+
if (!startInfo) return;
|
|
5265
|
+
const publishBlockers = (blocked) => {
|
|
5266
|
+
const constraint = activityConstraintRef.current;
|
|
5267
|
+
if (!activityBlockStore) return;
|
|
5268
|
+
if (!blocked || !constraint) {
|
|
5269
|
+
activityBlockStore.clear();
|
|
5270
|
+
setActivityBlockedBy((current) => current === null ? current : null);
|
|
5271
|
+
return;
|
|
5272
|
+
}
|
|
5273
|
+
const blockers = /* @__PURE__ */ new Map();
|
|
5274
|
+
const names = [];
|
|
5275
|
+
for (const blocker of constraint.blockers) {
|
|
5276
|
+
const set = blockers.get(blocker.taskId) ?? /* @__PURE__ */ new Set();
|
|
5277
|
+
set.add(blocker.activityId);
|
|
5278
|
+
blockers.set(blocker.taskId, set);
|
|
5279
|
+
const source = (allTasks ?? []).find((candidate) => candidate.id === blocker.taskId);
|
|
5280
|
+
const work = source?.activities?.find((activity) => activity.id === blocker.activityId);
|
|
5281
|
+
const workName = work?.name ?? source?.name ?? blocker.activityId;
|
|
5282
|
+
names.push(blocker.taskId === task.id ? workName : `${source?.name ?? blocker.taskId} \xB7 ${workName}`);
|
|
5283
|
+
}
|
|
5284
|
+
activityBlockStore.setBlockers(blockers);
|
|
5285
|
+
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 });
|
|
5286
|
+
};
|
|
5287
|
+
const onMove = (event) => {
|
|
5288
|
+
const rawDelta = Math.round((event.clientX - startInfo.x) / dayWidth);
|
|
5289
|
+
let requested = rawDelta;
|
|
5290
|
+
if (activityDrag.mode === "resize-left") requested = Math.min(rawDelta, startInfo.durationDays - 1);
|
|
5291
|
+
if (activityDrag.mode === "resize-right") requested = Math.max(rawDelta, -(startInfo.durationDays - 1));
|
|
5292
|
+
const constraint = activityConstraintRef.current;
|
|
5293
|
+
let clamped = requested;
|
|
5294
|
+
let blocked = false;
|
|
5295
|
+
if (constraint && activityDrag.mode !== "resize-right" && requested < constraint.minStartDelta) {
|
|
5296
|
+
clamped = constraint.minStartDelta;
|
|
5297
|
+
blocked = true;
|
|
5298
|
+
}
|
|
5299
|
+
setActivityDrag((current) => current ? { ...current, dayDelta: clamped } : current);
|
|
5300
|
+
publishBlockers(blocked);
|
|
5301
|
+
const anchor = activityTipAnchor.current;
|
|
5302
|
+
if (anchor) {
|
|
5303
|
+
const shiftPx = activityDrag.mode === "resize-right" ? 0 : clamped * dayWidth;
|
|
5304
|
+
setActivityTipPosition({
|
|
5305
|
+
left: Math.max(8, Math.min(anchor.left + shiftPx, window.innerWidth - 160)),
|
|
5306
|
+
top: anchor.top,
|
|
5307
|
+
below: anchor.below
|
|
5308
|
+
});
|
|
5309
|
+
}
|
|
5310
|
+
publishChainPreview(activityDrag.id, activityDrag.mode, clamped);
|
|
5311
|
+
};
|
|
5312
|
+
const onUp = () => {
|
|
5313
|
+
activityPreviewStore?.clear();
|
|
5314
|
+
activityBlockStore?.clear();
|
|
5315
|
+
activityDragOwner?.set(null);
|
|
5316
|
+
setActivityBlockedBy(null);
|
|
5317
|
+
setActivityTipId(null);
|
|
5318
|
+
activityTipAnchor.current = null;
|
|
5319
|
+
setActivityTipPosition(null);
|
|
5320
|
+
if (activityDrag.dayDelta !== 0) {
|
|
5321
|
+
const chainPush = task.activityChain === "push";
|
|
5322
|
+
let delta = activityDrag.dayDelta;
|
|
5323
|
+
if (chainPush && activityDrag.mode !== "resize-right") {
|
|
5324
|
+
const minDelta = activityConstraintRef.current?.minStartDelta ?? null;
|
|
5325
|
+
if (minDelta !== null) delta = Math.max(delta, minDelta);
|
|
5326
|
+
}
|
|
5327
|
+
const clampLeft = Math.min(delta, startInfo.durationDays - 1);
|
|
5328
|
+
const clampRight = Math.max(delta, -(startInfo.durationDays - 1));
|
|
5329
|
+
const updatedActivities = (task.activities ?? []).map((activity) => {
|
|
5330
|
+
if (activity.id !== activityDrag.id) return activity;
|
|
5331
|
+
if (activityDrag.mode === "move") {
|
|
5332
|
+
return {
|
|
5333
|
+
...activity,
|
|
5334
|
+
startDate: toIsoDay(shiftActivityDay(activity.startDate, delta)),
|
|
5335
|
+
endDate: toIsoDay(shiftActivityDay(activity.endDate, delta))
|
|
5336
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
if (activityDrag.mode === "resize-left") {
|
|
5339
|
+
return { ...activity, startDate: toIsoDay(shiftActivityDay(activity.startDate, clampLeft)) };
|
|
5340
|
+
}
|
|
5341
|
+
return { ...activity, endDate: toIsoDay(shiftActivityDay(activity.endDate, clampRight)) };
|
|
5342
|
+
});
|
|
5343
|
+
const chainShifts = chainPush ? delta !== 0 ? pushActivityChain(allTasks ?? [], task.id, activityDrag.id, delta).shifts : [] : (() => {
|
|
5344
|
+
const endDeltaDays = chainEndDeltaDays(activityDrag.mode, delta, startInfo.durationDays);
|
|
5345
|
+
return endDeltaDays !== 0 ? shiftActivityChain(allTasks ?? [], task.id, activityDrag.id, endDeltaDays) : [];
|
|
5346
|
+
})();
|
|
5347
|
+
const changedTasks = [];
|
|
5348
|
+
let hostHandled = false;
|
|
5349
|
+
for (const shift of chainShifts) {
|
|
5350
|
+
if (shift.taskId === task.id) {
|
|
5351
|
+
hostHandled = true;
|
|
5352
|
+
changedTasks.push({
|
|
5353
|
+
...normalizedTask,
|
|
5354
|
+
activities: shift.activities.map((activity) => activity.id === activityDrag.id ? updatedActivities.find((item) => item.id === activityDrag.id) ?? activity : activity)
|
|
5355
|
+
});
|
|
5356
|
+
} else {
|
|
5357
|
+
const source = (allTasks ?? []).find((candidate) => candidate.id === shift.taskId);
|
|
5358
|
+
changedTasks.push({ ...source ?? { id: shift.taskId }, activities: shift.activities });
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
if (!hostHandled) {
|
|
5362
|
+
changedTasks.unshift({ ...normalizedTask, activities: updatedActivities });
|
|
5363
|
+
}
|
|
5364
|
+
onTasksChange?.(changedTasks);
|
|
5365
|
+
}
|
|
5366
|
+
setActivityDrag(null);
|
|
5367
|
+
activityDragStart.current = null;
|
|
5368
|
+
activityConstraintRef.current = null;
|
|
5369
|
+
document.body.style.cursor = "";
|
|
5370
|
+
};
|
|
5371
|
+
document.body.style.cursor = activityDrag.mode === "move" ? "grabbing" : "ew-resize";
|
|
5372
|
+
window.addEventListener("mousemove", onMove);
|
|
5373
|
+
window.addEventListener("mouseup", onUp);
|
|
5374
|
+
return () => {
|
|
5375
|
+
window.removeEventListener("mousemove", onMove);
|
|
5376
|
+
window.removeEventListener("mouseup", onUp);
|
|
5377
|
+
document.body.style.cursor = "";
|
|
5378
|
+
};
|
|
5379
|
+
}, [activityDrag, activityPreviewStore, activityBlockStore, activityDragOwner, allTasks, dayWidth, normalizedTask, onTasksChange, publishChainPreview, task.activities]);
|
|
5380
|
+
const anchorActivityTip = (rect) => {
|
|
5381
|
+
const below = rect.top < 34;
|
|
5382
|
+
const anchor = {
|
|
5383
|
+
left: Math.max(8, Math.min(rect.left, window.innerWidth - 160)),
|
|
5384
|
+
// Вплотную к полосе (1px зазора), сверху или снизу при нехватке места.
|
|
5385
|
+
top: below ? rect.bottom + 1 : rect.top - 1,
|
|
5386
|
+
below
|
|
5387
|
+
};
|
|
5388
|
+
activityTipAnchor.current = anchor;
|
|
5389
|
+
return anchor;
|
|
5390
|
+
};
|
|
5391
|
+
const handleActivityPointerDown = (segment, mode) => (event) => {
|
|
5392
|
+
if (!activityDragEnabled) return;
|
|
5393
|
+
event.preventDefault();
|
|
5394
|
+
event.stopPropagation();
|
|
5395
|
+
activityDragStart.current = {
|
|
5396
|
+
x: event.clientX,
|
|
5397
|
+
durationDays: Math.max(1, Math.round(segment.width / dayWidth) - 1)
|
|
5398
|
+
};
|
|
5399
|
+
activityConstraintRef.current = task.activityChain === "push" && mode !== "resize-right" ? activityStartConstraint(allTasks ?? [], task.id, segment.id) : null;
|
|
5400
|
+
activityDragOwner?.set(activityOwnerKey(task.id, segment.id));
|
|
5401
|
+
setActivityBlockedBy(null);
|
|
5402
|
+
setActivityTipPosition(anchorActivityTip(event.currentTarget.getBoundingClientRect()));
|
|
5403
|
+
setActivityTipId(segment.id);
|
|
5404
|
+
setActivityDrag({ id: segment.id, mode, dayDelta: 0 });
|
|
5405
|
+
};
|
|
5406
|
+
const liveActivityGeometry = (segment) => {
|
|
5407
|
+
if (!activityDrag || activityDrag.id !== segment.id || activityDrag.dayDelta === 0) return segment;
|
|
5408
|
+
if (activityDrag.mode === "move") {
|
|
5409
|
+
return { left: segment.left + activityDrag.dayDelta * dayWidth, width: segment.width };
|
|
5410
|
+
}
|
|
5411
|
+
if (activityDrag.mode === "resize-left") {
|
|
5412
|
+
return { left: segment.left + activityDrag.dayDelta * dayWidth, width: segment.width - activityDrag.dayDelta * dayWidth };
|
|
5413
|
+
}
|
|
5414
|
+
return { left: segment.left, width: segment.width + activityDrag.dayDelta * dayWidth };
|
|
5415
|
+
};
|
|
5416
|
+
const activityLinks = (0, import_react3.useMemo)(() => {
|
|
5417
|
+
const gaps = [];
|
|
5418
|
+
const elbows = [];
|
|
5419
|
+
if (!showActivityLinks || !renderActivities || !activityLayout) return { gaps, elbows };
|
|
5420
|
+
const activities = task.activities ?? [];
|
|
5421
|
+
const segmentById = new Map(activityLayout.segments.map((segment) => [segment.id, segment]));
|
|
5422
|
+
const visibleIds = new Set(visibleActivitySegments.map((segment) => segment.id));
|
|
5423
|
+
const laneTopY = (lane) => activityTopOffset + lane * ACTIVITY_LANE_STEP + (ACTIVITY_LANE_STEP - ACTIVITY_LANE_BAR_HEIGHT) / 2;
|
|
5424
|
+
const liveRect = (segment) => {
|
|
5425
|
+
const geometry = liveActivityGeometry(segment);
|
|
5426
|
+
const chainDelta = activityOverrides?.get(segment.id) ?? 0;
|
|
5427
|
+
const left2 = geometry.left + (chainDelta !== 0 ? chainDelta * dayWidth : 0);
|
|
5428
|
+
return { left: left2, right: left2 + geometry.width };
|
|
5429
|
+
};
|
|
5430
|
+
for (let index = 0; index < activities.length - 1; index += 1) {
|
|
5431
|
+
const predecessor = segmentById.get(activities[index].id);
|
|
5432
|
+
const successor = segmentById.get(activities[index + 1].id);
|
|
5433
|
+
if (!predecessor || !successor) continue;
|
|
5434
|
+
if (!visibleIds.has(predecessor.id) || !visibleIds.has(successor.id)) continue;
|
|
5435
|
+
const lag = activities[index + 1].lag ?? 0;
|
|
5436
|
+
const predRect = liveRect(predecessor);
|
|
5437
|
+
const succRect = liveRect(successor);
|
|
5438
|
+
if (predecessor.lane === successor.lane && succRect.left >= predRect.right) {
|
|
5439
|
+
const width2 = succRect.left - predRect.right;
|
|
5440
|
+
if (width2 >= 1) {
|
|
5441
|
+
gaps.push({ key: `${predecessor.id}->${successor.id}`, left: predRect.right, width: width2, lane: predecessor.lane, lag });
|
|
5442
|
+
}
|
|
5443
|
+
continue;
|
|
5444
|
+
}
|
|
5445
|
+
const predecessorLaneTop = laneTopY(predecessor.lane);
|
|
5446
|
+
const successorLaneTop = laneTopY(successor.lane);
|
|
5447
|
+
const sameLane = predecessor.lane === successor.lane;
|
|
5448
|
+
const reverseOrder = !sameLane && successorLaneTop < predecessorLaneTop;
|
|
5449
|
+
const fromY = reverseOrder ? predecessorLaneTop + 6 : predecessorLaneTop + ACTIVITY_LANE_BAR_HEIGHT - 6;
|
|
5450
|
+
const toY = reverseOrder ? successorLaneTop + ACTIVITY_LANE_BAR_HEIGHT - 6 : successorLaneTop + (sameLane ? ACTIVITY_LANE_STEP : 0) + 6;
|
|
5451
|
+
const fromX = predRect.right;
|
|
5452
|
+
const toX = succRect.left;
|
|
5453
|
+
elbows.push({
|
|
5454
|
+
key: `${predecessor.id}->${successor.id}`,
|
|
5455
|
+
d: calculateDependencyPath({ x: fromX, y: fromY }, { x: toX, y: toY }, false),
|
|
5456
|
+
lagX: toX - 14,
|
|
5457
|
+
lagY: reverseOrder ? fromY - 4 : fromY + 12,
|
|
5458
|
+
lag
|
|
5459
|
+
});
|
|
5460
|
+
}
|
|
5461
|
+
return { gaps, elbows };
|
|
5462
|
+
}, [showActivityLinks, renderActivities, activityLayout, visibleActivitySegments, activityTopOffset, activityOverrides, activityDrag, dayWidth, task.activities]);
|
|
5463
|
+
const activityTipData = (0, import_react3.useMemo)(() => {
|
|
5464
|
+
if (!activityTipId || !task.activities) return null;
|
|
5465
|
+
const activity = task.activities.find((item) => item.id === activityTipId);
|
|
5466
|
+
if (!activity) return null;
|
|
5467
|
+
let startDate = parseUTCDate(activity.startDate);
|
|
5468
|
+
let endDate = parseUTCDate(activity.endDate);
|
|
5469
|
+
if (activityDrag && activityDrag.id === activity.id && activityDrag.dayDelta !== 0) {
|
|
5470
|
+
startDate = shiftActivityDay(startDate, activityDrag.dayDelta);
|
|
5471
|
+
endDate = shiftActivityDay(endDate, activityDrag.dayDelta);
|
|
5472
|
+
}
|
|
5473
|
+
const progress = activity.progress === void 0 ? void 0 : Math.min(100, Math.max(0, Math.round(activity.progress)));
|
|
5474
|
+
return {
|
|
5475
|
+
floor: task.name,
|
|
5476
|
+
name: activity.name,
|
|
5477
|
+
// Готовность — первой строкой подсказки, дальше пользовательские поля.
|
|
5478
|
+
fields: [
|
|
5479
|
+
...progress !== void 0 ? [{ label: "\u0413\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u044C", value: `${progress}%` }] : [],
|
|
5480
|
+
...activity.tooltipFields ?? []
|
|
5481
|
+
],
|
|
5482
|
+
context: {
|
|
5483
|
+
task,
|
|
5484
|
+
activity,
|
|
5485
|
+
startDate,
|
|
5486
|
+
endDate,
|
|
5487
|
+
durationDays: activityDurationDays({ startDate, endDate }),
|
|
5488
|
+
progress
|
|
5489
|
+
}
|
|
5490
|
+
};
|
|
5491
|
+
}, [activityDrag, activityTipId, task, task.activities, task.name]);
|
|
4637
5492
|
const currentStartDate = hasPreviewPosition ? pixelsToDate(displayLeft, monthStart, dayWidth) : taskStartDate;
|
|
4638
5493
|
const currentEndDate = hasPreviewPosition ? milestone ? pixelsToDate(displayLeft, monthStart, dayWidth) : pixelsToDate(displayLeft + displayWidth - dayWidth, monthStart, dayWidth) : taskEndDate;
|
|
4639
5494
|
const dateRangeLabel = formatDateRangeLabel(currentStartDate, currentEndDate);
|
|
@@ -4666,7 +5521,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4666
5521
|
children: [
|
|
4667
5522
|
taskDivider === "top" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-divider gantt-tr-divider-top" }),
|
|
4668
5523
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-taskContainer", children: [
|
|
4669
|
-
shouldRenderBaseline && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5524
|
+
shouldRenderBaseline && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4670
5525
|
"div",
|
|
4671
5526
|
{
|
|
4672
5527
|
className: `gantt-tr-baseline ${isParent ? "gantt-tr-baseline-parent" : ""} ${milestone ? "gantt-tr-baseline-milestone" : ""}`,
|
|
@@ -4676,7 +5531,122 @@ var TaskRow = import_react3.default.memo(
|
|
|
4676
5531
|
}
|
|
4677
5532
|
}
|
|
4678
5533
|
),
|
|
4679
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5534
|
+
renderActivities && activityLayout && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "gantt-tr-activityLanes", children: [
|
|
5535
|
+
activityLinks.elbows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { className: "gantt-tr-activityLinksSvg", width: "100%", height: "100%", "aria-hidden": "true", children: [
|
|
5536
|
+
/* @__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)" }) }) }),
|
|
5537
|
+
activityLinks.elbows.map((link) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
|
|
5538
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { className: "gantt-dependency-path", d: link.d, markerEnd: "url(#arrowhead)" }),
|
|
5539
|
+
showActivityLag && link.lag !== 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5540
|
+
"text",
|
|
5541
|
+
{
|
|
5542
|
+
className: "gantt-dependency-lag-label",
|
|
5543
|
+
x: link.lagX,
|
|
5544
|
+
y: link.lagY,
|
|
5545
|
+
textAnchor: "middle",
|
|
5546
|
+
fontSize: "10",
|
|
5547
|
+
fill: "var(--gantt-dependency-line-color, #666666)",
|
|
5548
|
+
children: link.lag > 0 ? `+${link.lag}` : `${link.lag}`
|
|
5549
|
+
}
|
|
5550
|
+
)
|
|
5551
|
+
] }, link.key))
|
|
5552
|
+
] }),
|
|
5553
|
+
visibleActivitySegments.map((segment) => {
|
|
5554
|
+
const live = liveActivityGeometry(segment);
|
|
5555
|
+
const chainDelta = activityOverrides?.get(segment.id) ?? 0;
|
|
5556
|
+
const activityProgress = segment.progress === void 0 ? 0 : Math.min(100, Math.max(0, Math.round(segment.progress)));
|
|
5557
|
+
const liveLeft = live.left + (chainDelta !== 0 ? chainDelta * dayWidth : 0);
|
|
5558
|
+
const isDraggingActivity = activityDrag?.id === segment.id;
|
|
5559
|
+
const isBlockingActivity = blockedActivityIds?.has(segment.id) ?? false;
|
|
5560
|
+
const isAtLimit = isDraggingActivity && activityBlockedBy !== null;
|
|
5561
|
+
const isActiveActivity = activationVisualEnabled && activeActivityKey === activityOwnerKey(task.id, segment.id);
|
|
5562
|
+
const isSameWorkType = activeWork !== null && (segment.id === activeWork.id || activeWork.name !== null && segment.name === activeWork.name);
|
|
5563
|
+
const isDimmed = dimOthers && activeWork !== null && !isSameWorkType;
|
|
5564
|
+
const isChainHighlighted = highlightChain && activeWork !== null && isSameWorkType;
|
|
5565
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5566
|
+
"div",
|
|
5567
|
+
{
|
|
5568
|
+
"data-activity-id": segment.id,
|
|
5569
|
+
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"}`,
|
|
5570
|
+
style: {
|
|
5571
|
+
left: `${liveLeft}px`,
|
|
5572
|
+
width: `${live.width}px`,
|
|
5573
|
+
top: `${activityTopOffset + segment.lane * ACTIVITY_LANE_STEP + (ACTIVITY_LANE_STEP - ACTIVITY_LANE_BAR_HEIGHT) / 2}px`,
|
|
5574
|
+
height: `${ACTIVITY_LANE_BAR_HEIGHT}px`,
|
|
5575
|
+
backgroundColor: segment.color || "var(--gantt-task-bar-default-color)"
|
|
5576
|
+
},
|
|
5577
|
+
onMouseDown: (event) => {
|
|
5578
|
+
if (!activityDragEnabled) return;
|
|
5579
|
+
const activationKey = activityOwnerKey(task.id, segment.id);
|
|
5580
|
+
const isActive = activityActivationStore?.getActiveKey() === activationKey;
|
|
5581
|
+
if (activationVisualEnabled && !isActive) {
|
|
5582
|
+
activityActivationStore?.setActiveKey(activationKey);
|
|
5583
|
+
}
|
|
5584
|
+
if (activityClickToDrag && !isActive) return;
|
|
5585
|
+
const target = event.target;
|
|
5586
|
+
const mode = target.closest(".gantt-tr-resizeHandleLeft") ? "resize-left" : target.closest(".gantt-tr-resizeHandleRight") ? "resize-right" : "move";
|
|
5587
|
+
handleActivityPointerDown(segment, mode)(event);
|
|
5588
|
+
},
|
|
5589
|
+
onMouseEnter: (event) => {
|
|
5590
|
+
if (activityDrag || activityDragOwner?.get()) return;
|
|
5591
|
+
setActivityTipPosition(anchorActivityTip(event.currentTarget.getBoundingClientRect()));
|
|
5592
|
+
setActivityTipId(segment.id);
|
|
5593
|
+
},
|
|
5594
|
+
onMouseLeave: () => {
|
|
5595
|
+
if (!activityDrag) {
|
|
5596
|
+
setActivityTipId((current) => current === segment.id ? null : current);
|
|
5597
|
+
activityTipAnchor.current = null;
|
|
5598
|
+
setActivityTipPosition(null);
|
|
5599
|
+
}
|
|
5600
|
+
},
|
|
5601
|
+
children: [
|
|
5602
|
+
activityProgress > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5603
|
+
"span",
|
|
5604
|
+
{
|
|
5605
|
+
className: "gantt-tr-activityProgress",
|
|
5606
|
+
style: { width: `${activityProgress}%` }
|
|
5607
|
+
}
|
|
5608
|
+
),
|
|
5609
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityName", style: { color: activityTextColor(segment.color) }, children: segment.name }),
|
|
5610
|
+
activityDragEnabled && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
5611
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleLeft" }),
|
|
5612
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-resizeHandle gantt-tr-resizeHandleRight" })
|
|
5613
|
+
] })
|
|
5614
|
+
]
|
|
5615
|
+
},
|
|
5616
|
+
segment.id
|
|
5617
|
+
);
|
|
5618
|
+
}),
|
|
5619
|
+
activityLinks.gaps.map((gap) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react3.default.Fragment, { children: [
|
|
5620
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5621
|
+
"span",
|
|
5622
|
+
{
|
|
5623
|
+
className: "gantt-tr-activityLink",
|
|
5624
|
+
"aria-hidden": "true",
|
|
5625
|
+
style: {
|
|
5626
|
+
left: `${gap.left}px`,
|
|
5627
|
+
width: `${gap.width}px`,
|
|
5628
|
+
top: `${activityTopOffset + gap.lane * ACTIVITY_LANE_STEP + ACTIVITY_LANE_STEP / 2}px`
|
|
5629
|
+
}
|
|
5630
|
+
}
|
|
5631
|
+
),
|
|
5632
|
+
showActivityLag && gap.lag > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5633
|
+
"span",
|
|
5634
|
+
{
|
|
5635
|
+
className: "gantt-tr-activityLag",
|
|
5636
|
+
title: `\u0417\u0430\u0437\u043E\u0440 ${gap.lag} \u0434`,
|
|
5637
|
+
style: {
|
|
5638
|
+
left: `${gap.left + gap.width - 14}px`,
|
|
5639
|
+
top: `${activityTopOffset + gap.lane * ACTIVITY_LANE_STEP + ACTIVITY_LANE_STEP / 2}px`
|
|
5640
|
+
},
|
|
5641
|
+
children: [
|
|
5642
|
+
"+",
|
|
5643
|
+
gap.lag
|
|
5644
|
+
]
|
|
5645
|
+
}
|
|
5646
|
+
)
|
|
5647
|
+
] }, gap.key))
|
|
5648
|
+
] }),
|
|
5649
|
+
!renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
4680
5650
|
"div",
|
|
4681
5651
|
{
|
|
4682
5652
|
"data-taskbar": true,
|
|
@@ -4832,7 +5802,33 @@ var TaskRow = import_react3.default.memo(
|
|
|
4832
5802
|
),
|
|
4833
5803
|
document.body
|
|
4834
5804
|
),
|
|
4835
|
-
|
|
5805
|
+
activityTipData && activityTipPosition && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
|
|
5806
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5807
|
+
"div",
|
|
5808
|
+
{
|
|
5809
|
+
className: `gantt-tr-activityTip${activityTipPosition.below ? " gantt-tr-activityTip-below" : ""}`,
|
|
5810
|
+
style: { left: `${activityTipPosition.left}px`, top: `${activityTipPosition.top}px` },
|
|
5811
|
+
role: "tooltip",
|
|
5812
|
+
children: [
|
|
5813
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipName", children: activityTipData.floor }),
|
|
5814
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipDot", "aria-hidden": "true", children: "\xB7" }),
|
|
5815
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipWork", children: activityTipData.name }),
|
|
5816
|
+
activityTooltip && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "gantt-tr-activityTipExtra", children: activityTooltip(activityTipData.context) }),
|
|
5817
|
+
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: [
|
|
5818
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipFieldLabel", children: field.label }),
|
|
5819
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-activityTipFieldValue", children: field.value })
|
|
5820
|
+
] }, field.label)) }),
|
|
5821
|
+
activityBlockedBy && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-activityTipReason", children: [
|
|
5822
|
+
"\xB7 \u0411\u043B\u043E\u043A: ",
|
|
5823
|
+
activityBlockedBy.names.join(", "),
|
|
5824
|
+
activityBlockedBy.lag > 0 ? ` \xB7 \u0437\u0430\u0437\u043E\u0440 ${activityBlockedBy.lag} \u0434` : ""
|
|
5825
|
+
] })
|
|
5826
|
+
]
|
|
5827
|
+
}
|
|
5828
|
+
),
|
|
5829
|
+
document.body
|
|
5830
|
+
),
|
|
5831
|
+
!disableDependencyEditing && onDependencyPortPointerDown && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
4836
5832
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4837
5833
|
"button",
|
|
4838
5834
|
{
|
|
@@ -4862,7 +5858,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4862
5858
|
}
|
|
4863
5859
|
)
|
|
4864
5860
|
] }),
|
|
4865
|
-
showTaskDateLabels && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5861
|
+
showTaskDateLabels && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4866
5862
|
"div",
|
|
4867
5863
|
{
|
|
4868
5864
|
className: `gantt-tr-leftLabels ${task.locked ? "gantt-tr-leftLabels-locked" : ""}`,
|
|
@@ -4872,7 +5868,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4872
5868
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-dateLabel gantt-tr-dateLabelLeft", children: dateRangeLabel })
|
|
4873
5869
|
}
|
|
4874
5870
|
),
|
|
4875
|
-
task.locked && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
5871
|
+
task.locked && !renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4876
5872
|
"svg",
|
|
4877
5873
|
{
|
|
4878
5874
|
className: "gantt-tr-lockIcon",
|
|
@@ -4893,15 +5889,15 @@ var TaskRow = import_react3.default.memo(
|
|
|
4893
5889
|
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" })
|
|
4894
5890
|
}
|
|
4895
5891
|
),
|
|
4896
|
-
|
|
5892
|
+
compactDetail && !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
4897
5893
|
"span",
|
|
4898
5894
|
{
|
|
4899
|
-
className:
|
|
5895
|
+
className: "gantt-tr-externalDuration gantt-tr-durationBeforeBar gantt-tr-compositeDuration",
|
|
4900
5896
|
style: { left: `${visualLeft - 46}px`, color: isParent ? task.color || defaultParentBarColor : barColor },
|
|
4901
5897
|
children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434`
|
|
4902
5898
|
}
|
|
4903
5899
|
),
|
|
4904
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
5900
|
+
!renderActivities && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
4905
5901
|
"div",
|
|
4906
5902
|
{
|
|
4907
5903
|
className: "gantt-tr-rightLabels",
|
|
@@ -4910,6 +5906,7 @@ var TaskRow = import_react3.default.memo(
|
|
|
4910
5906
|
color: isParent ? task.color || defaultParentBarColor : barColor
|
|
4911
5907
|
},
|
|
4912
5908
|
children: [
|
|
5909
|
+
!compactDetail && !showDurationInside && !milestone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "gantt-tr-externalDuration", children: isVisualParent ? getChildCountLabel(childCount) : `${durationDays} \u0434` }),
|
|
4913
5910
|
progressWidth > 0 && !showProgressInside && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "gantt-tr-externalProgress", children: [
|
|
4914
5911
|
progressWidth,
|
|
4915
5912
|
"%"
|
|
@@ -5037,10 +6034,10 @@ var import_react6 = __toESM(require("react"));
|
|
|
5037
6034
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
5038
6035
|
var arePropsEqual2 = (prevProps, nextProps) => {
|
|
5039
6036
|
return prevProps.dayWidth === nextProps.dayWidth && prevProps.dateRange.length === nextProps.dateRange.length && prevProps.totalHeight === nextProps.totalHeight && // skip re-render only when totalHeight unchanged
|
|
5040
|
-
prevProps.viewMode === nextProps.viewMode && prevProps.isCustomWeekend === nextProps.isCustomWeekend;
|
|
6037
|
+
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;
|
|
5041
6038
|
};
|
|
5042
6039
|
var GridBackground = import_react6.default.memo(
|
|
5043
|
-
({ dateRange, dayWidth, totalHeight, viewMode = "day", isCustomWeekend, className, showWeekendBlocks = true, showGridLines = true }) => {
|
|
6040
|
+
({ dateRange, dayWidth, totalHeight, viewMode = "day", isCustomWeekend, className, showWeekendBlocks = true, showGridLines = true, horizontalWindow }) => {
|
|
5044
6041
|
const weekGridLines = (0, import_react6.useMemo)(() => {
|
|
5045
6042
|
if (viewMode !== "week") return [];
|
|
5046
6043
|
return calculateWeekGridLines(dateRange, dayWidth);
|
|
@@ -5057,6 +6054,23 @@ var GridBackground = import_react6.default.memo(
|
|
|
5057
6054
|
if (viewMode === "week" || viewMode === "month") return [];
|
|
5058
6055
|
return calculateWeekendBlocks(dateRange, dayWidth, isCustomWeekend);
|
|
5059
6056
|
}, [dateRange, dayWidth, viewMode, isCustomWeekend]);
|
|
6057
|
+
const inHorizontalWindow = (left, width = 1) => !horizontalWindow || left <= horizontalWindow.endPx && left + width >= horizontalWindow.startPx;
|
|
6058
|
+
const visibleWeekendBlocks = (0, import_react6.useMemo)(
|
|
6059
|
+
() => weekendBlocks.filter((block) => inHorizontalWindow(block.left, block.width)),
|
|
6060
|
+
[weekendBlocks, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
6061
|
+
);
|
|
6062
|
+
const visibleWeekGridLines = (0, import_react6.useMemo)(
|
|
6063
|
+
() => weekGridLines.filter((line) => inHorizontalWindow(line.x)),
|
|
6064
|
+
[weekGridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
6065
|
+
);
|
|
6066
|
+
const visibleMonthGridLines = (0, import_react6.useMemo)(
|
|
6067
|
+
() => monthGridLines.filter((line) => inHorizontalWindow(line.x)),
|
|
6068
|
+
[monthGridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
6069
|
+
);
|
|
6070
|
+
const visibleGridLines = (0, import_react6.useMemo)(
|
|
6071
|
+
() => gridLines.filter((line) => inHorizontalWindow(line.x)),
|
|
6072
|
+
[gridLines, horizontalWindow?.startPx, horizontalWindow?.endPx]
|
|
6073
|
+
);
|
|
5060
6074
|
const gridWidth = (0, import_react6.useMemo)(() => {
|
|
5061
6075
|
return Math.round(dateRange.length * dayWidth);
|
|
5062
6076
|
}, [dateRange.length, dayWidth]);
|
|
@@ -5069,7 +6083,7 @@ var GridBackground = import_react6.default.memo(
|
|
|
5069
6083
|
height: `${totalHeight}px`
|
|
5070
6084
|
},
|
|
5071
6085
|
children: [
|
|
5072
|
-
showWeekendBlocks &&
|
|
6086
|
+
showWeekendBlocks && visibleWeekendBlocks.map((block, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5073
6087
|
"div",
|
|
5074
6088
|
{
|
|
5075
6089
|
className: "gantt-gb-weekendBlock",
|
|
@@ -5082,7 +6096,7 @@ var GridBackground = import_react6.default.memo(
|
|
|
5082
6096
|
)),
|
|
5083
6097
|
showGridLines && (viewMode === "week" ? (
|
|
5084
6098
|
// Week-view: one line per week column boundary
|
|
5085
|
-
|
|
6099
|
+
visibleWeekGridLines.map((line, index) => {
|
|
5086
6100
|
const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : "gantt-gb-weekSeparator";
|
|
5087
6101
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5088
6102
|
"div",
|
|
@@ -5095,7 +6109,7 @@ var GridBackground = import_react6.default.memo(
|
|
|
5095
6109
|
})
|
|
5096
6110
|
) : viewMode === "month" ? (
|
|
5097
6111
|
// Month-view: thin line at each month boundary, thick at year boundary
|
|
5098
|
-
|
|
6112
|
+
visibleMonthGridLines.map((line, index) => {
|
|
5099
6113
|
const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : "gantt-gb-weekSeparator";
|
|
5100
6114
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5101
6115
|
"div",
|
|
@@ -5107,8 +6121,8 @@ var GridBackground = import_react6.default.memo(
|
|
|
5107
6121
|
);
|
|
5108
6122
|
})
|
|
5109
6123
|
) : (
|
|
5110
|
-
// Day-view:
|
|
5111
|
-
|
|
6124
|
+
// Day-view: render only the lines inside the horizontal window.
|
|
6125
|
+
visibleGridLines.map((line, index) => {
|
|
5112
6126
|
const lineClass = line.isMonthStart ? "gantt-gb-monthSeparator" : line.isWeekStart ? "gantt-gb-weekSeparator" : "gantt-gb-dayLine";
|
|
5113
6127
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
5114
6128
|
"div",
|
|
@@ -5220,7 +6234,8 @@ var DependencyLines = import_react7.default.memo(({
|
|
|
5220
6234
|
criticalTaskIds,
|
|
5221
6235
|
businessDays = true,
|
|
5222
6236
|
weekendPredicate,
|
|
5223
|
-
onDependencyClick
|
|
6237
|
+
onDependencyClick,
|
|
6238
|
+
horizontalWindow
|
|
5224
6239
|
}) => {
|
|
5225
6240
|
const [hoveredLineId, setHoveredLineId] = (0, import_react7.useState)(null);
|
|
5226
6241
|
const tasksForPositions = allTasks ?? tasks;
|
|
@@ -5384,6 +6399,10 @@ var DependencyLines = import_react7.default.memo(({
|
|
|
5384
6399
|
}
|
|
5385
6400
|
return lines2;
|
|
5386
6401
|
}, [tasks, allTasks, taskPositions, taskIndices, cycleInfo, collapsedParentIds, criticalTaskIds]);
|
|
6402
|
+
const visibleLines = (0, import_react7.useMemo)(() => {
|
|
6403
|
+
if (!horizontalWindow) return lines;
|
|
6404
|
+
return lines.filter((line) => Math.max(line.fromX, line.toX) >= horizontalWindow.startPx && Math.min(line.fromX, line.toX) <= horizontalWindow.endPx);
|
|
6405
|
+
}, [lines, horizontalWindow?.startPx, horizontalWindow?.endPx]);
|
|
5387
6406
|
const svgHeight = totalHeight ?? tasks.length * rowHeight;
|
|
5388
6407
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
5389
6408
|
"svg",
|
|
@@ -5491,7 +6510,7 @@ var DependencyLines = import_react7.default.memo(({
|
|
|
5491
6510
|
}
|
|
5492
6511
|
)
|
|
5493
6512
|
] }),
|
|
5494
|
-
|
|
6513
|
+
visibleLines.map(({ id, predecessorId, successorId, linkType, path, hasCycle, lag, fromX, toX, fromY, reverseOrder, isVirtual, isCritical }) => {
|
|
5495
6514
|
const isHovered = hoveredLineId === id;
|
|
5496
6515
|
const isSelected = selectedDep != null && id === `${selectedDep.predecessorId}-${selectedDep.successorId}-${selectedDep.linkType}`;
|
|
5497
6516
|
let pathClassName = "gantt-dependency-path";
|
|
@@ -6331,7 +7350,7 @@ var LINK_TYPE_LABELS = {
|
|
|
6331
7350
|
};
|
|
6332
7351
|
|
|
6333
7352
|
// src/components/TaskList/defaultTaskDates.ts
|
|
6334
|
-
var
|
|
7353
|
+
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
6335
7354
|
var DEFAULT_TASK_DURATION_DAYS = 5;
|
|
6336
7355
|
function toISODate(date) {
|
|
6337
7356
|
return date.toISOString().split("T")[0];
|
|
@@ -6360,25 +7379,25 @@ function buildDefaultTaskDateRange(startDate, {
|
|
|
6360
7379
|
}
|
|
6361
7380
|
return {
|
|
6362
7381
|
startDate: toISODate(start),
|
|
6363
|
-
endDate: toISODate(new Date(start.getTime() + (durationDays - 1) *
|
|
7382
|
+
endDate: toISODate(new Date(start.getTime() + (durationDays - 1) * DAY_MS3))
|
|
6364
7383
|
};
|
|
6365
7384
|
}
|
|
6366
7385
|
|
|
6367
7386
|
// src/components/TaskList/TaskListRow.tsx
|
|
6368
7387
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
6369
|
-
var
|
|
7388
|
+
var DAY_MS4 = 24 * 60 * 60 * 1e3;
|
|
6370
7389
|
var LINK_TYPE_ORDER = ["FS", "SS", "FF", "SF"];
|
|
6371
7390
|
var getInclusiveDurationDays = (startDate, endDate) => {
|
|
6372
7391
|
const start = parseUTCDate(startDate);
|
|
6373
7392
|
const end = parseUTCDate(endDate);
|
|
6374
7393
|
return Math.max(
|
|
6375
7394
|
1,
|
|
6376
|
-
Math.round((end.getTime() - start.getTime()) /
|
|
7395
|
+
Math.round((end.getTime() - start.getTime()) / DAY_MS4) + 1
|
|
6377
7396
|
);
|
|
6378
7397
|
};
|
|
6379
7398
|
var getEndDateFromDuration = (startDate, durationDays) => {
|
|
6380
7399
|
const start = parseUTCDate(startDate);
|
|
6381
|
-
return new Date(start.getTime() + (durationDays - 1) *
|
|
7400
|
+
return new Date(start.getTime() + (durationDays - 1) * DAY_MS4).toISOString().split("T")[0];
|
|
6382
7401
|
};
|
|
6383
7402
|
var TrashIcon = () => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
6384
7403
|
"svg",
|
|
@@ -10484,7 +11503,11 @@ var TaskList = ({
|
|
|
10484
11503
|
onActiveCustomCellChange: setActiveCustomCell,
|
|
10485
11504
|
taskListMenuCommands,
|
|
10486
11505
|
hideTaskListRowActions,
|
|
10487
|
-
rowClassName: [
|
|
11506
|
+
rowClassName: [
|
|
11507
|
+
task.parentId && compositeParentIds.has(task.parentId) ? "gantt-tl-row-composite-detail" : "",
|
|
11508
|
+
(task.activities?.length ?? 0) > 0 && (rowHeights?.[index] ?? rowHeight) > rowHeight ? "gantt-tl-row-activities" : "",
|
|
11509
|
+
getTaskListRowClassName?.(task)
|
|
11510
|
+
].filter(Boolean).join(" "),
|
|
10488
11511
|
compositeParentColor: task.parentId ? compositeParentsById.get(task.parentId)?.color : void 0,
|
|
10489
11512
|
getTaskListNamePrefixIcon,
|
|
10490
11513
|
taskDateChangeMode,
|
|
@@ -12462,7 +13485,7 @@ var import_react17 = require("react");
|
|
|
12462
13485
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
12463
13486
|
var AUTO_COLUMN_MIN_WIDTH = 72;
|
|
12464
13487
|
var AUTO_COLUMN_MAX_WIDTH = 180;
|
|
12465
|
-
var
|
|
13488
|
+
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
12466
13489
|
function joinClasses(...values) {
|
|
12467
13490
|
return values.filter(Boolean).join(" ");
|
|
12468
13491
|
}
|
|
@@ -12497,8 +13520,8 @@ function getOverlayWidthPercent(column, overlayDateMs) {
|
|
|
12497
13520
|
if (overlayDateMs >= endMs) {
|
|
12498
13521
|
return 100;
|
|
12499
13522
|
}
|
|
12500
|
-
const totalDays = Math.max(1, Math.round((endMs - startMs) /
|
|
12501
|
-
const elapsedDays = Math.min(totalDays, Math.max(0, Math.round((overlayDateMs - startMs) /
|
|
13523
|
+
const totalDays = Math.max(1, Math.round((endMs - startMs) / DAY_MS5) + 1);
|
|
13524
|
+
const elapsedDays = Math.min(totalDays, Math.max(0, Math.round((overlayDateMs - startMs) / DAY_MS5) + 1));
|
|
12502
13525
|
return elapsedDays / totalDays * 100;
|
|
12503
13526
|
}
|
|
12504
13527
|
function isOverlayDateInColumn(column, overlayDateMs) {
|
|
@@ -12828,7 +13851,7 @@ function TableMatrix({
|
|
|
12828
13851
|
// src/components/PlanFactMatrix/PlanFactMatrix.tsx
|
|
12829
13852
|
var import_react18 = __toESM(require("react"));
|
|
12830
13853
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
12831
|
-
var
|
|
13854
|
+
var DAY_MS6 = 24 * 60 * 60 * 1e3;
|
|
12832
13855
|
function joinClasses2(...values) {
|
|
12833
13856
|
return values.filter(Boolean).join(" ");
|
|
12834
13857
|
}
|
|
@@ -12858,8 +13881,8 @@ function getPlannedIndexRange(task, rangeStartMs, rangeLength) {
|
|
|
12858
13881
|
if (rangeLength <= 0) return null;
|
|
12859
13882
|
const start = parseUTCDate(task.startDate);
|
|
12860
13883
|
const end = parseUTCDate(task.endDate);
|
|
12861
|
-
const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) /
|
|
12862
|
-
const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) /
|
|
13884
|
+
const startIndex = Math.ceil((getDateOnlyMs(start) - rangeStartMs) / DAY_MS6);
|
|
13885
|
+
const endIndex = Math.floor((getDateOnlyMs(end) - rangeStartMs) / DAY_MS6);
|
|
12863
13886
|
const clampedStartIndex = Math.max(0, startIndex);
|
|
12864
13887
|
const clampedEndIndex = Math.min(rangeLength - 1, endIndex);
|
|
12865
13888
|
if (clampedStartIndex > clampedEndIndex) return null;
|
|
@@ -14228,80 +15251,13 @@ async function printGanttChart({
|
|
|
14228
15251
|
}
|
|
14229
15252
|
}
|
|
14230
15253
|
|
|
14231
|
-
// src/components/GanttChart/previewStore.ts
|
|
14232
|
-
function positionsEqual(left, right) {
|
|
14233
|
-
return left?.left === right?.left && left?.width === right?.width;
|
|
14234
|
-
}
|
|
14235
|
-
function createTaskPreviewPositionStore() {
|
|
14236
|
-
let positions = /* @__PURE__ */ new Map();
|
|
14237
|
-
const listenersByTaskId = /* @__PURE__ */ new Map();
|
|
14238
|
-
function notify(taskIds) {
|
|
14239
|
-
for (const taskId of taskIds) {
|
|
14240
|
-
const listeners = listenersByTaskId.get(taskId);
|
|
14241
|
-
if (!listeners) continue;
|
|
14242
|
-
for (const listener of listeners) {
|
|
14243
|
-
listener();
|
|
14244
|
-
}
|
|
14245
|
-
}
|
|
14246
|
-
}
|
|
14247
|
-
return {
|
|
14248
|
-
subscribeTask(taskId, listener) {
|
|
14249
|
-
const listeners = listenersByTaskId.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
14250
|
-
listeners.add(listener);
|
|
14251
|
-
listenersByTaskId.set(taskId, listeners);
|
|
14252
|
-
return () => {
|
|
14253
|
-
listeners.delete(listener);
|
|
14254
|
-
if (listeners.size === 0) {
|
|
14255
|
-
listenersByTaskId.delete(taskId);
|
|
14256
|
-
}
|
|
14257
|
-
};
|
|
14258
|
-
},
|
|
14259
|
-
getTaskPosition(taskId) {
|
|
14260
|
-
return positions.get(taskId);
|
|
14261
|
-
},
|
|
14262
|
-
setPositions(nextPositions) {
|
|
14263
|
-
const changedTaskIds = /* @__PURE__ */ new Set();
|
|
14264
|
-
const mergedPositions = /* @__PURE__ */ new Map();
|
|
14265
|
-
for (const [taskId, nextPosition] of nextPositions) {
|
|
14266
|
-
const currentPosition = positions.get(taskId);
|
|
14267
|
-
if (positionsEqual(currentPosition, nextPosition)) {
|
|
14268
|
-
if (currentPosition) {
|
|
14269
|
-
mergedPositions.set(taskId, currentPosition);
|
|
14270
|
-
}
|
|
14271
|
-
continue;
|
|
14272
|
-
}
|
|
14273
|
-
changedTaskIds.add(taskId);
|
|
14274
|
-
mergedPositions.set(taskId, {
|
|
14275
|
-
left: nextPosition.left,
|
|
14276
|
-
width: nextPosition.width
|
|
14277
|
-
});
|
|
14278
|
-
}
|
|
14279
|
-
for (const taskId of positions.keys()) {
|
|
14280
|
-
if (!nextPositions.has(taskId)) {
|
|
14281
|
-
changedTaskIds.add(taskId);
|
|
14282
|
-
}
|
|
14283
|
-
}
|
|
14284
|
-
if (changedTaskIds.size === 0) {
|
|
14285
|
-
return;
|
|
14286
|
-
}
|
|
14287
|
-
positions = mergedPositions;
|
|
14288
|
-
notify(changedTaskIds);
|
|
14289
|
-
},
|
|
14290
|
-
clear() {
|
|
14291
|
-
if (positions.size === 0) {
|
|
14292
|
-
return;
|
|
14293
|
-
}
|
|
14294
|
-
const changedTaskIds = new Set(positions.keys());
|
|
14295
|
-
positions = /* @__PURE__ */ new Map();
|
|
14296
|
-
notify(changedTaskIds);
|
|
14297
|
-
}
|
|
14298
|
-
};
|
|
14299
|
-
}
|
|
14300
|
-
|
|
14301
15254
|
// src/components/GanttChart/GanttChart.tsx
|
|
14302
15255
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
14303
15256
|
var SCROLL_TO_ROW_CONTEXT_ROWS = 2;
|
|
14304
15257
|
var TASK_ROW_OVERSCAN = 8;
|
|
15258
|
+
var SCROLL_TOP_BUCKET_PX = 64;
|
|
15259
|
+
var HORIZONTAL_WINDOW_BUCKET_PX = 480;
|
|
15260
|
+
var HORIZONTAL_WINDOW_OVERSCAN_PX = 960;
|
|
14305
15261
|
var PLAN_FACT_COLUMN_OVERSCAN = 24;
|
|
14306
15262
|
var PLAN_FACT_COLUMN_WINDOW_STEP = 14;
|
|
14307
15263
|
var DEFAULT_INITIAL_VIEWPORT_HEIGHT = 768;
|
|
@@ -14495,6 +15451,11 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14495
15451
|
showTaskDateLabels = true,
|
|
14496
15452
|
showTaskNames = true,
|
|
14497
15453
|
showCompositeSegments = true,
|
|
15454
|
+
activityClickToDrag = false,
|
|
15455
|
+
activityActivationMode = "none",
|
|
15456
|
+
showActivityLinks = true,
|
|
15457
|
+
showActivityLag = true,
|
|
15458
|
+
activityTooltip,
|
|
14498
15459
|
timelineMarkers,
|
|
14499
15460
|
additionalColumns,
|
|
14500
15461
|
hiddenTaskListColumns,
|
|
@@ -14528,17 +15489,41 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14528
15489
|
const clearSelectedTaskTimeoutRef = (0, import_react19.useRef)(null);
|
|
14529
15490
|
const hasAutoScrolledToTodayRef = (0, import_react19.useRef)(false);
|
|
14530
15491
|
const previewPositionStoreRef = (0, import_react19.useRef)(null);
|
|
15492
|
+
const activityPreviewStoreRef = (0, import_react19.useRef)(null);
|
|
15493
|
+
const activityBlockStoreRef = (0, import_react19.useRef)(null);
|
|
15494
|
+
const activityDragOwnerRef = (0, import_react19.useRef)(null);
|
|
15495
|
+
const activityActivationStoreRef = (0, import_react19.useRef)(null);
|
|
14531
15496
|
const renderedTaskIdsRef = (0, import_react19.useRef)(/* @__PURE__ */ new Set());
|
|
14532
15497
|
if (previewPositionStoreRef.current === null) {
|
|
14533
15498
|
previewPositionStoreRef.current = createTaskPreviewPositionStore();
|
|
14534
15499
|
}
|
|
15500
|
+
if (activityPreviewStoreRef.current === null) {
|
|
15501
|
+
activityPreviewStoreRef.current = createActivityPreviewStore();
|
|
15502
|
+
}
|
|
15503
|
+
if (activityBlockStoreRef.current === null) {
|
|
15504
|
+
activityBlockStoreRef.current = createActivityBlockStore();
|
|
15505
|
+
}
|
|
15506
|
+
if (activityDragOwnerRef.current === null) {
|
|
15507
|
+
activityDragOwnerRef.current = createActivityDragOwner();
|
|
15508
|
+
}
|
|
15509
|
+
if (activityActivationStoreRef.current === null) {
|
|
15510
|
+
activityActivationStoreRef.current = createActivityActivationStore();
|
|
15511
|
+
}
|
|
14535
15512
|
const previewPositionStore = previewPositionStoreRef.current;
|
|
15513
|
+
const activityPreviewStore = activityPreviewStoreRef.current;
|
|
15514
|
+
const activityBlockStore = activityBlockStoreRef.current;
|
|
15515
|
+
const activityDragOwner = activityDragOwnerRef.current;
|
|
15516
|
+
const activityActivationStore = activityActivationStoreRef.current;
|
|
14536
15517
|
const [selectedTaskId, setSelectedTaskId] = (0, import_react19.useState)(null);
|
|
14537
15518
|
const [taskListHasRightShadow, setTaskListHasRightShadow] = (0, import_react19.useState)(false);
|
|
14538
15519
|
const [internalTaskDateChangeMode, setInternalTaskDateChangeMode] = (0, import_react19.useState)("preserve-duration");
|
|
14539
15520
|
const [scrollViewport, setScrollViewport] = (0, import_react19.useState)(() => ({
|
|
14540
15521
|
scrollTop: 0,
|
|
14541
|
-
viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1)
|
|
15522
|
+
viewportHeight: getInitialScrollViewportHeight(containerHeight, headerHeight + 1),
|
|
15523
|
+
// Ширина видимой области графика и «бакетированный» горизонтальный скролл —
|
|
15524
|
+
// основа горизонтального окна рендера (см. horizontalWindow).
|
|
15525
|
+
viewportWidth: 0,
|
|
15526
|
+
chartScrollLeft: 0
|
|
14542
15527
|
}));
|
|
14543
15528
|
const [forceFullRenderForPrint, setForceFullRenderForPrint] = (0, import_react19.useState)(false);
|
|
14544
15529
|
const [planFactDateWindow, setPlanFactDateWindow] = (0, import_react19.useState)({ start: 0, end: 0 });
|
|
@@ -14776,14 +15761,21 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14776
15761
|
if (isPlanFactMode) {
|
|
14777
15762
|
return getPlanFactRangeTasks(normalizedTasks);
|
|
14778
15763
|
}
|
|
14779
|
-
|
|
14780
|
-
|
|
15764
|
+
const rangeTasks = [];
|
|
15765
|
+
for (const task of normalizedTasks) {
|
|
15766
|
+
if (showBaseline) {
|
|
15767
|
+
rangeTasks.push({
|
|
15768
|
+
startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
|
|
15769
|
+
endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
|
|
15770
|
+
});
|
|
15771
|
+
} else {
|
|
15772
|
+
rangeTasks.push(task);
|
|
15773
|
+
}
|
|
15774
|
+
for (const activity of task.activities ?? []) {
|
|
15775
|
+
rangeTasks.push({ startDate: activity.startDate, endDate: activity.endDate });
|
|
15776
|
+
}
|
|
14781
15777
|
}
|
|
14782
|
-
return
|
|
14783
|
-
...task,
|
|
14784
|
-
startDate: task.baselineStartDate && parseUTCDate(task.baselineStartDate).getTime() < parseUTCDate(task.startDate).getTime() ? task.baselineStartDate : task.startDate,
|
|
14785
|
-
endDate: task.baselineEndDate && parseUTCDate(task.baselineEndDate).getTime() > parseUTCDate(task.endDate).getTime() ? task.baselineEndDate : task.endDate
|
|
14786
|
-
}));
|
|
15778
|
+
return rangeTasks;
|
|
14787
15779
|
}, [isPlanFactMode, normalizedTasks, showBaseline]);
|
|
14788
15780
|
const dateRange = (0, import_react19.useMemo)(
|
|
14789
15781
|
() => {
|
|
@@ -14828,6 +15820,25 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14828
15820
|
}
|
|
14829
15821
|
return tasks2;
|
|
14830
15822
|
}, [normalizedTasks, collapsedParentIds, filterMode, taskFilter, criticalPathMode, criticalTaskIds]);
|
|
15823
|
+
const monthStart = (0, import_react19.useMemo)(() => {
|
|
15824
|
+
if (dateRange.length === 0) {
|
|
15825
|
+
return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
|
|
15826
|
+
}
|
|
15827
|
+
const firstDay = dateRange[0];
|
|
15828
|
+
if (explicitDateRange && !isPlanFactMode) {
|
|
15829
|
+
return new Date(firstDay);
|
|
15830
|
+
}
|
|
15831
|
+
return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
|
|
15832
|
+
}, [dateRange, explicitDateRange, isPlanFactMode]);
|
|
15833
|
+
const activityLaneCounts = (0, import_react19.useMemo)(() => {
|
|
15834
|
+
const counts = /* @__PURE__ */ new Map();
|
|
15835
|
+
for (const task of visibleTasks) {
|
|
15836
|
+
if (task.activities && task.activities.length > 0) {
|
|
15837
|
+
counts.set(task.id, computeActivityLanes(task.activities, monthStart, dayWidth).laneCount);
|
|
15838
|
+
}
|
|
15839
|
+
}
|
|
15840
|
+
return counts;
|
|
15841
|
+
}, [visibleTasks, monthStart, dayWidth]);
|
|
14831
15842
|
const compositeRowLayout = (0, import_react19.useMemo)(() => {
|
|
14832
15843
|
const compositeIds = new Set(normalizedTasks.filter((task) => task.composite).map((task) => task.id));
|
|
14833
15844
|
const tops = [];
|
|
@@ -14835,12 +15846,12 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14835
15846
|
let top = 0;
|
|
14836
15847
|
for (const task of visibleTasks) {
|
|
14837
15848
|
tops.push(top);
|
|
14838
|
-
const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : effectiveRowHeight;
|
|
15849
|
+
const height = task.parentId && compositeIds.has(task.parentId) ? Math.min(effectiveRowHeight, 22) : Math.max(effectiveRowHeight, activityLanesExtraHeight(activityLaneCounts.get(task.id) ?? 0));
|
|
14839
15850
|
heights.push(height);
|
|
14840
15851
|
top += height;
|
|
14841
15852
|
}
|
|
14842
15853
|
return { tops, heights, totalHeight: top };
|
|
14843
|
-
}, [normalizedTasks, visibleTasks, effectiveRowHeight]);
|
|
15854
|
+
}, [normalizedTasks, visibleTasks, effectiveRowHeight, activityLaneCounts]);
|
|
14844
15855
|
const compositeParentsById = (0, import_react19.useMemo)(
|
|
14845
15856
|
() => new Map(normalizedTasks.filter((task) => task.composite).map((task) => [task.id, task])),
|
|
14846
15857
|
[normalizedTasks]
|
|
@@ -14879,16 +15890,6 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14879
15890
|
}
|
|
14880
15891
|
return `calc(${containerHeight} - ${timelineHeaderHeight}px)`;
|
|
14881
15892
|
}, [containerHeight, isPlanFactMode, isTableMatrixMode, timelineHeaderHeight]);
|
|
14882
|
-
const monthStart = (0, import_react19.useMemo)(() => {
|
|
14883
|
-
if (dateRange.length === 0) {
|
|
14884
|
-
return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
|
|
14885
|
-
}
|
|
14886
|
-
const firstDay = dateRange[0];
|
|
14887
|
-
if (explicitDateRange && !isPlanFactMode) {
|
|
14888
|
-
return new Date(firstDay);
|
|
14889
|
-
}
|
|
14890
|
-
return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
|
|
14891
|
-
}, [dateRange, explicitDateRange, isPlanFactMode]);
|
|
14892
15893
|
const todayIndex = (0, import_react19.useMemo)(() => {
|
|
14893
15894
|
const today = getTodayLocalUtcDate();
|
|
14894
15895
|
return dateRange.findIndex((day) => day.getTime() === today.getTime());
|
|
@@ -14931,12 +15932,23 @@ function TaskGanttChartInner(props, ref) {
|
|
|
14931
15932
|
const nextScrollTop = container.scrollTop;
|
|
14932
15933
|
const nextScrollLeft = container.scrollLeft;
|
|
14933
15934
|
const nextChartScrollLeft = Math.max(0, nextScrollLeft);
|
|
15935
|
+
const bucketedScrollTop = Math.floor(nextScrollTop / SCROLL_TOP_BUCKET_PX) * SCROLL_TOP_BUCKET_PX;
|
|
15936
|
+
const bucketedChartScrollLeft = Math.floor(nextChartScrollLeft / HORIZONTAL_WINDOW_BUCKET_PX) * HORIZONTAL_WINDOW_BUCKET_PX;
|
|
14934
15937
|
setTaskListHasRightShadow(
|
|
14935
15938
|
(previous) => previous === nextHasRightShadow ? previous : nextHasRightShadow
|
|
14936
15939
|
);
|
|
14937
|
-
setScrollViewport(
|
|
14938
|
-
|
|
14939
|
-
|
|
15940
|
+
setScrollViewport((previous) => {
|
|
15941
|
+
const resolvedViewportHeight = previous.viewportHeight > 0 && nextViewportHeight === 0 ? previous.viewportHeight : nextViewportHeight;
|
|
15942
|
+
if (previous.scrollTop === bucketedScrollTop && previous.viewportHeight === resolvedViewportHeight && previous.viewportWidth === nextViewportWidth && previous.chartScrollLeft === bucketedChartScrollLeft) {
|
|
15943
|
+
return previous;
|
|
15944
|
+
}
|
|
15945
|
+
return {
|
|
15946
|
+
scrollTop: bucketedScrollTop,
|
|
15947
|
+
viewportHeight: resolvedViewportHeight,
|
|
15948
|
+
viewportWidth: nextViewportWidth,
|
|
15949
|
+
chartScrollLeft: bucketedChartScrollLeft
|
|
15950
|
+
};
|
|
15951
|
+
});
|
|
14940
15952
|
setPlanFactDateWindow((previous) => {
|
|
14941
15953
|
if (!isPlanFactMode || dateRange.length === 0 || nextViewportWidth <= 0) {
|
|
14942
15954
|
return previous;
|
|
@@ -15052,11 +16064,15 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15052
16064
|
return;
|
|
15053
16065
|
}
|
|
15054
16066
|
previewPositionStore.clear();
|
|
16067
|
+
activityPreviewStore.clear();
|
|
16068
|
+
activityBlockStore.clear();
|
|
16069
|
+
activityDragOwner.set(null);
|
|
16070
|
+
activityActivationStore.setActiveKey(null);
|
|
15055
16071
|
setCascadeOverrides((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
15056
16072
|
setPreviewTasksById((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
15057
16073
|
setDraggedTaskOverride((current) => current === null ? current : null);
|
|
15058
16074
|
setDragGuideLines((current) => current === null ? current : null);
|
|
15059
|
-
}, [controlledScheduleIdentity, previewPositionStore]);
|
|
16075
|
+
}, [controlledScheduleIdentity, previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore]);
|
|
15060
16076
|
(0, import_react19.useEffect)(() => {
|
|
15061
16077
|
const result = validateDependencies(tasks);
|
|
15062
16078
|
setValidationResult(result);
|
|
@@ -15067,7 +16083,11 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15067
16083
|
window.clearTimeout(clearSelectedTaskTimeoutRef.current);
|
|
15068
16084
|
}
|
|
15069
16085
|
previewPositionStore.clear();
|
|
15070
|
-
|
|
16086
|
+
activityPreviewStore.clear();
|
|
16087
|
+
activityBlockStore.clear();
|
|
16088
|
+
activityDragOwner.set(null);
|
|
16089
|
+
activityActivationStore.setActiveKey(null);
|
|
16090
|
+
}, [previewPositionStore, activityPreviewStore, activityBlockStore, activityDragOwner, activityActivationStore]);
|
|
15071
16091
|
const handleTaskChange = (0, import_react19.useCallback)((updatedTasks, durationEdit) => {
|
|
15072
16092
|
let updatedTask = updatedTasks[0];
|
|
15073
16093
|
if (!updatedTask) return;
|
|
@@ -15113,9 +16133,9 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15113
16133
|
return;
|
|
15114
16134
|
}
|
|
15115
16135
|
if (datesChanged && isTaskParent(updatedTask.id, tasks)) {
|
|
15116
|
-
const
|
|
15117
|
-
const deltaStart =
|
|
15118
|
-
const deltaEnd =
|
|
16136
|
+
const projectDayDelta2 = (from, to) => businessDays && isCustomWeekend ? getBusinessDayOffset(from, to, isCustomWeekend) : Math.round((to.getTime() - from.getTime()) / DAY_MS);
|
|
16137
|
+
const deltaStart = projectDayDelta2(origStart, newStart);
|
|
16138
|
+
const deltaEnd = projectDayDelta2(origEnd, newEnd);
|
|
15119
16139
|
const isUniformMove = deltaStart === deltaEnd && deltaStart !== 0;
|
|
15120
16140
|
if (!isUniformMove) {
|
|
15121
16141
|
const anchor = deltaStart === 0 ? "start" : deltaEnd === 0 ? "end" : "start";
|
|
@@ -15303,6 +16323,15 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15303
16323
|
}
|
|
15304
16324
|
return ids;
|
|
15305
16325
|
}, [draggedTaskOverride]);
|
|
16326
|
+
const horizontalWindow = (0, import_react19.useMemo)(() => {
|
|
16327
|
+
const width = scrollViewport.viewportWidth;
|
|
16328
|
+
if (width <= 0) return void 0;
|
|
16329
|
+
const left = scrollViewport.chartScrollLeft;
|
|
16330
|
+
return {
|
|
16331
|
+
startPx: Math.max(0, left - HORIZONTAL_WINDOW_OVERSCAN_PX),
|
|
16332
|
+
endPx: left + width + HORIZONTAL_WINDOW_OVERSCAN_PX
|
|
16333
|
+
};
|
|
16334
|
+
}, [scrollViewport.chartScrollLeft, scrollViewport.viewportWidth]);
|
|
15306
16335
|
const visibleTaskWindowIndices = (0, import_react19.useMemo)(() => {
|
|
15307
16336
|
const totalTasks = visibleTasks.length;
|
|
15308
16337
|
if (totalTasks === 0) {
|
|
@@ -15637,6 +16666,9 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15637
16666
|
const handlePanStart = (0, import_react19.useCallback)((e) => {
|
|
15638
16667
|
if (e.button !== 0) return;
|
|
15639
16668
|
const target = e.target;
|
|
16669
|
+
if (!target.closest(".gantt-tr-activityBar")) {
|
|
16670
|
+
activityActivationStore.setActiveKey(null);
|
|
16671
|
+
}
|
|
15640
16672
|
if (target.closest("[data-taskbar]")) return;
|
|
15641
16673
|
if (target.closest("input, button, textarea, [contenteditable]")) return;
|
|
15642
16674
|
if (target.closest(".gantt-tl-overlay")) return;
|
|
@@ -15657,7 +16689,7 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15657
16689
|
}
|
|
15658
16690
|
container.style.cursor = "grabbing";
|
|
15659
16691
|
e.preventDefault();
|
|
15660
|
-
}, []);
|
|
16692
|
+
}, [activityActivationStore]);
|
|
15661
16693
|
(0, import_react19.useEffect)(() => {
|
|
15662
16694
|
const flushPanMove = () => {
|
|
15663
16695
|
const pan = panStateRef.current;
|
|
@@ -15887,7 +16919,8 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15887
16919
|
dayWidth,
|
|
15888
16920
|
totalHeight: totalGridHeight,
|
|
15889
16921
|
viewMode,
|
|
15890
|
-
isCustomWeekend
|
|
16922
|
+
isCustomWeekend,
|
|
16923
|
+
horizontalWindow
|
|
15891
16924
|
}
|
|
15892
16925
|
),
|
|
15893
16926
|
todayInRange && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
@@ -15928,6 +16961,7 @@ function TaskGanttChartInner(props, ref) {
|
|
|
15928
16961
|
selectedDep: selectedChip,
|
|
15929
16962
|
businessDays,
|
|
15930
16963
|
weekendPredicate: isCustomWeekend,
|
|
16964
|
+
horizontalWindow,
|
|
15931
16965
|
onDependencyClick: handleDependencyLineClick,
|
|
15932
16966
|
criticalTaskIds: criticalPathMode ? criticalTaskIds : void 0
|
|
15933
16967
|
}
|
|
@@ -16034,6 +17068,7 @@ function TaskGanttChartInner(props, ref) {
|
|
|
16034
17068
|
setDragGuideLines((current) => current === null ? current : null);
|
|
16035
17069
|
setDraggedTaskOverride((current) => current === null ? current : null);
|
|
16036
17070
|
previewPositionStore.clear();
|
|
17071
|
+
activityPreviewStore.clear();
|
|
16037
17072
|
setCascadeOverrides((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
16038
17073
|
setPreviewTasksById((current) => current.size === 0 ? current : /* @__PURE__ */ new Map());
|
|
16039
17074
|
}
|
|
@@ -16044,6 +17079,16 @@ function TaskGanttChartInner(props, ref) {
|
|
|
16044
17079
|
disableConstraints: disableConstraints ?? false,
|
|
16045
17080
|
deferCascadePreview: normalizedTasks.length >= 1e3,
|
|
16046
17081
|
previewPositionStore,
|
|
17082
|
+
activityPreviewStore,
|
|
17083
|
+
activityBlockStore,
|
|
17084
|
+
activityDragOwner,
|
|
17085
|
+
activityActivationStore,
|
|
17086
|
+
activityClickToDrag,
|
|
17087
|
+
activityActivationMode,
|
|
17088
|
+
showActivityLinks,
|
|
17089
|
+
showActivityLag,
|
|
17090
|
+
activityTooltip,
|
|
17091
|
+
horizontalWindow,
|
|
16047
17092
|
onCascadeProgress: handleCascadeProgress,
|
|
16048
17093
|
onCascade: handleCascade,
|
|
16049
17094
|
onScheduleIntent,
|
|
@@ -16151,6 +17196,9 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16151
17196
|
};
|
|
16152
17197
|
// Annotate the CommonJS export names for ESM import in node:
|
|
16153
17198
|
0 && (module.exports = {
|
|
17199
|
+
ACTIVITY_LANES_PADDING,
|
|
17200
|
+
ACTIVITY_LANE_BAR_HEIGHT,
|
|
17201
|
+
ACTIVITY_LANE_STEP,
|
|
16154
17202
|
Button,
|
|
16155
17203
|
Calendar,
|
|
16156
17204
|
DAY_MS,
|
|
@@ -16172,6 +17220,11 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16172
17220
|
TaskRow,
|
|
16173
17221
|
TimeScaleHeader,
|
|
16174
17222
|
TodayIndicator,
|
|
17223
|
+
activityBounds,
|
|
17224
|
+
activityChainMinStartDelta,
|
|
17225
|
+
activityLanesExtraHeight,
|
|
17226
|
+
activitySpanMs,
|
|
17227
|
+
activityStartConstraint,
|
|
16175
17228
|
addBusinessDays,
|
|
16176
17229
|
alignToWorkingDay,
|
|
16177
17230
|
and,
|
|
@@ -16194,6 +17247,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16194
17247
|
cascadeByLinks,
|
|
16195
17248
|
clampDateRangeForIncomingFS,
|
|
16196
17249
|
clampTaskRangeForIncomingFS,
|
|
17250
|
+
computeActivityLanes,
|
|
16197
17251
|
computeCriticalPath,
|
|
16198
17252
|
computeLagFromDates,
|
|
16199
17253
|
computeParentDates,
|
|
@@ -16248,10 +17302,12 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16248
17302
|
normalizeUTCDate,
|
|
16249
17303
|
not,
|
|
16250
17304
|
or,
|
|
17305
|
+
packIntervals,
|
|
16251
17306
|
parseDateOnly,
|
|
16252
17307
|
parseUTCDate,
|
|
16253
17308
|
pixelsToDate,
|
|
16254
17309
|
progressInRange,
|
|
17310
|
+
pushActivityChain,
|
|
16255
17311
|
recalculateIncomingLags,
|
|
16256
17312
|
recalculateProjectSchedule,
|
|
16257
17313
|
recalculateTaskFromDependencies,
|
|
@@ -16261,6 +17317,8 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
16261
17317
|
resolveDateRangeFromPixels,
|
|
16262
17318
|
resolveTaskHorizontalGeometry,
|
|
16263
17319
|
scaleTaskSubtreeDuration,
|
|
17320
|
+
shiftActivityChain,
|
|
17321
|
+
shiftActivityDate,
|
|
16264
17322
|
shiftBusinessDayOffset,
|
|
16265
17323
|
subtractBusinessDays,
|
|
16266
17324
|
universalCascade,
|