gantt-lib 0.80.1 → 0.82.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
@@ -7632,7 +7632,7 @@ import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-r
7632
7632
  var DEFAULT_DAY_WIDTH = 40;
7633
7633
  var DEFAULT_HEADER_HEIGHT = 40;
7634
7634
  var DEFAULT_LANE_HEIGHT = 40;
7635
- var DEFAULT_ROW_HEADER_WIDTH = 240;
7635
+ var DEFAULT_ROW_HEADER_WIDTH = 420;
7636
7636
  var DEFAULT_RESOURCE_ROW_GAP = 8;
7637
7637
  var ITEM_OUTER_VERTICAL_INSET = 2;
7638
7638
  var ITEM_INNER_VERTICAL_INSET = 1;
@@ -7720,20 +7720,97 @@ var clampOverlaySegments = (segments2, maxWidth) => segments2.flatMap((segment)
7720
7720
  return width > 0 ? [{ left, width }] : [];
7721
7721
  });
7722
7722
  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);
7723
+ 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"];
7724
+ var RESOURCE_SCOPE_OPTIONS = ["Shared", "Project"];
7725
+ var RESOURCE_SCOPE_LABELS = {
7726
+ Shared: "\u041E\u0431\u0449\u0438\u0439",
7727
+ Project: "\u041F\u0440\u043E\u0435\u043A\u0442"
7728
+ };
7729
+ var RESOURCE_TYPE_CLASS_NAMES = {
7730
+ \u041B\u044E\u0434\u0438: "People",
7731
+ \u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435: "Equipment",
7732
+ \u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B: "Materials",
7733
+ \u0414\u0440\u0443\u0433\u043E\u0435: "Other"
7734
+ };
7735
+ var ResourceTypeIcon = ({ type }) => {
7736
+ if (type === "\u041B\u044E\u0434\u0438") {
7737
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconPeople", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7738
+ /* @__PURE__ */ jsx15("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
7739
+ /* @__PURE__ */ jsx15("circle", { cx: "9", cy: "7", r: "4" }),
7740
+ /* @__PURE__ */ jsx15("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }),
7741
+ /* @__PURE__ */ jsx15("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
7742
+ ] });
7743
+ }
7744
+ if (type === "\u041E\u0431\u043E\u0440\u0443\u0434\u043E\u0432\u0430\u043D\u0438\u0435") {
7745
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconEquipment", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7746
+ /* @__PURE__ */ jsx15("path", { d: "m15 12-9.373 9.373a1 1 0 0 1-3.001-3L12 9" }),
7747
+ /* @__PURE__ */ jsx15("path", { d: "m18 15 4-4" }),
7748
+ /* @__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" })
7749
+ ] });
7750
+ }
7751
+ if (type === "\u041C\u0430\u0442\u0435\u0440\u0438\u0430\u043B\u044B") {
7752
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconMaterials", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7753
+ /* @__PURE__ */ jsx15("path", { d: "M10 22v-8" }),
7754
+ /* @__PURE__ */ jsx15("path", { d: "M2.336 8.89 10 14l11.715-7.029" }),
7755
+ /* @__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" })
7756
+ ] });
7757
+ }
7758
+ return /* @__PURE__ */ jsxs12("svg", { className: "gantt-resourceTimeline-resourceTypeIcon gantt-resourceTimeline-resourceTypeIconOther", width: "16", height: "16", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
7759
+ /* @__PURE__ */ jsx15("circle", { cx: "12", cy: "12", r: "9" }),
7760
+ /* @__PURE__ */ jsx15("path", { d: "M12 8v4" }),
7761
+ /* @__PURE__ */ jsx15("path", { d: "M12 16h.01" })
7762
+ ] });
7763
+ };
7723
7764
  var ResourceHeader = ({
7724
7765
  resource,
7725
7766
  resourceId,
7767
+ rowIndex,
7726
7768
  conflictCount,
7769
+ workedDays,
7770
+ assignmentCount,
7727
7771
  height,
7728
7772
  paddingBottom,
7729
- menuCommands
7773
+ menuCommands,
7774
+ onResourceChange,
7775
+ onConflictBadgeClick
7730
7776
  }) => {
7731
7777
  const [menuOpen, setMenuOpen] = useState8(false);
7778
+ const [typeMenuOpen, setTypeMenuOpen] = useState8(false);
7779
+ const [scopeMenuOpen, setScopeMenuOpen] = useState8(false);
7780
+ const [draftName, setDraftName] = useState8(resource.name);
7732
7781
  const visibleCommands = useMemo9(
7733
7782
  () => menuCommands.filter((command) => command.isVisible?.(resource) ?? true),
7734
7783
  [menuCommands, resource]
7735
7784
  );
7736
7785
  const hasMenu = visibleCommands.length > 0;
7786
+ const type = resource.type ?? "\u0414\u0440\u0443\u0433\u043E\u0435";
7787
+ const scope = resource.scope ?? "Project";
7788
+ const scopeLabel = RESOURCE_SCOPE_LABELS[scope] ?? scope;
7789
+ useEffect8(() => {
7790
+ setDraftName(resource.name);
7791
+ }, [resource.name]);
7792
+ const applyResourcePatch = useCallback7((patch) => {
7793
+ onResourceChange?.({ ...resource, ...patch });
7794
+ }, [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 });
7803
+ }
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();
7812
+ }
7813
+ }, [resource.name]);
7737
7814
  const handleCommandClick = (command, event) => {
7738
7815
  event.stopPropagation();
7739
7816
  if (command.closeOnSelect !== false) {
@@ -7751,44 +7828,154 @@ var ResourceHeader = ({
7751
7828
  paddingBottom: `${paddingBottom}px`
7752
7829
  },
7753
7830
  children: [
7754
- /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceName", children: resource.name }),
7755
- conflictCount > 0 && /* @__PURE__ */ jsx15(
7756
- "span",
7757
- {
7758
- className: "gantt-resourceTimeline-conflictBadge",
7759
- "aria-label": formatOverlapCount(conflictCount),
7760
- title: formatOverlapCount(conflictCount),
7761
- children: conflictCount
7762
- }
7763
- ),
7764
- hasMenu && /* @__PURE__ */ jsxs12(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
7831
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceNumber", children: rowIndex + 1 }),
7832
+ /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceName", children: [
7833
+ /* @__PURE__ */ jsxs12(Popover, { open: typeMenuOpen, onOpenChange: setTypeMenuOpen, children: [
7834
+ /* @__PURE__ */ jsx15(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx15(
7835
+ "button",
7836
+ {
7837
+ type: "button",
7838
+ className: `gantt-resourceTimeline-resourceTypeIconButton gantt-resourceTimeline-resourceTypeIconButton${RESOURCE_TYPE_CLASS_NAMES[type] ?? "Other"}`,
7839
+ disabled: !onResourceChange,
7840
+ "aria-label": `\u0422\u0438\u043F \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}: ${type}`,
7841
+ title: type,
7842
+ onClick: (event) => {
7843
+ event.stopPropagation();
7844
+ setTypeMenuOpen((open) => !open);
7845
+ },
7846
+ children: /* @__PURE__ */ jsx15(ResourceTypeIcon, { type })
7847
+ }
7848
+ ) }),
7849
+ /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceOptionMenu", portal: true, align: "start", children: RESOURCE_TYPE_OPTIONS.map((option) => /* @__PURE__ */ jsxs12(
7850
+ "button",
7851
+ {
7852
+ type: "button",
7853
+ className: `gantt-resourceTimeline-resourceOption${type === option ? " gantt-resourceTimeline-resourceOptionActive" : ""}`,
7854
+ onClick: (event) => {
7855
+ event.stopPropagation();
7856
+ setTypeMenuOpen(false);
7857
+ applyResourcePatch({ type: option });
7858
+ },
7859
+ children: [
7860
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceOptionIcon", children: /* @__PURE__ */ jsx15(ResourceTypeIcon, { type: option }) }),
7861
+ option
7862
+ ]
7863
+ },
7864
+ option
7865
+ )) })
7866
+ ] }),
7867
+ /* @__PURE__ */ jsx15(
7868
+ "textarea",
7869
+ {
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,
7877
+ onKeyDown: handleNameKeyDown,
7878
+ onClick: (event) => event.stopPropagation()
7879
+ }
7880
+ )
7881
+ ] }),
7882
+ /* @__PURE__ */ jsxs12(Popover, { open: scopeMenuOpen, onOpenChange: setScopeMenuOpen, children: [
7765
7883
  /* @__PURE__ */ jsx15(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx15(
7766
7884
  "button",
7767
7885
  {
7768
- className: "gantt-resourceTimeline-resourceMenuButton",
7769
7886
  type: "button",
7770
- "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
7887
+ className: `gantt-resourceTimeline-resourceScopeChip gantt-resourceTimeline-resourceScope${scope === "Shared" ? "Shared" : "Project"}`,
7888
+ disabled: !onResourceChange,
7889
+ "aria-label": `\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u044C \u0440\u0435\u0441\u0443\u0440\u0441\u0430 ${resource.name}`,
7771
7890
  onClick: (event) => {
7772
7891
  event.stopPropagation();
7773
- setMenuOpen((open) => !open);
7892
+ setScopeMenuOpen((open) => !open);
7774
7893
  },
7775
- children: /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u22EE" })
7894
+ children: /* @__PURE__ */ jsx15("span", { children: scopeLabel })
7776
7895
  }
7777
7896
  ) }),
7778
- /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceMenu", portal: true, align: "end", children: visibleCommands.map((command) => /* @__PURE__ */ jsxs12(
7897
+ /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceOptionMenu", portal: true, align: "start", children: RESOURCE_SCOPE_OPTIONS.map((option) => /* @__PURE__ */ jsxs12(
7779
7898
  "button",
7780
7899
  {
7781
7900
  type: "button",
7782
- className: `gantt-resourceTimeline-resourceMenuItem${command.danger ? " gantt-resourceTimeline-resourceMenuItemDanger" : ""}`,
7783
- disabled: command.isDisabled?.(resource) ?? false,
7784
- onClick: (event) => handleCommandClick(command, event),
7901
+ className: `gantt-resourceTimeline-resourceOption${scope === option ? " gantt-resourceTimeline-resourceOptionActive" : ""}`,
7902
+ onClick: (event) => {
7903
+ event.stopPropagation();
7904
+ setScopeMenuOpen(false);
7905
+ applyResourcePatch({ scope: option });
7906
+ },
7785
7907
  children: [
7786
- command.icon,
7787
- command.label
7908
+ /* @__PURE__ */ jsx15("span", { className: `gantt-resourceTimeline-resourceOptionScopeDot gantt-resourceTimeline-resourceScope${option}` }),
7909
+ RESOURCE_SCOPE_LABELS[option] ?? option
7788
7910
  ]
7789
7911
  },
7790
- command.id
7912
+ option
7791
7913
  )) })
7914
+ ] }),
7915
+ /* @__PURE__ */ jsxs12(
7916
+ "span",
7917
+ {
7918
+ className: "gantt-resourceTimeline-resourceAssignments",
7919
+ "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
+ children: [
7921
+ /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceWorkedDays", children: [
7922
+ workedDays,
7923
+ " \u0434\u043D."
7924
+ ] }),
7925
+ /* @__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: [
7927
+ /* @__PURE__ */ jsx15("rect", { width: "15", height: "5", x: "4", y: "5", rx: "2" }),
7928
+ /* @__PURE__ */ jsx15("rect", { width: "10", height: "5", x: "4", y: "14", rx: "2" })
7929
+ ] }),
7930
+ /* @__PURE__ */ jsx15("span", { children: assignmentCount })
7931
+ ] })
7932
+ ]
7933
+ }
7934
+ ),
7935
+ /* @__PURE__ */ jsxs12("span", { className: "gantt-resourceTimeline-resourceActions", children: [
7936
+ conflictCount > 0 && /* @__PURE__ */ jsx15(
7937
+ "button",
7938
+ {
7939
+ type: "button",
7940
+ className: "gantt-resourceTimeline-conflictBadge",
7941
+ "aria-label": formatOverlapCount(conflictCount),
7942
+ title: formatOverlapCount(conflictCount),
7943
+ onClick: (event) => {
7944
+ event.stopPropagation();
7945
+ onConflictBadgeClick?.(resourceId);
7946
+ },
7947
+ children: conflictCount
7948
+ }
7949
+ ),
7950
+ hasMenu && /* @__PURE__ */ jsxs12(Popover, { open: menuOpen, onOpenChange: setMenuOpen, children: [
7951
+ /* @__PURE__ */ jsx15(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx15(
7952
+ "button",
7953
+ {
7954
+ className: "gantt-resourceTimeline-resourceMenuButton",
7955
+ type: "button",
7956
+ "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
7957
+ onClick: (event) => {
7958
+ event.stopPropagation();
7959
+ setMenuOpen((open) => !open);
7960
+ },
7961
+ children: /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u22EE" })
7962
+ }
7963
+ ) }),
7964
+ /* @__PURE__ */ jsx15(PopoverContent, { className: "gantt-resourceTimeline-resourceMenu", portal: true, align: "end", children: visibleCommands.map((command) => /* @__PURE__ */ jsxs12(
7965
+ "button",
7966
+ {
7967
+ type: "button",
7968
+ className: `gantt-resourceTimeline-resourceMenuItem${command.danger ? " gantt-resourceTimeline-resourceMenuItemDanger" : ""}`,
7969
+ disabled: command.isDisabled?.(resource) ?? false,
7970
+ onClick: (event) => handleCommandClick(command, event),
7971
+ children: [
7972
+ command.icon,
7973
+ command.label
7974
+ ]
7975
+ },
7976
+ command.id
7977
+ )) })
7978
+ ] })
7792
7979
  ] })
7793
7980
  ]
7794
7981
  }
@@ -7827,7 +8014,7 @@ var NewResourceRow = ({ height, onConfirm, onCancel }) => {
7827
8014
  handleConfirm();
7828
8015
  }
7829
8016
  };
7830
- return /* @__PURE__ */ jsx15(
8017
+ return /* @__PURE__ */ jsxs12(
7831
8018
  "div",
7832
8019
  {
7833
8020
  className: "gantt-resourceTimeline-resourceHeader gantt-resourceTimeline-resourceHeaderNew",
@@ -7835,18 +8022,21 @@ var NewResourceRow = ({ height, onConfirm, onCancel }) => {
7835
8022
  height: `${height}px`,
7836
8023
  paddingBottom: `${DEFAULT_RESOURCE_ROW_GAP}px`
7837
8024
  },
7838
- children: /* @__PURE__ */ jsx15(
7839
- Input,
7840
- {
7841
- ref: inputRef,
7842
- value: nameValue,
7843
- onChange: (event) => setNameValue(event.target.value),
7844
- onKeyDown: handleKeyDown,
7845
- onBlur: handleBlur,
7846
- placeholder: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
7847
- className: "gantt-resourceTimeline-resourceInput"
7848
- }
7849
- )
8025
+ children: [
8026
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceNumber", children: "+" }),
8027
+ /* @__PURE__ */ jsx15(
8028
+ Input,
8029
+ {
8030
+ ref: inputRef,
8031
+ value: nameValue,
8032
+ onChange: (event) => setNameValue(event.target.value),
8033
+ onKeyDown: handleKeyDown,
8034
+ onBlur: handleBlur,
8035
+ placeholder: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u0430",
8036
+ className: "gantt-resourceTimeline-resourceInput"
8037
+ }
8038
+ )
8039
+ ]
7850
8040
  }
7851
8041
  );
7852
8042
  };
@@ -7867,7 +8057,10 @@ function ResourceTimelineChart({
7867
8057
  renderItem,
7868
8058
  getItemClassName,
7869
8059
  onResourceItemClick,
8060
+ onResourceItemMenuClick,
8061
+ activeResourceItemId,
7870
8062
  onResourceItemMove,
8063
+ onResourceChange,
7871
8064
  onAddResource,
7872
8065
  enableAddResource = true,
7873
8066
  resourceMenuCommands = []
@@ -7875,7 +8068,10 @@ function ResourceTimelineChart({
7875
8068
  const scrollContainerRef = useRef8(null);
7876
8069
  const gridRef = useRef8(null);
7877
8070
  const panStateRef = useRef8(null);
8071
+ const conflictNavigationIndexRef = useRef8(/* @__PURE__ */ new Map());
8072
+ const conflictHighlightTimeoutRef = useRef8(null);
7878
8073
  const [isCreatingResource, setIsCreatingResource] = useState8(false);
8074
+ const [activeConflictItemId, setActiveConflictItemId] = useState8(null);
7879
8075
  const validItems = useMemo9(() => collectValidItems(resources), [resources]);
7880
8076
  const dateRange = useMemo9(() => getMultiMonthDays(validItems), [validItems]);
7881
8077
  const monthStart = useMemo9(() => {
@@ -7887,6 +8083,26 @@ function ResourceTimelineChart({
7887
8083
  [customDays, isWeekend3]
7888
8084
  );
7889
8085
  const gridWidth = useMemo9(() => Math.round(dateRange.length * dayWidth), [dateRange.length, dayWidth]);
8086
+ const effectiveRowHeaderWidth = Math.max(rowHeaderWidth, DEFAULT_ROW_HEADER_WIDTH);
8087
+ const workedDaysByResourceId = useMemo9(() => {
8088
+ const map = /* @__PURE__ */ new Map();
8089
+ for (const resource of resources) {
8090
+ const workedDays = resource.items.reduce((sum, item) => {
8091
+ try {
8092
+ const startDate = parseUTCDate(item.startDate);
8093
+ const endDate = parseUTCDate(item.endDate);
8094
+ if (!isValidDate(startDate) || !isValidDate(endDate)) {
8095
+ return sum;
8096
+ }
8097
+ return sum + getDurationValue(startDate, endDate, businessDays, weekendPredicate);
8098
+ } catch {
8099
+ return sum;
8100
+ }
8101
+ }, 0);
8102
+ map.set(resource.id, workedDays);
8103
+ }
8104
+ return map;
8105
+ }, [businessDays, resources, weekendPredicate]);
7890
8106
  const layout = useMemo9(
7891
8107
  () => layoutResourceTimelineItems(resources, {
7892
8108
  monthStart,
@@ -7910,6 +8126,23 @@ function ResourceTimelineChart({
7910
8126
  }
7911
8127
  return map;
7912
8128
  }, [layout.items]);
8129
+ const conflictItemsByResourceId = useMemo9(() => {
8130
+ const map = /* @__PURE__ */ new Map();
8131
+ for (const item of layout.items) {
8132
+ if (item.conflictRanges.length === 0) {
8133
+ continue;
8134
+ }
8135
+ const next = map.get(item.resourceId) ?? [];
8136
+ next.push(item);
8137
+ map.set(item.resourceId, next);
8138
+ }
8139
+ for (const items of map.values()) {
8140
+ items.sort(
8141
+ (left, right) => left.top - right.top || left.left - right.left || left.itemId.localeCompare(right.itemId)
8142
+ );
8143
+ }
8144
+ return map;
8145
+ }, [layout.items]);
7913
8146
  const canAddResource = enableAddResource && Boolean(onAddResource);
7914
8147
  const resourceAddRowHeight = laneHeight + DEFAULT_RESOURCE_ROW_GAP;
7915
8148
  const displayTotalHeight = layout.totalHeight + (canAddResource ? resourceAddRowHeight : 0);
@@ -7917,11 +8150,38 @@ function ResourceTimelineChart({
7917
8150
  onAddResource?.({
7918
8151
  id: crypto.randomUUID(),
7919
8152
  name,
8153
+ type: "\u0414\u0440\u0443\u0433\u043E\u0435",
8154
+ scope: "Project",
7920
8155
  items: []
7921
8156
  });
7922
8157
  setIsCreatingResource(false);
7923
8158
  }, [onAddResource]);
7924
8159
  const handleCancelNewResource = useCallback7(() => setIsCreatingResource(false), []);
8160
+ const handleConflictBadgeClick = useCallback7((resourceId) => {
8161
+ const conflictItems = conflictItemsByResourceId.get(resourceId) ?? [];
8162
+ if (conflictItems.length === 0) {
8163
+ return;
8164
+ }
8165
+ const currentIndex = conflictNavigationIndexRef.current.get(resourceId) ?? 0;
8166
+ const target = conflictItems[currentIndex % conflictItems.length];
8167
+ conflictNavigationIndexRef.current.set(resourceId, (currentIndex + 1) % conflictItems.length);
8168
+ const container = scrollContainerRef.current;
8169
+ if (container) {
8170
+ container.scrollTo({
8171
+ left: Math.max(0, Math.round(target.left - dayWidth * 2)),
8172
+ top: Math.max(0, Math.round(target.top - laneHeight)),
8173
+ behavior: "smooth"
8174
+ });
8175
+ }
8176
+ setActiveConflictItemId(target.itemId);
8177
+ if (conflictHighlightTimeoutRef.current) {
8178
+ window.clearTimeout(conflictHighlightTimeoutRef.current);
8179
+ }
8180
+ conflictHighlightTimeoutRef.current = window.setTimeout(() => {
8181
+ setActiveConflictItemId((current) => current === target.itemId ? null : current);
8182
+ conflictHighlightTimeoutRef.current = null;
8183
+ }, 1600);
8184
+ }, [conflictItemsByResourceId, dayWidth, laneHeight]);
7925
8185
  const { preview, startDrag } = useResourceItemDrag({
7926
8186
  dayWidth,
7927
8187
  monthStart,
@@ -7990,6 +8250,13 @@ function ResourceTimelineChart({
7990
8250
  window.removeEventListener("mouseup", handlePanEnd);
7991
8251
  };
7992
8252
  }, [allowVerticalPan]);
8253
+ useEffect8(() => {
8254
+ return () => {
8255
+ if (conflictHighlightTimeoutRef.current) {
8256
+ window.clearTimeout(conflictHighlightTimeoutRef.current);
8257
+ }
8258
+ };
8259
+ }, []);
7993
8260
  return /* @__PURE__ */ jsx15("div", { className: "gantt-container gantt-resourceTimeline", children: /* @__PURE__ */ jsx15(
7994
8261
  "div",
7995
8262
  {
@@ -8007,24 +8274,36 @@ function ResourceTimelineChart({
8007
8274
  "div",
8008
8275
  {
8009
8276
  className: "gantt-resourceTimeline-resourceColumn",
8010
- style: { width: `${rowHeaderWidth}px`, minWidth: `${rowHeaderWidth}px` },
8277
+ style: { width: `${effectiveRowHeaderWidth}px`, minWidth: `${effectiveRowHeaderWidth}px` },
8011
8278
  children: [
8012
- /* @__PURE__ */ jsx15(
8279
+ /* @__PURE__ */ jsxs12(
8013
8280
  "div",
8014
8281
  {
8015
8282
  className: "gantt-resourceTimeline-corner",
8016
- style: { height: `${headerHeight}px` }
8283
+ style: { height: `${headerHeight + 0.5}px` },
8284
+ children: [
8285
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderNumber", children: "#" }),
8286
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderName", children: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435" }),
8287
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell", "aria-label": "\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u044C" }),
8288
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell", children: "\u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F" }),
8289
+ /* @__PURE__ */ jsx15("span", { className: "gantt-resourceTimeline-resourceHeaderCell gantt-resourceTimeline-resourceHeaderActions", "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F" })
8290
+ ]
8017
8291
  }
8018
8292
  ),
8019
- layout.rows.map((row) => /* @__PURE__ */ jsx15(
8293
+ layout.rows.map((row, rowIndex) => /* @__PURE__ */ jsx15(
8020
8294
  ResourceHeader,
8021
8295
  {
8022
8296
  resource: row.resource,
8023
8297
  resourceId: row.resourceId,
8298
+ rowIndex,
8024
8299
  conflictCount: row.conflictCount,
8300
+ workedDays: workedDaysByResourceId.get(row.resourceId) ?? 0,
8301
+ assignmentCount: row.resource.items.length,
8025
8302
  height: row.resourceRowHeight + DEFAULT_RESOURCE_ROW_GAP,
8026
8303
  paddingBottom: DEFAULT_RESOURCE_ROW_GAP,
8027
- menuCommands: resourceMenuCommands
8304
+ menuCommands: resourceMenuCommands,
8305
+ onResourceChange,
8306
+ onConflictBadgeClick: handleConflictBadgeClick
8028
8307
  },
8029
8308
  row.resourceId
8030
8309
  )),
@@ -8109,9 +8388,14 @@ function ResourceTimelineChart({
8109
8388
  (resourceItems) => resourceItems.map((layoutItem) => {
8110
8389
  const customClassName = getItemClassName?.(layoutItem.item);
8111
8390
  const isDraggingItem = preview?.itemId === layoutItem.itemId;
8391
+ const hasItemMenu = Boolean(onResourceItemMenuClick);
8392
+ const isActiveItem = activeResourceItemId === layoutItem.itemId;
8112
8393
  const className = [
8113
8394
  "gantt-resourceTimeline-item",
8395
+ hasItemMenu && "gantt-resourceTimeline-itemHasMenu",
8396
+ isActiveItem && "gantt-resourceTimeline-itemActive",
8114
8397
  isDraggingItem && "gantt-resourceTimeline-itemDragging",
8398
+ activeConflictItemId === layoutItem.itemId && "gantt-resourceTimeline-itemConflictActive",
8115
8399
  (readonly || layoutItem.item.locked) && "gantt-resourceTimeline-itemDisabled",
8116
8400
  customClassName
8117
8401
  ].filter(Boolean).join(" ");
@@ -8210,6 +8494,22 @@ function ResourceTimelineChart({
8210
8494
  },
8211
8495
  `conflict-overlay-${index}`
8212
8496
  )),
8497
+ hasItemMenu && /* @__PURE__ */ jsx15(
8498
+ "button",
8499
+ {
8500
+ type: "button",
8501
+ className: "gantt-resourceTimeline-itemMenuButton",
8502
+ "aria-label": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F",
8503
+ onMouseDown: (event) => {
8504
+ event.stopPropagation();
8505
+ },
8506
+ onClick: (event) => {
8507
+ event.stopPropagation();
8508
+ onResourceItemMenuClick?.(layoutItem.item);
8509
+ },
8510
+ children: /* @__PURE__ */ jsx15("span", { "aria-hidden": "true", children: "\u22EE" })
8511
+ }
8512
+ ),
8213
8513
  /* @__PURE__ */ jsx15("div", { className: "gantt-resourceTimeline-itemInner", children: renderItem ? renderItem(layoutItem.item, renderContext) : /* @__PURE__ */ jsxs12("div", { className: "gantt-resourceTimeline-defaultItemContent", children: [
8214
8514
  /* @__PURE__ */ jsxs12("div", { className: "gantt-resourceTimeline-defaultItemMain", children: [
8215
8515
  /* @__PURE__ */ jsx15(