gantt-lib 0.1.2 → 0.2.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.js CHANGED
@@ -55,6 +55,7 @@ __export(index_exports, {
55
55
  calculateTaskBar: () => calculateTaskBar,
56
56
  calculateWeekendBlocks: () => calculateWeekendBlocks,
57
57
  cascadeByLinks: () => cascadeByLinks,
58
+ computeLagFromDates: () => computeLagFromDates,
58
59
  detectCycles: () => detectCycles,
59
60
  detectEdgeZone: () => detectEdgeZone,
60
61
  formatDateLabel: () => formatDateLabel,
@@ -271,11 +272,35 @@ function detectCycles(tasks) {
271
272
  }
272
273
  return { hasCycle: false };
273
274
  }
275
+ function computeLagFromDates(linkType, predStart, predEnd, succStart, succEnd) {
276
+ const DAY_MS = 24 * 60 * 60 * 1e3;
277
+ const pS = Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate());
278
+ const pE = Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate());
279
+ const sS = Date.UTC(succStart.getUTCFullYear(), succStart.getUTCMonth(), succStart.getUTCDate());
280
+ const sE = Date.UTC(succEnd.getUTCFullYear(), succEnd.getUTCMonth(), succEnd.getUTCDate());
281
+ switch (linkType) {
282
+ case "FS":
283
+ return Math.round((sS - pE) / DAY_MS) - 1;
284
+ case "SS":
285
+ return Math.round((sS - pS) / DAY_MS);
286
+ case "FF":
287
+ return Math.round((sE - pE) / DAY_MS);
288
+ case "SF":
289
+ return Math.round((sE - pS) / DAY_MS) + 1;
290
+ }
291
+ }
274
292
  function calculateSuccessorDate(predecessorStart, predecessorEnd, linkType, lag = 0) {
275
- const baseDate = linkType.startsWith("F") ? predecessorEnd : predecessorStart;
276
- const lagMs = lag * 24 * 60 * 60 * 1e3;
277
- const resultDate = new Date(baseDate.getTime() + lagMs);
278
- return resultDate;
293
+ const DAY_MS = 24 * 60 * 60 * 1e3;
294
+ switch (linkType) {
295
+ case "FS":
296
+ return new Date(predecessorEnd.getTime() + (lag + 1) * DAY_MS);
297
+ case "SS":
298
+ return new Date(predecessorStart.getTime() + lag * DAY_MS);
299
+ case "FF":
300
+ return new Date(predecessorEnd.getTime() + lag * DAY_MS);
301
+ case "SF":
302
+ return new Date(predecessorStart.getTime() + (lag - 1) * DAY_MS);
303
+ }
279
304
  }
280
305
  function validateDependencies(tasks) {
281
306
  const errors = [];
@@ -422,35 +447,10 @@ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks) {
422
447
  return task.dependencies.map((dep) => {
423
448
  const predecessor = taskById.get(dep.taskId);
424
449
  if (!predecessor) return dep;
425
- if (dep.type === "FS") {
426
- const predEnd = new Date(predecessor.endDate);
427
- const lagDays = Math.round(
428
- (Date.UTC(newStartDate.getUTCFullYear(), newStartDate.getUTCMonth(), newStartDate.getUTCDate()) - Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate())) / (24 * 60 * 60 * 1e3)
429
- );
430
- return { ...dep, lag: lagDays };
431
- }
432
- if (dep.type === "SS") {
433
- const predStart = new Date(predecessor.startDate);
434
- const lagDays = Math.max(0, Math.round(
435
- (Date.UTC(newStartDate.getUTCFullYear(), newStartDate.getUTCMonth(), newStartDate.getUTCDate()) - Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate())) / (24 * 60 * 60 * 1e3)
436
- ));
437
- return { ...dep, lag: lagDays };
438
- }
439
- if (dep.type === "FF") {
440
- const predEnd = new Date(predecessor.endDate);
441
- const lagDays = Math.round(
442
- (Date.UTC(newEndDate.getUTCFullYear(), newEndDate.getUTCMonth(), newEndDate.getUTCDate()) - Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate())) / (24 * 60 * 60 * 1e3)
443
- );
444
- return { ...dep, lag: lagDays };
445
- }
446
- if (dep.type === "SF") {
447
- const predStart = new Date(predecessor.startDate);
448
- const lagDays = Math.min(0, Math.round(
449
- (Date.UTC(newEndDate.getUTCFullYear(), newEndDate.getUTCMonth(), newEndDate.getUTCDate()) - Date.UTC(predStart.getUTCFullYear(), predStart.getUTCMonth(), predStart.getUTCDate()) + 24 * 60 * 60 * 1e3) / (24 * 60 * 60 * 1e3)
450
- ));
451
- return { ...dep, lag: lagDays };
452
- }
453
- return dep;
450
+ const predStart = new Date(predecessor.startDate);
451
+ const predEnd = new Date(predecessor.endDate);
452
+ const lagDays = computeLagFromDates(dep.type, predStart, predEnd, newStartDate, newEndDate);
453
+ return { ...dep, lag: lagDays };
454
454
  });
455
455
  }
456
456
  function getAllDependencyEdges(tasks) {
@@ -1477,60 +1477,11 @@ var DragGuideLines_default = DragGuideLines;
1477
1477
  var import_react6 = __toESM(require("react"));
1478
1478
  var import_jsx_runtime6 = require("react/jsx-runtime");
1479
1479
  function calculateEffectiveLag(edge, predPosition, succPosition, monthStart, dayWidth) {
1480
- const predStartDate = pixelsToDate(predPosition.left, monthStart, dayWidth);
1481
- const predEndDate = pixelsToDate(predPosition.right - dayWidth, monthStart, dayWidth);
1482
- const succStartDate = pixelsToDate(succPosition.left, monthStart, dayWidth);
1483
- const succEndDate = pixelsToDate(succPosition.right - dayWidth, monthStart, dayWidth);
1484
- let lagMs = 0;
1485
- switch (edge.type) {
1486
- case "FS":
1487
- lagMs = Date.UTC(
1488
- succStartDate.getUTCFullYear(),
1489
- succStartDate.getUTCMonth(),
1490
- succStartDate.getUTCDate()
1491
- ) - Date.UTC(
1492
- predEndDate.getUTCFullYear(),
1493
- predEndDate.getUTCMonth(),
1494
- predEndDate.getUTCDate()
1495
- ) - 24 * 60 * 60 * 1e3;
1496
- break;
1497
- case "SS":
1498
- lagMs = Date.UTC(
1499
- succStartDate.getUTCFullYear(),
1500
- succStartDate.getUTCMonth(),
1501
- succStartDate.getUTCDate()
1502
- ) - Date.UTC(
1503
- predStartDate.getUTCFullYear(),
1504
- predStartDate.getUTCMonth(),
1505
- predStartDate.getUTCDate()
1506
- );
1507
- break;
1508
- case "FF":
1509
- lagMs = Date.UTC(
1510
- succEndDate.getUTCFullYear(),
1511
- succEndDate.getUTCMonth(),
1512
- succEndDate.getUTCDate()
1513
- ) - Date.UTC(
1514
- predEndDate.getUTCFullYear(),
1515
- predEndDate.getUTCMonth(),
1516
- predEndDate.getUTCDate()
1517
- );
1518
- break;
1519
- case "SF":
1520
- lagMs = Date.UTC(
1521
- succEndDate.getUTCFullYear(),
1522
- succEndDate.getUTCMonth(),
1523
- succEndDate.getUTCDate()
1524
- ) - Date.UTC(
1525
- predStartDate.getUTCFullYear(),
1526
- predStartDate.getUTCMonth(),
1527
- predStartDate.getUTCDate()
1528
- ) + 24 * 60 * 60 * 1e3;
1529
- break;
1530
- default:
1531
- return 0;
1532
- }
1533
- return Math.round(lagMs / (24 * 60 * 60 * 1e3));
1480
+ const predStart = pixelsToDate(predPosition.left, monthStart, dayWidth);
1481
+ const predEnd = pixelsToDate(predPosition.right - dayWidth, monthStart, dayWidth);
1482
+ const succStart = pixelsToDate(succPosition.left, monthStart, dayWidth);
1483
+ const succEnd = pixelsToDate(succPosition.right - dayWidth, monthStart, dayWidth);
1484
+ return computeLagFromDates(edge.type, predStart, predEnd, succStart, succEnd);
1534
1485
  }
1535
1486
  var DependencyLines = import_react6.default.memo(({
1536
1487
  tasks,
@@ -1538,7 +1489,8 @@ var DependencyLines = import_react6.default.memo(({
1538
1489
  dayWidth,
1539
1490
  rowHeight,
1540
1491
  gridWidth,
1541
- dragOverrides
1492
+ dragOverrides,
1493
+ selectedDep
1542
1494
  }) => {
1543
1495
  const { taskPositions, taskIndices } = (0, import_react6.useMemo)(() => {
1544
1496
  const positions = /* @__PURE__ */ new Map();
@@ -1652,30 +1604,60 @@ var DependencyLines = import_react6.default.memo(({
1652
1604
  }
1653
1605
  )
1654
1606
  }
1655
- )
1656
- ] }),
1657
- lines.map(({ id, path, hasCycle, lag, fromX, toX, fromY, reverseOrder }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react6.default.Fragment, { children: [
1658
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1659
- "path",
1660
- {
1661
- d: path,
1662
- className: hasCycle ? "gantt-dependency-path gantt-dependency-cycle" : "gantt-dependency-path",
1663
- markerEnd: hasCycle ? "url(#arrowhead-cycle)" : "url(#arrowhead)"
1664
- }
1665
1607
  ),
1666
- lag !== 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1667
- "text",
1608
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1609
+ "marker",
1668
1610
  {
1669
- className: "gantt-dependency-lag-label",
1670
- x: lag < 0 ? toX + 14 : toX - 14,
1671
- y: reverseOrder ? fromY - 4 : fromY + 12,
1672
- textAnchor: "middle",
1673
- fontSize: "10",
1674
- fill: hasCycle ? "var(--gantt-dependency-cycle-color, #ef4444)" : "var(--gantt-dependency-line-color, #666666)",
1675
- children: lag > 0 ? `+${lag}` : `${lag}`
1611
+ id: "arrowhead-selected",
1612
+ markerWidth: "8",
1613
+ markerHeight: "6",
1614
+ markerUnits: "userSpaceOnUse",
1615
+ refX: "7",
1616
+ refY: "3",
1617
+ orient: "auto",
1618
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1619
+ "polygon",
1620
+ {
1621
+ points: "0 0, 8 3, 0 6",
1622
+ fill: "#ef4444"
1623
+ }
1624
+ )
1676
1625
  }
1677
1626
  )
1678
- ] }, id))
1627
+ ] }),
1628
+ lines.map(({ id, path, hasCycle, lag, fromX, toX, fromY, reverseOrder }) => {
1629
+ const isSelected = selectedDep != null && id === `${selectedDep.predecessorId}-${selectedDep.successorId}-${selectedDep.linkType}`;
1630
+ let pathClassName = "gantt-dependency-path";
1631
+ if (isSelected) pathClassName += " gantt-dependency-selected";
1632
+ else if (hasCycle) pathClassName += " gantt-dependency-cycle";
1633
+ let markerEnd;
1634
+ if (isSelected) markerEnd = "url(#arrowhead-selected)";
1635
+ else if (hasCycle) markerEnd = "url(#arrowhead-cycle)";
1636
+ else markerEnd = "url(#arrowhead)";
1637
+ const lagColor = isSelected ? "#ef4444" : hasCycle ? "var(--gantt-dependency-cycle-color, #ef4444)" : "var(--gantt-dependency-line-color, #666666)";
1638
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react6.default.Fragment, { children: [
1639
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1640
+ "path",
1641
+ {
1642
+ d: path,
1643
+ className: pathClassName,
1644
+ markerEnd
1645
+ }
1646
+ ),
1647
+ lag !== 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1648
+ "text",
1649
+ {
1650
+ className: "gantt-dependency-lag-label",
1651
+ x: lag < 0 ? toX + 14 : toX - 14,
1652
+ y: reverseOrder ? fromY - 4 : fromY + 12,
1653
+ textAnchor: "middle",
1654
+ fontSize: "10",
1655
+ fill: lagColor,
1656
+ children: lag > 0 ? `+${lag}` : `${lag}`
1657
+ }
1658
+ )
1659
+ ] }, id);
1660
+ })
1679
1661
  ]
1680
1662
  }
1681
1663
  );
@@ -1684,17 +1666,49 @@ DependencyLines.displayName = "DependencyLines";
1684
1666
  var DependencyLines_default = DependencyLines;
1685
1667
 
1686
1668
  // src/components/TaskList/TaskList.tsx
1687
- var import_react11 = require("react");
1669
+ var import_react11 = __toESM(require("react"));
1670
+
1671
+ // src/components/ui/Popover.tsx
1672
+ var RadixPopover = __toESM(require("@radix-ui/react-popover"));
1673
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1674
+ var Popover = ({ open, onOpenChange, children }) => {
1675
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RadixPopover.Root, { open, onOpenChange, children });
1676
+ };
1677
+ var PopoverTrigger = RadixPopover.Trigger;
1678
+ var PopoverContent = ({
1679
+ children,
1680
+ className,
1681
+ align = "start",
1682
+ side = "bottom",
1683
+ portal = true,
1684
+ collisionPadding = 8
1685
+ }) => {
1686
+ const content = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1687
+ RadixPopover.Content,
1688
+ {
1689
+ className: `gantt-popover${className ? ` ${className}` : ""}`,
1690
+ align,
1691
+ side,
1692
+ collisionPadding,
1693
+ sideOffset: 4,
1694
+ children
1695
+ }
1696
+ );
1697
+ if (portal) {
1698
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(RadixPopover.Portal, { children: content });
1699
+ }
1700
+ return content;
1701
+ };
1688
1702
 
1689
1703
  // src/components/TaskList/TaskListRow.tsx
1690
1704
  var import_react10 = __toESM(require("react"));
1691
1705
 
1692
1706
  // src/components/ui/Input.tsx
1693
1707
  var import_react7 = __toESM(require("react"));
1694
- var import_jsx_runtime7 = require("react/jsx-runtime");
1708
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1695
1709
  var Input = import_react7.default.forwardRef(
1696
1710
  ({ className, ...props }, ref) => {
1697
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1711
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1698
1712
  "input",
1699
1713
  {
1700
1714
  ref,
@@ -1714,7 +1728,7 @@ var import_date_fns3 = require("date-fns");
1714
1728
  var import_react8 = require("react");
1715
1729
  var import_date_fns2 = require("date-fns");
1716
1730
  var import_locale2 = require("date-fns/locale");
1717
- var import_jsx_runtime8 = require("react/jsx-runtime");
1731
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1718
1732
  function getDayClassName(day, selected) {
1719
1733
  const classes = ["gantt-day-btn"];
1720
1734
  if (selected && (0, import_date_fns2.isSameDay)(day, selected)) classes.push("selected");
@@ -1784,12 +1798,12 @@ var Calendar = ({
1784
1798
  const emptyDays = ((0, import_date_fns2.getDay)(firstDay) + 6) % 7;
1785
1799
  const monthKey = (0, import_date_fns2.format)(month, "yyyy-MM");
1786
1800
  const monthLabel = (0, import_date_fns2.format)(month, "LLLL yyyy", { locale: import_locale2.ru });
1787
- const emptyCells = Array.from({ length: emptyDays }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "gantt-cal-empty-day" }, `e-${i}`));
1801
+ const emptyCells = Array.from({ length: emptyDays }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "gantt-cal-empty-day" }, `e-${i}`));
1788
1802
  const dayCells = Array.from({ length: totalDays }, (_, i) => {
1789
1803
  const dayNum = i + 1;
1790
1804
  const day = new Date(month.getFullYear(), month.getMonth(), dayNum);
1791
1805
  const className = getDayClassName(day, selected);
1792
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1806
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1793
1807
  "button",
1794
1808
  {
1795
1809
  type: "button",
@@ -1805,9 +1819,9 @@ var Calendar = ({
1805
1819
  dayNum
1806
1820
  );
1807
1821
  });
1808
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "gantt-cal-month", "data-month": monthKey, children: [
1809
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "gantt-cal-month-header", children: monthLabel }),
1810
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "gantt-cal-month-days", children: [
1822
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "gantt-cal-month", "data-month": monthKey, children: [
1823
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "gantt-cal-month-header", children: monthLabel }),
1824
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "gantt-cal-month-days", children: [
1811
1825
  emptyCells,
1812
1826
  dayCells
1813
1827
  ] })
@@ -1819,42 +1833,10 @@ var Calendar = ({
1819
1833
  () => months.map(renderMonth),
1820
1834
  [months, renderMonth]
1821
1835
  );
1822
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { ref: scrollRef, className: "gantt-cal-container", children: renderedMonths });
1836
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ref: scrollRef, className: "gantt-cal-container", children: renderedMonths });
1823
1837
  };
1824
1838
  Calendar.displayName = "Calendar";
1825
1839
 
1826
- // src/components/ui/Popover.tsx
1827
- var RadixPopover = __toESM(require("@radix-ui/react-popover"));
1828
- var import_jsx_runtime9 = require("react/jsx-runtime");
1829
- var Popover = ({ open, onOpenChange, children }) => {
1830
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RadixPopover.Root, { open, onOpenChange, children });
1831
- };
1832
- var PopoverTrigger = RadixPopover.Trigger;
1833
- var PopoverContent = ({
1834
- children,
1835
- className,
1836
- align = "start",
1837
- side = "bottom",
1838
- portal = true,
1839
- collisionPadding = 8
1840
- }) => {
1841
- const content = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1842
- RadixPopover.Content,
1843
- {
1844
- className: `gantt-popover${className ? ` ${className}` : ""}`,
1845
- align,
1846
- side,
1847
- collisionPadding,
1848
- sideOffset: 4,
1849
- children
1850
- }
1851
- );
1852
- if (portal) {
1853
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RadixPopover.Portal, { children: content });
1854
- }
1855
- return content;
1856
- };
1857
-
1858
1840
  // src/components/ui/DatePicker.tsx
1859
1841
  var import_jsx_runtime10 = require("react/jsx-runtime");
1860
1842
  var DatePicker = ({
@@ -1919,19 +1901,182 @@ var DatePicker = ({
1919
1901
  };
1920
1902
  DatePicker.displayName = "DatePicker";
1921
1903
 
1922
- // src/components/TaskList/TaskListRow.tsx
1904
+ // src/components/TaskList/DepIcons.tsx
1923
1905
  var import_jsx_runtime11 = require("react/jsx-runtime");
1906
+ var DepIconFS = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
1907
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m10 15 5 5 5-5" }),
1908
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M4 4h7a4 4 0 0 1 4 4v12" })
1909
+ ] });
1910
+ var DepIconSS = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
1911
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M3 5v14" }),
1912
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M21 12H7" }),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m15 18 6-6-6-6" })
1914
+ ] });
1915
+ var DepIconFF = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
1916
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M17 12H3" }),
1917
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m11 18 6-6-6-6" }),
1918
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M21 5v14" })
1919
+ ] });
1920
+ var DepIconSF = () => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
1921
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "m14 15-5 5-5-5" }),
1922
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("path", { d: "M20 4h-7a4 4 0 0 0-4 4v12" })
1923
+ ] });
1924
+ var LINK_TYPE_ICONS = {
1925
+ FS: DepIconFS,
1926
+ SS: DepIconSS,
1927
+ FF: DepIconFF,
1928
+ SF: DepIconSF
1929
+ };
1930
+ var LINK_TYPE_LABELS = {
1931
+ FS: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435-\u043D\u0430\u0447\u0430\u043B\u043E",
1932
+ SS: "\u041D\u0430\u0447\u0430\u043B\u043E-\u043D\u0430\u0447\u0430\u043B\u043E",
1933
+ FF: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435-\u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435",
1934
+ SF: "\u041D\u0430\u0447\u0430\u043B\u043E-\u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435"
1935
+ };
1936
+
1937
+ // src/components/TaskList/TaskListRow.tsx
1938
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1939
+ var TrashIcon = () => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("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: [
1940
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
1941
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M3 6h18" }),
1942
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
1943
+ ] });
1944
+ function formatDepDescription(type, lag) {
1945
+ const effectiveLag = lag ?? 0;
1946
+ if (type === "FS") {
1947
+ 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`;
1948
+ 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`;
1949
+ 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`;
1950
+ }
1951
+ if (type === "FF") {
1952
+ 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`;
1953
+ 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`;
1954
+ return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
1955
+ }
1956
+ if (type === "SS") {
1957
+ 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`;
1958
+ 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`;
1959
+ return `\u041D\u0430\u0447\u0430\u0442\u044C \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u0430\u0447\u0430\u043B\u043E\u043C`;
1960
+ }
1961
+ if (type === "SF") {
1962
+ 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`;
1963
+ 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`;
1964
+ return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0434\u043E \u043D\u0430\u0447\u0430\u043B\u0430`;
1965
+ }
1966
+ return "";
1967
+ }
1968
+ var DepChip = ({
1969
+ lag,
1970
+ dep,
1971
+ taskId,
1972
+ predecessorName,
1973
+ selectedChip,
1974
+ disableDependencyEditing,
1975
+ onChipSelect,
1976
+ onRowClick,
1977
+ onScrollToTask,
1978
+ onRemoveDependency,
1979
+ onChipSelectClear
1980
+ }) => {
1981
+ const isSelected = selectedChip?.successorId === taskId && selectedChip?.predecessorId === dep.taskId && selectedChip?.linkType === dep.type;
1982
+ const handleClick = (e) => {
1983
+ e.stopPropagation();
1984
+ if (disableDependencyEditing) return;
1985
+ onChipSelect?.(isSelected ? null : { successorId: taskId, predecessorId: dep.taskId, linkType: dep.type });
1986
+ if (!isSelected) {
1987
+ onRowClick?.(taskId);
1988
+ onScrollToTask?.(taskId);
1989
+ }
1990
+ };
1991
+ const handleTrashClick = (e) => {
1992
+ e.stopPropagation();
1993
+ onRemoveDependency?.(taskId, dep.taskId, dep.type);
1994
+ onChipSelectClear();
1995
+ };
1996
+ const Icon = LINK_TYPE_ICONS[dep.type];
1997
+ const depPrefix = formatDepDescription(dep.type, lag);
1998
+ const depName = predecessorName ?? dep.taskId;
1999
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: isSelected, onOpenChange: (open) => {
2000
+ if (!open) onChipSelectClear();
2001
+ }, children: [
2002
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "gantt-tl-dep-chip-wrapper", children: [
2003
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2004
+ "span",
2005
+ {
2006
+ className: `gantt-tl-dep-chip${isSelected ? " gantt-tl-dep-chip-selected" : ""}`,
2007
+ onClick: handleClick,
2008
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2009
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, {}),
2010
+ lag != null && lag !== 0 ? lag > 0 ? `+${lag}` : `${lag}` : ""
2011
+ ] })
2012
+ }
2013
+ ) }),
2014
+ !disableDependencyEditing && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2015
+ "button",
2016
+ {
2017
+ type: "button",
2018
+ className: "gantt-tl-dep-chip-trash",
2019
+ "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
2020
+ onClick: handleTrashClick,
2021
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TrashIcon, {})
2022
+ }
2023
+ )
2024
+ ] }),
2025
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(PopoverContent, { portal: true, side: "bottom", align: "start", className: "gantt-tl-dep-info-popover", children: [
2026
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-dep-info-prefix", children: depPrefix }),
2027
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-dep-info-name", children: depName })
2028
+ ] })
2029
+ ] });
2030
+ };
1924
2031
  var toISODate = (value) => {
1925
2032
  if (value instanceof Date) return value.toISOString().split("T")[0];
1926
2033
  if (typeof value === "string" && value.includes("T")) return value.split("T")[0];
1927
2034
  return value;
1928
2035
  };
1929
2036
  var TaskListRow = import_react10.default.memo(
1930
- ({ task, rowIndex, rowHeight, onTaskChange, selectedTaskId, onRowClick, disableTaskNameEditing = false }) => {
2037
+ ({
2038
+ task,
2039
+ rowIndex,
2040
+ rowHeight,
2041
+ onTaskChange,
2042
+ selectedTaskId,
2043
+ onRowClick,
2044
+ disableTaskNameEditing = false,
2045
+ disableDependencyEditing = false,
2046
+ allTasks = [],
2047
+ activeLinkType,
2048
+ selectingPredecessorFor,
2049
+ onSetSelectingPredecessorFor,
2050
+ onAddDependency,
2051
+ onRemoveDependency,
2052
+ selectedChip,
2053
+ onChipSelect,
2054
+ onScrollToTask
2055
+ }) => {
1931
2056
  const [editingName, setEditingName] = (0, import_react10.useState)(false);
1932
2057
  const [nameValue, setNameValue] = (0, import_react10.useState)("");
1933
2058
  const nameInputRef = (0, import_react10.useRef)(null);
2059
+ const [overflowOpen, setOverflowOpen] = (0, import_react10.useState)(false);
1934
2060
  const isSelected = selectedTaskId === task.id;
2061
+ const isPicking = selectingPredecessorFor != null;
2062
+ const isSourceRow = isPicking && selectingPredecessorFor === task.id;
2063
+ const chips = (0, import_react10.useMemo)(() => {
2064
+ const succStart = new Date(task.startDate);
2065
+ const succEnd = new Date(task.endDate);
2066
+ const taskById = new Map((allTasks ?? []).map((t) => [t.id, t]));
2067
+ return (task.dependencies ?? []).map((dep) => {
2068
+ const pred = taskById.get(dep.taskId);
2069
+ const lag = pred ? computeLagFromDates(
2070
+ dep.type,
2071
+ new Date(pred.startDate),
2072
+ new Date(pred.endDate),
2073
+ succStart,
2074
+ succEnd
2075
+ ) : dep.lag ?? 0;
2076
+ return { dep, lag, predecessorName: pred?.name ?? dep.taskId };
2077
+ });
2078
+ }, [task.dependencies, task.startDate, task.endDate, allTasks]);
2079
+ const linkWord = chips.length <= 4 ? "\u0441\u0432\u044F\u0437\u0438" : "\u0441\u0432\u044F\u0437\u0435\u0439";
1935
2080
  (0, import_react10.useEffect)(() => {
1936
2081
  if (editingName && nameInputRef.current) {
1937
2082
  nameInputRef.current.focus();
@@ -1977,18 +2122,60 @@ var TaskListRow = import_react10.default.memo(
1977
2122
  const handleRowClickInternal = (0, import_react10.useCallback)(() => {
1978
2123
  onRowClick?.(task.id);
1979
2124
  }, [task.id, onRowClick]);
2125
+ const handleNumberClick = (0, import_react10.useCallback)((e) => {
2126
+ e.stopPropagation();
2127
+ onRowClick?.(task.id);
2128
+ onScrollToTask?.(task.id);
2129
+ }, [task.id, onRowClick, onScrollToTask]);
2130
+ const handleAddClick = (0, import_react10.useCallback)((e) => {
2131
+ e.stopPropagation();
2132
+ onSetSelectingPredecessorFor?.(task.id);
2133
+ }, [task.id, onSetSelectingPredecessorFor]);
2134
+ const handlePredecessorPick = (0, import_react10.useCallback)((e) => {
2135
+ e.stopPropagation();
2136
+ if (!isPicking || isSourceRow) return;
2137
+ if (!selectingPredecessorFor || !activeLinkType) return;
2138
+ onAddDependency?.(task.id, selectingPredecessorFor, activeLinkType);
2139
+ }, [isPicking, isSourceRow, selectingPredecessorFor, task.id, activeLinkType, onAddDependency]);
2140
+ const isSelectedPredecessor = selectedChip != null && selectedChip.predecessorId === task.id;
2141
+ const handleDeleteSelected = (0, import_react10.useCallback)((e) => {
2142
+ e.stopPropagation();
2143
+ if (!selectedChip) return;
2144
+ onRemoveDependency?.(selectedChip.successorId, selectedChip.predecessorId, selectedChip.linkType);
2145
+ onChipSelect?.(null);
2146
+ }, [selectedChip, onRemoveDependency, onChipSelect]);
1980
2147
  const startDateISO = toISODate(task.startDate);
1981
2148
  const endDateISO = toISODate(task.endDate);
1982
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2149
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1983
2150
  "div",
1984
2151
  {
1985
- className: `gantt-tl-row ${isSelected ? "gantt-tl-row-selected" : ""}`,
2152
+ className: [
2153
+ "gantt-tl-row",
2154
+ isSelected ? "gantt-tl-row-selected" : "",
2155
+ isPicking && !isSourceRow ? "gantt-tl-row-picking" : "",
2156
+ isSourceRow ? "gantt-tl-row-picking-self" : ""
2157
+ ].filter(Boolean).join(" "),
1986
2158
  style: { minHeight: `${rowHeight}px` },
1987
2159
  onClick: handleRowClickInternal,
1988
2160
  children: [
1989
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "gantt-tl-cell gantt-tl-cell-number", children: rowIndex + 1 }),
1990
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "gantt-tl-cell gantt-tl-cell-name", children: [
1991
- editingName && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2161
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2162
+ "div",
2163
+ {
2164
+ className: "gantt-tl-cell gantt-tl-cell-number",
2165
+ onClick: handleNumberClick,
2166
+ title: "\u041F\u0435\u0440\u0435\u0439\u0442\u0438 \u043A \u0440\u0430\u0431\u043E\u0442\u0435",
2167
+ children: [
2168
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-num-label", children: rowIndex + 1 }),
2169
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("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: [
2170
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M17 12H3" }),
2171
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "m11 18 6-6-6-6" }),
2172
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("path", { d: "M21 5v14" })
2173
+ ] })
2174
+ ]
2175
+ }
2176
+ ),
2177
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-cell gantt-tl-cell-name", children: [
2178
+ editingName && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1992
2179
  Input,
1993
2180
  {
1994
2181
  ref: nameInputRef,
@@ -2001,7 +2188,7 @@ var TaskListRow = import_react10.default.memo(
2001
2188
  onClick: (e) => e.stopPropagation()
2002
2189
  }
2003
2190
  ),
2004
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2191
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2005
2192
  "button",
2006
2193
  {
2007
2194
  type: "button",
@@ -2012,7 +2199,7 @@ var TaskListRow = import_react10.default.memo(
2012
2199
  }
2013
2200
  )
2014
2201
  ] }),
2015
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2202
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2016
2203
  DatePicker,
2017
2204
  {
2018
2205
  value: startDateISO,
@@ -2022,7 +2209,7 @@ var TaskListRow = import_react10.default.memo(
2022
2209
  disabled: task.locked
2023
2210
  }
2024
2211
  ) }),
2025
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2212
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2026
2213
  DatePicker,
2027
2214
  {
2028
2215
  value: endDateISO,
@@ -2031,7 +2218,97 @@ var TaskListRow = import_react10.default.memo(
2031
2218
  portal: true,
2032
2219
  disabled: task.locked
2033
2220
  }
2034
- ) })
2221
+ ) }),
2222
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2223
+ "div",
2224
+ {
2225
+ className: "gantt-tl-cell gantt-tl-cell-deps",
2226
+ onClick: isPicking && !isSourceRow ? handlePredecessorPick : void 0,
2227
+ children: isSourceRow ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-dep-source-hint", children: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0437\u0430\u0434\u0430\u0447\u0443" }) : isSelectedPredecessor && !disableDependencyEditing ? (
2228
+ /* Full-replacement: "Зависит от [name]" → hover → "Удалить" */
2229
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2230
+ "button",
2231
+ {
2232
+ type: "button",
2233
+ className: "gantt-tl-dep-delete-label",
2234
+ onClick: handleDeleteSelected,
2235
+ "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
2236
+ children: [
2237
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-dep-delete-label-default", children: "\u0421\u0432\u044F\u0437\u0430\u043D\u043E \u0441" }),
2238
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "gantt-tl-dep-delete-label-hover", children: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C" })
2239
+ ]
2240
+ }
2241
+ )
2242
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2243
+ chips.length >= 2 ? (
2244
+ /* 2+ deps — show only "N связей" summary chip that opens a popover */
2245
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Popover, { open: overflowOpen, onOpenChange: setOverflowOpen, children: [
2246
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2247
+ "button",
2248
+ {
2249
+ type: "button",
2250
+ className: "gantt-tl-dep-summary-chip",
2251
+ onClick: (e) => {
2252
+ e.stopPropagation();
2253
+ setOverflowOpen((v) => !v);
2254
+ },
2255
+ children: [
2256
+ chips.length,
2257
+ " ",
2258
+ linkWord
2259
+ ]
2260
+ }
2261
+ ) }),
2262
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PopoverContent, { portal: true, align: "start", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-dep-overflow-list", onClick: (e) => e.stopPropagation(), children: chips.map(({ dep, lag, predecessorName }) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2263
+ DepChip,
2264
+ {
2265
+ lag,
2266
+ dep,
2267
+ taskId: task.id,
2268
+ predecessorName,
2269
+ selectedChip,
2270
+ disableDependencyEditing,
2271
+ onChipSelect,
2272
+ onRowClick,
2273
+ onScrollToTask,
2274
+ onRemoveDependency,
2275
+ onChipSelectClear: () => onChipSelect?.(null)
2276
+ },
2277
+ `${dep.taskId}-${dep.type}`
2278
+ )) }) })
2279
+ ] })
2280
+ ) : chips.length === 1 ? (
2281
+ /* Single chip — unified DepChip */
2282
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2283
+ DepChip,
2284
+ {
2285
+ lag: chips[0].lag,
2286
+ dep: chips[0].dep,
2287
+ taskId: task.id,
2288
+ predecessorName: chips[0].predecessorName,
2289
+ selectedChip,
2290
+ disableDependencyEditing,
2291
+ onChipSelect,
2292
+ onRowClick,
2293
+ onScrollToTask,
2294
+ onRemoveDependency,
2295
+ onChipSelectClear: () => onChipSelect?.(null)
2296
+ }
2297
+ )
2298
+ ) : null,
2299
+ !disableDependencyEditing && !isPicking && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2300
+ "button",
2301
+ {
2302
+ type: "button",
2303
+ className: "gantt-tl-dep-add",
2304
+ onClick: handleAddClick,
2305
+ "aria-label": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
2306
+ children: "+"
2307
+ }
2308
+ )
2309
+ ] })
2310
+ }
2311
+ )
2035
2312
  ]
2036
2313
  }
2037
2314
  );
@@ -2040,7 +2317,8 @@ var TaskListRow = import_react10.default.memo(
2040
2317
  TaskListRow.displayName = "TaskListRow";
2041
2318
 
2042
2319
  // src/components/TaskList/TaskList.tsx
2043
- var import_jsx_runtime12 = require("react/jsx-runtime");
2320
+ var import_jsx_runtime13 = require("react/jsx-runtime");
2321
+ var LINK_TYPE_ORDER = ["FS", "SS", "FF", "SF"];
2044
2322
  var TaskList = ({
2045
2323
  tasks,
2046
2324
  rowHeight,
@@ -2050,7 +2328,10 @@ var TaskList = ({
2050
2328
  selectedTaskId,
2051
2329
  onTaskSelect,
2052
2330
  show = true,
2053
- disableTaskNameEditing = false
2331
+ disableTaskNameEditing = false,
2332
+ disableDependencyEditing = false,
2333
+ onScrollToTask,
2334
+ onSelectedChipChange
2054
2335
  }) => {
2055
2336
  const totalHeight = (0, import_react11.useMemo)(
2056
2337
  () => tasks.length * rowHeight,
@@ -2059,19 +2340,144 @@ var TaskList = ({
2059
2340
  const handleRowClick = (0, import_react11.useCallback)((taskId) => {
2060
2341
  onTaskSelect?.(taskId);
2061
2342
  }, [onTaskSelect]);
2062
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2343
+ const [activeLinkType, setActiveLinkType] = (0, import_react11.useState)("FS");
2344
+ const [selectingPredecessorFor, setSelectingPredecessorFor] = (0, import_react11.useState)(null);
2345
+ const [typeMenuOpen, setTypeMenuOpen] = (0, import_react11.useState)(false);
2346
+ const [cycleError, setCycleError] = (0, import_react11.useState)(false);
2347
+ const overlayRef = (0, import_react11.useRef)(null);
2348
+ const [selectedChip, setSelectedChip] = (0, import_react11.useState)(null);
2349
+ const handleChipSelect = (0, import_react11.useCallback)((chip) => {
2350
+ setSelectedChip(chip);
2351
+ onSelectedChipChange?.(chip);
2352
+ }, [onSelectedChipChange]);
2353
+ (0, import_react11.useEffect)(() => {
2354
+ if (!selectingPredecessorFor && !selectedChip) return;
2355
+ const handleKeyDown = (e) => {
2356
+ if (e.key === "Escape") {
2357
+ setSelectingPredecessorFor(null);
2358
+ setSelectedChip(null);
2359
+ onSelectedChipChange?.(null);
2360
+ }
2361
+ };
2362
+ const handleMouseDown = (e) => {
2363
+ const target = e.target;
2364
+ if (overlayRef.current?.contains(target)) return;
2365
+ if (target.closest?.(".gantt-popover")) return;
2366
+ setSelectingPredecessorFor(null);
2367
+ setSelectedChip(null);
2368
+ onSelectedChipChange?.(null);
2369
+ };
2370
+ document.addEventListener("keydown", handleKeyDown);
2371
+ document.addEventListener("mousedown", handleMouseDown, true);
2372
+ return () => {
2373
+ document.removeEventListener("keydown", handleKeyDown);
2374
+ document.removeEventListener("mousedown", handleMouseDown, true);
2375
+ };
2376
+ }, [selectingPredecessorFor, selectedChip, onSelectedChipChange]);
2377
+ const handleAddDependency = (0, import_react11.useCallback)((successorTaskId, predecessorTaskId, linkType) => {
2378
+ if (successorTaskId === predecessorTaskId) return;
2379
+ const successor = tasks.find((t) => t.id === successorTaskId);
2380
+ if (!successor) return;
2381
+ const alreadyExists = (successor.dependencies ?? []).some(
2382
+ (d) => d.taskId === predecessorTaskId && d.type === linkType
2383
+ );
2384
+ if (alreadyExists) {
2385
+ setSelectingPredecessorFor(null);
2386
+ return;
2387
+ }
2388
+ const newDep = { taskId: predecessorTaskId, type: linkType, lag: 0 };
2389
+ const hypothetical = tasks.map(
2390
+ (t) => t.id === successorTaskId ? { ...t, dependencies: [...t.dependencies ?? [], newDep] } : t
2391
+ );
2392
+ const validation = validateDependencies(hypothetical);
2393
+ if (!validation.isValid) {
2394
+ setCycleError(true);
2395
+ setTimeout(() => setCycleError(false), 3e3);
2396
+ return;
2397
+ }
2398
+ const updatedTask = hypothetical.find((t) => t.id === successorTaskId);
2399
+ const predecessor = tasks.find((t) => t.id === predecessorTaskId);
2400
+ if (predecessor) {
2401
+ const predStart = new Date(predecessor.startDate);
2402
+ const predEnd = new Date(predecessor.endDate);
2403
+ const constraintDate = calculateSuccessorDate(predStart, predEnd, linkType, 0);
2404
+ const origSuccessor = tasks.find((t) => t.id === successorTaskId);
2405
+ const durationMs = new Date(origSuccessor.endDate).getTime() - new Date(origSuccessor.startDate).getTime();
2406
+ let newStart;
2407
+ let newEnd;
2408
+ if (linkType === "FS" || linkType === "SS") {
2409
+ newStart = constraintDate;
2410
+ newEnd = new Date(constraintDate.getTime() + durationMs);
2411
+ } else {
2412
+ newEnd = constraintDate;
2413
+ newStart = new Date(constraintDate.getTime() - durationMs);
2414
+ }
2415
+ const snappedTask = {
2416
+ ...updatedTask,
2417
+ startDate: newStart.toISOString().split("T")[0],
2418
+ endDate: newEnd.toISOString().split("T")[0]
2419
+ };
2420
+ onTaskChange?.(snappedTask);
2421
+ } else {
2422
+ onTaskChange?.(updatedTask);
2423
+ }
2424
+ setSelectingPredecessorFor(null);
2425
+ }, [tasks, onTaskChange]);
2426
+ const handleRemoveDependency = (0, import_react11.useCallback)((taskId, predecessorTaskId, linkType) => {
2427
+ const task = tasks.find((t) => t.id === taskId);
2428
+ if (!task) return;
2429
+ const updatedDeps = (task.dependencies ?? []).filter(
2430
+ (d) => !(d.taskId === predecessorTaskId && d.type === linkType)
2431
+ );
2432
+ onTaskChange?.({ ...task, dependencies: updatedDeps });
2433
+ }, [tasks, onTaskChange]);
2434
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2063
2435
  "div",
2064
2436
  {
2437
+ ref: overlayRef,
2065
2438
  className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}`,
2066
2439
  style: { width: `${taskListWidth}px` },
2067
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-table", children: [
2068
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: [
2069
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-number", children: "\u2116" }),
2070
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-name", children: "\u0418\u043C\u044F" }),
2071
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041D\u0430\u0447\u0430\u043B\u043E" }),
2072
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435" })
2440
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "gantt-tl-table", children: [
2441
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: [
2442
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-number", children: "\u2116" }),
2443
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-name", children: "\u0418\u043C\u044F" }),
2444
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041D\u0430\u0447\u0430\u043B\u043E" }),
2445
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435" }),
2446
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "gantt-tl-headerCell gantt-tl-cell-deps", style: { position: "relative" }, children: [
2447
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
2448
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2449
+ "button",
2450
+ {
2451
+ className: "gantt-tl-dep-type-trigger",
2452
+ disabled: disableDependencyEditing,
2453
+ onClick: (e) => e.stopPropagation(),
2454
+ children: [
2455
+ "\u0421\u0432\u044F\u0437\u0438 ",
2456
+ import_react11.default.createElement(LINK_TYPE_ICONS[activeLinkType]),
2457
+ " \u25BE"
2458
+ ]
2459
+ }
2460
+ ) }),
2461
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverContent, { portal: true, align: "start", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-tl-dep-type-menu", children: LINK_TYPE_ORDER.map((lt) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2462
+ "button",
2463
+ {
2464
+ className: `gantt-tl-dep-type-option${activeLinkType === lt ? " active" : ""}`,
2465
+ onClick: () => {
2466
+ setActiveLinkType(lt);
2467
+ setTypeMenuOpen(false);
2468
+ },
2469
+ children: [
2470
+ import_react11.default.createElement(LINK_TYPE_ICONS[lt]),
2471
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: LINK_TYPE_LABELS[lt] })
2472
+ ]
2473
+ },
2474
+ lt
2475
+ )) }) })
2476
+ ] }),
2477
+ cycleError && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-tl-dep-error", children: "\u0426\u0438\u043A\u043B \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0435\u0439!" })
2478
+ ] })
2073
2479
  ] }),
2074
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "gantt-tl-body", style: { height: `${totalHeight}px` }, children: tasks.map((task, index) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2480
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-tl-body", style: { height: `${totalHeight}px` }, children: tasks.map((task, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2075
2481
  TaskListRow,
2076
2482
  {
2077
2483
  task,
@@ -2080,7 +2486,17 @@ var TaskList = ({
2080
2486
  onTaskChange,
2081
2487
  selectedTaskId,
2082
2488
  onRowClick: handleRowClick,
2083
- disableTaskNameEditing
2489
+ disableTaskNameEditing,
2490
+ disableDependencyEditing,
2491
+ allTasks: tasks,
2492
+ activeLinkType,
2493
+ selectingPredecessorFor,
2494
+ onSetSelectingPredecessorFor: setSelectingPredecessorFor,
2495
+ onAddDependency: handleAddDependency,
2496
+ onRemoveDependency: handleRemoveDependency,
2497
+ selectedChip,
2498
+ onChipSelect: handleChipSelect,
2499
+ onScrollToTask
2084
2500
  },
2085
2501
  task.id
2086
2502
  )) })
@@ -2090,7 +2506,7 @@ var TaskList = ({
2090
2506
  };
2091
2507
 
2092
2508
  // src/components/GanttChart/GanttChart.tsx
2093
- var import_jsx_runtime13 = require("react/jsx-runtime");
2509
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2094
2510
  var GanttChart = (0, import_react12.forwardRef)(({
2095
2511
  tasks,
2096
2512
  dayWidth = 40,
@@ -2104,10 +2520,12 @@ var GanttChart = (0, import_react12.forwardRef)(({
2104
2520
  onCascade,
2105
2521
  showTaskList = false,
2106
2522
  taskListWidth = 520,
2107
- disableTaskNameEditing = false
2523
+ disableTaskNameEditing = false,
2524
+ disableDependencyEditing = false
2108
2525
  }, ref) => {
2109
2526
  const scrollContainerRef = (0, import_react12.useRef)(null);
2110
2527
  const [selectedTaskId, setSelectedTaskId] = (0, import_react12.useState)(null);
2528
+ const [selectedChip, setSelectedChip] = (0, import_react12.useState)(null);
2111
2529
  const dateRange = (0, import_react12.useMemo)(() => getMultiMonthDays(tasks), [tasks]);
2112
2530
  const [validationResult, setValidationResult] = (0, import_react12.useState)(null);
2113
2531
  const [cascadeOverrides, setCascadeOverrides] = (0, import_react12.useState)(/* @__PURE__ */ new Map());
@@ -2155,12 +2573,30 @@ var GanttChart = (0, import_react12.forwardRef)(({
2155
2573
  const scrollLeft = Math.round(todayOffset - containerWidth / 2 + dayWidth / 2);
2156
2574
  container.scrollLeft = Math.max(0, scrollLeft);
2157
2575
  }, [dateRange, dayWidth]);
2576
+ const scrollToTask = (0, import_react12.useCallback)((taskId) => {
2577
+ const container = scrollContainerRef.current;
2578
+ if (!container || dateRange.length === 0) return;
2579
+ const task = tasks.find((t) => t.id === taskId);
2580
+ if (!task) return;
2581
+ const taskStart = new Date(task.startDate);
2582
+ const taskStartUTC = new Date(Date.UTC(
2583
+ taskStart.getUTCFullYear(),
2584
+ taskStart.getUTCMonth(),
2585
+ taskStart.getUTCDate()
2586
+ ));
2587
+ const taskIndex = dateRange.findIndex((day) => day.getTime() === taskStartUTC.getTime());
2588
+ if (taskIndex === -1) return;
2589
+ const taskOffset = taskIndex * dayWidth;
2590
+ const scrollLeft = Math.round(taskOffset - dayWidth * 2);
2591
+ container.scrollLeft = Math.max(0, scrollLeft);
2592
+ }, [tasks, dateRange, dayWidth]);
2158
2593
  (0, import_react12.useImperativeHandle)(
2159
2594
  ref,
2160
2595
  () => ({
2161
- scrollToToday
2596
+ scrollToToday,
2597
+ scrollToTask
2162
2598
  }),
2163
- [scrollToToday]
2599
+ [scrollToToday, scrollToTask]
2164
2600
  );
2165
2601
  const [dragGuideLines, setDragGuideLines] = (0, import_react12.useState)(null);
2166
2602
  const [draggedTaskOverride, setDraggedTaskOverride] = (0, import_react12.useState)(null);
@@ -2260,15 +2696,15 @@ var GanttChart = (0, import_react12.forwardRef)(({
2260
2696
  window.removeEventListener("mouseup", handlePanEnd);
2261
2697
  };
2262
2698
  }, []);
2263
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-container", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2699
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-container", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2264
2700
  "div",
2265
2701
  {
2266
2702
  ref: scrollContainerRef,
2267
2703
  className: "gantt-scrollContainer",
2268
2704
  style: { height: containerHeight ?? "auto", cursor: "grab" },
2269
2705
  onMouseDown: handlePanStart,
2270
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "gantt-scrollContent", children: [
2271
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2706
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "gantt-scrollContent", children: [
2707
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2272
2708
  TaskList,
2273
2709
  {
2274
2710
  tasks,
@@ -2279,11 +2715,14 @@ var GanttChart = (0, import_react12.forwardRef)(({
2279
2715
  selectedTaskId: selectedTaskId ?? void 0,
2280
2716
  onTaskSelect: handleTaskSelect,
2281
2717
  show: showTaskList,
2282
- disableTaskNameEditing
2718
+ disableTaskNameEditing,
2719
+ disableDependencyEditing,
2720
+ onScrollToTask: scrollToTask,
2721
+ onSelectedChipChange: setSelectedChip
2283
2722
  }
2284
2723
  ),
2285
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
2286
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2724
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
2725
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2287
2726
  TimeScaleHeader_default,
2288
2727
  {
2289
2728
  days: dateRange,
@@ -2291,7 +2730,7 @@ var GanttChart = (0, import_react12.forwardRef)(({
2291
2730
  headerHeight
2292
2731
  }
2293
2732
  ) }),
2294
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2733
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2295
2734
  "div",
2296
2735
  {
2297
2736
  className: "gantt-taskArea",
@@ -2300,7 +2739,7 @@ var GanttChart = (0, import_react12.forwardRef)(({
2300
2739
  width: `${gridWidth}px`
2301
2740
  },
2302
2741
  children: [
2303
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2742
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2304
2743
  GridBackground_default,
2305
2744
  {
2306
2745
  dateRange,
@@ -2308,8 +2747,8 @@ var GanttChart = (0, import_react12.forwardRef)(({
2308
2747
  totalHeight: totalGridHeight
2309
2748
  }
2310
2749
  ),
2311
- todayInRange && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(TodayIndicator_default, { monthStart, dayWidth }),
2312
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2750
+ todayInRange && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(TodayIndicator_default, { monthStart, dayWidth }),
2751
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2313
2752
  DependencyLines_default,
2314
2753
  {
2315
2754
  tasks,
@@ -2317,10 +2756,11 @@ var GanttChart = (0, import_react12.forwardRef)(({
2317
2756
  dayWidth,
2318
2757
  rowHeight,
2319
2758
  gridWidth,
2320
- dragOverrides: dependencyOverrides
2759
+ dragOverrides: dependencyOverrides,
2760
+ selectedDep: selectedChip
2321
2761
  }
2322
2762
  ),
2323
- dragGuideLines && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2763
+ dragGuideLines && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2324
2764
  DragGuideLines_default,
2325
2765
  {
2326
2766
  isDragging: dragGuideLines.isDragging,
@@ -2330,7 +2770,7 @@ var GanttChart = (0, import_react12.forwardRef)(({
2330
2770
  totalHeight: totalGridHeight
2331
2771
  }
2332
2772
  ),
2333
- tasks.map((task, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2773
+ tasks.map((task, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2334
2774
  TaskRow_default,
2335
2775
  {
2336
2776
  task,
@@ -2369,7 +2809,7 @@ GanttChart.displayName = "GanttChart";
2369
2809
 
2370
2810
  // src/components/ui/Button.tsx
2371
2811
  var import_react13 = __toESM(require("react"));
2372
- var import_jsx_runtime14 = require("react/jsx-runtime");
2812
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2373
2813
  var Button = import_react13.default.forwardRef(
2374
2814
  ({ className, variant = "default", size = "default", children, ...props }, ref) => {
2375
2815
  const classes = [
@@ -2378,7 +2818,7 @@ var Button = import_react13.default.forwardRef(
2378
2818
  size !== "default" ? `gantt-btn-${size}` : "",
2379
2819
  className || ""
2380
2820
  ].filter(Boolean).join(" ");
2381
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { ref, className: classes, ...props, children });
2821
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { ref, className: classes, ...props, children });
2382
2822
  }
2383
2823
  );
2384
2824
  Button.displayName = "Button";
@@ -2408,6 +2848,7 @@ Button.displayName = "Button";
2408
2848
  calculateTaskBar,
2409
2849
  calculateWeekendBlocks,
2410
2850
  cascadeByLinks,
2851
+ computeLagFromDates,
2411
2852
  detectCycles,
2412
2853
  detectEdgeZone,
2413
2854
  formatDateLabel,