@snksergio/design-system 0.58.0 → 0.61.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.
Files changed (92) hide show
  1. package/dist-lib/index.cjs +4461 -16
  2. package/dist-lib/index.cjs.map +1 -1
  3. package/dist-lib/index.mjs +4477 -32
  4. package/dist-lib/index.mjs.map +1 -1
  5. package/dist-lib/src/components/index.d.ts +1 -0
  6. package/dist-lib/src/components/index.d.ts.map +1 -1
  7. package/dist-lib/src/components/ui/Gantt/gantt.d.ts +21 -0
  8. package/dist-lib/src/components/ui/Gantt/gantt.d.ts.map +1 -0
  9. package/dist-lib/src/components/ui/Gantt/gantt.styles.d.ts +2985 -0
  10. package/dist-lib/src/components/ui/Gantt/gantt.styles.d.ts.map +1 -0
  11. package/dist-lib/src/components/ui/Gantt/gantt.types.d.ts +445 -0
  12. package/dist-lib/src/components/ui/Gantt/gantt.types.d.ts.map +1 -0
  13. package/dist-lib/src/components/ui/Gantt/hooks/drag.d.ts +71 -0
  14. package/dist-lib/src/components/ui/Gantt/hooks/drag.d.ts.map +1 -0
  15. package/dist-lib/src/components/ui/Gantt/hooks/filters.d.ts +79 -0
  16. package/dist-lib/src/components/ui/Gantt/hooks/filters.d.ts.map +1 -0
  17. package/dist-lib/src/components/ui/Gantt/hooks/layout.d.ts +232 -0
  18. package/dist-lib/src/components/ui/Gantt/hooks/layout.d.ts.map +1 -0
  19. package/dist-lib/src/components/ui/Gantt/hooks/links.d.ts +120 -0
  20. package/dist-lib/src/components/ui/Gantt/hooks/links.d.ts.map +1 -0
  21. package/dist-lib/src/components/ui/Gantt/hooks/week-segments.d.ts +69 -0
  22. package/dist-lib/src/components/ui/Gantt/hooks/week-segments.d.ts.map +1 -0
  23. package/dist-lib/src/components/ui/Gantt/index.d.ts +46 -0
  24. package/dist-lib/src/components/ui/Gantt/index.d.ts.map +1 -0
  25. package/dist-lib/src/components/ui/Gantt/parts/gantt-applied-filters.d.ts +20 -0
  26. package/dist-lib/src/components/ui/Gantt/parts/gantt-applied-filters.d.ts.map +1 -0
  27. package/dist-lib/src/components/ui/Gantt/parts/gantt-bar.d.ts +68 -0
  28. package/dist-lib/src/components/ui/Gantt/parts/gantt-bar.d.ts.map +1 -0
  29. package/dist-lib/src/components/ui/Gantt/parts/gantt-filter-panel.d.ts +16 -0
  30. package/dist-lib/src/components/ui/Gantt/parts/gantt-filter-panel.d.ts.map +1 -0
  31. package/dist-lib/src/components/ui/Gantt/parts/gantt-grid.d.ts +55 -0
  32. package/dist-lib/src/components/ui/Gantt/parts/gantt-grid.d.ts.map +1 -0
  33. package/dist-lib/src/components/ui/Gantt/parts/gantt-links-layer.d.ts +46 -0
  34. package/dist-lib/src/components/ui/Gantt/parts/gantt-links-layer.d.ts.map +1 -0
  35. package/dist-lib/src/components/ui/Gantt/parts/gantt-skeleton.d.ts +36 -0
  36. package/dist-lib/src/components/ui/Gantt/parts/gantt-skeleton.d.ts.map +1 -0
  37. package/dist-lib/src/components/ui/Gantt/parts/gantt-toolbar.d.ts +46 -0
  38. package/dist-lib/src/components/ui/Gantt/parts/gantt-toolbar.d.ts.map +1 -0
  39. package/dist-lib/src/components/ui/Gantt/views/calendar.d.ts +64 -0
  40. package/dist-lib/src/components/ui/Gantt/views/calendar.d.ts.map +1 -0
  41. package/dist-lib/src/components/ui/Gantt/views/list.d.ts +53 -0
  42. package/dist-lib/src/components/ui/Gantt/views/list.d.ts.map +1 -0
  43. package/dist-lib/src/components/ui/Gantt/views/timeline.d.ts +85 -0
  44. package/dist-lib/src/components/ui/Gantt/views/timeline.d.ts.map +1 -0
  45. package/dist-lib/src/components/ui/MessageBubble/index.d.ts +1 -1
  46. package/dist-lib/src/components/ui/MessageBubble/index.d.ts.map +1 -1
  47. package/dist-lib/src/components/ui/MessageBubble/message-bubble.d.ts +1 -1
  48. package/dist-lib/src/components/ui/MessageBubble/message-bubble.d.ts.map +1 -1
  49. package/dist-lib/src/components/ui/MessageBubble/message-bubble.styles.d.ts +22 -2
  50. package/dist-lib/src/components/ui/MessageBubble/message-bubble.styles.d.ts.map +1 -1
  51. package/dist-lib/src/components/ui/MessageBubble/message-bubble.types.d.ts +8 -0
  52. package/dist-lib/src/components/ui/MessageBubble/message-bubble.types.d.ts.map +1 -1
  53. package/dist-lib/theme/brand-pay.css +205 -189
  54. package/dist-lib/theme/brand-vibrant.css +162 -148
  55. package/dist-lib/theme.css +19 -0
  56. package/dist-lib/tokens/brands/blue/primitives/color-palette.d.ts +28 -0
  57. package/dist-lib/tokens/brands/blue/primitives/color-palette.d.ts.map +1 -1
  58. package/dist-lib/tokens/brands/blue/semantic/color-dark.d.ts +16 -0
  59. package/dist-lib/tokens/brands/blue/semantic/color-dark.d.ts.map +1 -1
  60. package/dist-lib/tokens/brands/blue/semantic/color-light.d.ts +16 -0
  61. package/dist-lib/tokens/brands/blue/semantic/color-light.d.ts.map +1 -1
  62. package/dist-lib/tokens/brands/default/primitives/color-palette.d.ts +29 -0
  63. package/dist-lib/tokens/brands/default/primitives/color-palette.d.ts.map +1 -1
  64. package/dist-lib/tokens/brands/default/semantic/color-dark.d.ts +16 -0
  65. package/dist-lib/tokens/brands/default/semantic/color-dark.d.ts.map +1 -1
  66. package/dist-lib/tokens/brands/default/semantic/color-light.d.ts +16 -0
  67. package/dist-lib/tokens/brands/default/semantic/color-light.d.ts.map +1 -1
  68. package/dist-lib/tokens/brands/default/semantic/elevation.d.ts +4 -0
  69. package/dist-lib/tokens/brands/default/semantic/elevation.d.ts.map +1 -1
  70. package/dist-lib/tokens/brands/green/primitives/color-palette.d.ts +28 -0
  71. package/dist-lib/tokens/brands/green/primitives/color-palette.d.ts.map +1 -1
  72. package/dist-lib/tokens/brands/green/semantic/color-dark.d.ts +16 -0
  73. package/dist-lib/tokens/brands/green/semantic/color-dark.d.ts.map +1 -1
  74. package/dist-lib/tokens/brands/green/semantic/color-light.d.ts +16 -0
  75. package/dist-lib/tokens/brands/green/semantic/color-light.d.ts.map +1 -1
  76. package/dist-lib/tokens/brands/pay/primitives/color-palette.d.ts +6 -0
  77. package/dist-lib/tokens/brands/pay/primitives/color-palette.d.ts.map +1 -1
  78. package/dist-lib/tokens/brands/pay/semantic/color-dark.d.ts +16 -0
  79. package/dist-lib/tokens/brands/pay/semantic/color-dark.d.ts.map +1 -1
  80. package/dist-lib/tokens/brands/pay/semantic/color-light.d.ts +16 -0
  81. package/dist-lib/tokens/brands/pay/semantic/color-light.d.ts.map +1 -1
  82. package/dist-lib/tokens/brands/vibrant/primitives/color-palette.d.ts +29 -1
  83. package/dist-lib/tokens/brands/vibrant/primitives/color-palette.d.ts.map +1 -1
  84. package/dist-lib/tokens/brands/vibrant/semantic/color-dark.d.ts +16 -0
  85. package/dist-lib/tokens/brands/vibrant/semantic/color-dark.d.ts.map +1 -1
  86. package/dist-lib/tokens/brands/vibrant/semantic/color-light.d.ts +16 -0
  87. package/dist-lib/tokens/brands/vibrant/semantic/color-light.d.ts.map +1 -1
  88. package/dist-lib/tokens.cjs +23 -2
  89. package/dist-lib/tokens.cjs.map +1 -1
  90. package/dist-lib/tokens.mjs +23 -2
  91. package/dist-lib/tokens.mjs.map +1 -1
  92. package/package.json +1 -1
@@ -4289,6 +4289,7 @@ const messageBubbleStyles = avatar.tv({
4289
4289
  /* ações (hover) */
4290
4290
  actionsTrigger: [
4291
4291
  "absolute right-pad-md top-pad-md",
4292
+ "bg-bg-emphasis shadow-sh-sm hover:bg-bg-accent-hover",
4292
4293
  "opacity-0 transition-opacity",
4293
4294
  "group-hover/bubble:opacity-100 focus-visible:opacity-100"
4294
4295
  ],
@@ -4304,12 +4305,16 @@ const messageBubbleStyles = avatar.tv({
4304
4305
  received: {
4305
4306
  row: "flex-row",
4306
4307
  column: "items-start",
4307
- bubble: "bg-bg-surface"
4308
+ bubble: "bg-bg-surface border-border-subtle"
4308
4309
  }
4309
4310
  },
4310
4311
  tail: {
4311
4312
  true: {},
4312
4313
  false: {}
4314
+ },
4315
+ origin: {
4316
+ human: {},
4317
+ ai: { bubble: "border-border-brand-subtle" }
4313
4318
  }
4314
4319
  },
4315
4320
  compoundVariants: [
@@ -4319,7 +4324,8 @@ const messageBubbleStyles = avatar.tv({
4319
4324
  ],
4320
4325
  defaultVariants: {
4321
4326
  side: "received",
4322
- tail: true
4327
+ tail: true,
4328
+ origin: "human"
4323
4329
  }
4324
4330
  });
4325
4331
  const styles = messageBubbleStyles();
@@ -4493,6 +4499,7 @@ function MessageBubble({
4493
4499
  isEdited = false,
4494
4500
  isDeleted = false,
4495
4501
  tail = true,
4502
+ origin,
4496
4503
  authorName,
4497
4504
  avatar: avatar$1,
4498
4505
  actions,
@@ -4500,7 +4507,7 @@ function MessageBubble({
4500
4507
  className
4501
4508
  }) {
4502
4509
  const [actionsOpen, setActionsOpen] = React.useState(false);
4503
- const s = messageBubbleStyles({ side, tail });
4510
+ const s = messageBubbleStyles({ side, tail, origin });
4504
4511
  const time = formatTime(createdAt);
4505
4512
  const hasMedia = !isDeleted && mediaType !== "text";
4506
4513
  const showAck = side === "sent" && ack !== void 0 && !isDeleted;
@@ -4518,7 +4525,8 @@ function MessageBubble({
4518
4525
  avatar.Button,
4519
4526
  {
4520
4527
  type: "button",
4521
- size: "2xs",
4528
+ size: "icon-xs",
4529
+ shape: "pill",
4522
4530
  variant: "ghost",
4523
4531
  color: "secondary",
4524
4532
  "aria-label": "Ações da mensagem",
@@ -5877,7 +5885,7 @@ const schedulerPlaceholder = avatar.tv({
5877
5885
  "p-sp-3xl text-center"
5878
5886
  ]
5879
5887
  });
5880
- const VIEW_ITEMS = [
5888
+ const VIEW_ITEMS$1 = [
5881
5889
  { value: "month", label: "Mês", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, {}) },
5882
5890
  { value: "week", label: "Semana", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Columns3, {}) },
5883
5891
  { value: "day", label: "Dia", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, {}) },
@@ -5904,7 +5912,7 @@ function SchedulerToolbar({
5904
5912
  primaryAction
5905
5913
  }) {
5906
5914
  const hasFilters = (filterFields?.length ?? 0) > 0;
5907
- const activeView = VIEW_ITEMS.find((v) => v.value === view) ?? VIEW_ITEMS[0];
5915
+ const activeView = VIEW_ITEMS$1.find((v) => v.value === view) ?? VIEW_ITEMS$1[0];
5908
5916
  const filterEngaged = filterPanelOpen || appliedCount > 0;
5909
5917
  const appliedFields = (filterFields ?? []).filter(
5910
5918
  (field) => (filterModel[field.id] ?? []).length > 0
@@ -5974,7 +5982,7 @@ function SchedulerToolbar({
5974
5982
  {
5975
5983
  value: view,
5976
5984
  onValueChange: (v) => onViewChange(v),
5977
- children: VIEW_ITEMS.map((item) => /* @__PURE__ */ jsxRuntime.jsx(avatar.DropdownMenuRadioItem, { value: item.value, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerViewMenuItem(), children: item.label }) }, item.value))
5985
+ children: VIEW_ITEMS$1.map((item) => /* @__PURE__ */ jsxRuntime.jsx(avatar.DropdownMenuRadioItem, { value: item.value, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerViewMenuItem(), children: item.label }) }, item.value))
5978
5986
  }
5979
5987
  ) })
5980
5988
  ] }),
@@ -6036,7 +6044,7 @@ function SchedulerToolbar({
6036
6044
  ] }) : null
6037
6045
  ] });
6038
6046
  }
6039
- function buildMonthMatrix(date, weekStartsOn, locale) {
6047
+ function buildMonthMatrix$1(date, weekStartsOn, locale) {
6040
6048
  const monthStart = dateFns.startOfMonth(date);
6041
6049
  const gridStart = dateFns.startOfWeek(monthStart, { weekStartsOn, locale });
6042
6050
  const days = [];
@@ -6080,7 +6088,7 @@ function segmentMultiDay(events, weekRows) {
6080
6088
  function effectiveEndMs(event, snapMinutes) {
6081
6089
  return event.start.getTime() === event.end.getTime() ? event.end.getTime() + snapMinutes * 6e4 : event.end.getTime();
6082
6090
  }
6083
- function packLanes(events, snapMinutes) {
6091
+ function packLanes$1(events, snapMinutes) {
6084
6092
  if (events.length === 0) return [];
6085
6093
  const sorted = [...events].sort((a, b) => {
6086
6094
  const startDiff = a.start.getTime() - b.start.getTime();
@@ -6105,9 +6113,9 @@ function packLanes(events, snapMinutes) {
6105
6113
  laneEnds[freeLane] = evEnd;
6106
6114
  }
6107
6115
  }
6108
- const laneCount = laneEnds.length;
6116
+ const laneCount2 = laneEnds.length;
6109
6117
  for (const ev of group) {
6110
- result.push({ event: ev, laneIndex: laneOf.get(ev), laneCount });
6118
+ result.push({ event: ev, laneIndex: laneOf.get(ev), laneCount: laneCount2 });
6111
6119
  }
6112
6120
  group = [];
6113
6121
  groupMaxEnd = -Infinity;
@@ -6125,7 +6133,7 @@ function packLanes(events, snapMinutes) {
6125
6133
  }
6126
6134
  const MONTH_PILL_HEIGHT_PX = 20;
6127
6135
  const MONTH_PILL_GAP_PX = 2;
6128
- function computeOverflow(dayEvents, availableHeight) {
6136
+ function computeOverflow$1(dayEvents, availableHeight) {
6129
6137
  if (dayEvents.length === 0) {
6130
6138
  return { visible: [], overflowCount: 0 };
6131
6139
  }
@@ -6213,7 +6221,7 @@ function MiniMonth({
6213
6221
  [selected, offset]
6214
6222
  );
6215
6223
  const weeks = React.useMemo(
6216
- () => buildMonthMatrix(cursor, weekStartsOn, locale),
6224
+ () => buildMonthMatrix$1(cursor, weekStartsOn, locale),
6217
6225
  [cursor, weekStartsOn, locale]
6218
6226
  );
6219
6227
  const daysWithEvents = React.useMemo(() => {
@@ -6832,7 +6840,7 @@ function SchedulerMonthView({
6832
6840
  podeMover
6833
6841
  }) {
6834
6842
  const weeks = React.useMemo(
6835
- () => buildMonthMatrix(date, weekStartsOn, locale),
6843
+ () => buildMonthMatrix$1(date, weekStartsOn, locale),
6836
6844
  [date, weekStartsOn, locale]
6837
6845
  );
6838
6846
  const teclado = useSchedulerKeyboard({
@@ -6912,7 +6920,7 @@ function SchedulerMonthView({
6912
6920
  todos.length <= maxPerCell ? maxPerCell : Math.max(0, maxPerCell - 1)
6913
6921
  ),
6914
6922
  overflowCount: todos.length <= maxPerCell ? 0 : todos.length - Math.max(0, maxPerCell - 1)
6915
- } : computeOverflow(todos, alturaDisponivel);
6923
+ } : computeOverflow$1(todos, alturaDisponivel);
6916
6924
  const outside = !dateFns.isSameMonth(day, date);
6917
6925
  const today = dateFns.isSameDay(day, now);
6918
6926
  const indiceDaCelula = weekIndex * 7 + week.indexOf(day);
@@ -7097,7 +7105,7 @@ function SchedulerTimeGrid({
7097
7105
  end: ev.end.getTime() > gridBottom.getTime() ? gridBottom : ev.end
7098
7106
  }
7099
7107
  }));
7100
- const lanes = packLanes(
7108
+ const lanes = packLanes$1(
7101
7109
  recortados.map((r) => r.clamped),
7102
7110
  snapMinutes
7103
7111
  );
@@ -7858,6 +7866,4407 @@ const Scheduler = React.forwardRef(
7858
7866
  );
7859
7867
  }
7860
7868
  );
7869
+ const GANTT_ROW_HEIGHT_PX = 48;
7870
+ const GANTT_BAR_HEIGHT_PX = 26;
7871
+ const GANTT_SUMMARY_BAR_HEIGHT_PX = 12;
7872
+ const GANTT_LANE_HEIGHT_PX = 30;
7873
+ const GANTT_HEAD_HEIGHT_PX = 84;
7874
+ const GANTT_HEAD_GROUP_PX = 42;
7875
+ const GANTT_HEAD_UNIT_PX = 42;
7876
+ const GANTT_GROUP_LABEL_MIN_PX = 96;
7877
+ const GANTT_PX_PER_DAY = {
7878
+ day: 46,
7879
+ week: 100 / 7,
7880
+ month: 240 / 30.4,
7881
+ quarter: 320 / 91
7882
+ };
7883
+ const GANTT_WINDOW_DAYS = {
7884
+ day: 60,
7885
+ week: 180,
7886
+ month: 730,
7887
+ quarter: 1825
7888
+ };
7889
+ const ganttRoot = avatar.tv({
7890
+ base: [
7891
+ "flex h-full min-h-0 w-full flex-col gap-gp-3xl",
7892
+ "text-fg-default"
7893
+ ]
7894
+ });
7895
+ const ganttToolbar = avatar.tv({
7896
+ base: [
7897
+ "flex flex-col gap-gp-xl",
7898
+ "lg:flex-row lg:flex-wrap lg:items-center lg:justify-between"
7899
+ ]
7900
+ });
7901
+ const ganttToolbarSide = avatar.tv({
7902
+ base: "flex min-w-0 items-center",
7903
+ variants: {
7904
+ slot: {
7905
+ leading: [
7906
+ "w-full justify-between gap-gp-sm md:gap-gp-lg",
7907
+ "lg:w-auto lg:flex-wrap lg:justify-start"
7908
+ ],
7909
+ trailing: "w-full gap-gp-md lg:w-auto lg:flex-wrap"
7910
+ }
7911
+ },
7912
+ defaultVariants: { slot: "trailing" }
7913
+ });
7914
+ const ganttTitle = avatar.tv({
7915
+ base: [
7916
+ // O ícone e o texto são um par; o `gap` aqui é DENTRO do par.
7917
+ "flex min-w-0 items-center gap-gp-md",
7918
+ // O vizinho da ESQUERDA é o `‹ Hoje ›`, e o título é TEXTO — sem borda nem
7919
+ // fundo pra criar respiro do lado dele. 6px somam com o gap do grupo e dão
7920
+ // 16px no desktop / 12px no mobile. (Enquanto o `‹ Hoje ›` vinha depois do
7921
+ // título, esta margem morava lá, pelo mesmo motivo espelhado.)
7922
+ "ml-sp-sm",
7923
+ "text-title-sm font-semibold text-fg-default",
7924
+ "[&>svg]:size-icon-md [&>svg]:shrink-0 [&>svg]:text-fg-muted"
7925
+ ]
7926
+ });
7927
+ const ganttTitleText = avatar.tv({
7928
+ base: "min-w-0 truncate first-letter:uppercase"
7929
+ });
7930
+ const ganttViewSwitch = avatar.tv({
7931
+ base: [
7932
+ // `p-[3px]` fica literal: a escala `sp` vai 2 → 4px e não tem 3.
7933
+ // `max-md:hidden` — em <md este controle mora no menu de opções, como o
7934
+ // toggle Kanban/Lista da tabela mora no menu de Configurações.
7935
+ "max-md:hidden inline-flex shrink-0 items-center gap-gp-2xs p-[3px]",
7936
+ "h-form-lg rounded-radius-lg bg-bg-muted"
7937
+ ]
7938
+ });
7939
+ const ganttViewSwitchButton = avatar.tv({
7940
+ base: [
7941
+ "grid h-[34px] min-w-[36px] place-items-center px-pad-md",
7942
+ "cursor-pointer rounded-radius-md border-0 bg-transparent outline-none",
7943
+ "text-body-sm font-normal text-fg-muted",
7944
+ "transition-[background-color,color,box-shadow] duration-150",
7945
+ "hover:text-fg-default",
7946
+ "focus-visible:shadow-sh-ring",
7947
+ "[&_svg]:size-[14px]"
7948
+ ],
7949
+ variants: {
7950
+ isActive: {
7951
+ true: "bg-bg-accent font-semibold text-fg-default shadow-sh-sm",
7952
+ false: ""
7953
+ }
7954
+ },
7955
+ defaultVariants: { isActive: false }
7956
+ });
7957
+ const ganttToolbarDivider = avatar.tv({
7958
+ // `mx-sp-sm` = 6px e `w-sp-px` = 1px, os mesmos valores do `toolbarDivider`
7959
+ // da tabela — que os escreve literais. `h-[24px]` continua literal: a escala
7960
+ // `form` vai 28 → 32 e não tem 24.
7961
+ // `max-md:hidden`: em <md os dois grupos que ele separa não estão na barra
7962
+ // (foram pro menu), e um divisor sem nada de um dos lados é um risco solto.
7963
+ base: "max-md:hidden mx-sp-sm h-[24px] w-sp-px shrink-0 self-center bg-border-default"
7964
+ });
7965
+ const ganttNavGroup = avatar.tv({
7966
+ base: [
7967
+ // `max-md:hidden` — 142px de `‹ Hoje ›` numa barra de 375px. Em <md ele
7968
+ // aparece dentro do menu de opções, em largura cheia.
7969
+ "max-md:hidden inline-flex shrink-0 items-center",
7970
+ "[&>*:focus-visible]:relative [&>*:focus-visible]:z-[1]",
7971
+ "[&>*:first-child]:!rounded-r-none",
7972
+ "[&>*:last-child]:!rounded-l-none",
7973
+ "[&>*:not(:first-child):not(:last-child)]:!rounded-none",
7974
+ "[&>*]:border-y-border-subtle dark:[&>*]:border-y-border-input",
7975
+ "[&>*]:border-l-border-subtle dark:[&>*]:border-l-border-input",
7976
+ "[&>*:last-child]:border-r-border-subtle dark:[&>*:last-child]:border-r-border-input"
7977
+ ]
7978
+ });
7979
+ const ganttMobileMenu = avatar.tv({
7980
+ slots: {
7981
+ trigger: "shrink-0 md:hidden",
7982
+ /*
7983
+ Sem largura de propósito. O `PopoverContent` do DS já carrega
7984
+ `data-mobile-sheet` + `max-md:w-full max-md:max-w-none`, e a regra do tema
7985
+ (`@media (width < 768px)`) prende o wrapper do Popper no rodapé com
7986
+ `width: 100% !important` — este menu vira bottom-sheet sozinho.
7987
+
7988
+ MEDIDO: um `w-[280px] max-w-[calc(100vw-32px)]` que eu havia escrito aqui
7989
+ resolvia 375px na tela, ou seja, era declaração morta. E não teria como
7990
+ valer acima de `md`: o `trigger` é `md:hidden`, então o popover nunca
7991
+ abre onde a largura importaria.
7992
+ */
7993
+ content: "flex flex-col gap-px p-pad-sm",
7994
+ label: "px-pad-lg py-pad-sm text-caption-sm font-semibold uppercase tracking-wider text-fg-subtle",
7995
+ separator: "mx-pad-xs my-pad-xs h-px bg-border-default",
7996
+ field: "px-pad-lg pb-pad-sm",
7997
+ item: [
7998
+ "group/row relative flex w-full items-center gap-pad-lg",
7999
+ "rounded-radius-sm px-pad-lg py-pad-md",
8000
+ "cursor-pointer border-0 bg-transparent text-left outline-none",
8001
+ "text-body-sm font-medium text-fg-muted [&_svg]:text-fg-muted",
8002
+ "transition-colors duration-150",
8003
+ "hover:bg-bg-muted hover:text-fg-default hover:[&_svg]:text-fg-default",
8004
+ "focus-visible:bg-bg-muted focus-visible:text-fg-default",
8005
+ "[&_svg]:size-icon-sm [&_svg]:shrink-0"
8006
+ ]
8007
+ },
8008
+ variants: {
8009
+ active: {
8010
+ true: { item: "bg-bg-brand-subtle text-fg-brand [&_svg]:text-fg-brand" },
8011
+ false: {}
8012
+ }
8013
+ },
8014
+ defaultVariants: { active: false }
8015
+ });
8016
+ const ganttMenuGroupFluid = avatar.tv({
8017
+ base: "flex w-full [&>*]:flex-1"
8018
+ });
8019
+ const ganttSkeleton = avatar.tv({
8020
+ slots: {
8021
+ root: "flex min-h-0 flex-1 animate-pulse flex-col overflow-hidden",
8022
+ /** A faixa do cabeçalho do eixo — mesma altura do `ganttHead`. */
8023
+ head: "flex shrink-0 items-center gap-gp-2xl border-b border-border-default bg-bg-table-head px-pad-2xl",
8024
+ /** Linha do corpo — a MESMA altura de `GANTT_ROW_HEIGHT_PX`. */
8025
+ row: "flex shrink-0 items-center gap-gp-2xl border-b border-border-subtle px-pad-2xl",
8026
+ /** O retângulo cinza que faz as vezes de texto ou de barra. */
8027
+ bar: "rounded-radius-sm bg-bg-muted",
8028
+ /** Painel esquerdo da timeline — só ele tem borda de separação. */
8029
+ pane: "flex shrink-0 flex-col overflow-hidden border-r border-border-default",
8030
+ canvas: "flex min-w-0 flex-1 flex-col overflow-hidden",
8031
+ /** Célula da grade de mês. */
8032
+ cell: "flex flex-col gap-gp-xs border-b border-r border-border-subtle p-pad-md",
8033
+ grid: "grid min-h-0 flex-1 grid-cols-7"
8034
+ }
8035
+ });
8036
+ const ganttSearch = avatar.tv({
8037
+ base: [
8038
+ "relative flex cursor-text items-center gap-gp-md",
8039
+ "h-form-lg rounded-radius-lg px-pad-lg",
8040
+ "bg-bg-surface dark:bg-bg-muted",
8041
+ "border border-border-subtle dark:border-border-input",
8042
+ "shadow-sh-sm dark:shadow-sh-none",
8043
+ // #8 — expande no foco, igual à busca do `TableToolbar`: 200 → 300px.
8044
+ // A animação usa a mesma curva e duração de lá (220ms).
8045
+ "min-w-0 flex-1 lg:flex-initial lg:w-[200px] lg:focus-within:w-[300px]",
8046
+ "transition-[width,border-color,background-color,box-shadow] duration-[220ms] ease-[cubic-bezier(0.4,0,0.2,1)]",
8047
+ "focus-within:border-border-brand focus-within:ring-4 focus-within:ring-ring-brand",
8048
+ "[&_svg]:size-icon-sm [&_svg]:shrink-0 [&_svg]:text-fg-subtle"
8049
+ ]
8050
+ });
8051
+ const ganttSearchInput = avatar.tv({
8052
+ base: [
8053
+ "min-w-0 flex-1 border-0 bg-transparent outline-none",
8054
+ "text-body-sm font-normal text-fg-default",
8055
+ "placeholder:text-fg-muted"
8056
+ ]
8057
+ });
8058
+ const ganttAppliedRow = avatar.tv({
8059
+ base: [
8060
+ "flex items-center gap-gp-md",
8061
+ "mt-pad-2xl pt-pad-2xl border-t border-border-default",
8062
+ "flex-nowrap overflow-x-auto sm:flex-wrap sm:overflow-x-visible",
8063
+ "[scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
8064
+ ]
8065
+ });
8066
+ const ganttAppliedChip = avatar.tv({
8067
+ base: [
8068
+ "inline-flex shrink-0 items-center gap-gp-md",
8069
+ "h-form-md pl-pad-sm pr-pad-lg rounded-radius-lg",
8070
+ "bg-bg-surface dark:bg-bg-muted",
8071
+ "border border-dashed border-border-input",
8072
+ "text-body-xs font-normal text-fg-default",
8073
+ "transition-[background-color,border-color] duration-150"
8074
+ ],
8075
+ variants: {
8076
+ /** Clicável (abre as opções do campo). Sempre `true` hoje; ver o part. */
8077
+ interactive: {
8078
+ true: [
8079
+ "cursor-pointer",
8080
+ "hover:bg-bg-muted-hover hover:border-border-default",
8081
+ "dark:hover:bg-bg-accent",
8082
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
8083
+ ],
8084
+ false: ""
8085
+ }
8086
+ },
8087
+ defaultVariants: { interactive: false }
8088
+ });
8089
+ const ganttAppliedChipRemove = avatar.tv({
8090
+ base: [
8091
+ "grid size-[18px] shrink-0 place-items-center rounded-radius-full",
8092
+ "cursor-pointer border-0 bg-transparent p-0 text-fg-subtle outline-none",
8093
+ "transition-colors duration-150 hover:text-fg-default",
8094
+ "focus-visible:shadow-sh-ring",
8095
+ "[&_svg]:size-[14px]"
8096
+ ]
8097
+ });
8098
+ const ganttAppliedChipName = avatar.tv({
8099
+ base: "font-semibold text-fg-default"
8100
+ });
8101
+ const ganttAppliedChipOp = avatar.tv({
8102
+ base: "text-caption-sm text-fg-muted"
8103
+ });
8104
+ const ganttAppliedChipValue = avatar.tv({
8105
+ base: [
8106
+ "inline-flex h-[22px] items-center px-pad-md",
8107
+ "rounded-radius-sm bg-bg-muted dark:bg-bg-accent",
8108
+ "text-body-xs font-medium text-fg-default"
8109
+ ]
8110
+ });
8111
+ const ganttClearLink = avatar.tv({
8112
+ base: [
8113
+ "ml-pad-sm shrink-0 cursor-pointer border-0 bg-transparent p-0",
8114
+ "text-body-xs font-medium text-fg-brand outline-none",
8115
+ "underline-offset-2 transition-opacity duration-150",
8116
+ "hover:underline focus-visible:underline"
8117
+ ]
8118
+ });
8119
+ const ganttFilterDot = avatar.tv({
8120
+ base: [
8121
+ "pointer-events-none absolute -right-px -top-px",
8122
+ "size-[7px] rounded-radius-full",
8123
+ "bg-bg-brand ring-2 ring-bg-canvas"
8124
+ ]
8125
+ });
8126
+ const ganttFilterGroup = avatar.tv({
8127
+ base: [
8128
+ "flex flex-col py-pad-lg",
8129
+ "border-b border-border-subtle last:border-b-0"
8130
+ ]
8131
+ });
8132
+ const ganttFilterGroupHead = avatar.tv({
8133
+ base: [
8134
+ "flex w-full items-center justify-between gap-gp-md",
8135
+ "px-pad-xl py-pad-md text-left",
8136
+ "transition-colors duration-150 hover:bg-bg-muted",
8137
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
8138
+ ]
8139
+ });
8140
+ const ganttFilterGroupTitle = avatar.tv({
8141
+ base: [
8142
+ "flex min-w-0 items-baseline gap-gp-sm",
8143
+ "text-caption-md font-semibold text-fg-default"
8144
+ ]
8145
+ });
8146
+ const ganttFilterGroupChevron = avatar.tv({
8147
+ base: "size-icon-sm shrink-0 text-fg-subtle transition-transform duration-200",
8148
+ variants: {
8149
+ open: { true: "rotate-0", false: "-rotate-90" }
8150
+ },
8151
+ defaultVariants: { open: true }
8152
+ });
8153
+ const ganttFilterGroupActions = avatar.tv({
8154
+ base: [
8155
+ "flex items-center gap-gp-lg px-pad-xl pb-pad-sm pt-pad-2xs",
8156
+ "[&>button]:text-caption-sm [&>button]:font-medium [&>button]:text-fg-brand",
8157
+ "[&>button]:transition-colors [&>button]:duration-150",
8158
+ "[&>button:hover]:text-fg-default",
8159
+ "[&>button:disabled]:cursor-not-allowed [&>button:disabled]:text-fg-subtle",
8160
+ "[&>button:focus-visible]:outline-none [&>button:focus-visible]:ring-4",
8161
+ "[&>button:focus-visible]:ring-ring-brand [&>button:focus-visible]:rounded-radius-xs"
8162
+ ]
8163
+ });
8164
+ const ganttFilterSearch = avatar.tv({
8165
+ base: [
8166
+ "mx-pad-xl mb-pad-sm flex cursor-text items-center gap-gp-sm",
8167
+ "h-form-md rounded-radius-md px-pad-md",
8168
+ "bg-bg-surface dark:bg-bg-muted",
8169
+ "border border-border-subtle dark:border-border-input",
8170
+ "transition-[border-color,box-shadow] duration-150",
8171
+ "focus-within:border-border-brand focus-within:ring-4 focus-within:ring-ring-brand",
8172
+ "[&_svg]:size-icon-sm [&_svg]:shrink-0 [&_svg]:text-fg-subtle"
8173
+ ]
8174
+ });
8175
+ const ganttFilterSearchInput = avatar.tv({
8176
+ base: [
8177
+ "min-w-0 flex-1 border-0 bg-transparent outline-none",
8178
+ "text-body-sm font-normal text-fg-default",
8179
+ "placeholder:text-fg-muted"
8180
+ ]
8181
+ });
8182
+ const ganttFilterInput = avatar.tv({
8183
+ base: [
8184
+ "min-w-0 flex-1 h-form-md rounded-radius-md px-pad-md",
8185
+ "bg-bg-surface dark:bg-bg-muted",
8186
+ "border border-border-subtle dark:border-border-input",
8187
+ "text-body-sm font-normal text-fg-default outline-none",
8188
+ "placeholder:text-fg-muted",
8189
+ "transition-[border-color,box-shadow] duration-150",
8190
+ "focus-visible:border-border-brand focus-visible:ring-4 focus-visible:ring-ring-brand",
8191
+ // O seletor nativo de data vem escuro no dark sem isto.
8192
+ "dark:[color-scheme:dark]"
8193
+ ]
8194
+ });
8195
+ const ganttFilterRange = avatar.tv({
8196
+ base: "flex items-center gap-gp-sm px-pad-xl pb-pad-sm"
8197
+ });
8198
+ const ganttFilterRangeSep = avatar.tv({
8199
+ base: "shrink-0 text-caption-sm text-fg-muted"
8200
+ });
8201
+ const ganttFilterEmpty = avatar.tv({
8202
+ base: "px-pad-xl py-pad-md text-body-sm text-fg-muted"
8203
+ });
8204
+ const ganttFilterOption = avatar.tv({
8205
+ base: [
8206
+ "flex w-full min-w-0 cursor-pointer items-center gap-gp-md",
8207
+ "px-pad-xl py-pad-md",
8208
+ "transition-colors duration-150 hover:bg-bg-muted"
8209
+ ]
8210
+ });
8211
+ const ganttFilterOptionBox = avatar.tv({
8212
+ base: "shrink-0 transition-colors duration-150",
8213
+ variants: {
8214
+ colorKey: {
8215
+ "chart-1": "border-chart-1/60 data-[state=checked]:!bg-chart-1 data-[state=checked]:!border-chart-1",
8216
+ "chart-2": "border-chart-2/60 data-[state=checked]:!bg-chart-2 data-[state=checked]:!border-chart-2",
8217
+ "chart-3": "border-chart-3/60 data-[state=checked]:!bg-chart-3 data-[state=checked]:!border-chart-3",
8218
+ "chart-4": "border-chart-4/60 data-[state=checked]:!bg-chart-4 data-[state=checked]:!border-chart-4",
8219
+ "chart-5": "border-chart-5/60 data-[state=checked]:!bg-chart-5 data-[state=checked]:!border-chart-5",
8220
+ brand: "border-border-brand data-[state=checked]:!bg-bg-brand",
8221
+ success: "border-border-success-muted data-[state=checked]:!bg-bg-success data-[state=checked]:!border-bg-success",
8222
+ warning: "border-border-warning-muted data-[state=checked]:!bg-bg-warning data-[state=checked]:!border-bg-warning",
8223
+ danger: "border-border-danger-muted data-[state=checked]:!bg-bg-danger data-[state=checked]:!border-bg-danger",
8224
+ info: "border-border-info-muted data-[state=checked]:!bg-bg-info data-[state=checked]:!border-bg-info",
8225
+ neutral: "border-border-default"
8226
+ }
8227
+ },
8228
+ defaultVariants: { colorKey: "neutral" }
8229
+ });
8230
+ const ganttFilterOptionLabel = avatar.tv({
8231
+ base: "min-w-0 flex-1 truncate text-body-sm text-fg-default"
8232
+ });
8233
+ const ganttFilterOptionCount = avatar.tv({
8234
+ base: "shrink-0 text-caption-sm tabular-nums text-fg-subtle"
8235
+ });
8236
+ const ganttBody = avatar.tv({
8237
+ base: "flex min-h-0 flex-1 overflow-hidden",
8238
+ variants: {
8239
+ framed: {
8240
+ true: "rounded-radius-xl border border-border-default bg-bg-surface",
8241
+ false: ""
8242
+ }
8243
+ },
8244
+ defaultVariants: { framed: true }
8245
+ });
8246
+ const ganttGridPane = avatar.tv({
8247
+ base: [
8248
+ "flex min-h-0 shrink-0 flex-col",
8249
+ /**
8250
+ * ⛔ SEM borda direita — e isso é a correção de um defeito que eu criei.
8251
+ *
8252
+ * A grade precisa de uma linha grossa separando os dois painéis (é a âncora
8253
+ * de leitura: o olho volta pra ela a cada linha). Mas essa linha JÁ EXISTE:
8254
+ * é o `ganttSplitter`, que é `w-[3px] bg-bg-scrollbar-thumb/70` e vive logo depois
8255
+ * deste painel, sem vão nenhum. Pra mudar a espessura da separação, mexa
8256
+ * NELE — não acrescente borda aqui.
8257
+ *
8258
+ * Pôr `border-r-[3px] border-r-border-default` aqui somava **6px contíguos
8259
+ * da cor idêntica** — medido: borda de 478→481 e divisor de 481→484, ambos
8260
+ * `oklch(0.2645 0 0)`, vão de 0. Lia como uma borda com um segundo tracinho
8261
+ * grudado nela.
8262
+ *
8263
+ * A lição: antes de adicionar uma divisória, procure a que já está ali. O
8264
+ * divisor não é decoração — é o alvo de arraste, e ele vira verde no hover.
8265
+ * Duplicá-lo não engrossa a linha, cria duas.
8266
+ */
8267
+ // ⚠️ `overflow-hidden` e NÃO `overflow-y-auto`: o scroll vertical é do
8268
+ // painel direito, e os dois rolam juntos por `scrollTop` espelhado. Dois
8269
+ // scrollbars independentes dessincronizam as linhas na primeira rolagem.
8270
+ "overflow-hidden",
8271
+ /**
8272
+ * #6 — sombra pra a direita e `z-[2]`: o painel passa POR CIMA do canvas.
8273
+ *
8274
+ * Sem isso, a barra que começa no primeiro dia da janela encostava na
8275
+ * divisória e parecia continuar por baixo da grade. Com a sombra, a grade
8276
+ * lê como uma camada acima — e o corte da barra na borda fica intencional.
8277
+ *
8278
+ * ## Por que `10px 0 36px -18px` e não `4px 0 12px -4px`
8279
+ *
8280
+ * O que decide se a sombra lê como GRADIENTE ou como uma segunda linha é a
8281
+ * posição da borda do retângulo dela, que é `offsetX + spread` relativo à
8282
+ * borda do elemento — não o blur.
8283
+ *
8284
+ * antes: 4 + (-4) = 0 → retângulo termina EXATO na borda do painel.
8285
+ * No blur, a borda do retângulo é o ponto de ~50%
8286
+ * da opacidade: a sombra nascia forte encostada na
8287
+ * divisória e morria em 6px. É a faixa escura que
8288
+ * se lia como "tem mais um pixel depois da borda".
8289
+ * agora: 10 + (-18) = -8 → retângulo termina 8px DENTRO do painel, onde
8290
+ * ninguém vê. O que escapa pra fora é só a cauda
8291
+ * do blur: começa em ~28% na divisória e decai por
8292
+ * 10px (era 6).
8293
+ *
8294
+ * Mais larga e mais suave ao mesmo tempo, que era o pedido — e são coisas
8295
+ * independentes: aumentar o blur sozinho alarga a faixa forte junto.
8296
+ *
8297
+ * Dark em 0.55 contra 0.14 do light = 3.9×, acima do mínimo de 2× da L-011.
8298
+ */
8299
+ "relative z-[2] shadow-[10px_0_36px_-18px_oklch(0_0_0_/_0.14)]",
8300
+ "dark:shadow-[10px_0_36px_-18px_oklch(0_0_0_/_0.55)]"
8301
+ ]
8302
+ });
8303
+ const ganttSplitter = avatar.tv({
8304
+ base: [
8305
+ "group/split relative w-[3px] shrink-0 cursor-col-resize",
8306
+ "bg-bg-scrollbar-thumb/70 transition-colors duration-150",
8307
+ "hover:bg-border-brand",
8308
+ "after:absolute after:inset-y-0 after:-left-1 after:-right-1 after:content-['']",
8309
+ "focus-visible:outline-none focus-visible:bg-border-brand"
8310
+ ],
8311
+ variants: {
8312
+ active: { true: "bg-border-brand", false: "" }
8313
+ },
8314
+ defaultVariants: { active: false }
8315
+ });
8316
+ const ganttTimelinePane = avatar.tv({
8317
+ base: "flex min-w-0 min-h-0 flex-1 flex-col"
8318
+ });
8319
+ const ganttHead = avatar.tv({
8320
+ base: [
8321
+ /**
8322
+ * ⚠️ `z-[5]`, e o número não é folga — é a correção de um defeito.
8323
+ *
8324
+ * Era `z-[3]`, **o MESMO** do `ganttLinksLayer`. Empate de z-index no mesmo
8325
+ * contexto de empilhamento é resolvido por ORDEM NO DOM, e o canvas vem
8326
+ * depois do cabeçalho: ao rolar na vertical, as setas de vínculo passavam
8327
+ * POR CIMA dos rótulos do eixo.
8328
+ *
8329
+ * 5 e não 4 porque a barra arrastada é `z-[4]` (`ganttBar` variante
8330
+ * `dragging`) e o conector de vínculo é `z-[3]`: o cabeçalho tem que vencer
8331
+ * TODO o conteúdo do canvas, não só as setas.
8332
+ *
8333
+ * ⛔ Não baixe o `ganttLinksLayer` em vez disto: ele precisa ficar acima das
8334
+ * barras (`z-[1]` do rótulo) pra a seta não desaparecer atrás delas.
8335
+ */
8336
+ "sticky top-0 z-[5] shrink-0",
8337
+ "border-b border-border-default bg-bg-table-head"
8338
+ ]
8339
+ });
8340
+ const ganttHeadRow = avatar.tv({
8341
+ base: "relative",
8342
+ variants: {
8343
+ level: {
8344
+ /** Nível de cima: mês/ano. Divisória mais forte entre grupos. */
8345
+ group: "border-b border-border-subtle",
8346
+ /** Nível de baixo: a unidade da grade. */
8347
+ unit: ""
8348
+ }
8349
+ },
8350
+ defaultVariants: { level: "unit" }
8351
+ });
8352
+ const ganttHeadCell = avatar.tv({
8353
+ base: [
8354
+ // `absolute top-0` — a posição vem de `dateToX`, ver `ganttHeadRow`.
8355
+ "absolute top-0 flex items-center justify-center overflow-hidden",
8356
+ "border-r border-border-subtle last:border-r-0",
8357
+ "px-pad-xs text-center"
8358
+ ],
8359
+ variants: {
8360
+ level: {
8361
+ group: "h-[42px] text-caption-md font-semibold text-fg-default first-letter:uppercase",
8362
+ /**
8363
+ * #10 — a unidade EMPILHA: abreviação do dia em cima, número embaixo e
8364
+ * maior. Lado a lado (`S 21`), o par competia por largura com a
8365
+ * divisória; empilhado, a data ganha peso e a abreviação vira legenda.
8366
+ */
8367
+ unit: "h-[42px] flex-col justify-center gap-0 font-normal text-fg-muted"
8368
+ },
8369
+ /** Fim de semana: coluna sombreada, e o rótulo desce um tom. */
8370
+ weekend: { true: "bg-bg-subtle text-fg-subtle", false: "" },
8371
+ today: { true: "bg-bg-brand-subtle font-semibold text-fg-brand", false: "" },
8372
+ /** #2 — coluna selecionada por clique: cabeçalho no verde da marca. */
8373
+ selected: { true: "bg-bg-brand-subtle", false: "" },
8374
+ /**
8375
+ * #1 — última unidade de um grupo (o último dia do mês, em `day`).
8376
+ *
8377
+ * Divisória mais grossa e mais clara, atravessando o cabeçalho inteiro e o
8378
+ * conteúdo: é o que faz o mês ler como um BLOCO em vez de uma fileira
8379
+ * contínua de dias. O limite vem de `axis.groups`, então acompanha a escala
8380
+ * — vira mês em `day`/`week` e ano em `month`/`quarter`.
8381
+ *
8382
+ * ⚠️ `border-input` foi ESCOLHIDO por medição, não por nome. ΔL de cada
8383
+ * candidato contra a superfície onde a linha vive:
8384
+ *
8385
+ * token dark (bg 0.225) light (bg 1.0) × a linha comum
8386
+ * border-subtle (comum) 0.031 0.069 1.0×
8387
+ * border-default 0.040 0.092 1.3× / 1.3×
8388
+ * border-input 0.062 0.124 2.0× / 1.8×
8389
+ * chart-grid 0.093 0.092 3.0× / 1.3×
8390
+ *
8391
+ * Três voltas até aqui, e cada uma ensinou algo:
8392
+ *
8393
+ * 1. `border-default` a 2px ficou **invisível** — 0.040 contra 0.031 é a MESMA
8394
+ * linha. Eu tinha medido que o valor do token mudou, não que a mudança se
8395
+ * vê; espessura não compra o que falta em contraste.
8396
+ * 2. `chart-grid` a 3px resolveu o contraste e passou do ponto nos dois
8397
+ * eixos: 3px vira moldura, e 3× a linha comum vira estrutura competindo
8398
+ * com a barra.
8399
+ * 3. `border-input` a 2px é o degrau do meio, e é o único candidato que fica
8400
+ * em ~2× nos DOIS modos — por isso não precisa de par `dark:`. Menos
8401
+ * classe e sem risco de os dois modos divergirem na próxima edição.
8402
+ *
8403
+ * ⛔ Não meça isto durante a transição: a coluna tem `transition-colors`, e
8404
+ * ler no meio dela devolve o valor interpolado (serializado em `oklab`, que é
8405
+ * a pista). Já me enganou uma vez nesta mesma linha.
8406
+ *
8407
+ * ⛔ Não troque por um token de marca: o verde já significa "hoje" e
8408
+ * "selecionado" aqui, e a virada de mês não é nem uma nem outra.
8409
+ */
8410
+ boundary: { true: "border-r-[2px] border-r-border-input", false: "" }
8411
+ },
8412
+ defaultVariants: {
8413
+ level: "unit",
8414
+ weekend: false,
8415
+ today: false,
8416
+ selected: false,
8417
+ boundary: false
8418
+ }
8419
+ });
8420
+ const ganttHeadWeekday = avatar.tv({
8421
+ base: "text-caption-xs leading-none text-fg-subtle",
8422
+ variants: {
8423
+ weekend: { true: "text-fg-subtle/70", false: "" }
8424
+ },
8425
+ defaultVariants: { weekend: false }
8426
+ });
8427
+ const ganttHeadDayNumber = avatar.tv({
8428
+ base: "mt-[2px] text-body-sm leading-none tabular-nums",
8429
+ variants: {
8430
+ today: { true: "font-semibold text-fg-brand", false: "text-fg-default" }
8431
+ },
8432
+ defaultVariants: { today: false }
8433
+ });
8434
+ const ganttGridHead = avatar.tv({
8435
+ base: [
8436
+ "flex shrink-0 items-center",
8437
+ "border-b border-border-default bg-bg-table-head"
8438
+ ]
8439
+ });
8440
+ const ganttGridHeadCell = avatar.tv({
8441
+ base: [
8442
+ "flex shrink-0 items-center gap-gp-2xs overflow-hidden",
8443
+ // #6: `px-pad-xl` (14px) e não `pad-lg` — o conteúdo encostava nas duas
8444
+ // bordas da coluna, e a primeira célula ainda soma o recuo da hierarquia.
8445
+ "px-pad-2xl text-caption-sm font-semibold text-fg-muted",
8446
+ // Mesmo respiro da célula, senão o rótulo da coluna e o dado dela ficam com
8447
+ // margens diferentes na borda do painel.
8448
+ "last:pr-pad-4xl"
8449
+ ],
8450
+ variants: {
8451
+ align: {
8452
+ left: "justify-start text-left",
8453
+ center: "justify-center text-center",
8454
+ right: "justify-end text-right"
8455
+ }
8456
+ },
8457
+ defaultVariants: { align: "left" }
8458
+ });
8459
+ const ganttGridRow = avatar.tv({
8460
+ base: [
8461
+ "group/row flex shrink-0 items-center",
8462
+ "border-b border-border-subtle",
8463
+ "transition-colors duration-150",
8464
+ "hover:bg-bg-subtle"
8465
+ ],
8466
+ variants: {
8467
+ type: {
8468
+ task: "",
8469
+ /** `summary` é agrupador: peso maior e fundo levemente destacado. */
8470
+ summary: "bg-bg-subtle/60 font-semibold",
8471
+ milestone: ""
8472
+ },
8473
+ /**
8474
+ * #7 — linha selecionada, no verde da marca.
8475
+ *
8476
+ * Mesma leitura da linha selecionada do `DataTable`: a cor da marca diz "é
8477
+ * esta que estou olhando". Vence o hover no `compoundVariant` abaixo —
8478
+ * senão passar o mouse sobre a linha selecionada a apagava.
8479
+ */
8480
+ selected: { true: "", false: "" },
8481
+ /**
8482
+ * #3 — hover CRUZADO, controlado por estado e não por `:hover` do CSS.
8483
+ *
8484
+ * `:hover` só acende o elemento sob o cursor, e aqui a linha vive em DOIS
8485
+ * painéis irmãos: passar o mouse no nome tem que acender a faixa da barra e
8486
+ * vice-versa. Nenhum seletor CSS alcança um irmão de outro contêiner, então
8487
+ * o índice da linha sob o cursor é estado do React.
8488
+ *
8489
+ * ⚠️ `bg-bg-muted` e não `bg-bg-subtle`: no dark, `subtle` é
8490
+ * `oklch(1 0 0 / 0.01)` — 1% de branco, que é **invisível** na prática.
8491
+ * Medi o hover e o antes/depois diferiam em 1 centésimo de alfa. `muted` é
8492
+ * 3%, e ainda foi preciso somar a borda pra o realce se ler.
8493
+ */
8494
+ hovered: { true: "bg-bg-muted", false: "" }
8495
+ },
8496
+ compoundVariants: [
8497
+ // Selecionada vence hover — passar o mouse não pode apagar a seleção.
8498
+ { selected: true, class: "bg-bg-brand-subtle" },
8499
+ { selected: true, hovered: true, class: "bg-bg-brand-subtle-hover" }
8500
+ ],
8501
+ defaultVariants: { type: "task", selected: false, hovered: false }
8502
+ });
8503
+ const ganttGridCell = avatar.tv({
8504
+ base: [
8505
+ /**
8506
+ * `relative` é a origem dos conectores de árvore; `self-stretch` é o que os
8507
+ * faz FUNCIONAR.
8508
+ *
8509
+ * ⚠️ Sem `self-stretch`, a célula encolhe pro conteúdo: medido, **32px numa
8510
+ * linha de 48**, porque a linha é `flex items-center`. O conector vivia num
8511
+ * `inset-y-0` de 32px, sobrava 8px em cima e 8 embaixo, e as verticais de
8512
+ * linhas consecutivas NÃO SE TOCAVAM — a árvore virava uma fileira de
8513
+ * tracinhos soltos em vez de um eixo contínuo.
8514
+ *
8515
+ * O `items-center` interno continua centrando o conteúdo; só a caixa cresce.
8516
+ */
8517
+ "relative self-stretch flex min-w-0 shrink-0 items-center gap-gp-sm overflow-hidden px-pad-2xl",
8518
+ /**
8519
+ * A ÚLTIMA célula respira 24px à direita, não 16.
8520
+ *
8521
+ * Medido: o conteúdo da última coluna terminava a ~16px do divisor de 3px, e
8522
+ * as duas linhas verticais (a borda da célula e o divisor) somadas ao texto
8523
+ * apertado ali faziam a coluna parecer cortada. 24px separa "fim do dado" de
8524
+ * "fim do painel".
8525
+ */
8526
+ "last:pr-pad-4xl",
8527
+ // ⚠️ Sem cor na base: quem decide é a variante `tone`. Com `text-fg-default`
8528
+ // aqui, as duas classes empatavam e a ordem no CSS gerado decidia.
8529
+ "text-body-sm"
8530
+ ],
8531
+ variants: {
8532
+ align: {
8533
+ left: "justify-start",
8534
+ center: "justify-center",
8535
+ right: "justify-end"
8536
+ },
8537
+ /** Coluna de número/data: `tabular-nums` pra alinhar dígito com dígito. */
8538
+ numeric: { true: "tabular-nums", false: "" },
8539
+ /**
8540
+ * #6 — hierarquia dentro da linha.
8541
+ *
8542
+ * O nome da tarefa é a informação; data, duração e responsável são apoio.
8543
+ * Com todos em `fg-default` e o mesmo peso, as quatro colunas disputavam a
8544
+ * atenção e a grade lia como um bloco de texto. `muted` + `font-normal`
8545
+ * recua o apoio sem escondê-lo.
8546
+ */
8547
+ tone: {
8548
+ default: "text-fg-default",
8549
+ muted: "font-normal text-fg-muted"
8550
+ }
8551
+ },
8552
+ defaultVariants: { align: "left", numeric: false, tone: "default" }
8553
+ });
8554
+ const ganttGridGroupCell = avatar.tv({
8555
+ base: [
8556
+ // `self-stretch` pelo mesmo motivo da célula — ver a nota do `ganttGridCell`.
8557
+ "relative self-stretch flex min-w-0 flex-1 items-center gap-gp-sm overflow-hidden",
8558
+ // `pr-pad-4xl` porque é ele que carrega o `trailing` (o chip de status) — e
8559
+ // era o chip que encostava no divisor.
8560
+ "pl-pad-2xl pr-pad-4xl text-body-sm font-semibold text-fg-default"
8561
+ ]
8562
+ });
8563
+ const ganttTreeRail = avatar.tv({
8564
+ base: "pointer-events-none absolute inset-y-0 left-pad-2xl"
8565
+ });
8566
+ const ganttTreeColumn = avatar.tv({
8567
+ base: "absolute inset-y-0 w-[16px]"
8568
+ });
8569
+ const ganttTreeSegment = avatar.tv({
8570
+ base: "absolute bg-border-input"
8571
+ });
8572
+ const ganttGridLabel = avatar.tv({
8573
+ base: "flex min-w-0 flex-col justify-center"
8574
+ });
8575
+ const ganttGridLabelText = avatar.tv({
8576
+ base: "truncate text-body-sm text-fg-default"
8577
+ });
8578
+ const ganttGridSublabel = avatar.tv({
8579
+ base: "truncate text-caption-sm font-normal text-fg-muted"
8580
+ });
8581
+ const ganttChevron = avatar.tv({
8582
+ base: [
8583
+ "grid size-icon-md shrink-0 place-items-center rounded-radius-sm",
8584
+ "text-fg-subtle transition-transform duration-200",
8585
+ "hover:text-fg-default",
8586
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
8587
+ "[&_svg]:size-icon-sm"
8588
+ ],
8589
+ variants: {
8590
+ open: { true: "rotate-0", false: "-rotate-90" },
8591
+ /** Folha: reserva o espaço sem desenhar o chevron, pra o rótulo alinhar. */
8592
+ hidden: { true: "invisible", false: "" }
8593
+ },
8594
+ defaultVariants: { open: true, hidden: false }
8595
+ });
8596
+ const ganttCanvasScroll = avatar.tv({
8597
+ base: [
8598
+ // `overflow-auto` nos dois eixos: é ele que rola o cabeçalho junto (#5).
8599
+ "min-h-0 flex-1 overflow-auto",
8600
+ // `scrollbar-thin` é a barra do DS — `@utility` do tema GERADO, existe nos
8601
+ // 4 canais (não é do globals.css).
8602
+ "scrollbar-thin"
8603
+ ]
8604
+ });
8605
+ const ganttCanvas = avatar.tv({
8606
+ base: "relative"
8607
+ });
8608
+ const ganttCanvasRow = avatar.tv({
8609
+ base: [
8610
+ "absolute inset-x-0 border-b border-border-subtle",
8611
+ "transition-colors duration-150"
8612
+ ],
8613
+ variants: {
8614
+ type: {
8615
+ task: "",
8616
+ summary: "bg-bg-subtle/60",
8617
+ milestone: ""
8618
+ },
8619
+ hovered: { true: "bg-bg-muted", false: "" },
8620
+ selected: { true: "", false: "" }
8621
+ },
8622
+ compoundVariants: [
8623
+ { selected: true, class: "bg-bg-brand-subtle" },
8624
+ { selected: true, hovered: true, class: "bg-bg-brand-subtle-hover" }
8625
+ ],
8626
+ defaultVariants: { type: "task", hovered: false, selected: false }
8627
+ });
8628
+ const ganttGridColumn = avatar.tv({
8629
+ base: [
8630
+ /**
8631
+ * #1 — `border-border-subtle`, a MESMA cor das divisórias de linha.
8632
+ *
8633
+ * Era `border-chart-grid`, que é o token da grade de GRÁFICO e vem mais
8634
+ * claro de propósito (num chart a grade é fundo, não estrutura). Aqui as
8635
+ * duas famílias de linha formam uma malha: com tons diferentes, a vertical
8636
+ * lia como sombra da horizontal.
8637
+ */
8638
+ "absolute inset-y-0 border-r border-border-subtle",
8639
+ "transition-colors duration-150"
8640
+ ],
8641
+ variants: {
8642
+ weekend: { true: "bg-bg-subtle/50", false: "" },
8643
+ /** Coluna sob o cursor — a outra metade da mira. */
8644
+ hovered: { true: "bg-bg-muted", false: "" },
8645
+ /** #7 — coluna da linha selecionada, no verde da marca. */
8646
+ selected: { true: "bg-bg-brand-subtle", false: "" },
8647
+ /**
8648
+ * #1 — virada de mês/ano: o par da divisória do cabeçalho, MESMAS classes.
8649
+ * A tabela de ΔL que justifica a escolha do token está no `ganttHeadCell`.
8650
+ */
8651
+ boundary: { true: "border-r-[2px] border-r-border-input", false: "" }
8652
+ },
8653
+ defaultVariants: {
8654
+ weekend: false,
8655
+ hovered: false,
8656
+ selected: false,
8657
+ boundary: false
8658
+ }
8659
+ });
8660
+ const ganttBar = avatar.tv({
8661
+ slots: {
8662
+ root: [
8663
+ "group/bar absolute flex min-w-0 items-center gap-gp-2xs",
8664
+ "overflow-hidden border text-left outline-none",
8665
+ "rounded-radius-sm",
8666
+ "transition-[background-color,border-color,box-shadow] duration-150",
8667
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
8668
+ ],
8669
+ /** Acento sólido na borda esquerda — é AQUI que a cor viva mora. */
8670
+ accent: "pointer-events-none absolute inset-y-0 left-0 w-[3px]",
8671
+ /** Preenchimento de progresso, sobre o tingido. */
8672
+ progress: "pointer-events-none absolute inset-y-0 left-0",
8673
+ label: "relative z-[1] min-w-0 truncate pl-pad-md pr-pad-md text-caption-sm text-fg-default"
8674
+ },
8675
+ variants: {
8676
+ /**
8677
+ * ⚠️ A cor entra no fundo TINGIDO e no acento, nunca no texto — ver a nota
8678
+ * do topo. As chaves de chart usam `/14`, que é a mesma proporção do
8679
+ * `color-mix(… 14%, transparent)` com que o DS deriva os `-subtle`.
8680
+ */
8681
+ colorKey: {
8682
+ "chart-1": {
8683
+ root: "border-chart-1/40 bg-chart-1/14 hover:bg-chart-1/20",
8684
+ accent: "bg-chart-1",
8685
+ progress: "bg-chart-1/35"
8686
+ },
8687
+ "chart-2": {
8688
+ root: "border-chart-2/40 bg-chart-2/14 hover:bg-chart-2/20",
8689
+ accent: "bg-chart-2",
8690
+ progress: "bg-chart-2/35"
8691
+ },
8692
+ "chart-3": {
8693
+ root: "border-chart-3/40 bg-chart-3/14 hover:bg-chart-3/20",
8694
+ accent: "bg-chart-3",
8695
+ progress: "bg-chart-3/35"
8696
+ },
8697
+ "chart-4": {
8698
+ root: "border-chart-4/40 bg-chart-4/14 hover:bg-chart-4/20",
8699
+ accent: "bg-chart-4",
8700
+ progress: "bg-chart-4/35"
8701
+ },
8702
+ "chart-5": {
8703
+ root: "border-chart-5/40 bg-chart-5/14 hover:bg-chart-5/20",
8704
+ accent: "bg-chart-5",
8705
+ progress: "bg-chart-5/35"
8706
+ },
8707
+ brand: {
8708
+ root: "border-border-brand-subtle bg-bg-brand-subtle hover:bg-bg-brand-subtle-hover",
8709
+ accent: "bg-bg-brand",
8710
+ progress: "bg-bg-brand/30"
8711
+ },
8712
+ success: {
8713
+ root: "border-border-success-muted bg-bg-success-muted hover:bg-bg-success-muted-hover",
8714
+ accent: "bg-bg-success",
8715
+ progress: "bg-bg-success/30"
8716
+ },
8717
+ warning: {
8718
+ root: "border-border-warning-muted bg-bg-warning-muted hover:bg-bg-warning-muted-hover",
8719
+ accent: "bg-bg-warning",
8720
+ progress: "bg-bg-warning/30"
8721
+ },
8722
+ danger: {
8723
+ root: "border-border-danger-muted bg-bg-danger-muted hover:bg-bg-danger-muted-hover",
8724
+ accent: "bg-bg-danger",
8725
+ progress: "bg-bg-danger/30"
8726
+ },
8727
+ info: {
8728
+ root: "border-border-info-muted bg-bg-info-muted hover:bg-bg-info-muted-hover",
8729
+ accent: "bg-bg-info",
8730
+ progress: "bg-bg-info/30"
8731
+ },
8732
+ neutral: {
8733
+ root: "border-border-default bg-bg-muted hover:bg-bg-subtle",
8734
+ accent: "bg-fg-subtle",
8735
+ progress: "bg-fg-subtle/25"
8736
+ }
8737
+ },
8738
+ /**
8739
+ * `summary` é mais baixa e sem acento: ela agrega, não é trabalho. As
8740
+ * referências desenham as pontas viradas pra baixo; aqui o sinal é a altura
8741
+ * reduzida e o radius menor, que sobrevive em 4px de largura por dia.
8742
+ */
8743
+ type: {
8744
+ task: { root: "" },
8745
+ summary: { root: "rounded-radius-xs", accent: "hidden" },
8746
+ milestone: { root: "!border-0 !bg-transparent", accent: "hidden", label: "hidden" }
8747
+ },
8748
+ /** Barra que atravessa a janela: perde o canto e a borda daquele lado. */
8749
+ continuesBefore: { true: { root: "rounded-l-none border-l-0" }, false: {} },
8750
+ continuesAfter: { true: { root: "rounded-r-none border-r-0" }, false: {} },
8751
+ /** Vínculo violado — marcação não-cromática somada à cor. */
8752
+ conflict: {
8753
+ true: { root: "!border-border-danger-muted border-dashed" },
8754
+ false: {}
8755
+ },
8756
+ /** No caminho crítico: anel externo, sem trocar a cor da categoria. */
8757
+ critical: {
8758
+ true: { root: "ring-2 ring-bg-danger/50 ring-offset-0" },
8759
+ false: {}
8760
+ },
8761
+ dragging: {
8762
+ // ⚠️ Sem `transition`: o transform do dnd-kit muda a cada movimento do
8763
+ // ponteiro, e transição em cima disso faz a barra perseguir o cursor com
8764
+ // atraso. A transição vive no estado em repouso.
8765
+ true: { root: "z-[4] cursor-grabbing opacity-90 shadow-sh-lg transition-none" },
8766
+ false: {}
8767
+ },
8768
+ movable: { true: { root: "cursor-grab" }, false: { root: "cursor-pointer" } }
8769
+ },
8770
+ defaultVariants: {
8771
+ colorKey: "chart-1",
8772
+ type: "task",
8773
+ continuesBefore: false,
8774
+ continuesAfter: false,
8775
+ conflict: false,
8776
+ critical: false,
8777
+ dragging: false,
8778
+ movable: false
8779
+ }
8780
+ });
8781
+ const ganttMilestone = avatar.tv({
8782
+ base: [
8783
+ "absolute grid place-items-center outline-none",
8784
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
8785
+ "[&>span]:block [&>span]:size-[12px] [&>span]:rotate-45 [&>span]:rounded-[2px]"
8786
+ ],
8787
+ variants: {
8788
+ colorKey: {
8789
+ "chart-1": "[&>span]:bg-chart-1",
8790
+ "chart-2": "[&>span]:bg-chart-2",
8791
+ "chart-3": "[&>span]:bg-chart-3",
8792
+ "chart-4": "[&>span]:bg-chart-4",
8793
+ "chart-5": "[&>span]:bg-chart-5",
8794
+ brand: "[&>span]:bg-bg-brand",
8795
+ success: "[&>span]:bg-bg-success",
8796
+ warning: "[&>span]:bg-bg-warning",
8797
+ danger: "[&>span]:bg-bg-danger",
8798
+ info: "[&>span]:bg-bg-info",
8799
+ neutral: "[&>span]:bg-fg-subtle"
8800
+ }
8801
+ },
8802
+ defaultVariants: { colorKey: "brand" }
8803
+ });
8804
+ const ganttResizeHandle = avatar.tv({
8805
+ base: [
8806
+ "absolute inset-y-0 z-[2] w-[8px] cursor-col-resize",
8807
+ "opacity-0 transition-opacity duration-150",
8808
+ "group-hover/bar:opacity-100 focus-visible:opacity-100",
8809
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
8810
+ "before:absolute before:inset-y-[6px] before:left-1/2 before:w-[2px]",
8811
+ "before:-translate-x-1/2 before:rounded-radius-full before:bg-fg-default/40",
8812
+ "before:content-['']"
8813
+ ],
8814
+ variants: {
8815
+ side: { start: "left-0", end: "right-0" }
8816
+ }
8817
+ });
8818
+ const ganttLinkPort = avatar.tv({
8819
+ base: [
8820
+ "absolute top-1/2 z-[3] size-[9px] -translate-y-1/2 cursor-crosshair",
8821
+ "rounded-radius-full border-2 border-bg-surface bg-fg-subtle",
8822
+ "opacity-0 transition-opacity duration-150",
8823
+ "group-hover/bar:opacity-100 focus-visible:opacity-100",
8824
+ "hover:bg-bg-brand focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
8825
+ ],
8826
+ variants: {
8827
+ side: { start: "-left-[13px]", end: "-right-[13px]" }
8828
+ }
8829
+ });
8830
+ const ganttLinksLayer = avatar.tv({
8831
+ base: "pointer-events-none absolute inset-0 z-[3] overflow-visible"
8832
+ });
8833
+ const ganttLinkGhost = avatar.tv({
8834
+ base: [
8835
+ "pointer-events-none fill-none",
8836
+ "stroke-bg-brand stroke-2 [stroke-dasharray:5_4]"
8837
+ ]
8838
+ });
8839
+ const ganttLinkPath = avatar.tv({
8840
+ base: [
8841
+ "pointer-events-auto fill-none cursor-pointer",
8842
+ "transition-[stroke,stroke-width] duration-150"
8843
+ ],
8844
+ variants: {
8845
+ state: {
8846
+ default: "stroke-fg-subtle stroke-[1.5] hover:stroke-fg-default hover:stroke-2",
8847
+ /** Violado: tracejado + cor de perigo. Duplo canal, não só cor. */
8848
+ conflict: "stroke-bg-danger stroke-2 [stroke-dasharray:4_3]",
8849
+ critical: "stroke-bg-danger stroke-2"
8850
+ }
8851
+ },
8852
+ defaultVariants: { state: "default" }
8853
+ });
8854
+ avatar.tv({
8855
+ base: "fill-[context-stroke]"
8856
+ });
8857
+ const ganttNowLine = avatar.tv({
8858
+ base: "pointer-events-none absolute inset-y-0 z-[2] w-0"
8859
+ });
8860
+ const ganttNowStroke = avatar.tv({
8861
+ base: "absolute inset-y-0 left-0 w-px -translate-x-1/2 bg-bg-danger"
8862
+ });
8863
+ const ganttNowDot = avatar.tv({
8864
+ base: [
8865
+ "absolute left-0 top-0 size-[7px] -translate-x-1/2",
8866
+ "rounded-radius-full bg-bg-danger"
8867
+ ]
8868
+ });
8869
+ const ganttMonthFrame = avatar.tv({
8870
+ base: [
8871
+ "flex min-h-0 flex-1 flex-col overflow-hidden",
8872
+ "rounded-radius-xl border border-border-default bg-bg-surface"
8873
+ ]
8874
+ });
8875
+ const ganttWeekdayRow = avatar.tv({
8876
+ base: "grid shrink-0 grid-cols-7 border-b border-border-default bg-bg-table-head"
8877
+ });
8878
+ const ganttWeekdayCell = avatar.tv({
8879
+ base: [
8880
+ "px-pad-md py-pad-md text-center",
8881
+ "text-caption-sm font-semibold uppercase text-fg-muted"
8882
+ ]
8883
+ });
8884
+ const ganttMonthGrid = avatar.tv({
8885
+ base: [
8886
+ "flex min-h-0 flex-1 flex-col",
8887
+ "overflow-y-auto scrollbar-thin"
8888
+ ]
8889
+ });
8890
+ const ganttWeekRow = avatar.tv({
8891
+ base: "relative flex min-h-[88px] shrink-0 flex-1 flex-col"
8892
+ });
8893
+ const ganttWeekDays = avatar.tv({
8894
+ base: "grid min-h-0 flex-1 grid-cols-7"
8895
+ });
8896
+ const ganttWeekSegments = avatar.tv({
8897
+ base: "pointer-events-none absolute inset-x-0"
8898
+ });
8899
+ const ganttDaySegment = avatar.tv({
8900
+ slots: {
8901
+ root: [
8902
+ "pointer-events-auto absolute flex min-w-0 items-center gap-gp-2xs",
8903
+ "h-[18px] rounded-radius-sm border px-pad-sm",
8904
+ "text-left outline-none transition-colors duration-150",
8905
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
8906
+ ],
8907
+ dot: "size-[7px] shrink-0 rounded-radius-full",
8908
+ label: "min-w-0 flex-1 truncate text-caption-sm text-fg-default"
8909
+ },
8910
+ variants: {
8911
+ colorKey: {
8912
+ "chart-1": { root: "border-chart-1/40 bg-chart-1/14 hover:bg-chart-1/24", dot: "bg-chart-1" },
8913
+ "chart-2": { root: "border-chart-2/40 bg-chart-2/14 hover:bg-chart-2/24", dot: "bg-chart-2" },
8914
+ "chart-3": { root: "border-chart-3/40 bg-chart-3/14 hover:bg-chart-3/24", dot: "bg-chart-3" },
8915
+ "chart-4": { root: "border-chart-4/40 bg-chart-4/14 hover:bg-chart-4/24", dot: "bg-chart-4" },
8916
+ "chart-5": { root: "border-chart-5/40 bg-chart-5/14 hover:bg-chart-5/24", dot: "bg-chart-5" },
8917
+ brand: { root: "border-border-brand-subtle bg-bg-brand-subtle hover:bg-bg-brand-subtle-hover", dot: "bg-bg-brand" },
8918
+ success: { root: "border-border-success-muted bg-bg-success-muted", dot: "bg-bg-success" },
8919
+ warning: { root: "border-border-warning-muted bg-bg-warning-muted", dot: "bg-bg-warning" },
8920
+ danger: { root: "border-border-danger-muted bg-bg-danger-muted", dot: "bg-bg-danger" },
8921
+ info: { root: "border-border-info-muted bg-bg-info-muted", dot: "bg-bg-info" },
8922
+ neutral: { root: "border-border-default bg-bg-muted", dot: "bg-fg-subtle" }
8923
+ },
8924
+ continuesBefore: { true: { root: "rounded-l-none border-l-0" }, false: {} },
8925
+ continuesAfter: { true: { root: "rounded-r-none border-r-0" }, false: {} },
8926
+ conflict: { true: { root: "border-dashed !border-border-danger-muted" }, false: {} },
8927
+ critical: { true: { root: "ring-2 ring-bg-danger/50" }, false: {} }
8928
+ },
8929
+ defaultVariants: {
8930
+ colorKey: "chart-1",
8931
+ continuesBefore: false,
8932
+ continuesAfter: false,
8933
+ conflict: false,
8934
+ critical: false
8935
+ }
8936
+ });
8937
+ const ganttDayAdd = avatar.tv({
8938
+ base: [
8939
+ "absolute bottom-pad-sm right-pad-sm z-[2]",
8940
+ "grid size-icon-lg place-items-center rounded-radius-md",
8941
+ "border border-border-subtle bg-bg-surface text-fg-muted",
8942
+ "opacity-0 transition-opacity duration-150",
8943
+ "group-hover/cell:opacity-100 focus-visible:opacity-100",
8944
+ "hover:border-border-brand hover:text-fg-brand",
8945
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
8946
+ "[&_svg]:size-icon-sm"
8947
+ ]
8948
+ });
8949
+ const ganttMonthCell = avatar.tv({
8950
+ base: [
8951
+ // `group/cell` é o gatilho do hover do `ganttDayAdd`. Nome com barra pra
8952
+ // não colidir com o `group/row` da grade esquerda.
8953
+ "group/cell relative flex min-h-0 min-w-0 flex-col overflow-hidden",
8954
+ "border-b border-r border-border-subtle p-pad-sm",
8955
+ "[&:nth-child(7n)]:border-r-0",
8956
+ "transition-colors duration-150"
8957
+ ],
8958
+ variants: {
8959
+ /** Dia de fora do mês âncora: presente, mas recuado. */
8960
+ /**
8961
+ * ## O mês é UNIFORME. O que se destaca é o que não é dele.
8962
+ *
8963
+ * Todo dia do mês âncora tem o mesmo fundo — útil ou fim de semana, tanto
8964
+ * faz. Só as células de OUTRO mês recebem `bg-canvas`, e a grade passa a
8965
+ * responder uma pergunta só: onde este mês começa e onde acaba.
8966
+ *
8967
+ * ⛔ **Não existe variante de fim de semana aqui, e isso é decisão.**
8968
+ * Sombrear sábado e domingo divide o mês em blocos verticais e compete com
8969
+ * a única distinção que a grade precisa fazer. Num calendário de agenda o
8970
+ * fim de semana importa (não se marca reunião); num CRONOGRAMA a barra
8971
+ * atravessa o fim de semana de qualquer jeito, e listrar as colunas só
8972
+ * atrapalha a leitura do segmento contínuo que passa por cima.
8973
+ *
8974
+ * ## Por que `bg-canvas` e não escurecer com `/NN`
8975
+ *
8976
+ * O dia de outro mês não é uma SOBREPOSIÇÃO sobre a grade: é o buraco por
8977
+ * onde se vê o fundo da página. O token que significa isso é `bg-canvas`
8978
+ * (0.205), e contra a superfície da grade (`bg-surface`, 0.225) ele dá
8979
+ * ΔL 0.020.
8980
+ *
8981
+ * ⚠️ 0.020 é pouco pra uma LINHA e suficiente pra uma ÁREA — contraste de
8982
+ * área grande é muito mais perceptível que de traço fino. É por isso que
8983
+ * aqui 0.020 lê enquanto na virada de mês do eixo 0.040 não lia.
8984
+ *
8985
+ * ## O histórico, porque ele explica as duas variantes que sumiram
8986
+ *
8987
+ * outside `bg-bg-subtle/40` → branco 1% × 40% = 0.004 de alfa
8988
+ * weekend `bg-bg-subtle/30` → branco 1% × 30% = 0.003 de alfa
8989
+ *
8990
+ * As duas abaixo do limiar de percepção: existiam no CSS e não pintavam
8991
+ * nada — 20 das 42 células com estilo que ninguém via. Ao consertar, eu
8992
+ * tornei o fim de semana visível TAMBÉM, e aí ele virou o defeito oposto:
8993
+ * as colunas de sábado e domingo escuras de ponta a ponta, com o mês
8994
+ * parecendo quatro blocos. O conserto certo era **remover**, não calibrar.
8995
+ *
8996
+ * ⛔ E o `text-fg-subtle` que morava na variante `outside` era CÓDIGO MORTO:
8997
+ * medido, o `ganttDayNumber` põe a própria cor no `<span>` e vence a
8998
+ * herança. Quem dim o número do dia de fora é a variante `outside` DELE.
8999
+ */
9000
+ outside: { true: "bg-bg-canvas", false: "" },
9001
+ today: { true: "bg-bg-brand-subtle", false: "" }
9002
+ },
9003
+ compoundVariants: [
9004
+ /**
9005
+ * `today` vence `outside`.
9006
+ *
9007
+ * Se hoje cai num dia de outro mês visível na grade, marcar HOJE é mais
9008
+ * importante que marcar não-é-deste-mês: é a única célula que o usuário
9009
+ * procura sem saber a data.
9010
+ */
9011
+ { outside: true, today: true, class: "bg-bg-brand-subtle" }
9012
+ ],
9013
+ defaultVariants: { outside: false, today: false }
9014
+ });
9015
+ const ganttDayNumber = avatar.tv({
9016
+ base: "text-caption-sm tabular-nums text-fg-muted",
9017
+ variants: {
9018
+ outside: { true: "text-fg-disabled", false: "" },
9019
+ today: {
9020
+ true: [
9021
+ "grid size-icon-lg place-items-center rounded-radius-full",
9022
+ "bg-bg-brand font-semibold text-fg-on-brand"
9023
+ ],
9024
+ false: ""
9025
+ }
9026
+ },
9027
+ compoundVariants: [
9028
+ // `today` vence `outside`: hoje num dia de outro mês ainda é hoje.
9029
+ { outside: true, today: true, class: "font-semibold text-fg-on-brand" }
9030
+ ],
9031
+ defaultVariants: { today: false, outside: false }
9032
+ });
9033
+ avatar.tv({
9034
+ slots: {
9035
+ root: [
9036
+ "flex w-full min-w-0 items-center gap-gp-2xs",
9037
+ "min-h-comp-2xs rounded-radius-sm border px-pad-sm",
9038
+ "text-left outline-none transition-colors duration-150",
9039
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
9040
+ ],
9041
+ dot: "size-[7px] shrink-0 rounded-radius-full",
9042
+ label: "min-w-0 flex-1 truncate text-caption-sm text-fg-default"
9043
+ },
9044
+ variants: {
9045
+ colorKey: {
9046
+ "chart-1": { root: "border-chart-1/40 bg-chart-1/14", dot: "bg-chart-1" },
9047
+ "chart-2": { root: "border-chart-2/40 bg-chart-2/14", dot: "bg-chart-2" },
9048
+ "chart-3": { root: "border-chart-3/40 bg-chart-3/14", dot: "bg-chart-3" },
9049
+ "chart-4": { root: "border-chart-4/40 bg-chart-4/14", dot: "bg-chart-4" },
9050
+ "chart-5": { root: "border-chart-5/40 bg-chart-5/14", dot: "bg-chart-5" },
9051
+ brand: { root: "border-border-brand-subtle bg-bg-brand-subtle", dot: "bg-bg-brand" },
9052
+ success: { root: "border-border-success-muted bg-bg-success-muted", dot: "bg-bg-success" },
9053
+ warning: { root: "border-border-warning-muted bg-bg-warning-muted", dot: "bg-bg-warning" },
9054
+ danger: { root: "border-border-danger-muted bg-bg-danger-muted", dot: "bg-bg-danger" },
9055
+ info: { root: "border-border-info-muted bg-bg-info-muted", dot: "bg-bg-info" },
9056
+ neutral: { root: "border-border-default bg-bg-muted", dot: "bg-fg-subtle" }
9057
+ }
9058
+ },
9059
+ defaultVariants: { colorKey: "chart-1" }
9060
+ });
9061
+ const ganttOverflowButton = avatar.tv({
9062
+ base: [
9063
+ "absolute inset-x-pad-sm z-[2] truncate rounded-radius-sm px-pad-2xs text-left",
9064
+ "text-caption-sm font-medium text-fg-muted",
9065
+ "transition-colors duration-150",
9066
+ "hover:bg-bg-muted hover:text-fg-default",
9067
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
9068
+ ]
9069
+ });
9070
+ const ganttListFrame = avatar.tv({
9071
+ base: [
9072
+ "flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain scrollbar-thin",
9073
+ "rounded-radius-xl border border-border-default bg-bg-surface"
9074
+ ]
9075
+ });
9076
+ const ganttListDay = avatar.tv({
9077
+ base: "flex flex-col gap-gp-md border-b border-border-subtle p-sp-xl last:border-b-0"
9078
+ });
9079
+ const ganttListDayHead = avatar.tv({
9080
+ base: [
9081
+ "sticky top-0 z-[1] -mx-sp-xl -mt-sp-xl px-sp-xl pb-pad-md pt-sp-xl",
9082
+ "flex items-baseline gap-gp-md bg-bg-surface"
9083
+ ]
9084
+ });
9085
+ const ganttListDayNumber = avatar.tv({
9086
+ base: "text-title-sm font-semibold tabular-nums",
9087
+ variants: {
9088
+ today: { true: "text-fg-brand", false: "text-fg-default" }
9089
+ },
9090
+ defaultVariants: { today: false }
9091
+ });
9092
+ const ganttListDayName = avatar.tv({
9093
+ base: "text-body-sm text-fg-muted first-letter:uppercase"
9094
+ });
9095
+ const ganttListItems = avatar.tv({
9096
+ base: "flex flex-col gap-gp-sm"
9097
+ });
9098
+ const ganttListItem = avatar.tv({
9099
+ slots: {
9100
+ root: [
9101
+ "group/item flex min-h-form-lg w-full min-w-0 cursor-pointer items-center gap-gp-md",
9102
+ "rounded-radius-lg border px-pad-xl py-pad-md text-left outline-none",
9103
+ "transition-[background-color,border-color] duration-150",
9104
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
9105
+ ],
9106
+ dot: "size-[9px] shrink-0 rounded-radius-full",
9107
+ body: "flex min-w-0 flex-1 flex-col",
9108
+ title: "truncate text-body-sm font-medium text-fg-default",
9109
+ sub: "truncate text-caption-sm text-fg-muted",
9110
+ meta: "shrink-0 text-caption-sm tabular-nums text-fg-muted"
9111
+ },
9112
+ variants: {
9113
+ colorKey: {
9114
+ "chart-1": { root: "border-chart-1/40 bg-chart-1/14 hover:bg-chart-1/24", dot: "bg-chart-1" },
9115
+ "chart-2": { root: "border-chart-2/40 bg-chart-2/14 hover:bg-chart-2/24", dot: "bg-chart-2" },
9116
+ "chart-3": { root: "border-chart-3/40 bg-chart-3/14 hover:bg-chart-3/24", dot: "bg-chart-3" },
9117
+ "chart-4": { root: "border-chart-4/40 bg-chart-4/14 hover:bg-chart-4/24", dot: "bg-chart-4" },
9118
+ "chart-5": { root: "border-chart-5/40 bg-chart-5/14 hover:bg-chart-5/24", dot: "bg-chart-5" },
9119
+ brand: { root: "border-border-brand-subtle bg-bg-brand-subtle hover:bg-bg-brand-subtle-hover", dot: "bg-bg-brand" },
9120
+ success: { root: "border-border-success-muted bg-bg-success-muted", dot: "bg-bg-success" },
9121
+ warning: { root: "border-border-warning-muted bg-bg-warning-muted", dot: "bg-bg-warning" },
9122
+ danger: { root: "border-border-danger-muted bg-bg-danger-muted", dot: "bg-bg-danger" },
9123
+ info: { root: "border-border-info-muted bg-bg-info-muted", dot: "bg-bg-info" },
9124
+ neutral: { root: "border-border-default bg-bg-muted", dot: "bg-fg-subtle" }
9125
+ },
9126
+ conflict: { true: { root: "border-dashed !border-border-danger-muted" }, false: {} },
9127
+ critical: { true: { root: "ring-2 ring-bg-danger/50" }, false: {} }
9128
+ },
9129
+ defaultVariants: { colorKey: "chart-1", conflict: false, critical: false }
9130
+ });
9131
+ const ganttEmpty = avatar.tv({
9132
+ base: [
9133
+ "flex min-h-[200px] flex-1 flex-col items-center justify-center gap-gp-md",
9134
+ "px-pad-3xl py-pad-3xl text-center"
9135
+ ]
9136
+ });
9137
+ const ganttEmptyText = avatar.tv({
9138
+ base: "text-body-sm text-fg-muted"
9139
+ });
9140
+ avatar.tv({
9141
+ base: [
9142
+ "flex flex-col items-center justify-center gap-gp-md",
9143
+ "rounded-radius-xl border border-border-default bg-bg-subtle",
9144
+ "px-pad-3xl py-pad-3xl text-center"
9145
+ ]
9146
+ });
9147
+ function normalizarTexto(s) {
9148
+ return s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
9149
+ }
9150
+ function comoLista(v) {
9151
+ if (v === void 0 || v === null) return [];
9152
+ if (Array.isArray(v)) return v.map((x) => String(x));
9153
+ if (v instanceof Date) return [v.toISOString()];
9154
+ return [String(v)];
9155
+ }
9156
+ function comoNumero(v) {
9157
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
9158
+ if (typeof v === "string" && v.trim() !== "") {
9159
+ const n = Number(v.replace(",", "."));
9160
+ return Number.isFinite(n) ? n : null;
9161
+ }
9162
+ return null;
9163
+ }
9164
+ const SO_DIA = /^(\d{4})-(\d{2})-(\d{2})$/;
9165
+ function parseDiaISO(s) {
9166
+ const m = SO_DIA.exec(s.trim());
9167
+ if (m) {
9168
+ const d2 = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
9169
+ return Number.isNaN(d2.getTime()) ? null : d2;
9170
+ }
9171
+ const d = new Date(s);
9172
+ return Number.isNaN(d.getTime()) ? null : dateFns.startOfDay(d);
9173
+ }
9174
+ function comoData(v) {
9175
+ if (v instanceof Date) return Number.isNaN(v.getTime()) ? null : dateFns.startOfDay(v);
9176
+ if (typeof v === "string" && v.trim() !== "") return parseDiaISO(v);
9177
+ return null;
9178
+ }
9179
+ function operadorDoCampo(kind, values) {
9180
+ const [a = "", b = ""] = values;
9181
+ const temA = a.trim() !== "";
9182
+ const temB = b.trim() !== "";
9183
+ switch (kind) {
9184
+ case "text":
9185
+ return "contém";
9186
+ case "number":
9187
+ if (temA && temB) return "entre";
9188
+ if (temA) return "≥";
9189
+ return "≤";
9190
+ case "date":
9191
+ if (temA && temB) return "entre";
9192
+ if (temA) return "a partir de";
9193
+ return "até";
9194
+ case "multi":
9195
+ case "single":
9196
+ case "boolean":
9197
+ default:
9198
+ return "é";
9199
+ }
9200
+ }
9201
+ function campoVazio(kind, values) {
9202
+ if (!values || values.length === 0) return true;
9203
+ if (kind === "number" || kind === "date" || kind === "text") {
9204
+ return values.every((v) => v.trim() === "");
9205
+ }
9206
+ return false;
9207
+ }
9208
+ function linhaPassaNoCampo(campo, values, row) {
9209
+ const kind = campo.kind ?? "multi";
9210
+ if (campoVazio(kind, values)) return true;
9211
+ const vs = values ?? [];
9212
+ const bruto = campo.accessor(row);
9213
+ switch (kind) {
9214
+ case "multi":
9215
+ case "single": {
9216
+ const lista = comoLista(bruto);
9217
+ if (lista.length === 0) return false;
9218
+ return lista.some((x) => vs.includes(x));
9219
+ }
9220
+ case "boolean": {
9221
+ const lista = comoLista(bruto);
9222
+ if (lista.length === 0) return false;
9223
+ const normal = lista.map((x) => {
9224
+ const n = normalizarTexto(x);
9225
+ if (n === "1" || n === "sim" || n === "true" || n === "yes") return "true";
9226
+ if (n === "0" || n === "nao" || n === "false" || n === "no") return "false";
9227
+ return n;
9228
+ });
9229
+ return normal.some((x) => vs.includes(x));
9230
+ }
9231
+ case "text": {
9232
+ const termo = normalizarTexto(vs[0] ?? "");
9233
+ if (termo === "") return true;
9234
+ const lista = comoLista(bruto);
9235
+ if (lista.length === 0) return false;
9236
+ return lista.some((x) => normalizarTexto(x).includes(termo));
9237
+ }
9238
+ case "number": {
9239
+ const n = comoNumero(bruto);
9240
+ if (n === null) return false;
9241
+ const min = comoNumero(vs[0]);
9242
+ const max = comoNumero(vs[1]);
9243
+ if (min !== null && n < min) return false;
9244
+ if (max !== null && n > max) return false;
9245
+ return true;
9246
+ }
9247
+ case "date": {
9248
+ const d = comoData(bruto);
9249
+ if (d === null) return false;
9250
+ const de = comoData(vs[0]);
9251
+ const ate = comoData(vs[1]);
9252
+ if (de !== null && dateFns.differenceInCalendarDays(d, de) < 0) return false;
9253
+ if (ate !== null && dateFns.differenceInCalendarDays(d, ate) > 0) return false;
9254
+ return true;
9255
+ }
9256
+ default:
9257
+ return true;
9258
+ }
9259
+ }
9260
+ function aplicarFiltros(rows, fields, model) {
9261
+ if (!fields || fields.length === 0) return [...rows];
9262
+ let saida = [...rows];
9263
+ for (const campo of fields) {
9264
+ const values = model[campo.id];
9265
+ if (campoVazio(campo.kind ?? "multi", values)) continue;
9266
+ saida = saida.filter((r) => linhaPassaNoCampo(campo, values, r));
9267
+ }
9268
+ return saida;
9269
+ }
9270
+ function contarAplicados(fields, model) {
9271
+ if (!fields) return 0;
9272
+ return fields.filter(
9273
+ (c) => !campoVazio(c.kind ?? "multi", model[c.id])
9274
+ ).length;
9275
+ }
9276
+ function valoresDoChip(campo, values, formatarData) {
9277
+ const kind = campo.kind ?? "multi";
9278
+ const vs = values ?? [];
9279
+ const dia = (iso) => formatarData ? formatarData(iso) : iso;
9280
+ switch (kind) {
9281
+ case "multi":
9282
+ case "single":
9283
+ return (campo.options ?? []).filter((o) => vs.includes(o.value)).map((o) => o.label);
9284
+ case "boolean": {
9285
+ const achou = (campo.options ?? []).find((o) => vs.includes(o.value));
9286
+ return [achou?.label ?? (vs[0] === "true" ? "Sim" : "Não")];
9287
+ }
9288
+ case "text":
9289
+ return vs[0] ? [vs[0]] : [];
9290
+ case "number": {
9291
+ const [a = "", b = ""] = vs;
9292
+ if (a.trim() && b.trim()) return [`${a} e ${b}`];
9293
+ return [a.trim() || b.trim()];
9294
+ }
9295
+ case "date": {
9296
+ const [a = "", b = ""] = vs;
9297
+ if (a.trim() && b.trim()) return [`${dia(a)} e ${dia(b)}`];
9298
+ return [a.trim() ? dia(a) : dia(b)];
9299
+ }
9300
+ default:
9301
+ return [];
9302
+ }
9303
+ }
9304
+ const MAX_VALORES_VISIVEIS = 3;
9305
+ function GanttAppliedFilters({
9306
+ fields,
9307
+ model,
9308
+ counts,
9309
+ onToggleValue,
9310
+ onClearField,
9311
+ onClearAll,
9312
+ formatDate
9313
+ }) {
9314
+ const [aberto, setAberto] = React.useState(null);
9315
+ const aplicados = fields.filter(
9316
+ (campo) => !campoVazio(campo.kind ?? "multi", model[campo.id])
9317
+ );
9318
+ if (aplicados.length === 0) return null;
9319
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttAppliedRow(), children: [
9320
+ aplicados.map((campo) => {
9321
+ const kind = campo.kind ?? "multi";
9322
+ const marcados = model[campo.id] ?? [];
9323
+ const operador = operadorDoCampo(kind, marcados);
9324
+ const rotulos = valoresDoChip(campo, marcados, formatDate);
9325
+ const colapsa = rotulos.length > MAX_VALORES_VISIVEIS;
9326
+ const editavel = kind === "multi" || kind === "single" || kind === "boolean";
9327
+ const conteudoDoChip = /* @__PURE__ */ jsxRuntime.jsxs(
9328
+ "span",
9329
+ {
9330
+ role: editavel ? "button" : void 0,
9331
+ tabIndex: editavel ? 0 : void 0,
9332
+ "aria-label": editavel ? `Editar filtro ${campo.label}` : void 0,
9333
+ className: ganttAppliedChip({ interactive: editavel }),
9334
+ onKeyDown: (e) => {
9335
+ if (editavel && (e.key === "Enter" || e.key === " ")) {
9336
+ e.preventDefault();
9337
+ setAberto((a) => a === campo.id ? null : campo.id);
9338
+ }
9339
+ },
9340
+ children: [
9341
+ /* @__PURE__ */ jsxRuntime.jsx(
9342
+ "button",
9343
+ {
9344
+ type: "button",
9345
+ "aria-label": `Remover filtro ${campo.label}`,
9346
+ className: ganttAppliedChipRemove(),
9347
+ onClick: (e) => {
9348
+ e.stopPropagation();
9349
+ onClearField(campo.id);
9350
+ },
9351
+ onKeyDown: (e) => e.stopPropagation(),
9352
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleX, { strokeWidth: 2, "aria-hidden": true })
9353
+ }
9354
+ ),
9355
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttAppliedChipName(), children: campo.label }),
9356
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttAppliedChipOp(), children: operador }),
9357
+ colapsa ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttAppliedChipValue(), children: `${rotulos.length} selecionados` }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center gap-gp-2xs", children: rotulos.map((r) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttAppliedChipValue(), children: r }, r)) })
9358
+ ]
9359
+ }
9360
+ );
9361
+ if (!editavel) {
9362
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: conteudoDoChip }, campo.id);
9363
+ }
9364
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9365
+ avatar.Popover,
9366
+ {
9367
+ open: aberto === campo.id,
9368
+ onOpenChange: (v) => setAberto(v ? campo.id : null),
9369
+ children: [
9370
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.PopoverTrigger, { asChild: true, children: conteudoDoChip }),
9371
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.PopoverContent, { align: "start", className: "w-[260px] p-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-[320px] flex-col overflow-y-auto py-pad-sm", children: (campo.options ?? []).map((opcao) => {
9372
+ const id = `gantt-chip-${campo.id}-${opcao.value}`;
9373
+ const n = counts[campo.id]?.[opcao.value] ?? 0;
9374
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: ganttFilterOption(), children: [
9375
+ /* @__PURE__ */ jsxRuntime.jsx(
9376
+ sheet.Checkbox,
9377
+ {
9378
+ id,
9379
+ checked: marcados.includes(opcao.value),
9380
+ onCheckedChange: () => onToggleValue(campo.id, opcao.value),
9381
+ className: ganttFilterOptionBox({
9382
+ colorKey: opcao.colorKey ?? "neutral"
9383
+ })
9384
+ }
9385
+ ),
9386
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionLabel(), children: opcao.label }),
9387
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionCount(), children: n })
9388
+ ] }, opcao.value);
9389
+ }) }) })
9390
+ ]
9391
+ },
9392
+ campo.id
9393
+ );
9394
+ }),
9395
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onClearAll, className: ganttClearLink(), children: "Limpar todos" })
9396
+ ] });
9397
+ }
9398
+ const VIEW_ITEMS = [
9399
+ { value: "timeline", label: "Cronograma", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChartNoAxesGantt, {}) },
9400
+ { value: "calendar", label: "Calendário", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, {}) },
9401
+ { value: "list", label: "Lista", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, {}) }
9402
+ ];
9403
+ const ZOOM_ITEMS = [
9404
+ { value: "day", label: "Dia" },
9405
+ { value: "week", label: "Semana" },
9406
+ { value: "month", label: "Mês" },
9407
+ { value: "quarter", label: "Trimestre" }
9408
+ ];
9409
+ function GanttToolbar({
9410
+ title: title2,
9411
+ onPrev,
9412
+ onNext,
9413
+ onToday,
9414
+ view,
9415
+ views,
9416
+ onViewChange,
9417
+ granularity,
9418
+ onGranularityChange,
9419
+ searchable,
9420
+ search,
9421
+ onSearchChange,
9422
+ filterFields,
9423
+ onOpenFilterPanel,
9424
+ appliedCount,
9425
+ filterModel,
9426
+ onClearField,
9427
+ onClearAll,
9428
+ onToggleFilterValue,
9429
+ filterCounts,
9430
+ formatFilterDate,
9431
+ hasLinks,
9432
+ showCriticalToggle,
9433
+ criticalPath,
9434
+ onToggleCriticalPath,
9435
+ toolbarActions,
9436
+ primaryAction
9437
+ }) {
9438
+ const zoomAtivo = ZOOM_ITEMS.find((z) => z.value === granularity) ?? ZOOM_ITEMS[0];
9439
+ const itensDeVisao = VIEW_ITEMS.filter((v) => views.includes(v.value));
9440
+ const mostraSeletorDeVisao = itensDeVisao.length > 1;
9441
+ const temFiltros = (filterFields?.length ?? 0) > 0;
9442
+ const filtroEngajado = appliedCount > 0;
9443
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
9444
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttToolbar(), children: [
9445
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttToolbarSide({ slot: "leading" }), children: [
9446
+ mostraSeletorDeVisao ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9447
+ /* @__PURE__ */ jsxRuntime.jsx(
9448
+ "div",
9449
+ {
9450
+ role: "radiogroup",
9451
+ "aria-label": "Visualização",
9452
+ className: ganttViewSwitch(),
9453
+ children: itensDeVisao.map((v) => {
9454
+ const ativa = v.value === view;
9455
+ return /* @__PURE__ */ jsxRuntime.jsx(
9456
+ "button",
9457
+ {
9458
+ type: "button",
9459
+ role: "radio",
9460
+ "aria-checked": ativa,
9461
+ "aria-label": v.label,
9462
+ title: v.label,
9463
+ onClick: () => onViewChange(v.value),
9464
+ className: ganttViewSwitchButton({ isActive: ativa }),
9465
+ children: v.icon
9466
+ },
9467
+ v.value
9468
+ );
9469
+ })
9470
+ }
9471
+ ),
9472
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttToolbarDivider(), "aria-hidden": true })
9473
+ ] }) : null,
9474
+ /* @__PURE__ */ jsxRuntime.jsxs(
9475
+ "div",
9476
+ {
9477
+ role: "group",
9478
+ "aria-label": "Navegação de período",
9479
+ className: ganttNavGroup(),
9480
+ children: [
9481
+ /* @__PURE__ */ jsxRuntime.jsx(
9482
+ avatar.Button,
9483
+ {
9484
+ variant: "soft",
9485
+ color: "secondary",
9486
+ size: "icon-md",
9487
+ onClick: onPrev,
9488
+ "aria-label": "Período anterior",
9489
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, {})
9490
+ }
9491
+ ),
9492
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.Button, { variant: "soft", color: "secondary", size: "md", onClick: onToday, children: "Hoje" }),
9493
+ /* @__PURE__ */ jsxRuntime.jsx(
9494
+ avatar.Button,
9495
+ {
9496
+ variant: "soft",
9497
+ color: "secondary",
9498
+ size: "icon-md",
9499
+ onClick: onNext,
9500
+ "aria-label": "Próximo período",
9501
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, {})
9502
+ }
9503
+ )
9504
+ ]
9505
+ }
9506
+ ),
9507
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: ganttTitle(), children: [
9508
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, { "aria-hidden": true }),
9509
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttTitleText(), children: title2 })
9510
+ ] }),
9511
+ /* @__PURE__ */ jsxRuntime.jsx(
9512
+ GanttMobileMenu,
9513
+ {
9514
+ view,
9515
+ views,
9516
+ onViewChange,
9517
+ granularity,
9518
+ onGranularityChange,
9519
+ onPrev,
9520
+ onToday,
9521
+ onNext,
9522
+ hasLinks,
9523
+ showCriticalToggle,
9524
+ criticalPath,
9525
+ onToggleCriticalPath
9526
+ }
9527
+ )
9528
+ ] }),
9529
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttToolbarSide({ slot: "trailing" }), children: [
9530
+ searchable ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: ganttSearch(), children: [
9531
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { "aria-hidden": true }),
9532
+ /* @__PURE__ */ jsxRuntime.jsx(
9533
+ "input",
9534
+ {
9535
+ type: "search",
9536
+ value: search,
9537
+ onChange: (e) => onSearchChange(e.target.value),
9538
+ placeholder: "Buscar tarefa…",
9539
+ "aria-label": "Buscar tarefa",
9540
+ className: ganttSearchInput()
9541
+ }
9542
+ )
9543
+ ] }) : null,
9544
+ toolbarActions,
9545
+ hasLinks && showCriticalToggle ? /* @__PURE__ */ jsxRuntime.jsx(
9546
+ avatar.Button,
9547
+ {
9548
+ variant: criticalPath ? "soft" : "outline",
9549
+ color: criticalPath ? "primary" : "secondary",
9550
+ size: "md",
9551
+ iconLeft: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, {}),
9552
+ onClick: onToggleCriticalPath,
9553
+ "aria-pressed": criticalPath,
9554
+ "aria-label": "Caminho crítico",
9555
+ className: avatar.cn(
9556
+ // `max-md:hidden` — em <md ele vira item do menu de opções.
9557
+ "shrink-0 max-md:hidden max-lg:size-form-lg max-lg:p-0",
9558
+ criticalPath && "border-border-brand hover:border-border-brand"
9559
+ ),
9560
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden lg:inline", children: "Crítico" })
9561
+ }
9562
+ ) : null,
9563
+ view === "timeline" ? /* @__PURE__ */ jsxRuntime.jsxs(avatar.DropdownMenu, { children: [
9564
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
9565
+ avatar.Button,
9566
+ {
9567
+ variant: "outline",
9568
+ color: "secondary",
9569
+ size: "md",
9570
+ iconLeft: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Columns3, {}),
9571
+ iconRight: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, {}),
9572
+ "aria-label": `Escala: ${zoomAtivo.label}`,
9573
+ className: "shrink-0 max-md:hidden",
9574
+ children: zoomAtivo.label
9575
+ }
9576
+ ) }),
9577
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.DropdownMenuContent, { align: "end", className: "min-w-[168px]", children: /* @__PURE__ */ jsxRuntime.jsx(
9578
+ avatar.DropdownMenuRadioGroup,
9579
+ {
9580
+ value: granularity,
9581
+ onValueChange: (v) => onGranularityChange(v),
9582
+ children: ZOOM_ITEMS.map((z) => /* @__PURE__ */ jsxRuntime.jsx(avatar.DropdownMenuRadioItem, { value: z.value, children: z.label }, z.value))
9583
+ }
9584
+ ) })
9585
+ ] }) : null,
9586
+ temFiltros ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative shrink-0", children: [
9587
+ /* @__PURE__ */ jsxRuntime.jsx(
9588
+ avatar.Button,
9589
+ {
9590
+ variant: filtroEngajado ? "soft" : "outline",
9591
+ color: filtroEngajado ? "primary" : "secondary",
9592
+ size: "icon-md",
9593
+ onClick: onOpenFilterPanel,
9594
+ "aria-label": "Filtro",
9595
+ title: "Filtro",
9596
+ className: avatar.cn(
9597
+ filtroEngajado && "border-border-brand hover:border-border-brand"
9598
+ ),
9599
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Filter, {})
9600
+ }
9601
+ ),
9602
+ appliedCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterDot(), "aria-hidden": true }) : null
9603
+ ] }) : null,
9604
+ primaryAction
9605
+ ] })
9606
+ ] }),
9607
+ temFiltros ? /* @__PURE__ */ jsxRuntime.jsx(
9608
+ GanttAppliedFilters,
9609
+ {
9610
+ fields: filterFields ?? [],
9611
+ model: filterModel,
9612
+ counts: filterCounts,
9613
+ onToggleValue: onToggleFilterValue,
9614
+ onClearField,
9615
+ onClearAll,
9616
+ formatDate: formatFilterDate
9617
+ }
9618
+ ) : null
9619
+ ] });
9620
+ }
9621
+ function GanttMobileMenu({
9622
+ view,
9623
+ views,
9624
+ onViewChange,
9625
+ granularity,
9626
+ onGranularityChange,
9627
+ onPrev,
9628
+ onToday,
9629
+ onNext,
9630
+ hasLinks,
9631
+ showCriticalToggle,
9632
+ criticalPath,
9633
+ onToggleCriticalPath
9634
+ }) {
9635
+ const estilos = ganttMobileMenu();
9636
+ const mostraCritico = hasLinks && showCriticalToggle;
9637
+ const itensDeVisao = VIEW_ITEMS.filter((v) => views.includes(v.value));
9638
+ return /* @__PURE__ */ jsxRuntime.jsxs(avatar.Popover, { children: [
9639
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
9640
+ avatar.Button,
9641
+ {
9642
+ variant: "outline",
9643
+ color: "secondary",
9644
+ size: "icon-md",
9645
+ "aria-label": "Opções do cronograma",
9646
+ title: "Opções do cronograma",
9647
+ className: estilos.trigger(),
9648
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SlidersHorizontal, {})
9649
+ }
9650
+ ) }),
9651
+ /* @__PURE__ */ jsxRuntime.jsxs(avatar.PopoverContent, { align: "end", className: estilos.content(), children: [
9652
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: estilos.label(), children: "Período" }),
9653
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: estilos.field(), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttMenuGroupFluid(), children: /* @__PURE__ */ jsxRuntime.jsxs(
9654
+ "div",
9655
+ {
9656
+ role: "group",
9657
+ "aria-label": "Navegação de período",
9658
+ className: avatar.cn(ganttNavGroup(), "!flex w-full"),
9659
+ children: [
9660
+ /* @__PURE__ */ jsxRuntime.jsx(
9661
+ avatar.Button,
9662
+ {
9663
+ variant: "soft",
9664
+ color: "secondary",
9665
+ size: "icon-md",
9666
+ onClick: onPrev,
9667
+ "aria-label": "Período anterior",
9668
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, {})
9669
+ }
9670
+ ),
9671
+ /* @__PURE__ */ jsxRuntime.jsx(
9672
+ avatar.Button,
9673
+ {
9674
+ variant: "soft",
9675
+ color: "secondary",
9676
+ size: "md",
9677
+ onClick: onToday,
9678
+ className: "flex-1",
9679
+ children: "Hoje"
9680
+ }
9681
+ ),
9682
+ /* @__PURE__ */ jsxRuntime.jsx(
9683
+ avatar.Button,
9684
+ {
9685
+ variant: "soft",
9686
+ color: "secondary",
9687
+ size: "icon-md",
9688
+ onClick: onNext,
9689
+ "aria-label": "Próximo período",
9690
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, {})
9691
+ }
9692
+ )
9693
+ ]
9694
+ }
9695
+ ) }) }),
9696
+ itensDeVisao.length > 1 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9697
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: estilos.separator() }),
9698
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: estilos.label(), children: "Visualização" }),
9699
+ itensDeVisao.map((v) => /* @__PURE__ */ jsxRuntime.jsxs(
9700
+ "button",
9701
+ {
9702
+ type: "button",
9703
+ role: "radio",
9704
+ "aria-checked": v.value === view,
9705
+ onClick: () => onViewChange(v.value),
9706
+ className: estilos.item({ active: v.value === view }),
9707
+ children: [
9708
+ v.icon,
9709
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: v.label }),
9710
+ v.value === view ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { strokeWidth: 2.2 }) : null
9711
+ ]
9712
+ },
9713
+ v.value
9714
+ ))
9715
+ ] }) : null,
9716
+ view === "timeline" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9717
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: estilos.separator() }),
9718
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: estilos.label(), children: "Escala" }),
9719
+ ZOOM_ITEMS.map((z) => /* @__PURE__ */ jsxRuntime.jsxs(
9720
+ "button",
9721
+ {
9722
+ type: "button",
9723
+ role: "radio",
9724
+ "aria-checked": z.value === granularity,
9725
+ onClick: () => onGranularityChange(z.value),
9726
+ className: estilos.item({ active: z.value === granularity }),
9727
+ children: [
9728
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Columns3, {}),
9729
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: z.label }),
9730
+ z.value === granularity ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { strokeWidth: 2.2 }) : null
9731
+ ]
9732
+ },
9733
+ z.value
9734
+ ))
9735
+ ] }) : null,
9736
+ mostraCritico ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9737
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: estilos.separator() }),
9738
+ /* @__PURE__ */ jsxRuntime.jsxs(
9739
+ "button",
9740
+ {
9741
+ type: "button",
9742
+ "aria-pressed": criticalPath,
9743
+ onClick: onToggleCriticalPath,
9744
+ className: estilos.item({ active: criticalPath }),
9745
+ children: [
9746
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, {}),
9747
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: "Caminho crítico" }),
9748
+ criticalPath ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { strokeWidth: 2.2 }) : null
9749
+ ]
9750
+ }
9751
+ )
9752
+ ] }) : null
9753
+ ] })
9754
+ ] });
9755
+ }
9756
+ const LIMIAR_DE_BUSCA = 7;
9757
+ function GanttFilterPanel({
9758
+ open,
9759
+ onOpenChange,
9760
+ fields,
9761
+ model,
9762
+ onToggleValue,
9763
+ onSetFieldValues,
9764
+ onClearAll,
9765
+ appliedCount,
9766
+ counts
9767
+ }) {
9768
+ const [recolhidos, setRecolhidos] = React.useState(() => /* @__PURE__ */ new Set());
9769
+ const [buscas, setBuscas] = React.useState({});
9770
+ const alternarGrupo = (id) => setRecolhidos((prev) => {
9771
+ const proximo = new Set(prev);
9772
+ if (proximo.has(id)) proximo.delete(id);
9773
+ else proximo.add(id);
9774
+ return proximo;
9775
+ });
9776
+ return /* @__PURE__ */ jsxRuntime.jsx(
9777
+ panel.FloatingPanel,
9778
+ {
9779
+ open,
9780
+ onOpenChange,
9781
+ side: "right",
9782
+ size: "md",
9783
+ title: "Filtros",
9784
+ titleIcon: lucideReact.SlidersHorizontal,
9785
+ description: appliedCount === 1 ? "1 filtro ativo" : `${appliedCount} filtros ativos`,
9786
+ bodyPadded: false,
9787
+ footer: /* @__PURE__ */ jsxRuntime.jsx(
9788
+ avatar.Button,
9789
+ {
9790
+ variant: "ghost",
9791
+ color: "secondary",
9792
+ size: "sm",
9793
+ onClick: onClearAll,
9794
+ disabled: appliedCount === 0,
9795
+ children: "Limpar tudo"
9796
+ }
9797
+ ),
9798
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col", children: fields.map((campo) => /* @__PURE__ */ jsxRuntime.jsx(
9799
+ GrupoDeFiltro,
9800
+ {
9801
+ campo,
9802
+ marcadas: model[campo.id] ?? [],
9803
+ aberto: !recolhidos.has(campo.id),
9804
+ busca: buscas[campo.id] ?? "",
9805
+ contagens: counts?.[campo.id],
9806
+ onAlternarGrupo: () => alternarGrupo(campo.id),
9807
+ onBuscaChange: (v) => setBuscas((prev) => ({ ...prev, [campo.id]: v })),
9808
+ onToggleValue: (v) => onToggleValue(campo.id, v),
9809
+ onSetValues: (vs) => onSetFieldValues(campo.id, vs)
9810
+ },
9811
+ campo.id
9812
+ )) })
9813
+ }
9814
+ );
9815
+ }
9816
+ function GrupoDeFiltro({
9817
+ campo,
9818
+ marcadas,
9819
+ aberto,
9820
+ busca,
9821
+ contagens,
9822
+ onAlternarGrupo,
9823
+ onBuscaChange,
9824
+ onToggleValue,
9825
+ onSetValues
9826
+ }) {
9827
+ const kind = campo.kind ?? "multi";
9828
+ const temLista = kind === "multi" || kind === "single";
9829
+ const opcoes = campo.options ?? [];
9830
+ const temBusca = temLista && (campo.searchable ?? opcoes.length >= LIMIAR_DE_BUSCA);
9831
+ const visiveis = React.useMemo(() => {
9832
+ const termo = busca.trim().toLowerCase();
9833
+ if (!termo) return opcoes;
9834
+ return opcoes.filter((o) => o.label.toLowerCase().includes(termo));
9835
+ }, [opcoes, busca]);
9836
+ const idsVisiveis = visiveis.map((o) => o.value);
9837
+ const todasVisiveisMarcadas = idsVisiveis.length > 0 && idsVisiveis.every((v) => marcadas.includes(v));
9838
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttFilterGroup(), children: [
9839
+ /* @__PURE__ */ jsxRuntime.jsxs(
9840
+ "button",
9841
+ {
9842
+ type: "button",
9843
+ "aria-expanded": aberto,
9844
+ onClick: onAlternarGrupo,
9845
+ className: ganttFilterGroupHead(),
9846
+ children: [
9847
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: ganttFilterGroupTitle(), children: [
9848
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: campo.label }),
9849
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionCount(), children: temLista ? marcadas.length === 0 ? "Todas" : marcadas.length : marcadas.some((v) => v.trim() !== "") ? "1" : "—" })
9850
+ ] }),
9851
+ /* @__PURE__ */ jsxRuntime.jsx(
9852
+ lucideReact.ChevronDown,
9853
+ {
9854
+ className: ganttFilterGroupChevron({ open: aberto }),
9855
+ "aria-hidden": true
9856
+ }
9857
+ )
9858
+ ]
9859
+ }
9860
+ ),
9861
+ aberto ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9862
+ kind === "text" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttFilterRange(), children: /* @__PURE__ */ jsxRuntime.jsx(
9863
+ "input",
9864
+ {
9865
+ type: "text",
9866
+ value: marcadas[0] ?? "",
9867
+ onChange: (e) => onSetValues(e.target.value ? [e.target.value] : []),
9868
+ placeholder: campo.placeholder ?? `Contém…`,
9869
+ "aria-label": campo.label,
9870
+ className: ganttFilterInput()
9871
+ }
9872
+ ) }) : null,
9873
+ kind === "number" || kind === "date" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttFilterRange(), children: [
9874
+ /* @__PURE__ */ jsxRuntime.jsx(
9875
+ "input",
9876
+ {
9877
+ type: kind === "date" ? "date" : "number",
9878
+ value: marcadas[0] ?? "",
9879
+ onChange: (e) => onSetValues([e.target.value, marcadas[1] ?? ""]),
9880
+ placeholder: kind === "date" ? void 0 : "mín",
9881
+ "aria-label": `${campo.label} — de`,
9882
+ className: ganttFilterInput()
9883
+ }
9884
+ ),
9885
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterRangeSep(), children: "e" }),
9886
+ /* @__PURE__ */ jsxRuntime.jsx(
9887
+ "input",
9888
+ {
9889
+ type: kind === "date" ? "date" : "number",
9890
+ value: marcadas[1] ?? "",
9891
+ onChange: (e) => onSetValues([marcadas[0] ?? "", e.target.value]),
9892
+ placeholder: kind === "date" ? void 0 : "máx",
9893
+ "aria-label": `${campo.label} — até`,
9894
+ className: ganttFilterInput()
9895
+ }
9896
+ )
9897
+ ] }) : null,
9898
+ kind === "boolean" ? /* @__PURE__ */ jsxRuntime.jsx(
9899
+ toggleGroup.RadioGroup,
9900
+ {
9901
+ value: marcadas[0] ?? "",
9902
+ onValueChange: (v) => onSetValues(v ? [v] : []),
9903
+ className: "flex flex-col gap-0",
9904
+ children: (campo.options ?? [
9905
+ { value: "true", label: "Sim" },
9906
+ { value: "false", label: "Não" }
9907
+ ]).map((opt) => {
9908
+ const id = `gantt-filtro-${campo.id}-${opt.value}`;
9909
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: ganttFilterOption(), children: [
9910
+ /* @__PURE__ */ jsxRuntime.jsx(toggleGroup.RadioGroupItem, { id, value: opt.value, className: "shrink-0" }),
9911
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionLabel(), children: opt.label }),
9912
+ contagens?.[opt.value] !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionCount(), children: contagens[opt.value] }) : null
9913
+ ] }, opt.value);
9914
+ })
9915
+ }
9916
+ ) : null,
9917
+ temBusca ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: ganttFilterSearch(), children: [
9918
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { "aria-hidden": true }),
9919
+ /* @__PURE__ */ jsxRuntime.jsx(
9920
+ "input",
9921
+ {
9922
+ type: "search",
9923
+ value: busca,
9924
+ onChange: (e) => onBuscaChange(e.target.value),
9925
+ placeholder: `Buscar em ${campo.label.toLowerCase()}…`,
9926
+ "aria-label": `Buscar em ${campo.label}`,
9927
+ className: ganttFilterSearchInput()
9928
+ }
9929
+ )
9930
+ ] }) : null,
9931
+ kind === "multi" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttFilterGroupActions(), children: [
9932
+ /* @__PURE__ */ jsxRuntime.jsx(
9933
+ "button",
9934
+ {
9935
+ type: "button",
9936
+ onClick: () => onSetValues(
9937
+ todasVisiveisMarcadas ? marcadas.filter((v) => !idsVisiveis.includes(v)) : [.../* @__PURE__ */ new Set([...marcadas, ...idsVisiveis])]
9938
+ ),
9939
+ disabled: idsVisiveis.length === 0,
9940
+ children: todasVisiveisMarcadas ? "Desmarcar todas" : "Selecionar todas"
9941
+ }
9942
+ ),
9943
+ marcadas.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => onSetValues([]), children: "Limpar" }) : null
9944
+ ] }) : null,
9945
+ kind !== "multi" && marcadas.some((v) => v.trim() !== "") ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttFilterGroupActions(), children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => onSetValues([]), children: "Limpar" }) }) : null,
9946
+ !temLista ? null : visiveis.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: ganttFilterEmpty(), children: "Nada encontrado." }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
9947
+ kind === "single" ? (
9948
+ /*
9949
+ `single` = radio. Mesma anatomia de linha do `multi` (label
9950
+ nativo embrulhando o controle real — L-025), com o controle
9951
+ que expressa exclusividade.
9952
+ */
9953
+ /* @__PURE__ */ jsxRuntime.jsx(
9954
+ toggleGroup.RadioGroup,
9955
+ {
9956
+ value: marcadas[0] ?? "",
9957
+ onValueChange: (v) => onSetValues(v ? [v] : []),
9958
+ className: "flex flex-col gap-0",
9959
+ children: visiveis.map((opt) => {
9960
+ const id = `gantt-filtro-${campo.id}-${opt.value}`;
9961
+ const n = contagens?.[opt.value];
9962
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: ganttFilterOption(), children: [
9963
+ /* @__PURE__ */ jsxRuntime.jsx(toggleGroup.RadioGroupItem, { id, value: opt.value, className: "shrink-0" }),
9964
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionLabel(), children: opt.label }),
9965
+ n !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionCount(), children: n }) : null
9966
+ ] }, opt.value);
9967
+ })
9968
+ }
9969
+ )
9970
+ ) : null,
9971
+ kind === "multi" ? visiveis.map((opt) => {
9972
+ const id = `gantt-filtro-${campo.id}-${opt.value}`;
9973
+ const marcada = marcadas.includes(opt.value);
9974
+ const n = contagens?.[opt.value];
9975
+ return (
9976
+ /*
9977
+ `<label htmlFor>` nativo embrulhando o `Checkbox` — L-025.
9978
+ Com `<button onClick>` o leitor de tela anuncia "button" em
9979
+ vez de "checkbox", e o clique no rótulo não propaga pro
9980
+ controle real.
9981
+ */
9982
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: ganttFilterOption(), children: [
9983
+ /* @__PURE__ */ jsxRuntime.jsx(
9984
+ sheet.Checkbox,
9985
+ {
9986
+ id,
9987
+ checked: marcada,
9988
+ onCheckedChange: () => onToggleValue(opt.value),
9989
+ className: ganttFilterOptionBox({
9990
+ colorKey: opt.colorKey ?? "neutral"
9991
+ })
9992
+ }
9993
+ ),
9994
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionLabel(), children: opt.label }),
9995
+ n !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttFilterOptionCount(), children: n }) : null
9996
+ ] }, opt.value)
9997
+ );
9998
+ }) : null
9999
+ ] })
10000
+ ] }) : null
10001
+ ] });
10002
+ }
10003
+ const LARGURA_DO_NOME = [72, 58, 84, 64, 78, 52, 70, 60];
10004
+ const BARRAS = [
10005
+ { left: 4, width: 26 },
10006
+ { left: 18, width: 34 },
10007
+ { left: 30, width: 20 },
10008
+ { left: 44, width: 30 },
10009
+ { left: 38, width: 42 },
10010
+ { left: 60, width: 24 },
10011
+ { left: 55, width: 33 },
10012
+ { left: 70, width: 22 }
10013
+ ];
10014
+ function GanttSkeleton({
10015
+ view,
10016
+ gridWidth,
10017
+ rows = 8
10018
+ }) {
10019
+ const e = ganttSkeleton();
10020
+ const indices = Array.from({ length: rows }, (_, i) => i);
10021
+ const comum = {
10022
+ role: "status",
10023
+ "aria-busy": true,
10024
+ "aria-label": "Carregando cronograma"
10025
+ };
10026
+ if (view === "timeline") {
10027
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.root(), ...comum, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1", children: [
10028
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: e.pane(), style: { width: gridWidth }, children: [
10029
+ /* @__PURE__ */ jsxRuntime.jsxs(
10030
+ "div",
10031
+ {
10032
+ className: e.head(),
10033
+ style: { height: GANTT_HEAD_HEIGHT_PX },
10034
+ "aria-hidden": true,
10035
+ children: [
10036
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.bar(), style: { width: 64, height: 10 } }),
10037
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.bar(), style: { width: 44, height: 10 } })
10038
+ ]
10039
+ }
10040
+ ),
10041
+ indices.map((i) => /* @__PURE__ */ jsxRuntime.jsx(
10042
+ "div",
10043
+ {
10044
+ className: e.row(),
10045
+ style: { height: GANTT_ROW_HEIGHT_PX },
10046
+ "aria-hidden": true,
10047
+ children: /* @__PURE__ */ jsxRuntime.jsx(
10048
+ "div",
10049
+ {
10050
+ className: e.bar(),
10051
+ style: {
10052
+ width: `${LARGURA_DO_NOME[i % LARGURA_DO_NOME.length]}%`,
10053
+ height: 10
10054
+ }
10055
+ }
10056
+ )
10057
+ },
10058
+ i
10059
+ ))
10060
+ ] }),
10061
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: e.canvas(), children: [
10062
+ /* @__PURE__ */ jsxRuntime.jsx(
10063
+ "div",
10064
+ {
10065
+ className: e.head(),
10066
+ style: { height: GANTT_HEAD_HEIGHT_PX },
10067
+ "aria-hidden": true
10068
+ }
10069
+ ),
10070
+ indices.map((i) => {
10071
+ const b = BARRAS[i % BARRAS.length];
10072
+ return /* @__PURE__ */ jsxRuntime.jsx(
10073
+ "div",
10074
+ {
10075
+ className: e.row(),
10076
+ style: { height: GANTT_ROW_HEIGHT_PX },
10077
+ "aria-hidden": true,
10078
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-[22px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
10079
+ "div",
10080
+ {
10081
+ className: e.bar(),
10082
+ style: {
10083
+ position: "absolute",
10084
+ left: `${b.left}%`,
10085
+ width: `${b.width}%`,
10086
+ height: 22
10087
+ }
10088
+ }
10089
+ ) })
10090
+ },
10091
+ i
10092
+ );
10093
+ })
10094
+ ] })
10095
+ ] }) });
10096
+ }
10097
+ if (view === "calendar") {
10098
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.root(), ...comum, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.grid(), "aria-hidden": true, children: Array.from({ length: 42 }, (_, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: e.cell(), children: [
10099
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.bar(), style: { width: 16, height: 10 } }),
10100
+ i % 3 === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.bar(), style: { width: "80%", height: 14 } }) : null
10101
+ ] }, i)) }) });
10102
+ }
10103
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.root(), ...comum, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-gp-2xl p-pad-xl", "aria-hidden": true, children: indices.slice(0, 4).map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-gp-md", children: [
10104
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.bar(), style: { width: 140, height: 12 } }),
10105
+ Array.from({ length: i % 2 + 1 }, (_, j) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: e.bar(), style: { height: 44 } }, j))
10106
+ ] }, i)) }) });
10107
+ }
10108
+ const GANTT_DEFAULT_COLUMNS = [
10109
+ { id: "label", header: "Tarefa" },
10110
+ { id: "start", header: "Início", width: 92, align: "left" },
10111
+ { id: "end", header: "Fim", width: 92, align: "left" }
10112
+ ];
10113
+ const RECUO_POR_NIVEL_PX = 16;
10114
+ const MEIO_DO_CHEVRON_PX = 10;
10115
+ function ConectoresDeArvore({
10116
+ depth,
10117
+ isLast,
10118
+ ancestorHasNext
10119
+ }) {
10120
+ if (depth <= 0) return null;
10121
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttTreeRail(), "aria-hidden": true, children: Array.from({ length: depth }, (_, i) => {
10122
+ const esquerda = i * RECUO_POR_NIVEL_PX;
10123
+ if (i === depth - 1) {
10124
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: ganttTreeColumn(), style: { left: esquerda }, children: [
10125
+ /* @__PURE__ */ jsxRuntime.jsx(
10126
+ "span",
10127
+ {
10128
+ className: ganttTreeSegment(),
10129
+ style: { left: MEIO_DO_CHEVRON_PX, width: 1, top: 0, height: "50%" }
10130
+ }
10131
+ ),
10132
+ !isLast ? /* @__PURE__ */ jsxRuntime.jsx(
10133
+ "span",
10134
+ {
10135
+ className: ganttTreeSegment(),
10136
+ style: { left: MEIO_DO_CHEVRON_PX, width: 1, top: "50%", bottom: 0 }
10137
+ }
10138
+ ) : null,
10139
+ /* @__PURE__ */ jsxRuntime.jsx(
10140
+ "span",
10141
+ {
10142
+ className: ganttTreeSegment(),
10143
+ style: { left: MEIO_DO_CHEVRON_PX, right: 0, top: "50%", height: 1 }
10144
+ }
10145
+ )
10146
+ ] }, i);
10147
+ }
10148
+ if (!ancestorHasNext[i + 1]) return null;
10149
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttTreeColumn(), style: { left: esquerda }, children: /* @__PURE__ */ jsxRuntime.jsx(
10150
+ "span",
10151
+ {
10152
+ className: ganttTreeSegment(),
10153
+ style: { left: MEIO_DO_CHEVRON_PX, width: 1, top: 0, bottom: 0 }
10154
+ }
10155
+ ) }, i);
10156
+ }) });
10157
+ }
10158
+ function GanttGrid({
10159
+ rows,
10160
+ columns,
10161
+ heights,
10162
+ scrollTop,
10163
+ locale,
10164
+ selectedRow,
10165
+ hoveredRow,
10166
+ onHoverRow,
10167
+ onRowToggle,
10168
+ onRowClick
10169
+ }) {
10170
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10171
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttGridHead(), style: { height: GANTT_HEAD_HEIGHT_PX }, children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
10172
+ "div",
10173
+ {
10174
+ className: ganttGridHeadCell({ align: col.align ?? "left" }),
10175
+ style: col.width ? { width: col.width } : { flex: 1, minWidth: 0 },
10176
+ children: col.header
10177
+ },
10178
+ col.id
10179
+ )) }),
10180
+ /* @__PURE__ */ jsxRuntime.jsx(
10181
+ "div",
10182
+ {
10183
+ className: "min-h-0 flex-1 overflow-hidden",
10184
+ "aria-hidden": false,
10185
+ role: "rowgroup",
10186
+ children: /* @__PURE__ */ jsxRuntime.jsx(
10187
+ "div",
10188
+ {
10189
+ style: { transform: `translateY(${-scrollTop}px)` },
10190
+ onMouseLeave: () => onHoverRow?.(null),
10191
+ children: rows.map(
10192
+ ({ row, depth, hasChildren, collapsed, isLast, ancestorHasNext }, i) => /* @__PURE__ */ jsxRuntime.jsx(
10193
+ "div",
10194
+ {
10195
+ role: "row",
10196
+ className: ganttGridRow({
10197
+ type: row.type ?? "task",
10198
+ selected: selectedRow === i,
10199
+ hovered: hoveredRow === i
10200
+ }),
10201
+ style: { height: heights[i] },
10202
+ onMouseEnter: () => onHoverRow?.(i),
10203
+ onClick: (e) => onRowClick?.(row, e),
10204
+ children: (row.type ?? "task") === "summary" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "cell", className: ganttGridGroupCell(), children: [
10205
+ /* @__PURE__ */ jsxRuntime.jsx(
10206
+ ConectoresDeArvore,
10207
+ {
10208
+ depth,
10209
+ isLast,
10210
+ ancestorHasNext
10211
+ }
10212
+ ),
10213
+ /* @__PURE__ */ jsxRuntime.jsxs(
10214
+ "span",
10215
+ {
10216
+ className: "flex min-w-0 items-center gap-gp-sm",
10217
+ style: { paddingLeft: depth * 16 },
10218
+ children: [
10219
+ /* @__PURE__ */ jsxRuntime.jsx(
10220
+ "button",
10221
+ {
10222
+ type: "button",
10223
+ className: ganttChevron({
10224
+ open: !collapsed,
10225
+ hidden: !hasChildren
10226
+ }),
10227
+ "aria-label": collapsed ? "Expandir" : "Recolher",
10228
+ "aria-expanded": hasChildren ? !collapsed : void 0,
10229
+ tabIndex: hasChildren ? 0 : -1,
10230
+ onClick: (e) => {
10231
+ e.stopPropagation();
10232
+ onRowToggle?.(row.id, !collapsed);
10233
+ },
10234
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": true })
10235
+ }
10236
+ ),
10237
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: row.label })
10238
+ ]
10239
+ }
10240
+ ),
10241
+ row.trailing ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center", children: row.trailing }) : null
10242
+ ] }) : columns.map((col, i2) => {
10243
+ const primeira = i2 === 0;
10244
+ return /* @__PURE__ */ jsxRuntime.jsx(
10245
+ "div",
10246
+ {
10247
+ role: "cell",
10248
+ className: ganttGridCell({
10249
+ align: col.align ?? "left",
10250
+ numeric: col.id === "start" || col.id === "end",
10251
+ /**
10252
+ * #6 — só a PRIMEIRA coluna é informação.
10253
+ *
10254
+ * Data, duração e responsável são apoio: ficam em
10255
+ * `fg-muted` e peso normal. Com as quatro em
10256
+ * `fg-default`, a grade lia como um bloco de texto e o
10257
+ * nome da tarefa — que é o que se procura — não
10258
+ * ganhava nada por ser o primeiro.
10259
+ */
10260
+ tone: primeira ? "default" : "muted"
10261
+ }),
10262
+ style: col.width ? { width: col.width } : { flex: 1, minWidth: 0 },
10263
+ children: primeira ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10264
+ /* @__PURE__ */ jsxRuntime.jsx(
10265
+ ConectoresDeArvore,
10266
+ {
10267
+ depth,
10268
+ isLast,
10269
+ ancestorHasNext
10270
+ }
10271
+ ),
10272
+ /* @__PURE__ */ jsxRuntime.jsxs(
10273
+ "span",
10274
+ {
10275
+ className: "flex min-w-0 items-center gap-gp-sm",
10276
+ style: { paddingLeft: depth * 16 },
10277
+ children: [
10278
+ /* @__PURE__ */ jsxRuntime.jsx(
10279
+ "button",
10280
+ {
10281
+ type: "button",
10282
+ className: ganttChevron({
10283
+ open: !collapsed,
10284
+ hidden: !hasChildren
10285
+ }),
10286
+ "aria-label": collapsed ? "Expandir" : "Recolher",
10287
+ "aria-expanded": hasChildren ? !collapsed : void 0,
10288
+ tabIndex: hasChildren ? 0 : -1,
10289
+ onClick: (e) => {
10290
+ e.stopPropagation();
10291
+ onRowToggle?.(row.id, !collapsed);
10292
+ },
10293
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": true })
10294
+ }
10295
+ ),
10296
+ col.render ? col.render(row) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: ganttGridLabel(), children: [
10297
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttGridLabelText(), children: row.label }),
10298
+ row.sublabel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttGridSublabel(), children: row.sublabel }) : null
10299
+ ] })
10300
+ ]
10301
+ }
10302
+ ),
10303
+ row.trailing ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center pl-gp-2xs", children: row.trailing }) : null
10304
+ ] }) : col.render?.(row) ?? celulaDefault(col.id, row, locale)
10305
+ },
10306
+ col.id
10307
+ );
10308
+ })
10309
+ },
10310
+ row.id
10311
+ )
10312
+ )
10313
+ }
10314
+ )
10315
+ }
10316
+ )
10317
+ ] });
10318
+ }
10319
+ function celulaDefault(colId, row, locale) {
10320
+ const primeira = row.bars[0];
10321
+ if (!primeira) return null;
10322
+ if (colId === "start") return dateFns.format(primeira.start, "dd/MM/yy", { locale });
10323
+ if (colId === "end") return dateFns.format(primeira.end, "dd/MM/yy", { locale });
10324
+ if (colId === "duration") {
10325
+ const dias = Math.round(
10326
+ (primeira.end.getTime() - primeira.start.getTime()) / 864e5
10327
+ ) + 1;
10328
+ return `${dias}d`;
10329
+ }
10330
+ if (colId === "progress") {
10331
+ return primeira.progress === void 0 ? null : `${primeira.progress}%`;
10332
+ }
10333
+ return null;
10334
+ }
10335
+ function buildTimeAxis(windowStart, windowEnd, granularity, locale, weekStartsOn = 0) {
10336
+ const inicio = dateFns.startOfDay(windowStart);
10337
+ const fim = dateFns.startOfDay(windowEnd);
10338
+ const totalDays = Math.max(1, dateFns.differenceInCalendarDays(fim, inicio) + 1);
10339
+ const units = [];
10340
+ const groups = [];
10341
+ if (granularity === "day") {
10342
+ for (const dia of dateFns.eachDayOfInterval({ start: inicio, end: fim })) {
10343
+ const dow = dia.getDay();
10344
+ units.push({
10345
+ date: dia,
10346
+ spanDays: 1,
10347
+ label: dateFns.format(dia, "d", { locale }),
10348
+ isWeekend: dow === 0 || dow === 6
10349
+ });
10350
+ }
10351
+ empilharGrupos(groups, inicio, fim, "MMMM yyyy", dateFns.startOfMonth, locale);
10352
+ } else if (granularity === "week") {
10353
+ let cursor = dateFns.startOfWeek(inicio, { weekStartsOn });
10354
+ while (cursor <= fim) {
10355
+ const semanaFim = dateFns.startOfDay(dateFns.endOfWeek(cursor, { weekStartsOn }));
10356
+ const de = dateFns.max([dateFns.startOfDay(cursor), inicio]);
10357
+ const ate = dateFns.min([semanaFim, fim]);
10358
+ units.push({
10359
+ date: de,
10360
+ spanDays: dateFns.differenceInCalendarDays(ate, de) + 1,
10361
+ label: `${dateFns.format(de, "dd/MM", { locale })}`,
10362
+ isWeekend: false
10363
+ });
10364
+ cursor = dateFns.addDays(semanaFim, 1);
10365
+ }
10366
+ empilharGrupos(groups, inicio, fim, "MMMM yyyy", dateFns.startOfMonth, locale);
10367
+ } else if (granularity === "month") {
10368
+ empilharGrupos(units, inicio, fim, "MMM", dateFns.startOfMonth, locale);
10369
+ empilharGrupos(groups, inicio, fim, "yyyy", (d) => new Date(d.getFullYear(), 0, 1), locale);
10370
+ } else {
10371
+ empilharGrupos(units, inicio, fim, "QQQ", dateFns.startOfQuarter, locale);
10372
+ empilharGrupos(groups, inicio, fim, "yyyy", (d) => new Date(d.getFullYear(), 0, 1), locale);
10373
+ }
10374
+ return { groups, units, totalDays };
10375
+ }
10376
+ function empilharGrupos(saida, inicio, fim, formato, inicioDoPeriodo, locale) {
10377
+ let cursor = inicio;
10378
+ let guarda = 0;
10379
+ while (cursor <= fim && guarda < 600) {
10380
+ const dentroDoPeriodo = inicioDoPeriodo(cursor);
10381
+ const proximo = inicioDoPeriodo(dateFns.addDays(dentroDoPeriodo, diasDoPeriodo(formato)));
10382
+ const periodoFim = dateFns.addDays(proximo, -1);
10383
+ const ate = dateFns.min([periodoFim, fim]);
10384
+ saida.push({
10385
+ date: cursor,
10386
+ spanDays: dateFns.differenceInCalendarDays(ate, cursor) + 1,
10387
+ label: dateFns.format(cursor, formato, { locale }),
10388
+ isWeekend: false
10389
+ });
10390
+ cursor = dateFns.addDays(ate, 1);
10391
+ guarda++;
10392
+ }
10393
+ }
10394
+ function diasDoPeriodo(formato) {
10395
+ if (formato === "yyyy") return 366;
10396
+ if (formato === "QQQ") return 93;
10397
+ return 32;
10398
+ }
10399
+ function dateToDayOffset(date, windowStart) {
10400
+ const base = dateFns.startOfDay(windowStart);
10401
+ const dias = dateFns.differenceInCalendarDays(dateFns.startOfDay(date), base);
10402
+ const fracao = (date.getTime() - dateFns.startOfDay(date).getTime()) / 864e5;
10403
+ return dias + fracao;
10404
+ }
10405
+ function dateToX(date, windowStart, pxPerDay) {
10406
+ return dateToDayOffset(date, windowStart) * pxPerDay;
10407
+ }
10408
+ function xToDate(x, windowStart, pxPerDay) {
10409
+ if (pxPerDay <= 0) return dateFns.startOfDay(windowStart);
10410
+ const dias = x / pxPerDay;
10411
+ const inteiros = Math.floor(dias);
10412
+ const resto = dias - inteiros;
10413
+ const base = dateFns.addDays(dateFns.startOfDay(windowStart), inteiros);
10414
+ return new Date(base.getTime() + Math.round(resto * 864e5));
10415
+ }
10416
+ function snapDate(date, granularity, weekStartsOn = 0) {
10417
+ if (granularity === "day") return dateFns.startOfDay(date);
10418
+ if (granularity === "week") return dateFns.startOfWeek(date, { weekStartsOn });
10419
+ if (granularity === "month") return dateFns.startOfMonth(date);
10420
+ return dateFns.startOfQuarter(date);
10421
+ }
10422
+ function clipToWindow(bar, windowStart, windowEnd) {
10423
+ const jIni = dateFns.startOfDay(windowStart);
10424
+ const jFim = dateFns.startOfDay(windowEnd);
10425
+ const bIni = dateFns.startOfDay(bar.start);
10426
+ const bFim = dateFns.startOfDay(bar.end);
10427
+ if (bFim < jIni || bIni > jFim) return null;
10428
+ const antes = bIni < jIni;
10429
+ const depois = bFim > jFim;
10430
+ return {
10431
+ bar,
10432
+ start: antes ? jIni : bar.start,
10433
+ end: depois ? dateFns.addDays(jFim, 1) : bar.end,
10434
+ // O flag informado pelo consumidor VENCE o derivado: quem pagina no
10435
+ // servidor sabe que continua fora, e o componente não tem como saber.
10436
+ continuesBefore: bar.continuesBefore ?? antes,
10437
+ continuesAfter: bar.continuesAfter ?? depois
10438
+ };
10439
+ }
10440
+ function packLanes(bars, mode = "stack") {
10441
+ if (mode === "stack") return bars.map((b) => [b]);
10442
+ const lanes = [];
10443
+ const ordenadas = [...bars].sort((a, b) => a.start.getTime() - b.start.getTime());
10444
+ for (const barra of ordenadas) {
10445
+ const lane = lanes.find((l) => {
10446
+ const ultima = l[l.length - 1];
10447
+ return ultima.end.getTime() <= barra.start.getTime();
10448
+ });
10449
+ if (lane) lane.push(barra);
10450
+ else lanes.push([barra]);
10451
+ }
10452
+ return lanes;
10453
+ }
10454
+ function flattenRows(rows, collapsedIds) {
10455
+ const porPai = /* @__PURE__ */ new Map();
10456
+ const ids = new Set(rows.map((r) => r.id));
10457
+ for (const row of rows) {
10458
+ const pai = row.parent && ids.has(row.parent) ? row.parent : null;
10459
+ const lista = porPai.get(pai);
10460
+ if (lista) lista.push(row);
10461
+ else porPai.set(pai, [row]);
10462
+ }
10463
+ const saida = [];
10464
+ const visitados = /* @__PURE__ */ new Set();
10465
+ const visitarIrmaos = (irmaos, depth, ancestorHasNext) => {
10466
+ irmaos.forEach((row, idx) => {
10467
+ if (visitados.has(row.id)) return;
10468
+ visitados.add(row.id);
10469
+ const filhos = porPai.get(row.id) ?? [];
10470
+ const colapsada = collapsedIds.has(row.id) || (row.collapsed ?? false);
10471
+ const isLast = idx === irmaos.length - 1;
10472
+ saida.push({
10473
+ row,
10474
+ depth,
10475
+ hasChildren: filhos.length > 0,
10476
+ collapsed: colapsada,
10477
+ isLast,
10478
+ ancestorHasNext
10479
+ });
10480
+ if (!colapsada) {
10481
+ visitarIrmaos(porPai.get(row.id) ?? [], depth + 1, [
10482
+ ...ancestorHasNext,
10483
+ !isLast
10484
+ ]);
10485
+ }
10486
+ });
10487
+ };
10488
+ visitarIrmaos(porPai.get(null) ?? [], 0, []);
10489
+ const alcancaveis = /* @__PURE__ */ new Set();
10490
+ const alcancar = (paiId) => {
10491
+ for (const row of porPai.get(paiId) ?? []) {
10492
+ if (alcancaveis.has(row.id)) continue;
10493
+ alcancaveis.add(row.id);
10494
+ alcancar(row.id);
10495
+ }
10496
+ };
10497
+ alcancar(null);
10498
+ const orfaos = rows.filter((r) => !alcancaveis.has(r.id));
10499
+ if (orfaos.length > 0) visitarIrmaos(orfaos, 0, []);
10500
+ return saida;
10501
+ }
10502
+ function deriveSummaryRange(rowId, rows) {
10503
+ const porPai = /* @__PURE__ */ new Map();
10504
+ for (const r of rows) {
10505
+ if (!r.parent) continue;
10506
+ const l = porPai.get(r.parent);
10507
+ if (l) l.push(r);
10508
+ else porPai.set(r.parent, [r]);
10509
+ }
10510
+ const datas = [];
10511
+ const vistos = /* @__PURE__ */ new Set();
10512
+ const coletar = (id) => {
10513
+ if (vistos.has(id)) return;
10514
+ vistos.add(id);
10515
+ for (const filho of porPai.get(id) ?? []) {
10516
+ for (const b of filho.bars) {
10517
+ datas.push(b.start, b.end);
10518
+ }
10519
+ coletar(filho.id);
10520
+ }
10521
+ };
10522
+ coletar(rowId);
10523
+ if (datas.length === 0) return null;
10524
+ return { start: dateFns.min(datas), end: dateFns.max(datas) };
10525
+ }
10526
+ function deriveWindow(rows, granularity = "day") {
10527
+ const datas = [];
10528
+ for (const r of rows) for (const b of r.bars) datas.push(b.start, b.end);
10529
+ if (datas.length === 0) {
10530
+ const hoje = dateFns.startOfDay(/* @__PURE__ */ new Date());
10531
+ return { start: hoje, end: dateFns.addDays(hoje, 29) };
10532
+ }
10533
+ const folga = granularity === "day" ? 1 : granularity === "week" ? 7 : 31;
10534
+ return {
10535
+ start: dateFns.addDays(dateFns.startOfDay(dateFns.min(datas)), -folga),
10536
+ end: dateFns.addDays(dateFns.startOfDay(dateFns.max(datas)), folga)
10537
+ };
10538
+ }
10539
+ function rowHeights(rows, baseHeight, laneHeight) {
10540
+ return rows.map(({ row }) => {
10541
+ const modo = row.lanePacking ?? "stack";
10542
+ const n = modo === "stack" ? Math.max(1, row.bars.length) : 1;
10543
+ return Math.max(baseHeight, n * laneHeight + 8);
10544
+ });
10545
+ }
10546
+ function rowOffsets(heights) {
10547
+ const saida = [];
10548
+ let acc = 0;
10549
+ for (const h of heights) {
10550
+ saida.push(acc);
10551
+ acc += h;
10552
+ }
10553
+ return saida;
10554
+ }
10555
+ function buildMonthMatrix(anchor, weekStartsOn = 0) {
10556
+ const primeiro = dateFns.startOfMonth(anchor);
10557
+ const inicio = dateFns.startOfWeek(primeiro, { weekStartsOn });
10558
+ const semanas = [];
10559
+ for (let s = 0; s < 6; s++) {
10560
+ const semana = [];
10561
+ for (let d = 0; d < 7; d++) semana.push(dateFns.addDays(inicio, s * 7 + d));
10562
+ semanas.push(semana);
10563
+ }
10564
+ return semanas;
10565
+ }
10566
+ function computeOverflow(total, max) {
10567
+ if (max <= 0) return { visible: 0, overflowCount: total };
10568
+ if (total <= max) return { visible: total, overflowCount: 0 };
10569
+ const visible = Math.max(0, max - 1);
10570
+ return { visible, overflowCount: total - visible };
10571
+ }
10572
+ function daysOfBar(bar) {
10573
+ const saida = [];
10574
+ let cursor = dateFns.startOfDay(bar.start);
10575
+ const fim = dateFns.startOfDay(bar.end);
10576
+ if (fim < cursor) return saida;
10577
+ let guarda = 0;
10578
+ while (cursor.getTime() <= fim.getTime() && guarda < 400) {
10579
+ saida.push(cursor);
10580
+ cursor = dateFns.addDays(cursor, 1);
10581
+ guarda++;
10582
+ }
10583
+ return saida;
10584
+ }
10585
+ function monthMatrixRange(weeks) {
10586
+ return { start: weeks[0][0], end: endOfMonthMatrix(weeks) };
10587
+ }
10588
+ function endOfMonthMatrix(weeks) {
10589
+ const ultima = weeks[weeks.length - 1];
10590
+ return ultima[ultima.length - 1];
10591
+ }
10592
+ function monthBounds(anchor) {
10593
+ return { start: dateFns.startOfMonth(anchor), end: dateFns.endOfMonth(anchor) };
10594
+ }
10595
+ function buildWeekSegments(week, barras) {
10596
+ if (week.length === 0) return [];
10597
+ const primeiro = dateFns.startOfDay(week[0]);
10598
+ const ultimo = dateFns.startOfDay(week[week.length - 1]);
10599
+ const brutos = [];
10600
+ for (const b of barras) {
10601
+ const ini = dateFns.startOfDay(b.start);
10602
+ const fim = dateFns.startOfDay(b.end);
10603
+ if (dateFns.differenceInCalendarDays(fim, ini) < 0) continue;
10604
+ if (dateFns.differenceInCalendarDays(fim, primeiro) < 0 || dateFns.differenceInCalendarDays(ini, ultimo) > 0) {
10605
+ continue;
10606
+ }
10607
+ const colStart = Math.max(0, dateFns.differenceInCalendarDays(ini, primeiro));
10608
+ const colEnd = Math.min(
10609
+ week.length - 1,
10610
+ dateFns.differenceInCalendarDays(fim, primeiro)
10611
+ );
10612
+ brutos.push({
10613
+ barId: b.id,
10614
+ colStart,
10615
+ colSpan: colEnd - colStart + 1,
10616
+ // ⚠️ Comparado contra a BORDA da semana, não contra `colStart`: uma barra
10617
+ // que começa exatamente no domingo tem `colStart` 0 e NÃO continua antes.
10618
+ continuesBefore: dateFns.differenceInCalendarDays(ini, primeiro) < 0,
10619
+ continuesAfter: dateFns.differenceInCalendarDays(fim, ultimo) > 0
10620
+ });
10621
+ }
10622
+ brutos.sort(
10623
+ (a, b) => a.colStart !== b.colStart ? a.colStart - b.colStart : b.colSpan - a.colSpan || a.barId.localeCompare(b.barId)
10624
+ );
10625
+ const ocupacao = [];
10626
+ const saida = [];
10627
+ for (const s of brutos) {
10628
+ let lane = ocupacao.findIndex((ate) => ate < s.colStart);
10629
+ if (lane === -1) {
10630
+ lane = ocupacao.length;
10631
+ ocupacao.push(-1);
10632
+ }
10633
+ ocupacao[lane] = s.colStart + s.colSpan - 1;
10634
+ saida.push({ ...s, lane });
10635
+ }
10636
+ return saida;
10637
+ }
10638
+ function hiddenPerColumn(segmentos, maxLanes, colunas = 7) {
10639
+ const saida = new Array(colunas).fill(0);
10640
+ if (maxLanes < 0) return saida;
10641
+ for (const s of segmentos) {
10642
+ if (s.lane < maxLanes) continue;
10643
+ for (let c = s.colStart; c < s.colStart + s.colSpan && c < colunas; c++) {
10644
+ saida[c] = (saida[c] ?? 0) + 1;
10645
+ }
10646
+ }
10647
+ return saida;
10648
+ }
10649
+ function laneCount(segmentos) {
10650
+ let max = -1;
10651
+ for (const s of segmentos) if (s.lane > max) max = s.lane;
10652
+ return max + 1;
10653
+ }
10654
+ const ALTURA_DA_FAIXA_PX = 20;
10655
+ const ALTURA_DO_CABECALHO_PX = 26;
10656
+ const MIN_FAIXAS = 1;
10657
+ function GanttCalendarView({
10658
+ rows,
10659
+ anchor,
10660
+ weekStartsOn,
10661
+ now,
10662
+ locale,
10663
+ conflictBarIds,
10664
+ criticalBarIds,
10665
+ onBarClick,
10666
+ onDayAdd
10667
+ }) {
10668
+ const semanas = React.useMemo(
10669
+ () => buildMonthMatrix(anchor, weekStartsOn),
10670
+ [anchor, weekStartsOn]
10671
+ );
10672
+ const entradas = React.useMemo(() => {
10673
+ const mapa = /* @__PURE__ */ new Map();
10674
+ for (const { row } of rows) {
10675
+ if ((row.type ?? "task") === "summary") continue;
10676
+ for (const bar of row.bars) {
10677
+ mapa.set(bar.id, { bar, row, colorKey: bar.colorKey ?? "chart-1" });
10678
+ }
10679
+ }
10680
+ return mapa;
10681
+ }, [rows]);
10682
+ const barrasParaSegmentar = React.useMemo(
10683
+ () => [...entradas.values()].map(({ bar }) => ({
10684
+ id: bar.id,
10685
+ start: dateFns.startOfDay(bar.start),
10686
+ end: dateFns.startOfDay(bar.end)
10687
+ })),
10688
+ [entradas]
10689
+ );
10690
+ const porSemana = React.useMemo(
10691
+ () => semanas.map((semana) => buildWeekSegments(semana, barrasParaSegmentar)),
10692
+ [semanas, barrasParaSegmentar]
10693
+ );
10694
+ const [expandidas, setExpandidas] = React.useState(() => /* @__PURE__ */ new Set());
10695
+ const gradeRef = React.useRef(null);
10696
+ const [maxFaixas, setMaxFaixas] = React.useState(3);
10697
+ React.useEffect(() => {
10698
+ const el = gradeRef.current;
10699
+ if (!el) return;
10700
+ const medir = () => {
10701
+ const primeira = el.firstElementChild;
10702
+ const altura = primeira ? primeira.getBoundingClientRect().height : el.getBoundingClientRect().height / 6;
10703
+ const util = altura - ALTURA_DO_CABECALHO_PX;
10704
+ setMaxFaixas(Math.max(MIN_FAIXAS, Math.floor(util / ALTURA_DA_FAIXA_PX)));
10705
+ };
10706
+ medir();
10707
+ const ro = new ResizeObserver(medir);
10708
+ ro.observe(el);
10709
+ return () => ro.disconnect();
10710
+ }, []);
10711
+ const rotulosDaSemana = React.useMemo(
10712
+ () => (semanas[0] ?? []).map((d) => dateFns.format(d, "EEEEEE", { locale })),
10713
+ [semanas, locale]
10714
+ );
10715
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttMonthFrame(), children: [
10716
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttWeekdayRow(), children: rotulosDaSemana.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttWeekdayCell(), children: r }, i)) }),
10717
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttMonthGrid(), ref: gradeRef, children: semanas.map((semana, iSemana) => {
10718
+ const segmentos = porSemana[iSemana] ?? [];
10719
+ const aberta = expandidas.has(iSemana);
10720
+ const total = laneCount(segmentos);
10721
+ const visiveis = aberta ? total : total > maxFaixas ? Math.max(1, maxFaixas - 1) : total;
10722
+ const escondidosPorColuna = hiddenPerColumn(segmentos, visiveis);
10723
+ return /* @__PURE__ */ jsxRuntime.jsxs(
10724
+ "div",
10725
+ {
10726
+ className: ganttWeekRow(),
10727
+ style: aberta ? {
10728
+ minHeight: ALTURA_DO_CABECALHO_PX + total * ALTURA_DA_FAIXA_PX + 24
10729
+ } : void 0,
10730
+ children: [
10731
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttWeekDays(), children: semana.map((dia, iCol) => {
10732
+ const escondidos = escondidosPorColuna[iCol] ?? 0;
10733
+ return /* @__PURE__ */ jsxRuntime.jsxs(
10734
+ "div",
10735
+ {
10736
+ className: ganttMonthCell({
10737
+ outside: !dateFns.isSameMonth(dia, anchor),
10738
+ today: dateFns.isSameDay(dia, now)
10739
+ }),
10740
+ children: [
10741
+ /* @__PURE__ */ jsxRuntime.jsx(
10742
+ "span",
10743
+ {
10744
+ className: ganttDayNumber({
10745
+ today: dateFns.isSameDay(dia, now),
10746
+ // O fundo não distingue mais outro-mês de fim de
10747
+ // semana (os dois são `bg-canvas`) — quem distingue é
10748
+ // o número. Ver a nota do `ganttDayNumber`.
10749
+ outside: !dateFns.isSameMonth(dia, anchor)
10750
+ }),
10751
+ children: dateFns.format(dia, "d", { locale })
10752
+ }
10753
+ ),
10754
+ escondidos > 0 && !aberta ? /* @__PURE__ */ jsxRuntime.jsx(
10755
+ "button",
10756
+ {
10757
+ type: "button",
10758
+ className: ganttOverflowButton(),
10759
+ style: {
10760
+ top: ALTURA_DO_CABECALHO_PX + visiveis * ALTURA_DA_FAIXA_PX
10761
+ },
10762
+ onClick: () => setExpandidas((prev) => new Set(prev).add(iSemana)),
10763
+ children: `+${escondidos} mais`
10764
+ }
10765
+ ) : null,
10766
+ onDayAdd ? /* @__PURE__ */ jsxRuntime.jsx(
10767
+ "button",
10768
+ {
10769
+ type: "button",
10770
+ className: ganttDayAdd(),
10771
+ "aria-label": `Adicionar em ${dateFns.format(dia, "d 'de' MMMM", { locale })}`,
10772
+ title: "Adicionar",
10773
+ onClick: () => onDayAdd(dia),
10774
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { "aria-hidden": true })
10775
+ }
10776
+ ) : null
10777
+ ]
10778
+ },
10779
+ dia.getTime()
10780
+ );
10781
+ }) }),
10782
+ /* @__PURE__ */ jsxRuntime.jsx(
10783
+ "div",
10784
+ {
10785
+ className: ganttWeekSegments(),
10786
+ style: { top: ALTURA_DO_CABECALHO_PX },
10787
+ children: segmentos.filter((seg) => seg.lane < visiveis).map((seg) => {
10788
+ const entrada = entradas.get(seg.barId);
10789
+ if (!entrada) return null;
10790
+ const { bar, row, colorKey } = entrada;
10791
+ const estilos = ganttDaySegment({
10792
+ colorKey,
10793
+ continuesBefore: seg.continuesBefore,
10794
+ continuesAfter: seg.continuesAfter,
10795
+ conflict: conflictBarIds.has(bar.id),
10796
+ critical: criticalBarIds.has(bar.id)
10797
+ });
10798
+ return /* @__PURE__ */ jsxRuntime.jsxs(
10799
+ "button",
10800
+ {
10801
+ type: "button",
10802
+ className: estilos.root(),
10803
+ style: {
10804
+ left: `calc(${seg.colStart / 7 * 100}% + 2px)`,
10805
+ width: `calc(${seg.colSpan / 7 * 100}% - 4px)`,
10806
+ top: seg.lane * ALTURA_DA_FAIXA_PX
10807
+ },
10808
+ onClick: (e) => onBarClick?.(bar, row, e),
10809
+ title: bar.searchText ?? void 0,
10810
+ children: [
10811
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.dot(), "aria-hidden": true }),
10812
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.label(), children: bar.label ?? (typeof row.label === "string" ? row.label : bar.id) })
10813
+ ]
10814
+ },
10815
+ `${iSemana}-${seg.barId}`
10816
+ );
10817
+ })
10818
+ }
10819
+ ),
10820
+ aberta && total > maxFaixas ? /* @__PURE__ */ jsxRuntime.jsx(
10821
+ "button",
10822
+ {
10823
+ type: "button",
10824
+ className: `${ganttOverflowButton()} bottom-pad-2xs top-auto z-[3]`,
10825
+ onClick: () => setExpandidas((prev) => {
10826
+ const proximo = new Set(prev);
10827
+ proximo.delete(iSemana);
10828
+ return proximo;
10829
+ }),
10830
+ children: "Recolher"
10831
+ }
10832
+ ) : null
10833
+ ]
10834
+ },
10835
+ iSemana
10836
+ );
10837
+ }) })
10838
+ ] });
10839
+ }
10840
+ function GanttListView({
10841
+ rows,
10842
+ windowStart,
10843
+ windowEnd,
10844
+ now,
10845
+ locale,
10846
+ conflictBarIds,
10847
+ criticalBarIds,
10848
+ onBarClick,
10849
+ emptyState
10850
+ }) {
10851
+ const dias = React.useMemo(() => {
10852
+ const jIni = dateFns.startOfDay(windowStart);
10853
+ const jFim = dateFns.startOfDay(windowEnd);
10854
+ if (dateFns.differenceInCalendarDays(jFim, jIni) < 0) return [];
10855
+ const mapa = /* @__PURE__ */ new Map();
10856
+ for (const { row } of rows) {
10857
+ if ((row.type ?? "task") === "summary") continue;
10858
+ for (const bar of row.bars) {
10859
+ const bIni = dateFns.startOfDay(bar.start);
10860
+ const bFim = dateFns.startOfDay(bar.end);
10861
+ if (dateFns.differenceInCalendarDays(bFim, bIni) < 0) continue;
10862
+ const totalDeDias = dateFns.differenceInCalendarDays(bFim, bIni) + 1;
10863
+ const de = dateFns.differenceInCalendarDays(bIni, jIni) < 0 ? jIni : bIni;
10864
+ const ate = dateFns.differenceInCalendarDays(bFim, jFim) > 0 ? jFim : bFim;
10865
+ if (dateFns.differenceInCalendarDays(ate, de) < 0) continue;
10866
+ for (const dia of dateFns.eachDayOfInterval({ start: de, end: ate })) {
10867
+ const chave = dateFns.format(dia, "yyyy-MM-dd");
10868
+ const balde = mapa.get(chave) ?? { dia, itens: [] };
10869
+ balde.itens.push({
10870
+ bar,
10871
+ row,
10872
+ colorKey: bar.colorKey ?? "chart-1",
10873
+ diaDoIntervalo: dateFns.differenceInCalendarDays(dia, bIni) + 1,
10874
+ totalDeDias
10875
+ });
10876
+ mapa.set(chave, balde);
10877
+ }
10878
+ }
10879
+ }
10880
+ return [...mapa.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
10881
+ }, [rows, windowStart, windowEnd]);
10882
+ if (dias.length === 0) {
10883
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttListFrame(), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttEmpty(), children: emptyState ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10884
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarSearch, { className: "size-icon-xl text-fg-subtle", "aria-hidden": true }),
10885
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttEmptyText(), children: "Nenhuma tarefa neste período." })
10886
+ ] }) }) });
10887
+ }
10888
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttListFrame(), children: dias.map(({ dia, itens }) => {
10889
+ const hoje = dateFns.isSameDay(dia, now);
10890
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttListDay(), children: [
10891
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttListDayHead(), children: [
10892
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttListDayNumber({ today: hoje }), children: dateFns.format(dia, "d", { locale }) }),
10893
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttListDayName(), children: dateFns.format(dia, "EEEE, MMMM", { locale }) })
10894
+ ] }),
10895
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttListItems(), children: itens.map(({ bar, row, colorKey, diaDoIntervalo, totalDeDias }) => {
10896
+ const estilos = ganttListItem({
10897
+ colorKey,
10898
+ conflict: conflictBarIds.has(bar.id),
10899
+ critical: criticalBarIds.has(bar.id)
10900
+ });
10901
+ return /* @__PURE__ */ jsxRuntime.jsxs(
10902
+ "button",
10903
+ {
10904
+ type: "button",
10905
+ className: estilos.root(),
10906
+ onClick: (e) => onBarClick?.(bar, row, e),
10907
+ children: [
10908
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.dot(), "aria-hidden": true }),
10909
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: estilos.body(), children: [
10910
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.title(), children: typeof row.label === "string" ? row.label : bar.id }),
10911
+ row.bars.length > 1 && bar.label ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.sub(), children: bar.label }) : row.sublabel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.sub(), children: row.sublabel }) : null
10912
+ ] }),
10913
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.meta(), children: totalDeDias === 1 ? "dia único" : `dia ${diaDoIntervalo} de ${totalDeDias}` })
10914
+ ]
10915
+ },
10916
+ `${dateFns.format(dia, "yyyy-MM-dd")}-${bar.id}`
10917
+ );
10918
+ }) })
10919
+ ] }, dateFns.format(dia, "yyyy-MM-dd"));
10920
+ }) });
10921
+ }
10922
+ function dragToDates(bar, deltaPx, pxPerDay, mode) {
10923
+ const inicio = dateFns.startOfDay(bar.start);
10924
+ const fim = dateFns.startOfDay(bar.end);
10925
+ if (!Number.isFinite(pxPerDay) || pxPerDay <= 0) {
10926
+ return { start: inicio, end: fim };
10927
+ }
10928
+ const dias = Math.round(deltaPx / pxPerDay);
10929
+ if (dias === 0) return { start: inicio, end: fim };
10930
+ if (mode === "move") {
10931
+ return { start: dateFns.addDays(inicio, dias), end: dateFns.addDays(fim, dias) };
10932
+ }
10933
+ if (mode === "resize-start") {
10934
+ const novo2 = dateFns.addDays(inicio, dias);
10935
+ return {
10936
+ start: dateFns.differenceInCalendarDays(novo2, fim) > 0 ? fim : novo2,
10937
+ end: fim
10938
+ };
10939
+ }
10940
+ const novo = dateFns.addDays(fim, dias);
10941
+ return {
10942
+ start: inicio,
10943
+ // Mesmo clamp, pela mesma razão, na outra ponta.
10944
+ end: dateFns.differenceInCalendarDays(novo, inicio) < 0 ? inicio : novo
10945
+ };
10946
+ }
10947
+ function linkTypeFromSides(de, para) {
10948
+ if (de === "end") return para === "start" ? "FS" : "FF";
10949
+ return para === "start" ? "SS" : "SF";
10950
+ }
10951
+ function sideFromPointer(x, left, width) {
10952
+ if (width <= 0) return "start";
10953
+ return x < left + width / 2 ? "start" : "end";
10954
+ }
10955
+ const GANTT_BAR_MIN_WIDTH_PX = 6;
10956
+ const GanttBarView = React.forwardRef(
10957
+ function GanttBarView2({
10958
+ bar,
10959
+ left,
10960
+ width,
10961
+ lane,
10962
+ rowType,
10963
+ colorKey,
10964
+ continuesBefore,
10965
+ continuesAfter,
10966
+ height,
10967
+ conflict = false,
10968
+ critical = false,
10969
+ movable = false,
10970
+ resizable = false,
10971
+ linkable = false,
10972
+ dragging = false,
10973
+ onClick,
10974
+ onMoveStart,
10975
+ onResizeStart,
10976
+ onLinkStart,
10977
+ children,
10978
+ style
10979
+ }, ref) {
10980
+ if (rowType === "milestone") {
10981
+ return /* @__PURE__ */ jsxRuntime.jsx(
10982
+ "button",
10983
+ {
10984
+ type: "button",
10985
+ className: ganttMilestone({ colorKey }),
10986
+ style: { left: left - 7, width: 14, height: 14, ...style },
10987
+ onClick,
10988
+ "aria-label": bar.searchText ?? String(bar.id),
10989
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", {})
10990
+ }
10991
+ );
10992
+ }
10993
+ const estilos = ganttBar({
10994
+ colorKey,
10995
+ type: rowType,
10996
+ continuesBefore,
10997
+ continuesAfter,
10998
+ conflict,
10999
+ critical,
11000
+ dragging,
11001
+ movable
11002
+ });
11003
+ const larguraFinal = Math.max(GANTT_BAR_MIN_WIDTH_PX, width);
11004
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11005
+ "div",
11006
+ {
11007
+ ref,
11008
+ role: "button",
11009
+ tabIndex: 0,
11010
+ "aria-label": bar.searchText ?? void 0,
11011
+ "data-gantt-bar": bar.id,
11012
+ className: estilos.root(),
11013
+ style: {
11014
+ left,
11015
+ width: larguraFinal,
11016
+ height,
11017
+ ...style
11018
+ },
11019
+ onPointerDown: (e) => onMoveStart?.(e),
11020
+ onClick,
11021
+ onKeyDown: (e) => {
11022
+ if (e.key === "Enter" || e.key === " ") {
11023
+ e.preventDefault();
11024
+ onClick?.(e);
11025
+ }
11026
+ },
11027
+ children: [
11028
+ bar.progress !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
11029
+ "span",
11030
+ {
11031
+ className: estilos.progress(),
11032
+ style: { width: `${Math.min(100, Math.max(0, bar.progress))}%` },
11033
+ "aria-hidden": true
11034
+ }
11035
+ ) : null,
11036
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.accent(), "aria-hidden": true }),
11037
+ children ?? (bar.label ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: estilos.label(), children: bar.label }) : null),
11038
+ resizable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11039
+ /* @__PURE__ */ jsxRuntime.jsx(
11040
+ "span",
11041
+ {
11042
+ role: "separator",
11043
+ "aria-label": "Mudar início",
11044
+ tabIndex: -1,
11045
+ className: ganttResizeHandle({ side: "start" }),
11046
+ onPointerDown: (e) => {
11047
+ e.stopPropagation();
11048
+ onResizeStart?.("start", e);
11049
+ }
11050
+ }
11051
+ ),
11052
+ /* @__PURE__ */ jsxRuntime.jsx(
11053
+ "span",
11054
+ {
11055
+ role: "separator",
11056
+ "aria-label": "Mudar fim",
11057
+ tabIndex: -1,
11058
+ className: ganttResizeHandle({ side: "end" }),
11059
+ onPointerDown: (e) => {
11060
+ e.stopPropagation();
11061
+ onResizeStart?.("end", e);
11062
+ }
11063
+ }
11064
+ )
11065
+ ] }) : null,
11066
+ linkable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11067
+ /* @__PURE__ */ jsxRuntime.jsx(
11068
+ "span",
11069
+ {
11070
+ role: "button",
11071
+ "aria-label": "Criar vínculo a partir do início",
11072
+ tabIndex: -1,
11073
+ className: ganttLinkPort({ side: "start" }),
11074
+ onPointerDown: (e) => {
11075
+ e.stopPropagation();
11076
+ onLinkStart?.("start", e);
11077
+ }
11078
+ }
11079
+ ),
11080
+ /* @__PURE__ */ jsxRuntime.jsx(
11081
+ "span",
11082
+ {
11083
+ role: "button",
11084
+ "aria-label": "Criar vínculo a partir do fim",
11085
+ tabIndex: -1,
11086
+ className: ganttLinkPort({ side: "end" }),
11087
+ onPointerDown: (e) => {
11088
+ e.stopPropagation();
11089
+ onLinkStart?.("end", e);
11090
+ }
11091
+ }
11092
+ )
11093
+ ] }) : null
11094
+ ]
11095
+ }
11096
+ );
11097
+ }
11098
+ );
11099
+ function constraintDates(tipo, source, target, lag = 0) {
11100
+ const de = tipo === "FS" || tipo === "FF" ? source.end : source.start;
11101
+ const pontaAlvo = tipo === "FS" || tipo === "SS" ? target.start : target.end;
11102
+ return { limite: dateFns.addDays(dateFns.startOfDay(de), lag), pontaAlvo: dateFns.startOfDay(pontaAlvo) };
11103
+ }
11104
+ function checkLink(link2, bars) {
11105
+ const source = bars.get(link2.source);
11106
+ const target = bars.get(link2.target);
11107
+ if (!source || !target) return null;
11108
+ const { limite, pontaAlvo } = constraintDates(
11109
+ link2.type ?? "FS",
11110
+ source,
11111
+ target,
11112
+ link2.lag ?? 0
11113
+ );
11114
+ const deficitDays = dateFns.differenceInCalendarDays(limite, pontaAlvo);
11115
+ if (deficitDays <= 0) return null;
11116
+ return { link: link2, deficitDays };
11117
+ }
11118
+ function checkAllLinks(links, bars) {
11119
+ const saida = [];
11120
+ for (const l of links) {
11121
+ const v = checkLink(l, bars);
11122
+ if (v) saida.push(v);
11123
+ }
11124
+ return saida;
11125
+ }
11126
+ function topoSort(barIds, links) {
11127
+ const existe = new Set(barIds);
11128
+ const grauEntrada = /* @__PURE__ */ new Map();
11129
+ const saintes = /* @__PURE__ */ new Map();
11130
+ for (const id of barIds) {
11131
+ grauEntrada.set(id, 0);
11132
+ saintes.set(id, []);
11133
+ }
11134
+ for (const l of links) {
11135
+ if (!existe.has(l.source) || !existe.has(l.target)) continue;
11136
+ saintes.get(l.source).push(l.target);
11137
+ grauEntrada.set(l.target, (grauEntrada.get(l.target) ?? 0) + 1);
11138
+ }
11139
+ const fila = barIds.filter((id) => (grauEntrada.get(id) ?? 0) === 0);
11140
+ const order = [];
11141
+ while (fila.length > 0) {
11142
+ const atual = fila.shift();
11143
+ order.push(atual);
11144
+ for (const proximo of saintes.get(atual) ?? []) {
11145
+ const g = (grauEntrada.get(proximo) ?? 0) - 1;
11146
+ grauEntrada.set(proximo, g);
11147
+ if (g === 0) fila.push(proximo);
11148
+ }
11149
+ }
11150
+ if (order.length !== barIds.length) {
11151
+ return { ok: false, cycle: barIds.filter((id) => !order.includes(id)) };
11152
+ }
11153
+ return { ok: true, order };
11154
+ }
11155
+ function computeCriticalPath(bars, links) {
11156
+ const ids = bars.map((b) => b.id);
11157
+ const topo = topoSort(ids, links);
11158
+ if (!topo.ok) return { cycle: topo.cycle };
11159
+ const porId = new Map(bars.map((b) => [b.id, b]));
11160
+ const fsEntrando = /* @__PURE__ */ new Map();
11161
+ const fsSaindo = /* @__PURE__ */ new Map();
11162
+ for (const l of links) {
11163
+ if ((l.type ?? "FS") !== "FS") continue;
11164
+ if (!porId.has(l.source) || !porId.has(l.target)) continue;
11165
+ empurrar(fsEntrando, l.target, l);
11166
+ empurrar(fsSaindo, l.source, l);
11167
+ }
11168
+ const duracao = (id) => {
11169
+ const b = porId.get(id);
11170
+ return Math.max(0, dateFns.differenceInCalendarDays(dateFns.startOfDay(b.end), dateFns.startOfDay(b.start)));
11171
+ };
11172
+ const base = dateFns.startOfDay(
11173
+ bars.reduce((min, b) => b.start < min ? b.start : min, bars[0]?.start ?? /* @__PURE__ */ new Date())
11174
+ );
11175
+ const inicioRelativo = (id) => dateFns.differenceInCalendarDays(dateFns.startOfDay(porId.get(id).start), base);
11176
+ const early = /* @__PURE__ */ new Map();
11177
+ for (const id of topo.order) {
11178
+ const entradas = fsEntrando.get(id) ?? [];
11179
+ const porVinculo = entradas.map(
11180
+ (l) => (early.get(l.source) ?? 0) + duracao(l.source) + (l.lag ?? 0)
11181
+ );
11182
+ early.set(id, Math.max(inicioRelativo(id), ...porVinculo));
11183
+ }
11184
+ const fimProjeto = Math.max(
11185
+ 0,
11186
+ ...topo.order.map((id) => (early.get(id) ?? 0) + duracao(id))
11187
+ );
11188
+ const late = /* @__PURE__ */ new Map();
11189
+ for (const id of [...topo.order].reverse()) {
11190
+ const saidas = fsSaindo.get(id) ?? [];
11191
+ if (saidas.length === 0) {
11192
+ late.set(id, fimProjeto - duracao(id));
11193
+ } else {
11194
+ late.set(
11195
+ id,
11196
+ Math.min(
11197
+ ...saidas.map((l) => (late.get(l.target) ?? fimProjeto) - duracao(id) - (l.lag ?? 0))
11198
+ )
11199
+ );
11200
+ }
11201
+ }
11202
+ const critical = /* @__PURE__ */ new Set();
11203
+ for (const id of ids) {
11204
+ const folga = (late.get(id) ?? 0) - (early.get(id) ?? 0);
11205
+ if (folga <= 0) critical.add(id);
11206
+ }
11207
+ return { critical };
11208
+ }
11209
+ function empurrar(map, chave, valor) {
11210
+ const lista = map.get(chave);
11211
+ if (lista) lista.push(valor);
11212
+ else map.set(chave, [valor]);
11213
+ }
11214
+ function linkPath(de, para, opts) {
11215
+ const recuo = opts.recuo ?? 12;
11216
+ const x12 = de.x + (opts.saiDireita ? recuo : -recuo);
11217
+ const x2 = para.x + (opts.entraEsquerda ? -recuo : recuo);
11218
+ const meioY = de.y === para.y ? de.y : (de.y + para.y) / 2;
11219
+ return [
11220
+ `M ${de.x} ${de.y}`,
11221
+ `L ${x12} ${de.y}`,
11222
+ `L ${x12} ${meioY}`,
11223
+ `L ${x2} ${meioY}`,
11224
+ `L ${x2} ${para.y}`,
11225
+ `L ${para.x} ${para.y}`
11226
+ ].join(" ");
11227
+ }
11228
+ function linkSides(tipo) {
11229
+ return {
11230
+ saiDireita: tipo === "FS" || tipo === "FF",
11231
+ entraEsquerda: tipo === "FS" || tipo === "SS"
11232
+ };
11233
+ }
11234
+ function GanttLinksLayer({
11235
+ links,
11236
+ width,
11237
+ height,
11238
+ onLinkClick
11239
+ }) {
11240
+ const uid = React.useId().replace(/:/g, "");
11241
+ const seta = `gantt-arrow-${uid}`;
11242
+ const setaConflito = `gantt-arrow-conflict-${uid}`;
11243
+ if (links.length === 0) return null;
11244
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11245
+ "svg",
11246
+ {
11247
+ className: ganttLinksLayer(),
11248
+ width,
11249
+ height,
11250
+ viewBox: `0 0 ${width} ${height}`,
11251
+ "aria-hidden": true,
11252
+ focusable: "false",
11253
+ children: [
11254
+ /* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
11255
+ /* @__PURE__ */ jsxRuntime.jsx(
11256
+ "marker",
11257
+ {
11258
+ id: seta,
11259
+ viewBox: "0 0 8 8",
11260
+ refX: "6",
11261
+ refY: "4",
11262
+ markerWidth: "6",
11263
+ markerHeight: "6",
11264
+ orient: "auto-start-reverse",
11265
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 0 1 L 6 4 L 0 7 z", fill: "context-stroke" })
11266
+ }
11267
+ ),
11268
+ /* @__PURE__ */ jsxRuntime.jsx(
11269
+ "marker",
11270
+ {
11271
+ id: setaConflito,
11272
+ viewBox: "0 0 8 8",
11273
+ refX: "6",
11274
+ refY: "4",
11275
+ markerWidth: "6",
11276
+ markerHeight: "6",
11277
+ orient: "auto-start-reverse",
11278
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 0 1 L 6 4 L 0 7 z", fill: "context-stroke" })
11279
+ }
11280
+ )
11281
+ ] }),
11282
+ links.map((g) => {
11283
+ const tipo = g.link.type ?? "FS";
11284
+ const lados = linkSides(tipo);
11285
+ const d = linkPath(g.from, g.to, lados);
11286
+ const state = g.conflict ? "conflict" : g.critical ? "critical" : "default";
11287
+ return /* @__PURE__ */ jsxRuntime.jsx(
11288
+ "path",
11289
+ {
11290
+ d,
11291
+ className: ganttLinkPath({ state }),
11292
+ markerEnd: `url(#${g.conflict ? setaConflito : seta})`,
11293
+ onClick: () => onLinkClick?.(g.link),
11294
+ children: /* @__PURE__ */ jsxRuntime.jsxs("title", { children: [
11295
+ `${tipo}${g.link.lag ? ` ${g.link.lag > 0 ? "+" : ""}${g.link.lag}d` : ""}`,
11296
+ g.conflict ? " — conflito" : ""
11297
+ ] })
11298
+ },
11299
+ g.link.id
11300
+ );
11301
+ })
11302
+ ]
11303
+ }
11304
+ );
11305
+ }
11306
+ function GanttTimelineView({
11307
+ rows,
11308
+ allRows,
11309
+ links,
11310
+ windowStart,
11311
+ windowEnd,
11312
+ granularity,
11313
+ pxPerDay,
11314
+ heights,
11315
+ offsets,
11316
+ now,
11317
+ locale,
11318
+ weekStartsOn,
11319
+ conflictBarIds,
11320
+ criticalBarIds,
11321
+ draggable,
11322
+ resizable,
11323
+ linkable,
11324
+ hoveredRow,
11325
+ onHoverRow,
11326
+ hoveredDay,
11327
+ onHoverDay,
11328
+ selectedRow,
11329
+ selectedDay,
11330
+ onSelectDay,
11331
+ onBarMove,
11332
+ onBarResize,
11333
+ onLinkCreate,
11334
+ onScroll,
11335
+ scrollRef,
11336
+ onBarClick,
11337
+ onLinkClick
11338
+ }) {
11339
+ const [gesto, setGesto] = React.useState(null);
11340
+ const [deltaPx, setDeltaPx] = React.useState(0);
11341
+ const deltaRef = React.useRef(0);
11342
+ const [vinculo, setVinculo] = React.useState(null);
11343
+ const canvasRef = React.useRef(null);
11344
+ const axis = React.useMemo(
11345
+ () => buildTimeAxis(windowStart, windowEnd, granularity, locale, weekStartsOn),
11346
+ [windowStart, windowEnd, granularity, locale, weekStartsOn]
11347
+ );
11348
+ const larguraTotal = axis.totalDays * pxPerDay;
11349
+ const alturas = heights;
11350
+ const tops = offsets;
11351
+ const alturaTotal = alturas.reduce((s, h) => s + h, 0);
11352
+ const geos = React.useMemo(() => {
11353
+ const mapa = /* @__PURE__ */ new Map();
11354
+ const lista = [];
11355
+ rows.forEach(({ row }, i) => {
11356
+ const tipo = row.type ?? "task";
11357
+ const barras = tipo === "summary" && row.bars.length === 0 ? (() => {
11358
+ const r = deriveSummaryRange(row.id, allRows);
11359
+ return r ? [{ id: `${row.id}__summary`, start: r.start, end: r.end }] : [];
11360
+ })() : row.bars;
11361
+ const comPrevia = barras.map(
11362
+ (b) => gesto?.barId === b.id ? { ...b, ...dragToDates(b, deltaPx, pxPerDay, gesto.mode) } : b
11363
+ );
11364
+ const recortadas = comPrevia.map((b) => clipToWindow(b, windowStart, windowEnd)).filter((x) => x !== null);
11365
+ const lanes = packLanes(recortadas, row.lanePacking ?? "stack");
11366
+ lanes.forEach((lane, laneIndex) => {
11367
+ for (const c of lane) {
11368
+ const x12 = dateToX(c.start, windowStart, pxPerDay);
11369
+ const x2 = dateToX(c.end, windowStart, pxPerDay);
11370
+ const geo = {
11371
+ bar: c.bar,
11372
+ row,
11373
+ left: x12,
11374
+ width: Math.max(0, x2 - x12),
11375
+ lane: laneIndex,
11376
+ top: tops[i] ?? 0,
11377
+ continuesBefore: c.continuesBefore,
11378
+ continuesAfter: c.continuesAfter,
11379
+ colorKey: c.bar.colorKey ?? "chart-1"
11380
+ };
11381
+ mapa.set(c.bar.id, geo);
11382
+ lista.push(geo);
11383
+ }
11384
+ });
11385
+ });
11386
+ return { mapa, lista };
11387
+ }, [rows, allRows, windowStart, windowEnd, pxPerDay, tops, gesto, deltaPx]);
11388
+ const topDaBarra = (g, tipo) => {
11389
+ const altura = tipo === "summary" ? GANTT_SUMMARY_BAR_HEIGHT_PX : GANTT_BAR_HEIGHT_PX;
11390
+ const alturaDaFaixa = alturas[rows.findIndex((r) => r.row.id === g.row.id)] ?? GANTT_ROW_HEIGHT_PX;
11391
+ const umaLaneSo = (g.row.lanePacking ?? "stack") === "stack" && g.row.bars.length <= 1;
11392
+ const faixa = umaLaneSo ? alturaDaFaixa : GANTT_LANE_HEIGHT_PX;
11393
+ const offsetLane = umaLaneSo ? 0 : g.lane * GANTT_LANE_HEIGHT_PX;
11394
+ return g.top + offsetLane + Math.max(0, (faixa - altura) / 2);
11395
+ };
11396
+ const linkGeos = React.useMemo(() => {
11397
+ const saida = [];
11398
+ for (const link2 of links) {
11399
+ const de = geos.mapa.get(link2.source);
11400
+ const para = geos.mapa.get(link2.target);
11401
+ if (!de || !para) continue;
11402
+ const tipo = link2.type ?? "FS";
11403
+ const saiDireita = tipo === "FS" || tipo === "FF";
11404
+ const entraEsquerda = tipo === "FS" || tipo === "SS";
11405
+ const alturaDe = (de.row.type ?? "task") === "summary" ? GANTT_SUMMARY_BAR_HEIGHT_PX : GANTT_BAR_HEIGHT_PX;
11406
+ const alturaPara = (para.row.type ?? "task") === "summary" ? GANTT_SUMMARY_BAR_HEIGHT_PX : GANTT_BAR_HEIGHT_PX;
11407
+ const yDe = topDaBarra(de, de.row.type ?? "task") + alturaDe / 2;
11408
+ const yPara = topDaBarra(para, para.row.type ?? "task") + alturaPara / 2;
11409
+ saida.push({
11410
+ link: link2,
11411
+ from: { x: saiDireita ? de.left + de.width : de.left, y: yDe },
11412
+ to: { x: entraEsquerda ? para.left : para.left + para.width, y: yPara },
11413
+ conflict: conflictBarIds.has(link2.target),
11414
+ critical: criticalBarIds.has(link2.source) && criticalBarIds.has(link2.target)
11415
+ });
11416
+ }
11417
+ return saida;
11418
+ }, [links, geos, conflictBarIds, criticalBarIds, alturas, rows]);
11419
+ const iniciarGesto = (barId, mode, evt) => {
11420
+ if (evt.button !== 0) return;
11421
+ if (mode === "move" ? !draggable : !resizable) return;
11422
+ deltaRef.current = 0;
11423
+ setDeltaPx(0);
11424
+ setGesto({ barId, mode, xInicial: evt.clientX });
11425
+ };
11426
+ React.useEffect(() => {
11427
+ if (!gesto) return;
11428
+ const mover = (e) => {
11429
+ const dx = e.clientX - gesto.xInicial;
11430
+ deltaRef.current = dx;
11431
+ setDeltaPx(dx);
11432
+ };
11433
+ const soltar = () => {
11434
+ const geo = geos.mapa.get(gesto.barId);
11435
+ const dx = deltaRef.current;
11436
+ setGesto(null);
11437
+ setDeltaPx(0);
11438
+ deltaRef.current = 0;
11439
+ if (!geo) return;
11440
+ const destino = dragToDates(geo.bar, dx, pxPerDay, gesto.mode);
11441
+ if (destino.start.getTime() === dateFns.startOfDay(geo.bar.start).getTime() && destino.end.getTime() === dateFns.startOfDay(geo.bar.end).getTime()) {
11442
+ return;
11443
+ }
11444
+ const change = {
11445
+ bar: geo.bar,
11446
+ row: geo.row,
11447
+ start: destino.start,
11448
+ end: destino.end
11449
+ };
11450
+ if (gesto.mode === "move") onBarMove?.(change);
11451
+ else onBarResize?.(change);
11452
+ };
11453
+ window.addEventListener("pointermove", mover);
11454
+ window.addEventListener("pointerup", soltar);
11455
+ window.addEventListener("pointercancel", soltar);
11456
+ return () => {
11457
+ window.removeEventListener("pointermove", mover);
11458
+ window.removeEventListener("pointerup", soltar);
11459
+ window.removeEventListener("pointercancel", soltar);
11460
+ };
11461
+ }, [gesto]);
11462
+ const iniciarVinculo = (barId, side, evt) => {
11463
+ if (evt.button !== 0) return;
11464
+ if (!linkable) return;
11465
+ const caixa = canvasRef.current?.getBoundingClientRect();
11466
+ if (!caixa) return;
11467
+ setVinculo({
11468
+ barId,
11469
+ side,
11470
+ x: evt.clientX - caixa.left,
11471
+ y: evt.clientY - caixa.top
11472
+ });
11473
+ };
11474
+ React.useEffect(() => {
11475
+ if (!vinculo) return;
11476
+ const mover = (e) => {
11477
+ const caixa = canvasRef.current?.getBoundingClientRect();
11478
+ if (!caixa) return;
11479
+ setVinculo(
11480
+ (v) => v ? { ...v, x: e.clientX - caixa.left, y: e.clientY - caixa.top } : v
11481
+ );
11482
+ };
11483
+ const soltar = (e) => {
11484
+ const origem = vinculo;
11485
+ setVinculo(null);
11486
+ const sob = document.elementFromPoint(e.clientX, e.clientY);
11487
+ const alvo = sob?.closest("[data-gantt-bar]");
11488
+ const alvoId = alvo?.getAttribute("data-gantt-bar");
11489
+ if (!alvoId) return;
11490
+ if (alvoId === origem.barId) return;
11491
+ const geoAlvo = geos.mapa.get(alvoId);
11492
+ const caixa = canvasRef.current?.getBoundingClientRect();
11493
+ if (!geoAlvo || !caixa) return;
11494
+ const ladoAlvo = sideFromPointer(
11495
+ e.clientX - caixa.left,
11496
+ geoAlvo.left,
11497
+ geoAlvo.width
11498
+ );
11499
+ onLinkCreate?.({
11500
+ source: origem.barId,
11501
+ target: alvoId,
11502
+ type: linkTypeFromSides(origem.side, ladoAlvo)
11503
+ });
11504
+ };
11505
+ window.addEventListener("pointermove", mover);
11506
+ window.addEventListener("pointerup", soltar);
11507
+ window.addEventListener("pointercancel", () => setVinculo(null));
11508
+ return () => {
11509
+ window.removeEventListener("pointermove", mover);
11510
+ window.removeEventListener("pointerup", soltar);
11511
+ };
11512
+ }, [vinculo?.barId, vinculo?.side]);
11513
+ const diaDaUnidade = (data) => Math.round(dateToX(data, windowStart, pxPerDay) / pxPerDay);
11514
+ const limitesDeGrupo = React.useMemo(() => {
11515
+ const saida = /* @__PURE__ */ new Set();
11516
+ let acumulado = 0;
11517
+ for (const g of axis.groups) {
11518
+ acumulado += g.spanDays;
11519
+ if (acumulado < axis.totalDays) saida.add(acumulado);
11520
+ }
11521
+ return saida;
11522
+ }, [axis]);
11523
+ const vinculoFantasma = React.useMemo(() => {
11524
+ if (!vinculo) return null;
11525
+ const de = geos.mapa.get(vinculo.barId);
11526
+ if (!de) return null;
11527
+ const tipo = de.row.type ?? "task";
11528
+ const altura = tipo === "summary" ? GANTT_SUMMARY_BAR_HEIGHT_PX : GANTT_BAR_HEIGHT_PX;
11529
+ return {
11530
+ x1: vinculo.side === "end" ? de.left + de.width : de.left,
11531
+ y1: topDaBarra(de, tipo) + altura / 2,
11532
+ x2: vinculo.x,
11533
+ y2: vinculo.y
11534
+ };
11535
+ }, [vinculo, geos]);
11536
+ const nowX = dateToX(now, windowStart, pxPerDay);
11537
+ const nowVisivel = nowX >= 0 && nowX <= larguraTotal;
11538
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttCanvasScroll(), ref: scrollRef, onScroll, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: larguraTotal, minWidth: "100%" }, children: [
11539
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttHead(), style: { height: GANTT_HEAD_HEIGHT_PX }, children: [
11540
+ /* @__PURE__ */ jsxRuntime.jsx(
11541
+ "div",
11542
+ {
11543
+ className: ganttHeadRow({ level: "group" }),
11544
+ style: { height: GANTT_HEAD_GROUP_PX },
11545
+ children: axis.groups.map((g) => {
11546
+ const largura = g.spanDays * pxPerDay;
11547
+ const cabe = largura >= GANTT_GROUP_LABEL_MIN_PX;
11548
+ return /* @__PURE__ */ jsxRuntime.jsx(
11549
+ "div",
11550
+ {
11551
+ className: ganttHeadCell({
11552
+ level: "group",
11553
+ boundary: limitesDeGrupo.has(
11554
+ diaDaUnidade(g.date) + g.spanDays
11555
+ )
11556
+ }),
11557
+ style: {
11558
+ left: dateToX(g.date, windowStart, pxPerDay),
11559
+ width: largura
11560
+ },
11561
+ title: cabe ? void 0 : String(g.label),
11562
+ children: cabe ? g.label : null
11563
+ },
11564
+ `g-${g.date.getTime()}`
11565
+ );
11566
+ })
11567
+ }
11568
+ ),
11569
+ /* @__PURE__ */ jsxRuntime.jsx(
11570
+ "div",
11571
+ {
11572
+ className: ganttHeadRow({ level: "unit" }),
11573
+ style: { height: GANTT_HEAD_UNIT_PX },
11574
+ children: axis.units.map((u) => {
11575
+ const hoje = dateFns.isSameDay(u.date, now);
11576
+ const dia = diaDaUnidade(u.date);
11577
+ return /* @__PURE__ */ jsxRuntime.jsx(
11578
+ "div",
11579
+ {
11580
+ className: ganttHeadCell({
11581
+ level: "unit",
11582
+ weekend: u.isWeekend,
11583
+ today: hoje,
11584
+ selected: selectedDay === dia,
11585
+ boundary: limitesDeGrupo.has(dia + u.spanDays)
11586
+ }),
11587
+ style: {
11588
+ left: dateToX(u.date, windowStart, pxPerDay),
11589
+ width: u.spanDays * pxPerDay,
11590
+ cursor: "pointer"
11591
+ },
11592
+ role: "columnheader",
11593
+ "aria-selected": selectedDay === dia,
11594
+ onClick: () => (
11595
+ // Clicar na coluna já selecionada DESSELECIONA — mesma
11596
+ // regra da linha, senão não há volta ao estado neutro.
11597
+ onSelectDay(selectedDay === dia ? null : dia)
11598
+ ),
11599
+ children: granularity === "day" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11600
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttHeadWeekday({ weekend: u.isWeekend }), children: dateFns.format(u.date, "EEEEEE", { locale }) }),
11601
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttHeadDayNumber({ today: hoje }), children: u.label })
11602
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttHeadDayNumber({ today: hoje }), children: u.label })
11603
+ },
11604
+ `u-${u.date.getTime()}`
11605
+ );
11606
+ })
11607
+ }
11608
+ )
11609
+ ] }),
11610
+ /* @__PURE__ */ jsxRuntime.jsxs(
11611
+ "div",
11612
+ {
11613
+ ref: canvasRef,
11614
+ className: ganttCanvas(),
11615
+ style: { width: larguraTotal, height: Math.max(alturaTotal, 1) },
11616
+ onMouseMove: (e) => {
11617
+ const x = e.clientX - e.currentTarget.getBoundingClientRect().left;
11618
+ const dia = Math.floor(x / pxPerDay);
11619
+ if (dia !== hoveredDay) onHoverDay(dia);
11620
+ },
11621
+ onMouseLeave: () => {
11622
+ onHoverRow(null);
11623
+ onHoverDay(null);
11624
+ },
11625
+ children: [
11626
+ axis.units.map((u) => {
11627
+ const dia = diaDaUnidade(u.date);
11628
+ return /* @__PURE__ */ jsxRuntime.jsx(
11629
+ "div",
11630
+ {
11631
+ className: ganttGridColumn({
11632
+ weekend: u.isWeekend,
11633
+ hovered: hoveredDay !== null && hoveredDay >= dia && hoveredDay < dia + u.spanDays,
11634
+ selected: selectedDay !== null && selectedDay >= dia && selectedDay < dia + u.spanDays,
11635
+ boundary: limitesDeGrupo.has(dia + u.spanDays)
11636
+ }),
11637
+ style: {
11638
+ left: dateToX(u.date, windowStart, pxPerDay),
11639
+ width: u.spanDays * pxPerDay
11640
+ },
11641
+ "aria-hidden": true
11642
+ },
11643
+ `col-${u.date.getTime()}`
11644
+ );
11645
+ }),
11646
+ rows.map(({ row }, i) => /* @__PURE__ */ jsxRuntime.jsx(
11647
+ "div",
11648
+ {
11649
+ className: ganttCanvasRow({
11650
+ type: row.type ?? "task",
11651
+ hovered: hoveredRow === i,
11652
+ selected: selectedRow === i
11653
+ }),
11654
+ style: { top: tops[i], height: alturas[i] },
11655
+ onMouseEnter: () => onHoverRow(i),
11656
+ "aria-hidden": true
11657
+ },
11658
+ `row-${row.id}`
11659
+ )),
11660
+ geos.lista.map((g) => {
11661
+ const tipo = g.row.type ?? "task";
11662
+ const aceitaGesto = tipo !== "summary";
11663
+ return /* @__PURE__ */ jsxRuntime.jsx(
11664
+ GanttBarView,
11665
+ {
11666
+ bar: g.bar,
11667
+ left: g.left,
11668
+ width: g.width,
11669
+ lane: g.lane,
11670
+ rowType: tipo,
11671
+ colorKey: g.colorKey,
11672
+ continuesBefore: g.continuesBefore,
11673
+ continuesAfter: g.continuesAfter,
11674
+ conflict: conflictBarIds.has(g.bar.id),
11675
+ critical: criticalBarIds.has(g.bar.id),
11676
+ movable: draggable && aceitaGesto,
11677
+ resizable: resizable && aceitaGesto,
11678
+ linkable: linkable && aceitaGesto,
11679
+ height: tipo === "summary" ? GANTT_SUMMARY_BAR_HEIGHT_PX : GANTT_BAR_HEIGHT_PX,
11680
+ dragging: gesto?.barId === g.bar.id,
11681
+ style: { top: topDaBarra(g, tipo) },
11682
+ onClick: (e) => onBarClick?.(g.bar, g.row, e),
11683
+ onMoveStart: (e) => iniciarGesto(g.bar.id, "move", e),
11684
+ onResizeStart: (lado, e) => iniciarGesto(
11685
+ g.bar.id,
11686
+ lado === "start" ? "resize-start" : "resize-end",
11687
+ e
11688
+ ),
11689
+ onLinkStart: (lado, e) => iniciarVinculo(g.bar.id, lado, e)
11690
+ },
11691
+ g.bar.id
11692
+ );
11693
+ }),
11694
+ /* @__PURE__ */ jsxRuntime.jsx(
11695
+ GanttLinksLayer,
11696
+ {
11697
+ links: linkGeos,
11698
+ width: larguraTotal,
11699
+ height: Math.max(alturaTotal, 1),
11700
+ onLinkClick
11701
+ }
11702
+ ),
11703
+ vinculoFantasma ? /* @__PURE__ */ jsxRuntime.jsx(
11704
+ "svg",
11705
+ {
11706
+ className: "pointer-events-none absolute inset-0 z-[4] overflow-visible",
11707
+ width: larguraTotal,
11708
+ height: Math.max(alturaTotal, 1),
11709
+ "aria-hidden": true,
11710
+ children: /* @__PURE__ */ jsxRuntime.jsx(
11711
+ "line",
11712
+ {
11713
+ x1: vinculoFantasma.x1,
11714
+ y1: vinculoFantasma.y1,
11715
+ x2: vinculoFantasma.x2,
11716
+ y2: vinculoFantasma.y2,
11717
+ className: ganttLinkGhost()
11718
+ }
11719
+ )
11720
+ }
11721
+ ) : null,
11722
+ nowVisivel ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ganttNowLine(), style: { left: nowX }, "aria-hidden": true, children: [
11723
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttNowStroke() }),
11724
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttNowDot() })
11725
+ ] }) : null
11726
+ ]
11727
+ }
11728
+ )
11729
+ ] }) });
11730
+ }
11731
+ const Gantt = React.forwardRef(function Gantt2({
11732
+ rows,
11733
+ links = [],
11734
+ view: viewProp,
11735
+ views,
11736
+ onViewChange,
11737
+ windowStart: wsProp,
11738
+ windowEnd: weProp,
11739
+ granularity: granProp,
11740
+ onGranularityChange,
11741
+ columns,
11742
+ gridWidth: gridWidthProp = 360,
11743
+ draggable = false,
11744
+ resizable = false,
11745
+ linkable = false,
11746
+ // Sem default no destructuring de propósito: `undefined` é o sinal de
11747
+ // "não controlado", e um default aqui o apagaria.
11748
+ criticalPath: criticalProp,
11749
+ onCriticalPathChange,
11750
+ criticalPathToggle = true,
11751
+ onGraphError,
11752
+ onLinkViolations,
11753
+ searchable = false,
11754
+ filterFields,
11755
+ filterModel,
11756
+ onFilterModelChange,
11757
+ onBarClick,
11758
+ onRowClick,
11759
+ /**
11760
+ * ⚠️ Estes quatro estavam DECLARADOS no tipo e nunca desestruturados aqui —
11761
+ * a raiz não os recebia. Era superfície de API sem nada por trás: os punhos
11762
+ * e as portas renderizavam, acessíveis, e o gesto não existia.
11763
+ */
11764
+ onBarMove,
11765
+ onBarResize,
11766
+ onLinkCreate,
11767
+ onLinkDelete,
11768
+ onDayAdd,
11769
+ onRowToggle,
11770
+ now = /* @__PURE__ */ new Date(),
11771
+ locale,
11772
+ weekStartsOn = 0,
11773
+ toolbarActions,
11774
+ primaryAction,
11775
+ emptyState,
11776
+ loading = false,
11777
+ loadingState,
11778
+ className
11779
+ }, ref) {
11780
+ const [viewLocal, setViewLocal] = React.useState(viewProp ?? "timeline");
11781
+ const visoesDisponiveis = React.useMemo(
11782
+ () => views && views.length > 0 ? views : ["timeline", "calendar", "list"],
11783
+ [views]
11784
+ );
11785
+ const viewPedida = viewProp ?? viewLocal;
11786
+ const view = visoesDisponiveis.includes(viewPedida) ? viewPedida : visoesDisponiveis[0];
11787
+ const [granLocal, setGranLocal] = React.useState(granProp ?? "day");
11788
+ const granularity = granProp ?? granLocal;
11789
+ const [criticalLocal, setCriticalLocal] = React.useState(criticalProp ?? false);
11790
+ const criticalPath = criticalProp ?? criticalLocal;
11791
+ const [busca, setBusca] = React.useState("");
11792
+ const [filterLocal, setFilterLocal] = React.useState({});
11793
+ const [colapsadas, setColapsadas] = React.useState(() => /* @__PURE__ */ new Set());
11794
+ const [gridWidth, setGridWidth] = React.useState(gridWidthProp);
11795
+ const [arrastandoDivisor, setArrastandoDivisor] = React.useState(false);
11796
+ const [janelaLocal, setJanelaLocal] = React.useState(
11797
+ () => deriveWindow(rows, granProp ?? "day")
11798
+ );
11799
+ const windowStart = wsProp ?? janelaLocal.start;
11800
+ const windowEnd = weProp ?? janelaLocal.end;
11801
+ const pxPerDay = GANTT_PX_PER_DAY[granularity];
11802
+ const modeloEfetivo = filterModel ?? filterLocal;
11803
+ const aplicarModelo = React.useCallback(
11804
+ (proximo) => {
11805
+ setFilterLocal(proximo);
11806
+ onFilterModelChange?.(proximo);
11807
+ },
11808
+ [onFilterModelChange]
11809
+ );
11810
+ const definirValores = React.useCallback(
11811
+ (fieldId, values) => {
11812
+ aplicarModelo({ ...modeloEfetivo, [fieldId]: values });
11813
+ },
11814
+ [modeloEfetivo, aplicarModelo]
11815
+ );
11816
+ const alternarValor = React.useCallback(
11817
+ (fieldId, value) => {
11818
+ const atuais = modeloEfetivo[fieldId] ?? [];
11819
+ const proximas = atuais.includes(value) ? atuais.filter((v) => v !== value) : [...atuais, value];
11820
+ aplicarModelo({ ...modeloEfetivo, [fieldId]: proximas });
11821
+ },
11822
+ [modeloEfetivo, aplicarModelo]
11823
+ );
11824
+ const rowsFiltradas = React.useMemo(() => {
11825
+ let saida = rows;
11826
+ const termo = busca.trim().toLowerCase();
11827
+ if (termo) {
11828
+ const casa = (r) => {
11829
+ const alvo = [
11830
+ r.searchText,
11831
+ typeof r.label === "string" ? r.label : "",
11832
+ typeof r.sublabel === "string" ? r.sublabel : "",
11833
+ ...r.bars.map((b) => b.searchText ?? "")
11834
+ ].join(" ").toLowerCase();
11835
+ return alvo.includes(termo);
11836
+ };
11837
+ const porId = new Map(rows.map((r) => [r.id, r]));
11838
+ const manter = /* @__PURE__ */ new Set();
11839
+ for (const r of rows) {
11840
+ if (!casa(r)) continue;
11841
+ manter.add(r.id);
11842
+ let pai = r.parent;
11843
+ let guarda = 0;
11844
+ while (pai && guarda < 50) {
11845
+ if (manter.has(pai)) break;
11846
+ manter.add(pai);
11847
+ pai = porId.get(pai)?.parent;
11848
+ guarda++;
11849
+ }
11850
+ }
11851
+ saida = saida.filter((r) => manter.has(r.id));
11852
+ }
11853
+ saida = aplicarFiltros(saida, filterFields, modeloEfetivo);
11854
+ return saida;
11855
+ }, [rows, busca, filterFields, modeloEfetivo]);
11856
+ const flat = React.useMemo(
11857
+ () => flattenRows(rowsFiltradas, colapsadas),
11858
+ [rowsFiltradas, colapsadas]
11859
+ );
11860
+ const alturas = React.useMemo(
11861
+ () => rowHeights(flat, GANTT_ROW_HEIGHT_PX, GANTT_LANE_HEIGHT_PX),
11862
+ [flat]
11863
+ );
11864
+ const offsets = React.useMemo(() => rowOffsets(alturas), [alturas]);
11865
+ const contagens = React.useMemo(() => {
11866
+ const saida = {};
11867
+ for (const campo of filterFields ?? []) {
11868
+ const kind = campo.kind ?? "multi";
11869
+ if (kind !== "multi" && kind !== "single" && kind !== "boolean") continue;
11870
+ const porOpcao = {};
11871
+ for (const r of rows) {
11872
+ const v = campo.accessor(r);
11873
+ if (v === void 0 || v === null) continue;
11874
+ for (const x of Array.isArray(v) ? v : [v]) {
11875
+ const chave = String(x);
11876
+ porOpcao[chave] = (porOpcao[chave] ?? 0) + 1;
11877
+ }
11878
+ }
11879
+ saida[campo.id] = porOpcao;
11880
+ }
11881
+ return saida;
11882
+ }, [rows, filterFields]);
11883
+ const todasBarras = React.useMemo(() => {
11884
+ const lista = [];
11885
+ for (const r of rows) lista.push(...r.bars);
11886
+ return lista;
11887
+ }, [rows]);
11888
+ const mapaBarras = React.useMemo(
11889
+ () => new Map(todasBarras.map((b) => [b.id, b])),
11890
+ [todasBarras]
11891
+ );
11892
+ const violacoes = React.useMemo(
11893
+ () => checkAllLinks(links, mapaBarras),
11894
+ [links, mapaBarras]
11895
+ );
11896
+ const conflictBarIds = React.useMemo(
11897
+ () => new Set(violacoes.map((v) => v.link.target)),
11898
+ [violacoes]
11899
+ );
11900
+ const violacoesRef = React.useRef("");
11901
+ React.useEffect(() => {
11902
+ const assinatura = violacoes.map((v) => `${v.link.id}:${v.deficitDays}`).join("|");
11903
+ if (assinatura === violacoesRef.current) return;
11904
+ violacoesRef.current = assinatura;
11905
+ onLinkViolations?.(violacoes);
11906
+ }, [violacoes, onLinkViolations]);
11907
+ const criticalBarIds = React.useMemo(() => {
11908
+ if (!criticalPath || links.length === 0) return /* @__PURE__ */ new Set();
11909
+ const r = computeCriticalPath(todasBarras, links);
11910
+ if ("cycle" in r) return /* @__PURE__ */ new Set();
11911
+ return r.critical;
11912
+ }, [criticalPath, links, todasBarras]);
11913
+ const cicloRef = React.useRef("");
11914
+ React.useEffect(() => {
11915
+ if (!criticalPath || links.length === 0) return;
11916
+ const r = computeCriticalPath(todasBarras, links);
11917
+ if (!("cycle" in r)) {
11918
+ cicloRef.current = "";
11919
+ return;
11920
+ }
11921
+ const assinatura = r.cycle.join("|");
11922
+ if (assinatura === cicloRef.current) return;
11923
+ cicloRef.current = assinatura;
11924
+ onGraphError?.({ kind: "cycle", barIds: r.cycle });
11925
+ }, [criticalPath, links, todasBarras, onGraphError]);
11926
+ const [hoveredRow, setHoveredRow] = React.useState(null);
11927
+ const [selectedRow, setSelectedRow] = React.useState(null);
11928
+ const [selectedDay, setSelectedDay] = React.useState(null);
11929
+ const [filtroAberto, setFiltroAberto] = React.useState(false);
11930
+ const [hoveredDay, setHoveredDay] = React.useState(null);
11931
+ const [scrollTop, setScrollTop] = React.useState(0);
11932
+ const aoRolar = React.useCallback((e) => {
11933
+ setScrollTop(e.currentTarget.scrollTop);
11934
+ }, []);
11935
+ const bodyRef = React.useRef(null);
11936
+ const scrollerRef = React.useRef(null);
11937
+ const [centrarEm, setCentrarEm] = React.useState(null);
11938
+ React.useEffect(() => {
11939
+ if (!centrarEm) return;
11940
+ const el = scrollerRef.current;
11941
+ if (!el) return;
11942
+ const x = dateToX(centrarEm, windowStart, pxPerDay);
11943
+ el.scrollLeft = Math.max(0, x - el.clientWidth / 2);
11944
+ setCentrarEm(null);
11945
+ }, [centrarEm, windowStart, pxPerDay]);
11946
+ React.useEffect(() => {
11947
+ if (!arrastandoDivisor) return;
11948
+ const mover2 = (e) => {
11949
+ const caixa = bodyRef.current?.getBoundingClientRect();
11950
+ if (!caixa) return;
11951
+ const bruto = e.clientX - caixa.left;
11952
+ setGridWidth(Math.min(Math.max(180, bruto), caixa.width * 0.6));
11953
+ };
11954
+ const soltar = () => setArrastandoDivisor(false);
11955
+ window.addEventListener("pointermove", mover2);
11956
+ window.addEventListener("pointerup", soltar);
11957
+ return () => {
11958
+ window.removeEventListener("pointermove", mover2);
11959
+ window.removeEventListener("pointerup", soltar);
11960
+ };
11961
+ }, [arrastandoDivisor]);
11962
+ const larguraJanelaDias = dateFns.differenceInCalendarDays(dateFns.startOfDay(windowEnd), dateFns.startOfDay(windowStart)) + 1;
11963
+ const mover = React.useCallback(
11964
+ (dias) => {
11965
+ if (wsProp || weProp) return;
11966
+ setJanelaLocal((j) => ({
11967
+ start: dateFns.addDays(j.start, dias),
11968
+ end: dateFns.addDays(j.end, dias)
11969
+ }));
11970
+ },
11971
+ [wsProp, weProp]
11972
+ );
11973
+ React.useImperativeHandle(
11974
+ ref,
11975
+ () => ({
11976
+ goToDate: (date) => {
11977
+ if (wsProp || weProp) return;
11978
+ const meio = Math.floor(larguraJanelaDias / 2);
11979
+ setJanelaLocal({
11980
+ start: dateFns.addDays(dateFns.startOfDay(date), -meio),
11981
+ end: dateFns.addDays(dateFns.startOfDay(date), larguraJanelaDias - meio - 1)
11982
+ });
11983
+ },
11984
+ goToToday: () => {
11985
+ if (wsProp || weProp) return;
11986
+ const meio = Math.floor(larguraJanelaDias / 2);
11987
+ setJanelaLocal({
11988
+ start: dateFns.addDays(dateFns.startOfDay(now), -meio),
11989
+ end: dateFns.addDays(dateFns.startOfDay(now), larguraJanelaDias - meio - 1)
11990
+ });
11991
+ },
11992
+ expandAll: () => setColapsadas(/* @__PURE__ */ new Set()),
11993
+ collapseAll: () => setColapsadas(new Set(rows.filter((r) => r.parent === void 0).map((r) => r.id)))
11994
+ }),
11995
+ [wsProp, weProp, larguraJanelaDias, now, rows]
11996
+ );
11997
+ const ancoraDoMes = React.useMemo(
11998
+ () => dateFns.addDays(dateFns.startOfDay(windowStart), Math.floor(larguraJanelaDias / 2)),
11999
+ [windowStart, larguraJanelaDias]
12000
+ );
12001
+ const mesVisivel = React.useMemo(
12002
+ () => ({
12003
+ start: dateFns.startOfMonth(ancoraDoMes),
12004
+ end: dateFns.endOfMonth(ancoraDoMes)
12005
+ }),
12006
+ [ancoraDoMes]
12007
+ );
12008
+ const visaoMensal = view === "calendar" || view === "list";
12009
+ const titulo = React.useMemo(() => {
12010
+ if (visaoMensal) {
12011
+ return dateFns.format(ancoraDoMes, "MMMM yyyy", { locale });
12012
+ }
12013
+ const mesmoAno = windowStart.getFullYear() === windowEnd.getFullYear();
12014
+ const a = dateFns.format(windowStart, mesmoAno ? "d MMM" : "d MMM yyyy", { locale });
12015
+ const b = dateFns.format(windowEnd, "d MMM yyyy", { locale });
12016
+ return `${a} – ${b}`;
12017
+ }, [visaoMensal, ancoraDoMes, windowStart, windowEnd, locale]);
12018
+ const passoDeNavegacao = React.useCallback(
12019
+ (direcao) => {
12020
+ if (visaoMensal) {
12021
+ const destino = dateFns.addMonths(ancoraDoMes, direcao);
12022
+ return dateFns.differenceInCalendarDays(destino, ancoraDoMes);
12023
+ }
12024
+ return direcao * Math.max(1, Math.floor(larguraJanelaDias / 2));
12025
+ },
12026
+ [visaoMensal, ancoraDoMes, larguraJanelaDias]
12027
+ );
12028
+ const formatarDataDeFiltro = React.useCallback(
12029
+ (iso) => {
12030
+ const d = parseDiaISO(iso);
12031
+ if (!d) return iso;
12032
+ return dateFns.format(d, "dd/MM/yy", { locale });
12033
+ },
12034
+ [locale]
12035
+ );
12036
+ const colunas = columns ?? GANTT_DEFAULT_COLUMNS;
12037
+ const appliedCount = contarAplicados(filterFields, modeloEfetivo);
12038
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: avatar.cn(ganttRoot(), className), children: [
12039
+ /* @__PURE__ */ jsxRuntime.jsx(
12040
+ GanttToolbar,
12041
+ {
12042
+ title: titulo,
12043
+ onPrev: () => mover(passoDeNavegacao(-1)),
12044
+ onNext: () => mover(passoDeNavegacao(1)),
12045
+ onToday: () => {
12046
+ if (wsProp || weProp) return;
12047
+ const meio = Math.floor(larguraJanelaDias / 2);
12048
+ setJanelaLocal({
12049
+ start: dateFns.addDays(dateFns.startOfDay(now), -meio),
12050
+ end: dateFns.addDays(dateFns.startOfDay(now), larguraJanelaDias - meio - 1)
12051
+ });
12052
+ },
12053
+ view,
12054
+ views: visoesDisponiveis,
12055
+ onViewChange: (v) => {
12056
+ if (viewProp === void 0) setViewLocal(v);
12057
+ onViewChange?.(v);
12058
+ },
12059
+ granularity,
12060
+ onGranularityChange: (g) => {
12061
+ const el = scrollerRef.current;
12062
+ if (el) {
12063
+ setCentrarEm(
12064
+ xToDate(el.scrollLeft + el.clientWidth / 2, windowStart, pxPerDay)
12065
+ );
12066
+ }
12067
+ if (granProp === void 0) setGranLocal(g);
12068
+ onGranularityChange?.(g);
12069
+ if (wsProp || weProp) return;
12070
+ const dias = GANTT_WINDOW_DAYS[g];
12071
+ const extensao = deriveWindow(rows, g);
12072
+ setJanelaLocal((j) => {
12073
+ const meio = dateFns.addDays(
12074
+ dateFns.startOfDay(j.start),
12075
+ Math.floor(
12076
+ dateFns.differenceInCalendarDays(dateFns.startOfDay(j.end), dateFns.startOfDay(j.start)) / 2
12077
+ )
12078
+ );
12079
+ const start = dateFns.addDays(meio, -Math.floor(dias / 2));
12080
+ const end = dateFns.addDays(meio, dias - Math.floor(dias / 2) - 1);
12081
+ return {
12082
+ start: extensao.start < start ? extensao.start : start,
12083
+ end: extensao.end > end ? extensao.end : end
12084
+ };
12085
+ });
12086
+ },
12087
+ searchable,
12088
+ search: busca,
12089
+ onSearchChange: setBusca,
12090
+ filterFields,
12091
+ onOpenFilterPanel: () => setFiltroAberto(true),
12092
+ appliedCount,
12093
+ filterModel: modeloEfetivo,
12094
+ onClearField: (id) => definirValores(id, []),
12095
+ onClearAll: () => aplicarModelo({}),
12096
+ onToggleFilterValue: alternarValor,
12097
+ filterCounts: contagens,
12098
+ formatFilterDate: formatarDataDeFiltro,
12099
+ hasLinks: links.length > 0,
12100
+ showCriticalToggle: criticalPathToggle,
12101
+ criticalPath,
12102
+ onToggleCriticalPath: () => {
12103
+ const proximo = !criticalPath;
12104
+ if (criticalProp === void 0) setCriticalLocal(proximo);
12105
+ onCriticalPathChange?.(proximo);
12106
+ },
12107
+ toolbarActions,
12108
+ primaryAction
12109
+ }
12110
+ ),
12111
+ /* @__PURE__ */ jsxRuntime.jsx(
12112
+ "div",
12113
+ {
12114
+ ref: bodyRef,
12115
+ className: ganttBody({ framed: view === "timeline" }),
12116
+ role: "region",
12117
+ "aria-label": `Cronograma, ${titulo}`,
12118
+ children: loading ? loadingState ?? /* @__PURE__ */ jsxRuntime.jsx(GanttSkeleton, { view, gridWidth }) : flat.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttEmpty(), children: emptyState ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: ganttEmptyText(), children: "Nenhuma tarefa neste período." }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
12119
+ view === "timeline" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
12120
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttGridPane(), style: { width: gridWidth }, children: /* @__PURE__ */ jsxRuntime.jsx(
12121
+ GanttGrid,
12122
+ {
12123
+ rows: flat,
12124
+ columns: colunas,
12125
+ heights: alturas,
12126
+ scrollTop,
12127
+ locale,
12128
+ hoveredRow,
12129
+ onHoverRow: setHoveredRow,
12130
+ selectedRow,
12131
+ onRowToggle: (id, colapsar) => {
12132
+ setColapsadas((prev) => {
12133
+ const proximo = new Set(prev);
12134
+ if (colapsar) proximo.add(id);
12135
+ else proximo.delete(id);
12136
+ return proximo;
12137
+ });
12138
+ onRowToggle?.(id, colapsar);
12139
+ },
12140
+ onRowClick: (row, e) => {
12141
+ const i = flat.findIndex((f) => f.row.id === row.id);
12142
+ setSelectedRow((atual) => atual === i ? null : i);
12143
+ onRowClick?.(row, e);
12144
+ }
12145
+ }
12146
+ ) }),
12147
+ /* @__PURE__ */ jsxRuntime.jsx(
12148
+ "div",
12149
+ {
12150
+ role: "separator",
12151
+ "aria-orientation": "vertical",
12152
+ "aria-label": "Redimensionar a grade",
12153
+ tabIndex: 0,
12154
+ className: ganttSplitter({ active: arrastandoDivisor }),
12155
+ onPointerDown: () => setArrastandoDivisor(true),
12156
+ onKeyDown: (e) => {
12157
+ if (e.key === "ArrowLeft") setGridWidth((w) => Math.max(180, w - 16));
12158
+ if (e.key === "ArrowRight") setGridWidth((w) => w + 16);
12159
+ }
12160
+ }
12161
+ )
12162
+ ] }) : null,
12163
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: ganttTimelinePane(), children: view === "timeline" ? /* @__PURE__ */ jsxRuntime.jsx(
12164
+ GanttTimelineView,
12165
+ {
12166
+ rows: flat,
12167
+ allRows: rows,
12168
+ links,
12169
+ windowStart,
12170
+ windowEnd,
12171
+ granularity,
12172
+ pxPerDay,
12173
+ heights: alturas,
12174
+ offsets,
12175
+ now,
12176
+ locale,
12177
+ weekStartsOn,
12178
+ conflictBarIds,
12179
+ criticalBarIds,
12180
+ draggable,
12181
+ resizable,
12182
+ linkable,
12183
+ hoveredRow,
12184
+ onHoverRow: setHoveredRow,
12185
+ hoveredDay,
12186
+ onHoverDay: setHoveredDay,
12187
+ selectedRow,
12188
+ selectedDay,
12189
+ onSelectDay: setSelectedDay,
12190
+ onBarMove,
12191
+ onBarResize,
12192
+ onLinkCreate,
12193
+ onLinkClick: onLinkDelete,
12194
+ onScroll: aoRolar,
12195
+ scrollRef: scrollerRef,
12196
+ onBarClick
12197
+ }
12198
+ ) : view === "calendar" ? (
12199
+ /*
12200
+ ⚠️ O mês âncora é o MEIO da janela, e chegar nisso custou uma
12201
+ medição.
12202
+
12203
+ Não é `now`: aí navegar com `‹ ›` moveria a janela e a grade
12204
+ ficaria parada — as duas visões falariam de períodos
12205
+ diferentes, e trocar de visão teleportaria o usuário.
12206
+
12207
+ E não é `windowStart`, que foi a primeira tentativa. A janela
12208
+ derivada começa UM DIA ANTES da primeira barra, então o mês
12209
+ âncora caía no mês anterior ao trabalho: medido no exemplo,
12210
+ `windowStart` = 31/ago abria agosto com **5 células ocupadas
12211
+ de 42** enquanto os dados viviam em set–nov.
12212
+
12213
+ O meio da janela é onde a massa do cronograma está, acompanha
12214
+ o `‹ ›` e nunca cai num mês de borda.
12215
+ */
12216
+ /* @__PURE__ */ jsxRuntime.jsx(
12217
+ GanttCalendarView,
12218
+ {
12219
+ rows: flat,
12220
+ anchor: ancoraDoMes,
12221
+ weekStartsOn,
12222
+ now,
12223
+ locale,
12224
+ conflictBarIds,
12225
+ criticalBarIds,
12226
+ onBarClick,
12227
+ onDayAdd
12228
+ }
12229
+ )
12230
+ ) : (
12231
+ /*
12232
+ A agenda recebe o MÊS, não a janela — ver a nota do
12233
+ `mesVisivel`. As setas `‹ ›` andam um mês nesta visão, então o
12234
+ recorte e a navegação falam a mesma unidade.
12235
+ */
12236
+ /* @__PURE__ */ jsxRuntime.jsx(
12237
+ GanttListView,
12238
+ {
12239
+ rows: flat,
12240
+ windowStart: mesVisivel.start,
12241
+ windowEnd: mesVisivel.end,
12242
+ now,
12243
+ locale,
12244
+ conflictBarIds,
12245
+ criticalBarIds,
12246
+ onBarClick,
12247
+ emptyState
12248
+ }
12249
+ )
12250
+ ) })
12251
+ ] })
12252
+ }
12253
+ ),
12254
+ filterFields && filterFields.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
12255
+ GanttFilterPanel,
12256
+ {
12257
+ open: filtroAberto,
12258
+ onOpenChange: setFiltroAberto,
12259
+ fields: filterFields,
12260
+ model: modeloEfetivo,
12261
+ onToggleValue: alternarValor,
12262
+ onSetFieldValues: definirValores,
12263
+ onClearAll: () => aplicarModelo({}),
12264
+ appliedCount,
12265
+ counts: contagens
12266
+ }
12267
+ ) : null
12268
+ ] });
12269
+ });
7861
12270
  const spinnerStyles = avatar.tv({
7862
12271
  base: ["inline-block shrink-0", "animate-spin motion-reduce:animate-none"],
7863
12272
  variants: {
@@ -11674,6 +16083,17 @@ exports.DatePicker = DatePicker;
11674
16083
  exports.DateSeparatorChip = DateSeparatorChip;
11675
16084
  exports.EmptyState = EmptyState;
11676
16085
  exports.FileUploadField = FileUploadField;
16086
+ exports.GANTT_BAR_MIN_WIDTH_PX = GANTT_BAR_MIN_WIDTH_PX;
16087
+ exports.GANTT_DEFAULT_COLUMNS = GANTT_DEFAULT_COLUMNS;
16088
+ exports.GANTT_HEAD_HEIGHT_PX = GANTT_HEAD_HEIGHT_PX;
16089
+ exports.GANTT_LANE_HEIGHT_PX = GANTT_LANE_HEIGHT_PX;
16090
+ exports.GANTT_PX_PER_DAY = GANTT_PX_PER_DAY;
16091
+ exports.GANTT_ROW_HEIGHT_PX = GANTT_ROW_HEIGHT_PX;
16092
+ exports.Gantt = Gantt;
16093
+ exports.GanttBarView = GanttBarView;
16094
+ exports.GanttFilterPanel = GanttFilterPanel;
16095
+ exports.GanttGrid = GanttGrid;
16096
+ exports.GanttLinksLayer = GanttLinksLayer;
11677
16097
  exports.Icon = Icon;
11678
16098
  exports.MarkdownText = MarkdownText;
11679
16099
  exports.MessageAck = MessageAck;
@@ -11701,26 +16121,49 @@ exports.ToolbarMobileDialog = ToolbarMobileDialog;
11701
16121
  exports.ToolbarMobileSection = ToolbarMobileSection;
11702
16122
  exports.ToolbarMobileSheet = ToolbarMobileSheet;
11703
16123
  exports.actionColumn = actionColumn;
16124
+ exports.buildMonthMatrix = buildMonthMatrix;
16125
+ exports.buildTimeAxis = buildTimeAxis;
11704
16126
  exports.cardCheckbox = cardCheckbox;
11705
16127
  exports.cardOption = cardOption;
11706
16128
  exports.cardOptionGroup = cardOptionGroup;
16129
+ exports.checkAllLinks = checkAllLinks;
16130
+ exports.checkLink = checkLink;
16131
+ exports.clipToWindow = clipToWindow;
11707
16132
  exports.colorPickerStyles = colorPickerStyles;
16133
+ exports.computeCriticalPath = computeCriticalPath;
16134
+ exports.computeOverflow = computeOverflow;
16135
+ exports.constraintDates = constraintDates;
11708
16136
  exports.conversationListItemStyles = conversationListItemStyles;
11709
16137
  exports.currencyColumn = currencyColumn;
11710
16138
  exports.customColumn = customColumn;
11711
16139
  exports.dateColumn = dateColumn;
11712
16140
  exports.dateSeparatorChipStyles = dateSeparatorChipStyles;
16141
+ exports.dateToDayOffset = dateToDayOffset;
16142
+ exports.dateToX = dateToX;
16143
+ exports.daysOfBar = daysOfBar;
16144
+ exports.deriveSummaryRange = deriveSummaryRange;
16145
+ exports.deriveWindow = deriveWindow;
11713
16146
  exports.emailColumn = emailColumn;
11714
16147
  exports.emptyStateStyles = emptyStateStyles;
16148
+ exports.flattenRows = flattenRows;
16149
+ exports.ganttDaySegment = ganttDaySegment;
11715
16150
  exports.iconStyles = iconStyles;
11716
16151
  exports.icons = icons;
16152
+ exports.linkPath = linkPath;
16153
+ exports.linkSides = linkSides;
11717
16154
  exports.markdownTextStyles = markdownTextStyles;
11718
16155
  exports.messageAckStyles = messageAckStyles;
11719
16156
  exports.messageBubbleStyles = messageBubbleStyles;
11720
16157
  exports.messageComposerStyles = messageComposerStyles;
11721
16158
  exports.messageVariablesPickerStyles = messageVariablesPickerStyles;
16159
+ exports.monthBounds = monthBounds;
16160
+ exports.monthMatrixRange = monthMatrixRange;
11722
16161
  exports.numberColumn = numberColumn;
16162
+ exports.packLanes = packLanes;
11723
16163
  exports.phoneColumn = phoneColumn;
16164
+ exports.rowHeights = rowHeights;
16165
+ exports.rowOffsets = rowOffsets;
16166
+ exports.snapDate = snapDate;
11724
16167
  exports.statusColumn = statusColumn;
11725
16168
  exports.tabsNavigationAcao = tabsNavigationAcao;
11726
16169
  exports.tabsNavigationAcoes = tabsNavigationAcoes;
@@ -11733,9 +16176,11 @@ exports.tabsNavigationTrilho = tabsNavigationTrilho;
11733
16176
  exports.textColumn = textColumn;
11734
16177
  exports.toast = toast;
11735
16178
  exports.toastVariants = toastVariants;
16179
+ exports.topoSort = topoSort;
11736
16180
  exports.useBrand = useBrand;
11737
16181
  exports.useChart = useChart;
11738
16182
  exports.useToolbarFilterControl = useToolbarFilterControl;
11739
16183
  exports.useToolbarFilters = useToolbarFilters;
11740
16184
  exports.useToolbarSort = useToolbarSort;
16185
+ exports.xToDate = xToDate;
11741
16186
  //# sourceMappingURL=index.cjs.map