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.mjs CHANGED
@@ -6320,7 +6320,7 @@ function createBuiltInColumns(opts) {
6320
6320
  }
6321
6321
 
6322
6322
  // src/components/TaskList/columns/resolveTaskListColumns.ts
6323
- function resolveTaskListColumns(builtIn, custom) {
6323
+ function resolveTaskListColumns(builtIn, custom, hiddenColumnIds = []) {
6324
6324
  if (process.env.NODE_ENV !== "production") {
6325
6325
  const ids = /* @__PURE__ */ new Set();
6326
6326
  for (const col of [...builtIn, ...custom]) {
@@ -6330,8 +6330,10 @@ function resolveTaskListColumns(builtIn, custom) {
6330
6330
  ids.add(col.id);
6331
6331
  }
6332
6332
  }
6333
+ const hiddenIds = new Set(hiddenColumnIds);
6334
+ const filterHiddenColumns = (columns) => hiddenIds.size === 0 ? columns : columns.filter((col) => !hiddenIds.has(col.id));
6333
6335
  if (custom.length === 0) {
6334
- return [...builtIn];
6336
+ return filterHiddenColumns([...builtIn]);
6335
6337
  }
6336
6338
  const result = [...builtIn];
6337
6339
  const lastInsertAfter = /* @__PURE__ */ new Map();
@@ -6367,7 +6369,7 @@ function resolveTaskListColumns(builtIn, custom) {
6367
6369
  }
6368
6370
  result.splice(insertAt, 0, col);
6369
6371
  }
6370
- return result;
6372
+ return filterHiddenColumns(result);
6371
6373
  }
6372
6374
 
6373
6375
  // src/components/TaskList/TaskList.tsx
@@ -6484,6 +6486,7 @@ var TaskList = ({
6484
6486
  filteredTaskIds = /* @__PURE__ */ new Set(),
6485
6487
  isFilterActive = false,
6486
6488
  additionalColumns,
6489
+ hiddenTaskListColumns,
6487
6490
  taskListMenuCommands
6488
6491
  }) => {
6489
6492
  const [internalCollapsedParentIds, setInternalCollapsedParentIds] = useState6(/* @__PURE__ */ new Set());
@@ -7015,14 +7018,19 @@ var TaskList = ({
7015
7018
  }, [orderedTasks, onReorder]);
7016
7019
  const builtInColumns = useMemo8(() => createBuiltInColumns({ businessDays }), [businessDays]);
7017
7020
  const resolvedColumns = useMemo8(
7018
- () => resolveTaskListColumns(builtInColumns, additionalColumns ?? []),
7019
- [builtInColumns, additionalColumns]
7021
+ () => resolveTaskListColumns(
7022
+ builtInColumns,
7023
+ additionalColumns ?? [],
7024
+ hiddenTaskListColumns
7025
+ ),
7026
+ [builtInColumns, additionalColumns, hiddenTaskListColumns]
7020
7027
  );
7021
7028
  const resolvedColumnWidthTotal = useMemo8(
7022
7029
  () => resolvedColumns.reduce((sum, col) => sum + (col.width ?? 120), 0),
7023
7030
  [resolvedColumns]
7024
7031
  );
7025
7032
  const effectiveTaskListWidth = Math.max(taskListWidth, MIN_TASK_LIST_WIDTH, resolvedColumnWidthTotal);
7033
+ const tableHeaderHeight = headerHeight + 1;
7026
7034
  return /* @__PURE__ */ jsx14(
7027
7035
  "div",
7028
7036
  {
@@ -7030,7 +7038,7 @@ var TaskList = ({
7030
7038
  className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}${hasRightShadow ? " gantt-tl-overlay-shadowed" : ""}`,
7031
7039
  style: { "--tasklist-width": `${effectiveTaskListWidth}px` },
7032
7040
  children: /* @__PURE__ */ jsxs11("div", { className: "gantt-tl-table", children: [
7033
- /* @__PURE__ */ jsx14("div", { className: "gantt-tl-header", style: { height: `${headerHeight + 0.5}px` }, children: resolvedColumns.map((col) => {
7041
+ /* @__PURE__ */ jsx14("div", { className: "gantt-tl-header", style: { height: `${tableHeaderHeight}px` }, children: resolvedColumns.map((col) => {
7034
7042
  if (col.id === "dependencies") {
7035
7043
  return /* @__PURE__ */ jsxs11(
7036
7044
  "div",
@@ -7214,7 +7222,7 @@ var TaskList = ({
7214
7222
  };
7215
7223
 
7216
7224
  // src/components/ResourceTimelineChart/ResourceTimelineChart.tsx
7217
- import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo9, useRef as useRef8, useState as useState8 } from "react";
7225
+ import React12, { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo9, useRef as useRef8, useState as useState8 } from "react";
7218
7226
 
7219
7227
  // src/utils/resourceTimelineLayout.ts
7220
7228
  var isInvalidDate = (date) => Number.isNaN(date.getTime());
@@ -7634,6 +7642,7 @@ var DEFAULT_HEADER_HEIGHT = 40;
7634
7642
  var DEFAULT_LANE_HEIGHT = 40;
7635
7643
  var DEFAULT_ROW_HEADER_WIDTH = 420;
7636
7644
  var DEFAULT_RESOURCE_ROW_GAP = 8;
7645
+ var DEFAULT_RESOURCE_GROUP_HEIGHT = 28;
7637
7646
  var ITEM_OUTER_VERTICAL_INSET = 2;
7638
7647
  var ITEM_INNER_VERTICAL_INSET = 1;
7639
7648
  var ITEM_START_HORIZONTAL_INSET = 2;
@@ -7732,6 +7741,30 @@ var RESOURCE_TYPE_CLASS_NAMES = {
7732
7741
  \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: "Materials",
7733
7742
  \u0414\u0440\u0443\u0433\u043E\u0435: "Other"
7734
7743
  };
7744
+ var RESOURCE_TYPE_ORDER = {
7745
+ \u041B\u044E\u0434\u0438: 0,
7746
+ \u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435: 1,
7747
+ \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: 2,
7748
+ \u0414\u0440\u0443\u0433\u043E\u0435: 3
7749
+ };
7750
+ var RESOURCE_SCOPE_ORDER = {
7751
+ Shared: 0,
7752
+ Project: 1
7753
+ };
7754
+ var getResourceType = (resource) => resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7755
+ var getResourceScopeOrder = (resource) => RESOURCE_SCOPE_ORDER[resource.scope ?? "Project"] ?? 99;
7756
+ var orderResourcesByType = (resources) => {
7757
+ return resources.map((resource, index) => ({ resource, index })).sort((left, right) => {
7758
+ const leftType = getResourceType(left.resource);
7759
+ const rightType = getResourceType(right.resource);
7760
+ const typeDiff = (RESOURCE_TYPE_ORDER[leftType] ?? 99) - (RESOURCE_TYPE_ORDER[rightType] ?? 99);
7761
+ if (typeDiff !== 0) {
7762
+ return typeDiff;
7763
+ }
7764
+ const scopeDiff = getResourceScopeOrder(left.resource) - getResourceScopeOrder(right.resource);
7765
+ return scopeDiff !== 0 ? scopeDiff : left.index - right.index;
7766
+ }).map(({ resource }) => resource);
7767
+ };
7735
7768
  var ResourceTypeIcon = ({ type }) => {
7736
7769
  if (type === "\u041B\u044E\u0434\u0438") {
7737
7770
  return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconPeople", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
@@ -7772,12 +7805,16 @@ var ResourceHeader = ({
7772
7805
  paddingBottom,
7773
7806
  menuCommands,
7774
7807
  onResourceChange,
7808
+ onResourceNameClick,
7775
7809
  onConflictBadgeClick
7776
7810
  }) => {
7777
7811
  const [menuOpen, setMenuOpen] = useState8(false);
7778
7812
  const [typeMenuOpen, setTypeMenuOpen] = useState8(false);
7779
7813
  const [scopeMenuOpen, setScopeMenuOpen] = useState8(false);
7780
- const [draftName, setDraftName] = useState8(resource.name);
7814
+ const [editingName, setEditingName] = useState8(false);
7815
+ const [nameValue, setNameValue] = useState8(resource.name);
7816
+ const nameInputRef = useRef8(null);
7817
+ const nameConfirmedRef = useRef8(false);
7781
7818
  const visibleCommands = useMemo9(
7782
7819
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
7783
7820
  [menuCommands, resource]
@@ -7786,31 +7823,20 @@ var ResourceHeader = ({
7786
7823
  const type = resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7787
7824
  const scope = resource.scope ?? "Project";
7788
7825
  const scopeLabel = RESOURCE_SCOPE_LABELS[scope] ?? scope;
7789
- useEffect8(() => {
7790
- setDraftName(resource.name);
7791
- }, [resource.name]);
7792
7826
  const applyResourcePatch = useCallback7((patch) => {
7793
7827
  onResourceChange?.({ ...resource, ...patch });
7794
7828
  }, [onResourceChange, resource]);
7795
- const handleNameCommit = useCallback7(() => {
7796
- const nextName = draftName.trim();
7797
- if (!nextName) {
7798
- setDraftName(resource.name);
7799
- return;
7800
- }
7801
- if (nextName !== resource.name) {
7802
- applyResourcePatch({ name: nextName });
7829
+ useEffect8(() => {
7830
+ if (!editingName) {
7831
+ setNameValue(resource.name);
7803
7832
  }
7804
- }, [applyResourcePatch, draftName, resource.name]);
7805
- const handleNameKeyDown = useCallback7((event) => {
7806
- if (event.key === "Enter") {
7807
- event.preventDefault();
7808
- event.currentTarget.blur();
7809
- } else if (event.key === "Escape") {
7810
- setDraftName(resource.name);
7811
- event.currentTarget.blur();
7833
+ }, [editingName, resource.name]);
7834
+ useEffect8(() => {
7835
+ if (editingName && nameInputRef.current) {
7836
+ nameInputRef.current.focus();
7837
+ nameInputRef.current.select();
7812
7838
  }
7813
- }, [resource.name]);
7839
+ }, [editingName]);
7814
7840
  const handleCommandClick = (command, event) => {
7815
7841
  event.stopPropagation();
7816
7842
  if (command.closeOnSelect !== false) {
@@ -7818,6 +7844,42 @@ var ResourceHeader = ({
7818
7844
  }
7819
7845
  command.onSelect(resource);
7820
7846
  };
7847
+ const handleNameDoubleClick = useCallback7((event) => {
7848
+ if (!onResourceChange) {
7849
+ return;
7850
+ }
7851
+ event.stopPropagation();
7852
+ nameConfirmedRef.current = false;
7853
+ setNameValue(resource.name);
7854
+ setEditingName(true);
7855
+ }, [onResourceChange, resource.name]);
7856
+ const handleNameSave = useCallback7(() => {
7857
+ if (nameConfirmedRef.current) {
7858
+ nameConfirmedRef.current = false;
7859
+ return;
7860
+ }
7861
+ const nextName = nameValue.trim();
7862
+ if (nextName && nextName !== resource.name) {
7863
+ applyResourcePatch({ name: nextName });
7864
+ }
7865
+ setEditingName(false);
7866
+ }, [applyResourcePatch, nameValue, resource.name]);
7867
+ const handleNameCancel = useCallback7(() => {
7868
+ setNameValue(resource.name);
7869
+ setEditingName(false);
7870
+ }, [resource.name]);
7871
+ const handleNameKeyDown = useCallback7((event) => {
7872
+ if (event.key === "Enter") {
7873
+ nameConfirmedRef.current = true;
7874
+ const nextName = nameValue.trim();
7875
+ if (nextName && nextName !== resource.name) {
7876
+ applyResourcePatch({ name: nextName });
7877
+ }
7878
+ setEditingName(false);
7879
+ } else if (event.key === "Escape") {
7880
+ handleNameCancel();
7881
+ }
7882
+ }, [applyResourcePatch, handleNameCancel, nameValue, resource.name]);
7821
7883
  return /* @__PURE__ */ jsxs12(
7822
7884
  "div",
7823
7885
  {
@@ -7864,18 +7926,31 @@ var ResourceHeader = ({
7864
7926
  option
7865
7927
  )) })
7866
7928
  ] }),
7867
- /* @__PURE__ */ jsx15(
7868
- "textarea",
7929
+ editingName ? /* @__PURE__ */ jsx15(
7930
+ Input,
7869
7931
  {
7870
- className: "gantt-resourceTimeline-resourceNameInput",
7871
- value: draftName,
7872
- disabled: !onResourceChange,
7873
- "aria-label": `\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7874
- rows: 2,
7875
- onChange: (event) => setDraftName(event.target.value),
7876
- onBlur: handleNameCommit,
7932
+ ref: nameInputRef,
7933
+ value: nameValue,
7934
+ onChange: (event) => setNameValue(event.target.value),
7935
+ onBlur: handleNameSave,
7877
7936
  onKeyDown: handleNameKeyDown,
7878
- onClick: (event) => event.stopPropagation()
7937
+ onClick: (event) => event.stopPropagation(),
7938
+ "aria-label": `\u041D\u043E\u0432\u043E\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7939
+ className: "gantt-tl-name-input gantt-resourceTimeline-resourceNameInput"
7940
+ }
7941
+ ) : /* @__PURE__ */ jsx15(
7942
+ "button",
7943
+ {
7944
+ type: "button",
7945
+ className: "gantt-resourceTimeline-resourceNameButton",
7946
+ "aria-label": `\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7947
+ title: resource.name,
7948
+ onClick: (event) => {
7949
+ event.stopPropagation();
7950
+ onResourceNameClick?.(resourceId);
7951
+ },
7952
+ onDoubleClick: handleNameDoubleClick,
7953
+ children: resource.name
7879
7954
  }
7880
7955
  )
7881
7956
  ] }),
@@ -7919,15 +7994,15 @@ var ResourceHeader = ({
7919
7994
  "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.`,
7920
7995
  children: [
7921
7996
  /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceWorkedDays", children: [
7922
- workedDays,
7923
- " \u0434\u043D."
7997
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: workedDays > 0 ? workedDays : "-" }),
7998
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceMetricLabel", children: workedDays > 0 ? "\u0434\u043D." : "" })
7924
7999
  ] }),
7925
8000
  /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceAssignmentCount", children: [
7926
- /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceAssignmentIcon", width: "14", height: "14", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
8001
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: assignmentCount > 0 ? assignmentCount : "" }),
8002
+ assignmentCount > 0 && /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceAssignmentIcon", width: "14", height: "14", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7927
8003
  /* @__PURE__ */ jsx15("rect", { width: "15", height: "5", x: "4", y: "5", rx: "2" }),
7928
8004
  /* @__PURE__ */ jsx15("rect", { width: "10", height: "5", x: "4", y: "14", rx: "2" })
7929
- ] }),
7930
- /* @__PURE__ */ jsx15("span", { children: assignmentCount })
8005
+ ] })
7931
8006
  ] })
7932
8007
  ]
7933
8008
  }
@@ -8052,6 +8127,7 @@ function ResourceTimelineChart({
8052
8127
  customDays,
8053
8128
  isWeekend: isWeekend3,
8054
8129
  businessDays = true,
8130
+ resourceGrouping = false,
8055
8131
  readonly,
8056
8132
  disableResourceReassignment,
8057
8133
  renderItem,
@@ -8069,9 +8145,12 @@ function ResourceTimelineChart({
8069
8145
  const gridRef = useRef8(null);
8070
8146
  const panStateRef = useRef8(null);
8071
8147
  const conflictNavigationIndexRef = useRef8(/* @__PURE__ */ new Map());
8148
+ const resourceNavigationIndexRef = useRef8(/* @__PURE__ */ new Map());
8072
8149
  const conflictHighlightTimeoutRef = useRef8(null);
8073
8150
  const [isCreatingResource, setIsCreatingResource] = useState8(false);
8151
+ const [creatingResourceGroupType, setCreatingResourceGroupType] = useState8(null);
8074
8152
  const [activeConflictItemId, setActiveConflictItemId] = useState8(null);
8153
+ const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = useState8(false);
8075
8154
  const validItems = useMemo9(() => collectValidItems(resources), [resources]);
8076
8155
  const dateRange = useMemo9(() => getMultiMonthDays(validItems), [validItems]);
8077
8156
  const monthStart = useMemo9(() => {
@@ -8084,6 +8163,10 @@ function ResourceTimelineChart({
8084
8163
  );
8085
8164
  const gridWidth = useMemo9(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
8086
8165
  const effectiveRowHeaderWidth = Math.max(rowHeaderWidth, DEFAULT_ROW_HEADER_WIDTH);
8166
+ const orderedResources = useMemo9(
8167
+ () => resourceGrouping === "type" ? orderResourcesByType(resources) : resources,
8168
+ [resourceGrouping, resources]
8169
+ );
8087
8170
  const workedDaysByResourceId = useMemo9(() => {
8088
8171
  const map = /* @__PURE__ */ new Map();
8089
8172
  for (const resource of resources) {
@@ -8104,14 +8187,87 @@ function ResourceTimelineChart({
8104
8187
  return map;
8105
8188
  }, [businessDays, resources, weekendPredicate]);
8106
8189
  const layout = useMemo9(
8107
- () => layoutResourceTimelineItems(resources, {
8190
+ () => layoutResourceTimelineItems(orderedResources, {
8108
8191
  monthStart,
8109
8192
  dayWidth,
8110
8193
  laneHeight,
8111
8194
  rowGap: DEFAULT_RESOURCE_ROW_GAP
8112
8195
  }),
8113
- [resources, monthStart, dayWidth, laneHeight]
8196
+ [orderedResources, monthStart, dayWidth, laneHeight]
8114
8197
  );
8198
+ const canAddResource = enableAddResource && Boolean(onAddResource);
8199
+ const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8200
+ const displayLayout = useMemo9(() => {
8201
+ if (resourceGrouping !== "type") {
8202
+ return {
8203
+ rows: layout.rows,
8204
+ items: layout.items,
8205
+ groupHeaders: [],
8206
+ groupAddRows: [],
8207
+ totalHeight: layout.totalHeight
8208
+ };
8209
+ }
8210
+ const countsByType = /* @__PURE__ */ new Map();
8211
+ for (const row of layout.rows) {
8212
+ const type = getResourceType(row.resource);
8213
+ countsByType.set(type, (countsByType.get(type) ?? 0) + 1);
8214
+ }
8215
+ let offset = 0;
8216
+ let previousType = null;
8217
+ const rowTopOffsets = /* @__PURE__ */ new Map();
8218
+ const groupHeaders = [];
8219
+ const groupAddRows = [];
8220
+ const rows = layout.rows.map((row) => {
8221
+ const type = getResourceType(row.resource);
8222
+ if (type !== previousType) {
8223
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8224
+ groupAddRows.push({
8225
+ key: previousType,
8226
+ top: row.resourceRowTop + offset,
8227
+ height: resourceAddRowHeight
8228
+ });
8229
+ offset += resourceAddRowHeight;
8230
+ }
8231
+ groupHeaders.push({
8232
+ key: type,
8233
+ label: type,
8234
+ count: countsByType.get(type) ?? 0,
8235
+ top: row.resourceRowTop + offset,
8236
+ height: DEFAULT_RESOURCE_GROUP_HEIGHT
8237
+ });
8238
+ offset += DEFAULT_RESOURCE_GROUP_HEIGHT;
8239
+ previousType = type;
8240
+ }
8241
+ rowTopOffsets.set(row.resourceId, offset);
8242
+ return {
8243
+ ...row,
8244
+ resourceRowTop: row.resourceRowTop + offset
8245
+ };
8246
+ });
8247
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8248
+ groupAddRows.push({
8249
+ key: previousType,
8250
+ top: layout.totalHeight + offset,
8251
+ height: resourceAddRowHeight
8252
+ });
8253
+ offset += resourceAddRowHeight;
8254
+ }
8255
+ const items = layout.items.map((item) => {
8256
+ const itemOffset = rowTopOffsets.get(item.resourceId) ?? 0;
8257
+ return {
8258
+ ...item,
8259
+ resourceRowTop: item.resourceRowTop + itemOffset,
8260
+ top: item.top + itemOffset
8261
+ };
8262
+ });
8263
+ return {
8264
+ rows,
8265
+ items,
8266
+ groupHeaders,
8267
+ groupAddRows,
8268
+ totalHeight: layout.totalHeight + offset
8269
+ };
8270
+ }, [creatingResourceGroupType, layout, resourceAddRowHeight, resourceGrouping]);
8115
8271
  const todayInRange = useMemo9(() => {
8116
8272
  const now = /* @__PURE__ */ new Date();
8117
8273
  const today = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
@@ -8119,16 +8275,16 @@ function ResourceTimelineChart({
8119
8275
  }, [dateRange]);
8120
8276
  const itemsByResourceId = useMemo9(() => {
8121
8277
  const map = /* @__PURE__ */ new Map();
8122
- for (const item of layout.items) {
8278
+ for (const item of displayLayout.items) {
8123
8279
  const next = map.get(item.resourceId) ?? [];
8124
8280
  next.push(item);
8125
8281
  map.set(item.resourceId, next);
8126
8282
  }
8127
8283
  return map;
8128
- }, [layout.items]);
8284
+ }, [displayLayout.items]);
8129
8285
  const conflictItemsByResourceId = useMemo9(() => {
8130
8286
  const map = /* @__PURE__ */ new Map();
8131
- for (const item of layout.items) {
8287
+ for (const item of displayLayout.items) {
8132
8288
  if (item.conflictRanges.length === 0) {
8133
8289
  continue;
8134
8290
  }
@@ -8142,21 +8298,28 @@ function ResourceTimelineChart({
8142
8298
  );
8143
8299
  }
8144
8300
  return map;
8145
- }, [layout.items]);
8146
- const canAddResource = enableAddResource && Boolean(onAddResource);
8147
- const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8148
- const displayTotalHeight = layout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8301
+ }, [displayLayout.items]);
8302
+ const displayTotalHeight = displayLayout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8303
+ const timelineHeaderHeight = headerHeight + 1;
8149
8304
  const handleConfirmNewResource = useCallback7((name) => {
8150
8305
  onAddResource?.({
8151
8306
  id: crypto.randomUUID(),
8152
8307
  name,
8153
- type: "\u0414\u0440\u0443\u0433\u043E\u0435",
8308
+ type: creatingResourceGroupType ?? "\u0414\u0440\u0443\u0433\u043E\u0435",
8154
8309
  scope: "Project",
8155
8310
  items: []
8156
8311
  });
8157
8312
  setIsCreatingResource(false);
8158
- }, [onAddResource]);
8159
- const handleCancelNewResource = useCallback7(() => setIsCreatingResource(false), []);
8313
+ setCreatingResourceGroupType(null);
8314
+ }, [creatingResourceGroupType, onAddResource]);
8315
+ const handleStartAddResourceToGroup = useCallback7((type) => {
8316
+ setIsCreatingResource(false);
8317
+ setCreatingResourceGroupType(type);
8318
+ }, []);
8319
+ const handleCancelNewResource = useCallback7(() => {
8320
+ setIsCreatingResource(false);
8321
+ setCreatingResourceGroupType(null);
8322
+ }, []);
8160
8323
  const handleConflictBadgeClick = useCallback7((resourceId) => {
8161
8324
  const conflictItems = conflictItemsByResourceId.get(resourceId) ?? [];
8162
8325
  if (conflictItems.length === 0) {
@@ -8182,10 +8345,31 @@ function ResourceTimelineChart({
8182
8345
  conflictHighlightTimeoutRef.current = null;
8183
8346
  }, 1600);
8184
8347
  }, [conflictItemsByResourceId, dayWidth, laneHeight]);
8348
+ const handleResourceNameClick = useCallback7((resourceId) => {
8349
+ const resourceItems = itemsByResourceId.get(resourceId) ?? [];
8350
+ if (resourceItems.length === 0) {
8351
+ return;
8352
+ }
8353
+ const orderedItems = [...resourceItems].sort(
8354
+ (left, right) => left.left - right.left || left.top - right.top || left.itemId.localeCompare(right.itemId)
8355
+ );
8356
+ const currentIndex = resourceNavigationIndexRef.current.get(resourceId) ?? 0;
8357
+ const targetItem = orderedItems[currentIndex % orderedItems.length];
8358
+ resourceNavigationIndexRef.current.set(resourceId, (currentIndex + 1) % orderedItems.length);
8359
+ const container = scrollContainerRef.current;
8360
+ if (!container || !targetItem) {
8361
+ return;
8362
+ }
8363
+ container.scrollTo({
8364
+ left: Math.max(0, Math.round(targetItem.left - dayWidth * 2)),
8365
+ top: Math.max(0, Math.round(targetItem.top - laneHeight)),
8366
+ behavior: "smooth"
8367
+ });
8368
+ }, [dayWidth, itemsByResourceId, laneHeight]);
8185
8369
  const { preview, startDrag } = useResourceItemDrag({
8186
8370
  dayWidth,
8187
8371
  monthStart,
8188
- rows: layout.rows,
8372
+ rows: displayLayout.rows,
8189
8373
  gridElementRef: gridRef,
8190
8374
  readonly,
8191
8375
  disableResourceReassignment,
@@ -8193,6 +8377,18 @@ function ResourceTimelineChart({
8193
8377
  weekendPredicate,
8194
8378
  onResourceItemMove
8195
8379
  });
8380
+ useEffect8(() => {
8381
+ const container = scrollContainerRef.current;
8382
+ if (!container) return;
8383
+ const updateShadow = () => {
8384
+ setResourceColumnHasRightShadow(container.scrollLeft > 0);
8385
+ };
8386
+ updateShadow();
8387
+ container.addEventListener("scroll", updateShadow, { passive: true });
8388
+ return () => {
8389
+ container.removeEventListener("scroll", updateShadow);
8390
+ };
8391
+ }, []);
8196
8392
  const handlePanStart = useCallback7((event) => {
8197
8393
  if (event.button !== 0) {
8198
8394
  return;
@@ -8250,6 +8446,16 @@ function ResourceTimelineChart({
8250
8446
  window.removeEventListener("mouseup", handlePanEnd);
8251
8447
  };
8252
8448
  }, [allowVerticalPan]);
8449
+ useEffect8(() => {
8450
+ const nextNavigation = /* @__PURE__ */ new Map();
8451
+ for (const [resourceId, resourceItems] of itemsByResourceId.entries()) {
8452
+ if (resourceItems.length === 0) {
8453
+ continue;
8454
+ }
8455
+ nextNavigation.set(resourceId, (resourceNavigationIndexRef.current.get(resourceId) ?? 0) % resourceItems.length);
8456
+ }
8457
+ resourceNavigationIndexRef.current = nextNavigation;
8458
+ }, [itemsByResourceId]);
8253
8459
  useEffect8(() => {
8254
8460
  return () => {
8255
8461
  if (conflictHighlightTimeoutRef.current) {
@@ -8273,14 +8479,14 @@ function ResourceTimelineChart({
8273
8479
  /* @__PURE__ */ jsxs12(
8274
8480
  "div",
8275
8481
  {
8276
- className: "gantt-resourceTimeline-resourceColumn",
8482
+ className: `gantt-resourceTimeline-resourceColumn${resourceColumnHasRightShadow ? " gantt-resourceTimeline-resourceColumnShadowed" : ""}`,
8277
8483
  style: { width: `${effectiveRowHeaderWidth}px`, minWidth: `${effectiveRowHeaderWidth}px` },
8278
8484
  children: [
8279
8485
  /* @__PURE__ */ jsxs12(
8280
8486
  "div",
8281
8487
  {
8282
8488
  className: "gantt-resourceTimeline-corner",
8283
- style: { height: `${headerHeight + 0.5}px` },
8489
+ style: { height: `${timelineHeaderHeight}px` },
8284
8490
  children: [
8285
8491
  /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderNumber", children: "#" }),
8286
8492
  /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderName", children: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435" }),
@@ -8290,7 +8496,62 @@ function ResourceTimelineChart({
8290
8496
  ]
8291
8497
  }
8292
8498
  ),
8293
- layout.rows.map((row, rowIndex) => /* @__PURE__ */ jsx15(
8499
+ displayLayout.groupHeaders.length > 0 ? displayLayout.groupHeaders.map((group) => /* @__PURE__ */ jsxs12(React12.Fragment, { children: [
8500
+ /* @__PURE__ */ jsxs12(
8501
+ "div",
8502
+ {
8503
+ className: "gantt-resourceTimeline-resourceGroupHeader",
8504
+ style: { height: `${group.height}px` },
8505
+ children: [
8506
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceGroupTitle", children: group.label }),
8507
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceGroupCount", children: group.count }),
8508
+ canAddResource && /* @__PURE__ */ jsx15(
8509
+ "button",
8510
+ {
8511
+ type: "button",
8512
+ className: "gantt-resourceTimeline-resourceGroupAddButton",
8513
+ "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}`,
8514
+ title: `\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441 \u0432 \u0433\u0440\u0443\u043F\u043F\u0443 ${group.label}`,
8515
+ onClick: (event) => {
8516
+ event.stopPropagation();
8517
+ handleStartAddResourceToGroup(group.key);
8518
+ },
8519
+ children: "+"
8520
+ }
8521
+ )
8522
+ ]
8523
+ }
8524
+ ),
8525
+ displayLayout.rows.filter((row) => getResourceType(row.resource) === group.key).map((row) => {
8526
+ const rowIndex = displayLayout.rows.findIndex((candidate) => candidate.resourceId === row.resourceId);
8527
+ return /* @__PURE__ */ jsx15(
8528
+ ResourceHeader,
8529
+ {
8530
+ resource: row.resource,
8531
+ resourceId: row.resourceId,
8532
+ rowIndex,
8533
+ conflictCount: row.conflictCount,
8534
+ workedDays: workedDaysByResourceId.get(row.resourceId) ?? 0,
8535
+ assignmentCount: row.resource.items.length,
8536
+ height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8537
+ paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8538
+ menuCommands: resourceMenuCommands,
8539
+ onResourceChange,
8540
+ onResourceNameClick: handleResourceNameClick,
8541
+ onConflictBadgeClick: handleConflictBadgeClick
8542
+ },
8543
+ row.resourceId
8544
+ );
8545
+ }),
8546
+ displayLayout.groupAddRows.some((row) => row.key === group.key) && /* @__PURE__ */ jsx15(
8547
+ NewResourceRow,
8548
+ {
8549
+ height: resourceAddRowHeight,
8550
+ onConfirm: handleConfirmNewResource,
8551
+ onCancel: handleCancelNewResource
8552
+ }
8553
+ )
8554
+ ] }, group.key)) : displayLayout.rows.map((row, rowIndex) => /* @__PURE__ */ jsx15(
8294
8555
  ResourceHeader,
8295
8556
  {
8296
8557
  resource: row.resource,
@@ -8303,6 +8564,7 @@ function ResourceTimelineChart({
8303
8564
  paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8304
8565
  menuCommands: resourceMenuCommands,
8305
8566
  onResourceChange,
8567
+ onResourceNameClick: handleResourceNameClick,
8306
8568
  onConflictBadgeClick: handleConflictBadgeClick
8307
8569
  },
8308
8570
  row.resourceId
@@ -8320,7 +8582,10 @@ function ResourceTimelineChart({
8320
8582
  className: "gantt-resourceTimeline-addResourceButton",
8321
8583
  type: "button",
8322
8584
  style: { height: `${resourceAddRowHeight}px` },
8323
- onClick: () => setIsCreatingResource(true),
8585
+ onClick: () => {
8586
+ setCreatingResourceGroupType(null);
8587
+ setIsCreatingResource(true);
8588
+ },
8324
8589
  children: "+ \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441"
8325
8590
  }
8326
8591
  ))
@@ -8333,16 +8598,23 @@ function ResourceTimelineChart({
8333
8598
  className: "gantt-resourceTimeline-chartSurface",
8334
8599
  style: { minWidth: `${gridWidth}px` },
8335
8600
  children: [
8336
- /* @__PURE__ */ jsx15("div", { className: "gantt-resourceTimeline-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx15(
8337
- TimeScaleHeader_default,
8601
+ /* @__PURE__ */ jsx15(
8602
+ "div",
8338
8603
  {
8339
- days: dateRange,
8340
- dayWidth,
8341
- headerHeight,
8342
- viewMode,
8343
- isCustomWeekend: weekendPredicate
8604
+ className: "gantt-resourceTimeline-stickyHeader",
8605
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
8606
+ children: /* @__PURE__ */ jsx15(
8607
+ TimeScaleHeader_default,
8608
+ {
8609
+ days: dateRange,
8610
+ dayWidth,
8611
+ headerHeight,
8612
+ viewMode,
8613
+ isCustomWeekend: weekendPredicate
8614
+ }
8615
+ )
8344
8616
  }
8345
- ) }),
8617
+ ),
8346
8618
  /* @__PURE__ */ jsxs12(
8347
8619
  "div",
8348
8620
  {
@@ -8361,7 +8633,19 @@ function ResourceTimelineChart({
8361
8633
  }
8362
8634
  ),
8363
8635
  todayInRange && /* @__PURE__ */ jsx15(TodayIndicator_default, { monthStart, dayWidth }),
8364
- layout.rows.map((row) => /* @__PURE__ */ jsx15(
8636
+ displayLayout.groupHeaders.map((group) => /* @__PURE__ */ jsx15(
8637
+ "div",
8638
+ {
8639
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-groupRow",
8640
+ "data-resource-group": group.key,
8641
+ style: {
8642
+ top: `${group.top}px`,
8643
+ height: `${group.height}px`
8644
+ }
8645
+ },
8646
+ `group-row-${group.key}`
8647
+ )),
8648
+ displayLayout.rows.map((row) => /* @__PURE__ */ jsx15(
8365
8649
  "div",
8366
8650
  {
8367
8651
  className: "gantt-resourceTimeline-row",
@@ -8379,11 +8663,23 @@ function ResourceTimelineChart({
8379
8663
  className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8380
8664
  "data-resource-add-row": "true",
8381
8665
  style: {
8382
- top: `${layout.totalHeight}px`,
8666
+ top: `${displayLayout.totalHeight}px`,
8383
8667
  height: `${resourceAddRowHeight}px`
8384
8668
  }
8385
8669
  }
8386
8670
  ),
8671
+ displayLayout.groupAddRows.map((row) => /* @__PURE__ */ jsx15(
8672
+ "div",
8673
+ {
8674
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8675
+ "data-resource-group-add-row": row.key,
8676
+ style: {
8677
+ top: `${row.top}px`,
8678
+ height: `${row.height}px`
8679
+ }
8680
+ },
8681
+ `group-add-row-${row.key}`
8682
+ )),
8387
8683
  Array.from(itemsByResourceId.values()).flatMap(
8388
8684
  (resourceItems) => resourceItems.map((layoutItem) => {
8389
8685
  const customClassName = getItemClassName?.(layoutItem.item);
@@ -8932,6 +9228,7 @@ function TaskGanttChartInner(props, ref) {
8932
9228
  disableTaskDrag = false,
8933
9229
  showChart = true,
8934
9230
  additionalColumns,
9231
+ hiddenTaskListColumns,
8935
9232
  taskListMenuCommands
8936
9233
  } = props;
8937
9234
  const containerRef = useRef9(null);
@@ -8990,6 +9287,7 @@ function TaskGanttChartInner(props, ref) {
8990
9287
  () => visibleTasks.length * rowHeight,
8991
9288
  [visibleTasks.length, rowHeight]
8992
9289
  );
9290
+ const timelineHeaderHeight = headerHeight + 1;
8993
9291
  const monthStart = useMemo10(() => {
8994
9292
  if (dateRange.length === 0) {
8995
9293
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
@@ -9463,6 +9761,7 @@ function TaskGanttChartInner(props, ref) {
9463
9761
  filteredTaskIds: matchedTaskIds,
9464
9762
  isFilterActive: !!taskFilter,
9465
9763
  additionalColumns,
9764
+ hiddenTaskListColumns,
9466
9765
  taskListMenuCommands
9467
9766
  }
9468
9767
  ),
@@ -9472,16 +9771,23 @@ function TaskGanttChartInner(props, ref) {
9472
9771
  className: showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
9473
9772
  style: { minWidth: `${gridWidth}px`, flex: 1, display: showChart ? void 0 : "none" },
9474
9773
  children: [
9475
- /* @__PURE__ */ jsx16("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx16(
9476
- TimeScaleHeader_default,
9774
+ /* @__PURE__ */ jsx16(
9775
+ "div",
9477
9776
  {
9478
- days: dateRange,
9479
- dayWidth,
9480
- headerHeight,
9481
- viewMode,
9482
- isCustomWeekend
9777
+ className: "gantt-stickyHeader",
9778
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
9779
+ children: /* @__PURE__ */ jsx16(
9780
+ TimeScaleHeader_default,
9781
+ {
9782
+ days: dateRange,
9783
+ dayWidth,
9784
+ headerHeight,
9785
+ viewMode,
9786
+ isCustomWeekend
9787
+ }
9788
+ )
9483
9789
  }
9484
- ) }),
9790
+ ),
9485
9791
  /* @__PURE__ */ jsxs13(
9486
9792
  "div",
9487
9793
  {