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.mjs
CHANGED
|
@@ -22,32 +22,49 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
22
22
|
};
|
|
23
23
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
24
24
|
|
|
25
|
-
// src/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
25
|
+
// src/core/scheduling/dateMath.ts
|
|
26
|
+
function normalizeUTCDate(date) {
|
|
27
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
28
|
+
}
|
|
29
|
+
function parseDateOnly(date) {
|
|
30
|
+
const parsed = typeof date === "string" ? /* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(date);
|
|
31
|
+
return normalizeUTCDate(parsed);
|
|
32
|
+
}
|
|
33
|
+
function getBusinessDayOffset(fromDate, toDate, weekendPredicate) {
|
|
34
|
+
const from = normalizeUTCDate(fromDate);
|
|
35
|
+
const to = normalizeUTCDate(toDate);
|
|
36
|
+
if (from.getTime() === to.getTime()) {
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
const step = to.getTime() > from.getTime() ? 1 : -1;
|
|
40
|
+
const current = new Date(from);
|
|
41
|
+
let offset = 0;
|
|
42
|
+
while (current.getTime() !== to.getTime()) {
|
|
43
|
+
current.setUTCDate(current.getUTCDate() + step);
|
|
44
|
+
if (!weekendPredicate(current)) {
|
|
45
|
+
offset += step;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return offset;
|
|
49
|
+
}
|
|
50
|
+
function shiftBusinessDayOffset(date, offset, weekendPredicate) {
|
|
51
|
+
const current = normalizeUTCDate(date);
|
|
52
|
+
if (offset === 0) {
|
|
53
|
+
return current;
|
|
54
|
+
}
|
|
55
|
+
const step = offset > 0 ? 1 : -1;
|
|
56
|
+
let remaining = Math.abs(offset);
|
|
57
|
+
while (remaining > 0) {
|
|
58
|
+
current.setUTCDate(current.getUTCDate() + step);
|
|
59
|
+
if (!weekendPredicate(current)) {
|
|
60
|
+
remaining--;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return current;
|
|
64
|
+
}
|
|
48
65
|
function getBusinessDaysCount(startDate, endDate, weekendPredicate) {
|
|
49
|
-
const start =
|
|
50
|
-
const end =
|
|
66
|
+
const start = typeof startDate === "string" ? /* @__PURE__ */ new Date(`${startDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(startDate);
|
|
67
|
+
const end = typeof endDate === "string" ? /* @__PURE__ */ new Date(`${endDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(endDate);
|
|
51
68
|
let count = 0;
|
|
52
69
|
const current = new Date(start);
|
|
53
70
|
while (current.getTime() <= end.getTime()) {
|
|
@@ -59,7 +76,7 @@ function getBusinessDaysCount(startDate, endDate, weekendPredicate) {
|
|
|
59
76
|
return Math.max(1, count);
|
|
60
77
|
}
|
|
61
78
|
function addBusinessDays(startDate, businessDays, weekendPredicate) {
|
|
62
|
-
const start =
|
|
79
|
+
const start = typeof startDate === "string" ? /* @__PURE__ */ new Date(`${startDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(startDate);
|
|
63
80
|
const current = new Date(start);
|
|
64
81
|
let targetDays = Math.max(1, businessDays);
|
|
65
82
|
let businessDaysCounted = 0;
|
|
@@ -71,10 +88,10 @@ function addBusinessDays(startDate, businessDays, weekendPredicate) {
|
|
|
71
88
|
current.setUTCDate(current.getUTCDate() + 1);
|
|
72
89
|
}
|
|
73
90
|
}
|
|
74
|
-
return current
|
|
91
|
+
return current;
|
|
75
92
|
}
|
|
76
93
|
function subtractBusinessDays(endDate, businessDays, weekendPredicate) {
|
|
77
|
-
const end =
|
|
94
|
+
const end = typeof endDate === "string" ? /* @__PURE__ */ new Date(`${endDate.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(endDate);
|
|
78
95
|
const current = new Date(end);
|
|
79
96
|
let targetDays = Math.max(1, businessDays);
|
|
80
97
|
let businessDaysCounted = 0;
|
|
@@ -86,12 +103,70 @@ function subtractBusinessDays(endDate, businessDays, weekendPredicate) {
|
|
|
86
103
|
current.setUTCDate(current.getUTCDate() - 1);
|
|
87
104
|
}
|
|
88
105
|
}
|
|
89
|
-
return current
|
|
106
|
+
return current;
|
|
107
|
+
}
|
|
108
|
+
function alignToWorkingDay(date, direction, weekendPredicate) {
|
|
109
|
+
const current = normalizeUTCDate(date);
|
|
110
|
+
while (weekendPredicate(current)) {
|
|
111
|
+
current.setUTCDate(current.getUTCDate() + direction);
|
|
112
|
+
}
|
|
113
|
+
return current;
|
|
114
|
+
}
|
|
115
|
+
function getTaskDuration(startDate, endDate, businessDays = false, weekendPredicate) {
|
|
116
|
+
const start = parseDateOnly(startDate);
|
|
117
|
+
const end = parseDateOnly(endDate);
|
|
118
|
+
if (businessDays && weekendPredicate) {
|
|
119
|
+
return getBusinessDaysCount(start, end, weekendPredicate);
|
|
120
|
+
}
|
|
121
|
+
return Math.max(1, Math.round((end.getTime() - start.getTime()) / DAY_MS) + 1);
|
|
122
|
+
}
|
|
123
|
+
var DAY_MS;
|
|
124
|
+
var init_dateMath = __esm({
|
|
125
|
+
"src/core/scheduling/dateMath.ts"() {
|
|
126
|
+
"use strict";
|
|
127
|
+
DAY_MS = 24 * 60 * 60 * 1e3;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// src/utils/dateUtils.ts
|
|
132
|
+
var dateUtils_exports = {};
|
|
133
|
+
__export(dateUtils_exports, {
|
|
134
|
+
addBusinessDays: () => addBusinessDays2,
|
|
135
|
+
createCustomDayPredicate: () => createCustomDayPredicate,
|
|
136
|
+
createDateKey: () => createDateKey,
|
|
137
|
+
formatDateLabel: () => formatDateLabel,
|
|
138
|
+
formatDateRangeLabel: () => formatDateRangeLabel,
|
|
139
|
+
getBusinessDaysCount: () => getBusinessDaysCount2,
|
|
140
|
+
getDayOffset: () => getDayOffset,
|
|
141
|
+
getMonthBlocks: () => getMonthBlocks,
|
|
142
|
+
getMonthDays: () => getMonthDays,
|
|
143
|
+
getMonthSpans: () => getMonthSpans,
|
|
144
|
+
getMultiMonthDays: () => getMultiMonthDays,
|
|
145
|
+
getWeekBlocks: () => getWeekBlocks,
|
|
146
|
+
getWeekSpans: () => getWeekSpans,
|
|
147
|
+
getYearSpans: () => getYearSpans,
|
|
148
|
+
isToday: () => isToday,
|
|
149
|
+
isWeekend: () => isWeekend,
|
|
150
|
+
normalizeTaskDates: () => normalizeTaskDates,
|
|
151
|
+
parseUTCDate: () => parseUTCDate,
|
|
152
|
+
subtractBusinessDays: () => subtractBusinessDays2
|
|
153
|
+
});
|
|
154
|
+
function getBusinessDaysCount2(startDate, endDate, weekendPredicate) {
|
|
155
|
+
return getBusinessDaysCount(startDate, endDate, weekendPredicate);
|
|
156
|
+
}
|
|
157
|
+
function addBusinessDays2(startDate, businessDays, weekendPredicate) {
|
|
158
|
+
const result = addBusinessDays(startDate, businessDays, weekendPredicate);
|
|
159
|
+
return result.toISOString().split("T")[0];
|
|
160
|
+
}
|
|
161
|
+
function subtractBusinessDays2(endDate, businessDays, weekendPredicate) {
|
|
162
|
+
const result = subtractBusinessDays(endDate, businessDays, weekendPredicate);
|
|
163
|
+
return result.toISOString().split("T")[0];
|
|
90
164
|
}
|
|
91
165
|
var parseUTCDate, getMonthDays, getDayOffset, isToday, isWeekend, createDateKey, createCustomDayPredicate, getMultiMonthDays, getMonthSpans, formatDateLabel, MONTH_ABBR, formatDateRangeLabel, getWeekBlocks, getWeekSpans, getMonthBlocks, getYearSpans, normalizeTaskDates;
|
|
92
166
|
var init_dateUtils = __esm({
|
|
93
167
|
"src/utils/dateUtils.ts"() {
|
|
94
168
|
"use strict";
|
|
169
|
+
init_dateMath();
|
|
95
170
|
parseUTCDate = (date) => {
|
|
96
171
|
if (typeof date === "string") {
|
|
97
172
|
const dateStr = date.includes("T") ? date : `${date}T00:00:00Z`;
|
|
@@ -419,43 +494,11 @@ var init_dateUtils = __esm({
|
|
|
419
494
|
init_dateUtils();
|
|
420
495
|
import { useMemo as useMemo9, useCallback as useCallback6, useRef as useRef7, useState as useState7, useEffect as useEffect7, useImperativeHandle, forwardRef } from "react";
|
|
421
496
|
|
|
422
|
-
// src/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
function getBusinessDayOffset(fromDate, toDate, weekendPredicate) {
|
|
428
|
-
const from = normalizeUTCDate(fromDate);
|
|
429
|
-
const to = normalizeUTCDate(toDate);
|
|
430
|
-
if (from.getTime() === to.getTime()) {
|
|
431
|
-
return 0;
|
|
432
|
-
}
|
|
433
|
-
const step = to.getTime() > from.getTime() ? 1 : -1;
|
|
434
|
-
const current = new Date(from);
|
|
435
|
-
let offset = 0;
|
|
436
|
-
while (current.getTime() !== to.getTime()) {
|
|
437
|
-
current.setUTCDate(current.getUTCDate() + step);
|
|
438
|
-
if (!weekendPredicate(current)) {
|
|
439
|
-
offset += step;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return offset;
|
|
443
|
-
}
|
|
444
|
-
function shiftBusinessDayOffset(date, offset, weekendPredicate) {
|
|
445
|
-
const current = normalizeUTCDate(date);
|
|
446
|
-
if (offset === 0) {
|
|
447
|
-
return current;
|
|
448
|
-
}
|
|
449
|
-
const step = offset > 0 ? 1 : -1;
|
|
450
|
-
let remaining = Math.abs(offset);
|
|
451
|
-
while (remaining > 0) {
|
|
452
|
-
current.setUTCDate(current.getUTCDate() + step);
|
|
453
|
-
if (!weekendPredicate(current)) {
|
|
454
|
-
remaining--;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return current;
|
|
458
|
-
}
|
|
497
|
+
// src/core/scheduling/index.ts
|
|
498
|
+
init_dateMath();
|
|
499
|
+
|
|
500
|
+
// src/core/scheduling/dependencies.ts
|
|
501
|
+
init_dateMath();
|
|
459
502
|
function getDependencyLag(dep) {
|
|
460
503
|
return Number.isFinite(dep.lag) ? dep.lag : 0;
|
|
461
504
|
}
|
|
@@ -471,71 +514,268 @@ function normalizeDependencyLag(linkType, lag, predecessorStart, predecessorEnd,
|
|
|
471
514
|
);
|
|
472
515
|
return Math.max(-predecessorDuration, lag);
|
|
473
516
|
}
|
|
474
|
-
function
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
517
|
+
function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate) {
|
|
518
|
+
const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
|
|
519
|
+
const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
|
|
520
|
+
const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
|
|
521
|
+
const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
|
|
522
|
+
if (!businessDays || !weekendPredicate) {
|
|
523
|
+
switch (linkType) {
|
|
524
|
+
case "FS":
|
|
525
|
+
return normalizeDependencyLag(
|
|
526
|
+
linkType,
|
|
527
|
+
Math.round((sS - pE) / DAY_MS) - 1,
|
|
528
|
+
predStart,
|
|
529
|
+
predEnd,
|
|
530
|
+
businessDays,
|
|
531
|
+
weekendPredicate
|
|
532
|
+
);
|
|
533
|
+
case "SS":
|
|
534
|
+
return Math.round((sS - pS) / DAY_MS);
|
|
535
|
+
case "FF":
|
|
536
|
+
return Math.round((sE - pE) / DAY_MS);
|
|
537
|
+
case "SF":
|
|
538
|
+
return Math.round((sE - pS) / DAY_MS) + 1;
|
|
539
|
+
}
|
|
496
540
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
541
|
+
const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
|
|
542
|
+
const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
|
|
543
|
+
const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
|
|
544
|
+
switch (linkType) {
|
|
545
|
+
case "FS":
|
|
546
|
+
return normalizeDependencyLag(
|
|
547
|
+
linkType,
|
|
548
|
+
businessOffset - 1,
|
|
549
|
+
predStart,
|
|
550
|
+
predEnd,
|
|
551
|
+
businessDays,
|
|
552
|
+
weekendPredicate
|
|
553
|
+
);
|
|
554
|
+
case "SS":
|
|
555
|
+
return businessOffset;
|
|
556
|
+
case "FF":
|
|
557
|
+
return businessOffset;
|
|
558
|
+
case "SF":
|
|
559
|
+
return businessOffset + 1;
|
|
509
560
|
}
|
|
510
|
-
return {
|
|
511
|
-
start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS),
|
|
512
|
-
end: normalizedEnd
|
|
513
|
-
};
|
|
514
561
|
}
|
|
515
|
-
function
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
562
|
+
function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate) {
|
|
563
|
+
const normalizedLag = normalizeDependencyLag(
|
|
564
|
+
linkType,
|
|
565
|
+
lag,
|
|
566
|
+
predecessorStart,
|
|
567
|
+
predecessorEnd,
|
|
519
568
|
businessDays,
|
|
520
|
-
weekendPredicate
|
|
521
|
-
snapDirection
|
|
569
|
+
weekendPredicate
|
|
522
570
|
);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
534
|
-
if (!predecessor) {
|
|
535
|
-
continue;
|
|
571
|
+
if (!businessDays || !weekendPredicate) {
|
|
572
|
+
switch (linkType) {
|
|
573
|
+
case "FS":
|
|
574
|
+
return new Date(predecessorEnd.getTime() + (normalizedLag + 1) * DAY_MS);
|
|
575
|
+
case "SS":
|
|
576
|
+
return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
|
|
577
|
+
case "FF":
|
|
578
|
+
return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
|
|
579
|
+
case "SF":
|
|
580
|
+
return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
|
|
536
581
|
}
|
|
537
|
-
|
|
538
|
-
|
|
582
|
+
}
|
|
583
|
+
const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
|
|
584
|
+
let offset;
|
|
585
|
+
switch (linkType) {
|
|
586
|
+
case "FS":
|
|
587
|
+
offset = normalizedLag + 1;
|
|
588
|
+
break;
|
|
589
|
+
case "SS":
|
|
590
|
+
offset = normalizedLag;
|
|
591
|
+
break;
|
|
592
|
+
case "FF":
|
|
593
|
+
offset = normalizedLag;
|
|
594
|
+
break;
|
|
595
|
+
case "SF":
|
|
596
|
+
offset = normalizedLag - 1;
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/core/scheduling/cascade.ts
|
|
603
|
+
init_dateMath();
|
|
604
|
+
|
|
605
|
+
// src/core/scheduling/hierarchy.ts
|
|
606
|
+
function getChildren(parentId, tasks) {
|
|
607
|
+
return tasks.filter((t) => t.parentId === parentId);
|
|
608
|
+
}
|
|
609
|
+
function isTaskParent(taskId, tasks) {
|
|
610
|
+
return tasks.some((t) => t.parentId === taskId);
|
|
611
|
+
}
|
|
612
|
+
function computeParentDates(parentId, tasks) {
|
|
613
|
+
const children = getChildren(parentId, tasks);
|
|
614
|
+
if (children.length === 0) {
|
|
615
|
+
const parent = tasks.find((t) => t.id === parentId);
|
|
616
|
+
const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
|
|
617
|
+
const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
|
|
618
|
+
return { startDate: start, endDate: end };
|
|
619
|
+
}
|
|
620
|
+
const startDates = children.map((c) => new Date(c.startDate));
|
|
621
|
+
const endDates = children.map((c) => new Date(c.endDate));
|
|
622
|
+
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
623
|
+
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
624
|
+
return {
|
|
625
|
+
startDate: new Date(minTime),
|
|
626
|
+
endDate: new Date(maxTime)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function computeParentProgress(parentId, tasks) {
|
|
630
|
+
const children = getChildren(parentId, tasks);
|
|
631
|
+
if (children.length === 0) {
|
|
632
|
+
return 0;
|
|
633
|
+
}
|
|
634
|
+
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
635
|
+
let totalWeight = 0;
|
|
636
|
+
let weightedSum = 0;
|
|
637
|
+
for (const child of children) {
|
|
638
|
+
const start = new Date(child.startDate).getTime();
|
|
639
|
+
const end = new Date(child.endDate).getTime();
|
|
640
|
+
const duration = (end - start + DAY_MS3) / DAY_MS3;
|
|
641
|
+
const progress = child.progress ?? 0;
|
|
642
|
+
totalWeight += duration;
|
|
643
|
+
weightedSum += duration * progress;
|
|
644
|
+
}
|
|
645
|
+
if (totalWeight === 0) {
|
|
646
|
+
return 0;
|
|
647
|
+
}
|
|
648
|
+
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
649
|
+
}
|
|
650
|
+
function getAllDescendants(parentId, tasks) {
|
|
651
|
+
const descendants = [];
|
|
652
|
+
const visited = /* @__PURE__ */ new Set();
|
|
653
|
+
function collectChildren(taskId) {
|
|
654
|
+
if (visited.has(taskId)) return;
|
|
655
|
+
visited.add(taskId);
|
|
656
|
+
const children = getChildren(taskId, tasks);
|
|
657
|
+
for (const child of children) {
|
|
658
|
+
descendants.push(child);
|
|
659
|
+
collectChildren(child.id);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
collectChildren(parentId);
|
|
663
|
+
return descendants;
|
|
664
|
+
}
|
|
665
|
+
function getAllDependencyEdges(tasks) {
|
|
666
|
+
const edges = [];
|
|
667
|
+
for (const task of tasks) {
|
|
668
|
+
if (task.dependencies) {
|
|
669
|
+
for (const dep of task.dependencies) {
|
|
670
|
+
edges.push({
|
|
671
|
+
predecessorId: dep.taskId,
|
|
672
|
+
successorId: task.id,
|
|
673
|
+
type: dep.type,
|
|
674
|
+
lag: dep.lag ?? 0
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return edges;
|
|
680
|
+
}
|
|
681
|
+
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
682
|
+
return tasks.map((task) => {
|
|
683
|
+
if (task.id === taskId1 || task.id === taskId2) {
|
|
684
|
+
if (!task.dependencies) return task;
|
|
685
|
+
const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
|
|
686
|
+
const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
|
|
687
|
+
if (filteredDependencies.length === task.dependencies.length) {
|
|
688
|
+
return task;
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
...task,
|
|
692
|
+
dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
return task;
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
function findParentId(taskId, tasks) {
|
|
699
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
700
|
+
return task?.parentId;
|
|
701
|
+
}
|
|
702
|
+
function isAncestorTask(ancestorId, taskId, tasks) {
|
|
703
|
+
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
704
|
+
const visited = /* @__PURE__ */ new Set();
|
|
705
|
+
let current = taskById.get(taskId);
|
|
706
|
+
while (current?.parentId) {
|
|
707
|
+
if (current.parentId === ancestorId) {
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
if (visited.has(current.parentId)) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
visited.add(current.parentId);
|
|
714
|
+
current = taskById.get(current.parentId);
|
|
715
|
+
}
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
function areTasksHierarchicallyRelated(taskId1, taskId2, tasks) {
|
|
719
|
+
if (taskId1 === taskId2) {
|
|
720
|
+
return true;
|
|
721
|
+
}
|
|
722
|
+
return isAncestorTask(taskId1, taskId2, tasks) || isAncestorTask(taskId2, taskId1, tasks);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/core/scheduling/commands.ts
|
|
726
|
+
init_dateMath();
|
|
727
|
+
function buildTaskRangeFromStart(startDate, duration, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
728
|
+
const normalizedStart = businessDays && weekendPredicate ? alignToWorkingDay(startDate, snapDirection, weekendPredicate) : normalizeUTCDate(startDate);
|
|
729
|
+
if (businessDays && weekendPredicate) {
|
|
730
|
+
return {
|
|
731
|
+
start: normalizedStart,
|
|
732
|
+
end: parseDateOnly(addBusinessDays(normalizedStart, duration, weekendPredicate))
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
736
|
+
return {
|
|
737
|
+
start: normalizedStart,
|
|
738
|
+
end: new Date(normalizedStart.getTime() + (Math.max(1, duration) - 1) * DAY_MS3)
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
function buildTaskRangeFromEnd(endDate, duration, businessDays = false, weekendPredicate, snapDirection = -1) {
|
|
742
|
+
const normalizedEnd = businessDays && weekendPredicate ? alignToWorkingDay(endDate, snapDirection, weekendPredicate) : normalizeUTCDate(endDate);
|
|
743
|
+
if (businessDays && weekendPredicate) {
|
|
744
|
+
return {
|
|
745
|
+
start: parseDateOnly(subtractBusinessDays(normalizedEnd, duration, weekendPredicate)),
|
|
746
|
+
end: normalizedEnd
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
750
|
+
return {
|
|
751
|
+
start: new Date(normalizedEnd.getTime() - (Math.max(1, duration) - 1) * DAY_MS3),
|
|
752
|
+
end: normalizedEnd
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function moveTaskRange(originalStart, originalEnd, proposedStart, businessDays = false, weekendPredicate, snapDirection = 1) {
|
|
756
|
+
return buildTaskRangeFromStart(
|
|
757
|
+
proposedStart,
|
|
758
|
+
getTaskDuration(originalStart, originalEnd, businessDays, weekendPredicate),
|
|
759
|
+
businessDays,
|
|
760
|
+
weekendPredicate,
|
|
761
|
+
snapDirection
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks, businessDays = false, weekendPredicate) {
|
|
765
|
+
if (!task.dependencies?.length) {
|
|
766
|
+
return { start: proposedStart, end: proposedEnd };
|
|
767
|
+
}
|
|
768
|
+
let minAllowedStart = null;
|
|
769
|
+
for (const dep of task.dependencies) {
|
|
770
|
+
if (dep.type !== "FS") {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
774
|
+
if (!predecessor) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
const predecessorStart = parseDateOnly(predecessor.startDate);
|
|
778
|
+
const predecessorEnd = parseDateOnly(predecessor.endDate);
|
|
539
779
|
const predecessorDuration = getTaskDuration(
|
|
540
780
|
predecessorStart,
|
|
541
781
|
predecessorEnd,
|
|
@@ -564,193 +804,85 @@ function clampTaskRangeForIncomingFS(task, proposedStart, proposedEnd, allTasks,
|
|
|
564
804
|
weekendPredicate
|
|
565
805
|
);
|
|
566
806
|
}
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
return
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const graph = /* @__PURE__ */ new Map();
|
|
574
|
-
for (const task of tasks) {
|
|
575
|
-
const successors = [];
|
|
576
|
-
for (const otherTask of tasks) {
|
|
577
|
-
if (otherTask.dependencies) {
|
|
578
|
-
for (const dep of otherTask.dependencies) {
|
|
579
|
-
if (dep.taskId === task.id) {
|
|
580
|
-
successors.push(otherTask.id);
|
|
581
|
-
break;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
graph.set(task.id, successors);
|
|
587
|
-
}
|
|
588
|
-
return graph;
|
|
589
|
-
}
|
|
590
|
-
function detectCycles(tasks) {
|
|
591
|
-
const graph = buildAdjacencyList(tasks);
|
|
592
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
593
|
-
const visited = /* @__PURE__ */ new Set();
|
|
594
|
-
const path = [];
|
|
595
|
-
function dfs(taskId) {
|
|
596
|
-
if (visiting.has(taskId)) {
|
|
597
|
-
return true;
|
|
598
|
-
}
|
|
599
|
-
if (visited.has(taskId)) {
|
|
600
|
-
return false;
|
|
601
|
-
}
|
|
602
|
-
visiting.add(taskId);
|
|
603
|
-
path.push(taskId);
|
|
604
|
-
const successors = graph.get(taskId) || [];
|
|
605
|
-
for (const successor of successors) {
|
|
606
|
-
if (dfs(successor)) {
|
|
607
|
-
return true;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
visiting.delete(taskId);
|
|
611
|
-
path.pop();
|
|
612
|
-
visited.add(taskId);
|
|
613
|
-
return false;
|
|
614
|
-
}
|
|
615
|
-
for (const task of tasks) {
|
|
616
|
-
if (dfs(task.id)) {
|
|
617
|
-
return { hasCycle: true, cyclePath: [...path] };
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
return { hasCycle: false };
|
|
621
|
-
}
|
|
622
|
-
function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd, businessDays = false, weekendPredicate) {
|
|
623
|
-
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
624
|
-
const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
|
|
625
|
-
const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
|
|
626
|
-
const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
|
|
627
|
-
const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
|
|
628
|
-
if (!businessDays || !weekendPredicate) {
|
|
629
|
-
switch (linkType) {
|
|
630
|
-
case "FS":
|
|
631
|
-
return normalizeDependencyLag(
|
|
632
|
-
linkType,
|
|
633
|
-
Math.round((sS - pE) / DAY_MS3) - 1,
|
|
634
|
-
predStart,
|
|
635
|
-
predEnd,
|
|
636
|
-
businessDays,
|
|
637
|
-
weekendPredicate
|
|
638
|
-
);
|
|
639
|
-
case "SS":
|
|
640
|
-
return Math.round((sS - pS) / DAY_MS3);
|
|
641
|
-
case "FF":
|
|
642
|
-
return Math.round((sE - pE) / DAY_MS3);
|
|
643
|
-
case "SF":
|
|
644
|
-
return Math.round((sE - pS) / DAY_MS3) + 1;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
const anchorDate = linkType === "SS" || linkType === "SF" ? predStart : predEnd;
|
|
648
|
-
const targetDate = linkType === "FS" || linkType === "SS" ? succStart : succEnd;
|
|
649
|
-
const businessOffset = getBusinessDayOffset(anchorDate, targetDate, weekendPredicate);
|
|
650
|
-
switch (linkType) {
|
|
651
|
-
case "FS":
|
|
652
|
-
return normalizeDependencyLag(
|
|
653
|
-
linkType,
|
|
654
|
-
businessOffset - 1,
|
|
655
|
-
predStart,
|
|
656
|
-
predEnd,
|
|
657
|
-
businessDays,
|
|
658
|
-
weekendPredicate
|
|
659
|
-
);
|
|
660
|
-
case "SS":
|
|
661
|
-
return businessOffset;
|
|
662
|
-
case "FF":
|
|
663
|
-
return businessOffset;
|
|
664
|
-
case "SF":
|
|
665
|
-
return businessOffset + 1;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0, businessDays = false, weekendPredicate) {
|
|
669
|
-
const normalizedLag = normalizeDependencyLag(
|
|
670
|
-
linkType,
|
|
671
|
-
lag,
|
|
672
|
-
predecessorStart,
|
|
673
|
-
predecessorEnd,
|
|
674
|
-
businessDays,
|
|
675
|
-
weekendPredicate
|
|
676
|
-
);
|
|
677
|
-
if (!businessDays || !weekendPredicate) {
|
|
678
|
-
switch (linkType) {
|
|
679
|
-
case "FS":
|
|
680
|
-
return new Date(predecessorEnd.getTime() + (normalizedLag + 1) * DAY_MS);
|
|
681
|
-
case "SS":
|
|
682
|
-
return new Date(predecessorStart.getTime() + normalizedLag * DAY_MS);
|
|
683
|
-
case "FF":
|
|
684
|
-
return new Date(predecessorEnd.getTime() + normalizedLag * DAY_MS);
|
|
685
|
-
case "SF":
|
|
686
|
-
return new Date(predecessorStart.getTime() + (normalizedLag - 1) * DAY_MS);
|
|
807
|
+
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate) {
|
|
808
|
+
if (!task.dependencies) return [];
|
|
809
|
+
return task.dependencies.map((dep) => {
|
|
810
|
+
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
811
|
+
if (!predecessor) {
|
|
812
|
+
return { ...dep, lag: getDependencyLag(dep) };
|
|
687
813
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
case "SF":
|
|
702
|
-
offset = normalizedLag - 1;
|
|
703
|
-
break;
|
|
704
|
-
}
|
|
705
|
-
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
814
|
+
const predecessorStart = new Date(predecessor.startDate);
|
|
815
|
+
const predecessorEnd = new Date(predecessor.endDate);
|
|
816
|
+
const nextLag = computeLagFromDates(
|
|
817
|
+
dep.type,
|
|
818
|
+
predecessorStart,
|
|
819
|
+
predecessorEnd,
|
|
820
|
+
newStartDate,
|
|
821
|
+
newEndDate,
|
|
822
|
+
businessDays,
|
|
823
|
+
weekendPredicate
|
|
824
|
+
);
|
|
825
|
+
return { ...dep, lag: nextLag };
|
|
826
|
+
});
|
|
706
827
|
}
|
|
707
|
-
function
|
|
708
|
-
const
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
828
|
+
function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
|
|
829
|
+
const dayOffset = Math.round(left / dayWidth);
|
|
830
|
+
const rawStartDate = new Date(Date.UTC(
|
|
831
|
+
monthStart.getUTCFullYear(),
|
|
832
|
+
monthStart.getUTCMonth(),
|
|
833
|
+
monthStart.getUTCDate() + dayOffset
|
|
834
|
+
));
|
|
835
|
+
const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
|
|
836
|
+
const rawEndDate = new Date(Date.UTC(
|
|
837
|
+
monthStart.getUTCFullYear(),
|
|
838
|
+
monthStart.getUTCMonth(),
|
|
839
|
+
monthStart.getUTCDate() + rawEndOffset
|
|
840
|
+
));
|
|
841
|
+
if (!(businessDays && weekendPredicate)) {
|
|
842
|
+
return { start: rawStartDate, end: rawEndDate };
|
|
723
843
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
relatedTaskIds: [dep.taskId, task.id]
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
}
|
|
844
|
+
if (mode === "move") {
|
|
845
|
+
const originalStart2 = new Date(task.startDate);
|
|
846
|
+
const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
|
|
847
|
+
return moveTaskRange(
|
|
848
|
+
task.startDate,
|
|
849
|
+
task.endDate,
|
|
850
|
+
rawStartDate,
|
|
851
|
+
true,
|
|
852
|
+
weekendPredicate,
|
|
853
|
+
snapDirection2
|
|
854
|
+
);
|
|
739
855
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
});
|
|
856
|
+
if (mode === "resize-right") {
|
|
857
|
+
const fixedStart = new Date(task.startDate);
|
|
858
|
+
const originalEnd = new Date(task.endDate);
|
|
859
|
+
const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
|
|
860
|
+
const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
|
|
861
|
+
const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
|
|
862
|
+
return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
|
|
748
863
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
864
|
+
const fixedEnd = new Date(task.endDate);
|
|
865
|
+
const originalStart = new Date(task.startDate);
|
|
866
|
+
const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
|
|
867
|
+
const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
|
|
868
|
+
const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
|
|
869
|
+
return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
|
|
870
|
+
}
|
|
871
|
+
function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
|
|
872
|
+
if (mode === "resize-right") {
|
|
873
|
+
return range;
|
|
874
|
+
}
|
|
875
|
+
return clampTaskRangeForIncomingFS(
|
|
876
|
+
task,
|
|
877
|
+
range.start,
|
|
878
|
+
range.end,
|
|
879
|
+
allTasks,
|
|
880
|
+
businessDays,
|
|
881
|
+
weekendPredicate
|
|
882
|
+
);
|
|
753
883
|
}
|
|
884
|
+
|
|
885
|
+
// src/core/scheduling/cascade.ts
|
|
754
886
|
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
755
887
|
const successorMap = /* @__PURE__ */ new Map();
|
|
756
888
|
for (const task of allTasks) {
|
|
@@ -814,225 +946,85 @@ function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascad
|
|
|
814
946
|
visited.add(child.id);
|
|
815
947
|
updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
|
|
816
948
|
result.push({
|
|
817
|
-
...child,
|
|
818
|
-
startDate: newChildStart.toISOString().split("T")[0],
|
|
819
|
-
endDate: newChildEnd.toISOString().split("T")[0]
|
|
820
|
-
});
|
|
821
|
-
queue.push(child.id);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
for (const task of allTasks) {
|
|
825
|
-
if (visited.has(task.id) || !task.dependencies || task.locked) continue;
|
|
826
|
-
for (const dep of task.dependencies) {
|
|
827
|
-
if (dep.taskId !== currentId) continue;
|
|
828
|
-
const orig = taskById.get(task.id);
|
|
829
|
-
const origStart = new Date(orig.startDate);
|
|
830
|
-
const origEnd = new Date(orig.endDate);
|
|
831
|
-
const duration = getTaskDuration(origStart, origEnd);
|
|
832
|
-
const constraintDate = calculateSuccessorDate(predStart, predEnd, dep.type, getDependencyLag(dep));
|
|
833
|
-
let newSuccStart;
|
|
834
|
-
let newSuccEnd;
|
|
835
|
-
if (dep.type === "FS" || dep.type === "SS") {
|
|
836
|
-
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
|
|
837
|
-
} else {
|
|
838
|
-
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
|
|
839
|
-
}
|
|
840
|
-
visited.add(task.id);
|
|
841
|
-
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
842
|
-
result.push({
|
|
843
|
-
...task,
|
|
844
|
-
startDate: newSuccStart.toISOString().split("T")[0],
|
|
845
|
-
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
846
|
-
});
|
|
847
|
-
queue.push(task.id);
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
return result;
|
|
853
|
-
}
|
|
854
|
-
function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
|
|
855
|
-
const allTypesSuccessorMap = /* @__PURE__ */ new Map();
|
|
856
|
-
for (const task of allTasks) {
|
|
857
|
-
allTypesSuccessorMap.set(task.id, []);
|
|
858
|
-
}
|
|
859
|
-
for (const task of allTasks) {
|
|
860
|
-
if (!task.dependencies) continue;
|
|
861
|
-
for (const dep of task.dependencies) {
|
|
862
|
-
const list = allTypesSuccessorMap.get(dep.taskId) ?? [];
|
|
863
|
-
list.push(task);
|
|
864
|
-
allTypesSuccessorMap.set(dep.taskId, list);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
const directChildren = getChildren(changedTaskId, allTasks);
|
|
868
|
-
const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
|
|
869
|
-
const initialChain = [...directChildren, ...directSuccessors].filter(
|
|
870
|
-
(task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
|
|
871
|
-
);
|
|
872
|
-
const chain = [...initialChain];
|
|
873
|
-
const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
|
|
874
|
-
const queue = [...initialChain];
|
|
875
|
-
while (queue.length > 0) {
|
|
876
|
-
const current = queue.shift();
|
|
877
|
-
const children = getChildren(current.id, allTasks);
|
|
878
|
-
for (const child of children) {
|
|
879
|
-
if (!visited.has(child.id)) {
|
|
880
|
-
visited.add(child.id);
|
|
881
|
-
chain.push(child);
|
|
882
|
-
queue.push(child);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
const successors = allTypesSuccessorMap.get(current.id) ?? [];
|
|
886
|
-
for (const successor of successors) {
|
|
887
|
-
if (!visited.has(successor.id)) {
|
|
888
|
-
visited.add(successor.id);
|
|
889
|
-
chain.push(successor);
|
|
890
|
-
queue.push(successor);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
return chain;
|
|
895
|
-
}
|
|
896
|
-
function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, businessDays = false, weekendPredicate) {
|
|
897
|
-
if (!task.dependencies) return [];
|
|
898
|
-
return task.dependencies.map((dep) => {
|
|
899
|
-
const predecessor = allTasks.find((candidate) => candidate.id === dep.taskId);
|
|
900
|
-
if (!predecessor) {
|
|
901
|
-
return { ...dep, lag: getDependencyLag(dep) };
|
|
902
|
-
}
|
|
903
|
-
const predecessorStart = new Date(predecessor.startDate);
|
|
904
|
-
const predecessorEnd = new Date(predecessor.endDate);
|
|
905
|
-
const nextLag = computeLagFromDates(
|
|
906
|
-
dep.type,
|
|
907
|
-
predecessorStart,
|
|
908
|
-
predecessorEnd,
|
|
909
|
-
newStartDate,
|
|
910
|
-
newEndDate,
|
|
911
|
-
businessDays,
|
|
912
|
-
weekendPredicate
|
|
913
|
-
);
|
|
914
|
-
return { ...dep, lag: nextLag };
|
|
915
|
-
});
|
|
916
|
-
}
|
|
917
|
-
function getAllDependencyEdges(tasks) {
|
|
918
|
-
const edges = [];
|
|
919
|
-
for (const task of tasks) {
|
|
920
|
-
if (task.dependencies) {
|
|
921
|
-
for (const dep of task.dependencies) {
|
|
922
|
-
edges.push({
|
|
923
|
-
predecessorId: dep.taskId,
|
|
924
|
-
successorId: task.id,
|
|
925
|
-
type: dep.type,
|
|
926
|
-
lag: getDependencyLag(dep)
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
return edges;
|
|
932
|
-
}
|
|
933
|
-
function getChildren(parentId, tasks) {
|
|
934
|
-
return tasks.filter((t) => t.parentId === parentId);
|
|
935
|
-
}
|
|
936
|
-
function isTaskParent(taskId, tasks) {
|
|
937
|
-
return tasks.some((t) => t.parentId === taskId);
|
|
938
|
-
}
|
|
939
|
-
function computeParentDates(parentId, tasks) {
|
|
940
|
-
const children = getChildren(parentId, tasks);
|
|
941
|
-
if (children.length === 0) {
|
|
942
|
-
const parent = tasks.find((t) => t.id === parentId);
|
|
943
|
-
const start = parent ? new Date(parent.startDate) : /* @__PURE__ */ new Date();
|
|
944
|
-
const end = parent ? new Date(parent.endDate) : /* @__PURE__ */ new Date();
|
|
945
|
-
return { startDate: start, endDate: end };
|
|
946
|
-
}
|
|
947
|
-
const startDates = children.map((c) => new Date(c.startDate));
|
|
948
|
-
const endDates = children.map((c) => new Date(c.endDate));
|
|
949
|
-
const minTime = Math.min(...startDates.map((d) => d.getTime()));
|
|
950
|
-
const maxTime = Math.max(...endDates.map((d) => d.getTime()));
|
|
951
|
-
return {
|
|
952
|
-
startDate: new Date(minTime),
|
|
953
|
-
endDate: new Date(maxTime)
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
function computeParentProgress(parentId, tasks) {
|
|
957
|
-
const children = getChildren(parentId, tasks);
|
|
958
|
-
if (children.length === 0) {
|
|
959
|
-
return 0;
|
|
960
|
-
}
|
|
961
|
-
const DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
962
|
-
let totalWeight = 0;
|
|
963
|
-
let weightedSum = 0;
|
|
964
|
-
for (const child of children) {
|
|
965
|
-
const start = new Date(child.startDate).getTime();
|
|
966
|
-
const end = new Date(child.endDate).getTime();
|
|
967
|
-
const duration = (end - start + DAY_MS3) / DAY_MS3;
|
|
968
|
-
const progress = child.progress ?? 0;
|
|
969
|
-
totalWeight += duration;
|
|
970
|
-
weightedSum += duration * progress;
|
|
971
|
-
}
|
|
972
|
-
if (totalWeight === 0) {
|
|
973
|
-
return 0;
|
|
974
|
-
}
|
|
975
|
-
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
976
|
-
}
|
|
977
|
-
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
978
|
-
return tasks.map((task) => {
|
|
979
|
-
if (task.id === taskId1 || task.id === taskId2) {
|
|
980
|
-
if (!task.dependencies) return task;
|
|
981
|
-
const otherTaskId = task.id === taskId1 ? taskId2 : taskId1;
|
|
982
|
-
const filteredDependencies = task.dependencies.filter((dep) => dep.taskId !== otherTaskId);
|
|
983
|
-
if (filteredDependencies.length === task.dependencies.length) {
|
|
984
|
-
return task;
|
|
985
|
-
}
|
|
986
|
-
return {
|
|
987
|
-
...task,
|
|
988
|
-
dependencies: filteredDependencies.length > 0 ? filteredDependencies : void 0
|
|
989
|
-
};
|
|
990
|
-
}
|
|
991
|
-
return task;
|
|
992
|
-
});
|
|
993
|
-
}
|
|
994
|
-
function findParentId(taskId, tasks) {
|
|
995
|
-
const task = tasks.find((t) => t.id === taskId);
|
|
996
|
-
return task?.parentId;
|
|
997
|
-
}
|
|
998
|
-
function isAncestorTask(ancestorId, taskId, tasks) {
|
|
999
|
-
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
1000
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1001
|
-
let current = taskById.get(taskId);
|
|
1002
|
-
while (current?.parentId) {
|
|
1003
|
-
if (current.parentId === ancestorId) {
|
|
1004
|
-
return true;
|
|
949
|
+
...child,
|
|
950
|
+
startDate: newChildStart.toISOString().split("T")[0],
|
|
951
|
+
endDate: newChildEnd.toISOString().split("T")[0]
|
|
952
|
+
});
|
|
953
|
+
queue.push(child.id);
|
|
954
|
+
}
|
|
1005
955
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
956
|
+
for (const task of allTasks) {
|
|
957
|
+
if (visited.has(task.id) || !task.dependencies || task.locked) continue;
|
|
958
|
+
for (const dep of task.dependencies) {
|
|
959
|
+
if (dep.taskId !== currentId) continue;
|
|
960
|
+
const orig = taskById.get(task.id);
|
|
961
|
+
const origStart = new Date(orig.startDate);
|
|
962
|
+
const origEnd = new Date(orig.endDate);
|
|
963
|
+
const duration = getTaskDuration(origStart, origEnd);
|
|
964
|
+
const constraintDate = calculateSuccessorDate(predStart, predEnd, dep.type, getDependencyLag(dep));
|
|
965
|
+
let newSuccStart;
|
|
966
|
+
let newSuccEnd;
|
|
967
|
+
if (dep.type === "FS" || dep.type === "SS") {
|
|
968
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromStart(constraintDate, duration));
|
|
969
|
+
} else {
|
|
970
|
+
({ start: newSuccStart, end: newSuccEnd } = buildTaskRangeFromEnd(constraintDate, duration));
|
|
971
|
+
}
|
|
972
|
+
visited.add(task.id);
|
|
973
|
+
updatedDates.set(task.id, { start: newSuccStart, end: newSuccEnd });
|
|
974
|
+
result.push({
|
|
975
|
+
...task,
|
|
976
|
+
startDate: newSuccStart.toISOString().split("T")[0],
|
|
977
|
+
endDate: newSuccEnd.toISOString().split("T")[0]
|
|
978
|
+
});
|
|
979
|
+
queue.push(task.id);
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
1008
982
|
}
|
|
1009
|
-
visited.add(current.parentId);
|
|
1010
|
-
current = taskById.get(current.parentId);
|
|
1011
983
|
}
|
|
1012
|
-
return
|
|
984
|
+
return result;
|
|
1013
985
|
}
|
|
1014
|
-
function
|
|
1015
|
-
|
|
1016
|
-
|
|
986
|
+
function getTransitiveCascadeChain(changedTaskId, allTasks, firstLevelLinkTypes) {
|
|
987
|
+
const allTypesSuccessorMap = /* @__PURE__ */ new Map();
|
|
988
|
+
for (const task of allTasks) {
|
|
989
|
+
allTypesSuccessorMap.set(task.id, []);
|
|
1017
990
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
991
|
+
for (const task of allTasks) {
|
|
992
|
+
if (!task.dependencies) continue;
|
|
993
|
+
for (const dep of task.dependencies) {
|
|
994
|
+
const list = allTypesSuccessorMap.get(dep.taskId) ?? [];
|
|
995
|
+
list.push(task);
|
|
996
|
+
allTypesSuccessorMap.set(dep.taskId, list);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const directChildren = getChildren(changedTaskId, allTasks);
|
|
1000
|
+
const directSuccessors = getSuccessorChain(changedTaskId, allTasks, firstLevelLinkTypes);
|
|
1001
|
+
const initialChain = [...directChildren, ...directSuccessors].filter(
|
|
1002
|
+
(task, index, arr) => arr.findIndex((candidate) => candidate.id === task.id) === index
|
|
1003
|
+
);
|
|
1004
|
+
const chain = [...initialChain];
|
|
1005
|
+
const visited = /* @__PURE__ */ new Set([changedTaskId, ...initialChain.map((t) => t.id)]);
|
|
1006
|
+
const queue = [...initialChain];
|
|
1007
|
+
while (queue.length > 0) {
|
|
1008
|
+
const current = queue.shift();
|
|
1009
|
+
const children = getChildren(current.id, allTasks);
|
|
1027
1010
|
for (const child of children) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1011
|
+
if (!visited.has(child.id)) {
|
|
1012
|
+
visited.add(child.id);
|
|
1013
|
+
chain.push(child);
|
|
1014
|
+
queue.push(child);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const successors = allTypesSuccessorMap.get(current.id) ?? [];
|
|
1018
|
+
for (const successor of successors) {
|
|
1019
|
+
if (!visited.has(successor.id)) {
|
|
1020
|
+
visited.add(successor.id);
|
|
1021
|
+
chain.push(successor);
|
|
1022
|
+
queue.push(successor);
|
|
1023
|
+
}
|
|
1030
1024
|
}
|
|
1031
1025
|
}
|
|
1032
|
-
|
|
1033
|
-
return descendants;
|
|
1026
|
+
return chain;
|
|
1034
1027
|
}
|
|
1035
|
-
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1036
1028
|
function universalCascade(movedTask, newStart, newEnd, allTasks, businessDays = false, weekendPredicate) {
|
|
1037
1029
|
const taskById = new Map(allTasks.map((t) => [t.id, t]));
|
|
1038
1030
|
const updatedDates = /* @__PURE__ */ new Map();
|
|
@@ -1205,6 +1197,105 @@ function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate)
|
|
|
1205
1197
|
return tasks;
|
|
1206
1198
|
}
|
|
1207
1199
|
|
|
1200
|
+
// src/core/scheduling/validation.ts
|
|
1201
|
+
function buildAdjacencyList(tasks) {
|
|
1202
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1203
|
+
for (const task of tasks) {
|
|
1204
|
+
const successors = [];
|
|
1205
|
+
for (const otherTask of tasks) {
|
|
1206
|
+
if (otherTask.dependencies) {
|
|
1207
|
+
for (const dep of otherTask.dependencies) {
|
|
1208
|
+
if (dep.taskId === task.id) {
|
|
1209
|
+
successors.push(otherTask.id);
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
graph.set(task.id, successors);
|
|
1216
|
+
}
|
|
1217
|
+
return graph;
|
|
1218
|
+
}
|
|
1219
|
+
function detectCycles(tasks) {
|
|
1220
|
+
const graph = buildAdjacencyList(tasks);
|
|
1221
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1222
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1223
|
+
const path = [];
|
|
1224
|
+
function dfs(taskId) {
|
|
1225
|
+
if (visiting.has(taskId)) {
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
if (visited.has(taskId)) {
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
visiting.add(taskId);
|
|
1232
|
+
path.push(taskId);
|
|
1233
|
+
const successors = graph.get(taskId) || [];
|
|
1234
|
+
for (const successor of successors) {
|
|
1235
|
+
if (dfs(successor)) {
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
visiting.delete(taskId);
|
|
1240
|
+
path.pop();
|
|
1241
|
+
visited.add(taskId);
|
|
1242
|
+
return false;
|
|
1243
|
+
}
|
|
1244
|
+
for (const task of tasks) {
|
|
1245
|
+
if (dfs(task.id)) {
|
|
1246
|
+
return { hasCycle: true, cyclePath: [...path] };
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return { hasCycle: false };
|
|
1250
|
+
}
|
|
1251
|
+
function validateDependencies(tasks) {
|
|
1252
|
+
const errors = [];
|
|
1253
|
+
const taskIds = new Set(tasks.map((t) => t.id));
|
|
1254
|
+
for (const task of tasks) {
|
|
1255
|
+
if (task.dependencies) {
|
|
1256
|
+
for (const dep of task.dependencies) {
|
|
1257
|
+
if (!taskIds.has(dep.taskId)) {
|
|
1258
|
+
errors.push({
|
|
1259
|
+
type: "missing-task",
|
|
1260
|
+
taskId: task.id,
|
|
1261
|
+
message: `Dependency references non-existent task: ${dep.taskId}`,
|
|
1262
|
+
relatedTaskIds: [dep.taskId]
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
for (const task of tasks) {
|
|
1269
|
+
if (!task.dependencies) continue;
|
|
1270
|
+
for (const dep of task.dependencies) {
|
|
1271
|
+
if (!taskIds.has(dep.taskId)) {
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
if (areTasksHierarchicallyRelated(task.id, dep.taskId, tasks)) {
|
|
1275
|
+
errors.push({
|
|
1276
|
+
type: "constraint",
|
|
1277
|
+
taskId: task.id,
|
|
1278
|
+
message: `Dependencies between parent and child tasks are not allowed: ${dep.taskId} -> ${task.id}`,
|
|
1279
|
+
relatedTaskIds: [dep.taskId, task.id]
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
const cycleResult = detectCycles(tasks);
|
|
1285
|
+
if (cycleResult.hasCycle && cycleResult.cyclePath) {
|
|
1286
|
+
errors.push({
|
|
1287
|
+
type: "cycle",
|
|
1288
|
+
taskId: cycleResult.cyclePath[0],
|
|
1289
|
+
message: "Circular dependency detected",
|
|
1290
|
+
relatedTaskIds: cycleResult.cyclePath
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
isValid: errors.length === 0,
|
|
1295
|
+
errors
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1208
1299
|
// src/utils/hierarchyOrder.ts
|
|
1209
1300
|
init_dateUtils();
|
|
1210
1301
|
function flattenHierarchy(tasks) {
|
|
@@ -1686,7 +1777,6 @@ var isTaskExpired = (task, referenceDate = /* @__PURE__ */ new Date()) => {
|
|
|
1686
1777
|
|
|
1687
1778
|
// src/hooks/useTaskDrag.ts
|
|
1688
1779
|
import { useEffect, useRef, useState, useCallback } from "react";
|
|
1689
|
-
init_dateUtils();
|
|
1690
1780
|
var globalActiveDrag = null;
|
|
1691
1781
|
var globalRafId = null;
|
|
1692
1782
|
function getDayOffsetFromMonthStart(date, monthStart) {
|
|
@@ -1694,62 +1784,6 @@ function getDayOffsetFromMonthStart(date, monthStart) {
|
|
|
1694
1784
|
(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) - Date.UTC(monthStart.getUTCFullYear(), monthStart.getUTCMonth(), monthStart.getUTCDate())) / (24 * 60 * 60 * 1e3)
|
|
1695
1785
|
);
|
|
1696
1786
|
}
|
|
1697
|
-
function resolveDraggedRange(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
|
|
1698
|
-
const dayOffset = Math.round(left / dayWidth);
|
|
1699
|
-
const rawStartDate = new Date(Date.UTC(
|
|
1700
|
-
monthStart.getUTCFullYear(),
|
|
1701
|
-
monthStart.getUTCMonth(),
|
|
1702
|
-
monthStart.getUTCDate() + dayOffset
|
|
1703
|
-
));
|
|
1704
|
-
const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
|
|
1705
|
-
const rawEndDate = new Date(Date.UTC(
|
|
1706
|
-
monthStart.getUTCFullYear(),
|
|
1707
|
-
monthStart.getUTCMonth(),
|
|
1708
|
-
monthStart.getUTCDate() + rawEndOffset
|
|
1709
|
-
));
|
|
1710
|
-
if (!(businessDays && weekendPredicate)) {
|
|
1711
|
-
return { start: rawStartDate, end: rawEndDate };
|
|
1712
|
-
}
|
|
1713
|
-
if (mode === "move") {
|
|
1714
|
-
const originalStart2 = new Date(task.startDate);
|
|
1715
|
-
const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
|
|
1716
|
-
return moveTaskRange(
|
|
1717
|
-
task.startDate,
|
|
1718
|
-
task.endDate,
|
|
1719
|
-
rawStartDate,
|
|
1720
|
-
true,
|
|
1721
|
-
weekendPredicate,
|
|
1722
|
-
snapDirection2
|
|
1723
|
-
);
|
|
1724
|
-
}
|
|
1725
|
-
if (mode === "resize-right") {
|
|
1726
|
-
const fixedStart = new Date(task.startDate);
|
|
1727
|
-
const originalEnd = new Date(task.endDate);
|
|
1728
|
-
const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
|
|
1729
|
-
const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
|
|
1730
|
-
const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
|
|
1731
|
-
return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
|
|
1732
|
-
}
|
|
1733
|
-
const fixedEnd = new Date(task.endDate);
|
|
1734
|
-
const originalStart = new Date(task.startDate);
|
|
1735
|
-
const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
|
|
1736
|
-
const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
|
|
1737
|
-
const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
|
|
1738
|
-
return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
|
|
1739
|
-
}
|
|
1740
|
-
function clampDraggedRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
|
|
1741
|
-
if (mode === "resize-right") {
|
|
1742
|
-
return range;
|
|
1743
|
-
}
|
|
1744
|
-
return clampTaskRangeForIncomingFS(
|
|
1745
|
-
task,
|
|
1746
|
-
range.start,
|
|
1747
|
-
range.end,
|
|
1748
|
-
allTasks,
|
|
1749
|
-
businessDays,
|
|
1750
|
-
weekendPredicate
|
|
1751
|
-
);
|
|
1752
|
-
}
|
|
1753
1787
|
function completeDrag() {
|
|
1754
1788
|
if (globalRafId !== null) {
|
|
1755
1789
|
cancelAnimationFrame(globalRafId);
|
|
@@ -1807,9 +1841,9 @@ function handleGlobalMouseMove(e) {
|
|
|
1807
1841
|
}
|
|
1808
1842
|
const draggedTask = allTasks.find((t) => t.id === activeDrag.taskId);
|
|
1809
1843
|
if (activeDrag.businessDays && activeDrag.weekendPredicate && draggedTask) {
|
|
1810
|
-
const previewRange =
|
|
1844
|
+
const previewRange = clampDateRangeForIncomingFS(
|
|
1811
1845
|
draggedTask,
|
|
1812
|
-
|
|
1846
|
+
resolveDateRangeFromPixels(
|
|
1813
1847
|
mode,
|
|
1814
1848
|
newLeft,
|
|
1815
1849
|
newWidth,
|
|
@@ -1829,9 +1863,9 @@ function handleGlobalMouseMove(e) {
|
|
|
1829
1863
|
newLeft = Math.round(alignedStartDay * dayWidth);
|
|
1830
1864
|
newWidth = Math.round((alignedEndDay - alignedStartDay + 1) * dayWidth);
|
|
1831
1865
|
} else if (draggedTask) {
|
|
1832
|
-
const previewRange =
|
|
1866
|
+
const previewRange = clampDateRangeForIncomingFS(
|
|
1833
1867
|
draggedTask,
|
|
1834
|
-
|
|
1868
|
+
resolveDateRangeFromPixels(
|
|
1835
1869
|
mode,
|
|
1836
1870
|
newLeft,
|
|
1837
1871
|
newWidth,
|
|
@@ -1850,9 +1884,9 @@ function handleGlobalMouseMove(e) {
|
|
|
1850
1884
|
if (!activeDrag.disableConstraints && activeDrag.onCascadeProgress) {
|
|
1851
1885
|
const { dayWidth: dayWidth2, monthStart: mStart, taskId: dragId } = activeDrag;
|
|
1852
1886
|
const originalDraggedTask = draggedTask ?? allTasks.find((t) => t.id === dragId);
|
|
1853
|
-
const previewRange = originalDraggedTask ?
|
|
1887
|
+
const previewRange = originalDraggedTask ? clampDateRangeForIncomingFS(
|
|
1854
1888
|
originalDraggedTask,
|
|
1855
|
-
|
|
1889
|
+
resolveDateRangeFromPixels(
|
|
1856
1890
|
mode,
|
|
1857
1891
|
newLeft,
|
|
1858
1892
|
newWidth,
|
|
@@ -2034,9 +2068,9 @@ var useTaskDrag = (options) => {
|
|
|
2034
2068
|
const wasOwner = isOwnerRef.current;
|
|
2035
2069
|
isOwnerRef.current = false;
|
|
2036
2070
|
const currentTask = allTasks.find((t) => t.id === taskId);
|
|
2037
|
-
const finalRange = currentTask ?
|
|
2071
|
+
const finalRange = currentTask ? clampDateRangeForIncomingFS(
|
|
2038
2072
|
currentTask,
|
|
2039
|
-
|
|
2073
|
+
resolveDateRangeFromPixels(
|
|
2040
2074
|
finalMode,
|
|
2041
2075
|
finalLeft,
|
|
2042
2076
|
finalWidth,
|
|
@@ -3295,6 +3329,20 @@ var DatePicker = ({
|
|
|
3295
3329
|
},
|
|
3296
3330
|
[selectedDate, updateFromDate, businessDays, isWeekend3]
|
|
3297
3331
|
);
|
|
3332
|
+
const handleTriggerKeyDown = useCallback3((e) => {
|
|
3333
|
+
if (disabled) return;
|
|
3334
|
+
if (e.key === "ArrowUp") {
|
|
3335
|
+
e.preventDefault();
|
|
3336
|
+
e.stopPropagation();
|
|
3337
|
+
handleDayShift(1);
|
|
3338
|
+
return;
|
|
3339
|
+
}
|
|
3340
|
+
if (e.key === "ArrowDown") {
|
|
3341
|
+
e.preventDefault();
|
|
3342
|
+
e.stopPropagation();
|
|
3343
|
+
handleDayShift(-1);
|
|
3344
|
+
}
|
|
3345
|
+
}, [disabled, handleDayShift]);
|
|
3298
3346
|
const handleKeyDown = useCallback3((e) => {
|
|
3299
3347
|
if (!dateInputRef.current) return;
|
|
3300
3348
|
const { value: inputVal } = dateInputRef.current;
|
|
@@ -3402,6 +3450,7 @@ var DatePicker = ({
|
|
|
3402
3450
|
type: "button",
|
|
3403
3451
|
className: `gantt-datepicker-trigger${className ? ` ${className}` : ""}`,
|
|
3404
3452
|
disabled,
|
|
3453
|
+
onKeyDown: handleTriggerKeyDown,
|
|
3405
3454
|
onClick: (e) => {
|
|
3406
3455
|
e.stopPropagation();
|
|
3407
3456
|
},
|
|
@@ -3788,7 +3837,7 @@ var DepChip = ({
|
|
|
3788
3837
|
newStart = constraintDate;
|
|
3789
3838
|
if (businessDays) {
|
|
3790
3839
|
const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
|
|
3791
|
-
newEnd =
|
|
3840
|
+
newEnd = addBusinessDays(constraintDate, businessDuration, weekendPredicate);
|
|
3792
3841
|
} else {
|
|
3793
3842
|
newEnd = new Date(constraintDate.getTime() + durationMs);
|
|
3794
3843
|
}
|
|
@@ -3796,7 +3845,7 @@ var DepChip = ({
|
|
|
3796
3845
|
newEnd = constraintDate;
|
|
3797
3846
|
if (businessDays) {
|
|
3798
3847
|
const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
|
|
3799
|
-
newStart =
|
|
3848
|
+
newStart = subtractBusinessDays(constraintDate, businessDuration, weekendPredicate);
|
|
3800
3849
|
} else {
|
|
3801
3850
|
newStart = new Date(constraintDate.getTime() - durationMs);
|
|
3802
3851
|
}
|
|
@@ -4064,7 +4113,7 @@ var TaskListRow = React9.memo(
|
|
|
4064
4113
|
);
|
|
4065
4114
|
const getEndDate = useCallback4(
|
|
4066
4115
|
(start, duration) => {
|
|
4067
|
-
return businessDays ? addBusinessDays(start, duration, weekendPredicate) : getEndDateFromDuration(start, duration);
|
|
4116
|
+
return businessDays ? addBusinessDays(start, duration, weekendPredicate).toISOString().split("T")[0] : getEndDateFromDuration(start, duration);
|
|
4068
4117
|
},
|
|
4069
4118
|
[businessDays, weekendPredicate]
|
|
4070
4119
|
);
|
|
@@ -6603,20 +6652,8 @@ function GanttChartInner(props, ref) {
|
|
|
6603
6652
|
}
|
|
6604
6653
|
return;
|
|
6605
6654
|
}
|
|
6606
|
-
const
|
|
6607
|
-
|
|
6608
|
-
const { startDate: parentStart, endDate: parentEnd } = computeParentDates(updatedTask.id, tasks);
|
|
6609
|
-
const parentWithRecalcDates = {
|
|
6610
|
-
...updatedTask,
|
|
6611
|
-
startDate: parentStart.toISOString().split("T")[0],
|
|
6612
|
-
endDate: parentEnd.toISOString().split("T")[0]
|
|
6613
|
-
};
|
|
6614
|
-
const cascadedTasks = disableConstraints ? [parentWithRecalcDates] : universalCascade(parentWithRecalcDates, parentStart, parentEnd, tasks, businessDays, isCustomWeekend);
|
|
6615
|
-
onTasksChange?.(cascadedTasks);
|
|
6616
|
-
} else {
|
|
6617
|
-
const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
|
|
6618
|
-
onTasksChange?.(cascadedTasks);
|
|
6619
|
-
}
|
|
6655
|
+
const cascadedTasks = disableConstraints ? [updatedTask] : universalCascade(updatedTask, newStart, newEnd, tasks, businessDays, isCustomWeekend);
|
|
6656
|
+
onTasksChange?.(cascadedTasks);
|
|
6620
6657
|
}, [tasks, onTasksChange, disableConstraints, editingTaskId, businessDays, isCustomWeekend]);
|
|
6621
6658
|
const handleDelete = useCallback6((taskId) => {
|
|
6622
6659
|
const toDelete = /* @__PURE__ */ new Set([taskId]);
|
|
@@ -7042,6 +7079,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7042
7079
|
export {
|
|
7043
7080
|
Button,
|
|
7044
7081
|
Calendar,
|
|
7082
|
+
DAY_MS,
|
|
7045
7083
|
DatePicker,
|
|
7046
7084
|
DragGuideLines_default as DragGuideLines,
|
|
7047
7085
|
GanttChart,
|
|
@@ -7054,7 +7092,7 @@ export {
|
|
|
7054
7092
|
TaskRow_default as TaskRow,
|
|
7055
7093
|
TimeScaleHeader_default as TimeScaleHeader,
|
|
7056
7094
|
TodayIndicator_default as TodayIndicator,
|
|
7057
|
-
addBusinessDays,
|
|
7095
|
+
addBusinessDays2 as addBusinessDays,
|
|
7058
7096
|
alignToWorkingDay,
|
|
7059
7097
|
and,
|
|
7060
7098
|
areTasksHierarchicallyRelated,
|
|
@@ -7087,7 +7125,8 @@ export {
|
|
|
7087
7125
|
formatDateRangeLabel,
|
|
7088
7126
|
getAllDependencyEdges,
|
|
7089
7127
|
getAllDescendants,
|
|
7090
|
-
|
|
7128
|
+
getBusinessDayOffset,
|
|
7129
|
+
getBusinessDaysCount2 as getBusinessDaysCount,
|
|
7091
7130
|
getChildren,
|
|
7092
7131
|
getCursorForPosition,
|
|
7093
7132
|
getDayOffset,
|
|
@@ -7114,15 +7153,18 @@ export {
|
|
|
7114
7153
|
normalizeDependencyLag,
|
|
7115
7154
|
normalizeHierarchyTasks,
|
|
7116
7155
|
normalizeTaskDates,
|
|
7156
|
+
normalizeUTCDate,
|
|
7117
7157
|
not,
|
|
7118
7158
|
or,
|
|
7159
|
+
parseDateOnly,
|
|
7119
7160
|
parseUTCDate,
|
|
7120
7161
|
pixelsToDate,
|
|
7121
7162
|
progressInRange,
|
|
7122
7163
|
recalculateIncomingLags,
|
|
7123
7164
|
reflowTasksOnModeSwitch,
|
|
7124
7165
|
removeDependenciesBetweenTasks,
|
|
7125
|
-
|
|
7166
|
+
shiftBusinessDayOffset,
|
|
7167
|
+
subtractBusinessDays2 as subtractBusinessDays,
|
|
7126
7168
|
universalCascade,
|
|
7127
7169
|
useTaskDrag,
|
|
7128
7170
|
validateDependencies,
|