gantt-lib 0.1.1 → 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) {
@@ -1277,15 +1276,11 @@ var TodayIndicator = ({ monthStart, dayWidth }) => {
1277
1276
  today.getMonth(),
1278
1277
  today.getDate()
1279
1278
  ));
1280
- const isInMonth = useMemo3(() => {
1281
- return todayLocal.getUTCFullYear() === monthStart.getUTCFullYear() && todayLocal.getUTCMonth() === monthStart.getUTCMonth();
1282
- }, [monthStart, todayLocal]);
1283
1279
  const position = useMemo3(() => {
1284
- if (!isInMonth) return null;
1285
1280
  const offset = getDayOffset(todayLocal, monthStart);
1286
1281
  return Math.round(offset * dayWidth);
1287
- }, [isInMonth, monthStart, dayWidth, todayLocal]);
1288
- if (!isInMonth || position === null) {
1282
+ }, [monthStart, dayWidth, todayLocal]);
1283
+ if (isNaN(position)) {
1289
1284
  return null;
1290
1285
  }
1291
1286
  return /* @__PURE__ */ jsx3(
@@ -1405,60 +1400,11 @@ var DragGuideLines_default = DragGuideLines;
1405
1400
  import React5, { useMemo as useMemo5 } from "react";
1406
1401
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1407
1402
  function calculateEffectiveLag(edge, predPosition, succPosition, monthStart, dayWidth) {
1408
- const predStartDate = pixelsToDate(predPosition.left, monthStart, dayWidth);
1409
- const predEndDate = pixelsToDate(predPosition.right - dayWidth, monthStart, dayWidth);
1410
- const succStartDate = pixelsToDate(succPosition.left, monthStart, dayWidth);
1411
- const succEndDate = pixelsToDate(succPosition.right - dayWidth, monthStart, dayWidth);
1412
- let lagMs = 0;
1413
- switch (edge.type) {
1414
- case "FS":
1415
- lagMs = Date.UTC(
1416
- succStartDate.getUTCFullYear(),
1417
- succStartDate.getUTCMonth(),
1418
- succStartDate.getUTCDate()
1419
- ) - Date.UTC(
1420
- predEndDate.getUTCFullYear(),
1421
- predEndDate.getUTCMonth(),
1422
- predEndDate.getUTCDate()
1423
- ) - 24 * 60 * 60 * 1e3;
1424
- break;
1425
- case "SS":
1426
- lagMs = Date.UTC(
1427
- succStartDate.getUTCFullYear(),
1428
- succStartDate.getUTCMonth(),
1429
- succStartDate.getUTCDate()
1430
- ) - Date.UTC(
1431
- predStartDate.getUTCFullYear(),
1432
- predStartDate.getUTCMonth(),
1433
- predStartDate.getUTCDate()
1434
- );
1435
- break;
1436
- case "FF":
1437
- lagMs = Date.UTC(
1438
- succEndDate.getUTCFullYear(),
1439
- succEndDate.getUTCMonth(),
1440
- succEndDate.getUTCDate()
1441
- ) - Date.UTC(
1442
- predEndDate.getUTCFullYear(),
1443
- predEndDate.getUTCMonth(),
1444
- predEndDate.getUTCDate()
1445
- );
1446
- break;
1447
- case "SF":
1448
- lagMs = Date.UTC(
1449
- succEndDate.getUTCFullYear(),
1450
- succEndDate.getUTCMonth(),
1451
- succEndDate.getUTCDate()
1452
- ) - Date.UTC(
1453
- predStartDate.getUTCFullYear(),
1454
- predStartDate.getUTCMonth(),
1455
- predStartDate.getUTCDate()
1456
- ) + 24 * 60 * 60 * 1e3;
1457
- break;
1458
- default:
1459
- return 0;
1460
- }
1461
- 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);
1462
1408
  }
1463
1409
  var DependencyLines = React5.memo(({
1464
1410
  tasks,
@@ -1466,7 +1412,8 @@ var DependencyLines = React5.memo(({
1466
1412
  dayWidth,
1467
1413
  rowHeight,
1468
1414
  gridWidth,
1469
- dragOverrides
1415
+ dragOverrides,
1416
+ selectedDep
1470
1417
  }) => {
1471
1418
  const { taskPositions, taskIndices } = useMemo5(() => {
1472
1419
  const positions = /* @__PURE__ */ new Map();
@@ -1580,30 +1527,60 @@ var DependencyLines = React5.memo(({
1580
1527
  }
1581
1528
  )
1582
1529
  }
1583
- )
1584
- ] }),
1585
- lines.map(({ id, path, hasCycle, lag, fromX, toX, fromY, reverseOrder }) => /* @__PURE__ */ jsxs5(React5.Fragment, { children: [
1586
- /* @__PURE__ */ jsx6(
1587
- "path",
1588
- {
1589
- d: path,
1590
- className: hasCycle ? "gantt-dependency-path gantt-dependency-cycle" : "gantt-dependency-path",
1591
- markerEnd: hasCycle ? "url(#arrowhead-cycle)" : "url(#arrowhead)"
1592
- }
1593
1530
  ),
1594
- lag !== 0 && /* @__PURE__ */ jsx6(
1595
- "text",
1531
+ /* @__PURE__ */ jsx6(
1532
+ "marker",
1596
1533
  {
1597
- className: "gantt-dependency-lag-label",
1598
- x: lag < 0 ? toX + 14 : toX - 14,
1599
- y: reverseOrder ? fromY - 4 : fromY + 12,
1600
- textAnchor: "middle",
1601
- fontSize: "10",
1602
- fill: hasCycle ? "var(--gantt-dependency-cycle-color, #ef4444)" : "var(--gantt-dependency-line-color, #666666)",
1603
- 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
+ )
1604
1548
  }
1605
1549
  )
1606
- ] }, 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
+ })
1607
1584
  ]
1608
1585
  }
1609
1586
  );
@@ -1612,17 +1589,49 @@ DependencyLines.displayName = "DependencyLines";
1612
1589
  var DependencyLines_default = DependencyLines;
1613
1590
 
1614
1591
  // src/components/TaskList/TaskList.tsx
1615
- 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
+ };
1616
1625
 
1617
1626
  // src/components/TaskList/TaskListRow.tsx
1618
- 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";
1619
1628
 
1620
1629
  // src/components/ui/Input.tsx
1621
1630
  import React6 from "react";
1622
- import { jsx as jsx7 } from "react/jsx-runtime";
1631
+ import { jsx as jsx8 } from "react/jsx-runtime";
1623
1632
  var Input = React6.forwardRef(
1624
1633
  ({ className, ...props }, ref) => {
1625
- return /* @__PURE__ */ jsx7(
1634
+ return /* @__PURE__ */ jsx8(
1626
1635
  "input",
1627
1636
  {
1628
1637
  ref,
@@ -1654,19 +1663,19 @@ import {
1654
1663
  subMonths,
1655
1664
  isSameDay,
1656
1665
  getDay,
1657
- isToday as isToday3,
1666
+ isToday as isToday2,
1658
1667
  isWeekend as isWeekend2,
1659
1668
  isBefore,
1660
1669
  startOfDay
1661
1670
  } from "date-fns";
1662
1671
  import { ru as ru2 } from "date-fns/locale";
1663
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1672
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1664
1673
  function getDayClassName(day, selected) {
1665
1674
  const classes = ["gantt-day-btn"];
1666
1675
  if (selected && isSameDay(day, selected)) classes.push("selected");
1667
- if (isToday3(day)) classes.push("today");
1676
+ if (isToday2(day)) classes.push("today");
1668
1677
  if (isWeekend2(day)) classes.push("weekend");
1669
- if (isBefore(day, startOfDay(/* @__PURE__ */ new Date())) && !isToday3(day)) classes.push("past");
1678
+ if (isBefore(day, startOfDay(/* @__PURE__ */ new Date())) && !isToday2(day)) classes.push("past");
1670
1679
  return classes.join(" ");
1671
1680
  }
1672
1681
  var Calendar = ({
@@ -1730,12 +1739,12 @@ var Calendar = ({
1730
1739
  const emptyDays = (getDay(firstDay) + 6) % 7;
1731
1740
  const monthKey = format2(month, "yyyy-MM");
1732
1741
  const monthLabel = format2(month, "LLLL yyyy", { locale: ru2 });
1733
- 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}`));
1734
1743
  const dayCells = Array.from({ length: totalDays }, (_, i) => {
1735
1744
  const dayNum = i + 1;
1736
1745
  const day = new Date(month.getFullYear(), month.getMonth(), dayNum);
1737
1746
  const className = getDayClassName(day, selected);
1738
- return /* @__PURE__ */ jsx8(
1747
+ return /* @__PURE__ */ jsx9(
1739
1748
  "button",
1740
1749
  {
1741
1750
  type: "button",
@@ -1752,7 +1761,7 @@ var Calendar = ({
1752
1761
  );
1753
1762
  });
1754
1763
  return /* @__PURE__ */ jsxs6("div", { className: "gantt-cal-month", "data-month": monthKey, children: [
1755
- /* @__PURE__ */ jsx8("div", { className: "gantt-cal-month-header", children: monthLabel }),
1764
+ /* @__PURE__ */ jsx9("div", { className: "gantt-cal-month-header", children: monthLabel }),
1756
1765
  /* @__PURE__ */ jsxs6("div", { className: "gantt-cal-month-days", children: [
1757
1766
  emptyCells,
1758
1767
  dayCells
@@ -1765,42 +1774,10 @@ var Calendar = ({
1765
1774
  () => months.map(renderMonth),
1766
1775
  [months, renderMonth]
1767
1776
  );
1768
- 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 });
1769
1778
  };
1770
1779
  Calendar.displayName = "Calendar";
1771
1780
 
1772
- // src/components/ui/Popover.tsx
1773
- import * as RadixPopover from "@radix-ui/react-popover";
1774
- import { jsx as jsx9 } from "react/jsx-runtime";
1775
- var Popover = ({ open, onOpenChange, children }) => {
1776
- return /* @__PURE__ */ jsx9(RadixPopover.Root, { open, onOpenChange, children });
1777
- };
1778
- var PopoverTrigger = RadixPopover.Trigger;
1779
- var PopoverContent = ({
1780
- children,
1781
- className,
1782
- align = "start",
1783
- side = "bottom",
1784
- portal = true,
1785
- collisionPadding = 8
1786
- }) => {
1787
- const content = /* @__PURE__ */ jsx9(
1788
- RadixPopover.Content,
1789
- {
1790
- className: `gantt-popover${className ? ` ${className}` : ""}`,
1791
- align,
1792
- side,
1793
- collisionPadding,
1794
- sideOffset: 4,
1795
- children
1796
- }
1797
- );
1798
- if (portal) {
1799
- return /* @__PURE__ */ jsx9(RadixPopover.Portal, { children: content });
1800
- }
1801
- return content;
1802
- };
1803
-
1804
1781
  // src/components/ui/DatePicker.tsx
1805
1782
  import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1806
1783
  var DatePicker = ({
@@ -1865,19 +1842,182 @@ var DatePicker = ({
1865
1842
  };
1866
1843
  DatePicker.displayName = "DatePicker";
1867
1844
 
1868
- // src/components/TaskList/TaskListRow.tsx
1845
+ // src/components/TaskList/DepIcons.tsx
1869
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
+ };
1870
1972
  var toISODate = (value) => {
1871
1973
  if (value instanceof Date) return value.toISOString().split("T")[0];
1872
1974
  if (typeof value === "string" && value.includes("T")) return value.split("T")[0];
1873
1975
  return value;
1874
1976
  };
1875
1977
  var TaskListRow = React9.memo(
1876
- ({ 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
+ }) => {
1877
1997
  const [editingName, setEditingName] = useState4(false);
1878
1998
  const [nameValue, setNameValue] = useState4("");
1879
1999
  const nameInputRef = useRef3(null);
2000
+ const [overflowOpen, setOverflowOpen] = useState4(false);
1880
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";
1881
2021
  useEffect3(() => {
1882
2022
  if (editingName && nameInputRef.current) {
1883
2023
  nameInputRef.current.focus();
@@ -1923,18 +2063,60 @@ var TaskListRow = React9.memo(
1923
2063
  const handleRowClickInternal = useCallback4(() => {
1924
2064
  onRowClick?.(task.id);
1925
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]);
1926
2088
  const startDateISO = toISODate(task.startDate);
1927
2089
  const endDateISO = toISODate(task.endDate);
1928
- return /* @__PURE__ */ jsxs8(
2090
+ return /* @__PURE__ */ jsxs9(
1929
2091
  "div",
1930
2092
  {
1931
- 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(" "),
1932
2099
  style: { minHeight: `${rowHeight}px` },
1933
2100
  onClick: handleRowClickInternal,
1934
2101
  children: [
1935
- /* @__PURE__ */ jsx11("div", { className: "gantt-tl-cell gantt-tl-cell-number", children: rowIndex + 1 }),
1936
- /* @__PURE__ */ jsxs8("div", { className: "gantt-tl-cell gantt-tl-cell-name", children: [
1937
- 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(
1938
2120
  Input,
1939
2121
  {
1940
2122
  ref: nameInputRef,
@@ -1947,7 +2129,7 @@ var TaskListRow = React9.memo(
1947
2129
  onClick: (e) => e.stopPropagation()
1948
2130
  }
1949
2131
  ),
1950
- /* @__PURE__ */ jsx11(
2132
+ /* @__PURE__ */ jsx12(
1951
2133
  "button",
1952
2134
  {
1953
2135
  type: "button",
@@ -1958,7 +2140,7 @@ var TaskListRow = React9.memo(
1958
2140
  }
1959
2141
  )
1960
2142
  ] }),
1961
- /* @__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(
1962
2144
  DatePicker,
1963
2145
  {
1964
2146
  value: startDateISO,
@@ -1968,7 +2150,7 @@ var TaskListRow = React9.memo(
1968
2150
  disabled: task.locked
1969
2151
  }
1970
2152
  ) }),
1971
- /* @__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(
1972
2154
  DatePicker,
1973
2155
  {
1974
2156
  value: endDateISO,
@@ -1977,7 +2159,97 @@ var TaskListRow = React9.memo(
1977
2159
  portal: true,
1978
2160
  disabled: task.locked
1979
2161
  }
1980
- ) })
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
+ )
1981
2253
  ]
1982
2254
  }
1983
2255
  );
@@ -1986,7 +2258,8 @@ var TaskListRow = React9.memo(
1986
2258
  TaskListRow.displayName = "TaskListRow";
1987
2259
 
1988
2260
  // src/components/TaskList/TaskList.tsx
1989
- 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"];
1990
2263
  var TaskList = ({
1991
2264
  tasks,
1992
2265
  rowHeight,
@@ -1996,28 +2269,156 @@ var TaskList = ({
1996
2269
  selectedTaskId,
1997
2270
  onTaskSelect,
1998
2271
  show = true,
1999
- disableTaskNameEditing = false
2272
+ disableTaskNameEditing = false,
2273
+ disableDependencyEditing = false,
2274
+ onScrollToTask,
2275
+ onSelectedChipChange
2000
2276
  }) => {
2001
- const totalHeight = useMemo7(
2277
+ const totalHeight = useMemo8(
2002
2278
  () => tasks.length * rowHeight,
2003
2279
  [tasks.length, rowHeight]
2004
2280
  );
2005
2281
  const handleRowClick = useCallback5((taskId) => {
2006
2282
  onTaskSelect?.(taskId);
2007
2283
  }, [onTaskSelect]);
2008
- 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(
2009
2376
  "div",
2010
2377
  {
2378
+ ref: overlayRef,
2011
2379
  className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}`,
2012
2380
  style: { width: `${taskListWidth}px` },
2013
- children: /* @__PURE__ */ jsxs9("div", { className: "gantt-tl-table", children: [
2014
- /* @__PURE__ */ jsxs9("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: [
2015
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-headerCell gantt-tl-cell-number", children: "\u2116" }),
2016
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-headerCell gantt-tl-cell-name", children: "\u0418\u043C\u044F" }),
2017
- /* @__PURE__ */ jsx12("div", { className: "gantt-tl-headerCell gantt-tl-cell-date", children: "\u041D\u0430\u0447\u0430\u043B\u043E" }),
2018
- /* @__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
+ ] })
2019
2420
  ] }),
2020
- /* @__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(
2021
2422
  TaskListRow,
2022
2423
  {
2023
2424
  task,
@@ -2026,7 +2427,17 @@ var TaskList = ({
2026
2427
  onTaskChange,
2027
2428
  selectedTaskId,
2028
2429
  onRowClick: handleRowClick,
2029
- 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
2030
2441
  },
2031
2442
  task.id
2032
2443
  )) })
@@ -2036,13 +2447,13 @@ var TaskList = ({
2036
2447
  };
2037
2448
 
2038
2449
  // src/components/GanttChart/GanttChart.tsx
2039
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
2450
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
2040
2451
  var GanttChart = forwardRef(({
2041
2452
  tasks,
2042
2453
  dayWidth = 40,
2043
2454
  rowHeight = 40,
2044
2455
  headerHeight = 40,
2045
- containerHeight = 600,
2456
+ containerHeight,
2046
2457
  onChange,
2047
2458
  onValidateDependencies,
2048
2459
  enableAutoSchedule,
@@ -2050,34 +2461,36 @@ var GanttChart = forwardRef(({
2050
2461
  onCascade,
2051
2462
  showTaskList = false,
2052
2463
  taskListWidth = 520,
2053
- disableTaskNameEditing = false
2464
+ disableTaskNameEditing = false,
2465
+ disableDependencyEditing = false
2054
2466
  }, ref) => {
2055
- const scrollContainerRef = useRef4(null);
2056
- const [selectedTaskId, setSelectedTaskId] = useState5(null);
2057
- const dateRange = useMemo8(() => getMultiMonthDays(tasks), [tasks]);
2058
- const [validationResult, setValidationResult] = useState5(null);
2059
- const [cascadeOverrides, setCascadeOverrides] = useState5(/* @__PURE__ */ new Map());
2060
- 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(
2061
2474
  () => Math.round(dateRange.length * dayWidth),
2062
2475
  [dateRange.length, dayWidth]
2063
2476
  );
2064
- const totalGridHeight = useMemo8(
2477
+ const totalGridHeight = useMemo9(
2065
2478
  () => tasks.length * rowHeight,
2066
2479
  [tasks.length, rowHeight]
2067
2480
  );
2068
- const monthStart = useMemo8(() => {
2481
+ const monthStart = useMemo9(() => {
2069
2482
  if (dateRange.length === 0) {
2070
2483
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
2071
2484
  }
2072
2485
  const firstDay = dateRange[0];
2073
2486
  return new Date(Date.UTC(firstDay.getUTCFullYear(), firstDay.getUTCMonth(), 1));
2074
2487
  }, [dateRange]);
2075
- const todayInRange = useMemo8(() => {
2488
+ const todayInRange = useMemo9(() => {
2076
2489
  const now = /* @__PURE__ */ new Date();
2077
2490
  const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
2078
2491
  return dateRange.some((day) => day.getTime() === today.getTime());
2079
2492
  }, [dateRange]);
2080
- useEffect4(() => {
2493
+ useEffect5(() => {
2081
2494
  const container = scrollContainerRef.current;
2082
2495
  if (!container || dateRange.length === 0) return;
2083
2496
  const now = /* @__PURE__ */ new Date();
@@ -2101,16 +2514,34 @@ var GanttChart = forwardRef(({
2101
2514
  const scrollLeft = Math.round(todayOffset - containerWidth / 2 + dayWidth / 2);
2102
2515
  container.scrollLeft = Math.max(0, scrollLeft);
2103
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]);
2104
2534
  useImperativeHandle(
2105
2535
  ref,
2106
2536
  () => ({
2107
- scrollToToday
2537
+ scrollToToday,
2538
+ scrollToTask
2108
2539
  }),
2109
- [scrollToToday]
2540
+ [scrollToToday, scrollToTask]
2110
2541
  );
2111
- const [dragGuideLines, setDragGuideLines] = useState5(null);
2112
- const [draggedTaskOverride, setDraggedTaskOverride] = useState5(null);
2113
- useEffect4(() => {
2542
+ const [dragGuideLines, setDragGuideLines] = useState6(null);
2543
+ const [draggedTaskOverride, setDraggedTaskOverride] = useState6(null);
2544
+ useEffect5(() => {
2114
2545
  const result = validateDependencies(tasks);
2115
2546
  setValidationResult(result);
2116
2547
  onValidateDependencies?.(result);
@@ -2139,7 +2570,7 @@ var GanttChart = forwardRef(({
2139
2570
  });
2140
2571
  onCascade?.(allCascaded);
2141
2572
  }, [tasks, onChange, disableConstraints, onCascade]);
2142
- const dependencyOverrides = useMemo8(() => {
2573
+ const dependencyOverrides = useMemo9(() => {
2143
2574
  const map = new Map(cascadeOverrides);
2144
2575
  if (draggedTaskOverride) {
2145
2576
  map.set(draggedTaskOverride.taskId, {
@@ -2162,7 +2593,7 @@ var GanttChart = forwardRef(({
2162
2593
  const handleTaskSelect = useCallback6((taskId) => {
2163
2594
  setSelectedTaskId(taskId);
2164
2595
  }, []);
2165
- const panStateRef = useRef4(null);
2596
+ const panStateRef = useRef5(null);
2166
2597
  const handlePanStart = useCallback6((e) => {
2167
2598
  if (e.button !== 0) return;
2168
2599
  const target = e.target;
@@ -2184,7 +2615,7 @@ var GanttChart = forwardRef(({
2184
2615
  container.style.cursor = "grabbing";
2185
2616
  e.preventDefault();
2186
2617
  }, []);
2187
- useEffect4(() => {
2618
+ useEffect5(() => {
2188
2619
  const handlePanMove = (e) => {
2189
2620
  const pan = panStateRef.current;
2190
2621
  if (!pan?.active) return;
@@ -2206,15 +2637,15 @@ var GanttChart = forwardRef(({
2206
2637
  window.removeEventListener("mouseup", handlePanEnd);
2207
2638
  };
2208
2639
  }, []);
2209
- return /* @__PURE__ */ jsx13("div", { className: "gantt-container", children: /* @__PURE__ */ jsx13(
2640
+ return /* @__PURE__ */ jsx14("div", { className: "gantt-container", children: /* @__PURE__ */ jsx14(
2210
2641
  "div",
2211
2642
  {
2212
2643
  ref: scrollContainerRef,
2213
2644
  className: "gantt-scrollContainer",
2214
- style: { height: `${containerHeight}px`, cursor: "grab" },
2645
+ style: { height: containerHeight ?? "auto", cursor: "grab" },
2215
2646
  onMouseDown: handlePanStart,
2216
- children: /* @__PURE__ */ jsxs10("div", { className: "gantt-scrollContent", children: [
2217
- /* @__PURE__ */ jsx13(
2647
+ children: /* @__PURE__ */ jsxs11("div", { className: "gantt-scrollContent", children: [
2648
+ /* @__PURE__ */ jsx14(
2218
2649
  TaskList,
2219
2650
  {
2220
2651
  tasks,
@@ -2225,11 +2656,14 @@ var GanttChart = forwardRef(({
2225
2656
  selectedTaskId: selectedTaskId ?? void 0,
2226
2657
  onTaskSelect: handleTaskSelect,
2227
2658
  show: showTaskList,
2228
- disableTaskNameEditing
2659
+ disableTaskNameEditing,
2660
+ disableDependencyEditing,
2661
+ onScrollToTask: scrollToTask,
2662
+ onSelectedChipChange: setSelectedChip
2229
2663
  }
2230
2664
  ),
2231
- /* @__PURE__ */ jsxs10("div", { style: { minWidth: `${gridWidth}px`, flex: 1 }, children: [
2232
- /* @__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(
2233
2667
  TimeScaleHeader_default,
2234
2668
  {
2235
2669
  days: dateRange,
@@ -2237,7 +2671,7 @@ var GanttChart = forwardRef(({
2237
2671
  headerHeight
2238
2672
  }
2239
2673
  ) }),
2240
- /* @__PURE__ */ jsxs10(
2674
+ /* @__PURE__ */ jsxs11(
2241
2675
  "div",
2242
2676
  {
2243
2677
  className: "gantt-taskArea",
@@ -2246,7 +2680,7 @@ var GanttChart = forwardRef(({
2246
2680
  width: `${gridWidth}px`
2247
2681
  },
2248
2682
  children: [
2249
- /* @__PURE__ */ jsx13(
2683
+ /* @__PURE__ */ jsx14(
2250
2684
  GridBackground_default,
2251
2685
  {
2252
2686
  dateRange,
@@ -2254,8 +2688,8 @@ var GanttChart = forwardRef(({
2254
2688
  totalHeight: totalGridHeight
2255
2689
  }
2256
2690
  ),
2257
- todayInRange && /* @__PURE__ */ jsx13(TodayIndicator_default, { monthStart, dayWidth }),
2258
- /* @__PURE__ */ jsx13(
2691
+ todayInRange && /* @__PURE__ */ jsx14(TodayIndicator_default, { monthStart, dayWidth }),
2692
+ /* @__PURE__ */ jsx14(
2259
2693
  DependencyLines_default,
2260
2694
  {
2261
2695
  tasks,
@@ -2263,10 +2697,11 @@ var GanttChart = forwardRef(({
2263
2697
  dayWidth,
2264
2698
  rowHeight,
2265
2699
  gridWidth,
2266
- dragOverrides: dependencyOverrides
2700
+ dragOverrides: dependencyOverrides,
2701
+ selectedDep: selectedChip
2267
2702
  }
2268
2703
  ),
2269
- dragGuideLines && /* @__PURE__ */ jsx13(
2704
+ dragGuideLines && /* @__PURE__ */ jsx14(
2270
2705
  DragGuideLines_default,
2271
2706
  {
2272
2707
  isDragging: dragGuideLines.isDragging,
@@ -2276,7 +2711,7 @@ var GanttChart = forwardRef(({
2276
2711
  totalHeight: totalGridHeight
2277
2712
  }
2278
2713
  ),
2279
- tasks.map((task, index) => /* @__PURE__ */ jsx13(
2714
+ tasks.map((task, index) => /* @__PURE__ */ jsx14(
2280
2715
  TaskRow_default,
2281
2716
  {
2282
2717
  task,
@@ -2315,7 +2750,7 @@ GanttChart.displayName = "GanttChart";
2315
2750
 
2316
2751
  // src/components/ui/Button.tsx
2317
2752
  import React12 from "react";
2318
- import { jsx as jsx14 } from "react/jsx-runtime";
2753
+ import { jsx as jsx15 } from "react/jsx-runtime";
2319
2754
  var Button = React12.forwardRef(
2320
2755
  ({ className, variant = "default", size = "default", children, ...props }, ref) => {
2321
2756
  const classes = [
@@ -2324,7 +2759,7 @@ var Button = React12.forwardRef(
2324
2759
  size !== "default" ? `gantt-btn-${size}` : "",
2325
2760
  className || ""
2326
2761
  ].filter(Boolean).join(" ");
2327
- return /* @__PURE__ */ jsx14("button", { ref, className: classes, ...props, children });
2762
+ return /* @__PURE__ */ jsx15("button", { ref, className: classes, ...props, children });
2328
2763
  }
2329
2764
  );
2330
2765
  Button.displayName = "Button";
@@ -2353,6 +2788,7 @@ export {
2353
2788
  calculateTaskBar,
2354
2789
  calculateWeekendBlocks,
2355
2790
  cascadeByLinks,
2791
+ computeLagFromDates,
2356
2792
  detectCycles,
2357
2793
  detectEdgeZone,
2358
2794
  formatDateLabel,