gantt-lib 0.53.1 → 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 +639 -595
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +634 -595
- 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,219 +514,70 @@ 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;
|
|
536
|
-
}
|
|
537
|
-
const predecessorStart = parseDateOnly(predecessor.startDate);
|
|
538
|
-
const predecessorEnd = parseDateOnly(predecessor.endDate);
|
|
539
|
-
const predecessorDuration = getTaskDuration(
|
|
540
|
-
predecessorStart,
|
|
541
|
-
predecessorEnd,
|
|
542
|
-
businessDays,
|
|
543
|
-
weekendPredicate
|
|
544
|
-
);
|
|
545
|
-
const candidateMinStart = calculateSuccessorDate(
|
|
546
|
-
predecessorStart,
|
|
547
|
-
predecessorEnd,
|
|
548
|
-
"FS",
|
|
549
|
-
-predecessorDuration,
|
|
550
|
-
businessDays,
|
|
551
|
-
weekendPredicate
|
|
552
|
-
);
|
|
553
|
-
if (!minAllowedStart || candidateMinStart.getTime() > minAllowedStart.getTime()) {
|
|
554
|
-
minAllowedStart = candidateMinStart;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
if (!minAllowedStart || proposedStart.getTime() >= minAllowedStart.getTime()) {
|
|
558
|
-
return { start: proposedStart, end: proposedEnd };
|
|
559
|
-
}
|
|
560
|
-
return buildTaskRangeFromStart(
|
|
561
|
-
minAllowedStart,
|
|
562
|
-
getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
|
|
563
|
-
businessDays,
|
|
564
|
-
weekendPredicate
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
function parseDateOnly(date) {
|
|
568
|
-
const parsed = typeof date === "string" ? /* @__PURE__ */ new Date(`${date.split("T")[0]}T00:00:00.000Z`) : normalizeUTCDate(date);
|
|
569
|
-
return normalizeUTCDate(parsed);
|
|
570
|
-
}
|
|
571
|
-
function buildAdjacencyList(tasks) {
|
|
572
|
-
const taskMap = new Map(tasks.map((t) => [t.id, t]));
|
|
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);
|
|
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);
|
|
687
581
|
}
|
|
688
582
|
}
|
|
689
583
|
const anchorDate = linkType === "FS" || linkType === "FF" ? predecessorEnd : predecessorStart;
|
|
@@ -700,236 +594,15 @@ function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag
|
|
|
700
594
|
break;
|
|
701
595
|
case "SF":
|
|
702
596
|
offset = normalizedLag - 1;
|
|
703
|
-
break;
|
|
704
|
-
}
|
|
705
|
-
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
706
|
-
}
|
|
707
|
-
function validateDependencies(tasks) {
|
|
708
|
-
const errors = [];
|
|
709
|
-
const taskIds = new Set(tasks.map((t) => t.id));
|
|
710
|
-
for (const task of tasks) {
|
|
711
|
-
if (task.dependencies) {
|
|
712
|
-
for (const dep of task.dependencies) {
|
|
713
|
-
if (!taskIds.has(dep.taskId)) {
|
|
714
|
-
errors.push({
|
|
715
|
-
type: "missing-task",
|
|
716
|
-
taskId: task.id,
|
|
717
|
-
message: `Dependency references non-existent task: ${dep.taskId}`,
|
|
718
|
-
relatedTaskIds: [dep.taskId]
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
for (const task of tasks) {
|
|
725
|
-
if (!task.dependencies) continue;
|
|
726
|
-
for (const dep of task.dependencies) {
|
|
727
|
-
if (!taskIds.has(dep.taskId)) {
|
|
728
|
-
continue;
|
|
729
|
-
}
|
|
730
|
-
if (areTasksHierarchicallyRelated(task.id, dep.taskId, tasks)) {
|
|
731
|
-
errors.push({
|
|
732
|
-
type: "constraint",
|
|
733
|
-
taskId: task.id,
|
|
734
|
-
message: `Dependencies between parent and child tasks are not allowed: ${dep.taskId} -> ${task.id}`,
|
|
735
|
-
relatedTaskIds: [dep.taskId, task.id]
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
const cycleResult = detectCycles(tasks);
|
|
741
|
-
if (cycleResult.hasCycle && cycleResult.cyclePath) {
|
|
742
|
-
errors.push({
|
|
743
|
-
type: "cycle",
|
|
744
|
-
taskId: cycleResult.cyclePath[0],
|
|
745
|
-
message: "Circular dependency detected",
|
|
746
|
-
relatedTaskIds: cycleResult.cyclePath
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
return {
|
|
750
|
-
isValid: errors.length === 0,
|
|
751
|
-
errors
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
755
|
-
const successorMap = /* @__PURE__ */ new Map();
|
|
756
|
-
for (const task of allTasks) {
|
|
757
|
-
successorMap.set(task.id, []);
|
|
758
|
-
}
|
|
759
|
-
for (const task of allTasks) {
|
|
760
|
-
if (!task.dependencies) continue;
|
|
761
|
-
for (const dep of task.dependencies) {
|
|
762
|
-
if (linkTypes.includes(dep.type)) {
|
|
763
|
-
const list = successorMap.get(dep.taskId) ?? [];
|
|
764
|
-
list.push(task.id);
|
|
765
|
-
successorMap.set(dep.taskId, list);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const taskById = new Map(allTasks.map((t) => [t.id, t]));
|
|
770
|
-
const visited = /* @__PURE__ */ new Set();
|
|
771
|
-
const queue = [draggedTaskId];
|
|
772
|
-
const chain = [];
|
|
773
|
-
visited.add(draggedTaskId);
|
|
774
|
-
while (queue.length > 0) {
|
|
775
|
-
const current = queue.shift();
|
|
776
|
-
const successors = successorMap.get(current) ?? [];
|
|
777
|
-
for (const sid of successors) {
|
|
778
|
-
if (!visited.has(sid)) {
|
|
779
|
-
visited.add(sid);
|
|
780
|
-
const t = taskById.get(sid);
|
|
781
|
-
if (t) {
|
|
782
|
-
chain.push(t);
|
|
783
|
-
queue.push(sid);
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
return chain;
|
|
789
|
-
}
|
|
790
|
-
function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
|
|
791
|
-
const taskById = new Map(allTasks.map((t) => [t.id, t]));
|
|
792
|
-
const updatedDates = /* @__PURE__ */ new Map();
|
|
793
|
-
updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
|
|
794
|
-
const result = [];
|
|
795
|
-
const queue = [movedTaskId];
|
|
796
|
-
const visited = /* @__PURE__ */ new Set([movedTaskId]);
|
|
797
|
-
while (queue.length > 0) {
|
|
798
|
-
const currentId = queue.shift();
|
|
799
|
-
const { start: predStart, end: predEnd } = updatedDates.get(currentId);
|
|
800
|
-
if (!skipChildCascade) {
|
|
801
|
-
const children = getChildren(currentId, allTasks);
|
|
802
|
-
for (const child of children) {
|
|
803
|
-
if (visited.has(child.id) || child.locked) continue;
|
|
804
|
-
const origStart = new Date(child.startDate);
|
|
805
|
-
const origEnd = new Date(child.endDate);
|
|
806
|
-
const durationMs = origEnd.getTime() - origStart.getTime();
|
|
807
|
-
const parentOrig = taskById.get(currentId);
|
|
808
|
-
const parentOrigStart = new Date(parentOrig.startDate);
|
|
809
|
-
const parentOrigEnd = new Date(parentOrig.endDate);
|
|
810
|
-
const parentStartDelta = predStart.getTime() - parentOrigStart.getTime();
|
|
811
|
-
const parentEndDelta = predEnd.getTime() - parentOrigEnd.getTime();
|
|
812
|
-
const newChildStart = new Date(origStart.getTime() + parentStartDelta);
|
|
813
|
-
const newChildEnd = new Date(origEnd.getTime() + parentEndDelta);
|
|
814
|
-
visited.add(child.id);
|
|
815
|
-
updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
|
|
816
|
-
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
|
-
}
|
|
597
|
+
break;
|
|
930
598
|
}
|
|
931
|
-
return
|
|
599
|
+
return shiftBusinessDayOffset(anchorDate, offset, weekendPredicate);
|
|
932
600
|
}
|
|
601
|
+
|
|
602
|
+
// src/core/scheduling/cascade.ts
|
|
603
|
+
init_dateMath();
|
|
604
|
+
|
|
605
|
+
// src/core/scheduling/hierarchy.ts
|
|
933
606
|
function getChildren(parentId, tasks) {
|
|
934
607
|
return tasks.filter((t) => t.parentId === parentId);
|
|
935
608
|
}
|
|
@@ -974,6 +647,37 @@ function computeParentProgress(parentId, tasks) {
|
|
|
974
647
|
}
|
|
975
648
|
return Math.round(weightedSum / totalWeight * 10) / 10;
|
|
976
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
|
+
}
|
|
977
681
|
function removeDependenciesBetweenTasks(taskId1, taskId2, tasks) {
|
|
978
682
|
return tasks.map((task) => {
|
|
979
683
|
if (task.id === taskId1 || task.id === taskId2) {
|
|
@@ -1003,36 +707,324 @@ function isAncestorTask(ancestorId, taskId, tasks) {
|
|
|
1003
707
|
if (current.parentId === ancestorId) {
|
|
1004
708
|
return true;
|
|
1005
709
|
}
|
|
1006
|
-
if (visited.has(current.parentId)) {
|
|
1007
|
-
return false;
|
|
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);
|
|
779
|
+
const predecessorDuration = getTaskDuration(
|
|
780
|
+
predecessorStart,
|
|
781
|
+
predecessorEnd,
|
|
782
|
+
businessDays,
|
|
783
|
+
weekendPredicate
|
|
784
|
+
);
|
|
785
|
+
const candidateMinStart = calculateSuccessorDate(
|
|
786
|
+
predecessorStart,
|
|
787
|
+
predecessorEnd,
|
|
788
|
+
"FS",
|
|
789
|
+
-predecessorDuration,
|
|
790
|
+
businessDays,
|
|
791
|
+
weekendPredicate
|
|
792
|
+
);
|
|
793
|
+
if (!minAllowedStart || candidateMinStart.getTime() > minAllowedStart.getTime()) {
|
|
794
|
+
minAllowedStart = candidateMinStart;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!minAllowedStart || proposedStart.getTime() >= minAllowedStart.getTime()) {
|
|
798
|
+
return { start: proposedStart, end: proposedEnd };
|
|
799
|
+
}
|
|
800
|
+
return buildTaskRangeFromStart(
|
|
801
|
+
minAllowedStart,
|
|
802
|
+
getTaskDuration(proposedStart, proposedEnd, businessDays, weekendPredicate),
|
|
803
|
+
businessDays,
|
|
804
|
+
weekendPredicate
|
|
805
|
+
);
|
|
806
|
+
}
|
|
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) };
|
|
813
|
+
}
|
|
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
|
+
});
|
|
827
|
+
}
|
|
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 };
|
|
843
|
+
}
|
|
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
|
+
);
|
|
855
|
+
}
|
|
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);
|
|
863
|
+
}
|
|
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
|
+
);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/core/scheduling/cascade.ts
|
|
886
|
+
function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
|
|
887
|
+
const successorMap = /* @__PURE__ */ new Map();
|
|
888
|
+
for (const task of allTasks) {
|
|
889
|
+
successorMap.set(task.id, []);
|
|
890
|
+
}
|
|
891
|
+
for (const task of allTasks) {
|
|
892
|
+
if (!task.dependencies) continue;
|
|
893
|
+
for (const dep of task.dependencies) {
|
|
894
|
+
if (linkTypes.includes(dep.type)) {
|
|
895
|
+
const list = successorMap.get(dep.taskId) ?? [];
|
|
896
|
+
list.push(task.id);
|
|
897
|
+
successorMap.set(dep.taskId, list);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const taskById = new Map(allTasks.map((t) => [t.id, t]));
|
|
902
|
+
const visited = /* @__PURE__ */ new Set();
|
|
903
|
+
const queue = [draggedTaskId];
|
|
904
|
+
const chain = [];
|
|
905
|
+
visited.add(draggedTaskId);
|
|
906
|
+
while (queue.length > 0) {
|
|
907
|
+
const current = queue.shift();
|
|
908
|
+
const successors = successorMap.get(current) ?? [];
|
|
909
|
+
for (const sid of successors) {
|
|
910
|
+
if (!visited.has(sid)) {
|
|
911
|
+
visited.add(sid);
|
|
912
|
+
const t = taskById.get(sid);
|
|
913
|
+
if (t) {
|
|
914
|
+
chain.push(t);
|
|
915
|
+
queue.push(sid);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return chain;
|
|
921
|
+
}
|
|
922
|
+
function cascadeByLinks(movedTaskId, newStart, newEnd, allTasks, skipChildCascade = false) {
|
|
923
|
+
const taskById = new Map(allTasks.map((t) => [t.id, t]));
|
|
924
|
+
const updatedDates = /* @__PURE__ */ new Map();
|
|
925
|
+
updatedDates.set(movedTaskId, { start: newStart, end: newEnd });
|
|
926
|
+
const result = [];
|
|
927
|
+
const queue = [movedTaskId];
|
|
928
|
+
const visited = /* @__PURE__ */ new Set([movedTaskId]);
|
|
929
|
+
while (queue.length > 0) {
|
|
930
|
+
const currentId = queue.shift();
|
|
931
|
+
const { start: predStart, end: predEnd } = updatedDates.get(currentId);
|
|
932
|
+
if (!skipChildCascade) {
|
|
933
|
+
const children = getChildren(currentId, allTasks);
|
|
934
|
+
for (const child of children) {
|
|
935
|
+
if (visited.has(child.id) || child.locked) continue;
|
|
936
|
+
const origStart = new Date(child.startDate);
|
|
937
|
+
const origEnd = new Date(child.endDate);
|
|
938
|
+
const durationMs = origEnd.getTime() - origStart.getTime();
|
|
939
|
+
const parentOrig = taskById.get(currentId);
|
|
940
|
+
const parentOrigStart = new Date(parentOrig.startDate);
|
|
941
|
+
const parentOrigEnd = new Date(parentOrig.endDate);
|
|
942
|
+
const parentStartDelta = predStart.getTime() - parentOrigStart.getTime();
|
|
943
|
+
const parentEndDelta = predEnd.getTime() - parentOrigEnd.getTime();
|
|
944
|
+
const newChildStart = new Date(origStart.getTime() + parentStartDelta);
|
|
945
|
+
const newChildEnd = new Date(origEnd.getTime() + parentEndDelta);
|
|
946
|
+
visited.add(child.id);
|
|
947
|
+
updatedDates.set(child.id, { start: newChildStart, end: newChildEnd });
|
|
948
|
+
result.push({
|
|
949
|
+
...child,
|
|
950
|
+
startDate: newChildStart.toISOString().split("T")[0],
|
|
951
|
+
endDate: newChildEnd.toISOString().split("T")[0]
|
|
952
|
+
});
|
|
953
|
+
queue.push(child.id);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
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,
|
|
@@ -3803,7 +3837,7 @@ var DepChip = ({
|
|
|
3803
3837
|
newStart = constraintDate;
|
|
3804
3838
|
if (businessDays) {
|
|
3805
3839
|
const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
|
|
3806
|
-
newEnd =
|
|
3840
|
+
newEnd = addBusinessDays(constraintDate, businessDuration, weekendPredicate);
|
|
3807
3841
|
} else {
|
|
3808
3842
|
newEnd = new Date(constraintDate.getTime() + durationMs);
|
|
3809
3843
|
}
|
|
@@ -3811,7 +3845,7 @@ var DepChip = ({
|
|
|
3811
3845
|
newEnd = constraintDate;
|
|
3812
3846
|
if (businessDays) {
|
|
3813
3847
|
const businessDuration = getBusinessDaysCount(origStart, origEnd, weekendPredicate);
|
|
3814
|
-
newStart =
|
|
3848
|
+
newStart = subtractBusinessDays(constraintDate, businessDuration, weekendPredicate);
|
|
3815
3849
|
} else {
|
|
3816
3850
|
newStart = new Date(constraintDate.getTime() - durationMs);
|
|
3817
3851
|
}
|
|
@@ -4079,7 +4113,7 @@ var TaskListRow = React9.memo(
|
|
|
4079
4113
|
);
|
|
4080
4114
|
const getEndDate = useCallback4(
|
|
4081
4115
|
(start, duration) => {
|
|
4082
|
-
return businessDays ? addBusinessDays(start, duration, weekendPredicate) : getEndDateFromDuration(start, duration);
|
|
4116
|
+
return businessDays ? addBusinessDays(start, duration, weekendPredicate).toISOString().split("T")[0] : getEndDateFromDuration(start, duration);
|
|
4083
4117
|
},
|
|
4084
4118
|
[businessDays, weekendPredicate]
|
|
4085
4119
|
);
|
|
@@ -7045,6 +7079,7 @@ var nameContains = (substring, caseSensitive = false) => (task) => {
|
|
|
7045
7079
|
export {
|
|
7046
7080
|
Button,
|
|
7047
7081
|
Calendar,
|
|
7082
|
+
DAY_MS,
|
|
7048
7083
|
DatePicker,
|
|
7049
7084
|
DragGuideLines_default as DragGuideLines,
|
|
7050
7085
|
GanttChart,
|
|
@@ -7057,7 +7092,7 @@ export {
|
|
|
7057
7092
|
TaskRow_default as TaskRow,
|
|
7058
7093
|
TimeScaleHeader_default as TimeScaleHeader,
|
|
7059
7094
|
TodayIndicator_default as TodayIndicator,
|
|
7060
|
-
addBusinessDays,
|
|
7095
|
+
addBusinessDays2 as addBusinessDays,
|
|
7061
7096
|
alignToWorkingDay,
|
|
7062
7097
|
and,
|
|
7063
7098
|
areTasksHierarchicallyRelated,
|
|
@@ -7090,7 +7125,8 @@ export {
|
|
|
7090
7125
|
formatDateRangeLabel,
|
|
7091
7126
|
getAllDependencyEdges,
|
|
7092
7127
|
getAllDescendants,
|
|
7093
|
-
|
|
7128
|
+
getBusinessDayOffset,
|
|
7129
|
+
getBusinessDaysCount2 as getBusinessDaysCount,
|
|
7094
7130
|
getChildren,
|
|
7095
7131
|
getCursorForPosition,
|
|
7096
7132
|
getDayOffset,
|
|
@@ -7117,15 +7153,18 @@ export {
|
|
|
7117
7153
|
normalizeDependencyLag,
|
|
7118
7154
|
normalizeHierarchyTasks,
|
|
7119
7155
|
normalizeTaskDates,
|
|
7156
|
+
normalizeUTCDate,
|
|
7120
7157
|
not,
|
|
7121
7158
|
or,
|
|
7159
|
+
parseDateOnly,
|
|
7122
7160
|
parseUTCDate,
|
|
7123
7161
|
pixelsToDate,
|
|
7124
7162
|
progressInRange,
|
|
7125
7163
|
recalculateIncomingLags,
|
|
7126
7164
|
reflowTasksOnModeSwitch,
|
|
7127
7165
|
removeDependenciesBetweenTasks,
|
|
7128
|
-
|
|
7166
|
+
shiftBusinessDayOffset,
|
|
7167
|
+
subtractBusinessDays2 as subtractBusinessDays,
|
|
7129
7168
|
universalCascade,
|
|
7130
7169
|
useTaskDrag,
|
|
7131
7170
|
validateDependencies,
|