gantt-lib 0.1.2 → 0.3.1
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.css.map +1 -1
- package/dist/index.d.mts +42 -8
- package/dist/index.d.ts +42 -8
- package/dist/index.js +679 -196
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +695 -213
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +273 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/components/GanttChart/GanttChart.tsx
|
|
4
|
-
import { useMemo as
|
|
4
|
+
import { useMemo as useMemo9, useCallback as useCallback6, useRef as useRef5, useState as useState6, useEffect as useEffect5, useImperativeHandle, forwardRef } from "react";
|
|
5
5
|
|
|
6
6
|
// src/utils/dateUtils.ts
|
|
7
7
|
var parseUTCDate = (date) => {
|
|
@@ -195,11 +195,35 @@ function detectCycles(tasks) {
|
|
|
195
195
|
}
|
|
196
196
|
return { hasCycle: false };
|
|
197
197
|
}
|
|
198
|
+
function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd) {
|
|
199
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
200
|
+
const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
|
|
201
|
+
const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
|
|
202
|
+
const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
|
|
203
|
+
const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
|
|
204
|
+
switch (linkType) {
|
|
205
|
+
case "FS":
|
|
206
|
+
return Math.round((sS - pE) / DAY_MS) - 1;
|
|
207
|
+
case "SS":
|
|
208
|
+
return Math.round((sS - pS) / DAY_MS);
|
|
209
|
+
case "FF":
|
|
210
|
+
return Math.round((sE - pE) / DAY_MS);
|
|
211
|
+
case "SF":
|
|
212
|
+
return Math.round((sE - pS) / DAY_MS) + 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
198
215
|
function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0) {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
217
|
+
switch (linkType) {
|
|
218
|
+
case "FS":
|
|
219
|
+
return new Date(predecessorEnd.getTime() + (lag + 1) * DAY_MS);
|
|
220
|
+
case "SS":
|
|
221
|
+
return new Date(predecessorStart.getTime() + lag * DAY_MS);
|
|
222
|
+
case "FF":
|
|
223
|
+
return new Date(predecessorEnd.getTime() + lag * DAY_MS);
|
|
224
|
+
case "SF":
|
|
225
|
+
return new Date(predecessorStart.getTime() + (lag - 1) * DAY_MS);
|
|
226
|
+
}
|
|
203
227
|
}
|
|
204
228
|
function validateDependencies(tasks) {
|
|
205
229
|
const errors = [];
|
|
@@ -346,35 +370,10 @@ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks) {
|
|
|
346
370
|
return task.dependencies.map((dep) => {
|
|
347
371
|
const predecessor = taskById.get(dep.taskId);
|
|
348
372
|
if (!predecessor) return dep;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
);
|
|
354
|
-
return { ...dep, lag: lagDays };
|
|
355
|
-
}
|
|
356
|
-
if (dep.type === "SS") {
|
|
357
|
-
const predStart = new Date(predecessor.startDate);
|
|
358
|
-
const lagDays = Math.max(0, Math.round(
|
|
359
|
-
(Date.UTC(newStartDate.getUTCFullYear(), newStartDate.getUTCMonth(), newStartDate.getUTCDate()) - Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate())) / (24 * 60 * 60 * 1e3)
|
|
360
|
-
));
|
|
361
|
-
return { ...dep, lag: lagDays };
|
|
362
|
-
}
|
|
363
|
-
if (dep.type === "FF") {
|
|
364
|
-
const predEnd = new Date(predecessor.endDate);
|
|
365
|
-
const lagDays = Math.round(
|
|
366
|
-
(Date.UTC(newEndDate.getUTCFullYear(), newEndDate.getUTCMonth(), newEndDate.getUTCDate()) - Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate())) / (24 * 60 * 60 * 1e3)
|
|
367
|
-
);
|
|
368
|
-
return { ...dep, lag: lagDays };
|
|
369
|
-
}
|
|
370
|
-
if (dep.type === "SF") {
|
|
371
|
-
const predStart = new Date(predecessor.startDate);
|
|
372
|
-
const lagDays = Math.min(0, Math.round(
|
|
373
|
-
(Date.UTC(newEndDate.getUTCFullYear(), newEndDate.getUTCMonth(), newEndDate.getUTCDate()) - Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate()) + 24 * 60 * 60 * 1e3) / (24 * 60 * 60 * 1e3)
|
|
374
|
-
));
|
|
375
|
-
return { ...dep, lag: lagDays };
|
|
376
|
-
}
|
|
377
|
-
return dep;
|
|
373
|
+
const predStart = new Date(predecessor.startDate);
|
|
374
|
+
const predEnd = new Date(predecessor.endDate);
|
|
375
|
+
const lagDays = computeLagFromDates(dep.type, predStart, predEnd, newStartDate, newEndDate);
|
|
376
|
+
return { ...dep, lag: lagDays };
|
|
378
377
|
});
|
|
379
378
|
}
|
|
380
379
|
function getAllDependencyEdges(tasks) {
|
|
@@ -1093,29 +1092,51 @@ var useTaskDrag = (options) => {
|
|
|
1093
1092
|
// src/components/TaskRow/TaskRow.tsx
|
|
1094
1093
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1095
1094
|
var arePropsEqual = (prevProps, nextProps) => {
|
|
1096
|
-
return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.task.locked === nextProps.task.locked && prevProps.task.divider === nextProps.task.divider;
|
|
1095
|
+
return prevProps.task.id === nextProps.task.id && prevProps.task.name === nextProps.task.name && prevProps.task.startDate === nextProps.task.startDate && prevProps.task.endDate === nextProps.task.endDate && prevProps.task.color === nextProps.task.color && prevProps.task.progress === nextProps.task.progress && prevProps.task.accepted === nextProps.task.accepted && prevProps.monthStart.getTime() === nextProps.monthStart.getTime() && prevProps.dayWidth === nextProps.dayWidth && prevProps.rowHeight === nextProps.rowHeight && prevProps.overridePosition?.left === nextProps.overridePosition?.left && prevProps.overridePosition?.width === nextProps.overridePosition?.width && prevProps.allTasks === nextProps.allTasks && prevProps.disableConstraints === nextProps.disableConstraints && prevProps.task.locked === nextProps.task.locked && prevProps.task.divider === nextProps.task.divider && prevProps.highlightExpiredTasks === nextProps.highlightExpiredTasks;
|
|
1097
1096
|
};
|
|
1098
1097
|
var TaskRow = React2.memo(
|
|
1099
|
-
({ task, monthStart, dayWidth, rowHeight, onChange, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, overridePosition, onCascadeProgress, onCascade, divider }) => {
|
|
1098
|
+
({ task, monthStart, dayWidth, rowHeight, onChange, onDragStateChange, rowIndex, allTasks, enableAutoSchedule, disableConstraints, overridePosition, onCascadeProgress, onCascade, divider, highlightExpiredTasks }) => {
|
|
1100
1099
|
const { divider: taskDivider } = task;
|
|
1101
1100
|
const taskStartDate = useMemo2(() => parseUTCDate(task.startDate), [task.startDate]);
|
|
1102
1101
|
const taskEndDate = useMemo2(() => parseUTCDate(task.endDate), [task.endDate]);
|
|
1102
|
+
const isExpired = useMemo2(() => {
|
|
1103
|
+
if (!highlightExpiredTasks) return false;
|
|
1104
|
+
const now = /* @__PURE__ */ new Date();
|
|
1105
|
+
const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
1106
|
+
const tomorrow = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() + 1));
|
|
1107
|
+
const taskStart = parseUTCDate(task.startDate);
|
|
1108
|
+
const taskEnd = parseUTCDate(task.endDate);
|
|
1109
|
+
const actualProgress = task.progress ?? 0;
|
|
1110
|
+
if (actualProgress >= 100) {
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
const msPerDay = 1e3 * 60 * 60 * 24;
|
|
1114
|
+
const taskDuration = taskEnd.getTime() - taskStart.getTime() + msPerDay;
|
|
1115
|
+
const isTaskEndingTodayOrTomorrow = today.getUTCFullYear() === taskEnd.getUTCFullYear() && today.getUTCMonth() === taskEnd.getUTCMonth() && today.getUTCDate() === taskEnd.getUTCDate() || tomorrow.getUTCFullYear() === taskEnd.getUTCFullYear() && tomorrow.getUTCMonth() === taskEnd.getUTCMonth() && tomorrow.getUTCDate() === taskEnd.getUTCDate();
|
|
1116
|
+
const elapsedCutoff = isTaskEndingTodayOrTomorrow ? new Date(today.getTime() - msPerDay) : today;
|
|
1117
|
+
const daysFromStart = elapsedCutoff.getTime() - taskStart.getTime();
|
|
1118
|
+
const todayPosition = Math.min(100, Math.max(0, daysFromStart / taskDuration * 100));
|
|
1119
|
+
return actualProgress < todayPosition;
|
|
1120
|
+
}, [task.startDate, task.endDate, task.progress, highlightExpiredTasks]);
|
|
1103
1121
|
const { left, width } = useMemo2(
|
|
1104
1122
|
() => calculateTaskBar(taskStartDate, taskEndDate, monthStart, dayWidth),
|
|
1105
1123
|
[taskStartDate, taskEndDate, monthStart, dayWidth]
|
|
1106
1124
|
);
|
|
1107
|
-
const barColor = task.color || "var(--gantt-task-bar-default-color)";
|
|
1125
|
+
const barColor = isExpired ? "var(--gantt-expired-color)" : task.color || "var(--gantt-task-bar-default-color)";
|
|
1108
1126
|
const progressWidth = useMemo2(() => {
|
|
1109
1127
|
if (task.progress === void 0 || task.progress <= 0) return 0;
|
|
1110
1128
|
return Math.min(100, Math.max(0, Math.round(task.progress)));
|
|
1111
1129
|
}, [task.progress]);
|
|
1112
1130
|
const progressColor = useMemo2(() => {
|
|
1131
|
+
if (isExpired) {
|
|
1132
|
+
return "color-mix(in srgb, var(--gantt-expired-color) 40%, black)";
|
|
1133
|
+
}
|
|
1113
1134
|
if (progressWidth === 100) {
|
|
1114
1135
|
return task.accepted ? "var(--gantt-progress-accepted, #22c55e)" : "var(--gantt-progress-completed, #fbbf24)";
|
|
1115
1136
|
}
|
|
1116
1137
|
const baseColor = task.color || "var(--gantt-task-bar-default-color)";
|
|
1117
1138
|
return `color-mix(in srgb, ${baseColor} 40%, black)`;
|
|
1118
|
-
}, [progressWidth, task.accepted, task.color]);
|
|
1139
|
+
}, [isExpired, progressWidth, task.accepted, task.color]);
|
|
1119
1140
|
const handleDragEnd = (result) => {
|
|
1120
1141
|
const updatedTask = {
|
|
1121
1142
|
...task,
|
|
@@ -1401,60 +1422,11 @@ var DragGuideLines_default = DragGuideLines;
|
|
|
1401
1422
|
import React5, { useMemo as useMemo5 } from "react";
|
|
1402
1423
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1403
1424
|
function calculateEffectiveLag(edge, predPosition, succPosition, monthStart, dayWidth) {
|
|
1404
|
-
const
|
|
1405
|
-
const
|
|
1406
|
-
const
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1409
|
-
switch (edge.type) {
|
|
1410
|
-
case "FS":
|
|
1411
|
-
lagMs = Date.UTC(
|
|
1412
|
-
succStartDate.getUTCFullYear(),
|
|
1413
|
-
succStartDate.getUTCMonth(),
|
|
1414
|
-
succStartDate.getUTCDate()
|
|
1415
|
-
) - Date.UTC(
|
|
1416
|
-
predEndDate.getUTCFullYear(),
|
|
1417
|
-
predEndDate.getUTCMonth(),
|
|
1418
|
-
predEndDate.getUTCDate()
|
|
1419
|
-
) - 24 * 60 * 60 * 1e3;
|
|
1420
|
-
break;
|
|
1421
|
-
case "SS":
|
|
1422
|
-
lagMs = Date.UTC(
|
|
1423
|
-
succStartDate.getUTCFullYear(),
|
|
1424
|
-
succStartDate.getUTCMonth(),
|
|
1425
|
-
succStartDate.getUTCDate()
|
|
1426
|
-
) - Date.UTC(
|
|
1427
|
-
predStartDate.getUTCFullYear(),
|
|
1428
|
-
predStartDate.getUTCMonth(),
|
|
1429
|
-
predStartDate.getUTCDate()
|
|
1430
|
-
);
|
|
1431
|
-
break;
|
|
1432
|
-
case "FF":
|
|
1433
|
-
lagMs = Date.UTC(
|
|
1434
|
-
succEndDate.getUTCFullYear(),
|
|
1435
|
-
succEndDate.getUTCMonth(),
|
|
1436
|
-
succEndDate.getUTCDate()
|
|
1437
|
-
) - Date.UTC(
|
|
1438
|
-
predEndDate.getUTCFullYear(),
|
|
1439
|
-
predEndDate.getUTCMonth(),
|
|
1440
|
-
predEndDate.getUTCDate()
|
|
1441
|
-
);
|
|
1442
|
-
break;
|
|
1443
|
-
case "SF":
|
|
1444
|
-
lagMs = Date.UTC(
|
|
1445
|
-
succEndDate.getUTCFullYear(),
|
|
1446
|
-
succEndDate.getUTCMonth(),
|
|
1447
|
-
succEndDate.getUTCDate()
|
|
1448
|
-
) - Date.UTC(
|
|
1449
|
-
predStartDate.getUTCFullYear(),
|
|
1450
|
-
predStartDate.getUTCMonth(),
|
|
1451
|
-
predStartDate.getUTCDate()
|
|
1452
|
-
) + 24 * 60 * 60 * 1e3;
|
|
1453
|
-
break;
|
|
1454
|
-
default:
|
|
1455
|
-
return 0;
|
|
1456
|
-
}
|
|
1457
|
-
return Math.round(lagMs / (24 * 60 * 60 * 1e3));
|
|
1425
|
+
const predStart = pixelsToDate(predPosition.left, monthStart, dayWidth);
|
|
1426
|
+
const predEnd = pixelsToDate(predPosition.right - dayWidth, monthStart, dayWidth);
|
|
1427
|
+
const succStart = pixelsToDate(succPosition.left, monthStart, dayWidth);
|
|
1428
|
+
const succEnd = pixelsToDate(succPosition.right - dayWidth, monthStart, dayWidth);
|
|
1429
|
+
return computeLagFromDates(edge.type, predStart, predEnd, succStart, succEnd);
|
|
1458
1430
|
}
|
|
1459
1431
|
var DependencyLines = React5.memo(({
|
|
1460
1432
|
tasks,
|
|
@@ -1462,7 +1434,8 @@ var DependencyLines = React5.memo(({
|
|
|
1462
1434
|
dayWidth,
|
|
1463
1435
|
rowHeight,
|
|
1464
1436
|
gridWidth,
|
|
1465
|
-
dragOverrides
|
|
1437
|
+
dragOverrides,
|
|
1438
|
+
selectedDep
|
|
1466
1439
|
}) => {
|
|
1467
1440
|
const { taskPositions, taskIndices } = useMemo5(() => {
|
|
1468
1441
|
const positions = /* @__PURE__ */ new Map();
|
|
@@ -1576,30 +1549,60 @@ var DependencyLines = React5.memo(({
|
|
|
1576
1549
|
}
|
|
1577
1550
|
)
|
|
1578
1551
|
}
|
|
1579
|
-
)
|
|
1580
|
-
] }),
|
|
1581
|
-
lines.map(({ id, path, hasCycle, lag, fromX, toX, fromY, reverseOrder }) => /* @__PURE__ */ jsxs5(React5.Fragment, { children: [
|
|
1582
|
-
/* @__PURE__ */ jsx6(
|
|
1583
|
-
"path",
|
|
1584
|
-
{
|
|
1585
|
-
d: path,
|
|
1586
|
-
className: hasCycle ? "gantt-dependency-path gantt-dependency-cycle" : "gantt-dependency-path",
|
|
1587
|
-
markerEnd: hasCycle ? "url(#arrowhead-cycle)" : "url(#arrowhead)"
|
|
1588
|
-
}
|
|
1589
1552
|
),
|
|
1590
|
-
|
|
1591
|
-
"
|
|
1553
|
+
/* @__PURE__ */ jsx6(
|
|
1554
|
+
"marker",
|
|
1592
1555
|
{
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1556
|
+
id: "arrowhead-selected",
|
|
1557
|
+
markerWidth: "8",
|
|
1558
|
+
markerHeight: "6",
|
|
1559
|
+
markerUnits: "userSpaceOnUse",
|
|
1560
|
+
refX: "7",
|
|
1561
|
+
refY: "3",
|
|
1562
|
+
orient: "auto",
|
|
1563
|
+
children: /* @__PURE__ */ jsx6(
|
|
1564
|
+
"polygon",
|
|
1565
|
+
{
|
|
1566
|
+
points: "0 0, 8 3, 0 6",
|
|
1567
|
+
fill: "#ef4444"
|
|
1568
|
+
}
|
|
1569
|
+
)
|
|
1600
1570
|
}
|
|
1601
1571
|
)
|
|
1602
|
-
] },
|
|
1572
|
+
] }),
|
|
1573
|
+
lines.map(({ id, path, hasCycle, lag, fromX, toX, fromY, reverseOrder }) => {
|
|
1574
|
+
const isSelected = selectedDep != null && id === `${selectedDep.predecessorId}-${selectedDep.successorId}-${selectedDep.linkType}`;
|
|
1575
|
+
let pathClassName = "gantt-dependency-path";
|
|
1576
|
+
if (isSelected) pathClassName += " gantt-dependency-selected";
|
|
1577
|
+
else if (hasCycle) pathClassName += " gantt-dependency-cycle";
|
|
1578
|
+
let markerEnd;
|
|
1579
|
+
if (isSelected) markerEnd = "url(#arrowhead-selected)";
|
|
1580
|
+
else if (hasCycle) markerEnd = "url(#arrowhead-cycle)";
|
|
1581
|
+
else markerEnd = "url(#arrowhead)";
|
|
1582
|
+
const lagColor = isSelected ? "#ef4444" : hasCycle ? "var(--gantt-dependency-cycle-color, #ef4444)" : "var(--gantt-dependency-line-color, #666666)";
|
|
1583
|
+
return /* @__PURE__ */ jsxs5(React5.Fragment, { children: [
|
|
1584
|
+
/* @__PURE__ */ jsx6(
|
|
1585
|
+
"path",
|
|
1586
|
+
{
|
|
1587
|
+
d: path,
|
|
1588
|
+
className: pathClassName,
|
|
1589
|
+
markerEnd
|
|
1590
|
+
}
|
|
1591
|
+
),
|
|
1592
|
+
lag !== 0 && /* @__PURE__ */ jsx6(
|
|
1593
|
+
"text",
|
|
1594
|
+
{
|
|
1595
|
+
className: "gantt-dependency-lag-label",
|
|
1596
|
+
x: lag < 0 ? toX + 14 : toX - 14,
|
|
1597
|
+
y: reverseOrder ? fromY - 4 : fromY + 12,
|
|
1598
|
+
textAnchor: "middle",
|
|
1599
|
+
fontSize: "10",
|
|
1600
|
+
fill: lagColor,
|
|
1601
|
+
children: lag > 0 ? `+${lag}` : `${lag}`
|
|
1602
|
+
}
|
|
1603
|
+
)
|
|
1604
|
+
] }, id);
|
|
1605
|
+
})
|
|
1603
1606
|
]
|
|
1604
1607
|
}
|
|
1605
1608
|
);
|
|
@@ -1608,17 +1611,51 @@ DependencyLines.displayName = "DependencyLines";
|
|
|
1608
1611
|
var DependencyLines_default = DependencyLines;
|
|
1609
1612
|
|
|
1610
1613
|
// src/components/TaskList/TaskList.tsx
|
|
1611
|
-
import { useMemo as
|
|
1614
|
+
import React10, { useMemo as useMemo8, useCallback as useCallback5, useState as useState5, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
1615
|
+
|
|
1616
|
+
// src/components/ui/Popover.tsx
|
|
1617
|
+
import * as RadixPopover from "@radix-ui/react-popover";
|
|
1618
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1619
|
+
var Popover = ({ open, onOpenChange, children }) => {
|
|
1620
|
+
return /* @__PURE__ */ jsx7(RadixPopover.Root, { open, onOpenChange, children });
|
|
1621
|
+
};
|
|
1622
|
+
var PopoverTrigger = RadixPopover.Trigger;
|
|
1623
|
+
var PopoverContent = ({
|
|
1624
|
+
children,
|
|
1625
|
+
className,
|
|
1626
|
+
align = "start",
|
|
1627
|
+
side = "bottom",
|
|
1628
|
+
portal = true,
|
|
1629
|
+
collisionPadding = 8,
|
|
1630
|
+
onInteractOutside
|
|
1631
|
+
}) => {
|
|
1632
|
+
const content = /* @__PURE__ */ jsx7(
|
|
1633
|
+
RadixPopover.Content,
|
|
1634
|
+
{
|
|
1635
|
+
className: `gantt-popover${className ? ` ${className}` : ""}`,
|
|
1636
|
+
align,
|
|
1637
|
+
side,
|
|
1638
|
+
collisionPadding,
|
|
1639
|
+
sideOffset: 4,
|
|
1640
|
+
onInteractOutside,
|
|
1641
|
+
children
|
|
1642
|
+
}
|
|
1643
|
+
);
|
|
1644
|
+
if (portal) {
|
|
1645
|
+
return /* @__PURE__ */ jsx7(RadixPopover.Portal, { children: content });
|
|
1646
|
+
}
|
|
1647
|
+
return content;
|
|
1648
|
+
};
|
|
1612
1649
|
|
|
1613
1650
|
// src/components/TaskList/TaskListRow.tsx
|
|
1614
|
-
import React9, { useState as useState4, useRef as useRef3, useEffect as useEffect3, useCallback as useCallback4 } from "react";
|
|
1651
|
+
import React9, { useState as useState4, useRef as useRef3, useEffect as useEffect3, useCallback as useCallback4, useMemo as useMemo7 } from "react";
|
|
1615
1652
|
|
|
1616
1653
|
// src/components/ui/Input.tsx
|
|
1617
1654
|
import React6 from "react";
|
|
1618
|
-
import { jsx as
|
|
1655
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1619
1656
|
var Input = React6.forwardRef(
|
|
1620
1657
|
({ className, ...props }, ref) => {
|
|
1621
|
-
return /* @__PURE__ */
|
|
1658
|
+
return /* @__PURE__ */ jsx8(
|
|
1622
1659
|
"input",
|
|
1623
1660
|
{
|
|
1624
1661
|
ref,
|
|
@@ -1656,7 +1693,7 @@ import {
|
|
|
1656
1693
|
startOfDay
|
|
1657
1694
|
} from "date-fns";
|
|
1658
1695
|
import { ru as ru2 } from "date-fns/locale";
|
|
1659
|
-
import { jsx as
|
|
1696
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1660
1697
|
function getDayClassName(day, selected) {
|
|
1661
1698
|
const classes = ["gantt-day-btn"];
|
|
1662
1699
|
if (selected && isSameDay(day, selected)) classes.push("selected");
|
|
@@ -1726,12 +1763,12 @@ var Calendar = ({
|
|
|
1726
1763
|
const emptyDays = (getDay(firstDay) + 6) % 7;
|
|
1727
1764
|
const monthKey = format2(month, "yyyy-MM");
|
|
1728
1765
|
const monthLabel = format2(month, "LLLL yyyy", { locale: ru2 });
|
|
1729
|
-
const emptyCells = Array.from({ length: emptyDays }, (_, i) => /* @__PURE__ */
|
|
1766
|
+
const emptyCells = Array.from({ length: emptyDays }, (_, i) => /* @__PURE__ */ jsx9("div", { className: "gantt-cal-empty-day" }, `e-${i}`));
|
|
1730
1767
|
const dayCells = Array.from({ length: totalDays }, (_, i) => {
|
|
1731
1768
|
const dayNum = i + 1;
|
|
1732
1769
|
const day = new Date(month.getFullYear(), month.getMonth(), dayNum);
|
|
1733
1770
|
const className = getDayClassName(day, selected);
|
|
1734
|
-
return /* @__PURE__ */
|
|
1771
|
+
return /* @__PURE__ */ jsx9(
|
|
1735
1772
|
"button",
|
|
1736
1773
|
{
|
|
1737
1774
|
type: "button",
|
|
@@ -1748,7 +1785,7 @@ var Calendar = ({
|
|
|
1748
1785
|
);
|
|
1749
1786
|
});
|
|
1750
1787
|
return /* @__PURE__ */ jsxs6("div", { className: "gantt-cal-month", "data-month": monthKey, children: [
|
|
1751
|
-
/* @__PURE__ */
|
|
1788
|
+
/* @__PURE__ */ jsx9("div", { className: "gantt-cal-month-header", children: monthLabel }),
|
|
1752
1789
|
/* @__PURE__ */ jsxs6("div", { className: "gantt-cal-month-days", children: [
|
|
1753
1790
|
emptyCells,
|
|
1754
1791
|
dayCells
|
|
@@ -1761,42 +1798,10 @@ var Calendar = ({
|
|
|
1761
1798
|
() => months.map(renderMonth),
|
|
1762
1799
|
[months, renderMonth]
|
|
1763
1800
|
);
|
|
1764
|
-
return /* @__PURE__ */
|
|
1801
|
+
return /* @__PURE__ */ jsx9("div", { ref: scrollRef, className: "gantt-cal-container", children: renderedMonths });
|
|
1765
1802
|
};
|
|
1766
1803
|
Calendar.displayName = "Calendar";
|
|
1767
1804
|
|
|
1768
|
-
// src/components/ui/Popover.tsx
|
|
1769
|
-
import * as RadixPopover from "@radix-ui/react-popover";
|
|
1770
|
-
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1771
|
-
var Popover = ({ open, onOpenChange, children }) => {
|
|
1772
|
-
return /* @__PURE__ */ jsx9(RadixPopover.Root, { open, onOpenChange, children });
|
|
1773
|
-
};
|
|
1774
|
-
var PopoverTrigger = RadixPopover.Trigger;
|
|
1775
|
-
var PopoverContent = ({
|
|
1776
|
-
children,
|
|
1777
|
-
className,
|
|
1778
|
-
align = "start",
|
|
1779
|
-
side = "bottom",
|
|
1780
|
-
portal = true,
|
|
1781
|
-
collisionPadding = 8
|
|
1782
|
-
}) => {
|
|
1783
|
-
const content = /* @__PURE__ */ jsx9(
|
|
1784
|
-
RadixPopover.Content,
|
|
1785
|
-
{
|
|
1786
|
-
className: `gantt-popover${className ? ` ${className}` : ""}`,
|
|
1787
|
-
align,
|
|
1788
|
-
side,
|
|
1789
|
-
collisionPadding,
|
|
1790
|
-
sideOffset: 4,
|
|
1791
|
-
children
|
|
1792
|
-
}
|
|
1793
|
-
);
|
|
1794
|
-
if (portal) {
|
|
1795
|
-
return /* @__PURE__ */ jsx9(RadixPopover.Portal, { children: content });
|
|
1796
|
-
}
|
|
1797
|
-
return content;
|
|
1798
|
-
};
|
|
1799
|
-
|
|
1800
1805
|
// src/components/ui/DatePicker.tsx
|
|
1801
1806
|
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1802
1807
|
var DatePicker = ({
|
|
@@ -1861,19 +1866,198 @@ var DatePicker = ({
|
|
|
1861
1866
|
};
|
|
1862
1867
|
DatePicker.displayName = "DatePicker";
|
|
1863
1868
|
|
|
1864
|
-
// src/components/TaskList/
|
|
1869
|
+
// src/components/TaskList/DepIcons.tsx
|
|
1865
1870
|
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1871
|
+
var DepIconFS = () => /* @__PURE__ */ jsxs8("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1872
|
+
/* @__PURE__ */ jsx11("path", { d: "m10 15 5 5 5-5" }),
|
|
1873
|
+
/* @__PURE__ */ jsx11("path", { d: "M4 4h7a4 4 0 0 1 4 4v12" })
|
|
1874
|
+
] });
|
|
1875
|
+
var DepIconSS = () => /* @__PURE__ */ jsxs8("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1876
|
+
/* @__PURE__ */ jsx11("path", { d: "M3 5v14" }),
|
|
1877
|
+
/* @__PURE__ */ jsx11("path", { d: "M21 12H7" }),
|
|
1878
|
+
/* @__PURE__ */ jsx11("path", { d: "m15 18 6-6-6-6" })
|
|
1879
|
+
] });
|
|
1880
|
+
var DepIconFF = () => /* @__PURE__ */ jsxs8("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1881
|
+
/* @__PURE__ */ jsx11("path", { d: "M17 12H3" }),
|
|
1882
|
+
/* @__PURE__ */ jsx11("path", { d: "m11 18 6-6-6-6" }),
|
|
1883
|
+
/* @__PURE__ */ jsx11("path", { d: "M21 5v14" })
|
|
1884
|
+
] });
|
|
1885
|
+
var DepIconSF = () => /* @__PURE__ */ jsxs8("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1886
|
+
/* @__PURE__ */ jsx11("path", { d: "m14 15-5 5-5-5" }),
|
|
1887
|
+
/* @__PURE__ */ jsx11("path", { d: "M20 4h-7a4 4 0 0 0-4 4v12" })
|
|
1888
|
+
] });
|
|
1889
|
+
var LINK_TYPE_ICONS = {
|
|
1890
|
+
FS: DepIconFS,
|
|
1891
|
+
SS: DepIconSS,
|
|
1892
|
+
FF: DepIconFF,
|
|
1893
|
+
SF: DepIconSF
|
|
1894
|
+
};
|
|
1895
|
+
var LINK_TYPE_LABELS = {
|
|
1896
|
+
FS: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435-\u043D\u0430\u0447\u0430\u043B\u043E",
|
|
1897
|
+
SS: "\u041D\u0430\u0447\u0430\u043B\u043E-\u043D\u0430\u0447\u0430\u043B\u043E",
|
|
1898
|
+
FF: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435-\u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435",
|
|
1899
|
+
SF: "\u041D\u0430\u0447\u0430\u043B\u043E-\u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435"
|
|
1900
|
+
};
|
|
1901
|
+
|
|
1902
|
+
// src/components/TaskList/TaskListRow.tsx
|
|
1903
|
+
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1904
|
+
var TrashIcon = () => /* @__PURE__ */ jsxs9("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1905
|
+
/* @__PURE__ */ jsx12("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
|
|
1906
|
+
/* @__PURE__ */ jsx12("path", { d: "M3 6h18" }),
|
|
1907
|
+
/* @__PURE__ */ jsx12("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
|
|
1908
|
+
] });
|
|
1909
|
+
function formatDepDescription(type, lag) {
|
|
1910
|
+
const effectiveLag = lag ?? 0;
|
|
1911
|
+
if (type === "FS") {
|
|
1912
|
+
if (effectiveLag > 0) return `\u041D\u0430\u0447\u0430\u0442\u044C \u0447\u0435\u0440\u0435\u0437 ${effectiveLag} \u0434\u043D. \u043F\u043E\u0441\u043B\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
|
|
1913
|
+
if (effectiveLag < 0) return `\u041D\u0430\u0447\u0430\u0442\u044C \u0437\u0430 ${Math.abs(effectiveLag)} \u0434\u043D. \u0434\u043E \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
|
|
1914
|
+
return `\u041D\u0430\u0447\u0430\u0442\u044C \u0441\u0440\u0430\u0437\u0443 \u043F\u043E\u0441\u043B\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
|
|
1915
|
+
}
|
|
1916
|
+
if (type === "FF") {
|
|
1917
|
+
if (effectiveLag > 0) return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0447\u0435\u0440\u0435\u0437 ${effectiveLag} \u0434\u043D. \u043F\u043E\u0441\u043B\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
|
|
1918
|
+
if (effectiveLag < 0) return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0437\u0430 ${Math.abs(effectiveLag)} \u0434\u043D. \u0434\u043E \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
|
|
1919
|
+
return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
|
|
1920
|
+
}
|
|
1921
|
+
if (type === "SS") {
|
|
1922
|
+
if (effectiveLag > 0) return `\u041D\u0430\u0447\u0430\u0442\u044C \u0447\u0435\u0440\u0435\u0437 ${effectiveLag} \u0434\u043D. \u043F\u043E\u0441\u043B\u0435 \u043D\u0430\u0447\u0430\u043B\u0430`;
|
|
1923
|
+
if (effectiveLag < 0) return `\u041D\u0430\u0447\u0430\u0442\u044C \u0437\u0430 ${Math.abs(effectiveLag)} \u0434\u043D. \u0434\u043E \u043D\u0430\u0447\u0430\u043B\u0430`;
|
|
1924
|
+
return `\u041D\u0430\u0447\u0430\u0442\u044C \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u0430\u0447\u0430\u043B\u043E\u043C`;
|
|
1925
|
+
}
|
|
1926
|
+
if (type === "SF") {
|
|
1927
|
+
if (effectiveLag > 0) return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0447\u0435\u0440\u0435\u0437 ${effectiveLag} \u0434\u043D. \u043F\u043E\u0441\u043B\u0435 \u043D\u0430\u0447\u0430\u043B\u0430`;
|
|
1928
|
+
if (effectiveLag < 0) return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0437\u0430 ${Math.abs(effectiveLag)} \u0434\u043D. \u0434\u043E \u043D\u0430\u0447\u0430\u043B\u0430`;
|
|
1929
|
+
return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0434\u043E \u043D\u0430\u0447\u0430\u043B\u0430`;
|
|
1930
|
+
}
|
|
1931
|
+
return "";
|
|
1932
|
+
}
|
|
1933
|
+
var DepChip = ({
|
|
1934
|
+
lag,
|
|
1935
|
+
dep,
|
|
1936
|
+
taskId,
|
|
1937
|
+
predecessorName,
|
|
1938
|
+
selectedChip,
|
|
1939
|
+
disableDependencyEditing,
|
|
1940
|
+
onChipSelect,
|
|
1941
|
+
onRowClick,
|
|
1942
|
+
onScrollToTask,
|
|
1943
|
+
onRemoveDependency,
|
|
1944
|
+
onChipSelectClear
|
|
1945
|
+
}) => {
|
|
1946
|
+
const isSelected = selectedChip?.successorId === taskId && selectedChip?.predecessorId === dep.taskId && selectedChip?.linkType === dep.type;
|
|
1947
|
+
const handleClick = (e) => {
|
|
1948
|
+
e.stopPropagation();
|
|
1949
|
+
if (disableDependencyEditing) return;
|
|
1950
|
+
onChipSelect?.(isSelected ? null : { successorId: taskId, predecessorId: dep.taskId, linkType: dep.type });
|
|
1951
|
+
if (!isSelected) {
|
|
1952
|
+
onRowClick?.(taskId);
|
|
1953
|
+
onScrollToTask?.(taskId);
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
const handleTrashClick = (e) => {
|
|
1957
|
+
e.stopPropagation();
|
|
1958
|
+
onRemoveDependency?.(taskId, dep.taskId, dep.type);
|
|
1959
|
+
onChipSelectClear();
|
|
1960
|
+
};
|
|
1961
|
+
const Icon = LINK_TYPE_ICONS[dep.type];
|
|
1962
|
+
const depPrefix = formatDepDescription(dep.type, lag);
|
|
1963
|
+
const depName = predecessorName ?? dep.taskId;
|
|
1964
|
+
return /* @__PURE__ */ jsxs9(Popover, { open: isSelected, onOpenChange: (open) => {
|
|
1965
|
+
}, children: [
|
|
1966
|
+
/* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs9("span", { className: "gantt-tl-dep-chip-wrapper", children: [
|
|
1967
|
+
/* @__PURE__ */ jsx12(
|
|
1968
|
+
"span",
|
|
1969
|
+
{
|
|
1970
|
+
className: `gantt-tl-dep-chip${isSelected ? " gantt-tl-dep-chip-selected" : ""}`,
|
|
1971
|
+
onClick: handleClick,
|
|
1972
|
+
children: /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
1973
|
+
/* @__PURE__ */ jsx12(Icon, {}),
|
|
1974
|
+
lag != null && lag !== 0 ? lag > 0 ? `+${lag}` : `${lag}` : ""
|
|
1975
|
+
] })
|
|
1976
|
+
}
|
|
1977
|
+
),
|
|
1978
|
+
!disableDependencyEditing && /* @__PURE__ */ jsx12(
|
|
1979
|
+
"button",
|
|
1980
|
+
{
|
|
1981
|
+
type: "button",
|
|
1982
|
+
className: "gantt-tl-dep-chip-trash",
|
|
1983
|
+
"aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
|
|
1984
|
+
onClick: handleTrashClick,
|
|
1985
|
+
children: /* @__PURE__ */ jsx12(TrashIcon, {})
|
|
1986
|
+
}
|
|
1987
|
+
)
|
|
1988
|
+
] }) }),
|
|
1989
|
+
/* @__PURE__ */ jsxs9(
|
|
1990
|
+
PopoverContent,
|
|
1991
|
+
{
|
|
1992
|
+
portal: true,
|
|
1993
|
+
side: "bottom",
|
|
1994
|
+
align: "start",
|
|
1995
|
+
className: "gantt-tl-dep-info-popover",
|
|
1996
|
+
onInteractOutside: (event) => {
|
|
1997
|
+
const target = event.target;
|
|
1998
|
+
if (target?.closest?.(".gantt-tl-dep-chip") || target?.closest?.(".gantt-tl-dep-delete-label") || target?.closest?.(".gantt-tl-dep-chip-trash")) {
|
|
1999
|
+
event.preventDefault();
|
|
2000
|
+
} else {
|
|
2001
|
+
onChipSelectClear();
|
|
2002
|
+
}
|
|
2003
|
+
},
|
|
2004
|
+
children: [
|
|
2005
|
+
/* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-info-prefix", children: depPrefix }),
|
|
2006
|
+
/* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-info-name", children: depName })
|
|
2007
|
+
]
|
|
2008
|
+
}
|
|
2009
|
+
)
|
|
2010
|
+
] });
|
|
2011
|
+
};
|
|
1866
2012
|
var toISODate = (value) => {
|
|
1867
2013
|
if (value instanceof Date) return value.toISOString().split("T")[0];
|
|
1868
2014
|
if (typeof value === "string" && value.includes("T")) return value.split("T")[0];
|
|
1869
2015
|
return value;
|
|
1870
2016
|
};
|
|
1871
2017
|
var TaskListRow = React9.memo(
|
|
1872
|
-
({
|
|
2018
|
+
({
|
|
2019
|
+
task,
|
|
2020
|
+
rowIndex,
|
|
2021
|
+
rowHeight,
|
|
2022
|
+
onTaskChange,
|
|
2023
|
+
selectedTaskId,
|
|
2024
|
+
onRowClick,
|
|
2025
|
+
disableTaskNameEditing = false,
|
|
2026
|
+
disableDependencyEditing = false,
|
|
2027
|
+
allTasks = [],
|
|
2028
|
+
activeLinkType,
|
|
2029
|
+
selectingPredecessorFor,
|
|
2030
|
+
onSetSelectingPredecessorFor,
|
|
2031
|
+
onAddDependency,
|
|
2032
|
+
onRemoveDependency,
|
|
2033
|
+
selectedChip,
|
|
2034
|
+
onChipSelect,
|
|
2035
|
+
onScrollToTask
|
|
2036
|
+
}) => {
|
|
1873
2037
|
const [editingName, setEditingName] = useState4(false);
|
|
1874
2038
|
const [nameValue, setNameValue] = useState4("");
|
|
1875
2039
|
const nameInputRef = useRef3(null);
|
|
2040
|
+
const [overflowOpen, setOverflowOpen] = useState4(false);
|
|
1876
2041
|
const isSelected = selectedTaskId === task.id;
|
|
2042
|
+
const isPicking = selectingPredecessorFor != null;
|
|
2043
|
+
const isSourceRow = isPicking && selectingPredecessorFor === task.id;
|
|
2044
|
+
const chips = useMemo7(() => {
|
|
2045
|
+
const succStart = new Date(task.startDate);
|
|
2046
|
+
const succEnd = new Date(task.endDate);
|
|
2047
|
+
const taskById = new Map((allTasks ?? []).map((t) => [t.id, t]));
|
|
2048
|
+
return (task.dependencies ?? []).map((dep) => {
|
|
2049
|
+
const pred = taskById.get(dep.taskId);
|
|
2050
|
+
const lag = pred ? computeLagFromDates(
|
|
2051
|
+
dep.type,
|
|
2052
|
+
new Date(pred.startDate),
|
|
2053
|
+
new Date(pred.endDate),
|
|
2054
|
+
succStart,
|
|
2055
|
+
succEnd
|
|
2056
|
+
) : dep.lag ?? 0;
|
|
2057
|
+
return { dep, lag, predecessorName: pred?.name ?? dep.taskId };
|
|
2058
|
+
});
|
|
2059
|
+
}, [task.dependencies, task.startDate, task.endDate, allTasks]);
|
|
2060
|
+
const linkWord = chips.length <= 4 ? "\u0441\u0432\u044F\u0437\u0438" : "\u0441\u0432\u044F\u0437\u0435\u0439";
|
|
1877
2061
|
useEffect3(() => {
|
|
1878
2062
|
if (editingName && nameInputRef.current) {
|
|
1879
2063
|
nameInputRef.current.focus();
|
|
@@ -1919,18 +2103,60 @@ var TaskListRow = React9.memo(
|
|
|
1919
2103
|
const handleRowClickInternal = useCallback4(() => {
|
|
1920
2104
|
onRowClick?.(task.id);
|
|
1921
2105
|
}, [task.id, onRowClick]);
|
|
2106
|
+
const handleNumberClick = useCallback4((e) => {
|
|
2107
|
+
e.stopPropagation();
|
|
2108
|
+
onRowClick?.(task.id);
|
|
2109
|
+
onScrollToTask?.(task.id);
|
|
2110
|
+
}, [task.id, onRowClick, onScrollToTask]);
|
|
2111
|
+
const handleAddClick = useCallback4((e) => {
|
|
2112
|
+
e.stopPropagation();
|
|
2113
|
+
onSetSelectingPredecessorFor?.(task.id);
|
|
2114
|
+
}, [task.id, onSetSelectingPredecessorFor]);
|
|
2115
|
+
const handlePredecessorPick = useCallback4((e) => {
|
|
2116
|
+
e.stopPropagation();
|
|
2117
|
+
if (!isPicking || isSourceRow) return;
|
|
2118
|
+
if (!selectingPredecessorFor || !activeLinkType) return;
|
|
2119
|
+
onAddDependency?.(task.id, selectingPredecessorFor, activeLinkType);
|
|
2120
|
+
}, [isPicking, isSourceRow, selectingPredecessorFor, task.id, activeLinkType, onAddDependency]);
|
|
2121
|
+
const isSelectedPredecessor = selectedChip != null && selectedChip.predecessorId === task.id;
|
|
2122
|
+
const handleDeleteSelected = useCallback4((e) => {
|
|
2123
|
+
e.stopPropagation();
|
|
2124
|
+
if (!selectedChip) return;
|
|
2125
|
+
onRemoveDependency?.(selectedChip.successorId, selectedChip.predecessorId, selectedChip.linkType);
|
|
2126
|
+
onChipSelect?.(null);
|
|
2127
|
+
}, [selectedChip, onRemoveDependency, onChipSelect]);
|
|
1922
2128
|
const startDateISO = toISODate(task.startDate);
|
|
1923
2129
|
const endDateISO = toISODate(task.endDate);
|
|
1924
|
-
return /* @__PURE__ */
|
|
2130
|
+
return /* @__PURE__ */ jsxs9(
|
|
1925
2131
|
"div",
|
|
1926
2132
|
{
|
|
1927
|
-
className:
|
|
2133
|
+
className: [
|
|
2134
|
+
"gantt-tl-row",
|
|
2135
|
+
isSelected ? "gantt-tl-row-selected" : "",
|
|
2136
|
+
isPicking && !isSourceRow ? "gantt-tl-row-picking" : "",
|
|
2137
|
+
isSourceRow ? "gantt-tl-row-picking-self" : ""
|
|
2138
|
+
].filter(Boolean).join(" "),
|
|
1928
2139
|
style: { minHeight: `${rowHeight}px` },
|
|
1929
2140
|
onClick: handleRowClickInternal,
|
|
1930
2141
|
children: [
|
|
1931
|
-
/* @__PURE__ */
|
|
1932
|
-
|
|
1933
|
-
|
|
2142
|
+
/* @__PURE__ */ jsxs9(
|
|
2143
|
+
"div",
|
|
2144
|
+
{
|
|
2145
|
+
className: "gantt-tl-cell gantt-tl-cell-number",
|
|
2146
|
+
onClick: handleNumberClick,
|
|
2147
|
+
title: "\u041F\u0435\u0440\u0435\u0439\u0442\u0438 \u043A \u0440\u0430\u0431\u043E\u0442\u0435",
|
|
2148
|
+
children: [
|
|
2149
|
+
/* @__PURE__ */ jsx12("span", { className: "gantt-tl-num-label", children: rowIndex + 1 }),
|
|
2150
|
+
/* @__PURE__ */ jsxs9("svg", { className: "gantt-tl-num-icon", xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
2151
|
+
/* @__PURE__ */ jsx12("path", { d: "M17 12H3" }),
|
|
2152
|
+
/* @__PURE__ */ jsx12("path", { d: "m11 18 6-6-6-6" }),
|
|
2153
|
+
/* @__PURE__ */ jsx12("path", { d: "M21 5v14" })
|
|
2154
|
+
] })
|
|
2155
|
+
]
|
|
2156
|
+
}
|
|
2157
|
+
),
|
|
2158
|
+
/* @__PURE__ */ jsxs9("div", { className: "gantt-tl-cell gantt-tl-cell-name", children: [
|
|
2159
|
+
editingName && /* @__PURE__ */ jsx12(
|
|
1934
2160
|
Input,
|
|
1935
2161
|
{
|
|
1936
2162
|
ref: nameInputRef,
|
|
@@ -1943,7 +2169,7 @@ var TaskListRow = React9.memo(
|
|
|
1943
2169
|
onClick: (e) => e.stopPropagation()
|
|
1944
2170
|
}
|
|
1945
2171
|
),
|
|
1946
|
-
/* @__PURE__ */
|
|
2172
|
+
/* @__PURE__ */ jsx12(
|
|
1947
2173
|
"button",
|
|
1948
2174
|
{
|
|
1949
2175
|
type: "button",
|
|
@@ -1954,7 +2180,7 @@ var TaskListRow = React9.memo(
|
|
|
1954
2180
|
}
|
|
1955
2181
|
)
|
|
1956
2182
|
] }),
|
|
1957
|
-
/* @__PURE__ */
|
|
2183
|
+
/* @__PURE__ */ jsx12("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx12(
|
|
1958
2184
|
DatePicker,
|
|
1959
2185
|
{
|
|
1960
2186
|
value: startDateISO,
|
|
@@ -1964,7 +2190,7 @@ var TaskListRow = React9.memo(
|
|
|
1964
2190
|
disabled: task.locked
|
|
1965
2191
|
}
|
|
1966
2192
|
) }),
|
|
1967
|
-
/* @__PURE__ */
|
|
2193
|
+
/* @__PURE__ */ jsx12("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx12(
|
|
1968
2194
|
DatePicker,
|
|
1969
2195
|
{
|
|
1970
2196
|
value: endDateISO,
|
|
@@ -1973,7 +2199,97 @@ var TaskListRow = React9.memo(
|
|
|
1973
2199
|
portal: true,
|
|
1974
2200
|
disabled: task.locked
|
|
1975
2201
|
}
|
|
1976
|
-
) })
|
|
2202
|
+
) }),
|
|
2203
|
+
/* @__PURE__ */ jsx12(
|
|
2204
|
+
"div",
|
|
2205
|
+
{
|
|
2206
|
+
className: "gantt-tl-cell gantt-tl-cell-deps",
|
|
2207
|
+
onClick: isPicking && !isSourceRow ? handlePredecessorPick : void 0,
|
|
2208
|
+
children: isSourceRow ? /* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-source-hint", children: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u0434\u0430\u0447\u0443" }) : isSelectedPredecessor && !disableDependencyEditing ? (
|
|
2209
|
+
/* Full-replacement: "Зависит от [name]" → hover → "Удалить" */
|
|
2210
|
+
/* @__PURE__ */ jsxs9(
|
|
2211
|
+
"button",
|
|
2212
|
+
{
|
|
2213
|
+
type: "button",
|
|
2214
|
+
className: "gantt-tl-dep-delete-label",
|
|
2215
|
+
onClick: handleDeleteSelected,
|
|
2216
|
+
"aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
|
|
2217
|
+
children: [
|
|
2218
|
+
/* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-delete-label-default", children: "\u0421\u0432\u044F\u0437\u0430\u043D\u043E \u0441" }),
|
|
2219
|
+
/* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-delete-label-hover", children: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C" })
|
|
2220
|
+
]
|
|
2221
|
+
}
|
|
2222
|
+
)
|
|
2223
|
+
) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
2224
|
+
chips.length >= 2 ? (
|
|
2225
|
+
/* 2+ deps — show only "N связей" summary chip that opens a popover */
|
|
2226
|
+
/* @__PURE__ */ jsxs9(Popover, { open: overflowOpen, onOpenChange: setOverflowOpen, children: [
|
|
2227
|
+
/* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs9(
|
|
2228
|
+
"button",
|
|
2229
|
+
{
|
|
2230
|
+
type: "button",
|
|
2231
|
+
className: "gantt-tl-dep-summary-chip",
|
|
2232
|
+
onClick: (e) => {
|
|
2233
|
+
e.stopPropagation();
|
|
2234
|
+
setOverflowOpen((v) => !v);
|
|
2235
|
+
},
|
|
2236
|
+
children: [
|
|
2237
|
+
chips.length,
|
|
2238
|
+
" ",
|
|
2239
|
+
linkWord
|
|
2240
|
+
]
|
|
2241
|
+
}
|
|
2242
|
+
) }),
|
|
2243
|
+
/* @__PURE__ */ jsx12(PopoverContent, { portal: true, align: "start", children: /* @__PURE__ */ jsx12("div", { className: "gantt-tl-dep-overflow-list", onClick: (e) => e.stopPropagation(), children: chips.map(({ dep, lag, predecessorName }) => /* @__PURE__ */ jsx12(
|
|
2244
|
+
DepChip,
|
|
2245
|
+
{
|
|
2246
|
+
lag,
|
|
2247
|
+
dep,
|
|
2248
|
+
taskId: task.id,
|
|
2249
|
+
predecessorName,
|
|
2250
|
+
selectedChip,
|
|
2251
|
+
disableDependencyEditing,
|
|
2252
|
+
onChipSelect,
|
|
2253
|
+
onRowClick,
|
|
2254
|
+
onScrollToTask,
|
|
2255
|
+
onRemoveDependency,
|
|
2256
|
+
onChipSelectClear: () => onChipSelect?.(null)
|
|
2257
|
+
},
|
|
2258
|
+
`${dep.taskId}-${dep.type}`
|
|
2259
|
+
)) }) })
|
|
2260
|
+
] })
|
|
2261
|
+
) : chips.length === 1 ? (
|
|
2262
|
+
/* Single chip — unified DepChip */
|
|
2263
|
+
/* @__PURE__ */ jsx12(
|
|
2264
|
+
DepChip,
|
|
2265
|
+
{
|
|
2266
|
+
lag: chips[0].lag,
|
|
2267
|
+
dep: chips[0].dep,
|
|
2268
|
+
taskId: task.id,
|
|
2269
|
+
predecessorName: chips[0].predecessorName,
|
|
2270
|
+
selectedChip,
|
|
2271
|
+
disableDependencyEditing,
|
|
2272
|
+
onChipSelect,
|
|
2273
|
+
onRowClick,
|
|
2274
|
+
onScrollToTask,
|
|
2275
|
+
onRemoveDependency,
|
|
2276
|
+
onChipSelectClear: () => onChipSelect?.(null)
|
|
2277
|
+
}
|
|
2278
|
+
)
|
|
2279
|
+
) : null,
|
|
2280
|
+
!disableDependencyEditing && !isPicking && /* @__PURE__ */ jsx12(
|
|
2281
|
+
"button",
|
|
2282
|
+
{
|
|
2283
|
+
type: "button",
|
|
2284
|
+
className: "gantt-tl-dep-add",
|
|
2285
|
+
onClick: handleAddClick,
|
|
2286
|
+
"aria-label": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
|
|
2287
|
+
children: "+"
|
|
2288
|
+
}
|
|
2289
|
+
)
|
|
2290
|
+
] })
|
|
2291
|
+
}
|
|
2292
|
+
)
|
|
1977
2293
|
]
|
|
1978
2294
|
}
|
|
1979
2295
|
);
|
|
@@ -1982,7 +2298,8 @@ var TaskListRow = React9.memo(
|
|
|
1982
2298
|
TaskListRow.displayName = "TaskListRow";
|
|
1983
2299
|
|
|
1984
2300
|
// src/components/TaskList/TaskList.tsx
|
|
1985
|
-
import { jsx as
|
|
2301
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2302
|
+
var LINK_TYPE_ORDER = ["FS", "SS", "FF", "SF"];
|
|
1986
2303
|
var TaskList = ({
|
|
1987
2304
|
tasks,
|
|
1988
2305
|
rowHeight,
|
|
@@ -1992,28 +2309,156 @@ var TaskList = ({
|
|
|
1992
2309
|
selectedTaskId,
|
|
1993
2310
|
onTaskSelect,
|
|
1994
2311
|
show = true,
|
|
1995
|
-
disableTaskNameEditing = false
|
|
2312
|
+
disableTaskNameEditing = false,
|
|
2313
|
+
disableDependencyEditing = false,
|
|
2314
|
+
onScrollToTask,
|
|
2315
|
+
onSelectedChipChange
|
|
1996
2316
|
}) => {
|
|
1997
|
-
const totalHeight =
|
|
2317
|
+
const totalHeight = useMemo8(
|
|
1998
2318
|
() => tasks.length * rowHeight,
|
|
1999
2319
|
[tasks.length, rowHeight]
|
|
2000
2320
|
);
|
|
2001
2321
|
const handleRowClick = useCallback5((taskId) => {
|
|
2002
2322
|
onTaskSelect?.(taskId);
|
|
2003
2323
|
}, [onTaskSelect]);
|
|
2004
|
-
|
|
2324
|
+
const [activeLinkType, setActiveLinkType] = useState5("FS");
|
|
2325
|
+
const [selectingPredecessorFor, setSelectingPredecessorFor] = useState5(null);
|
|
2326
|
+
const [typeMenuOpen, setTypeMenuOpen] = useState5(false);
|
|
2327
|
+
const [cycleError, setCycleError] = useState5(false);
|
|
2328
|
+
const overlayRef = useRef4(null);
|
|
2329
|
+
const [selectedChip, setSelectedChip] = useState5(null);
|
|
2330
|
+
const handleChipSelect = useCallback5((chip) => {
|
|
2331
|
+
setSelectedChip(chip);
|
|
2332
|
+
onSelectedChipChange?.(chip);
|
|
2333
|
+
}, [onSelectedChipChange]);
|
|
2334
|
+
useEffect4(() => {
|
|
2335
|
+
if (!selectingPredecessorFor && !selectedChip) return;
|
|
2336
|
+
const handleKeyDown = (e) => {
|
|
2337
|
+
if (e.key === "Escape") {
|
|
2338
|
+
setSelectingPredecessorFor(null);
|
|
2339
|
+
setSelectedChip(null);
|
|
2340
|
+
onSelectedChipChange?.(null);
|
|
2341
|
+
}
|
|
2342
|
+
};
|
|
2343
|
+
const handleMouseDown = (e) => {
|
|
2344
|
+
const target = e.target;
|
|
2345
|
+
if (overlayRef.current?.contains(target)) return;
|
|
2346
|
+
if (target.closest?.(".gantt-popover")) return;
|
|
2347
|
+
setSelectingPredecessorFor(null);
|
|
2348
|
+
setSelectedChip(null);
|
|
2349
|
+
onSelectedChipChange?.(null);
|
|
2350
|
+
};
|
|
2351
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
2352
|
+
document.addEventListener("mousedown", handleMouseDown, true);
|
|
2353
|
+
return () => {
|
|
2354
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
2355
|
+
document.removeEventListener("mousedown", handleMouseDown, true);
|
|
2356
|
+
};
|
|
2357
|
+
}, [selectingPredecessorFor, selectedChip, onSelectedChipChange]);
|
|
2358
|
+
const handleAddDependency = useCallback5((successorTaskId, predecessorTaskId, linkType) => {
|
|
2359
|
+
if (successorTaskId === predecessorTaskId) return;
|
|
2360
|
+
const successor = tasks.find((t) => t.id === successorTaskId);
|
|
2361
|
+
if (!successor) return;
|
|
2362
|
+
const alreadyExists = (successor.dependencies ?? []).some(
|
|
2363
|
+
(d) => d.taskId === predecessorTaskId && d.type === linkType
|
|
2364
|
+
);
|
|
2365
|
+
if (alreadyExists) {
|
|
2366
|
+
setSelectingPredecessorFor(null);
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
const newDep = { taskId: predecessorTaskId, type: linkType, lag: 0 };
|
|
2370
|
+
const hypothetical = tasks.map(
|
|
2371
|
+
(t) => t.id === successorTaskId ? { ...t, dependencies: [...t.dependencies ?? [], newDep] } : t
|
|
2372
|
+
);
|
|
2373
|
+
const validation = validateDependencies(hypothetical);
|
|
2374
|
+
if (!validation.isValid) {
|
|
2375
|
+
setCycleError(true);
|
|
2376
|
+
setTimeout(() => setCycleError(false), 3e3);
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
const updatedTask = hypothetical.find((t) => t.id === successorTaskId);
|
|
2380
|
+
const predecessor = tasks.find((t) => t.id === predecessorTaskId);
|
|
2381
|
+
if (predecessor) {
|
|
2382
|
+
const predStart = new Date(predecessor.startDate);
|
|
2383
|
+
const predEnd = new Date(predecessor.endDate);
|
|
2384
|
+
const constraintDate = calculateSuccessorDate(predStart, predEnd, linkType, 0);
|
|
2385
|
+
const origSuccessor = tasks.find((t) => t.id === successorTaskId);
|
|
2386
|
+
const durationMs = new Date(origSuccessor.endDate).getTime() - new Date(origSuccessor.startDate).getTime();
|
|
2387
|
+
let newStart;
|
|
2388
|
+
let newEnd;
|
|
2389
|
+
if (linkType === "FS" || linkType === "SS") {
|
|
2390
|
+
newStart = constraintDate;
|
|
2391
|
+
newEnd = new Date(constraintDate.getTime() + durationMs);
|
|
2392
|
+
} else {
|
|
2393
|
+
newEnd = constraintDate;
|
|
2394
|
+
newStart = new Date(constraintDate.getTime() - durationMs);
|
|
2395
|
+
}
|
|
2396
|
+
const snappedTask = {
|
|
2397
|
+
...updatedTask,
|
|
2398
|
+
startDate: newStart.toISOString().split("T")[0],
|
|
2399
|
+
endDate: newEnd.toISOString().split("T")[0]
|
|
2400
|
+
};
|
|
2401
|
+
onTaskChange?.(snappedTask);
|
|
2402
|
+
} else {
|
|
2403
|
+
onTaskChange?.(updatedTask);
|
|
2404
|
+
}
|
|
2405
|
+
setSelectingPredecessorFor(null);
|
|
2406
|
+
}, [tasks, onTaskChange]);
|
|
2407
|
+
const handleRemoveDependency = useCallback5((taskId, predecessorTaskId, linkType) => {
|
|
2408
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
2409
|
+
if (!task) return;
|
|
2410
|
+
const updatedDeps = (task.dependencies ?? []).filter(
|
|
2411
|
+
(d) => !(d.taskId === predecessorTaskId && d.type === linkType)
|
|
2412
|
+
);
|
|
2413
|
+
onTaskChange?.({ ...task, dependencies: updatedDeps });
|
|
2414
|
+
}, [tasks, onTaskChange]);
|
|
2415
|
+
return /* @__PURE__ */ jsx13(
|
|
2005
2416
|
"div",
|
|
2006
2417
|
{
|
|
2418
|
+
ref: overlayRef,
|
|
2007
2419
|
className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}`,
|
|
2008
2420
|
style: { width: `${taskListWidth}px` },
|
|
2009
|
-
children: /* @__PURE__ */
|
|
2010
|
-
/* @__PURE__ */
|
|
2011
|
-
/* @__PURE__ */
|
|
2012
|
-
/* @__PURE__ */
|
|
2013
|
-
/* @__PURE__ */
|
|
2014
|
-
/* @__PURE__ */
|
|
2421
|
+
children: /* @__PURE__ */ jsxs10("div", { className: "gantt-tl-table", children: [
|
|
2422
|
+
/* @__PURE__ */ jsxs10("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: [
|
|
2423
|
+
/* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-number", children: "\u2116" }),
|
|
2424
|
+
/* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-name", children: "\u0418\u043C\u044F" }),
|
|
2425
|
+
/* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041D\u0430\u0447\u0430\u043B\u043E" }),
|
|
2426
|
+
/* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435" }),
|
|
2427
|
+
/* @__PURE__ */ jsxs10("div", { className: "gantt-tl-headerCell gantt-tl-cell-deps", style: { position: "relative" }, children: [
|
|
2428
|
+
/* @__PURE__ */ jsxs10(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
|
|
2429
|
+
/* @__PURE__ */ jsx13(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs10(
|
|
2430
|
+
"button",
|
|
2431
|
+
{
|
|
2432
|
+
className: "gantt-tl-dep-type-trigger",
|
|
2433
|
+
disabled: disableDependencyEditing,
|
|
2434
|
+
onClick: (e) => e.stopPropagation(),
|
|
2435
|
+
children: [
|
|
2436
|
+
"\u0421\u0432\u044F\u0437\u0438 ",
|
|
2437
|
+
React10.createElement(LINK_TYPE_ICONS[activeLinkType]),
|
|
2438
|
+
" \u25BE"
|
|
2439
|
+
]
|
|
2440
|
+
}
|
|
2441
|
+
) }),
|
|
2442
|
+
/* @__PURE__ */ jsx13(PopoverContent, { portal: true, align: "start", children: /* @__PURE__ */ jsx13("div", { className: "gantt-tl-dep-type-menu", children: LINK_TYPE_ORDER.map((lt) => /* @__PURE__ */ jsxs10(
|
|
2443
|
+
"button",
|
|
2444
|
+
{
|
|
2445
|
+
className: `gantt-tl-dep-type-option${activeLinkType === lt ? " active" : ""}`,
|
|
2446
|
+
onClick: () => {
|
|
2447
|
+
setActiveLinkType(lt);
|
|
2448
|
+
setTypeMenuOpen(false);
|
|
2449
|
+
},
|
|
2450
|
+
children: [
|
|
2451
|
+
React10.createElement(LINK_TYPE_ICONS[lt]),
|
|
2452
|
+
/* @__PURE__ */ jsx13("span", { children: LINK_TYPE_LABELS[lt] })
|
|
2453
|
+
]
|
|
2454
|
+
},
|
|
2455
|
+
lt
|
|
2456
|
+
)) }) })
|
|
2457
|
+
] }),
|
|
2458
|
+
cycleError && /* @__PURE__ */ jsx13("div", { className: "gantt-tl-dep-error", children: "\u0426\u0438\u043A\u043B \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439!" })
|
|
2459
|
+
] })
|
|
2015
2460
|
] }),
|
|
2016
|
-
/* @__PURE__ */
|
|
2461
|
+
/* @__PURE__ */ jsx13("div", { className: "gantt-tl-body", style: { height: `${totalHeight}px` }, children: tasks.map((task, index) => /* @__PURE__ */ jsx13(
|
|
2017
2462
|
TaskListRow,
|
|
2018
2463
|
{
|
|
2019
2464
|
task,
|
|
@@ -2022,7 +2467,17 @@ var TaskList = ({
|
|
|
2022
2467
|
onTaskChange,
|
|
2023
2468
|
selectedTaskId,
|
|
2024
2469
|
onRowClick: handleRowClick,
|
|
2025
|
-
disableTaskNameEditing
|
|
2470
|
+
disableTaskNameEditing,
|
|
2471
|
+
disableDependencyEditing,
|
|
2472
|
+
allTasks: tasks,
|
|
2473
|
+
activeLinkType,
|
|
2474
|
+
selectingPredecessorFor,
|
|
2475
|
+
onSetSelectingPredecessorFor: setSelectingPredecessorFor,
|
|
2476
|
+
onAddDependency: handleAddDependency,
|
|
2477
|
+
onRemoveDependency: handleRemoveDependency,
|
|
2478
|
+
selectedChip,
|
|
2479
|
+
onChipSelect: handleChipSelect,
|
|
2480
|
+
onScrollToTask
|
|
2026
2481
|
},
|
|
2027
2482
|
task.id
|
|
2028
2483
|
)) })
|
|
@@ -2032,7 +2487,7 @@ var TaskList = ({
|
|
|
2032
2487
|
};
|
|
2033
2488
|
|
|
2034
2489
|
// src/components/GanttChart/GanttChart.tsx
|
|
2035
|
-
import { jsx as
|
|
2490
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2036
2491
|
var GanttChart = forwardRef(({
|
|
2037
2492
|
tasks,
|
|
2038
2493
|
dayWidth = 40,
|
|
@@ -2046,34 +2501,37 @@ var GanttChart = forwardRef(({
|
|
|
2046
2501
|
onCascade,
|
|
2047
2502
|
showTaskList = false,
|
|
2048
2503
|
taskListWidth = 520,
|
|
2049
|
-
disableTaskNameEditing = false
|
|
2504
|
+
disableTaskNameEditing = false,
|
|
2505
|
+
disableDependencyEditing = false,
|
|
2506
|
+
highlightExpiredTasks = false
|
|
2050
2507
|
}, ref) => {
|
|
2051
|
-
const scrollContainerRef =
|
|
2052
|
-
const [selectedTaskId, setSelectedTaskId] =
|
|
2053
|
-
const
|
|
2054
|
-
const
|
|
2055
|
-
const [
|
|
2056
|
-
const
|
|
2508
|
+
const scrollContainerRef = useRef5(null);
|
|
2509
|
+
const [selectedTaskId, setSelectedTaskId] = useState6(null);
|
|
2510
|
+
const [selectedChip, setSelectedChip] = useState6(null);
|
|
2511
|
+
const dateRange = useMemo9(() => getMultiMonthDays(tasks), [tasks]);
|
|
2512
|
+
const [validationResult, setValidationResult] = useState6(null);
|
|
2513
|
+
const [cascadeOverrides, setCascadeOverrides] = useState6(/* @__PURE__ */ new Map());
|
|
2514
|
+
const gridWidth = useMemo9(
|
|
2057
2515
|
() => Math.round(dateRange.length * dayWidth),
|
|
2058
2516
|
[dateRange.length, dayWidth]
|
|
2059
2517
|
);
|
|
2060
|
-
const totalGridHeight =
|
|
2518
|
+
const totalGridHeight = useMemo9(
|
|
2061
2519
|
() => tasks.length * rowHeight,
|
|
2062
2520
|
[tasks.length, rowHeight]
|
|
2063
2521
|
);
|
|
2064
|
-
const monthStart =
|
|
2522
|
+
const monthStart = useMemo9(() => {
|
|
2065
2523
|
if (dateRange.length === 0) {
|
|
2066
2524
|
return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
|
|
2067
2525
|
}
|
|
2068
2526
|
const firstDay = dateRange[0];
|
|
2069
2527
|
return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
|
|
2070
2528
|
}, [dateRange]);
|
|
2071
|
-
const todayInRange =
|
|
2529
|
+
const todayInRange = useMemo9(() => {
|
|
2072
2530
|
const now = /* @__PURE__ */ new Date();
|
|
2073
2531
|
const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
2074
2532
|
return dateRange.some((day) => day.getTime() === today.getTime());
|
|
2075
2533
|
}, [dateRange]);
|
|
2076
|
-
|
|
2534
|
+
useEffect5(() => {
|
|
2077
2535
|
const container = scrollContainerRef.current;
|
|
2078
2536
|
if (!container || dateRange.length === 0) return;
|
|
2079
2537
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -2097,16 +2555,34 @@ var GanttChart = forwardRef(({
|
|
|
2097
2555
|
const scrollLeft = Math.round(todayOffset - containerWidth / 2 + dayWidth / 2);
|
|
2098
2556
|
container.scrollLeft = Math.max(0, scrollLeft);
|
|
2099
2557
|
}, [dateRange, dayWidth]);
|
|
2558
|
+
const scrollToTask = useCallback6((taskId) => {
|
|
2559
|
+
const container = scrollContainerRef.current;
|
|
2560
|
+
if (!container || dateRange.length === 0) return;
|
|
2561
|
+
const task = tasks.find((t) => t.id === taskId);
|
|
2562
|
+
if (!task) return;
|
|
2563
|
+
const taskStart = new Date(task.startDate);
|
|
2564
|
+
const taskStartUTC = new Date(Date.UTC(
|
|
2565
|
+
taskStart.getUTCFullYear(),
|
|
2566
|
+
taskStart.getUTCMonth(),
|
|
2567
|
+
taskStart.getUTCDate()
|
|
2568
|
+
));
|
|
2569
|
+
const taskIndex = dateRange.findIndex((day) => day.getTime() === taskStartUTC.getTime());
|
|
2570
|
+
if (taskIndex === -1) return;
|
|
2571
|
+
const taskOffset = taskIndex * dayWidth;
|
|
2572
|
+
const scrollLeft = Math.round(taskOffset - dayWidth * 2);
|
|
2573
|
+
container.scrollLeft = Math.max(0, scrollLeft);
|
|
2574
|
+
}, [tasks, dateRange, dayWidth]);
|
|
2100
2575
|
useImperativeHandle(
|
|
2101
2576
|
ref,
|
|
2102
2577
|
() => ({
|
|
2103
|
-
scrollToToday
|
|
2578
|
+
scrollToToday,
|
|
2579
|
+
scrollToTask
|
|
2104
2580
|
}),
|
|
2105
|
-
[scrollToToday]
|
|
2581
|
+
[scrollToToday, scrollToTask]
|
|
2106
2582
|
);
|
|
2107
|
-
const [dragGuideLines, setDragGuideLines] =
|
|
2108
|
-
const [draggedTaskOverride, setDraggedTaskOverride] =
|
|
2109
|
-
|
|
2583
|
+
const [dragGuideLines, setDragGuideLines] = useState6(null);
|
|
2584
|
+
const [draggedTaskOverride, setDraggedTaskOverride] = useState6(null);
|
|
2585
|
+
useEffect5(() => {
|
|
2110
2586
|
const result = validateDependencies(tasks);
|
|
2111
2587
|
setValidationResult(result);
|
|
2112
2588
|
onValidateDependencies?.(result);
|
|
@@ -2135,7 +2611,7 @@ var GanttChart = forwardRef(({
|
|
|
2135
2611
|
});
|
|
2136
2612
|
onCascade?.(allCascaded);
|
|
2137
2613
|
}, [tasks, onChange, disableConstraints, onCascade]);
|
|
2138
|
-
const dependencyOverrides =
|
|
2614
|
+
const dependencyOverrides = useMemo9(() => {
|
|
2139
2615
|
const map = new Map(cascadeOverrides);
|
|
2140
2616
|
if (draggedTaskOverride) {
|
|
2141
2617
|
map.set(draggedTaskOverride.taskId, {
|
|
@@ -2158,7 +2634,7 @@ var GanttChart = forwardRef(({
|
|
|
2158
2634
|
const handleTaskSelect = useCallback6((taskId) => {
|
|
2159
2635
|
setSelectedTaskId(taskId);
|
|
2160
2636
|
}, []);
|
|
2161
|
-
const panStateRef =
|
|
2637
|
+
const panStateRef = useRef5(null);
|
|
2162
2638
|
const handlePanStart = useCallback6((e) => {
|
|
2163
2639
|
if (e.button !== 0) return;
|
|
2164
2640
|
const target = e.target;
|
|
@@ -2180,7 +2656,7 @@ var GanttChart = forwardRef(({
|
|
|
2180
2656
|
container.style.cursor = "grabbing";
|
|
2181
2657
|
e.preventDefault();
|
|
2182
2658
|
}, []);
|
|
2183
|
-
|
|
2659
|
+
useEffect5(() => {
|
|
2184
2660
|
const handlePanMove = (e) => {
|
|
2185
2661
|
const pan = panStateRef.current;
|
|
2186
2662
|
if (!pan?.active) return;
|
|
@@ -2202,15 +2678,15 @@ var GanttChart = forwardRef(({
|
|
|
2202
2678
|
window.removeEventListener("mouseup", handlePanEnd);
|
|
2203
2679
|
};
|
|
2204
2680
|
}, []);
|
|
2205
|
-
return /* @__PURE__ */
|
|
2681
|
+
return /* @__PURE__ */ jsx14("div", { className: "gantt-container", children: /* @__PURE__ */ jsx14(
|
|
2206
2682
|
"div",
|
|
2207
2683
|
{
|
|
2208
2684
|
ref: scrollContainerRef,
|
|
2209
2685
|
className: "gantt-scrollContainer",
|
|
2210
2686
|
style: { height: containerHeight ?? "auto", cursor: "grab" },
|
|
2211
2687
|
onMouseDown: handlePanStart,
|
|
2212
|
-
children: /* @__PURE__ */
|
|
2213
|
-
/* @__PURE__ */
|
|
2688
|
+
children: /* @__PURE__ */ jsxs11("div", { className: "gantt-scrollContent", children: [
|
|
2689
|
+
/* @__PURE__ */ jsx14(
|
|
2214
2690
|
TaskList,
|
|
2215
2691
|
{
|
|
2216
2692
|
tasks,
|
|
@@ -2221,11 +2697,14 @@ var GanttChart = forwardRef(({
|
|
|
2221
2697
|
selectedTaskId: selectedTaskId ?? void 0,
|
|
2222
2698
|
onTaskSelect: handleTaskSelect,
|
|
2223
2699
|
show: showTaskList,
|
|
2224
|
-
disableTaskNameEditing
|
|
2700
|
+
disableTaskNameEditing,
|
|
2701
|
+
disableDependencyEditing,
|
|
2702
|
+
onScrollToTask: scrollToTask,
|
|
2703
|
+
onSelectedChipChange: setSelectedChip
|
|
2225
2704
|
}
|
|
2226
2705
|
),
|
|
2227
|
-
/* @__PURE__ */
|
|
2228
|
-
/* @__PURE__ */
|
|
2706
|
+
/* @__PURE__ */ jsxs11("div", { style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
|
|
2707
|
+
/* @__PURE__ */ jsx14("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx14(
|
|
2229
2708
|
TimeScaleHeader_default,
|
|
2230
2709
|
{
|
|
2231
2710
|
days: dateRange,
|
|
@@ -2233,7 +2712,7 @@ var GanttChart = forwardRef(({
|
|
|
2233
2712
|
headerHeight
|
|
2234
2713
|
}
|
|
2235
2714
|
) }),
|
|
2236
|
-
/* @__PURE__ */
|
|
2715
|
+
/* @__PURE__ */ jsxs11(
|
|
2237
2716
|
"div",
|
|
2238
2717
|
{
|
|
2239
2718
|
className: "gantt-taskArea",
|
|
@@ -2242,7 +2721,7 @@ var GanttChart = forwardRef(({
|
|
|
2242
2721
|
width: `${gridWidth}px`
|
|
2243
2722
|
},
|
|
2244
2723
|
children: [
|
|
2245
|
-
/* @__PURE__ */
|
|
2724
|
+
/* @__PURE__ */ jsx14(
|
|
2246
2725
|
GridBackground_default,
|
|
2247
2726
|
{
|
|
2248
2727
|
dateRange,
|
|
@@ -2250,8 +2729,8 @@ var GanttChart = forwardRef(({
|
|
|
2250
2729
|
totalHeight: totalGridHeight
|
|
2251
2730
|
}
|
|
2252
2731
|
),
|
|
2253
|
-
todayInRange && /* @__PURE__ */
|
|
2254
|
-
/* @__PURE__ */
|
|
2732
|
+
todayInRange && /* @__PURE__ */ jsx14(TodayIndicator_default, { monthStart, dayWidth }),
|
|
2733
|
+
/* @__PURE__ */ jsx14(
|
|
2255
2734
|
DependencyLines_default,
|
|
2256
2735
|
{
|
|
2257
2736
|
tasks,
|
|
@@ -2259,10 +2738,11 @@ var GanttChart = forwardRef(({
|
|
|
2259
2738
|
dayWidth,
|
|
2260
2739
|
rowHeight,
|
|
2261
2740
|
gridWidth,
|
|
2262
|
-
dragOverrides: dependencyOverrides
|
|
2741
|
+
dragOverrides: dependencyOverrides,
|
|
2742
|
+
selectedDep: selectedChip
|
|
2263
2743
|
}
|
|
2264
2744
|
),
|
|
2265
|
-
dragGuideLines && /* @__PURE__ */
|
|
2745
|
+
dragGuideLines && /* @__PURE__ */ jsx14(
|
|
2266
2746
|
DragGuideLines_default,
|
|
2267
2747
|
{
|
|
2268
2748
|
isDragging: dragGuideLines.isDragging,
|
|
@@ -2272,7 +2752,7 @@ var GanttChart = forwardRef(({
|
|
|
2272
2752
|
totalHeight: totalGridHeight
|
|
2273
2753
|
}
|
|
2274
2754
|
),
|
|
2275
|
-
tasks.map((task, index) => /* @__PURE__ */
|
|
2755
|
+
tasks.map((task, index) => /* @__PURE__ */ jsx14(
|
|
2276
2756
|
TaskRow_default,
|
|
2277
2757
|
{
|
|
2278
2758
|
task,
|
|
@@ -2295,7 +2775,8 @@ var GanttChart = forwardRef(({
|
|
|
2295
2775
|
disableConstraints: disableConstraints ?? false,
|
|
2296
2776
|
overridePosition: cascadeOverrides.get(task.id),
|
|
2297
2777
|
onCascadeProgress: handleCascadeProgress,
|
|
2298
|
-
onCascade: handleCascade
|
|
2778
|
+
onCascade: handleCascade,
|
|
2779
|
+
highlightExpiredTasks
|
|
2299
2780
|
},
|
|
2300
2781
|
task.id
|
|
2301
2782
|
))
|
|
@@ -2311,7 +2792,7 @@ GanttChart.displayName = "GanttChart";
|
|
|
2311
2792
|
|
|
2312
2793
|
// src/components/ui/Button.tsx
|
|
2313
2794
|
import React12 from "react";
|
|
2314
|
-
import { jsx as
|
|
2795
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
2315
2796
|
var Button = React12.forwardRef(
|
|
2316
2797
|
({ className, variant = "default", size = "default", children, ...props }, ref) => {
|
|
2317
2798
|
const classes = [
|
|
@@ -2320,7 +2801,7 @@ var Button = React12.forwardRef(
|
|
|
2320
2801
|
size !== "default" ? `gantt-btn-${size}` : "",
|
|
2321
2802
|
className || ""
|
|
2322
2803
|
].filter(Boolean).join(" ");
|
|
2323
|
-
return /* @__PURE__ */
|
|
2804
|
+
return /* @__PURE__ */ jsx15("button", { ref, className: classes, ...props, children });
|
|
2324
2805
|
}
|
|
2325
2806
|
);
|
|
2326
2807
|
Button.displayName = "Button";
|
|
@@ -2349,6 +2830,7 @@ export {
|
|
|
2349
2830
|
calculateTaskBar,
|
|
2350
2831
|
calculateWeekendBlocks,
|
|
2351
2832
|
cascadeByLinks,
|
|
2833
|
+
computeLagFromDates,
|
|
2352
2834
|
detectCycles,
|
|
2353
2835
|
detectEdgeZone,
|
|
2354
2836
|
formatDateLabel,
|