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.mjs CHANGED
@@ -7023,6 +7023,7 @@ var TaskList = ({
7023
7023
  [resolvedColumns]
7024
7024
  );
7025
7025
  const effectiveTaskListWidth = Math.max(taskListWidth, MIN_TASK_LIST_WIDTH, resolvedColumnWidthTotal);
7026
+ const tableHeaderHeight = headerHeight + 1;
7026
7027
  return /* @__PURE__ */ jsx14(
7027
7028
  "div",
7028
7029
  {
@@ -7030,7 +7031,7 @@ var TaskList = ({
7030
7031
  className: `gantt-tl-overlay${show ? "" : " gantt-tl-hidden"}${hasRightShadow ? " gantt-tl-overlay-shadowed" : ""}`,
7031
7032
  style: { "--tasklist-width": `${effectiveTaskListWidth}px` },
7032
7033
  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) => {
7034
+ /* @__PURE__ */ jsx14("div", { className: "gantt-tl-header", style: { height: `${tableHeaderHeight}px` }, children: resolvedColumns.map((col) => {
7034
7035
  if (col.id === "dependencies") {
7035
7036
  return /* @__PURE__ */ jsxs11(
7036
7037
  "div",
@@ -7214,7 +7215,7 @@ var TaskList = ({
7214
7215
  };
7215
7216
 
7216
7217
  // src/components/ResourceTimelineChart/ResourceTimelineChart.tsx
7217
- import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo9, useRef as useRef8, useState as useState8 } from "react";
7218
+ import React12, { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo9, useRef as useRef8, useState as useState8 } from "react";
7218
7219
 
7219
7220
  // src/utils/resourceTimelineLayout.ts
7220
7221
  var isInvalidDate = (date) => Number.isNaN(date.getTime());
@@ -7632,8 +7633,9 @@ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-r
7632
7633
  var DEFAULT_DAY_WIDTH = 40;
7633
7634
  var DEFAULT_HEADER_HEIGHT = 40;
7634
7635
  var DEFAULT_LANE_HEIGHT = 40;
7635
- var DEFAULT_ROW_HEADER_WIDTH = 240;
7636
+ var DEFAULT_ROW_HEADER_WIDTH = 420;
7636
7637
  var DEFAULT_RESOURCE_ROW_GAP = 8;
7638
+ var DEFAULT_RESOURCE_GROUP_HEIGHT = 28;
7637
7639
  var ITEM_OUTER_VERTICAL_INSET = 2;
7638
7640
  var ITEM_INNER_VERTICAL_INSET = 1;
7639
7641
  var ITEM_START_HORIZONTAL_INSET = 2;
@@ -7720,21 +7722,114 @@ var clampOverlaySegments = (segments2, maxWidth) => segments2.flatMap((segment)
7720
7722
  return width > 0 ? [{ left, width }] : [];
7721
7723
  });
7722
7724
  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);
7725
+ 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"];
7726
+ var RESOURCE_SCOPE_OPTIONS = ["Shared", "Project"];
7727
+ var RESOURCE_SCOPE_LABELS = {
7728
+ Shared: "\u041E\u0431\u0449\u0438\u0439",
7729
+ Project: "\u041F\u0440\u043E\u0435\u043A\u0442"
7730
+ };
7731
+ var RESOURCE_TYPE_CLASS_NAMES = {
7732
+ \u041B\u044E\u0434\u0438: "People",
7733
+ \u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435: "Equipment",
7734
+ \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: "Materials",
7735
+ \u0414\u0440\u0443\u0433\u043E\u0435: "Other"
7736
+ };
7737
+ var RESOURCE_TYPE_ORDER = {
7738
+ \u041B\u044E\u0434\u0438: 0,
7739
+ \u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435: 1,
7740
+ \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: 2,
7741
+ \u0414\u0440\u0443\u0433\u043E\u0435: 3
7742
+ };
7743
+ var RESOURCE_SCOPE_ORDER = {
7744
+ Shared: 0,
7745
+ Project: 1
7746
+ };
7747
+ var getResourceType = (resource) => resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7748
+ var getResourceScopeOrder = (resource) => RESOURCE_SCOPE_ORDER[resource.scope ?? "Project"] ?? 99;
7749
+ var orderResourcesByType = (resources) => {
7750
+ return resources.map((resource, index) => ({ resource, index })).sort((left, right) => {
7751
+ const leftType = getResourceType(left.resource);
7752
+ const rightType = getResourceType(right.resource);
7753
+ const typeDiff = (RESOURCE_TYPE_ORDER[leftType] ?? 99) - (RESOURCE_TYPE_ORDER[rightType] ?? 99);
7754
+ if (typeDiff !== 0) {
7755
+ return typeDiff;
7756
+ }
7757
+ const scopeDiff = getResourceScopeOrder(left.resource) - getResourceScopeOrder(right.resource);
7758
+ return scopeDiff !== 0 ? scopeDiff : left.index - right.index;
7759
+ }).map(({ resource }) => resource);
7760
+ };
7761
+ var ResourceTypeIcon = ({ type }) => {
7762
+ if (type === "\u041B\u044E\u0434\u0438") {
7763
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconPeople", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7764
+ /* @__PURE__ */ jsx15("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
7765
+ /* @__PURE__ */ jsx15("circle", { cx: "9", cy: "7", r: "4" }),
7766
+ /* @__PURE__ */ jsx15("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }),
7767
+ /* @__PURE__ */ jsx15("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
7768
+ ] });
7769
+ }
7770
+ if (type === "\u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435") {
7771
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconEquipment", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7772
+ /* @__PURE__ */ jsx15("path", { d: "m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9" }),
7773
+ /* @__PURE__ */ jsx15("path", { d: "m18 15 4-4" }),
7774
+ /* @__PURE__ */ jsx15("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" })
7775
+ ] });
7776
+ }
7777
+ if (type === "\u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B") {
7778
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconMaterials", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7779
+ /* @__PURE__ */ jsx15("path", { d: "M10 22v-8" }),
7780
+ /* @__PURE__ */ jsx15("path", { d: "M2.336 8.89 10 14l11.715-7.029" }),
7781
+ /* @__PURE__ */ jsx15("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" })
7782
+ ] });
7783
+ }
7784
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconOther", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7785
+ /* @__PURE__ */ jsx15("circle", { cx: "12", cy: "12", r: "9" }),
7786
+ /* @__PURE__ */ jsx15("path", { d: "M12 8v4" }),
7787
+ /* @__PURE__ */ jsx15("path", { d: "M12 16h.01" })
7788
+ ] });
7789
+ };
7723
7790
  var ResourceHeader = ({
7724
7791
  resource,
7725
7792
  resourceId,
7793
+ rowIndex,
7726
7794
  conflictCount,
7795
+ workedDays,
7796
+ assignmentCount,
7727
7797
  height,
7728
7798
  paddingBottom,
7729
7799
  menuCommands,
7800
+ onResourceChange,
7801
+ onResourceNameClick,
7730
7802
  onConflictBadgeClick
7731
7803
  }) => {
7732
7804
  const [menuOpen, setMenuOpen] = useState8(false);
7805
+ const [typeMenuOpen, setTypeMenuOpen] = useState8(false);
7806
+ const [scopeMenuOpen, setScopeMenuOpen] = useState8(false);
7807
+ const [editingName, setEditingName] = useState8(false);
7808
+ const [nameValue, setNameValue] = useState8(resource.name);
7809
+ const nameInputRef = useRef8(null);
7810
+ const nameConfirmedRef = useRef8(false);
7733
7811
  const visibleCommands = useMemo9(
7734
7812
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
7735
7813
  [menuCommands, resource]
7736
7814
  );
7737
7815
  const hasMenu = visibleCommands.length > 0;
7816
+ const type = resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7817
+ const scope = resource.scope ?? "Project";
7818
+ const scopeLabel = RESOURCE_SCOPE_LABELS[scope] ?? scope;
7819
+ const applyResourcePatch = useCallback7((patch) => {
7820
+ onResourceChange?.({ ...resource, ...patch });
7821
+ }, [onResourceChange, resource]);
7822
+ useEffect8(() => {
7823
+ if (!editingName) {
7824
+ setNameValue(resource.name);
7825
+ }
7826
+ }, [editingName, resource.name]);
7827
+ useEffect8(() => {
7828
+ if (editingName && nameInputRef.current) {
7829
+ nameInputRef.current.focus();
7830
+ nameInputRef.current.select();
7831
+ }
7832
+ }, [editingName]);
7738
7833
  const handleCommandClick = (command, event) => {
7739
7834
  event.stopPropagation();
7740
7835
  if (command.closeOnSelect !== false) {
@@ -7742,6 +7837,42 @@ var ResourceHeader = ({
7742
7837
  }
7743
7838
  command.onSelect(resource);
7744
7839
  };
7840
+ const handleNameDoubleClick = useCallback7((event) => {
7841
+ if (!onResourceChange) {
7842
+ return;
7843
+ }
7844
+ event.stopPropagation();
7845
+ nameConfirmedRef.current = false;
7846
+ setNameValue(resource.name);
7847
+ setEditingName(true);
7848
+ }, [onResourceChange, resource.name]);
7849
+ const handleNameSave = useCallback7(() => {
7850
+ if (nameConfirmedRef.current) {
7851
+ nameConfirmedRef.current = false;
7852
+ return;
7853
+ }
7854
+ const nextName = nameValue.trim();
7855
+ if (nextName && nextName !== resource.name) {
7856
+ applyResourcePatch({ name: nextName });
7857
+ }
7858
+ setEditingName(false);
7859
+ }, [applyResourcePatch, nameValue, resource.name]);
7860
+ const handleNameCancel = useCallback7(() => {
7861
+ setNameValue(resource.name);
7862
+ setEditingName(false);
7863
+ }, [resource.name]);
7864
+ const handleNameKeyDown = useCallback7((event) => {
7865
+ if (event.key === "Enter") {
7866
+ nameConfirmedRef.current = true;
7867
+ const nextName = nameValue.trim();
7868
+ if (nextName && nextName !== resource.name) {
7869
+ applyResourcePatch({ name: nextName });
7870
+ }
7871
+ setEditingName(false);
7872
+ } else if (event.key === "Escape") {
7873
+ handleNameCancel();
7874
+ }
7875
+ }, [applyResourcePatch, handleNameCancel, nameValue, resource.name]);
7745
7876
  return /* @__PURE__ */ jsxs12(
7746
7877
  "div",
7747
7878
  {
@@ -7752,49 +7883,167 @@ var ResourceHeader = ({
7752
7883
  paddingBottom: `${paddingBottom}px`
7753
7884
  },
7754
7885
  children: [
7755
- /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceName", children: resource.name }),
7756
- conflictCount > 0 && /* @__PURE__ */ jsx15(
7757
- "button",
7758
- {
7759
- type: "button",
7760
- className: "gantt-resourceTimeline-conflictBadge",
7761
- "aria-label": formatOverlapCount(conflictCount),
7762
- title: formatOverlapCount(conflictCount),
7763
- onClick: (event) => {
7764
- event.stopPropagation();
7765
- onConflictBadgeClick?.(resourceId);
7766
- },
7767
- children: conflictCount
7768
- }
7769
- ),
7770
- hasMenu && /* @__PURE__ */ jsxs12(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
7886
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceNumber", children: rowIndex + 1 }),
7887
+ /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceName", children: [
7888
+ /* @__PURE__ */ jsxs12(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
7889
+ /* @__PURE__ */ jsx15(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx15(
7890
+ "button",
7891
+ {
7892
+ type: "button",
7893
+ className: `gantt-resourceTimeline-resourceTypeIconButton gantt-resourceTimeline-resourceTypeIconButton${RESOURCE_TYPE_CLASS_NAMES[type] ?? "Other"}`,
7894
+ disabled: !onResourceChange,
7895
+ "aria-label": `\u0422\u0438\u043F \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}: ${type}`,
7896
+ title: type,
7897
+ onClick: (event) => {
7898
+ event.stopPropagation();
7899
+ setTypeMenuOpen((open) => !open);
7900
+ },
7901
+ children: /* @__PURE__ */ jsx15(ResourceTypeIcon, { type })
7902
+ }
7903
+ ) }),
7904
+ /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceOptionMenu", portal: true, align: "start", children: RESOURCE_TYPE_OPTIONS.map((option) => /* @__PURE__ */ jsxs12(
7905
+ "button",
7906
+ {
7907
+ type: "button",
7908
+ className: `gantt-resourceTimeline-resourceOption${type === option ? " gantt-resourceTimeline-resourceOptionActive" : ""}`,
7909
+ onClick: (event) => {
7910
+ event.stopPropagation();
7911
+ setTypeMenuOpen(false);
7912
+ applyResourcePatch({ type: option });
7913
+ },
7914
+ children: [
7915
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceOptionIcon", children: /* @__PURE__ */ jsx15(ResourceTypeIcon, { type: option }) }),
7916
+ option
7917
+ ]
7918
+ },
7919
+ option
7920
+ )) })
7921
+ ] }),
7922
+ editingName ? /* @__PURE__ */ jsx15(
7923
+ Input,
7924
+ {
7925
+ ref: nameInputRef,
7926
+ value: nameValue,
7927
+ onChange: (event) => setNameValue(event.target.value),
7928
+ onBlur: handleNameSave,
7929
+ onKeyDown: handleNameKeyDown,
7930
+ onClick: (event) => event.stopPropagation(),
7931
+ "aria-label": `\u041D\u043E\u0432\u043E\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7932
+ className: "gantt-tl-name-input gantt-resourceTimeline-resourceNameInput"
7933
+ }
7934
+ ) : /* @__PURE__ */ jsx15(
7935
+ "button",
7936
+ {
7937
+ type: "button",
7938
+ className: "gantt-resourceTimeline-resourceNameButton",
7939
+ "aria-label": `\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7940
+ title: resource.name,
7941
+ onClick: (event) => {
7942
+ event.stopPropagation();
7943
+ onResourceNameClick?.(resourceId);
7944
+ },
7945
+ onDoubleClick: handleNameDoubleClick,
7946
+ children: resource.name
7947
+ }
7948
+ )
7949
+ ] }),
7950
+ /* @__PURE__ */ jsxs12(Popover, { open: scopeMenuOpen, onOpenChange: setScopeMenuOpen, children: [
7771
7951
  /* @__PURE__ */ jsx15(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx15(
7772
7952
  "button",
7773
7953
  {
7774
- className: "gantt-resourceTimeline-resourceMenuButton",
7775
7954
  type: "button",
7776
- "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
7955
+ className: `gantt-resourceTimeline-resourceScopeChip gantt-resourceTimeline-resourceScope${scope === "Shared" ? "Shared" : "Project"}`,
7956
+ disabled: !onResourceChange,
7957
+ "aria-label": `\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7777
7958
  onClick: (event) => {
7778
7959
  event.stopPropagation();
7779
- setMenuOpen((open) => !open);
7960
+ setScopeMenuOpen((open) => !open);
7780
7961
  },
7781
- children: /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u22EE" })
7962
+ children: /* @__PURE__ */ jsx15("span", { children: scopeLabel })
7782
7963
  }
7783
7964
  ) }),
7784
- /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceMenu", portal: true, align: "end", children: visibleCommands.map((command) => /* @__PURE__ */ jsxs12(
7965
+ /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceOptionMenu", portal: true, align: "start", children: RESOURCE_SCOPE_OPTIONS.map((option) => /* @__PURE__ */ jsxs12(
7785
7966
  "button",
7786
7967
  {
7787
7968
  type: "button",
7788
- className: `gantt-resourceTimeline-resourceMenuItem${command.danger ? " gantt-resourceTimeline-resourceMenuItemDanger" : ""}`,
7789
- disabled: command.isDisabled?.(resource) ?? false,
7790
- onClick: (event) => handleCommandClick(command, event),
7969
+ className: `gantt-resourceTimeline-resourceOption${scope === option ? " gantt-resourceTimeline-resourceOptionActive" : ""}`,
7970
+ onClick: (event) => {
7971
+ event.stopPropagation();
7972
+ setScopeMenuOpen(false);
7973
+ applyResourcePatch({ scope: option });
7974
+ },
7791
7975
  children: [
7792
- command.icon,
7793
- command.label
7976
+ /* @__PURE__ */ jsx15("span", { className: `gantt-resourceTimeline-resourceOptionScopeDot gantt-resourceTimeline-resourceScope${option}` }),
7977
+ RESOURCE_SCOPE_LABELS[option] ?? option
7794
7978
  ]
7795
7979
  },
7796
- command.id
7980
+ option
7797
7981
  )) })
7982
+ ] }),
7983
+ /* @__PURE__ */ jsxs12(
7984
+ "span",
7985
+ {
7986
+ className: "gantt-resourceTimeline-resourceAssignments",
7987
+ "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.`,
7988
+ children: [
7989
+ /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceWorkedDays", children: [
7990
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: workedDays > 0 ? workedDays : "-" }),
7991
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceMetricLabel", children: workedDays > 0 ? "\u0434\u043D." : "" })
7992
+ ] }),
7993
+ /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceAssignmentCount", children: [
7994
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceMetricValue", children: assignmentCount > 0 ? assignmentCount : "" }),
7995
+ assignmentCount > 0 && /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceAssignmentIcon", width: "14", height: "14", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7996
+ /* @__PURE__ */ jsx15("rect", { width: "15", height: "5", x: "4", y: "5", rx: "2" }),
7997
+ /* @__PURE__ */ jsx15("rect", { width: "10", height: "5", x: "4", y: "14", rx: "2" })
7998
+ ] })
7999
+ ] })
8000
+ ]
8001
+ }
8002
+ ),
8003
+ /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceActions", children: [
8004
+ conflictCount > 0 && /* @__PURE__ */ jsx15(
8005
+ "button",
8006
+ {
8007
+ type: "button",
8008
+ className: "gantt-resourceTimeline-conflictBadge",
8009
+ "aria-label": formatOverlapCount(conflictCount),
8010
+ title: formatOverlapCount(conflictCount),
8011
+ onClick: (event) => {
8012
+ event.stopPropagation();
8013
+ onConflictBadgeClick?.(resourceId);
8014
+ },
8015
+ children: conflictCount
8016
+ }
8017
+ ),
8018
+ hasMenu && /* @__PURE__ */ jsxs12(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
8019
+ /* @__PURE__ */ jsx15(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx15(
8020
+ "button",
8021
+ {
8022
+ className: "gantt-resourceTimeline-resourceMenuButton",
8023
+ type: "button",
8024
+ "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
8025
+ onClick: (event) => {
8026
+ event.stopPropagation();
8027
+ setMenuOpen((open) => !open);
8028
+ },
8029
+ children: /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u22EE" })
8030
+ }
8031
+ ) }),
8032
+ /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceMenu", portal: true, align: "end", children: visibleCommands.map((command) => /* @__PURE__ */ jsxs12(
8033
+ "button",
8034
+ {
8035
+ type: "button",
8036
+ className: `gantt-resourceTimeline-resourceMenuItem${command.danger ? " gantt-resourceTimeline-resourceMenuItemDanger" : ""}`,
8037
+ disabled: command.isDisabled?.(resource) ?? false,
8038
+ onClick: (event) => handleCommandClick(command, event),
8039
+ children: [
8040
+ command.icon,
8041
+ command.label
8042
+ ]
8043
+ },
8044
+ command.id
8045
+ )) })
8046
+ ] })
7798
8047
  ] })
7799
8048
  ]
7800
8049
  }
@@ -7833,7 +8082,7 @@ var NewResourceRow = ({ height, onConfirm, onCancel }) => {
7833
8082
  handleConfirm();
7834
8083
  }
7835
8084
  };
7836
- return /* @__PURE__ */ jsx15(
8085
+ return /* @__PURE__ */ jsxs12(
7837
8086
  "div",
7838
8087
  {
7839
8088
  className: "gantt-resourceTimeline-resourceHeader gantt-resourceTimeline-resourceHeaderNew",
@@ -7841,18 +8090,21 @@ var NewResourceRow = ({ height, onConfirm, onCancel }) => {
7841
8090
  height: `${height}px`,
7842
8091
  paddingBottom: `${DEFAULT_RESOURCE_ROW_GAP}px`
7843
8092
  },
7844
- children: /* @__PURE__ */ jsx15(
7845
- Input,
7846
- {
7847
- ref: inputRef,
7848
- value: nameValue,
7849
- onChange: (event) => setNameValue(event.target.value),
7850
- onKeyDown: handleKeyDown,
7851
- onBlur: handleBlur,
7852
- placeholder: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
7853
- className: "gantt-resourceTimeline-resourceInput"
7854
- }
7855
- )
8093
+ children: [
8094
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceNumber", children: "+" }),
8095
+ /* @__PURE__ */ jsx15(
8096
+ Input,
8097
+ {
8098
+ ref: inputRef,
8099
+ value: nameValue,
8100
+ onChange: (event) => setNameValue(event.target.value),
8101
+ onKeyDown: handleKeyDown,
8102
+ onBlur: handleBlur,
8103
+ placeholder: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
8104
+ className: "gantt-resourceTimeline-resourceInput"
8105
+ }
8106
+ )
8107
+ ]
7856
8108
  }
7857
8109
  );
7858
8110
  };
@@ -7868,6 +8120,7 @@ function ResourceTimelineChart({
7868
8120
  customDays,
7869
8121
  isWeekend: isWeekend3,
7870
8122
  businessDays = true,
8123
+ resourceGrouping = false,
7871
8124
  readonly,
7872
8125
  disableResourceReassignment,
7873
8126
  renderItem,
@@ -7876,6 +8129,7 @@ function ResourceTimelineChart({
7876
8129
  onResourceItemMenuClick,
7877
8130
  activeResourceItemId,
7878
8131
  onResourceItemMove,
8132
+ onResourceChange,
7879
8133
  onAddResource,
7880
8134
  enableAddResource = true,
7881
8135
  resourceMenuCommands = []
@@ -7884,9 +8138,12 @@ function ResourceTimelineChart({
7884
8138
  const gridRef = useRef8(null);
7885
8139
  const panStateRef = useRef8(null);
7886
8140
  const conflictNavigationIndexRef = useRef8(/* @__PURE__ */ new Map());
8141
+ const resourceNavigationIndexRef = useRef8(/* @__PURE__ */ new Map());
7887
8142
  const conflictHighlightTimeoutRef = useRef8(null);
7888
8143
  const [isCreatingResource, setIsCreatingResource] = useState8(false);
8144
+ const [creatingResourceGroupType, setCreatingResourceGroupType] = useState8(null);
7889
8145
  const [activeConflictItemId, setActiveConflictItemId] = useState8(null);
8146
+ const [resourceColumnHasRightShadow, setResourceColumnHasRightShadow] = useState8(false);
7890
8147
  const validItems = useMemo9(() => collectValidItems(resources), [resources]);
7891
8148
  const dateRange = useMemo9(() => getMultiMonthDays(validItems), [validItems]);
7892
8149
  const monthStart = useMemo9(() => {
@@ -7898,15 +8155,112 @@ function ResourceTimelineChart({
7898
8155
  [customDays, isWeekend3]
7899
8156
  );
7900
8157
  const gridWidth = useMemo9(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
8158
+ const effectiveRowHeaderWidth = Math.max(rowHeaderWidth, DEFAULT_ROW_HEADER_WIDTH);
8159
+ const orderedResources = useMemo9(
8160
+ () => resourceGrouping === "type" ? orderResourcesByType(resources) : resources,
8161
+ [resourceGrouping, resources]
8162
+ );
8163
+ const workedDaysByResourceId = useMemo9(() => {
8164
+ const map = /* @__PURE__ */ new Map();
8165
+ for (const resource of resources) {
8166
+ const workedDays = resource.items.reduce((sum, item) => {
8167
+ try {
8168
+ const startDate = parseUTCDate(item.startDate);
8169
+ const endDate = parseUTCDate(item.endDate);
8170
+ if (!isValidDate(startDate) || !isValidDate(endDate)) {
8171
+ return sum;
8172
+ }
8173
+ return sum + getDurationValue(startDate, endDate, businessDays, weekendPredicate);
8174
+ } catch {
8175
+ return sum;
8176
+ }
8177
+ }, 0);
8178
+ map.set(resource.id, workedDays);
8179
+ }
8180
+ return map;
8181
+ }, [businessDays, resources, weekendPredicate]);
7901
8182
  const layout = useMemo9(
7902
- () => layoutResourceTimelineItems(resources, {
8183
+ () => layoutResourceTimelineItems(orderedResources, {
7903
8184
  monthStart,
7904
8185
  dayWidth,
7905
8186
  laneHeight,
7906
8187
  rowGap: DEFAULT_RESOURCE_ROW_GAP
7907
8188
  }),
7908
- [resources, monthStart, dayWidth, laneHeight]
8189
+ [orderedResources, monthStart, dayWidth, laneHeight]
7909
8190
  );
8191
+ const canAddResource = enableAddResource && Boolean(onAddResource);
8192
+ const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
8193
+ const displayLayout = useMemo9(() => {
8194
+ if (resourceGrouping !== "type") {
8195
+ return {
8196
+ rows: layout.rows,
8197
+ items: layout.items,
8198
+ groupHeaders: [],
8199
+ groupAddRows: [],
8200
+ totalHeight: layout.totalHeight
8201
+ };
8202
+ }
8203
+ const countsByType = /* @__PURE__ */ new Map();
8204
+ for (const row of layout.rows) {
8205
+ const type = getResourceType(row.resource);
8206
+ countsByType.set(type, (countsByType.get(type) ?? 0) + 1);
8207
+ }
8208
+ let offset = 0;
8209
+ let previousType = null;
8210
+ const rowTopOffsets = /* @__PURE__ */ new Map();
8211
+ const groupHeaders = [];
8212
+ const groupAddRows = [];
8213
+ const rows = layout.rows.map((row) => {
8214
+ const type = getResourceType(row.resource);
8215
+ if (type !== previousType) {
8216
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8217
+ groupAddRows.push({
8218
+ key: previousType,
8219
+ top: row.resourceRowTop + offset,
8220
+ height: resourceAddRowHeight
8221
+ });
8222
+ offset += resourceAddRowHeight;
8223
+ }
8224
+ groupHeaders.push({
8225
+ key: type,
8226
+ label: type,
8227
+ count: countsByType.get(type) ?? 0,
8228
+ top: row.resourceRowTop + offset,
8229
+ height: DEFAULT_RESOURCE_GROUP_HEIGHT
8230
+ });
8231
+ offset += DEFAULT_RESOURCE_GROUP_HEIGHT;
8232
+ previousType = type;
8233
+ }
8234
+ rowTopOffsets.set(row.resourceId, offset);
8235
+ return {
8236
+ ...row,
8237
+ resourceRowTop: row.resourceRowTop + offset
8238
+ };
8239
+ });
8240
+ if (previousType !== null && creatingResourceGroupType === previousType) {
8241
+ groupAddRows.push({
8242
+ key: previousType,
8243
+ top: layout.totalHeight + offset,
8244
+ height: resourceAddRowHeight
8245
+ });
8246
+ offset += resourceAddRowHeight;
8247
+ }
8248
+ const items = layout.items.map((item) => {
8249
+ const itemOffset = rowTopOffsets.get(item.resourceId) ?? 0;
8250
+ return {
8251
+ ...item,
8252
+ resourceRowTop: item.resourceRowTop + itemOffset,
8253
+ top: item.top + itemOffset
8254
+ };
8255
+ });
8256
+ return {
8257
+ rows,
8258
+ items,
8259
+ groupHeaders,
8260
+ groupAddRows,
8261
+ totalHeight: layout.totalHeight + offset
8262
+ };
8263
+ }, [creatingResourceGroupType, layout, resourceAddRowHeight, resourceGrouping]);
7910
8264
  const todayInRange = useMemo9(() => {
7911
8265
  const now = /* @__PURE__ */ new Date();
7912
8266
  const today = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
@@ -7914,16 +8268,16 @@ function ResourceTimelineChart({
7914
8268
  }, [dateRange]);
7915
8269
  const itemsByResourceId = useMemo9(() => {
7916
8270
  const map = /* @__PURE__ */ new Map();
7917
- for (const item of layout.items) {
8271
+ for (const item of displayLayout.items) {
7918
8272
  const next = map.get(item.resourceId) ?? [];
7919
8273
  next.push(item);
7920
8274
  map.set(item.resourceId, next);
7921
8275
  }
7922
8276
  return map;
7923
- }, [layout.items]);
8277
+ }, [displayLayout.items]);
7924
8278
  const conflictItemsByResourceId = useMemo9(() => {
7925
8279
  const map = /* @__PURE__ */ new Map();
7926
- for (const item of layout.items) {
8280
+ for (const item of displayLayout.items) {
7927
8281
  if (item.conflictRanges.length === 0) {
7928
8282
  continue;
7929
8283
  }
@@ -7937,19 +8291,28 @@ function ResourceTimelineChart({
7937
8291
  );
7938
8292
  }
7939
8293
  return map;
7940
- }, [layout.items]);
7941
- const canAddResource = enableAddResource && Boolean(onAddResource);
7942
- const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
7943
- const displayTotalHeight = layout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8294
+ }, [displayLayout.items]);
8295
+ const displayTotalHeight = displayLayout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
8296
+ const timelineHeaderHeight = headerHeight + 1;
7944
8297
  const handleConfirmNewResource = useCallback7((name) => {
7945
8298
  onAddResource?.({
7946
8299
  id: crypto.randomUUID(),
7947
8300
  name,
8301
+ type: creatingResourceGroupType ?? "\u0414\u0440\u0443\u0433\u043E\u0435",
8302
+ scope: "Project",
7948
8303
  items: []
7949
8304
  });
7950
8305
  setIsCreatingResource(false);
7951
- }, [onAddResource]);
7952
- const handleCancelNewResource = useCallback7(() => setIsCreatingResource(false), []);
8306
+ setCreatingResourceGroupType(null);
8307
+ }, [creatingResourceGroupType, onAddResource]);
8308
+ const handleStartAddResourceToGroup = useCallback7((type) => {
8309
+ setIsCreatingResource(false);
8310
+ setCreatingResourceGroupType(type);
8311
+ }, []);
8312
+ const handleCancelNewResource = useCallback7(() => {
8313
+ setIsCreatingResource(false);
8314
+ setCreatingResourceGroupType(null);
8315
+ }, []);
7953
8316
  const handleConflictBadgeClick = useCallback7((resourceId) => {
7954
8317
  const conflictItems = conflictItemsByResourceId.get(resourceId) ?? [];
7955
8318
  if (conflictItems.length === 0) {
@@ -7975,10 +8338,31 @@ function ResourceTimelineChart({
7975
8338
  conflictHighlightTimeoutRef.current = null;
7976
8339
  }, 1600);
7977
8340
  }, [conflictItemsByResourceId, dayWidth, laneHeight]);
8341
+ const handleResourceNameClick = useCallback7((resourceId) => {
8342
+ const resourceItems = itemsByResourceId.get(resourceId) ?? [];
8343
+ if (resourceItems.length === 0) {
8344
+ return;
8345
+ }
8346
+ const orderedItems = [...resourceItems].sort(
8347
+ (left, right) => left.left - right.left || left.top - right.top || left.itemId.localeCompare(right.itemId)
8348
+ );
8349
+ const currentIndex = resourceNavigationIndexRef.current.get(resourceId) ?? 0;
8350
+ const targetItem = orderedItems[currentIndex % orderedItems.length];
8351
+ resourceNavigationIndexRef.current.set(resourceId, (currentIndex + 1) % orderedItems.length);
8352
+ const container = scrollContainerRef.current;
8353
+ if (!container || !targetItem) {
8354
+ return;
8355
+ }
8356
+ container.scrollTo({
8357
+ left: Math.max(0, Math.round(targetItem.left - dayWidth * 2)),
8358
+ top: Math.max(0, Math.round(targetItem.top - laneHeight)),
8359
+ behavior: "smooth"
8360
+ });
8361
+ }, [dayWidth, itemsByResourceId, laneHeight]);
7978
8362
  const { preview, startDrag } = useResourceItemDrag({
7979
8363
  dayWidth,
7980
8364
  monthStart,
7981
- rows: layout.rows,
8365
+ rows: displayLayout.rows,
7982
8366
  gridElementRef: gridRef,
7983
8367
  readonly,
7984
8368
  disableResourceReassignment,
@@ -7986,6 +8370,18 @@ function ResourceTimelineChart({
7986
8370
  weekendPredicate,
7987
8371
  onResourceItemMove
7988
8372
  });
8373
+ useEffect8(() => {
8374
+ const container = scrollContainerRef.current;
8375
+ if (!container) return;
8376
+ const updateShadow = () => {
8377
+ setResourceColumnHasRightShadow(container.scrollLeft > 0);
8378
+ };
8379
+ updateShadow();
8380
+ container.addEventListener("scroll", updateShadow, { passive: true });
8381
+ return () => {
8382
+ container.removeEventListener("scroll", updateShadow);
8383
+ };
8384
+ }, []);
7989
8385
  const handlePanStart = useCallback7((event) => {
7990
8386
  if (event.button !== 0) {
7991
8387
  return;
@@ -8043,6 +8439,16 @@ function ResourceTimelineChart({
8043
8439
  window.removeEventListener("mouseup", handlePanEnd);
8044
8440
  };
8045
8441
  }, [allowVerticalPan]);
8442
+ useEffect8(() => {
8443
+ const nextNavigation = /* @__PURE__ */ new Map();
8444
+ for (const [resourceId, resourceItems] of itemsByResourceId.entries()) {
8445
+ if (resourceItems.length === 0) {
8446
+ continue;
8447
+ }
8448
+ nextNavigation.set(resourceId, (resourceNavigationIndexRef.current.get(resourceId) ?? 0) % resourceItems.length);
8449
+ }
8450
+ resourceNavigationIndexRef.current = nextNavigation;
8451
+ }, [itemsByResourceId]);
8046
8452
  useEffect8(() => {
8047
8453
  return () => {
8048
8454
  if (conflictHighlightTimeoutRef.current) {
@@ -8066,25 +8472,92 @@ function ResourceTimelineChart({
8066
8472
  /* @__PURE__ */ jsxs12(
8067
8473
  "div",
8068
8474
  {
8069
- className: "gantt-resourceTimeline-resourceColumn",
8070
- style: { width: `${rowHeaderWidth}px`, minWidth: `${rowHeaderWidth}px` },
8475
+ className: `gantt-resourceTimeline-resourceColumn${resourceColumnHasRightShadow ? " gantt-resourceTimeline-resourceColumnShadowed" : ""}`,
8476
+ style: { width: `${effectiveRowHeaderWidth}px`, minWidth: `${effectiveRowHeaderWidth}px` },
8071
8477
  children: [
8072
- /* @__PURE__ */ jsx15(
8478
+ /* @__PURE__ */ jsxs12(
8073
8479
  "div",
8074
8480
  {
8075
8481
  className: "gantt-resourceTimeline-corner",
8076
- style: { height: `${headerHeight}px` }
8482
+ style: { height: `${timelineHeaderHeight}px` },
8483
+ children: [
8484
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderNumber", children: "#" }),
8485
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderName", children: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435" }),
8486
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell", "aria-label": "\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u044C" }),
8487
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell", children: "\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F" }),
8488
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderActions", "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F" })
8489
+ ]
8077
8490
  }
8078
8491
  ),
8079
- layout.rows.map((row) => /* @__PURE__ */ jsx15(
8492
+ displayLayout.groupHeaders.length > 0 ? displayLayout.groupHeaders.map((group) => /* @__PURE__ */ jsxs12(React12.Fragment, { children: [
8493
+ /* @__PURE__ */ jsxs12(
8494
+ "div",
8495
+ {
8496
+ className: "gantt-resourceTimeline-resourceGroupHeader",
8497
+ style: { height: `${group.height}px` },
8498
+ children: [
8499
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceGroupTitle", children: group.label }),
8500
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceGroupCount", children: group.count }),
8501
+ canAddResource && /* @__PURE__ */ jsx15(
8502
+ "button",
8503
+ {
8504
+ type: "button",
8505
+ className: "gantt-resourceTimeline-resourceGroupAddButton",
8506
+ "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}`,
8507
+ title: `\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441 \u0432 \u0433\u0440\u0443\u043F\u043F\u0443 ${group.label}`,
8508
+ onClick: (event) => {
8509
+ event.stopPropagation();
8510
+ handleStartAddResourceToGroup(group.key);
8511
+ },
8512
+ children: "+"
8513
+ }
8514
+ )
8515
+ ]
8516
+ }
8517
+ ),
8518
+ displayLayout.rows.filter((row) => getResourceType(row.resource) === group.key).map((row) => {
8519
+ const rowIndex = displayLayout.rows.findIndex((candidate) => candidate.resourceId === row.resourceId);
8520
+ return /* @__PURE__ */ jsx15(
8521
+ ResourceHeader,
8522
+ {
8523
+ resource: row.resource,
8524
+ resourceId: row.resourceId,
8525
+ rowIndex,
8526
+ conflictCount: row.conflictCount,
8527
+ workedDays: workedDaysByResourceId.get(row.resourceId) ?? 0,
8528
+ assignmentCount: row.resource.items.length,
8529
+ height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8530
+ paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8531
+ menuCommands: resourceMenuCommands,
8532
+ onResourceChange,
8533
+ onResourceNameClick: handleResourceNameClick,
8534
+ onConflictBadgeClick: handleConflictBadgeClick
8535
+ },
8536
+ row.resourceId
8537
+ );
8538
+ }),
8539
+ displayLayout.groupAddRows.some((row) => row.key === group.key) && /* @__PURE__ */ jsx15(
8540
+ NewResourceRow,
8541
+ {
8542
+ height: resourceAddRowHeight,
8543
+ onConfirm: handleConfirmNewResource,
8544
+ onCancel: handleCancelNewResource
8545
+ }
8546
+ )
8547
+ ] }, group.key)) : displayLayout.rows.map((row, rowIndex) => /* @__PURE__ */ jsx15(
8080
8548
  ResourceHeader,
8081
8549
  {
8082
8550
  resource: row.resource,
8083
8551
  resourceId: row.resourceId,
8552
+ rowIndex,
8084
8553
  conflictCount: row.conflictCount,
8554
+ workedDays: workedDaysByResourceId.get(row.resourceId) ?? 0,
8555
+ assignmentCount: row.resource.items.length,
8085
8556
  height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8086
8557
  paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8087
8558
  menuCommands: resourceMenuCommands,
8559
+ onResourceChange,
8560
+ onResourceNameClick: handleResourceNameClick,
8088
8561
  onConflictBadgeClick: handleConflictBadgeClick
8089
8562
  },
8090
8563
  row.resourceId
@@ -8102,7 +8575,10 @@ function ResourceTimelineChart({
8102
8575
  className: "gantt-resourceTimeline-addResourceButton",
8103
8576
  type: "button",
8104
8577
  style: { height: `${resourceAddRowHeight}px` },
8105
- onClick: () => setIsCreatingResource(true),
8578
+ onClick: () => {
8579
+ setCreatingResourceGroupType(null);
8580
+ setIsCreatingResource(true);
8581
+ },
8106
8582
  children: "+ \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441"
8107
8583
  }
8108
8584
  ))
@@ -8115,16 +8591,23 @@ function ResourceTimelineChart({
8115
8591
  className: "gantt-resourceTimeline-chartSurface",
8116
8592
  style: { minWidth: `${gridWidth}px` },
8117
8593
  children: [
8118
- /* @__PURE__ */ jsx15("div", { className: "gantt-resourceTimeline-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx15(
8119
- TimeScaleHeader_default,
8594
+ /* @__PURE__ */ jsx15(
8595
+ "div",
8120
8596
  {
8121
- days: dateRange,
8122
- dayWidth,
8123
- headerHeight,
8124
- viewMode,
8125
- isCustomWeekend: weekendPredicate
8597
+ className: "gantt-resourceTimeline-stickyHeader",
8598
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
8599
+ children: /* @__PURE__ */ jsx15(
8600
+ TimeScaleHeader_default,
8601
+ {
8602
+ days: dateRange,
8603
+ dayWidth,
8604
+ headerHeight,
8605
+ viewMode,
8606
+ isCustomWeekend: weekendPredicate
8607
+ }
8608
+ )
8126
8609
  }
8127
- ) }),
8610
+ ),
8128
8611
  /* @__PURE__ */ jsxs12(
8129
8612
  "div",
8130
8613
  {
@@ -8143,7 +8626,19 @@ function ResourceTimelineChart({
8143
8626
  }
8144
8627
  ),
8145
8628
  todayInRange && /* @__PURE__ */ jsx15(TodayIndicator_default, { monthStart, dayWidth }),
8146
- layout.rows.map((row) => /* @__PURE__ */ jsx15(
8629
+ displayLayout.groupHeaders.map((group) => /* @__PURE__ */ jsx15(
8630
+ "div",
8631
+ {
8632
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-groupRow",
8633
+ "data-resource-group": group.key,
8634
+ style: {
8635
+ top: `${group.top}px`,
8636
+ height: `${group.height}px`
8637
+ }
8638
+ },
8639
+ `group-row-${group.key}`
8640
+ )),
8641
+ displayLayout.rows.map((row) => /* @__PURE__ */ jsx15(
8147
8642
  "div",
8148
8643
  {
8149
8644
  className: "gantt-resourceTimeline-row",
@@ -8161,11 +8656,23 @@ function ResourceTimelineChart({
8161
8656
  className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8162
8657
  "data-resource-add-row": "true",
8163
8658
  style: {
8164
- top: `${layout.totalHeight}px`,
8659
+ top: `${displayLayout.totalHeight}px`,
8165
8660
  height: `${resourceAddRowHeight}px`
8166
8661
  }
8167
8662
  }
8168
8663
  ),
8664
+ displayLayout.groupAddRows.map((row) => /* @__PURE__ */ jsx15(
8665
+ "div",
8666
+ {
8667
+ className: "gantt-resourceTimeline-row gantt-resourceTimeline-rowNew",
8668
+ "data-resource-group-add-row": row.key,
8669
+ style: {
8670
+ top: `${row.top}px`,
8671
+ height: `${row.height}px`
8672
+ }
8673
+ },
8674
+ `group-add-row-${row.key}`
8675
+ )),
8169
8676
  Array.from(itemsByResourceId.values()).flatMap(
8170
8677
  (resourceItems) => resourceItems.map((layoutItem) => {
8171
8678
  const customClassName = getItemClassName?.(layoutItem.item);
@@ -8772,6 +9279,7 @@ function TaskGanttChartInner(props, ref) {
8772
9279
  () => visibleTasks.length * rowHeight,
8773
9280
  [visibleTasks.length, rowHeight]
8774
9281
  );
9282
+ const timelineHeaderHeight = headerHeight + 1;
8775
9283
  const monthStart = useMemo10(() => {
8776
9284
  if (dateRange.length === 0) {
8777
9285
  return new Date(Date.UTC((/* @__PURE__ */ new Date()).getUTCFullYear(), (/* @__PURE__ */ new Date()).getUTCMonth(), 1));
@@ -9254,16 +9762,23 @@ function TaskGanttChartInner(props, ref) {
9254
9762
  className: showChart ? "gantt-chartSurface" : "gantt-chartSurface gantt-chart-hidden",
9255
9763
  style: { minWidth: `${gridWidth}px`, flex: 1, display: showChart ? void 0 : "none" },
9256
9764
  children: [
9257
- /* @__PURE__ */ jsx16("div", { className: "gantt-stickyHeader", style: { width: `${gridWidth}px` }, children: /* @__PURE__ */ jsx16(
9258
- TimeScaleHeader_default,
9765
+ /* @__PURE__ */ jsx16(
9766
+ "div",
9259
9767
  {
9260
- days: dateRange,
9261
- dayWidth,
9262
- headerHeight,
9263
- viewMode,
9264
- isCustomWeekend
9768
+ className: "gantt-stickyHeader",
9769
+ style: { width: `${gridWidth}px`, height: `${timelineHeaderHeight}px` },
9770
+ children: /* @__PURE__ */ jsx16(
9771
+ TimeScaleHeader_default,
9772
+ {
9773
+ days: dateRange,
9774
+ dayWidth,
9775
+ headerHeight,
9776
+ viewMode,
9777
+ isCustomWeekend
9778
+ }
9779
+ )
9265
9780
  }
9266
- ) }),
9781
+ ),
9267
9782
  /* @__PURE__ */ jsxs13(
9268
9783
  "div",
9269
9784
  {