gantt-lib 0.81.0 → 0.83.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
@@ -7137,6 +7137,7 @@ var TaskList = ({
7137
7137
  [resolvedColumns]
7138
7138
  );
7139
7139
  const effectiveTaskListWidth = Math.max(taskListWidth, MIN_TASK_LIST_WIDTH, resolvedColumnWidthTotal);
7140
+ const tableHeaderHeight = headerHeight + 1;
7140
7141
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
7141
7142
  "div",
7142
7143
  {
@@ -7144,7 +7145,7 @@ var TaskList = ({
7144
7145
  className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}${hasRightShadow ? " gantt-tl-overlay-shadowed" : ""}`,
7145
7146
  style: { "--tasklist-width": `${effectiveTaskListWidth}px` },
7146
7147
  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) => {
7148
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "gantt-tl-header", style: { height: `${tableHeaderHeight}px` }, children: resolvedColumns.map((col) => {
7148
7149
  if (col.id === "dependencies") {
7149
7150
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
7150
7151
  "div",
@@ -7328,7 +7329,7 @@ var TaskList = ({
7328
7329
  };
7329
7330
 
7330
7331
  // src/components/ResourceTimelineChart/ResourceTimelineChart.tsx
7331
- var import_react14 = require("react");
7332
+ var import_react14 = __toESM(require("react"));
7332
7333
 
7333
7334
  // src/utils/resourceTimelineLayout.ts
7334
7335
  var isInvalidDate = (date) => Number.isNaN(date.getTime());
@@ -7746,8 +7747,9 @@ var import_jsx_runtime15 = require("react/jsx-runtime");
7746
7747
  var DEFAULT_DAY_WIDTH = 40;
7747
7748
  var DEFAULT_HEADER_HEIGHT = 40;
7748
7749
  var DEFAULT_LANE_HEIGHT = 40;
7749
- var DEFAULT_ROW_HEADER_WIDTH = 240;
7750
+ var DEFAULT_ROW_HEADER_WIDTH = 420;
7750
7751
  var DEFAULT_RESOURCE_ROW_GAP = 8;
7752
+ var DEFAULT_RESOURCE_GROUP_HEIGHT = 28;
7751
7753
  var ITEM_OUTER_VERTICAL_INSET = 2;
7752
7754
  var ITEM_INNER_VERTICAL_INSET = 1;
7753
7755
  var ITEM_START_HORIZONTAL_INSET = 2;
@@ -7834,21 +7836,114 @@ var clampOverlaySegments = (segments2, maxWidth) => segments2.flatMap((segment)
7834
7836
  return width > 0 ? [{ left, width }] : [];
7835
7837
  });
7836
7838
  var getDurationValue = (startDate, endDate, businessDays, weekendPredicate) => businessDays ? getBusinessDaysCount(startDate, endDate, weekendPredicate) : Math.max(1, Math.round((endDate.getTime() - startDate.getTime()) / (24 * 60 * 60 * 1e3)) + 1);
7839
+ var RESOURCE_TYPE_OPTIONS = ["\u041B\u044E\u0434\u0438", "\u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435", "\u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B", "\u0414\u0440\u0443\u0433\u043E\u0435"];
7840
+ var RESOURCE_SCOPE_OPTIONS = ["Shared", "Project"];
7841
+ var RESOURCE_SCOPE_LABELS = {
7842
+ Shared: "\u041E\u0431\u0449\u0438\u0439",
7843
+ Project: "\u041F\u0440\u043E\u0435\u043A\u0442"
7844
+ };
7845
+ var RESOURCE_TYPE_CLASS_NAMES = {
7846
+ \u041B\u044E\u0434\u0438: "People",
7847
+ \u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435: "Equipment",
7848
+ \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: "Materials",
7849
+ \u0414\u0440\u0443\u0433\u043E\u0435: "Other"
7850
+ };
7851
+ var RESOURCE_TYPE_ORDER = {
7852
+ \u041B\u044E\u0434\u0438: 0,
7853
+ \u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435: 1,
7854
+ \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: 2,
7855
+ \u0414\u0440\u0443\u0433\u043E\u0435: 3
7856
+ };
7857
+ var RESOURCE_SCOPE_ORDER = {
7858
+ Shared: 0,
7859
+ Project: 1
7860
+ };
7861
+ var getResourceType = (resource) => resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7862
+ var getResourceScopeOrder = (resource) => RESOURCE_SCOPE_ORDER[resource.scope ?? "Project"] ?? 99;
7863
+ var orderResourcesByType = (resources) => {
7864
+ return resources.map((resource, index) => ({ resource, index })).sort((left, right) => {
7865
+ const leftType = getResourceType(left.resource);
7866
+ const rightType = getResourceType(right.resource);
7867
+ const typeDiff = (RESOURCE_TYPE_ORDER[leftType] ?? 99) - (RESOURCE_TYPE_ORDER[rightType] ?? 99);
7868
+ if (typeDiff !== 0) {
7869
+ return typeDiff;
7870
+ }
7871
+ const scopeDiff = getResourceScopeOrder(left.resource) - getResourceScopeOrder(right.resource);
7872
+ return scopeDiff !== 0 ? scopeDiff : left.index - right.index;
7873
+ }).map(({ resource }) => resource);
7874
+ };
7875
+ var ResourceTypeIcon = ({ type }) => {
7876
+ if (type === "\u041B\u044E\u0434\u0438") {
7877
+ 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: [
7878
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
7879
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("circle", { cx: "9", cy: "7", r: "4" }),
7880
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }),
7881
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
7882
+ ] });
7883
+ }
7884
+ if (type === "\u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435") {
7885
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconEquipment", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7886
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9" }),
7887
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "m18 15 4-4" }),
7888
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172v-.344a2 2 0 0 0-.586-1.414l-1.657-1.657A6 6 0 0 0 12.516 3H9l1.243 1.243A6 6 0 0 1 12 8.485V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5" })
7889
+ ] });
7890
+ }
7891
+ if (type === "\u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B") {
7892
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconMaterials", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7893
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M10 22v-8" }),
7894
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M2.336 8.89 10 14l11.715-7.029" }),
7895
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M22 14a2 2 0 0 1-.971 1.715l-10 6a2 2 0 0 1-2.138-.05l-6-4A2 2 0 0 1 2 16v-6a2 2 0 0 1 .971-1.715l10-6a2 2 0 0 1 2.138.05l6 4A2 2 0 0 1 22 8z" })
7896
+ ] });
7897
+ }
7898
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconOther", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7899
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("circle", { cx: "12", cy: "12", r: "9" }),
7900
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M12 8v4" }),
7901
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("path", { d: "M12 16h.01" })
7902
+ ] });
7903
+ };
7837
7904
  var ResourceHeader = ({
7838
7905
  resource,
7839
7906
  resourceId,
7907
+ rowIndex,
7840
7908
  conflictCount,
7909
+ workedDays,
7910
+ assignmentCount,
7841
7911
  height,
7842
7912
  paddingBottom,
7843
7913
  menuCommands,
7914
+ onResourceChange,
7915
+ onResourceNameClick,
7844
7916
  onConflictBadgeClick
7845
7917
  }) => {
7846
7918
  const [menuOpen, setMenuOpen] = (0, import_react14.useState)(false);
7919
+ const [typeMenuOpen, setTypeMenuOpen] = (0, import_react14.useState)(false);
7920
+ const [scopeMenuOpen, setScopeMenuOpen] = (0, import_react14.useState)(false);
7921
+ const [editingName, setEditingName] = (0, import_react14.useState)(false);
7922
+ const [nameValue, setNameValue] = (0, import_react14.useState)(resource.name);
7923
+ const nameInputRef = (0, import_react14.useRef)(null);
7924
+ const nameConfirmedRef = (0, import_react14.useRef)(false);
7847
7925
  const visibleCommands = (0, import_react14.useMemo)(
7848
7926
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
7849
7927
  [menuCommands, resource]
7850
7928
  );
7851
7929
  const hasMenu = visibleCommands.length > 0;
7930
+ const type = resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7931
+ const scope = resource.scope ?? "Project";
7932
+ const scopeLabel = RESOURCE_SCOPE_LABELS[scope] ?? scope;
7933
+ const applyResourcePatch = (0, import_react14.useCallback)((patch) => {
7934
+ onResourceChange?.({ ...resource, ...patch });
7935
+ }, [onResourceChange, resource]);
7936
+ (0, import_react14.useEffect)(() => {
7937
+ if (!editingName) {
7938
+ setNameValue(resource.name);
7939
+ }
7940
+ }, [editingName, resource.name]);
7941
+ (0, import_react14.useEffect)(() => {
7942
+ if (editingName && nameInputRef.current) {
7943
+ nameInputRef.current.focus();
7944
+ nameInputRef.current.select();
7945
+ }
7946
+ }, [editingName]);
7852
7947
  const handleCommandClick = (command, event) => {
7853
7948
  event.stopPropagation();
7854
7949
  if (command.closeOnSelect !== false) {
@@ -7856,6 +7951,42 @@ var ResourceHeader = ({
7856
7951
  }
7857
7952
  command.onSelect(resource);
7858
7953
  };
7954
+ const handleNameDoubleClick = (0, import_react14.useCallback)((event) => {
7955
+ if (!onResourceChange) {
7956
+ return;
7957
+ }
7958
+ event.stopPropagation();
7959
+ nameConfirmedRef.current = false;
7960
+ setNameValue(resource.name);
7961
+ setEditingName(true);
7962
+ }, [onResourceChange, resource.name]);
7963
+ const handleNameSave = (0, import_react14.useCallback)(() => {
7964
+ if (nameConfirmedRef.current) {
7965
+ nameConfirmedRef.current = false;
7966
+ return;
7967
+ }
7968
+ const nextName = nameValue.trim();
7969
+ if (nextName && nextName !== resource.name) {
7970
+ applyResourcePatch({ name: nextName });
7971
+ }
7972
+ setEditingName(false);
7973
+ }, [applyResourcePatch, nameValue, resource.name]);
7974
+ const handleNameCancel = (0, import_react14.useCallback)(() => {
7975
+ setNameValue(resource.name);
7976
+ setEditingName(false);
7977
+ }, [resource.name]);
7978
+ const handleNameKeyDown = (0, import_react14.useCallback)((event) => {
7979
+ if (event.key === "Enter") {
7980
+ nameConfirmedRef.current = true;
7981
+ const nextName = nameValue.trim();
7982
+ if (nextName && nextName !== resource.name) {
7983
+ applyResourcePatch({ name: nextName });
7984
+ }
7985
+ setEditingName(false);
7986
+ } else if (event.key === "Escape") {
7987
+ handleNameCancel();
7988
+ }
7989
+ }, [applyResourcePatch, handleNameCancel, nameValue, resource.name]);
7859
7990
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7860
7991
  "div",
7861
7992
  {
@@ -7866,49 +7997,167 @@ var ResourceHeader = ({
7866
7997
  paddingBottom: `${paddingBottom}px`
7867
7998
  },
7868
7999
  children: [
7869
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceName", children: resource.name }),
7870
- conflictCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7871
- "button",
7872
- {
7873
- type: "button",
7874
- className: "gantt-resourceTimeline-conflictBadge",
7875
- "aria-label": formatOverlapCount(conflictCount),
7876
- title: formatOverlapCount(conflictCount),
7877
- onClick: (event) => {
7878
- event.stopPropagation();
7879
- onConflictBadgeClick?.(resourceId);
7880
- },
7881
- children: conflictCount
7882
- }
7883
- ),
7884
- hasMenu && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
8000
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceNumber", children: rowIndex + 1 }),
8001
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gantt-resourceTimeline-resourceName", children: [
8002
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
8003
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8004
+ "button",
8005
+ {
8006
+ type: "button",
8007
+ className: `gantt-resourceTimeline-resourceTypeIconButton gantt-resourceTimeline-resourceTypeIconButton${RESOURCE_TYPE_CLASS_NAMES[type] ?? "Other"}`,
8008
+ disabled: !onResourceChange,
8009
+ "aria-label": `\u0422\u0438\u043F \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}: ${type}`,
8010
+ title: type,
8011
+ onClick: (event) => {
8012
+ event.stopPropagation();
8013
+ setTypeMenuOpen((open) => !open);
8014
+ },
8015
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ResourceTypeIcon, { type })
8016
+ }
8017
+ ) }),
8018
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverContent, { className: "gantt-resourceTimeline-resourceOptionMenu", portal: true, align: "start", children: RESOURCE_TYPE_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8019
+ "button",
8020
+ {
8021
+ type: "button",
8022
+ className: `gantt-resourceTimeline-resourceOption${type === option ? " gantt-resourceTimeline-resourceOptionActive" : ""}`,
8023
+ onClick: (event) => {
8024
+ event.stopPropagation();
8025
+ setTypeMenuOpen(false);
8026
+ applyResourcePatch({ type: option });
8027
+ },
8028
+ children: [
8029
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceOptionIcon", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ResourceTypeIcon, { type: option }) }),
8030
+ option
8031
+ ]
8032
+ },
8033
+ option
8034
+ )) })
8035
+ ] }),
8036
+ editingName ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8037
+ Input,
8038
+ {
8039
+ ref: nameInputRef,
8040
+ value: nameValue,
8041
+ onChange: (event) => setNameValue(event.target.value),
8042
+ onBlur: handleNameSave,
8043
+ onKeyDown: handleNameKeyDown,
8044
+ onClick: (event) => event.stopPropagation(),
8045
+ "aria-label": `\u041D\u043E\u0432\u043E\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
8046
+ className: "gantt-tl-name-input gantt-resourceTimeline-resourceNameInput"
8047
+ }
8048
+ ) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8049
+ "button",
8050
+ {
8051
+ type: "button",
8052
+ className: "gantt-resourceTimeline-resourceNameButton",
8053
+ "aria-label": `\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
8054
+ title: resource.name,
8055
+ onClick: (event) => {
8056
+ event.stopPropagation();
8057
+ onResourceNameClick?.(resourceId);
8058
+ },
8059
+ onDoubleClick: handleNameDoubleClick,
8060
+ children: resource.name
8061
+ }
8062
+ )
8063
+ ] }),
8064
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Popover, { open: scopeMenuOpen, onOpenChange: setScopeMenuOpen, children: [
7885
8065
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7886
8066
  "button",
7887
8067
  {
7888
- className: "gantt-resourceTimeline-resourceMenuButton",
7889
8068
  type: "button",
7890
- "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
8069
+ className: `gantt-resourceTimeline-resourceScopeChip gantt-resourceTimeline-resourceScope${scope === "Shared" ? "Shared" : "Project"}`,
8070
+ disabled: !onResourceChange,
8071
+ "aria-label": `\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7891
8072
  onClick: (event) => {
7892
8073
  event.stopPropagation();
7893
- setMenuOpen((open) => !open);
8074
+ setScopeMenuOpen((open) => !open);
7894
8075
  },
7895
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-hidden": "true", children: "\u22EE" })
8076
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { children: scopeLabel })
7896
8077
  }
7897
8078
  ) }),
7898
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverContent, { className: "gantt-resourceTimeline-resourceMenu", portal: true, align: "end", children: visibleCommands.map((command) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8079
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverContent, { className: "gantt-resourceTimeline-resourceOptionMenu", portal: true, align: "start", children: RESOURCE_SCOPE_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7899
8080
  "button",
7900
8081
  {
7901
8082
  type: "button",
7902
- className: `gantt-resourceTimeline-resourceMenuItem${command.danger ? " gantt-resourceTimeline-resourceMenuItemDanger" : ""}`,
7903
- disabled: command.isDisabled?.(resource) ?? false,
7904
- onClick: (event) => handleCommandClick(command, event),
8083
+ className: `gantt-resourceTimeline-resourceOption${scope === option ? " gantt-resourceTimeline-resourceOptionActive" : ""}`,
8084
+ onClick: (event) => {
8085
+ event.stopPropagation();
8086
+ setScopeMenuOpen(false);
8087
+ applyResourcePatch({ scope: option });
8088
+ },
7905
8089
  children: [
7906
- command.icon,
7907
- command.label
8090
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: `gantt-resourceTimeline-resourceOptionScopeDot gantt-resourceTimeline-resourceScope${option}` }),
8091
+ RESOURCE_SCOPE_LABELS[option] ?? option
7908
8092
  ]
7909
8093
  },
7910
- command.id
8094
+ option
7911
8095
  )) })
8096
+ ] }),
8097
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8098
+ "span",
8099
+ {
8100
+ className: "gantt-resourceTimeline-resourceAssignments",
8101
+ "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.`,
8102
+ children: [
8103
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gantt-resourceTimeline-resourceWorkedDays", children: [
8104
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: workedDays > 0 ? workedDays : "-" }),
8105
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceMetricLabel", children: workedDays > 0 ? "\u0434\u043D." : "" })
8106
+ ] }),
8107
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gantt-resourceTimeline-resourceAssignmentCount", children: [
8108
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: assignmentCount > 0 ? assignmentCount : "" }),
8109
+ 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: [
8110
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("rect", { width: "15", height: "5", x: "4", y: "5", rx: "2" }),
8111
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("rect", { width: "10", height: "5", x: "4", y: "14", rx: "2" })
8112
+ ] })
8113
+ ] })
8114
+ ]
8115
+ }
8116
+ ),
8117
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("span", { className: "gantt-resourceTimeline-resourceActions", children: [
8118
+ conflictCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8119
+ "button",
8120
+ {
8121
+ type: "button",
8122
+ className: "gantt-resourceTimeline-conflictBadge",
8123
+ "aria-label": formatOverlapCount(conflictCount),
8124
+ title: formatOverlapCount(conflictCount),
8125
+ onClick: (event) => {
8126
+ event.stopPropagation();
8127
+ onConflictBadgeClick?.(resourceId);
8128
+ },
8129
+ children: conflictCount
8130
+ }
8131
+ ),
8132
+ hasMenu && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
8133
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8134
+ "button",
8135
+ {
8136
+ className: "gantt-resourceTimeline-resourceMenuButton",
8137
+ type: "button",
8138
+ "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
8139
+ onClick: (event) => {
8140
+ event.stopPropagation();
8141
+ setMenuOpen((open) => !open);
8142
+ },
8143
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { "aria-hidden": "true", children: "\u22EE" })
8144
+ }
8145
+ ) }),
8146
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PopoverContent, { className: "gantt-resourceTimeline-resourceMenu", portal: true, align: "end", children: visibleCommands.map((command) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8147
+ "button",
8148
+ {
8149
+ type: "button",
8150
+ className: `gantt-resourceTimeline-resourceMenuItem${command.danger ? " gantt-resourceTimeline-resourceMenuItemDanger" : ""}`,
8151
+ disabled: command.isDisabled?.(resource) ?? false,
8152
+ onClick: (event) => handleCommandClick(command, event),
8153
+ children: [
8154
+ command.icon,
8155
+ command.label
8156
+ ]
8157
+ },
8158
+ command.id
8159
+ )) })
8160
+ ] })
7912
8161
  ] })
7913
8162
  ]
7914
8163
  }
@@ -7947,7 +8196,7 @@ var NewResourceRow = ({ height, onConfirm, onCancel }) => {
7947
8196
  handleConfirm();
7948
8197
  }
7949
8198
  };
7950
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8199
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
7951
8200
  "div",
7952
8201
  {
7953
8202
  className: "gantt-resourceTimeline-resourceHeader gantt-resourceTimeline-resourceHeaderNew",
@@ -7955,18 +8204,21 @@ var NewResourceRow = ({ height, onConfirm, onCancel }) => {
7955
8204
  height: `${height}px`,
7956
8205
  paddingBottom: `${DEFAULT_RESOURCE_ROW_GAP}px`
7957
8206
  },
7958
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
7959
- Input,
7960
- {
7961
- ref: inputRef,
7962
- value: nameValue,
7963
- onChange: (event) => setNameValue(event.target.value),
7964
- onKeyDown: handleKeyDown,
7965
- onBlur: handleBlur,
7966
- placeholder: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
7967
- className: "gantt-resourceTimeline-resourceInput"
7968
- }
7969
- )
8207
+ children: [
8208
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceNumber", children: "+" }),
8209
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8210
+ Input,
8211
+ {
8212
+ ref: inputRef,
8213
+ value: nameValue,
8214
+ onChange: (event) => setNameValue(event.target.value),
8215
+ onKeyDown: handleKeyDown,
8216
+ onBlur: handleBlur,
8217
+ placeholder: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
8218
+ className: "gantt-resourceTimeline-resourceInput"
8219
+ }
8220
+ )
8221
+ ]
7970
8222
  }
7971
8223
  );
7972
8224
  };
@@ -7982,6 +8234,7 @@ function ResourceTimelineChart({
7982
8234
  customDays,
7983
8235
  isWeekend: isWeekend3,
7984
8236
  businessDays = true,
8237
+ resourceGrouping = false,
7985
8238
  readonly,
7986
8239
  disableResourceReassignment,
7987
8240
  renderItem,
@@ -7990,6 +8243,7 @@ function ResourceTimelineChart({
7990
8243
  onResourceItemMenuClick,
7991
8244
  activeResourceItemId,
7992
8245
  onResourceItemMove,
8246
+ onResourceChange,
7993
8247
  onAddResource,
7994
8248
  enableAddResource = true,
7995
8249
  resourceMenuCommands = []
@@ -7998,9 +8252,12 @@ function ResourceTimelineChart({
7998
8252
  const gridRef = (0, import_react14.useRef)(null);
7999
8253
  const panStateRef = (0, import_react14.useRef)(null);
8000
8254
  const conflictNavigationIndexRef = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
8255
+ const resourceNavigationIndexRef = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
8001
8256
  const conflictHighlightTimeoutRef = (0, import_react14.useRef)(null);
8002
8257
  const [isCreatingResource, setIsCreatingResource] = (0, import_react14.useState)(false);
8258
+ const [creatingResourceGroupType, setCreatingResourceGroupType] = (0, import_react14.useState)(null);
8003
8259
  const [activeConflictItemId, setActiveConflictItemId] = (0, import_react14.useState)(null);
8260
+ const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = (0, import_react14.useState)(false);
8004
8261
  const validItems = (0, import_react14.useMemo)(() => collectValidItems(resources), [resources]);
8005
8262
  const dateRange = (0, import_react14.useMemo)(() => getMultiMonthDays(validItems), [validItems]);
8006
8263
  const monthStart = (0, import_react14.useMemo)(() => {
@@ -8012,15 +8269,112 @@ function ResourceTimelineChart({
8012
8269
  [customDays, isWeekend3]
8013
8270
  );
8014
8271
  const gridWidth = (0, import_react14.useMemo)(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
8272
+ const effectiveRowHeaderWidth = Math.max(rowHeaderWidth, DEFAULT_ROW_HEADER_WIDTH);
8273
+ const orderedResources = (0, import_react14.useMemo)(
8274
+ () => resourceGrouping === "type" ? orderResourcesByType(resources) : resources,
8275
+ [resourceGrouping, resources]
8276
+ );
8277
+ const workedDaysByResourceId = (0, import_react14.useMemo)(() => {
8278
+ const map = /* @__PURE__ */ new Map();
8279
+ for (const resource of resources) {
8280
+ const workedDays = resource.items.reduce((sum, item) => {
8281
+ try {
8282
+ const startDate = parseUTCDate(item.startDate);
8283
+ const endDate = parseUTCDate(item.endDate);
8284
+ if (!isValidDate(startDate) || !isValidDate(endDate)) {
8285
+ return sum;
8286
+ }
8287
+ return sum + getDurationValue(startDate, endDate, businessDays, weekendPredicate);
8288
+ } catch {
8289
+ return sum;
8290
+ }
8291
+ }, 0);
8292
+ map.set(resource.id, workedDays);
8293
+ }
8294
+ return map;
8295
+ }, [businessDays, resources, weekendPredicate]);
8015
8296
  const layout = (0, import_react14.useMemo)(
8016
- () => layoutResourceTimelineItems(resources, {
8297
+ () => layoutResourceTimelineItems(orderedResources, {
8017
8298
  monthStart,
8018
8299
  dayWidth,
8019
8300
  laneHeight,
8020
8301
  rowGap: DEFAULT_RESOURCE_ROW_GAP
8021
8302
  }),
8022
- [resources, monthStart, dayWidth, laneHeight]
8303
+ [orderedResources, monthStart, dayWidth, laneHeight]
8023
8304
  );
8305
+ const canAddResource = enableAddResource && Boolean(onAddResource);
8306
+ const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8307
+ const displayLayout = (0, import_react14.useMemo)(() => {
8308
+ if (resourceGrouping !== "type") {
8309
+ return {
8310
+ rows: layout.rows,
8311
+ items: layout.items,
8312
+ groupHeaders: [],
8313
+ groupAddRows: [],
8314
+ totalHeight: layout.totalHeight
8315
+ };
8316
+ }
8317
+ const countsByType = /* @__PURE__ */ new Map();
8318
+ for (const row of layout.rows) {
8319
+ const type = getResourceType(row.resource);
8320
+ countsByType.set(type, (countsByType.get(type) ?? 0) + 1);
8321
+ }
8322
+ let offset = 0;
8323
+ let previousType = null;
8324
+ const rowTopOffsets = /* @__PURE__ */ new Map();
8325
+ const groupHeaders = [];
8326
+ const groupAddRows = [];
8327
+ const rows = layout.rows.map((row) => {
8328
+ const type = getResourceType(row.resource);
8329
+ if (type !== previousType) {
8330
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8331
+ groupAddRows.push({
8332
+ key: previousType,
8333
+ top: row.resourceRowTop + offset,
8334
+ height: resourceAddRowHeight
8335
+ });
8336
+ offset += resourceAddRowHeight;
8337
+ }
8338
+ groupHeaders.push({
8339
+ key: type,
8340
+ label: type,
8341
+ count: countsByType.get(type) ?? 0,
8342
+ top: row.resourceRowTop + offset,
8343
+ height: DEFAULT_RESOURCE_GROUP_HEIGHT
8344
+ });
8345
+ offset += DEFAULT_RESOURCE_GROUP_HEIGHT;
8346
+ previousType = type;
8347
+ }
8348
+ rowTopOffsets.set(row.resourceId, offset);
8349
+ return {
8350
+ ...row,
8351
+ resourceRowTop: row.resourceRowTop + offset
8352
+ };
8353
+ });
8354
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8355
+ groupAddRows.push({
8356
+ key: previousType,
8357
+ top: layout.totalHeight + offset,
8358
+ height: resourceAddRowHeight
8359
+ });
8360
+ offset += resourceAddRowHeight;
8361
+ }
8362
+ const items = layout.items.map((item) => {
8363
+ const itemOffset = rowTopOffsets.get(item.resourceId) ?? 0;
8364
+ return {
8365
+ ...item,
8366
+ resourceRowTop: item.resourceRowTop + itemOffset,
8367
+ top: item.top + itemOffset
8368
+ };
8369
+ });
8370
+ return {
8371
+ rows,
8372
+ items,
8373
+ groupHeaders,
8374
+ groupAddRows,
8375
+ totalHeight: layout.totalHeight + offset
8376
+ };
8377
+ }, [creatingResourceGroupType, layout, resourceAddRowHeight, resourceGrouping]);
8024
8378
  const todayInRange = (0, import_react14.useMemo)(() => {
8025
8379
  const now = /* @__PURE__ */ new Date();
8026
8380
  const today = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
@@ -8028,16 +8382,16 @@ function ResourceTimelineChart({
8028
8382
  }, [dateRange]);
8029
8383
  const itemsByResourceId = (0, import_react14.useMemo)(() => {
8030
8384
  const map = /* @__PURE__ */ new Map();
8031
- for (const item of layout.items) {
8385
+ for (const item of displayLayout.items) {
8032
8386
  const next = map.get(item.resourceId) ?? [];
8033
8387
  next.push(item);
8034
8388
  map.set(item.resourceId, next);
8035
8389
  }
8036
8390
  return map;
8037
- }, [layout.items]);
8391
+ }, [displayLayout.items]);
8038
8392
  const conflictItemsByResourceId = (0, import_react14.useMemo)(() => {
8039
8393
  const map = /* @__PURE__ */ new Map();
8040
- for (const item of layout.items) {
8394
+ for (const item of displayLayout.items) {
8041
8395
  if (item.conflictRanges.length === 0) {
8042
8396
  continue;
8043
8397
  }
@@ -8051,19 +8405,28 @@ function ResourceTimelineChart({
8051
8405
  );
8052
8406
  }
8053
8407
  return map;
8054
- }, [layout.items]);
8055
- const canAddResource = enableAddResource && Boolean(onAddResource);
8056
- const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8057
- const displayTotalHeight = layout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8408
+ }, [displayLayout.items]);
8409
+ const displayTotalHeight = displayLayout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8410
+ const timelineHeaderHeight = headerHeight + 1;
8058
8411
  const handleConfirmNewResource = (0, import_react14.useCallback)((name) => {
8059
8412
  onAddResource?.({
8060
8413
  id: crypto.randomUUID(),
8061
8414
  name,
8415
+ type: creatingResourceGroupType ?? "\u0414\u0440\u0443\u0433\u043E\u0435",
8416
+ scope: "Project",
8062
8417
  items: []
8063
8418
  });
8064
8419
  setIsCreatingResource(false);
8065
- }, [onAddResource]);
8066
- const handleCancelNewResource = (0, import_react14.useCallback)(() => setIsCreatingResource(false), []);
8420
+ setCreatingResourceGroupType(null);
8421
+ }, [creatingResourceGroupType, onAddResource]);
8422
+ const handleStartAddResourceToGroup = (0, import_react14.useCallback)((type) => {
8423
+ setIsCreatingResource(false);
8424
+ setCreatingResourceGroupType(type);
8425
+ }, []);
8426
+ const handleCancelNewResource = (0, import_react14.useCallback)(() => {
8427
+ setIsCreatingResource(false);
8428
+ setCreatingResourceGroupType(null);
8429
+ }, []);
8067
8430
  const handleConflictBadgeClick = (0, import_react14.useCallback)((resourceId) => {
8068
8431
  const conflictItems = conflictItemsByResourceId.get(resourceId) ?? [];
8069
8432
  if (conflictItems.length === 0) {
@@ -8089,10 +8452,31 @@ function ResourceTimelineChart({
8089
8452
  conflictHighlightTimeoutRef.current = null;
8090
8453
  }, 1600);
8091
8454
  }, [conflictItemsByResourceId, dayWidth, laneHeight]);
8455
+ const handleResourceNameClick = (0, import_react14.useCallback)((resourceId) => {
8456
+ const resourceItems = itemsByResourceId.get(resourceId) ?? [];
8457
+ if (resourceItems.length === 0) {
8458
+ return;
8459
+ }
8460
+ const orderedItems = [...resourceItems].sort(
8461
+ (left, right) => left.left - right.left || left.top - right.top || left.itemId.localeCompare(right.itemId)
8462
+ );
8463
+ const currentIndex = resourceNavigationIndexRef.current.get(resourceId) ?? 0;
8464
+ const targetItem = orderedItems[currentIndex % orderedItems.length];
8465
+ resourceNavigationIndexRef.current.set(resourceId, (currentIndex + 1) % orderedItems.length);
8466
+ const container = scrollContainerRef.current;
8467
+ if (!container || !targetItem) {
8468
+ return;
8469
+ }
8470
+ container.scrollTo({
8471
+ left: Math.max(0, Math.round(targetItem.left - dayWidth * 2)),
8472
+ top: Math.max(0, Math.round(targetItem.top - laneHeight)),
8473
+ behavior: "smooth"
8474
+ });
8475
+ }, [dayWidth, itemsByResourceId, laneHeight]);
8092
8476
  const { preview, startDrag } = useResourceItemDrag({
8093
8477
  dayWidth,
8094
8478
  monthStart,
8095
- rows: layout.rows,
8479
+ rows: displayLayout.rows,
8096
8480
  gridElementRef: gridRef,
8097
8481
  readonly,
8098
8482
  disableResourceReassignment,
@@ -8100,6 +8484,18 @@ function ResourceTimelineChart({
8100
8484
  weekendPredicate,
8101
8485
  onResourceItemMove
8102
8486
  });
8487
+ (0, import_react14.useEffect)(() => {
8488
+ const container = scrollContainerRef.current;
8489
+ if (!container) return;
8490
+ const updateShadow = () => {
8491
+ setResourceColumnHasRightShadow(container.scrollLeft > 0);
8492
+ };
8493
+ updateShadow();
8494
+ container.addEventListener("scroll", updateShadow, { passive: true });
8495
+ return () => {
8496
+ container.removeEventListener("scroll", updateShadow);
8497
+ };
8498
+ }, []);
8103
8499
  const handlePanStart = (0, import_react14.useCallback)((event) => {
8104
8500
  if (event.button !== 0) {
8105
8501
  return;
@@ -8157,6 +8553,16 @@ function ResourceTimelineChart({
8157
8553
  window.removeEventListener("mouseup", handlePanEnd);
8158
8554
  };
8159
8555
  }, [allowVerticalPan]);
8556
+ (0, import_react14.useEffect)(() => {
8557
+ const nextNavigation = /* @__PURE__ */ new Map();
8558
+ for (const [resourceId, resourceItems] of itemsByResourceId.entries()) {
8559
+ if (resourceItems.length === 0) {
8560
+ continue;
8561
+ }
8562
+ nextNavigation.set(resourceId, (resourceNavigationIndexRef.current.get(resourceId) ?? 0) % resourceItems.length);
8563
+ }
8564
+ resourceNavigationIndexRef.current = nextNavigation;
8565
+ }, [itemsByResourceId]);
8160
8566
  (0, import_react14.useEffect)(() => {
8161
8567
  return () => {
8162
8568
  if (conflictHighlightTimeoutRef.current) {
@@ -8180,25 +8586,92 @@ function ResourceTimelineChart({
8180
8586
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8181
8587
  "div",
8182
8588
  {
8183
- className: "gantt-resourceTimeline-resourceColumn",
8184
- style: { width: `${rowHeaderWidth}px`, minWidth: `${rowHeaderWidth}px` },
8589
+ className: `gantt-resourceTimeline-resourceColumn${resourceColumnHasRightShadow ? " gantt-resourceTimeline-resourceColumnShadowed" : ""}`,
8590
+ style: { width: `${effectiveRowHeaderWidth}px`, minWidth: `${effectiveRowHeaderWidth}px` },
8185
8591
  children: [
8186
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8592
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8187
8593
  "div",
8188
8594
  {
8189
8595
  className: "gantt-resourceTimeline-corner",
8190
- style: { height: `${headerHeight}px` }
8596
+ style: { height: `${timelineHeaderHeight}px` },
8597
+ children: [
8598
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderNumber", children: "#" }),
8599
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderName", children: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435" }),
8600
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceHeaderCell", "aria-label": "\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u044C" }),
8601
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceHeaderCell", children: "\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F" }),
8602
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderActions", "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F" })
8603
+ ]
8191
8604
  }
8192
8605
  ),
8193
- layout.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8606
+ displayLayout.groupHeaders.length > 0 ? displayLayout.groupHeaders.map((group) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react14.default.Fragment, { children: [
8607
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8608
+ "div",
8609
+ {
8610
+ className: "gantt-resourceTimeline-resourceGroupHeader",
8611
+ style: { height: `${group.height}px` },
8612
+ children: [
8613
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceGroupTitle", children: group.label }),
8614
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "gantt-resourceTimeline-resourceGroupCount", children: group.count }),
8615
+ canAddResource && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8616
+ "button",
8617
+ {
8618
+ type: "button",
8619
+ className: "gantt-resourceTimeline-resourceGroupAddButton",
8620
+ "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}`,
8621
+ title: `\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441 \u0432 \u0433\u0440\u0443\u043F\u043F\u0443 ${group.label}`,
8622
+ onClick: (event) => {
8623
+ event.stopPropagation();
8624
+ handleStartAddResourceToGroup(group.key);
8625
+ },
8626
+ children: "+"
8627
+ }
8628
+ )
8629
+ ]
8630
+ }
8631
+ ),
8632
+ displayLayout.rows.filter((row) => getResourceType(row.resource) === group.key).map((row) => {
8633
+ const rowIndex = displayLayout.rows.findIndex((candidate) => candidate.resourceId === row.resourceId);
8634
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8635
+ ResourceHeader,
8636
+ {
8637
+ resource: row.resource,
8638
+ resourceId: row.resourceId,
8639
+ rowIndex,
8640
+ conflictCount: row.conflictCount,
8641
+ workedDays: workedDaysByResourceId.get(row.resourceId) ?? 0,
8642
+ assignmentCount: row.resource.items.length,
8643
+ height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8644
+ paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8645
+ menuCommands: resourceMenuCommands,
8646
+ onResourceChange,
8647
+ onResourceNameClick: handleResourceNameClick,
8648
+ onConflictBadgeClick: handleConflictBadgeClick
8649
+ },
8650
+ row.resourceId
8651
+ );
8652
+ }),
8653
+ displayLayout.groupAddRows.some((row) => row.key === group.key) && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8654
+ NewResourceRow,
8655
+ {
8656
+ height: resourceAddRowHeight,
8657
+ onConfirm: handleConfirmNewResource,
8658
+ onCancel: handleCancelNewResource
8659
+ }
8660
+ )
8661
+ ] }, group.key)) : displayLayout.rows.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8194
8662
  ResourceHeader,
8195
8663
  {
8196
8664
  resource: row.resource,
8197
8665
  resourceId: row.resourceId,
8666
+ rowIndex,
8198
8667
  conflictCount: row.conflictCount,
8668
+ workedDays: workedDaysByResourceId.get(row.resourceId) ?? 0,
8669
+ assignmentCount: row.resource.items.length,
8199
8670
  height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8200
8671
  paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8201
8672
  menuCommands: resourceMenuCommands,
8673
+ onResourceChange,
8674
+ onResourceNameClick: handleResourceNameClick,
8202
8675
  onConflictBadgeClick: handleConflictBadgeClick
8203
8676
  },
8204
8677
  row.resourceId
@@ -8216,7 +8689,10 @@ function ResourceTimelineChart({
8216
8689
  className: "gantt-resourceTimeline-addResourceButton",
8217
8690
  type: "button",
8218
8691
  style: { height: `${resourceAddRowHeight}px` },
8219
- onClick: () => setIsCreatingResource(true),
8692
+ onClick: () => {
8693
+ setCreatingResourceGroupType(null);
8694
+ setIsCreatingResource(true);
8695
+ },
8220
8696
  children: "+ \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441"
8221
8697
  }
8222
8698
  ))
@@ -8229,16 +8705,23 @@ function ResourceTimelineChart({
8229
8705
  className: "gantt-resourceTimeline-chartSurface",
8230
8706
  style: { minWidth: `${gridWidth}px` },
8231
8707
  children: [
8232
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "gantt-resourceTimeline-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8233
- TimeScaleHeader_default,
8708
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8709
+ "div",
8234
8710
  {
8235
- days: dateRange,
8236
- dayWidth,
8237
- headerHeight,
8238
- viewMode,
8239
- isCustomWeekend: weekendPredicate
8711
+ className: "gantt-resourceTimeline-stickyHeader",
8712
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
8713
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8714
+ TimeScaleHeader_default,
8715
+ {
8716
+ days: dateRange,
8717
+ dayWidth,
8718
+ headerHeight,
8719
+ viewMode,
8720
+ isCustomWeekend: weekendPredicate
8721
+ }
8722
+ )
8240
8723
  }
8241
- ) }),
8724
+ ),
8242
8725
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
8243
8726
  "div",
8244
8727
  {
@@ -8257,7 +8740,19 @@ function ResourceTimelineChart({
8257
8740
  }
8258
8741
  ),
8259
8742
  todayInRange && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TodayIndicator_default, { monthStart, dayWidth }),
8260
- layout.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8743
+ displayLayout.groupHeaders.map((group) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8744
+ "div",
8745
+ {
8746
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-groupRow",
8747
+ "data-resource-group": group.key,
8748
+ style: {
8749
+ top: `${group.top}px`,
8750
+ height: `${group.height}px`
8751
+ }
8752
+ },
8753
+ `group-row-${group.key}`
8754
+ )),
8755
+ displayLayout.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8261
8756
  "div",
8262
8757
  {
8263
8758
  className: "gantt-resourceTimeline-row",
@@ -8275,11 +8770,23 @@ function ResourceTimelineChart({
8275
8770
  className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8276
8771
  "data-resource-add-row": "true",
8277
8772
  style: {
8278
- top: `${layout.totalHeight}px`,
8773
+ top: `${displayLayout.totalHeight}px`,
8279
8774
  height: `${resourceAddRowHeight}px`
8280
8775
  }
8281
8776
  }
8282
8777
  ),
8778
+ displayLayout.groupAddRows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
8779
+ "div",
8780
+ {
8781
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8782
+ "data-resource-group-add-row": row.key,
8783
+ style: {
8784
+ top: `${row.top}px`,
8785
+ height: `${row.height}px`
8786
+ }
8787
+ },
8788
+ `group-add-row-${row.key}`
8789
+ )),
8283
8790
  Array.from(itemsByResourceId.values()).flatMap(
8284
8791
  (resourceItems) => resourceItems.map((layoutItem) => {
8285
8792
  const customClassName = getItemClassName?.(layoutItem.item);
@@ -8886,6 +9393,7 @@ function TaskGanttChartInner(props, ref) {
8886
9393
  () => visibleTasks.length * rowHeight,
8887
9394
  [visibleTasks.length, rowHeight]
8888
9395
  );
9396
+ const timelineHeaderHeight = headerHeight + 1;
8889
9397
  const monthStart = (0, import_react15.useMemo)(() => {
8890
9398
  if (dateRange.length === 0) {
8891
9399
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
@@ -9368,16 +9876,23 @@ function TaskGanttChartInner(props, ref) {
9368
9876
  className: showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
9369
9877
  style: { minWidth: `${gridWidth}px`, flex: 1, display: showChart ? void 0 : "none" },
9370
9878
  children: [
9371
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
9372
- TimeScaleHeader_default,
9879
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
9880
+ "div",
9373
9881
  {
9374
- days: dateRange,
9375
- dayWidth,
9376
- headerHeight,
9377
- viewMode,
9378
- isCustomWeekend
9882
+ className: "gantt-stickyHeader",
9883
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
9884
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
9885
+ TimeScaleHeader_default,
9886
+ {
9887
+ days: dateRange,
9888
+ dayWidth,
9889
+ headerHeight,
9890
+ viewMode,
9891
+ isCustomWeekend
9892
+ }
9893
+ )
9379
9894
  }
9380
- ) }),
9895
+ ),
9381
9896
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
9382
9897
  "div",
9383
9898
  {