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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/components/GanttChart/GanttChart.tsx
4
- import { useMemo as useMemo8, useCallback as useCallback6, useRef as useRef4, useState as useState5, useEffect as useEffect4, useImperativeHandle, forwardRef } from "react";
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 baseDate = linkType.startsWith("F") ? predecessorEnd : predecessorStart;
200
- const lagMs = lag * 24 * 60 * 60 * 1e3;
201
- const resultDate = new Date(baseDate.getTime() + lagMs);
202
- return resultDate;
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
- if (dep.type === "FS") {
350
- const predEnd = new Date(predecessor.endDate);
351
- const lagDays = Math.round(
352
- (Date.UTC(newStartDate.getUTCFullYear(), newStartDate.getUTCMonth(), newStartDate.getUTCDate()) - Date.UTC(predEnd.getUTCFullYear(), predEnd.getUTCMonth(), predEnd.getUTCDate())) / (24 * 60 * 60 * 1e3)
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) {
@@ -1401,60 +1400,11 @@ var DragGuideLines_default = DragGuideLines;
1401
1400
  import React5, { useMemo as useMemo5 } from "react";
1402
1401
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1403
1402
  function calculateEffectiveLag(edge, predPosition, succPosition, monthStart, dayWidth) {
1404
- const predStartDate = pixelsToDate(predPosition.left, monthStart, dayWidth);
1405
- const predEndDate = pixelsToDate(predPosition.right - dayWidth, monthStart, dayWidth);
1406
- const succStartDate = pixelsToDate(succPosition.left, monthStart, dayWidth);
1407
- const succEndDate = pixelsToDate(succPosition.right - dayWidth, monthStart, dayWidth);
1408
- let lagMs = 0;
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));
1403
+ const predStart = pixelsToDate(predPosition.left, monthStart, dayWidth);
1404
+ const predEnd = pixelsToDate(predPosition.right - dayWidth, monthStart, dayWidth);
1405
+ const succStart = pixelsToDate(succPosition.left, monthStart, dayWidth);
1406
+ const succEnd = pixelsToDate(succPosition.right - dayWidth, monthStart, dayWidth);
1407
+ return computeLagFromDates(edge.type, predStart, predEnd, succStart, succEnd);
1458
1408
  }
1459
1409
  var DependencyLines = React5.memo(({
1460
1410
  tasks,
@@ -1462,7 +1412,8 @@ var DependencyLines = React5.memo(({
1462
1412
  dayWidth,
1463
1413
  rowHeight,
1464
1414
  gridWidth,
1465
- dragOverrides
1415
+ dragOverrides,
1416
+ selectedDep
1466
1417
  }) => {
1467
1418
  const { taskPositions, taskIndices } = useMemo5(() => {
1468
1419
  const positions = /* @__PURE__ */ new Map();
@@ -1576,30 +1527,60 @@ var DependencyLines = React5.memo(({
1576
1527
  }
1577
1528
  )
1578
1529
  }
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
1530
  ),
1590
- lag !== 0 && /* @__PURE__ */ jsx6(
1591
- "text",
1531
+ /* @__PURE__ */ jsx6(
1532
+ "marker",
1592
1533
  {
1593
- className: "gantt-dependency-lag-label",
1594
- x: lag < 0 ? toX + 14 : toX - 14,
1595
- y: reverseOrder ? fromY - 4 : fromY + 12,
1596
- textAnchor: "middle",
1597
- fontSize: "10",
1598
- fill: hasCycle ? "var(--gantt-dependency-cycle-color, #ef4444)" : "var(--gantt-dependency-line-color, #666666)",
1599
- children: lag > 0 ? `+${lag}` : `${lag}`
1534
+ id: "arrowhead-selected",
1535
+ markerWidth: "8",
1536
+ markerHeight: "6",
1537
+ markerUnits: "userSpaceOnUse",
1538
+ refX: "7",
1539
+ refY: "3",
1540
+ orient: "auto",
1541
+ children: /* @__PURE__ */ jsx6(
1542
+ "polygon",
1543
+ {
1544
+ points: "0 0, 8 3, 0 6",
1545
+ fill: "#ef4444"
1546
+ }
1547
+ )
1600
1548
  }
1601
1549
  )
1602
- ] }, id))
1550
+ ] }),
1551
+ lines.map(({ id, path, hasCycle, lag, fromX, toX, fromY, reverseOrder }) => {
1552
+ const isSelected = selectedDep != null && id === `${selectedDep.predecessorId}-${selectedDep.successorId}-${selectedDep.linkType}`;
1553
+ let pathClassName = "gantt-dependency-path";
1554
+ if (isSelected) pathClassName += " gantt-dependency-selected";
1555
+ else if (hasCycle) pathClassName += " gantt-dependency-cycle";
1556
+ let markerEnd;
1557
+ if (isSelected) markerEnd = "url(#arrowhead-selected)";
1558
+ else if (hasCycle) markerEnd = "url(#arrowhead-cycle)";
1559
+ else markerEnd = "url(#arrowhead)";
1560
+ const lagColor = isSelected ? "#ef4444" : hasCycle ? "var(--gantt-dependency-cycle-color, #ef4444)" : "var(--gantt-dependency-line-color, #666666)";
1561
+ return /* @__PURE__ */ jsxs5(React5.Fragment, { children: [
1562
+ /* @__PURE__ */ jsx6(
1563
+ "path",
1564
+ {
1565
+ d: path,
1566
+ className: pathClassName,
1567
+ markerEnd
1568
+ }
1569
+ ),
1570
+ lag !== 0 && /* @__PURE__ */ jsx6(
1571
+ "text",
1572
+ {
1573
+ className: "gantt-dependency-lag-label",
1574
+ x: lag < 0 ? toX + 14 : toX - 14,
1575
+ y: reverseOrder ? fromY - 4 : fromY + 12,
1576
+ textAnchor: "middle",
1577
+ fontSize: "10",
1578
+ fill: lagColor,
1579
+ children: lag > 0 ? `+${lag}` : `${lag}`
1580
+ }
1581
+ )
1582
+ ] }, id);
1583
+ })
1603
1584
  ]
1604
1585
  }
1605
1586
  );
@@ -1608,17 +1589,49 @@ DependencyLines.displayName = "DependencyLines";
1608
1589
  var DependencyLines_default = DependencyLines;
1609
1590
 
1610
1591
  // src/components/TaskList/TaskList.tsx
1611
- import { useMemo as useMemo7, useCallback as useCallback5 } from "react";
1592
+ import React10, { useMemo as useMemo8, useCallback as useCallback5, useState as useState5, useEffect as useEffect4, useRef as useRef4 } from "react";
1593
+
1594
+ // src/components/ui/Popover.tsx
1595
+ import * as RadixPopover from "@radix-ui/react-popover";
1596
+ import { jsx as jsx7 } from "react/jsx-runtime";
1597
+ var Popover = ({ open, onOpenChange, children }) => {
1598
+ return /* @__PURE__ */ jsx7(RadixPopover.Root, { open, onOpenChange, children });
1599
+ };
1600
+ var PopoverTrigger = RadixPopover.Trigger;
1601
+ var PopoverContent = ({
1602
+ children,
1603
+ className,
1604
+ align = "start",
1605
+ side = "bottom",
1606
+ portal = true,
1607
+ collisionPadding = 8
1608
+ }) => {
1609
+ const content = /* @__PURE__ */ jsx7(
1610
+ RadixPopover.Content,
1611
+ {
1612
+ className: `gantt-popover${className ? ` ${className}` : ""}`,
1613
+ align,
1614
+ side,
1615
+ collisionPadding,
1616
+ sideOffset: 4,
1617
+ children
1618
+ }
1619
+ );
1620
+ if (portal) {
1621
+ return /* @__PURE__ */ jsx7(RadixPopover.Portal, { children: content });
1622
+ }
1623
+ return content;
1624
+ };
1612
1625
 
1613
1626
  // src/components/TaskList/TaskListRow.tsx
1614
- import React9, { useState as useState4, useRef as useRef3, useEffect as useEffect3, useCallback as useCallback4 } from "react";
1627
+ import React9, { useState as useState4, useRef as useRef3, useEffect as useEffect3, useCallback as useCallback4, useMemo as useMemo7 } from "react";
1615
1628
 
1616
1629
  // src/components/ui/Input.tsx
1617
1630
  import React6 from "react";
1618
- import { jsx as jsx7 } from "react/jsx-runtime";
1631
+ import { jsx as jsx8 } from "react/jsx-runtime";
1619
1632
  var Input = React6.forwardRef(
1620
1633
  ({ className, ...props }, ref) => {
1621
- return /* @__PURE__ */ jsx7(
1634
+ return /* @__PURE__ */ jsx8(
1622
1635
  "input",
1623
1636
  {
1624
1637
  ref,
@@ -1656,7 +1669,7 @@ import {
1656
1669
  startOfDay
1657
1670
  } from "date-fns";
1658
1671
  import { ru as ru2 } from "date-fns/locale";
1659
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1672
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1660
1673
  function getDayClassName(day, selected) {
1661
1674
  const classes = ["gantt-day-btn"];
1662
1675
  if (selected && isSameDay(day, selected)) classes.push("selected");
@@ -1726,12 +1739,12 @@ var Calendar = ({
1726
1739
  const emptyDays = (getDay(firstDay) + 6) % 7;
1727
1740
  const monthKey = format2(month, "yyyy-MM");
1728
1741
  const monthLabel = format2(month, "LLLL yyyy", { locale: ru2 });
1729
- const emptyCells = Array.from({ length: emptyDays }, (_, i) => /* @__PURE__ */ jsx8("div", { className: "gantt-cal-empty-day" }, `e-${i}`));
1742
+ const emptyCells = Array.from({ length: emptyDays }, (_, i) => /* @__PURE__ */ jsx9("div", { className: "gantt-cal-empty-day" }, `e-${i}`));
1730
1743
  const dayCells = Array.from({ length: totalDays }, (_, i) => {
1731
1744
  const dayNum = i + 1;
1732
1745
  const day = new Date(month.getFullYear(), month.getMonth(), dayNum);
1733
1746
  const className = getDayClassName(day, selected);
1734
- return /* @__PURE__ */ jsx8(
1747
+ return /* @__PURE__ */ jsx9(
1735
1748
  "button",
1736
1749
  {
1737
1750
  type: "button",
@@ -1748,7 +1761,7 @@ var Calendar = ({
1748
1761
  );
1749
1762
  });
1750
1763
  return /* @__PURE__ */ jsxs6("div", { className: "gantt-cal-month", "data-month": monthKey, children: [
1751
- /* @__PURE__ */ jsx8("div", { className: "gantt-cal-month-header", children: monthLabel }),
1764
+ /* @__PURE__ */ jsx9("div", { className: "gantt-cal-month-header", children: monthLabel }),
1752
1765
  /* @__PURE__ */ jsxs6("div", { className: "gantt-cal-month-days", children: [
1753
1766
  emptyCells,
1754
1767
  dayCells
@@ -1761,42 +1774,10 @@ var Calendar = ({
1761
1774
  () => months.map(renderMonth),
1762
1775
  [months, renderMonth]
1763
1776
  );
1764
- return /* @__PURE__ */ jsx8("div", { ref: scrollRef, className: "gantt-cal-container", children: renderedMonths });
1777
+ return /* @__PURE__ */ jsx9("div", { ref: scrollRef, className: "gantt-cal-container", children: renderedMonths });
1765
1778
  };
1766
1779
  Calendar.displayName = "Calendar";
1767
1780
 
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
1781
  // src/components/ui/DatePicker.tsx
1801
1782
  import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1802
1783
  var DatePicker = ({
@@ -1861,19 +1842,182 @@ var DatePicker = ({
1861
1842
  };
1862
1843
  DatePicker.displayName = "DatePicker";
1863
1844
 
1864
- // src/components/TaskList/TaskListRow.tsx
1845
+ // src/components/TaskList/DepIcons.tsx
1865
1846
  import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1847
+ 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: [
1848
+ /* @__PURE__ */ jsx11("path", { d: "m10 15 5 5 5-5" }),
1849
+ /* @__PURE__ */ jsx11("path", { d: "M4 4h7a4 4 0 0 1 4 4v12" })
1850
+ ] });
1851
+ 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: [
1852
+ /* @__PURE__ */ jsx11("path", { d: "M3 5v14" }),
1853
+ /* @__PURE__ */ jsx11("path", { d: "M21 12H7" }),
1854
+ /* @__PURE__ */ jsx11("path", { d: "m15 18 6-6-6-6" })
1855
+ ] });
1856
+ 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: [
1857
+ /* @__PURE__ */ jsx11("path", { d: "M17 12H3" }),
1858
+ /* @__PURE__ */ jsx11("path", { d: "m11 18 6-6-6-6" }),
1859
+ /* @__PURE__ */ jsx11("path", { d: "M21 5v14" })
1860
+ ] });
1861
+ 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: [
1862
+ /* @__PURE__ */ jsx11("path", { d: "m14 15-5 5-5-5" }),
1863
+ /* @__PURE__ */ jsx11("path", { d: "M20 4h-7a4 4 0 0 0-4 4v12" })
1864
+ ] });
1865
+ var LINK_TYPE_ICONS = {
1866
+ FS: DepIconFS,
1867
+ SS: DepIconSS,
1868
+ FF: DepIconFF,
1869
+ SF: DepIconSF
1870
+ };
1871
+ var LINK_TYPE_LABELS = {
1872
+ FS: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435-\u043D\u0430\u0447\u0430\u043B\u043E",
1873
+ SS: "\u041D\u0430\u0447\u0430\u043B\u043E-\u043D\u0430\u0447\u0430\u043B\u043E",
1874
+ FF: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435-\u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435",
1875
+ SF: "\u041D\u0430\u0447\u0430\u043B\u043E-\u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435"
1876
+ };
1877
+
1878
+ // src/components/TaskList/TaskListRow.tsx
1879
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1880
+ 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: [
1881
+ /* @__PURE__ */ jsx12("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
1882
+ /* @__PURE__ */ jsx12("path", { d: "M3 6h18" }),
1883
+ /* @__PURE__ */ jsx12("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
1884
+ ] });
1885
+ function formatDepDescription(type, lag) {
1886
+ const effectiveLag = lag ?? 0;
1887
+ if (type === "FS") {
1888
+ 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`;
1889
+ 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`;
1890
+ 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`;
1891
+ }
1892
+ if (type === "FF") {
1893
+ 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`;
1894
+ 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`;
1895
+ return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F`;
1896
+ }
1897
+ if (type === "SS") {
1898
+ 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`;
1899
+ 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`;
1900
+ return `\u041D\u0430\u0447\u0430\u0442\u044C \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u0430\u0447\u0430\u043B\u043E\u043C`;
1901
+ }
1902
+ if (type === "SF") {
1903
+ 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`;
1904
+ 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`;
1905
+ return `\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044C \u0434\u043E \u043D\u0430\u0447\u0430\u043B\u0430`;
1906
+ }
1907
+ return "";
1908
+ }
1909
+ var DepChip = ({
1910
+ lag,
1911
+ dep,
1912
+ taskId,
1913
+ predecessorName,
1914
+ selectedChip,
1915
+ disableDependencyEditing,
1916
+ onChipSelect,
1917
+ onRowClick,
1918
+ onScrollToTask,
1919
+ onRemoveDependency,
1920
+ onChipSelectClear
1921
+ }) => {
1922
+ const isSelected = selectedChip?.successorId === taskId && selectedChip?.predecessorId === dep.taskId && selectedChip?.linkType === dep.type;
1923
+ const handleClick = (e) => {
1924
+ e.stopPropagation();
1925
+ if (disableDependencyEditing) return;
1926
+ onChipSelect?.(isSelected ? null : { successorId: taskId, predecessorId: dep.taskId, linkType: dep.type });
1927
+ if (!isSelected) {
1928
+ onRowClick?.(taskId);
1929
+ onScrollToTask?.(taskId);
1930
+ }
1931
+ };
1932
+ const handleTrashClick = (e) => {
1933
+ e.stopPropagation();
1934
+ onRemoveDependency?.(taskId, dep.taskId, dep.type);
1935
+ onChipSelectClear();
1936
+ };
1937
+ const Icon = LINK_TYPE_ICONS[dep.type];
1938
+ const depPrefix = formatDepDescription(dep.type, lag);
1939
+ const depName = predecessorName ?? dep.taskId;
1940
+ return /* @__PURE__ */ jsxs9(Popover, { open: isSelected, onOpenChange: (open) => {
1941
+ if (!open) onChipSelectClear();
1942
+ }, children: [
1943
+ /* @__PURE__ */ jsxs9("span", { className: "gantt-tl-dep-chip-wrapper", children: [
1944
+ /* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx12(
1945
+ "span",
1946
+ {
1947
+ className: `gantt-tl-dep-chip${isSelected ? " gantt-tl-dep-chip-selected" : ""}`,
1948
+ onClick: handleClick,
1949
+ children: /* @__PURE__ */ jsxs9(Fragment2, { children: [
1950
+ /* @__PURE__ */ jsx12(Icon, {}),
1951
+ lag != null && lag !== 0 ? lag > 0 ? `+${lag}` : `${lag}` : ""
1952
+ ] })
1953
+ }
1954
+ ) }),
1955
+ !disableDependencyEditing && /* @__PURE__ */ jsx12(
1956
+ "button",
1957
+ {
1958
+ type: "button",
1959
+ className: "gantt-tl-dep-chip-trash",
1960
+ "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
1961
+ onClick: handleTrashClick,
1962
+ children: /* @__PURE__ */ jsx12(TrashIcon, {})
1963
+ }
1964
+ )
1965
+ ] }),
1966
+ /* @__PURE__ */ jsxs9(PopoverContent, { portal: true, side: "bottom", align: "start", className: "gantt-tl-dep-info-popover", children: [
1967
+ /* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-info-prefix", children: depPrefix }),
1968
+ /* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-info-name", children: depName })
1969
+ ] })
1970
+ ] });
1971
+ };
1866
1972
  var toISODate = (value) => {
1867
1973
  if (value instanceof Date) return value.toISOString().split("T")[0];
1868
1974
  if (typeof value === "string" && value.includes("T")) return value.split("T")[0];
1869
1975
  return value;
1870
1976
  };
1871
1977
  var TaskListRow = React9.memo(
1872
- ({ task, rowIndex, rowHeight, onTaskChange, selectedTaskId, onRowClick, disableTaskNameEditing = false }) => {
1978
+ ({
1979
+ task,
1980
+ rowIndex,
1981
+ rowHeight,
1982
+ onTaskChange,
1983
+ selectedTaskId,
1984
+ onRowClick,
1985
+ disableTaskNameEditing = false,
1986
+ disableDependencyEditing = false,
1987
+ allTasks = [],
1988
+ activeLinkType,
1989
+ selectingPredecessorFor,
1990
+ onSetSelectingPredecessorFor,
1991
+ onAddDependency,
1992
+ onRemoveDependency,
1993
+ selectedChip,
1994
+ onChipSelect,
1995
+ onScrollToTask
1996
+ }) => {
1873
1997
  const [editingName, setEditingName] = useState4(false);
1874
1998
  const [nameValue, setNameValue] = useState4("");
1875
1999
  const nameInputRef = useRef3(null);
2000
+ const [overflowOpen, setOverflowOpen] = useState4(false);
1876
2001
  const isSelected = selectedTaskId === task.id;
2002
+ const isPicking = selectingPredecessorFor != null;
2003
+ const isSourceRow = isPicking && selectingPredecessorFor === task.id;
2004
+ const chips = useMemo7(() => {
2005
+ const succStart = new Date(task.startDate);
2006
+ const succEnd = new Date(task.endDate);
2007
+ const taskById = new Map((allTasks ?? []).map((t) => [t.id, t]));
2008
+ return (task.dependencies ?? []).map((dep) => {
2009
+ const pred = taskById.get(dep.taskId);
2010
+ const lag = pred ? computeLagFromDates(
2011
+ dep.type,
2012
+ new Date(pred.startDate),
2013
+ new Date(pred.endDate),
2014
+ succStart,
2015
+ succEnd
2016
+ ) : dep.lag ?? 0;
2017
+ return { dep, lag, predecessorName: pred?.name ?? dep.taskId };
2018
+ });
2019
+ }, [task.dependencies, task.startDate, task.endDate, allTasks]);
2020
+ const linkWord = chips.length <= 4 ? "\u0441\u0432\u044F\u0437\u0438" : "\u0441\u0432\u044F\u0437\u0435\u0439";
1877
2021
  useEffect3(() => {
1878
2022
  if (editingName && nameInputRef.current) {
1879
2023
  nameInputRef.current.focus();
@@ -1919,18 +2063,60 @@ var TaskListRow = React9.memo(
1919
2063
  const handleRowClickInternal = useCallback4(() => {
1920
2064
  onRowClick?.(task.id);
1921
2065
  }, [task.id, onRowClick]);
2066
+ const handleNumberClick = useCallback4((e) => {
2067
+ e.stopPropagation();
2068
+ onRowClick?.(task.id);
2069
+ onScrollToTask?.(task.id);
2070
+ }, [task.id, onRowClick, onScrollToTask]);
2071
+ const handleAddClick = useCallback4((e) => {
2072
+ e.stopPropagation();
2073
+ onSetSelectingPredecessorFor?.(task.id);
2074
+ }, [task.id, onSetSelectingPredecessorFor]);
2075
+ const handlePredecessorPick = useCallback4((e) => {
2076
+ e.stopPropagation();
2077
+ if (!isPicking || isSourceRow) return;
2078
+ if (!selectingPredecessorFor || !activeLinkType) return;
2079
+ onAddDependency?.(task.id, selectingPredecessorFor, activeLinkType);
2080
+ }, [isPicking, isSourceRow, selectingPredecessorFor, task.id, activeLinkType, onAddDependency]);
2081
+ const isSelectedPredecessor = selectedChip != null && selectedChip.predecessorId === task.id;
2082
+ const handleDeleteSelected = useCallback4((e) => {
2083
+ e.stopPropagation();
2084
+ if (!selectedChip) return;
2085
+ onRemoveDependency?.(selectedChip.successorId, selectedChip.predecessorId, selectedChip.linkType);
2086
+ onChipSelect?.(null);
2087
+ }, [selectedChip, onRemoveDependency, onChipSelect]);
1922
2088
  const startDateISO = toISODate(task.startDate);
1923
2089
  const endDateISO = toISODate(task.endDate);
1924
- return /* @__PURE__ */ jsxs8(
2090
+ return /* @__PURE__ */ jsxs9(
1925
2091
  "div",
1926
2092
  {
1927
- className: `gantt-tl-row ${isSelected ? "gantt-tl-row-selected" : ""}`,
2093
+ className: [
2094
+ "gantt-tl-row",
2095
+ isSelected ? "gantt-tl-row-selected" : "",
2096
+ isPicking && !isSourceRow ? "gantt-tl-row-picking" : "",
2097
+ isSourceRow ? "gantt-tl-row-picking-self" : ""
2098
+ ].filter(Boolean).join(" "),
1928
2099
  style: { minHeight: `${rowHeight}px` },
1929
2100
  onClick: handleRowClickInternal,
1930
2101
  children: [
1931
- /* @__PURE__ */ jsx11("div", { className: "gantt-tl-cell gantt-tl-cell-number", children: rowIndex + 1 }),
1932
- /* @__PURE__ */ jsxs8("div", { className: "gantt-tl-cell gantt-tl-cell-name", children: [
1933
- editingName && /* @__PURE__ */ jsx11(
2102
+ /* @__PURE__ */ jsxs9(
2103
+ "div",
2104
+ {
2105
+ className: "gantt-tl-cell gantt-tl-cell-number",
2106
+ onClick: handleNumberClick,
2107
+ title: "\u041F\u0435\u0440\u0435\u0439\u0442\u0438 \u043A \u0440\u0430\u0431\u043E\u0442\u0435",
2108
+ children: [
2109
+ /* @__PURE__ */ jsx12("span", { className: "gantt-tl-num-label", children: rowIndex + 1 }),
2110
+ /* @__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: [
2111
+ /* @__PURE__ */ jsx12("path", { d: "M17 12H3" }),
2112
+ /* @__PURE__ */ jsx12("path", { d: "m11 18 6-6-6-6" }),
2113
+ /* @__PURE__ */ jsx12("path", { d: "M21 5v14" })
2114
+ ] })
2115
+ ]
2116
+ }
2117
+ ),
2118
+ /* @__PURE__ */ jsxs9("div", { className: "gantt-tl-cell gantt-tl-cell-name", children: [
2119
+ editingName && /* @__PURE__ */ jsx12(
1934
2120
  Input,
1935
2121
  {
1936
2122
  ref: nameInputRef,
@@ -1943,7 +2129,7 @@ var TaskListRow = React9.memo(
1943
2129
  onClick: (e) => e.stopPropagation()
1944
2130
  }
1945
2131
  ),
1946
- /* @__PURE__ */ jsx11(
2132
+ /* @__PURE__ */ jsx12(
1947
2133
  "button",
1948
2134
  {
1949
2135
  type: "button",
@@ -1954,7 +2140,7 @@ var TaskListRow = React9.memo(
1954
2140
  }
1955
2141
  )
1956
2142
  ] }),
1957
- /* @__PURE__ */ jsx11("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx11(
2143
+ /* @__PURE__ */ jsx12("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx12(
1958
2144
  DatePicker,
1959
2145
  {
1960
2146
  value: startDateISO,
@@ -1964,7 +2150,7 @@ var TaskListRow = React9.memo(
1964
2150
  disabled: task.locked
1965
2151
  }
1966
2152
  ) }),
1967
- /* @__PURE__ */ jsx11("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx11(
2153
+ /* @__PURE__ */ jsx12("div", { className: "gantt-tl-cell gantt-tl-cell-date", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx12(
1968
2154
  DatePicker,
1969
2155
  {
1970
2156
  value: endDateISO,
@@ -1973,7 +2159,97 @@ var TaskListRow = React9.memo(
1973
2159
  portal: true,
1974
2160
  disabled: task.locked
1975
2161
  }
1976
- ) })
2162
+ ) }),
2163
+ /* @__PURE__ */ jsx12(
2164
+ "div",
2165
+ {
2166
+ className: "gantt-tl-cell gantt-tl-cell-deps",
2167
+ onClick: isPicking && !isSourceRow ? handlePredecessorPick : void 0,
2168
+ 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 ? (
2169
+ /* Full-replacement: "Зависит от [name]" → hover → "Удалить" */
2170
+ /* @__PURE__ */ jsxs9(
2171
+ "button",
2172
+ {
2173
+ type: "button",
2174
+ className: "gantt-tl-dep-delete-label",
2175
+ onClick: handleDeleteSelected,
2176
+ "aria-label": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
2177
+ children: [
2178
+ /* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-delete-label-default", children: "\u0421\u0432\u044F\u0437\u0430\u043D\u043E \u0441" }),
2179
+ /* @__PURE__ */ jsx12("span", { className: "gantt-tl-dep-delete-label-hover", children: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C" })
2180
+ ]
2181
+ }
2182
+ )
2183
+ ) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
2184
+ chips.length >= 2 ? (
2185
+ /* 2+ deps — show only "N связей" summary chip that opens a popover */
2186
+ /* @__PURE__ */ jsxs9(Popover, { open: overflowOpen, onOpenChange: setOverflowOpen, children: [
2187
+ /* @__PURE__ */ jsx12(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs9(
2188
+ "button",
2189
+ {
2190
+ type: "button",
2191
+ className: "gantt-tl-dep-summary-chip",
2192
+ onClick: (e) => {
2193
+ e.stopPropagation();
2194
+ setOverflowOpen((v) => !v);
2195
+ },
2196
+ children: [
2197
+ chips.length,
2198
+ " ",
2199
+ linkWord
2200
+ ]
2201
+ }
2202
+ ) }),
2203
+ /* @__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(
2204
+ DepChip,
2205
+ {
2206
+ lag,
2207
+ dep,
2208
+ taskId: task.id,
2209
+ predecessorName,
2210
+ selectedChip,
2211
+ disableDependencyEditing,
2212
+ onChipSelect,
2213
+ onRowClick,
2214
+ onScrollToTask,
2215
+ onRemoveDependency,
2216
+ onChipSelectClear: () => onChipSelect?.(null)
2217
+ },
2218
+ `${dep.taskId}-${dep.type}`
2219
+ )) }) })
2220
+ ] })
2221
+ ) : chips.length === 1 ? (
2222
+ /* Single chip — unified DepChip */
2223
+ /* @__PURE__ */ jsx12(
2224
+ DepChip,
2225
+ {
2226
+ lag: chips[0].lag,
2227
+ dep: chips[0].dep,
2228
+ taskId: task.id,
2229
+ predecessorName: chips[0].predecessorName,
2230
+ selectedChip,
2231
+ disableDependencyEditing,
2232
+ onChipSelect,
2233
+ onRowClick,
2234
+ onScrollToTask,
2235
+ onRemoveDependency,
2236
+ onChipSelectClear: () => onChipSelect?.(null)
2237
+ }
2238
+ )
2239
+ ) : null,
2240
+ !disableDependencyEditing && !isPicking && /* @__PURE__ */ jsx12(
2241
+ "button",
2242
+ {
2243
+ type: "button",
2244
+ className: "gantt-tl-dep-add",
2245
+ onClick: handleAddClick,
2246
+ "aria-label": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0441\u0432\u044F\u0437\u044C",
2247
+ children: "+"
2248
+ }
2249
+ )
2250
+ ] })
2251
+ }
2252
+ )
1977
2253
  ]
1978
2254
  }
1979
2255
  );
@@ -1982,7 +2258,8 @@ var TaskListRow = React9.memo(
1982
2258
  TaskListRow.displayName = "TaskListRow";
1983
2259
 
1984
2260
  // src/components/TaskList/TaskList.tsx
1985
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
2261
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
2262
+ var LINK_TYPE_ORDER = ["FS", "SS", "FF", "SF"];
1986
2263
  var TaskList = ({
1987
2264
  tasks,
1988
2265
  rowHeight,
@@ -1992,28 +2269,156 @@ var TaskList = ({
1992
2269
  selectedTaskId,
1993
2270
  onTaskSelect,
1994
2271
  show = true,
1995
- disableTaskNameEditing = false
2272
+ disableTaskNameEditing = false,
2273
+ disableDependencyEditing = false,
2274
+ onScrollToTask,
2275
+ onSelectedChipChange
1996
2276
  }) => {
1997
- const totalHeight = useMemo7(
2277
+ const totalHeight = useMemo8(
1998
2278
  () => tasks.length * rowHeight,
1999
2279
  [tasks.length, rowHeight]
2000
2280
  );
2001
2281
  const handleRowClick = useCallback5((taskId) => {
2002
2282
  onTaskSelect?.(taskId);
2003
2283
  }, [onTaskSelect]);
2004
- return /* @__PURE__ */ jsx12(
2284
+ const [activeLinkType, setActiveLinkType] = useState5("FS");
2285
+ const [selectingPredecessorFor, setSelectingPredecessorFor] = useState5(null);
2286
+ const [typeMenuOpen, setTypeMenuOpen] = useState5(false);
2287
+ const [cycleError, setCycleError] = useState5(false);
2288
+ const overlayRef = useRef4(null);
2289
+ const [selectedChip, setSelectedChip] = useState5(null);
2290
+ const handleChipSelect = useCallback5((chip) => {
2291
+ setSelectedChip(chip);
2292
+ onSelectedChipChange?.(chip);
2293
+ }, [onSelectedChipChange]);
2294
+ useEffect4(() => {
2295
+ if (!selectingPredecessorFor && !selectedChip) return;
2296
+ const handleKeyDown = (e) => {
2297
+ if (e.key === "Escape") {
2298
+ setSelectingPredecessorFor(null);
2299
+ setSelectedChip(null);
2300
+ onSelectedChipChange?.(null);
2301
+ }
2302
+ };
2303
+ const handleMouseDown = (e) => {
2304
+ const target = e.target;
2305
+ if (overlayRef.current?.contains(target)) return;
2306
+ if (target.closest?.(".gantt-popover")) return;
2307
+ setSelectingPredecessorFor(null);
2308
+ setSelectedChip(null);
2309
+ onSelectedChipChange?.(null);
2310
+ };
2311
+ document.addEventListener("keydown", handleKeyDown);
2312
+ document.addEventListener("mousedown", handleMouseDown, true);
2313
+ return () => {
2314
+ document.removeEventListener("keydown", handleKeyDown);
2315
+ document.removeEventListener("mousedown", handleMouseDown, true);
2316
+ };
2317
+ }, [selectingPredecessorFor, selectedChip, onSelectedChipChange]);
2318
+ const handleAddDependency = useCallback5((successorTaskId, predecessorTaskId, linkType) => {
2319
+ if (successorTaskId === predecessorTaskId) return;
2320
+ const successor = tasks.find((t) => t.id === successorTaskId);
2321
+ if (!successor) return;
2322
+ const alreadyExists = (successor.dependencies ?? []).some(
2323
+ (d) => d.taskId === predecessorTaskId && d.type === linkType
2324
+ );
2325
+ if (alreadyExists) {
2326
+ setSelectingPredecessorFor(null);
2327
+ return;
2328
+ }
2329
+ const newDep = { taskId: predecessorTaskId, type: linkType, lag: 0 };
2330
+ const hypothetical = tasks.map(
2331
+ (t) => t.id === successorTaskId ? { ...t, dependencies: [...t.dependencies ?? [], newDep] } : t
2332
+ );
2333
+ const validation = validateDependencies(hypothetical);
2334
+ if (!validation.isValid) {
2335
+ setCycleError(true);
2336
+ setTimeout(() => setCycleError(false), 3e3);
2337
+ return;
2338
+ }
2339
+ const updatedTask = hypothetical.find((t) => t.id === successorTaskId);
2340
+ const predecessor = tasks.find((t) => t.id === predecessorTaskId);
2341
+ if (predecessor) {
2342
+ const predStart = new Date(predecessor.startDate);
2343
+ const predEnd = new Date(predecessor.endDate);
2344
+ const constraintDate = calculateSuccessorDate(predStart, predEnd, linkType, 0);
2345
+ const origSuccessor = tasks.find((t) => t.id === successorTaskId);
2346
+ const durationMs = new Date(origSuccessor.endDate).getTime() - new Date(origSuccessor.startDate).getTime();
2347
+ let newStart;
2348
+ let newEnd;
2349
+ if (linkType === "FS" || linkType === "SS") {
2350
+ newStart = constraintDate;
2351
+ newEnd = new Date(constraintDate.getTime() + durationMs);
2352
+ } else {
2353
+ newEnd = constraintDate;
2354
+ newStart = new Date(constraintDate.getTime() - durationMs);
2355
+ }
2356
+ const snappedTask = {
2357
+ ...updatedTask,
2358
+ startDate: newStart.toISOString().split("T")[0],
2359
+ endDate: newEnd.toISOString().split("T")[0]
2360
+ };
2361
+ onTaskChange?.(snappedTask);
2362
+ } else {
2363
+ onTaskChange?.(updatedTask);
2364
+ }
2365
+ setSelectingPredecessorFor(null);
2366
+ }, [tasks, onTaskChange]);
2367
+ const handleRemoveDependency = useCallback5((taskId, predecessorTaskId, linkType) => {
2368
+ const task = tasks.find((t) => t.id === taskId);
2369
+ if (!task) return;
2370
+ const updatedDeps = (task.dependencies ?? []).filter(
2371
+ (d) => !(d.taskId === predecessorTaskId && d.type === linkType)
2372
+ );
2373
+ onTaskChange?.({ ...task, dependencies: updatedDeps });
2374
+ }, [tasks, onTaskChange]);
2375
+ return /* @__PURE__ */ jsx13(
2005
2376
  "div",
2006
2377
  {
2378
+ ref: overlayRef,
2007
2379
  className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}`,
2008
2380
  style: { width: `${taskListWidth}px` },
2009
- children: /* @__PURE__ */ jsxs9("div", { className: "gantt-tl-table", children: [
2010
- /* @__PURE__ */ jsxs9("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: [
2011
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-headerCell gantt-tl-cell-number", children: "\u2116" }),
2012
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-headerCell gantt-tl-cell-name", children: "\u0418\u043C\u044F" }),
2013
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041D\u0430\u0447\u0430\u043B\u043E" }),
2014
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435" })
2381
+ children: /* @__PURE__ */ jsxs10("div", { className: "gantt-tl-table", children: [
2382
+ /* @__PURE__ */ jsxs10("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: [
2383
+ /* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-number", children: "\u2116" }),
2384
+ /* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-name", children: "\u0418\u043C\u044F" }),
2385
+ /* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041D\u0430\u0447\u0430\u043B\u043E" }),
2386
+ /* @__PURE__ */ jsx13("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0435" }),
2387
+ /* @__PURE__ */ jsxs10("div", { className: "gantt-tl-headerCell gantt-tl-cell-deps", style: { position: "relative" }, children: [
2388
+ /* @__PURE__ */ jsxs10(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
2389
+ /* @__PURE__ */ jsx13(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs10(
2390
+ "button",
2391
+ {
2392
+ className: "gantt-tl-dep-type-trigger",
2393
+ disabled: disableDependencyEditing,
2394
+ onClick: (e) => e.stopPropagation(),
2395
+ children: [
2396
+ "\u0421\u0432\u044F\u0437\u0438 ",
2397
+ React10.createElement(LINK_TYPE_ICONS[activeLinkType]),
2398
+ " \u25BE"
2399
+ ]
2400
+ }
2401
+ ) }),
2402
+ /* @__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(
2403
+ "button",
2404
+ {
2405
+ className: `gantt-tl-dep-type-option${activeLinkType === lt ? " active" : ""}`,
2406
+ onClick: () => {
2407
+ setActiveLinkType(lt);
2408
+ setTypeMenuOpen(false);
2409
+ },
2410
+ children: [
2411
+ React10.createElement(LINK_TYPE_ICONS[lt]),
2412
+ /* @__PURE__ */ jsx13("span", { children: LINK_TYPE_LABELS[lt] })
2413
+ ]
2414
+ },
2415
+ lt
2416
+ )) }) })
2417
+ ] }),
2418
+ 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!" })
2419
+ ] })
2015
2420
  ] }),
2016
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-body", style: { height: `${totalHeight}px` }, children: tasks.map((task, index) => /* @__PURE__ */ jsx12(
2421
+ /* @__PURE__ */ jsx13("div", { className: "gantt-tl-body", style: { height: `${totalHeight}px` }, children: tasks.map((task, index) => /* @__PURE__ */ jsx13(
2017
2422
  TaskListRow,
2018
2423
  {
2019
2424
  task,
@@ -2022,7 +2427,17 @@ var TaskList = ({
2022
2427
  onTaskChange,
2023
2428
  selectedTaskId,
2024
2429
  onRowClick: handleRowClick,
2025
- disableTaskNameEditing
2430
+ disableTaskNameEditing,
2431
+ disableDependencyEditing,
2432
+ allTasks: tasks,
2433
+ activeLinkType,
2434
+ selectingPredecessorFor,
2435
+ onSetSelectingPredecessorFor: setSelectingPredecessorFor,
2436
+ onAddDependency: handleAddDependency,
2437
+ onRemoveDependency: handleRemoveDependency,
2438
+ selectedChip,
2439
+ onChipSelect: handleChipSelect,
2440
+ onScrollToTask
2026
2441
  },
2027
2442
  task.id
2028
2443
  )) })
@@ -2032,7 +2447,7 @@ var TaskList = ({
2032
2447
  };
2033
2448
 
2034
2449
  // src/components/GanttChart/GanttChart.tsx
2035
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
2450
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
2036
2451
  var GanttChart = forwardRef(({
2037
2452
  tasks,
2038
2453
  dayWidth = 40,
@@ -2046,34 +2461,36 @@ var GanttChart = forwardRef(({
2046
2461
  onCascade,
2047
2462
  showTaskList = false,
2048
2463
  taskListWidth = 520,
2049
- disableTaskNameEditing = false
2464
+ disableTaskNameEditing = false,
2465
+ disableDependencyEditing = false
2050
2466
  }, ref) => {
2051
- const scrollContainerRef = useRef4(null);
2052
- const [selectedTaskId, setSelectedTaskId] = useState5(null);
2053
- const dateRange = useMemo8(() => getMultiMonthDays(tasks), [tasks]);
2054
- const [validationResult, setValidationResult] = useState5(null);
2055
- const [cascadeOverrides, setCascadeOverrides] = useState5(/* @__PURE__ */ new Map());
2056
- const gridWidth = useMemo8(
2467
+ const scrollContainerRef = useRef5(null);
2468
+ const [selectedTaskId, setSelectedTaskId] = useState6(null);
2469
+ const [selectedChip, setSelectedChip] = useState6(null);
2470
+ const dateRange = useMemo9(() => getMultiMonthDays(tasks), [tasks]);
2471
+ const [validationResult, setValidationResult] = useState6(null);
2472
+ const [cascadeOverrides, setCascadeOverrides] = useState6(/* @__PURE__ */ new Map());
2473
+ const gridWidth = useMemo9(
2057
2474
  () => Math.round(dateRange.length * dayWidth),
2058
2475
  [dateRange.length, dayWidth]
2059
2476
  );
2060
- const totalGridHeight = useMemo8(
2477
+ const totalGridHeight = useMemo9(
2061
2478
  () => tasks.length * rowHeight,
2062
2479
  [tasks.length, rowHeight]
2063
2480
  );
2064
- const monthStart = useMemo8(() => {
2481
+ const monthStart = useMemo9(() => {
2065
2482
  if (dateRange.length === 0) {
2066
2483
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
2067
2484
  }
2068
2485
  const firstDay = dateRange[0];
2069
2486
  return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
2070
2487
  }, [dateRange]);
2071
- const todayInRange = useMemo8(() => {
2488
+ const todayInRange = useMemo9(() => {
2072
2489
  const now = /* @__PURE__ */ new Date();
2073
2490
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
2074
2491
  return dateRange.some((day) => day.getTime() === today.getTime());
2075
2492
  }, [dateRange]);
2076
- useEffect4(() => {
2493
+ useEffect5(() => {
2077
2494
  const container = scrollContainerRef.current;
2078
2495
  if (!container || dateRange.length === 0) return;
2079
2496
  const now = /* @__PURE__ */ new Date();
@@ -2097,16 +2514,34 @@ var GanttChart = forwardRef(({
2097
2514
  const scrollLeft = Math.round(todayOffset - containerWidth / 2 + dayWidth / 2);
2098
2515
  container.scrollLeft = Math.max(0, scrollLeft);
2099
2516
  }, [dateRange, dayWidth]);
2517
+ const scrollToTask = useCallback6((taskId) => {
2518
+ const container = scrollContainerRef.current;
2519
+ if (!container || dateRange.length === 0) return;
2520
+ const task = tasks.find((t) => t.id === taskId);
2521
+ if (!task) return;
2522
+ const taskStart = new Date(task.startDate);
2523
+ const taskStartUTC = new Date(Date.UTC(
2524
+ taskStart.getUTCFullYear(),
2525
+ taskStart.getUTCMonth(),
2526
+ taskStart.getUTCDate()
2527
+ ));
2528
+ const taskIndex = dateRange.findIndex((day) => day.getTime() === taskStartUTC.getTime());
2529
+ if (taskIndex === -1) return;
2530
+ const taskOffset = taskIndex * dayWidth;
2531
+ const scrollLeft = Math.round(taskOffset - dayWidth * 2);
2532
+ container.scrollLeft = Math.max(0, scrollLeft);
2533
+ }, [tasks, dateRange, dayWidth]);
2100
2534
  useImperativeHandle(
2101
2535
  ref,
2102
2536
  () => ({
2103
- scrollToToday
2537
+ scrollToToday,
2538
+ scrollToTask
2104
2539
  }),
2105
- [scrollToToday]
2540
+ [scrollToToday, scrollToTask]
2106
2541
  );
2107
- const [dragGuideLines, setDragGuideLines] = useState5(null);
2108
- const [draggedTaskOverride, setDraggedTaskOverride] = useState5(null);
2109
- useEffect4(() => {
2542
+ const [dragGuideLines, setDragGuideLines] = useState6(null);
2543
+ const [draggedTaskOverride, setDraggedTaskOverride] = useState6(null);
2544
+ useEffect5(() => {
2110
2545
  const result = validateDependencies(tasks);
2111
2546
  setValidationResult(result);
2112
2547
  onValidateDependencies?.(result);
@@ -2135,7 +2570,7 @@ var GanttChart = forwardRef(({
2135
2570
  });
2136
2571
  onCascade?.(allCascaded);
2137
2572
  }, [tasks, onChange, disableConstraints, onCascade]);
2138
- const dependencyOverrides = useMemo8(() => {
2573
+ const dependencyOverrides = useMemo9(() => {
2139
2574
  const map = new Map(cascadeOverrides);
2140
2575
  if (draggedTaskOverride) {
2141
2576
  map.set(draggedTaskOverride.taskId, {
@@ -2158,7 +2593,7 @@ var GanttChart = forwardRef(({
2158
2593
  const handleTaskSelect = useCallback6((taskId) => {
2159
2594
  setSelectedTaskId(taskId);
2160
2595
  }, []);
2161
- const panStateRef = useRef4(null);
2596
+ const panStateRef = useRef5(null);
2162
2597
  const handlePanStart = useCallback6((e) => {
2163
2598
  if (e.button !== 0) return;
2164
2599
  const target = e.target;
@@ -2180,7 +2615,7 @@ var GanttChart = forwardRef(({
2180
2615
  container.style.cursor = "grabbing";
2181
2616
  e.preventDefault();
2182
2617
  }, []);
2183
- useEffect4(() => {
2618
+ useEffect5(() => {
2184
2619
  const handlePanMove = (e) => {
2185
2620
  const pan = panStateRef.current;
2186
2621
  if (!pan?.active) return;
@@ -2202,15 +2637,15 @@ var GanttChart = forwardRef(({
2202
2637
  window.removeEventListener("mouseup", handlePanEnd);
2203
2638
  };
2204
2639
  }, []);
2205
- return /* @__PURE__ */ jsx13("div", { className: "gantt-container", children: /* @__PURE__ */ jsx13(
2640
+ return /* @__PURE__ */ jsx14("div", { className: "gantt-container", children: /* @__PURE__ */ jsx14(
2206
2641
  "div",
2207
2642
  {
2208
2643
  ref: scrollContainerRef,
2209
2644
  className: "gantt-scrollContainer",
2210
2645
  style: { height: containerHeight ?? "auto", cursor: "grab" },
2211
2646
  onMouseDown: handlePanStart,
2212
- children: /* @__PURE__ */ jsxs10("div", { className: "gantt-scrollContent", children: [
2213
- /* @__PURE__ */ jsx13(
2647
+ children: /* @__PURE__ */ jsxs11("div", { className: "gantt-scrollContent", children: [
2648
+ /* @__PURE__ */ jsx14(
2214
2649
  TaskList,
2215
2650
  {
2216
2651
  tasks,
@@ -2221,11 +2656,14 @@ var GanttChart = forwardRef(({
2221
2656
  selectedTaskId: selectedTaskId ?? void 0,
2222
2657
  onTaskSelect: handleTaskSelect,
2223
2658
  show: showTaskList,
2224
- disableTaskNameEditing
2659
+ disableTaskNameEditing,
2660
+ disableDependencyEditing,
2661
+ onScrollToTask: scrollToTask,
2662
+ onSelectedChipChange: setSelectedChip
2225
2663
  }
2226
2664
  ),
2227
- /* @__PURE__ */ jsxs10("div", { style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
2228
- /* @__PURE__ */ jsx13("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx13(
2665
+ /* @__PURE__ */ jsxs11("div", { style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
2666
+ /* @__PURE__ */ jsx14("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx14(
2229
2667
  TimeScaleHeader_default,
2230
2668
  {
2231
2669
  days: dateRange,
@@ -2233,7 +2671,7 @@ var GanttChart = forwardRef(({
2233
2671
  headerHeight
2234
2672
  }
2235
2673
  ) }),
2236
- /* @__PURE__ */ jsxs10(
2674
+ /* @__PURE__ */ jsxs11(
2237
2675
  "div",
2238
2676
  {
2239
2677
  className: "gantt-taskArea",
@@ -2242,7 +2680,7 @@ var GanttChart = forwardRef(({
2242
2680
  width: `${gridWidth}px`
2243
2681
  },
2244
2682
  children: [
2245
- /* @__PURE__ */ jsx13(
2683
+ /* @__PURE__ */ jsx14(
2246
2684
  GridBackground_default,
2247
2685
  {
2248
2686
  dateRange,
@@ -2250,8 +2688,8 @@ var GanttChart = forwardRef(({
2250
2688
  totalHeight: totalGridHeight
2251
2689
  }
2252
2690
  ),
2253
- todayInRange && /* @__PURE__ */ jsx13(TodayIndicator_default, { monthStart, dayWidth }),
2254
- /* @__PURE__ */ jsx13(
2691
+ todayInRange && /* @__PURE__ */ jsx14(TodayIndicator_default, { monthStart, dayWidth }),
2692
+ /* @__PURE__ */ jsx14(
2255
2693
  DependencyLines_default,
2256
2694
  {
2257
2695
  tasks,
@@ -2259,10 +2697,11 @@ var GanttChart = forwardRef(({
2259
2697
  dayWidth,
2260
2698
  rowHeight,
2261
2699
  gridWidth,
2262
- dragOverrides: dependencyOverrides
2700
+ dragOverrides: dependencyOverrides,
2701
+ selectedDep: selectedChip
2263
2702
  }
2264
2703
  ),
2265
- dragGuideLines && /* @__PURE__ */ jsx13(
2704
+ dragGuideLines && /* @__PURE__ */ jsx14(
2266
2705
  DragGuideLines_default,
2267
2706
  {
2268
2707
  isDragging: dragGuideLines.isDragging,
@@ -2272,7 +2711,7 @@ var GanttChart = forwardRef(({
2272
2711
  totalHeight: totalGridHeight
2273
2712
  }
2274
2713
  ),
2275
- tasks.map((task, index) => /* @__PURE__ */ jsx13(
2714
+ tasks.map((task, index) => /* @__PURE__ */ jsx14(
2276
2715
  TaskRow_default,
2277
2716
  {
2278
2717
  task,
@@ -2311,7 +2750,7 @@ GanttChart.displayName = "GanttChart";
2311
2750
 
2312
2751
  // src/components/ui/Button.tsx
2313
2752
  import React12 from "react";
2314
- import { jsx as jsx14 } from "react/jsx-runtime";
2753
+ import { jsx as jsx15 } from "react/jsx-runtime";
2315
2754
  var Button = React12.forwardRef(
2316
2755
  ({ className, variant = "default", size = "default", children, ...props }, ref) => {
2317
2756
  const classes = [
@@ -2320,7 +2759,7 @@ var Button = React12.forwardRef(
2320
2759
  size !== "default" ? `gantt-btn-${size}` : "",
2321
2760
  className || ""
2322
2761
  ].filter(Boolean).join(" ");
2323
- return /* @__PURE__ */ jsx14("button", { ref, className: classes, ...props, children });
2762
+ return /* @__PURE__ */ jsx15("button", { ref, className: classes, ...props, children });
2324
2763
  }
2325
2764
  );
2326
2765
  Button.displayName = "Button";
@@ -2349,6 +2788,7 @@ export {
2349
2788
  calculateTaskBar,
2350
2789
  calculateWeekendBlocks,
2351
2790
  cascadeByLinks,
2791
+ computeLagFromDates,
2352
2792
  detectCycles,
2353
2793
  detectEdgeZone,
2354
2794
  formatDateLabel,