gantt-lib 0.53.0 → 0.60.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/dist/index.d.mts +149 -154
- package/dist/index.d.ts +149 -154
- package/dist/index.js +648 -601
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +643 -601
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -33,32 +33,49 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
));
|
|
34
34
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
35
|
|
|
36
|
-
// src/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
36
|
+
// src/core/scheduling/dateMath.ts
|
|
37
|
+
function normalizeUTCDate(date) {
|
|
38
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
39
|
+
}
|
|
40
|
+
function parseDateOnly(date) {
|
|
41
|
+
const parsed = typeof date === "string" ? /* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(date);
|
|
42
|
+
return normalizeUTCDate(parsed);
|
|
43
|
+
}
|
|
44
|
+
function getBusinessDayOffset(fromDate, toDate, weekendPredicate) {
|
|
45
|
+
const from = normalizeUTCDate(fromDate);
|
|
46
|
+
const to = normalizeUTCDate(toDate);
|
|
47
|
+
if (from.getTime() === to.getTime()) {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
const step = to.getTime() > from.getTime() ? 1 : -1;
|
|
51
|
+
const current = new Date(from);
|
|
52
|
+
let offset = 0;
|
|
53
|
+
while (current.getTime() !== to.getTime()) {
|
|
54
|
+
current.setUTCDate(current.getUTCDate() + step);
|
|
55
|
+
if (!weekendPredicate(current)) {
|
|
56
|
+
offset += step;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return offset;
|
|
60
|
+
}
|
|
61
|
+
function shiftBusinessDayOffset(date, offset, weekendPredicate) {
|
|
62
|
+
const current = normalizeUTCDate(date);
|
|
63
|
+
if (offset === 0) {
|
|
64
|
+
return current;
|
|
65
|
+
}
|
|
66
|
+
const step = offset > 0 ? 1 : -1;
|
|
67
|
+
let remaining = Math.abs(offset);
|
|
68
|
+
while (remaining > 0) {
|
|
69
|
+
current.setUTCDate(current.getUTCDate() + step);
|
|
70
|
+
if (!weekendPredicate(current)) {
|
|
71
|
+
remaining--;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return current;
|
|
75
|
+
}
|
|
59
76
|
function getBusinessDaysCount(startDate, endDate, weekendPredicate) {
|
|
60
|
-
const start =
|
|
61
|
-
const end =
|
|
77
|
+
const start = typeof startDate === "string" ? /* @__PURE__ */ new Date(`${startDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(startDate);
|
|
78
|
+
const end = typeof endDate === "string" ? /* @__PURE__ */ new Date(`${endDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(endDate);
|
|
62
79
|
let count = 0;
|
|
63
80
|
const current = new Date(start);
|
|
64
81
|
while (current.getTime() <= end.getTime()) {
|
|
@@ -70,7 +87,7 @@ function getBusinessDaysCount(startDate, endDate, weekendPredicate) {
|
|
|
70
87
|
return Math.max(1, count);
|
|
71
88
|
}
|
|
72
89
|
function addBusinessDays(startDate, businessDays, weekendPredicate) {
|
|
73
|
-
const start =
|
|
90
|
+
const start = typeof startDate === "string" ? /* @__PURE__ */ new Date(`${startDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(startDate);
|
|
74
91
|
const current = new Date(start);
|
|
75
92
|
let targetDays = Math.max(1, businessDays);
|
|
76
93
|
let businessDaysCounted = 0;
|
|
@@ -82,10 +99,10 @@ function addBusinessDays(startDate, businessDays, weekendPredicate) {
|
|
|
82
99
|
current.setUTCDate(current.getUTCDate() + 1);
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
|
-
return current
|
|
102
|
+
return current;
|
|
86
103
|
}
|
|
87
104
|
function subtractBusinessDays(endDate, businessDays, weekendPredicate) {
|
|
88
|
-
const end =
|
|
105
|
+
const end = typeof endDate === "string" ? /* @__PURE__ */ new Date(`${endDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(endDate);
|
|
89
106
|
const current = new Date(end);
|
|
90
107
|
let targetDays = Math.max(1, businessDays);
|
|
91
108
|
let businessDaysCounted = 0;
|
|
@@ -97,12 +114,70 @@ function subtractBusinessDays(endDate, businessDays, weekendPredicate) {
|
|
|
97
114
|
current.setUTCDate(current.getUTCDate() - 1);
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
|
-
return current
|
|
117
|
+
return current;
|
|
118
|
+
}
|
|
119
|
+
function alignToWorkingDay(date, direction, weekendPredicate) {
|
|
120
|
+
const current = normalizeUTCDate(date);
|
|
121
|
+
while (weekendPredicate(current)) {
|
|
122
|
+
current.setUTCDate(current.getUTCDate() + direction);
|
|
123
|
+
}
|
|
124
|
+
return current;
|
|
125
|
+
}
|
|
126
|
+
function getTaskDuration(startDate, endDate, businessDays = false, weekendPredicate) {
|
|
127
|
+
const start = parseDateOnly(startDate);
|
|
128
|
+
const end = parseDateOnly(endDate);
|
|
129
|
+
if (businessDays && weekendPredicate) {
|
|
130
|
+
return getBusinessDaysCount(start, end, weekendPredicate);
|
|
131
|
+
}
|
|
132
|
+
return Math.max(1, Math.round((end.getTime() - start.getTime()) / DAY_MS) + 1);
|
|
133
|
+
}
|
|
134
|
+
var DAY_MS;
|
|
135
|
+
var init_dateMath = __esm({
|
|
136
|
+
"src/core/scheduling/dateMath.ts"() {
|
|
137
|
+
"use strict";
|
|
138
|
+
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// src/utils/dateUtils.ts
|
|
143
|
+
var dateUtils_exports = {};
|
|
144
|
+
__export(dateUtils_exports, {
|
|
145
|
+
addBusinessDays: () => addBusinessDays2,
|
|
146
|
+
createCustomDayPredicate: () => createCustomDayPredicate,
|
|
147
|
+
createDateKey: () => createDateKey,
|
|
148
|
+
formatDateLabel: () => formatDateLabel,
|
|
149
|
+
formatDateRangeLabel: () => formatDateRangeLabel,
|
|
150
|
+
getBusinessDaysCount: () => getBusinessDaysCount2,
|
|
151
|
+
getDayOffset: () => getDayOffset,
|
|
152
|
+
getMonthBlocks: () => getMonthBlocks,
|
|
153
|
+
getMonthDays: () => getMonthDays,
|
|
154
|
+
getMonthSpans: () => getMonthSpans,
|
|
155
|
+
getMultiMonthDays: () => getMultiMonthDays,
|
|
156
|
+
getWeekBlocks: () => getWeekBlocks,
|
|
157
|
+
getWeekSpans: () => getWeekSpans,
|
|
158
|
+
getYearSpans: () => getYearSpans,
|
|
159
|
+
isToday: () => isToday,
|
|
160
|
+
isWeekend: () => isWeekend,
|
|
161
|
+
normalizeTaskDates: () => normalizeTaskDates,
|
|
162
|
+
parseUTCDate: () => parseUTCDate,
|
|
163
|
+
subtractBusinessDays: () => subtractBusinessDays2
|
|
164
|
+
});
|
|
165
|
+
function getBusinessDaysCount2(startDate, endDate, weekendPredicate) {
|
|
166
|
+
return getBusinessDaysCount(startDate, endDate, weekendPredicate);
|
|
167
|
+
}
|
|
168
|
+
function addBusinessDays2(startDate, businessDays, weekendPredicate) {
|
|
169
|
+
const result = addBusinessDays(startDate, businessDays, weekendPredicate);
|
|
170
|
+
return result.toISOString().split("T")[0];
|
|
171
|
+
}
|
|
172
|
+
function subtractBusinessDays2(endDate, businessDays, weekendPredicate) {
|
|
173
|
+
const result = subtractBusinessDays(endDate, businessDays, weekendPredicate);
|
|
174
|
+
return result.toISOString().split("T")[0];
|
|
101
175
|
}
|
|
102
176
|
var parseUTCDate, getMonthDays, getDayOffset, isToday, isWeekend, createDateKey, createCustomDayPredicate, getMultiMonthDays, getMonthSpans, formatDateLabel, MONTH_ABBR, formatDateRangeLabel, getWeekBlocks, getWeekSpans, getMonthBlocks, getYearSpans, normalizeTaskDates;
|
|
103
177
|
var init_dateUtils = __esm({
|
|
104
178
|
"src/utils/dateUtils.ts"() {
|
|
105
179
|
"use strict";
|
|
180
|
+
init_dateMath();
|
|
106
181
|
parseUTCDate = (date) => {
|
|
107
182
|
if (typeof date === "string") {
|
|
108
183
|
const dateStr = date.includes("T") ? date : `${date}T00:00:00Z`;
|
|
@@ -431,6 +506,7 @@ var index_exports = {};
|
|
|
431
506
|
__export(index_exports, {
|
|
432
507
|
Button: () => Button,
|
|
433
508
|
Calendar: () => Calendar,
|
|
509
|
+
DAY_MS: () => DAY_MS,
|
|
434
510
|
DatePicker: () => DatePicker,
|
|
435
511
|
DragGuideLines: () => DragGuideLines_default,
|
|
436
512
|
GanttChart: () => GanttChart,
|
|
@@ -443,7 +519,7 @@ __export(index_exports, {
|
|
|
443
519
|
TaskRow: () => TaskRow_default,
|
|
444
520
|
TimeScaleHeader: () => TimeScaleHeader_default,
|
|
445
521
|
TodayIndicator: () => TodayIndicator_default,
|
|
446
|
-
addBusinessDays: () =>
|
|
522
|
+
addBusinessDays: () => addBusinessDays2,
|
|
447
523
|
alignToWorkingDay: () => alignToWorkingDay,
|
|
448
524
|
and: () => and,
|
|
449
525
|
areTasksHierarchicallyRelated: () => areTasksHierarchicallyRelated,
|
|
@@ -476,7 +552,8 @@ __export(index_exports, {
|
|
|
476
552
|
formatDateRangeLabel: () => formatDateRangeLabel,
|
|
477
553
|
getAllDependencyEdges: () => getAllDependencyEdges,
|
|
478
554
|
getAllDescendants: () => getAllDescendants,
|
|
479
|
-
|
|
555
|
+
getBusinessDayOffset: () => getBusinessDayOffset,
|
|
556
|
+
getBusinessDaysCount: () => getBusinessDaysCount2,
|
|
480
557
|
getChildren: () => getChildren,
|
|
481
558
|
getCursorForPosition: () => getCursorForPosition,
|
|
482
559
|
getDayOffset: () => getDayOffset,
|
|
@@ -503,15 +580,18 @@ __export(index_exports, {
|
|
|
503
580
|
normalizeDependencyLag: () => normalizeDependencyLag,
|
|
504
581
|
normalizeHierarchyTasks: () => normalizeHierarchyTasks,
|
|
505
582
|
normalizeTaskDates: () => normalizeTaskDates,
|
|
583
|
+
normalizeUTCDate: () => normalizeUTCDate,
|
|
506
584
|
not: () => not,
|
|
507
585
|
or: () => or,
|
|
586
|
+
parseDateOnly: () => parseDateOnly,
|
|
508
587
|
parseUTCDate: () => parseUTCDate,
|
|
509
588
|
pixelsToDate: () => pixelsToDate,
|
|
510
589
|
progressInRange: () => progressInRange,
|
|
511
590
|
recalculateIncomingLags: () => recalculateIncomingLags,
|
|
512
591
|
reflowTasksOnModeSwitch: () => reflowTasksOnModeSwitch,
|
|
513
592
|
removeDependenciesBetweenTasks: () => removeDependenciesBetweenTasks,
|
|
514
|
-
|
|
593
|
+
shiftBusinessDayOffset: () => shiftBusinessDayOffset,
|
|
594
|
+
subtractBusinessDays: () => subtractBusinessDays2,
|
|
515
595
|
universalCascade: () => universalCascade,
|
|
516
596
|
useTaskDrag: () => useTaskDrag,
|
|
517
597
|
validateDependencies: () => validateDependencies,
|
|
@@ -523,43 +603,11 @@ module.exports = __toCommonJS(index_exports);
|
|
|
523
603
|
var import_react13 = require("react");
|
|
524
604
|
init_dateUtils();
|
|
525
605
|
|
|
526
|
-
// src/
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
function getBusinessDayOffset(fromDate, toDate, weekendPredicate) {
|
|
532
|
-
const from = normalizeUTCDate(fromDate);
|
|
533
|
-
const to = normalizeUTCDate(toDate);
|
|
534
|
-
if (from.getTime() === to.getTime()) {
|
|
535
|
-
return 0;
|
|
536
|
-
}
|
|
537
|
-
const step = to.getTime() > from.getTime() ? 1 : -1;
|
|
538
|
-
const current = new Date(from);
|
|
539
|
-
let offset = 0;
|
|
540
|
-
while (current.getTime() !== to.getTime()) {
|
|
541
|
-
current.setUTCDate(current.getUTCDate() + step);
|
|
542
|
-
if (!weekendPredicate(current)) {
|
|
543
|
-
offset += step;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
return offset;
|
|
547
|
-
}
|
|
548
|
-
function shiftBusinessDayOffset(date, offset, weekendPredicate) {
|
|
549
|
-
const current = normalizeUTCDate(date);
|
|
550
|
-
if (offset === 0) {
|
|
551
|
-
return current;
|
|
552
|
-
}
|
|
553
|
-
const step = offset > 0 ? 1 : -1;
|
|
554
|
-
let remaining = Math.abs(offset);
|
|
555
|
-
while (remaining > 0) {
|
|
556
|
-
current.setUTCDate(current.getUTCDate() + step);
|
|
557
|
-
if (!weekendPredicate(current)) {
|
|
558
|
-
remaining--;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return current;
|
|
562
|
-
}
|
|
606
|
+
// src/core/scheduling/index.ts
|
|
607
|
+
init_dateMath();
|
|
608
|
+
|
|
609
|
+
// src/core/scheduling/dependencies.ts
|
|
610
|
+
init_dateMath();
|
|
563
611
|
function getDependencyLag(dep) {
|
|
564
612
|
return Number.isFinite(dep.lag) ? dep.lag : 0;
|
|
565
613
|
}
|
|
@@ -575,71 +623,268 @@ function normalizeDependencyLag(linkType, lag, predecessorStart, predecessorEnd,
|
|
|
575
623
|
);
|
|
576
624
|
return Math.max(-predecessorDuration, lag);
|
|
577
625
|
}
|
|
578
|
-
function
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
626
|
+
function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate) {
|
|
627
|
+
const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
|
|
628
|
+
const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
|
|
629
|
+
const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
|
|
630
|
+
const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
|
|
631
|
+
if (!businessDays || !weekendPredicate) {
|
|
632
|
+
switch (linkType) {
|
|
633
|
+
case "FS":
|
|
634
|
+
return normalizeDependencyLag(
|
|
635
|
+
linkType,
|
|
636
|
+
Math.round((sS - pE) / DAY_MS) - 1,
|
|
637
|
+
predStart,
|
|
638
|
+
predEnd,
|
|
639
|
+
businessDays,
|
|
640
|
+
weekendPredicate
|
|
641
|
+
);
|
|
642
|
+
case "SS":
|
|
643
|
+
return Math.round((sS - pS) / DAY_MS);
|
|
644
|
+
case "FF":
|
|
645
|
+
return Math.round((sE - pE) / DAY_MS);
|
|
646
|
+
case "SF":
|
|
647
|
+
return Math.round((sE - pS) / DAY_MS) + 1;
|
|
648
|
+
}
|
|
600
649
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
650
|
+
const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
|
|
651
|
+
const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
|
|
652
|
+
const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
|
|
653
|
+
switch (linkType) {
|
|
654
|
+
case "FS":
|
|
655
|
+
return normalizeDependencyLag(
|
|
656
|
+
linkType,
|
|
657
|
+
businessOffset - 1,
|
|
658
|
+
predStart,
|
|
659
|
+
predEnd,
|
|
660
|
+
businessDays,
|
|
661
|
+
weekendPredicate
|
|
662
|
+
);
|
|
663
|
+
case "SS":
|
|
664
|
+
return businessOffset;
|
|
665
|
+
case "FF":
|
|
666
|
+
return businessOffset;
|
|
667
|
+
case "SF":
|
|
668
|
+
return businessOffset + 1;
|
|
613
669
|
}
|
|
614
|
-
return {
|
|
615
|
-
start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS),
|
|
616
|
-
end: normalizedEnd
|
|
617
|
-
};
|
|
618
670
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
671
|
+
function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate) {
|
|
672
|
+
const normalizedLag = normalizeDependencyLag(
|
|
673
|
+
linkType,
|
|
674
|
+
lag,
|
|
675
|
+
predecessorStart,
|
|
676
|
+
predecessorEnd,
|
|
623
677
|
businessDays,
|
|
624
|
-
weekendPredicate
|
|
625
|
-
snapDirection
|
|
678
|
+
weekendPredicate
|
|
626
679
|
);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
638
|
-
if (!predecessor) {
|
|
639
|
-
continue;
|
|
680
|
+
if (!businessDays || !weekendPredicate) {
|
|
681
|
+
switch (linkType) {
|
|
682
|
+
case "FS":
|
|
683
|
+
return new Date(predecessorEnd.getTime() + (normalizedLag + 1) * DAY_MS);
|
|
684
|
+
case "SS":
|
|
685
|
+
return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
|
|
686
|
+
case "FF":
|
|
687
|
+
return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
|
|
688
|
+
case "SF":
|
|
689
|
+
return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
|
|
640
690
|
}
|
|
641
|
-
|
|
642
|
-
|
|
691
|
+
}
|
|
692
|
+
const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
|
|
693
|
+
let offset;
|
|
694
|
+
switch (linkType) {
|
|
695
|
+
case "FS":
|
|
696
|
+
offset = normalizedLag + 1;
|
|
697
|
+
break;
|
|
698
|
+
case "SS":
|
|
699
|
+
offset = normalizedLag;
|
|
700
|
+
break;
|
|
701
|
+
case "FF":
|
|
702
|
+
offset = normalizedLag;
|
|
703
|
+
break;
|
|
704
|
+
case "SF":
|
|
705
|
+
offset = normalizedLag - 1;
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/core/scheduling/cascade.ts
|
|
712
|
+
init_dateMath();
|
|
713
|
+
|
|
714
|
+
// src/core/scheduling/hierarchy.ts
|
|
715
|
+
function getChildren(parentId, tasks) {
|
|
716
|
+
return tasks.filter((t) => t.parentId === parentId);
|
|
717
|
+
}
|
|
718
|
+
function isTaskParent(taskId, tasks) {
|
|
719
|
+
return tasks.some((t) => t.parentId === taskId);
|
|
720
|
+
}
|
|
721
|
+
function computeParentDates(parentId, tasks) {
|
|
722
|
+
const children = getChildren(parentId, tasks);
|
|
723
|
+
if (children.length === 0) {
|
|
724
|
+
const parent = tasks.find((t) => t.id === parentId);
|
|
725
|
+
const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
|
|
726
|
+
const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
|
|
727
|
+
return { startDate: start, endDate: end };
|
|
728
|
+
}
|
|
729
|
+
const startDates = children.map((c) => new Date(c.startDate));
|
|
730
|
+
const endDates = children.map((c) => new Date(c.endDate));
|
|
731
|
+
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
732
|
+
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
733
|
+
return {
|
|
734
|
+
startDate: new Date(minTime),
|
|
735
|
+
endDate: new Date(maxTime)
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function computeParentProgress(parentId, tasks) {
|
|
739
|
+
const children = getChildren(parentId, tasks);
|
|
740
|
+
if (children.length === 0) {
|
|
741
|
+
return 0;
|
|
742
|
+
}
|
|
743
|
+
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
744
|
+
let totalWeight = 0;
|
|
745
|
+
let weightedSum = 0;
|
|
746
|
+
for (const child of children) {
|
|
747
|
+
const start = new Date(child.startDate).getTime();
|
|
748
|
+
const end = new Date(child.endDate).getTime();
|
|
749
|
+
const duration = (end - start + DAY_MS3) / DAY_MS3;
|
|
750
|
+
const progress = child.progress ?? 0;
|
|
751
|
+
totalWeight += duration;
|
|
752
|
+
weightedSum += duration * progress;
|
|
753
|
+
}
|
|
754
|
+
if (totalWeight === 0) {
|
|
755
|
+
return 0;
|
|
756
|
+
}
|
|
757
|
+
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
758
|
+
}
|
|
759
|
+
function getAllDescendants(parentId, tasks) {
|
|
760
|
+
const descendants = [];
|
|
761
|
+
const visited = /* @__PURE__ */ new Set();
|
|
762
|
+
function collectChildren(taskId) {
|
|
763
|
+
if (visited.has(taskId)) return;
|
|
764
|
+
visited.add(taskId);
|
|
765
|
+
const children = getChildren(taskId, tasks);
|
|
766
|
+
for (const child of children) {
|
|
767
|
+
descendants.push(child);
|
|
768
|
+
collectChildren(child.id);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
collectChildren(parentId);
|
|
772
|
+
return descendants;
|
|
773
|
+
}
|
|
774
|
+
function getAllDependencyEdges(tasks) {
|
|
775
|
+
const edges = [];
|
|
776
|
+
for (const task of tasks) {
|
|
777
|
+
if (task.dependencies) {
|
|
778
|
+
for (const dep of task.dependencies) {
|
|
779
|
+
edges.push({
|
|
780
|
+
predecessorId: dep.taskId,
|
|
781
|
+
successorId: task.id,
|
|
782
|
+
type: dep.type,
|
|
783
|
+
lag: dep.lag ?? 0
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return edges;
|
|
789
|
+
}
|
|
790
|
+
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
791
|
+
return tasks.map((task) => {
|
|
792
|
+
if (task.id === taskId1 || task.id === taskId2) {
|
|
793
|
+
if (!task.dependencies) return task;
|
|
794
|
+
const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
|
|
795
|
+
const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
|
|
796
|
+
if (filteredDependencies.length === task.dependencies.length) {
|
|
797
|
+
return task;
|
|
798
|
+
}
|
|
799
|
+
return {
|
|
800
|
+
...task,
|
|
801
|
+
dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
return task;
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
function findParentId(taskId, tasks) {
|
|
808
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
809
|
+
return task?.parentId;
|
|
810
|
+
}
|
|
811
|
+
function isAncestorTask(ancestorId, taskId, tasks) {
|
|
812
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
813
|
+
const visited = /* @__PURE__ */ new Set();
|
|
814
|
+
let current = taskById.get(taskId);
|
|
815
|
+
while (current?.parentId) {
|
|
816
|
+
if (current.parentId === ancestorId) {
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
if (visited.has(current.parentId)) {
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
visited.add(current.parentId);
|
|
823
|
+
current = taskById.get(current.parentId);
|
|
824
|
+
}
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
|
|
828
|
+
if (taskId1 === taskId2) {
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/core/scheduling/commands.ts
|
|
835
|
+
init_dateMath();
|
|
836
|
+
function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
837
|
+
const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
|
|
838
|
+
if (businessDays && weekendPredicate) {
|
|
839
|
+
return {
|
|
840
|
+
start: normalizedStart,
|
|
841
|
+
end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
845
|
+
return {
|
|
846
|
+
start: normalizedStart,
|
|
847
|
+
end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS3)
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
|
|
851
|
+
const normalizedEnd = businessDays && weekendPredicate ? alignToWorkingDay(endDate, snapDirection, weekendPredicate) : normalizeUTCDate(endDate);
|
|
852
|
+
if (businessDays && weekendPredicate) {
|
|
853
|
+
return {
|
|
854
|
+
start: parseDateOnly(subtractBusinessDays(normalizedEnd, duration, weekendPredicate)),
|
|
855
|
+
end: normalizedEnd
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
859
|
+
return {
|
|
860
|
+
start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS3),
|
|
861
|
+
end: normalizedEnd
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
865
|
+
return buildTaskRangeFromStart(
|
|
866
|
+
proposedStart,
|
|
867
|
+
getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
|
|
868
|
+
businessDays,
|
|
869
|
+
weekendPredicate,
|
|
870
|
+
snapDirection
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate) {
|
|
874
|
+
if (!task.dependencies?.length) {
|
|
875
|
+
return { start: proposedStart, end: proposedEnd };
|
|
876
|
+
}
|
|
877
|
+
let minAllowedStart = null;
|
|
878
|
+
for (const dep of task.dependencies) {
|
|
879
|
+
if (dep.type !== "FS") {
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
883
|
+
if (!predecessor) {
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const predecessorStart = parseDateOnly(predecessor.startDate);
|
|
887
|
+
const predecessorEnd = parseDateOnly(predecessor.endDate);
|
|
643
888
|
const predecessorDuration = getTaskDuration(
|
|
644
889
|
predecessorStart,
|
|
645
890
|
predecessorEnd,
|
|
@@ -668,193 +913,85 @@ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks,
|
|
|
668
913
|
weekendPredicate
|
|
669
914
|
);
|
|
670
915
|
}
|
|
671
|
-
function
|
|
672
|
-
|
|
673
|
-
return
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const graph = /* @__PURE__ */ new Map();
|
|
678
|
-
for (const task of tasks) {
|
|
679
|
-
const successors = [];
|
|
680
|
-
for (const otherTask of tasks) {
|
|
681
|
-
if (otherTask.dependencies) {
|
|
682
|
-
for (const dep of otherTask.dependencies) {
|
|
683
|
-
if (dep.taskId === task.id) {
|
|
684
|
-
successors.push(otherTask.id);
|
|
685
|
-
break;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
graph.set(task.id, successors);
|
|
691
|
-
}
|
|
692
|
-
return graph;
|
|
693
|
-
}
|
|
694
|
-
function detectCycles(tasks) {
|
|
695
|
-
const graph = buildAdjacencyList(tasks);
|
|
696
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
697
|
-
const visited = /* @__PURE__ */ new Set();
|
|
698
|
-
const path = [];
|
|
699
|
-
function dfs(taskId) {
|
|
700
|
-
if (visiting.has(taskId)) {
|
|
701
|
-
return true;
|
|
702
|
-
}
|
|
703
|
-
if (visited.has(taskId)) {
|
|
704
|
-
return false;
|
|
705
|
-
}
|
|
706
|
-
visiting.add(taskId);
|
|
707
|
-
path.push(taskId);
|
|
708
|
-
const successors = graph.get(taskId) || [];
|
|
709
|
-
for (const successor of successors) {
|
|
710
|
-
if (dfs(successor)) {
|
|
711
|
-
return true;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
visiting.delete(taskId);
|
|
715
|
-
path.pop();
|
|
716
|
-
visited.add(taskId);
|
|
717
|
-
return false;
|
|
718
|
-
}
|
|
719
|
-
for (const task of tasks) {
|
|
720
|
-
if (dfs(task.id)) {
|
|
721
|
-
return { hasCycle: true, cyclePath: [...path] };
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
return { hasCycle: false };
|
|
725
|
-
}
|
|
726
|
-
function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate) {
|
|
727
|
-
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
728
|
-
const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
|
|
729
|
-
const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
|
|
730
|
-
const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
|
|
731
|
-
const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
|
|
732
|
-
if (!businessDays || !weekendPredicate) {
|
|
733
|
-
switch (linkType) {
|
|
734
|
-
case "FS":
|
|
735
|
-
return normalizeDependencyLag(
|
|
736
|
-
linkType,
|
|
737
|
-
Math.round((sS - pE) / DAY_MS3) - 1,
|
|
738
|
-
predStart,
|
|
739
|
-
predEnd,
|
|
740
|
-
businessDays,
|
|
741
|
-
weekendPredicate
|
|
742
|
-
);
|
|
743
|
-
case "SS":
|
|
744
|
-
return Math.round((sS - pS) / DAY_MS3);
|
|
745
|
-
case "FF":
|
|
746
|
-
return Math.round((sE - pE) / DAY_MS3);
|
|
747
|
-
case "SF":
|
|
748
|
-
return Math.round((sE - pS) / DAY_MS3) + 1;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
|
|
752
|
-
const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
|
|
753
|
-
const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
|
|
754
|
-
switch (linkType) {
|
|
755
|
-
case "FS":
|
|
756
|
-
return normalizeDependencyLag(
|
|
757
|
-
linkType,
|
|
758
|
-
businessOffset - 1,
|
|
759
|
-
predStart,
|
|
760
|
-
predEnd,
|
|
761
|
-
businessDays,
|
|
762
|
-
weekendPredicate
|
|
763
|
-
);
|
|
764
|
-
case "SS":
|
|
765
|
-
return businessOffset;
|
|
766
|
-
case "FF":
|
|
767
|
-
return businessOffset;
|
|
768
|
-
case "SF":
|
|
769
|
-
return businessOffset + 1;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate) {
|
|
773
|
-
const normalizedLag = normalizeDependencyLag(
|
|
774
|
-
linkType,
|
|
775
|
-
lag,
|
|
776
|
-
predecessorStart,
|
|
777
|
-
predecessorEnd,
|
|
778
|
-
businessDays,
|
|
779
|
-
weekendPredicate
|
|
780
|
-
);
|
|
781
|
-
if (!businessDays || !weekendPredicate) {
|
|
782
|
-
switch (linkType) {
|
|
783
|
-
case "FS":
|
|
784
|
-
return new Date(predecessorEnd.getTime() + (normalizedLag + 1) * DAY_MS);
|
|
785
|
-
case "SS":
|
|
786
|
-
return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
|
|
787
|
-
case "FF":
|
|
788
|
-
return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
|
|
789
|
-
case "SF":
|
|
790
|
-
return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
|
|
916
|
+
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate) {
|
|
917
|
+
if (!task.dependencies) return [];
|
|
918
|
+
return task.dependencies.map((dep) => {
|
|
919
|
+
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
920
|
+
if (!predecessor) {
|
|
921
|
+
return { ...dep, lag: getDependencyLag(dep) };
|
|
791
922
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
case "SF":
|
|
806
|
-
offset = normalizedLag - 1;
|
|
807
|
-
break;
|
|
808
|
-
}
|
|
809
|
-
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
923
|
+
const predecessorStart = new Date(predecessor.startDate);
|
|
924
|
+
const predecessorEnd = new Date(predecessor.endDate);
|
|
925
|
+
const nextLag = computeLagFromDates(
|
|
926
|
+
dep.type,
|
|
927
|
+
predecessorStart,
|
|
928
|
+
predecessorEnd,
|
|
929
|
+
newStartDate,
|
|
930
|
+
newEndDate,
|
|
931
|
+
businessDays,
|
|
932
|
+
weekendPredicate
|
|
933
|
+
);
|
|
934
|
+
return { ...dep, lag: nextLag };
|
|
935
|
+
});
|
|
810
936
|
}
|
|
811
|
-
function
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
937
|
+
function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
|
|
938
|
+
const dayOffset = Math.round(left / dayWidth);
|
|
939
|
+
const rawStartDate = new Date(Date.UTC(
|
|
940
|
+
monthStart.getUTCFullYear(),
|
|
941
|
+
monthStart.getUTCMonth(),
|
|
942
|
+
monthStart.getUTCDate() + dayOffset
|
|
943
|
+
));
|
|
944
|
+
const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
|
|
945
|
+
const rawEndDate = new Date(Date.UTC(
|
|
946
|
+
monthStart.getUTCFullYear(),
|
|
947
|
+
monthStart.getUTCMonth(),
|
|
948
|
+
monthStart.getUTCDate() + rawEndOffset
|
|
949
|
+
));
|
|
950
|
+
if (!(businessDays && weekendPredicate)) {
|
|
951
|
+
return { start: rawStartDate, end: rawEndDate };
|
|
827
952
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
relatedTaskIds: [dep.taskId, task.id]
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
}
|
|
953
|
+
if (mode === "move") {
|
|
954
|
+
const originalStart2 = new Date(task.startDate);
|
|
955
|
+
const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
|
|
956
|
+
return moveTaskRange(
|
|
957
|
+
task.startDate,
|
|
958
|
+
task.endDate,
|
|
959
|
+
rawStartDate,
|
|
960
|
+
true,
|
|
961
|
+
weekendPredicate,
|
|
962
|
+
snapDirection2
|
|
963
|
+
);
|
|
843
964
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
});
|
|
965
|
+
if (mode === "resize-right") {
|
|
966
|
+
const fixedStart = new Date(task.startDate);
|
|
967
|
+
const originalEnd = new Date(task.endDate);
|
|
968
|
+
const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
|
|
969
|
+
const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
|
|
970
|
+
const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
|
|
971
|
+
return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
|
|
852
972
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
973
|
+
const fixedEnd = new Date(task.endDate);
|
|
974
|
+
const originalStart = new Date(task.startDate);
|
|
975
|
+
const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
|
|
976
|
+
const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
|
|
977
|
+
const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
|
|
978
|
+
return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
|
|
979
|
+
}
|
|
980
|
+
function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
|
|
981
|
+
if (mode === "resize-right") {
|
|
982
|
+
return range;
|
|
983
|
+
}
|
|
984
|
+
return clampTaskRangeForIncomingFS(
|
|
985
|
+
task,
|
|
986
|
+
range.start,
|
|
987
|
+
range.end,
|
|
988
|
+
allTasks,
|
|
989
|
+
businessDays,
|
|
990
|
+
weekendPredicate
|
|
991
|
+
);
|
|
857
992
|
}
|
|
993
|
+
|
|
994
|
+
// src/core/scheduling/cascade.ts
|
|
858
995
|
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
859
996
|
const successorMap = /* @__PURE__ */ new Map();
|
|
860
997
|
for (const task of allTasks) {
|
|
@@ -918,225 +1055,85 @@ function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascad
|
|
|
918
1055
|
visited.add(child.id);
|
|
919
1056
|
updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
|
|
920
1057
|
result.push({
|
|
921
|
-
...child,
|
|
922
|
-
startDate: newChildStart.toISOString().split("T")[0],
|
|
923
|
-
endDate: newChildEnd.toISOString().split("T")[0]
|
|
924
|
-
});
|
|
925
|
-
queue.push(child.id);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
for (const task of allTasks) {
|
|
929
|
-
if (visited.has(task.id) || !task.dependencies || task.locked) continue;
|
|
930
|
-
for (const dep of task.dependencies) {
|
|
931
|
-
if (dep.taskId !== currentId) continue;
|
|
932
|
-
const orig = taskById.get(task.id);
|
|
933
|
-
const origStart = new Date(orig.startDate);
|
|
934
|
-
const origEnd = new Date(orig.endDate);
|
|
935
|
-
const duration = getTaskDuration(origStart, origEnd);
|
|
936
|
-
const constraintDate = calculateSuccessorDate(predStart, predEnd, dep.type, getDependencyLag(dep));
|
|
937
|
-
let newSuccStart;
|
|
938
|
-
let newSuccEnd;
|
|
939
|
-
if (dep.type === "FS" || dep.type === "SS") {
|
|
940
|
-
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
|
|
941
|
-
} else {
|
|
942
|
-
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
|
|
943
|
-
}
|
|
944
|
-
visited.add(task.id);
|
|
945
|
-
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
946
|
-
result.push({
|
|
947
|
-
...task,
|
|
948
|
-
startDate: newSuccStart.toISOString().split("T")[0],
|
|
949
|
-
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
950
|
-
});
|
|
951
|
-
queue.push(task.id);
|
|
952
|
-
break;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return result;
|
|
957
|
-
}
|
|
958
|
-
function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
|
|
959
|
-
const allTypesSuccessorMap = /* @__PURE__ */ new Map();
|
|
960
|
-
for (const task of allTasks) {
|
|
961
|
-
allTypesSuccessorMap.set(task.id, []);
|
|
962
|
-
}
|
|
963
|
-
for (const task of allTasks) {
|
|
964
|
-
if (!task.dependencies) continue;
|
|
965
|
-
for (const dep of task.dependencies) {
|
|
966
|
-
const list = allTypesSuccessorMap.get(dep.taskId) ?? [];
|
|
967
|
-
list.push(task);
|
|
968
|
-
allTypesSuccessorMap.set(dep.taskId, list);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
const directChildren = getChildren(changedTaskId, allTasks);
|
|
972
|
-
const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
|
|
973
|
-
const initialChain = [...directChildren, ...directSuccessors].filter(
|
|
974
|
-
(task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
|
|
975
|
-
);
|
|
976
|
-
const chain = [...initialChain];
|
|
977
|
-
const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
|
|
978
|
-
const queue = [...initialChain];
|
|
979
|
-
while (queue.length > 0) {
|
|
980
|
-
const current = queue.shift();
|
|
981
|
-
const children = getChildren(current.id, allTasks);
|
|
982
|
-
for (const child of children) {
|
|
983
|
-
if (!visited.has(child.id)) {
|
|
984
|
-
visited.add(child.id);
|
|
985
|
-
chain.push(child);
|
|
986
|
-
queue.push(child);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
const successors = allTypesSuccessorMap.get(current.id) ?? [];
|
|
990
|
-
for (const successor of successors) {
|
|
991
|
-
if (!visited.has(successor.id)) {
|
|
992
|
-
visited.add(successor.id);
|
|
993
|
-
chain.push(successor);
|
|
994
|
-
queue.push(successor);
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
return chain;
|
|
999
|
-
}
|
|
1000
|
-
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate) {
|
|
1001
|
-
if (!task.dependencies) return [];
|
|
1002
|
-
return task.dependencies.map((dep) => {
|
|
1003
|
-
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
1004
|
-
if (!predecessor) {
|
|
1005
|
-
return { ...dep, lag: getDependencyLag(dep) };
|
|
1006
|
-
}
|
|
1007
|
-
const predecessorStart = new Date(predecessor.startDate);
|
|
1008
|
-
const predecessorEnd = new Date(predecessor.endDate);
|
|
1009
|
-
const nextLag = computeLagFromDates(
|
|
1010
|
-
dep.type,
|
|
1011
|
-
predecessorStart,
|
|
1012
|
-
predecessorEnd,
|
|
1013
|
-
newStartDate,
|
|
1014
|
-
newEndDate,
|
|
1015
|
-
businessDays,
|
|
1016
|
-
weekendPredicate
|
|
1017
|
-
);
|
|
1018
|
-
return { ...dep, lag: nextLag };
|
|
1019
|
-
});
|
|
1020
|
-
}
|
|
1021
|
-
function getAllDependencyEdges(tasks) {
|
|
1022
|
-
const edges = [];
|
|
1023
|
-
for (const task of tasks) {
|
|
1024
|
-
if (task.dependencies) {
|
|
1025
|
-
for (const dep of task.dependencies) {
|
|
1026
|
-
edges.push({
|
|
1027
|
-
predecessorId: dep.taskId,
|
|
1028
|
-
successorId: task.id,
|
|
1029
|
-
type: dep.type,
|
|
1030
|
-
lag: getDependencyLag(dep)
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
return edges;
|
|
1036
|
-
}
|
|
1037
|
-
function getChildren(parentId, tasks) {
|
|
1038
|
-
return tasks.filter((t) => t.parentId === parentId);
|
|
1039
|
-
}
|
|
1040
|
-
function isTaskParent(taskId, tasks) {
|
|
1041
|
-
return tasks.some((t) => t.parentId === taskId);
|
|
1042
|
-
}
|
|
1043
|
-
function computeParentDates(parentId, tasks) {
|
|
1044
|
-
const children = getChildren(parentId, tasks);
|
|
1045
|
-
if (children.length === 0) {
|
|
1046
|
-
const parent = tasks.find((t) => t.id === parentId);
|
|
1047
|
-
const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
|
|
1048
|
-
const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
|
|
1049
|
-
return { startDate: start, endDate: end };
|
|
1050
|
-
}
|
|
1051
|
-
const startDates = children.map((c) => new Date(c.startDate));
|
|
1052
|
-
const endDates = children.map((c) => new Date(c.endDate));
|
|
1053
|
-
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
1054
|
-
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
1055
|
-
return {
|
|
1056
|
-
startDate: new Date(minTime),
|
|
1057
|
-
endDate: new Date(maxTime)
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
function computeParentProgress(parentId, tasks) {
|
|
1061
|
-
const children = getChildren(parentId, tasks);
|
|
1062
|
-
if (children.length === 0) {
|
|
1063
|
-
return 0;
|
|
1064
|
-
}
|
|
1065
|
-
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
1066
|
-
let totalWeight = 0;
|
|
1067
|
-
let weightedSum = 0;
|
|
1068
|
-
for (const child of children) {
|
|
1069
|
-
const start = new Date(child.startDate).getTime();
|
|
1070
|
-
const end = new Date(child.endDate).getTime();
|
|
1071
|
-
const duration = (end - start + DAY_MS3) / DAY_MS3;
|
|
1072
|
-
const progress = child.progress ?? 0;
|
|
1073
|
-
totalWeight += duration;
|
|
1074
|
-
weightedSum += duration * progress;
|
|
1075
|
-
}
|
|
1076
|
-
if (totalWeight === 0) {
|
|
1077
|
-
return 0;
|
|
1078
|
-
}
|
|
1079
|
-
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
1080
|
-
}
|
|
1081
|
-
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
1082
|
-
return tasks.map((task) => {
|
|
1083
|
-
if (task.id === taskId1 || task.id === taskId2) {
|
|
1084
|
-
if (!task.dependencies) return task;
|
|
1085
|
-
const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
|
|
1086
|
-
const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
|
|
1087
|
-
if (filteredDependencies.length === task.dependencies.length) {
|
|
1088
|
-
return task;
|
|
1089
|
-
}
|
|
1090
|
-
return {
|
|
1091
|
-
...task,
|
|
1092
|
-
dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
return task;
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
function findParentId(taskId, tasks) {
|
|
1099
|
-
const task = tasks.find((t) => t.id === taskId);
|
|
1100
|
-
return task?.parentId;
|
|
1101
|
-
}
|
|
1102
|
-
function isAncestorTask(ancestorId, taskId, tasks) {
|
|
1103
|
-
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
1104
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1105
|
-
let current = taskById.get(taskId);
|
|
1106
|
-
while (current?.parentId) {
|
|
1107
|
-
if (current.parentId === ancestorId) {
|
|
1108
|
-
return true;
|
|
1058
|
+
...child,
|
|
1059
|
+
startDate: newChildStart.toISOString().split("T")[0],
|
|
1060
|
+
endDate: newChildEnd.toISOString().split("T")[0]
|
|
1061
|
+
});
|
|
1062
|
+
queue.push(child.id);
|
|
1063
|
+
}
|
|
1109
1064
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1065
|
+
for (const task of allTasks) {
|
|
1066
|
+
if (visited.has(task.id) || !task.dependencies || task.locked) continue;
|
|
1067
|
+
for (const dep of task.dependencies) {
|
|
1068
|
+
if (dep.taskId !== currentId) continue;
|
|
1069
|
+
const orig = taskById.get(task.id);
|
|
1070
|
+
const origStart = new Date(orig.startDate);
|
|
1071
|
+
const origEnd = new Date(orig.endDate);
|
|
1072
|
+
const duration = getTaskDuration(origStart, origEnd);
|
|
1073
|
+
const constraintDate = calculateSuccessorDate(predStart, predEnd, dep.type, getDependencyLag(dep));
|
|
1074
|
+
let newSuccStart;
|
|
1075
|
+
let newSuccEnd;
|
|
1076
|
+
if (dep.type === "FS" || dep.type === "SS") {
|
|
1077
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
|
|
1078
|
+
} else {
|
|
1079
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
|
|
1080
|
+
}
|
|
1081
|
+
visited.add(task.id);
|
|
1082
|
+
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
1083
|
+
result.push({
|
|
1084
|
+
...task,
|
|
1085
|
+
startDate: newSuccStart.toISOString().split("T")[0],
|
|
1086
|
+
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
1087
|
+
});
|
|
1088
|
+
queue.push(task.id);
|
|
1089
|
+
break;
|
|
1090
|
+
}
|
|
1112
1091
|
}
|
|
1113
|
-
visited.add(current.parentId);
|
|
1114
|
-
current = taskById.get(current.parentId);
|
|
1115
1092
|
}
|
|
1116
|
-
return
|
|
1093
|
+
return result;
|
|
1117
1094
|
}
|
|
1118
|
-
function
|
|
1119
|
-
|
|
1120
|
-
|
|
1095
|
+
function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
|
|
1096
|
+
const allTypesSuccessorMap = /* @__PURE__ */ new Map();
|
|
1097
|
+
for (const task of allTasks) {
|
|
1098
|
+
allTypesSuccessorMap.set(task.id, []);
|
|
1121
1099
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1100
|
+
for (const task of allTasks) {
|
|
1101
|
+
if (!task.dependencies) continue;
|
|
1102
|
+
for (const dep of task.dependencies) {
|
|
1103
|
+
const list = allTypesSuccessorMap.get(dep.taskId) ?? [];
|
|
1104
|
+
list.push(task);
|
|
1105
|
+
allTypesSuccessorMap.set(dep.taskId, list);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
const directChildren = getChildren(changedTaskId, allTasks);
|
|
1109
|
+
const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
|
|
1110
|
+
const initialChain = [...directChildren, ...directSuccessors].filter(
|
|
1111
|
+
(task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
|
|
1112
|
+
);
|
|
1113
|
+
const chain = [...initialChain];
|
|
1114
|
+
const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
|
|
1115
|
+
const queue = [...initialChain];
|
|
1116
|
+
while (queue.length > 0) {
|
|
1117
|
+
const current = queue.shift();
|
|
1118
|
+
const children = getChildren(current.id, allTasks);
|
|
1131
1119
|
for (const child of children) {
|
|
1132
|
-
|
|
1133
|
-
|
|
1120
|
+
if (!visited.has(child.id)) {
|
|
1121
|
+
visited.add(child.id);
|
|
1122
|
+
chain.push(child);
|
|
1123
|
+
queue.push(child);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const successors = allTypesSuccessorMap.get(current.id) ?? [];
|
|
1127
|
+
for (const successor of successors) {
|
|
1128
|
+
if (!visited.has(successor.id)) {
|
|
1129
|
+
visited.add(successor.id);
|
|
1130
|
+
chain.push(successor);
|
|
1131
|
+
queue.push(successor);
|
|
1132
|
+
}
|
|
1134
1133
|
}
|
|
1135
1134
|
}
|
|
1136
|
-
|
|
1137
|
-
return descendants;
|
|
1135
|
+
return chain;
|
|
1138
1136
|
}
|
|
1139
|
-
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1140
1137
|
function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate) {
|
|
1141
1138
|
const taskById = new Map(allTasks.map((t) => [t.id, t]));
|
|
1142
1139
|
const updatedDates = /* @__PURE__ */ new Map();
|
|
@@ -1309,6 +1306,105 @@ function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate)
|
|
|
1309
1306
|
return tasks;
|
|
1310
1307
|
}
|
|
1311
1308
|
|
|
1309
|
+
// src/core/scheduling/validation.ts
|
|
1310
|
+
function buildAdjacencyList(tasks) {
|
|
1311
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1312
|
+
for (const task of tasks) {
|
|
1313
|
+
const successors = [];
|
|
1314
|
+
for (const otherTask of tasks) {
|
|
1315
|
+
if (otherTask.dependencies) {
|
|
1316
|
+
for (const dep of otherTask.dependencies) {
|
|
1317
|
+
if (dep.taskId === task.id) {
|
|
1318
|
+
successors.push(otherTask.id);
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
graph.set(task.id, successors);
|
|
1325
|
+
}
|
|
1326
|
+
return graph;
|
|
1327
|
+
}
|
|
1328
|
+
function detectCycles(tasks) {
|
|
1329
|
+
const graph = buildAdjacencyList(tasks);
|
|
1330
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1331
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1332
|
+
const path = [];
|
|
1333
|
+
function dfs(taskId) {
|
|
1334
|
+
if (visiting.has(taskId)) {
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
if (visited.has(taskId)) {
|
|
1338
|
+
return false;
|
|
1339
|
+
}
|
|
1340
|
+
visiting.add(taskId);
|
|
1341
|
+
path.push(taskId);
|
|
1342
|
+
const successors = graph.get(taskId) || [];
|
|
1343
|
+
for (const successor of successors) {
|
|
1344
|
+
if (dfs(successor)) {
|
|
1345
|
+
return true;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
visiting.delete(taskId);
|
|
1349
|
+
path.pop();
|
|
1350
|
+
visited.add(taskId);
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
for (const task of tasks) {
|
|
1354
|
+
if (dfs(task.id)) {
|
|
1355
|
+
return { hasCycle: true, cyclePath: [...path] };
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
return { hasCycle: false };
|
|
1359
|
+
}
|
|
1360
|
+
function validateDependencies(tasks) {
|
|
1361
|
+
const errors = [];
|
|
1362
|
+
const taskIds = new Set(tasks.map((t) => t.id));
|
|
1363
|
+
for (const task of tasks) {
|
|
1364
|
+
if (task.dependencies) {
|
|
1365
|
+
for (const dep of task.dependencies) {
|
|
1366
|
+
if (!taskIds.has(dep.taskId)) {
|
|
1367
|
+
errors.push({
|
|
1368
|
+
type: "missing-task",
|
|
1369
|
+
taskId: task.id,
|
|
1370
|
+
message: `Dependency references non-existent task: ${dep.taskId}`,
|
|
1371
|
+
relatedTaskIds: [dep.taskId]
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
for (const task of tasks) {
|
|
1378
|
+
if (!task.dependencies) continue;
|
|
1379
|
+
for (const dep of task.dependencies) {
|
|
1380
|
+
if (!taskIds.has(dep.taskId)) {
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
if (areTasksHierarchicallyRelated(task.id, dep.taskId, tasks)) {
|
|
1384
|
+
errors.push({
|
|
1385
|
+
type: "constraint",
|
|
1386
|
+
taskId: task.id,
|
|
1387
|
+
message: `Dependencies between parent and child tasks are not allowed: ${dep.taskId} -> ${task.id}`,
|
|
1388
|
+
relatedTaskIds: [dep.taskId, task.id]
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
const cycleResult = detectCycles(tasks);
|
|
1394
|
+
if (cycleResult.hasCycle && cycleResult.cyclePath) {
|
|
1395
|
+
errors.push({
|
|
1396
|
+
type: "cycle",
|
|
1397
|
+
taskId: cycleResult.cyclePath[0],
|
|
1398
|
+
message: "Circular dependency detected",
|
|
1399
|
+
relatedTaskIds: cycleResult.cyclePath
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
return {
|
|
1403
|
+
isValid: errors.length === 0,
|
|
1404
|
+
errors
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1312
1408
|
// src/utils/hierarchyOrder.ts
|
|
1313
1409
|
init_dateUtils();
|
|
1314
1410
|
function flattenHierarchy(tasks) {
|
|
@@ -1790,7 +1886,6 @@ var isTaskExpired = (task, referenceDate = /* @__PURE__ */ new Date()) => {
|
|
|
1790
1886
|
|
|
1791
1887
|
// src/hooks/useTaskDrag.ts
|
|
1792
1888
|
var import_react2 = require("react");
|
|
1793
|
-
init_dateUtils();
|
|
1794
1889
|
var globalActiveDrag = null;
|
|
1795
1890
|
var globalRafId = null;
|
|
1796
1891
|
function getDayOffsetFromMonthStart(date, monthStart) {
|
|
@@ -1798,62 +1893,6 @@ function getDayOffsetFromMonthStart(date, monthStart) {
|
|
|
1798
1893
|
(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) - Date.UTC(monthStart.getUTCFullYear(), monthStart.getUTCMonth(), monthStart.getUTCDate())) / (24 * 60 * 60 * 1e3)
|
|
1799
1894
|
);
|
|
1800
1895
|
}
|
|
1801
|
-
function resolveDraggedRange(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
|
|
1802
|
-
const dayOffset = Math.round(left / dayWidth);
|
|
1803
|
-
const rawStartDate = new Date(Date.UTC(
|
|
1804
|
-
monthStart.getUTCFullYear(),
|
|
1805
|
-
monthStart.getUTCMonth(),
|
|
1806
|
-
monthStart.getUTCDate() + dayOffset
|
|
1807
|
-
));
|
|
1808
|
-
const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
|
|
1809
|
-
const rawEndDate = new Date(Date.UTC(
|
|
1810
|
-
monthStart.getUTCFullYear(),
|
|
1811
|
-
monthStart.getUTCMonth(),
|
|
1812
|
-
monthStart.getUTCDate() + rawEndOffset
|
|
1813
|
-
));
|
|
1814
|
-
if (!(businessDays && weekendPredicate)) {
|
|
1815
|
-
return { start: rawStartDate, end: rawEndDate };
|
|
1816
|
-
}
|
|
1817
|
-
if (mode === "move") {
|
|
1818
|
-
const originalStart2 = new Date(task.startDate);
|
|
1819
|
-
const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
|
|
1820
|
-
return moveTaskRange(
|
|
1821
|
-
task.startDate,
|
|
1822
|
-
task.endDate,
|
|
1823
|
-
rawStartDate,
|
|
1824
|
-
true,
|
|
1825
|
-
weekendPredicate,
|
|
1826
|
-
snapDirection2
|
|
1827
|
-
);
|
|
1828
|
-
}
|
|
1829
|
-
if (mode === "resize-right") {
|
|
1830
|
-
const fixedStart = new Date(task.startDate);
|
|
1831
|
-
const originalEnd = new Date(task.endDate);
|
|
1832
|
-
const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
|
|
1833
|
-
const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
|
|
1834
|
-
const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
|
|
1835
|
-
return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
|
|
1836
|
-
}
|
|
1837
|
-
const fixedEnd = new Date(task.endDate);
|
|
1838
|
-
const originalStart = new Date(task.startDate);
|
|
1839
|
-
const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
|
|
1840
|
-
const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
|
|
1841
|
-
const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
|
|
1842
|
-
return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
|
|
1843
|
-
}
|
|
1844
|
-
function clampDraggedRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
|
|
1845
|
-
if (mode === "resize-right") {
|
|
1846
|
-
return range;
|
|
1847
|
-
}
|
|
1848
|
-
return clampTaskRangeForIncomingFS(
|
|
1849
|
-
task,
|
|
1850
|
-
range.start,
|
|
1851
|
-
range.end,
|
|
1852
|
-
allTasks,
|
|
1853
|
-
businessDays,
|
|
1854
|
-
weekendPredicate
|
|
1855
|
-
);
|
|
1856
|
-
}
|
|
1857
1896
|
function completeDrag() {
|
|
1858
1897
|
if (globalRafId !== null) {
|
|
1859
1898
|
cancelAnimationFrame(globalRafId);
|
|
@@ -1911,9 +1950,9 @@ function handleGlobalMouseMove(e) {
|
|
|
1911
1950
|
}
|
|
1912
1951
|
const draggedTask = allTasks.find((t) => t.id === activeDrag.taskId);
|
|
1913
1952
|
if (activeDrag.businessDays && activeDrag.weekendPredicate && draggedTask) {
|
|
1914
|
-
const previewRange =
|
|
1953
|
+
const previewRange = clampDateRangeForIncomingFS(
|
|
1915
1954
|
draggedTask,
|
|
1916
|
-
|
|
1955
|
+
resolveDateRangeFromPixels(
|
|
1917
1956
|
mode,
|
|
1918
1957
|
newLeft,
|
|
1919
1958
|
newWidth,
|
|
@@ -1933,9 +1972,9 @@ function handleGlobalMouseMove(e) {
|
|
|
1933
1972
|
newLeft = Math.round(alignedStartDay * dayWidth);
|
|
1934
1973
|
newWidth = Math.round((alignedEndDay - alignedStartDay + 1) * dayWidth);
|
|
1935
1974
|
} else if (draggedTask) {
|
|
1936
|
-
const previewRange =
|
|
1975
|
+
const previewRange = clampDateRangeForIncomingFS(
|
|
1937
1976
|
draggedTask,
|
|
1938
|
-
|
|
1977
|
+
resolveDateRangeFromPixels(
|
|
1939
1978
|
mode,
|
|
1940
1979
|
newLeft,
|
|
1941
1980
|
newWidth,
|
|
@@ -1954,9 +1993,9 @@ function handleGlobalMouseMove(e) {
|
|
|
1954
1993
|
if (!activeDrag.disableConstraints && activeDrag.onCascadeProgress) {
|
|
1955
1994
|
const { dayWidth: dayWidth2, monthStart: mStart, taskId: dragId } = activeDrag;
|
|
1956
1995
|
const originalDraggedTask = draggedTask ?? allTasks.find((t) => t.id === dragId);
|
|
1957
|
-
const previewRange = originalDraggedTask ?
|
|
1996
|
+
const previewRange = originalDraggedTask ? clampDateRangeForIncomingFS(
|
|
1958
1997
|
originalDraggedTask,
|
|
1959
|
-
|
|
1998
|
+
resolveDateRangeFromPixels(
|
|
1960
1999
|
mode,
|
|
1961
2000
|
newLeft,
|
|
1962
2001
|
newWidth,
|
|
@@ -2138,9 +2177,9 @@ var useTaskDrag = (options) => {
|
|
|
2138
2177
|
const wasOwner = isOwnerRef.current;
|
|
2139
2178
|
isOwnerRef.current = false;
|
|
2140
2179
|
const currentTask = allTasks.find((t) => t.id === taskId);
|
|
2141
|
-
const finalRange = currentTask ?
|
|
2180
|
+
const finalRange = currentTask ? clampDateRangeForIncomingFS(
|
|
2142
2181
|
currentTask,
|
|
2143
|
-
|
|
2182
|
+
resolveDateRangeFromPixels(
|
|
2144
2183
|
finalMode,
|
|
2145
2184
|
finalLeft,
|
|
2146
2185
|
finalWidth,
|
|
@@ -3375,6 +3414,20 @@ var DatePicker = ({
|
|
|
3375
3414
|
},
|
|
3376
3415
|
[selectedDate, updateFromDate, businessDays, isWeekend3]
|
|
3377
3416
|
);
|
|
3417
|
+
const handleTriggerKeyDown = (0, import_react9.useCallback)((e) => {
|
|
3418
|
+
if (disabled) return;
|
|
3419
|
+
if (e.key === "ArrowUp") {
|
|
3420
|
+
e.preventDefault();
|
|
3421
|
+
e.stopPropagation();
|
|
3422
|
+
handleDayShift(1);
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
if (e.key === "ArrowDown") {
|
|
3426
|
+
e.preventDefault();
|
|
3427
|
+
e.stopPropagation();
|
|
3428
|
+
handleDayShift(-1);
|
|
3429
|
+
}
|
|
3430
|
+
}, [disabled, handleDayShift]);
|
|
3378
3431
|
const handleKeyDown = (0, import_react9.useCallback)((e) => {
|
|
3379
3432
|
if (!dateInputRef.current) return;
|
|
3380
3433
|
const { value: inputVal } = dateInputRef.current;
|
|
@@ -3482,6 +3535,7 @@ var DatePicker = ({
|
|
|
3482
3535
|
type: "button",
|
|
3483
3536
|
className: `gantt-datepicker-trigger${className ? ` ${className}` : ""}`,
|
|
3484
3537
|
disabled,
|
|
3538
|
+
onKeyDown: handleTriggerKeyDown,
|
|
3485
3539
|
onClick: (e) => {
|
|
3486
3540
|
e.stopPropagation();
|
|
3487
3541
|
},
|
|
@@ -3868,7 +3922,7 @@ var DepChip = ({
|
|
|
3868
3922
|
newStart = constraintDate;
|
|
3869
3923
|
if (businessDays) {
|
|
3870
3924
|
const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
|
|
3871
|
-
newEnd =
|
|
3925
|
+
newEnd = addBusinessDays(constraintDate, businessDuration, weekendPredicate);
|
|
3872
3926
|
} else {
|
|
3873
3927
|
newEnd = new Date(constraintDate.getTime() + durationMs);
|
|
3874
3928
|
}
|
|
@@ -3876,7 +3930,7 @@ var DepChip = ({
|
|
|
3876
3930
|
newEnd = constraintDate;
|
|
3877
3931
|
if (businessDays) {
|
|
3878
3932
|
const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
|
|
3879
|
-
newStart =
|
|
3933
|
+
newStart = subtractBusinessDays(constraintDate, businessDuration, weekendPredicate);
|
|
3880
3934
|
} else {
|
|
3881
3935
|
newStart = new Date(constraintDate.getTime() - durationMs);
|
|
3882
3936
|
}
|
|
@@ -4144,7 +4198,7 @@ var TaskListRow = import_react10.default.memo(
|
|
|
4144
4198
|
);
|
|
4145
4199
|
const getEndDate = (0, import_react10.useCallback)(
|
|
4146
4200
|
(start, duration) => {
|
|
4147
|
-
return businessDays ? addBusinessDays(start, duration, weekendPredicate) : getEndDateFromDuration(start, duration);
|
|
4201
|
+
return businessDays ? addBusinessDays(start, duration, weekendPredicate).toISOString().split("T")[0] : getEndDateFromDuration(start, duration);
|
|
4148
4202
|
},
|
|
4149
4203
|
[businessDays, weekendPredicate]
|
|
4150
4204
|
);
|
|
@@ -6683,20 +6737,8 @@ function GanttChartInner(props, ref) {
|
|
|
6683
6737
|
}
|
|
6684
6738
|
return;
|
|
6685
6739
|
}
|
|
6686
|
-
const
|
|
6687
|
-
|
|
6688
|
-
const { startDate: parentStart, endDate: parentEnd } = computeParentDates(updatedTask.id, tasks);
|
|
6689
|
-
const parentWithRecalcDates = {
|
|
6690
|
-
...updatedTask,
|
|
6691
|
-
startDate: parentStart.toISOString().split("T")[0],
|
|
6692
|
-
endDate: parentEnd.toISOString().split("T")[0]
|
|
6693
|
-
};
|
|
6694
|
-
const cascadedTasks = disableConstraints ? [parentWithRecalcDates] : universalCascade(parentWithRecalcDates, parentStart, parentEnd, tasks, businessDays, isCustomWeekend);
|
|
6695
|
-
onTasksChange?.(cascadedTasks);
|
|
6696
|
-
} else {
|
|
6697
|
-
const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
|
|
6698
|
-
onTasksChange?.(cascadedTasks);
|
|
6699
|
-
}
|
|
6740
|
+
const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
|
|
6741
|
+
onTasksChange?.(cascadedTasks);
|
|
6700
6742
|
}, [tasks, onTasksChange, disableConstraints, editingTaskId, businessDays, isCustomWeekend]);
|
|
6701
6743
|
const handleDelete = (0, import_react13.useCallback)((taskId) => {
|
|
6702
6744
|
const toDelete = /* @__PURE__ */ new Set([taskId]);
|
|
@@ -7123,6 +7165,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7123
7165
|
0 && (module.exports = {
|
|
7124
7166
|
Button,
|
|
7125
7167
|
Calendar,
|
|
7168
|
+
DAY_MS,
|
|
7126
7169
|
DatePicker,
|
|
7127
7170
|
DragGuideLines,
|
|
7128
7171
|
GanttChart,
|
|
@@ -7168,6 +7211,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7168
7211
|
formatDateRangeLabel,
|
|
7169
7212
|
getAllDependencyEdges,
|
|
7170
7213
|
getAllDescendants,
|
|
7214
|
+
getBusinessDayOffset,
|
|
7171
7215
|
getBusinessDaysCount,
|
|
7172
7216
|
getChildren,
|
|
7173
7217
|
getCursorForPosition,
|
|
@@ -7195,14 +7239,17 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7195
7239
|
normalizeDependencyLag,
|
|
7196
7240
|
normalizeHierarchyTasks,
|
|
7197
7241
|
normalizeTaskDates,
|
|
7242
|
+
normalizeUTCDate,
|
|
7198
7243
|
not,
|
|
7199
7244
|
or,
|
|
7245
|
+
parseDateOnly,
|
|
7200
7246
|
parseUTCDate,
|
|
7201
7247
|
pixelsToDate,
|
|
7202
7248
|
progressInRange,
|
|
7203
7249
|
recalculateIncomingLags,
|
|
7204
7250
|
reflowTasksOnModeSwitch,
|
|
7205
7251
|
removeDependenciesBetweenTasks,
|
|
7252
|
+
shiftBusinessDayOffset,
|
|
7206
7253
|
subtractBusinessDays,
|
|
7207
7254
|
universalCascade,
|
|
7208
7255
|
useTaskDrag,
|