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