gantt-lib 0.82.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6434,7 +6434,7 @@ function createBuiltInColumns(opts) {
6434
6434
  }
6435
6435
 
6436
6436
  // src/components/TaskList/columns/resolveTaskListColumns.ts
6437
- function resolveTaskListColumns(builtIn, custom) {
6437
+ function resolveTaskListColumns(builtIn, custom, hiddenColumnIds = []) {
6438
6438
  if (process.env.NODE_ENV !== "production") {
6439
6439
  const ids = /* @__PURE__ */ new Set();
6440
6440
  for (const col of [...builtIn, ...custom]) {
@@ -6444,8 +6444,10 @@ function resolveTaskListColumns(builtIn, custom) {
6444
6444
  ids.add(col.id);
6445
6445
  }
6446
6446
  }
6447
+ const hiddenIds = new Set(hiddenColumnIds);
6448
+ const filterHiddenColumns = (columns) => hiddenIds.size === 0 ? columns : columns.filter((col) => !hiddenIds.has(col.id));
6447
6449
  if (custom.length === 0) {
6448
- return [...builtIn];
6450
+ return filterHiddenColumns([...builtIn]);
6449
6451
  }
6450
6452
  const result = [...builtIn];
6451
6453
  const lastInsertAfter = /* @__PURE__ */ new Map();
@@ -6481,7 +6483,7 @@ function resolveTaskListColumns(builtIn, custom) {
6481
6483
  }
6482
6484
  result.splice(insertAt, 0, col);
6483
6485
  }
6484
- return result;
6486
+ return filterHiddenColumns(result);
6485
6487
  }
6486
6488
 
6487
6489
  // src/components/TaskList/TaskList.tsx
@@ -6598,6 +6600,7 @@ var TaskList = ({
6598
6600
  filteredTaskIds = /* @__PURE__ */ new Set(),
6599
6601
  isFilterActive = false,
6600
6602
  additionalColumns,
6603
+ hiddenTaskListColumns,
6601
6604
  taskListMenuCommands
6602
6605
  }) => {
6603
6606
  const [internalCollapsedParentIds, setInternalCollapsedParentIds] = (0, import_react12.useState)(/* @__PURE__ */ new Set());
@@ -7129,14 +7132,19 @@ var TaskList = ({
7129
7132
  }, [orderedTasks, onReorder]);
7130
7133
  const builtInColumns = (0, import_react12.useMemo)(() => createBuiltInColumns({ businessDays }), [businessDays]);
7131
7134
  const resolvedColumns = (0, import_react12.useMemo)(
7132
- () => resolveTaskListColumns(builtInColumns, additionalColumns ?? []),
7133
- [builtInColumns, additionalColumns]
7135
+ () => resolveTaskListColumns(
7136
+ builtInColumns,
7137
+ additionalColumns ?? [],
7138
+ hiddenTaskListColumns
7139
+ ),
7140
+ [builtInColumns, additionalColumns, hiddenTaskListColumns]
7134
7141
  );
7135
7142
  const resolvedColumnWidthTotal = (0, import_react12.useMemo)(
7136
7143
  () => resolvedColumns.reduce((sum, col) => sum + (col.width ?? 120), 0),
7137
7144
  [resolvedColumns]
7138
7145
  );
7139
7146
  const effectiveTaskListWidth = Math.max(taskListWidth, MIN_TASK_LIST_WIDTH, resolvedColumnWidthTotal);
7147
+ const tableHeaderHeight = headerHeight + 1;
7140
7148
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7141
7149
  "div",
7142
7150
  {
@@ -7144,7 +7152,7 @@ var TaskList = ({
7144
7152
  className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}${hasRightShadow ? " gantt-tl-overlay-shadowed" : ""}`,
7145
7153
  style: { "--tasklist-width": `${effectiveTaskListWidth}px` },
7146
7154
  children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "gantt-tl-table", children: [
7147
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: resolvedColumns.map((col) => {
7155
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-header", style: { height: `${tableHeaderHeight}px` }, children: resolvedColumns.map((col) => {
7148
7156
  if (col.id === "dependencies") {
7149
7157
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
7150
7158
  "div",
@@ -7328,7 +7336,7 @@ var TaskList = ({
7328
7336
  };
7329
7337
 
7330
7338
  // src/components/ResourceTimelineChart/ResourceTimelineChart.tsx
7331
- var import_react14 = require("react");
7339
+ var import_react14 = __toESM(require("react"));
7332
7340
 
7333
7341
  // src/utils/resourceTimelineLayout.ts
7334
7342
  var isInvalidDate = (date) => Number.isNaN(date.getTime());
@@ -7748,6 +7756,7 @@ var DEFAULT_HEADER_HEIGHT = 40;
7748
7756
  var DEFAULT_LANE_HEIGHT = 40;
7749
7757
  var DEFAULT_ROW_HEADER_WIDTH = 420;
7750
7758
  var DEFAULT_RESOURCE_ROW_GAP = 8;
7759
+ var DEFAULT_RESOURCE_GROUP_HEIGHT = 28;
7751
7760
  var ITEM_OUTER_VERTICAL_INSET = 2;
7752
7761
  var ITEM_INNER_VERTICAL_INSET = 1;
7753
7762
  var ITEM_START_HORIZONTAL_INSET = 2;
@@ -7846,6 +7855,30 @@ var RESOURCE_TYPE_CLASS_NAMES = {
7846
7855
  \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: "Materials",
7847
7856
  \u0414\u0440\u0443\u0433\u043E\u0435: "Other"
7848
7857
  };
7858
+ var RESOURCE_TYPE_ORDER = {
7859
+ \u041B\u044E\u0434\u0438: 0,
7860
+ \u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435: 1,
7861
+ \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: 2,
7862
+ \u0414\u0440\u0443\u0433\u043E\u0435: 3
7863
+ };
7864
+ var RESOURCE_SCOPE_ORDER = {
7865
+ Shared: 0,
7866
+ Project: 1
7867
+ };
7868
+ var getResourceType = (resource) => resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7869
+ var getResourceScopeOrder = (resource) => RESOURCE_SCOPE_ORDER[resource.scope ?? "Project"] ?? 99;
7870
+ var orderResourcesByType = (resources) => {
7871
+ return resources.map((resource, index) => ({ resource, index })).sort((left, right) => {
7872
+ const leftType = getResourceType(left.resource);
7873
+ const rightType = getResourceType(right.resource);
7874
+ const typeDiff = (RESOURCE_TYPE_ORDER[leftType] ?? 99) - (RESOURCE_TYPE_ORDER[rightType] ?? 99);
7875
+ if (typeDiff !== 0) {
7876
+ return typeDiff;
7877
+ }
7878
+ const scopeDiff = getResourceScopeOrder(left.resource) - getResourceScopeOrder(right.resource);
7879
+ return scopeDiff !== 0 ? scopeDiff : left.index - right.index;
7880
+ }).map(({ resource }) => resource);
7881
+ };
7849
7882
  var ResourceTypeIcon = ({ type }) => {
7850
7883
  if (type === "\u041B\u044E\u0434\u0438") {
7851
7884
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconPeople", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
@@ -7886,12 +7919,16 @@ var ResourceHeader = ({
7886
7919
  paddingBottom,
7887
7920
  menuCommands,
7888
7921
  onResourceChange,
7922
+ onResourceNameClick,
7889
7923
  onConflictBadgeClick
7890
7924
  }) => {
7891
7925
  const [menuOpen, setMenuOpen] = (0, import_react14.useState)(false);
7892
7926
  const [typeMenuOpen, setTypeMenuOpen] = (0, import_react14.useState)(false);
7893
7927
  const [scopeMenuOpen, setScopeMenuOpen] = (0, import_react14.useState)(false);
7894
- const [draftName, setDraftName] = (0, import_react14.useState)(resource.name);
7928
+ const [editingName, setEditingName] = (0, import_react14.useState)(false);
7929
+ const [nameValue, setNameValue] = (0, import_react14.useState)(resource.name);
7930
+ const nameInputRef = (0, import_react14.useRef)(null);
7931
+ const nameConfirmedRef = (0, import_react14.useRef)(false);
7895
7932
  const visibleCommands = (0, import_react14.useMemo)(
7896
7933
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
7897
7934
  [menuCommands, resource]
@@ -7900,31 +7937,20 @@ var ResourceHeader = ({
7900
7937
  const type = resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7901
7938
  const scope = resource.scope ?? "Project";
7902
7939
  const scopeLabel = RESOURCE_SCOPE_LABELS[scope] ?? scope;
7903
- (0, import_react14.useEffect)(() => {
7904
- setDraftName(resource.name);
7905
- }, [resource.name]);
7906
7940
  const applyResourcePatch = (0, import_react14.useCallback)((patch) => {
7907
7941
  onResourceChange?.({ ...resource, ...patch });
7908
7942
  }, [onResourceChange, resource]);
7909
- const handleNameCommit = (0, import_react14.useCallback)(() => {
7910
- const nextName = draftName.trim();
7911
- if (!nextName) {
7912
- setDraftName(resource.name);
7913
- return;
7914
- }
7915
- if (nextName !== resource.name) {
7916
- applyResourcePatch({ name: nextName });
7943
+ (0, import_react14.useEffect)(() => {
7944
+ if (!editingName) {
7945
+ setNameValue(resource.name);
7917
7946
  }
7918
- }, [applyResourcePatch, draftName, resource.name]);
7919
- const handleNameKeyDown = (0, import_react14.useCallback)((event) => {
7920
- if (event.key === "Enter") {
7921
- event.preventDefault();
7922
- event.currentTarget.blur();
7923
- } else if (event.key === "Escape") {
7924
- setDraftName(resource.name);
7925
- event.currentTarget.blur();
7947
+ }, [editingName, resource.name]);
7948
+ (0, import_react14.useEffect)(() => {
7949
+ if (editingName && nameInputRef.current) {
7950
+ nameInputRef.current.focus();
7951
+ nameInputRef.current.select();
7926
7952
  }
7927
- }, [resource.name]);
7953
+ }, [editingName]);
7928
7954
  const handleCommandClick = (command, event) => {
7929
7955
  event.stopPropagation();
7930
7956
  if (command.closeOnSelect !== false) {
@@ -7932,6 +7958,42 @@ var ResourceHeader = ({
7932
7958
  }
7933
7959
  command.onSelect(resource);
7934
7960
  };
7961
+ const handleNameDoubleClick = (0, import_react14.useCallback)((event) => {
7962
+ if (!onResourceChange) {
7963
+ return;
7964
+ }
7965
+ event.stopPropagation();
7966
+ nameConfirmedRef.current = false;
7967
+ setNameValue(resource.name);
7968
+ setEditingName(true);
7969
+ }, [onResourceChange, resource.name]);
7970
+ const handleNameSave = (0, import_react14.useCallback)(() => {
7971
+ if (nameConfirmedRef.current) {
7972
+ nameConfirmedRef.current = false;
7973
+ return;
7974
+ }
7975
+ const nextName = nameValue.trim();
7976
+ if (nextName && nextName !== resource.name) {
7977
+ applyResourcePatch({ name: nextName });
7978
+ }
7979
+ setEditingName(false);
7980
+ }, [applyResourcePatch, nameValue, resource.name]);
7981
+ const handleNameCancel = (0, import_react14.useCallback)(() => {
7982
+ setNameValue(resource.name);
7983
+ setEditingName(false);
7984
+ }, [resource.name]);
7985
+ const handleNameKeyDown = (0, import_react14.useCallback)((event) => {
7986
+ if (event.key === "Enter") {
7987
+ nameConfirmedRef.current = true;
7988
+ const nextName = nameValue.trim();
7989
+ if (nextName && nextName !== resource.name) {
7990
+ applyResourcePatch({ name: nextName });
7991
+ }
7992
+ setEditingName(false);
7993
+ } else if (event.key === "Escape") {
7994
+ handleNameCancel();
7995
+ }
7996
+ }, [applyResourcePatch, handleNameCancel, nameValue, resource.name]);
7935
7997
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7936
7998
  "div",
7937
7999
  {
@@ -7978,18 +8040,31 @@ var ResourceHeader = ({
7978
8040
  option
7979
8041
  )) })
7980
8042
  ] }),
7981
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7982
- "textarea",
8043
+ editingName ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8044
+ Input,
7983
8045
  {
7984
- className: "gantt-resourceTimeline-resourceNameInput",
7985
- value: draftName,
7986
- disabled: !onResourceChange,
7987
- "aria-label": `\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7988
- rows: 2,
7989
- onChange: (event) => setDraftName(event.target.value),
7990
- onBlur: handleNameCommit,
8046
+ ref: nameInputRef,
8047
+ value: nameValue,
8048
+ onChange: (event) => setNameValue(event.target.value),
8049
+ onBlur: handleNameSave,
7991
8050
  onKeyDown: handleNameKeyDown,
7992
- onClick: (event) => event.stopPropagation()
8051
+ onClick: (event) => event.stopPropagation(),
8052
+ "aria-label": `\u041D\u043E\u0432\u043E\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
8053
+ className: "gantt-tl-name-input gantt-resourceTimeline-resourceNameInput"
8054
+ }
8055
+ ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8056
+ "button",
8057
+ {
8058
+ type: "button",
8059
+ className: "gantt-resourceTimeline-resourceNameButton",
8060
+ "aria-label": `\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
8061
+ title: resource.name,
8062
+ onClick: (event) => {
8063
+ event.stopPropagation();
8064
+ onResourceNameClick?.(resourceId);
8065
+ },
8066
+ onDoubleClick: handleNameDoubleClick,
8067
+ children: resource.name
7993
8068
  }
7994
8069
  )
7995
8070
  ] }),
@@ -8033,15 +8108,15 @@ var ResourceHeader = ({
8033
8108
  "aria-label": `\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}: ${assignmentCount}, ${workedDays} \u0434\u043D.`,
8034
8109
  children: [
8035
8110
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gantt-resourceTimeline-resourceWorkedDays", children: [
8036
- workedDays,
8037
- " \u0434\u043D."
8111
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: workedDays > 0 ? workedDays : "-" }),
8112
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceMetricLabel", children: workedDays > 0 ? "\u0434\u043D." : "" })
8038
8113
  ] }),
8039
8114
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gantt-resourceTimeline-resourceAssignmentCount", children: [
8040
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { className: "gantt-resourceTimeline-resourceAssignmentIcon", width: "14", height: "14", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
8115
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: assignmentCount > 0 ? assignmentCount : "" }),
8116
+ assignmentCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { className: "gantt-resourceTimeline-resourceAssignmentIcon", width: "14", height: "14", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
8041
8117
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("rect", { width: "15", height: "5", x: "4", y: "5", rx: "2" }),
8042
8118
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("rect", { width: "10", height: "5", x: "4", y: "14", rx: "2" })
8043
- ] }),
8044
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: assignmentCount })
8119
+ ] })
8045
8120
  ] })
8046
8121
  ]
8047
8122
  }
@@ -8166,6 +8241,7 @@ function ResourceTimelineChart({
8166
8241
  customDays,
8167
8242
  isWeekend: isWeekend3,
8168
8243
  businessDays = true,
8244
+ resourceGrouping = false,
8169
8245
  readonly,
8170
8246
  disableResourceReassignment,
8171
8247
  renderItem,
@@ -8183,9 +8259,12 @@ function ResourceTimelineChart({
8183
8259
  const gridRef = (0, import_react14.useRef)(null);
8184
8260
  const panStateRef = (0, import_react14.useRef)(null);
8185
8261
  const conflictNavigationIndexRef = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
8262
+ const resourceNavigationIndexRef = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
8186
8263
  const conflictHighlightTimeoutRef = (0, import_react14.useRef)(null);
8187
8264
  const [isCreatingResource, setIsCreatingResource] = (0, import_react14.useState)(false);
8265
+ const [creatingResourceGroupType, setCreatingResourceGroupType] = (0, import_react14.useState)(null);
8188
8266
  const [activeConflictItemId, setActiveConflictItemId] = (0, import_react14.useState)(null);
8267
+ const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = (0, import_react14.useState)(false);
8189
8268
  const validItems = (0, import_react14.useMemo)(() => collectValidItems(resources), [resources]);
8190
8269
  const dateRange = (0, import_react14.useMemo)(() => getMultiMonthDays(validItems), [validItems]);
8191
8270
  const monthStart = (0, import_react14.useMemo)(() => {
@@ -8198,6 +8277,10 @@ function ResourceTimelineChart({
8198
8277
  );
8199
8278
  const gridWidth = (0, import_react14.useMemo)(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
8200
8279
  const effectiveRowHeaderWidth = Math.max(rowHeaderWidth, DEFAULT_ROW_HEADER_WIDTH);
8280
+ const orderedResources = (0, import_react14.useMemo)(
8281
+ () => resourceGrouping === "type" ? orderResourcesByType(resources) : resources,
8282
+ [resourceGrouping, resources]
8283
+ );
8201
8284
  const workedDaysByResourceId = (0, import_react14.useMemo)(() => {
8202
8285
  const map = /* @__PURE__ */ new Map();
8203
8286
  for (const resource of resources) {
@@ -8218,14 +8301,87 @@ function ResourceTimelineChart({
8218
8301
  return map;
8219
8302
  }, [businessDays, resources, weekendPredicate]);
8220
8303
  const layout = (0, import_react14.useMemo)(
8221
- () => layoutResourceTimelineItems(resources, {
8304
+ () => layoutResourceTimelineItems(orderedResources, {
8222
8305
  monthStart,
8223
8306
  dayWidth,
8224
8307
  laneHeight,
8225
8308
  rowGap: DEFAULT_RESOURCE_ROW_GAP
8226
8309
  }),
8227
- [resources, monthStart, dayWidth, laneHeight]
8310
+ [orderedResources, monthStart, dayWidth, laneHeight]
8228
8311
  );
8312
+ const canAddResource = enableAddResource && Boolean(onAddResource);
8313
+ const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8314
+ const displayLayout = (0, import_react14.useMemo)(() => {
8315
+ if (resourceGrouping !== "type") {
8316
+ return {
8317
+ rows: layout.rows,
8318
+ items: layout.items,
8319
+ groupHeaders: [],
8320
+ groupAddRows: [],
8321
+ totalHeight: layout.totalHeight
8322
+ };
8323
+ }
8324
+ const countsByType = /* @__PURE__ */ new Map();
8325
+ for (const row of layout.rows) {
8326
+ const type = getResourceType(row.resource);
8327
+ countsByType.set(type, (countsByType.get(type) ?? 0) + 1);
8328
+ }
8329
+ let offset = 0;
8330
+ let previousType = null;
8331
+ const rowTopOffsets = /* @__PURE__ */ new Map();
8332
+ const groupHeaders = [];
8333
+ const groupAddRows = [];
8334
+ const rows = layout.rows.map((row) => {
8335
+ const type = getResourceType(row.resource);
8336
+ if (type !== previousType) {
8337
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8338
+ groupAddRows.push({
8339
+ key: previousType,
8340
+ top: row.resourceRowTop + offset,
8341
+ height: resourceAddRowHeight
8342
+ });
8343
+ offset += resourceAddRowHeight;
8344
+ }
8345
+ groupHeaders.push({
8346
+ key: type,
8347
+ label: type,
8348
+ count: countsByType.get(type) ?? 0,
8349
+ top: row.resourceRowTop + offset,
8350
+ height: DEFAULT_RESOURCE_GROUP_HEIGHT
8351
+ });
8352
+ offset += DEFAULT_RESOURCE_GROUP_HEIGHT;
8353
+ previousType = type;
8354
+ }
8355
+ rowTopOffsets.set(row.resourceId, offset);
8356
+ return {
8357
+ ...row,
8358
+ resourceRowTop: row.resourceRowTop + offset
8359
+ };
8360
+ });
8361
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8362
+ groupAddRows.push({
8363
+ key: previousType,
8364
+ top: layout.totalHeight + offset,
8365
+ height: resourceAddRowHeight
8366
+ });
8367
+ offset += resourceAddRowHeight;
8368
+ }
8369
+ const items = layout.items.map((item) => {
8370
+ const itemOffset = rowTopOffsets.get(item.resourceId) ?? 0;
8371
+ return {
8372
+ ...item,
8373
+ resourceRowTop: item.resourceRowTop + itemOffset,
8374
+ top: item.top + itemOffset
8375
+ };
8376
+ });
8377
+ return {
8378
+ rows,
8379
+ items,
8380
+ groupHeaders,
8381
+ groupAddRows,
8382
+ totalHeight: layout.totalHeight + offset
8383
+ };
8384
+ }, [creatingResourceGroupType, layout, resourceAddRowHeight, resourceGrouping]);
8229
8385
  const todayInRange = (0, import_react14.useMemo)(() => {
8230
8386
  const now = /* @__PURE__ */ new Date();
8231
8387
  const today = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
@@ -8233,16 +8389,16 @@ function ResourceTimelineChart({
8233
8389
  }, [dateRange]);
8234
8390
  const itemsByResourceId = (0, import_react14.useMemo)(() => {
8235
8391
  const map = /* @__PURE__ */ new Map();
8236
- for (const item of layout.items) {
8392
+ for (const item of displayLayout.items) {
8237
8393
  const next = map.get(item.resourceId) ?? [];
8238
8394
  next.push(item);
8239
8395
  map.set(item.resourceId, next);
8240
8396
  }
8241
8397
  return map;
8242
- }, [layout.items]);
8398
+ }, [displayLayout.items]);
8243
8399
  const conflictItemsByResourceId = (0, import_react14.useMemo)(() => {
8244
8400
  const map = /* @__PURE__ */ new Map();
8245
- for (const item of layout.items) {
8401
+ for (const item of displayLayout.items) {
8246
8402
  if (item.conflictRanges.length === 0) {
8247
8403
  continue;
8248
8404
  }
@@ -8256,21 +8412,28 @@ function ResourceTimelineChart({
8256
8412
  );
8257
8413
  }
8258
8414
  return map;
8259
- }, [layout.items]);
8260
- const canAddResource = enableAddResource && Boolean(onAddResource);
8261
- const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8262
- const displayTotalHeight = layout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8415
+ }, [displayLayout.items]);
8416
+ const displayTotalHeight = displayLayout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8417
+ const timelineHeaderHeight = headerHeight + 1;
8263
8418
  const handleConfirmNewResource = (0, import_react14.useCallback)((name) => {
8264
8419
  onAddResource?.({
8265
8420
  id: crypto.randomUUID(),
8266
8421
  name,
8267
- type: "\u0414\u0440\u0443\u0433\u043E\u0435",
8422
+ type: creatingResourceGroupType ?? "\u0414\u0440\u0443\u0433\u043E\u0435",
8268
8423
  scope: "Project",
8269
8424
  items: []
8270
8425
  });
8271
8426
  setIsCreatingResource(false);
8272
- }, [onAddResource]);
8273
- const handleCancelNewResource = (0, import_react14.useCallback)(() => setIsCreatingResource(false), []);
8427
+ setCreatingResourceGroupType(null);
8428
+ }, [creatingResourceGroupType, onAddResource]);
8429
+ const handleStartAddResourceToGroup = (0, import_react14.useCallback)((type) => {
8430
+ setIsCreatingResource(false);
8431
+ setCreatingResourceGroupType(type);
8432
+ }, []);
8433
+ const handleCancelNewResource = (0, import_react14.useCallback)(() => {
8434
+ setIsCreatingResource(false);
8435
+ setCreatingResourceGroupType(null);
8436
+ }, []);
8274
8437
  const handleConflictBadgeClick = (0, import_react14.useCallback)((resourceId) => {
8275
8438
  const conflictItems = conflictItemsByResourceId.get(resourceId) ?? [];
8276
8439
  if (conflictItems.length === 0) {
@@ -8296,10 +8459,31 @@ function ResourceTimelineChart({
8296
8459
  conflictHighlightTimeoutRef.current = null;
8297
8460
  }, 1600);
8298
8461
  }, [conflictItemsByResourceId, dayWidth, laneHeight]);
8462
+ const handleResourceNameClick = (0, import_react14.useCallback)((resourceId) => {
8463
+ const resourceItems = itemsByResourceId.get(resourceId) ?? [];
8464
+ if (resourceItems.length === 0) {
8465
+ return;
8466
+ }
8467
+ const orderedItems = [...resourceItems].sort(
8468
+ (left, right) => left.left - right.left || left.top - right.top || left.itemId.localeCompare(right.itemId)
8469
+ );
8470
+ const currentIndex = resourceNavigationIndexRef.current.get(resourceId) ?? 0;
8471
+ const targetItem = orderedItems[currentIndex % orderedItems.length];
8472
+ resourceNavigationIndexRef.current.set(resourceId, (currentIndex + 1) % orderedItems.length);
8473
+ const container = scrollContainerRef.current;
8474
+ if (!container || !targetItem) {
8475
+ return;
8476
+ }
8477
+ container.scrollTo({
8478
+ left: Math.max(0, Math.round(targetItem.left - dayWidth * 2)),
8479
+ top: Math.max(0, Math.round(targetItem.top - laneHeight)),
8480
+ behavior: "smooth"
8481
+ });
8482
+ }, [dayWidth, itemsByResourceId, laneHeight]);
8299
8483
  const { preview, startDrag } = useResourceItemDrag({
8300
8484
  dayWidth,
8301
8485
  monthStart,
8302
- rows: layout.rows,
8486
+ rows: displayLayout.rows,
8303
8487
  gridElementRef: gridRef,
8304
8488
  readonly,
8305
8489
  disableResourceReassignment,
@@ -8307,6 +8491,18 @@ function ResourceTimelineChart({
8307
8491
  weekendPredicate,
8308
8492
  onResourceItemMove
8309
8493
  });
8494
+ (0, import_react14.useEffect)(() => {
8495
+ const container = scrollContainerRef.current;
8496
+ if (!container) return;
8497
+ const updateShadow = () => {
8498
+ setResourceColumnHasRightShadow(container.scrollLeft > 0);
8499
+ };
8500
+ updateShadow();
8501
+ container.addEventListener("scroll", updateShadow, { passive: true });
8502
+ return () => {
8503
+ container.removeEventListener("scroll", updateShadow);
8504
+ };
8505
+ }, []);
8310
8506
  const handlePanStart = (0, import_react14.useCallback)((event) => {
8311
8507
  if (event.button !== 0) {
8312
8508
  return;
@@ -8364,6 +8560,16 @@ function ResourceTimelineChart({
8364
8560
  window.removeEventListener("mouseup", handlePanEnd);
8365
8561
  };
8366
8562
  }, [allowVerticalPan]);
8563
+ (0, import_react14.useEffect)(() => {
8564
+ const nextNavigation = /* @__PURE__ */ new Map();
8565
+ for (const [resourceId, resourceItems] of itemsByResourceId.entries()) {
8566
+ if (resourceItems.length === 0) {
8567
+ continue;
8568
+ }
8569
+ nextNavigation.set(resourceId, (resourceNavigationIndexRef.current.get(resourceId) ?? 0) % resourceItems.length);
8570
+ }
8571
+ resourceNavigationIndexRef.current = nextNavigation;
8572
+ }, [itemsByResourceId]);
8367
8573
  (0, import_react14.useEffect)(() => {
8368
8574
  return () => {
8369
8575
  if (conflictHighlightTimeoutRef.current) {
@@ -8387,14 +8593,14 @@ function ResourceTimelineChart({
8387
8593
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8388
8594
  "div",
8389
8595
  {
8390
- className: "gantt-resourceTimeline-resourceColumn",
8596
+ className: `gantt-resourceTimeline-resourceColumn${resourceColumnHasRightShadow ? " gantt-resourceTimeline-resourceColumnShadowed" : ""}`,
8391
8597
  style: { width: `${effectiveRowHeaderWidth}px`, minWidth: `${effectiveRowHeaderWidth}px` },
8392
8598
  children: [
8393
8599
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8394
8600
  "div",
8395
8601
  {
8396
8602
  className: "gantt-resourceTimeline-corner",
8397
- style: { height: `${headerHeight + 0.5}px` },
8603
+ style: { height: `${timelineHeaderHeight}px` },
8398
8604
  children: [
8399
8605
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderNumber", children: "#" }),
8400
8606
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderName", children: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435" }),
@@ -8404,7 +8610,62 @@ function ResourceTimelineChart({
8404
8610
  ]
8405
8611
  }
8406
8612
  ),
8407
- layout.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8613
+ displayLayout.groupHeaders.length > 0 ? displayLayout.groupHeaders.map((group) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react14.default.Fragment, { children: [
8614
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8615
+ "div",
8616
+ {
8617
+ className: "gantt-resourceTimeline-resourceGroupHeader",
8618
+ style: { height: `${group.height}px` },
8619
+ children: [
8620
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceGroupTitle", children: group.label }),
8621
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceGroupCount", children: group.count }),
8622
+ canAddResource && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8623
+ "button",
8624
+ {
8625
+ type: "button",
8626
+ className: "gantt-resourceTimeline-resourceGroupAddButton",
8627
+ "aria-label": `\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441 \u0432 \u0433\u0440\u0443\u043F\u043F\u0443 ${group.label}`,
8628
+ title: `\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441 \u0432 \u0433\u0440\u0443\u043F\u043F\u0443 ${group.label}`,
8629
+ onClick: (event) => {
8630
+ event.stopPropagation();
8631
+ handleStartAddResourceToGroup(group.key);
8632
+ },
8633
+ children: "+"
8634
+ }
8635
+ )
8636
+ ]
8637
+ }
8638
+ ),
8639
+ displayLayout.rows.filter((row) => getResourceType(row.resource) === group.key).map((row) => {
8640
+ const rowIndex = displayLayout.rows.findIndex((candidate) => candidate.resourceId === row.resourceId);
8641
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8642
+ ResourceHeader,
8643
+ {
8644
+ resource: row.resource,
8645
+ resourceId: row.resourceId,
8646
+ rowIndex,
8647
+ conflictCount: row.conflictCount,
8648
+ workedDays: workedDaysByResourceId.get(row.resourceId) ?? 0,
8649
+ assignmentCount: row.resource.items.length,
8650
+ height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8651
+ paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8652
+ menuCommands: resourceMenuCommands,
8653
+ onResourceChange,
8654
+ onResourceNameClick: handleResourceNameClick,
8655
+ onConflictBadgeClick: handleConflictBadgeClick
8656
+ },
8657
+ row.resourceId
8658
+ );
8659
+ }),
8660
+ displayLayout.groupAddRows.some((row) => row.key === group.key) && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8661
+ NewResourceRow,
8662
+ {
8663
+ height: resourceAddRowHeight,
8664
+ onConfirm: handleConfirmNewResource,
8665
+ onCancel: handleCancelNewResource
8666
+ }
8667
+ )
8668
+ ] }, group.key)) : displayLayout.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8408
8669
  ResourceHeader,
8409
8670
  {
8410
8671
  resource: row.resource,
@@ -8417,6 +8678,7 @@ function ResourceTimelineChart({
8417
8678
  paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8418
8679
  menuCommands: resourceMenuCommands,
8419
8680
  onResourceChange,
8681
+ onResourceNameClick: handleResourceNameClick,
8420
8682
  onConflictBadgeClick: handleConflictBadgeClick
8421
8683
  },
8422
8684
  row.resourceId
@@ -8434,7 +8696,10 @@ function ResourceTimelineChart({
8434
8696
  className: "gantt-resourceTimeline-addResourceButton",
8435
8697
  type: "button",
8436
8698
  style: { height: `${resourceAddRowHeight}px` },
8437
- onClick: () => setIsCreatingResource(true),
8699
+ onClick: () => {
8700
+ setCreatingResourceGroupType(null);
8701
+ setIsCreatingResource(true);
8702
+ },
8438
8703
  children: "+ \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441"
8439
8704
  }
8440
8705
  ))
@@ -8447,16 +8712,23 @@ function ResourceTimelineChart({
8447
8712
  className: "gantt-resourceTimeline-chartSurface",
8448
8713
  style: { minWidth: `${gridWidth}px` },
8449
8714
  children: [
8450
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "gantt-resourceTimeline-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8451
- TimeScaleHeader_default,
8715
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8716
+ "div",
8452
8717
  {
8453
- days: dateRange,
8454
- dayWidth,
8455
- headerHeight,
8456
- viewMode,
8457
- isCustomWeekend: weekendPredicate
8718
+ className: "gantt-resourceTimeline-stickyHeader",
8719
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
8720
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8721
+ TimeScaleHeader_default,
8722
+ {
8723
+ days: dateRange,
8724
+ dayWidth,
8725
+ headerHeight,
8726
+ viewMode,
8727
+ isCustomWeekend: weekendPredicate
8728
+ }
8729
+ )
8458
8730
  }
8459
- ) }),
8731
+ ),
8460
8732
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8461
8733
  "div",
8462
8734
  {
@@ -8475,7 +8747,19 @@ function ResourceTimelineChart({
8475
8747
  }
8476
8748
  ),
8477
8749
  todayInRange && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TodayIndicator_default, { monthStart, dayWidth }),
8478
- layout.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8750
+ displayLayout.groupHeaders.map((group) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8751
+ "div",
8752
+ {
8753
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-groupRow",
8754
+ "data-resource-group": group.key,
8755
+ style: {
8756
+ top: `${group.top}px`,
8757
+ height: `${group.height}px`
8758
+ }
8759
+ },
8760
+ `group-row-${group.key}`
8761
+ )),
8762
+ displayLayout.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8479
8763
  "div",
8480
8764
  {
8481
8765
  className: "gantt-resourceTimeline-row",
@@ -8493,11 +8777,23 @@ function ResourceTimelineChart({
8493
8777
  className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8494
8778
  "data-resource-add-row": "true",
8495
8779
  style: {
8496
- top: `${layout.totalHeight}px`,
8780
+ top: `${displayLayout.totalHeight}px`,
8497
8781
  height: `${resourceAddRowHeight}px`
8498
8782
  }
8499
8783
  }
8500
8784
  ),
8785
+ displayLayout.groupAddRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8786
+ "div",
8787
+ {
8788
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8789
+ "data-resource-group-add-row": row.key,
8790
+ style: {
8791
+ top: `${row.top}px`,
8792
+ height: `${row.height}px`
8793
+ }
8794
+ },
8795
+ `group-add-row-${row.key}`
8796
+ )),
8501
8797
  Array.from(itemsByResourceId.values()).flatMap(
8502
8798
  (resourceItems) => resourceItems.map((layoutItem) => {
8503
8799
  const customClassName = getItemClassName?.(layoutItem.item);
@@ -9046,6 +9342,7 @@ function TaskGanttChartInner(props, ref) {
9046
9342
  disableTaskDrag = false,
9047
9343
  showChart = true,
9048
9344
  additionalColumns,
9345
+ hiddenTaskListColumns,
9049
9346
  taskListMenuCommands
9050
9347
  } = props;
9051
9348
  const containerRef = (0, import_react15.useRef)(null);
@@ -9104,6 +9401,7 @@ function TaskGanttChartInner(props, ref) {
9104
9401
  () => visibleTasks.length * rowHeight,
9105
9402
  [visibleTasks.length, rowHeight]
9106
9403
  );
9404
+ const timelineHeaderHeight = headerHeight + 1;
9107
9405
  const monthStart = (0, import_react15.useMemo)(() => {
9108
9406
  if (dateRange.length === 0) {
9109
9407
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
@@ -9577,6 +9875,7 @@ function TaskGanttChartInner(props, ref) {
9577
9875
  filteredTaskIds: matchedTaskIds,
9578
9876
  isFilterActive: !!taskFilter,
9579
9877
  additionalColumns,
9878
+ hiddenTaskListColumns,
9580
9879
  taskListMenuCommands
9581
9880
  }
9582
9881
  ),
@@ -9586,16 +9885,23 @@ function TaskGanttChartInner(props, ref) {
9586
9885
  className: showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
9587
9886
  style: { minWidth: `${gridWidth}px`, flex: 1, display: showChart ? void 0 : "none" },
9588
9887
  children: [
9589
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
9590
- TimeScaleHeader_default,
9888
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
9889
+ "div",
9591
9890
  {
9592
- days: dateRange,
9593
- dayWidth,
9594
- headerHeight,
9595
- viewMode,
9596
- isCustomWeekend
9891
+ className: "gantt-stickyHeader",
9892
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
9893
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
9894
+ TimeScaleHeader_default,
9895
+ {
9896
+ days: dateRange,
9897
+ dayWidth,
9898
+ headerHeight,
9899
+ viewMode,
9900
+ isCustomWeekend
9901
+ }
9902
+ )
9597
9903
  }
9598
- ) }),
9904
+ ),
9599
9905
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
9600
9906
  "div",
9601
9907
  {