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