@snksergio/design-system 0.55.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist-lib/index.cjs +2597 -3
  2. package/dist-lib/index.cjs.map +1 -1
  3. package/dist-lib/index.mjs +2631 -37
  4. package/dist-lib/index.mjs.map +1 -1
  5. package/dist-lib/src/components/index.d.ts +1 -0
  6. package/dist-lib/src/components/index.d.ts.map +1 -1
  7. package/dist-lib/src/components/ui/Scheduler/hooks/layout.d.ts +165 -0
  8. package/dist-lib/src/components/ui/Scheduler/hooks/layout.d.ts.map +1 -0
  9. package/dist-lib/src/components/ui/Scheduler/hooks/use-media-query.d.ts +21 -0
  10. package/dist-lib/src/components/ui/Scheduler/hooks/use-media-query.d.ts.map +1 -0
  11. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-dnd.d.ts +41 -0
  12. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-dnd.d.ts.map +1 -0
  13. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-filter.d.ts +35 -0
  14. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-filter.d.ts.map +1 -0
  15. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-keyboard.d.ts +47 -0
  16. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-keyboard.d.ts.map +1 -0
  17. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-state.d.ts +44 -0
  18. package/dist-lib/src/components/ui/Scheduler/hooks/use-scheduler-state.d.ts.map +1 -0
  19. package/dist-lib/src/components/ui/Scheduler/index.d.ts +14 -0
  20. package/dist-lib/src/components/ui/Scheduler/index.d.ts.map +1 -0
  21. package/dist-lib/src/components/ui/Scheduler/parts/draggable-event.d.ts +30 -0
  22. package/dist-lib/src/components/ui/Scheduler/parts/draggable-event.d.ts.map +1 -0
  23. package/dist-lib/src/components/ui/Scheduler/parts/mini-month.d.ts +28 -0
  24. package/dist-lib/src/components/ui/Scheduler/parts/mini-month.d.ts.map +1 -0
  25. package/dist-lib/src/components/ui/Scheduler/parts/scheduler-event.d.ts +53 -0
  26. package/dist-lib/src/components/ui/Scheduler/parts/scheduler-event.d.ts.map +1 -0
  27. package/dist-lib/src/components/ui/Scheduler/parts/scheduler-filter-panel.d.ts +54 -0
  28. package/dist-lib/src/components/ui/Scheduler/parts/scheduler-filter-panel.d.ts.map +1 -0
  29. package/dist-lib/src/components/ui/Scheduler/parts/scheduler-toolbar.d.ts +25 -0
  30. package/dist-lib/src/components/ui/Scheduler/parts/scheduler-toolbar.d.ts.map +1 -0
  31. package/dist-lib/src/components/ui/Scheduler/scheduler.d.ts +44 -0
  32. package/dist-lib/src/components/ui/Scheduler/scheduler.d.ts.map +1 -0
  33. package/dist-lib/src/components/ui/Scheduler/scheduler.styles.d.ts +911 -0
  34. package/dist-lib/src/components/ui/Scheduler/scheduler.styles.d.ts.map +1 -0
  35. package/dist-lib/src/components/ui/Scheduler/scheduler.types.d.ts +285 -0
  36. package/dist-lib/src/components/ui/Scheduler/scheduler.types.d.ts.map +1 -0
  37. package/dist-lib/src/components/ui/Scheduler/views/list.d.ts +33 -0
  38. package/dist-lib/src/components/ui/Scheduler/views/list.d.ts.map +1 -0
  39. package/dist-lib/src/components/ui/Scheduler/views/month.d.ts +47 -0
  40. package/dist-lib/src/components/ui/Scheduler/views/month.d.ts.map +1 -0
  41. package/dist-lib/src/components/ui/Scheduler/views/time-grid.d.ts +48 -0
  42. package/dist-lib/src/components/ui/Scheduler/views/time-grid.d.ts.map +1 -0
  43. package/package.json +1 -1
@@ -11,6 +11,8 @@ const lucideReact = require("lucide-react");
11
11
  const input = require("./chunks/input-DqaLFxER.cjs");
12
12
  const kpiDelta = require("./chunks/kpi-delta-wEzw5zSu.cjs");
13
13
  const pageHeader = require("./chunks/page-header-sValhWca.cjs");
14
+ const core = require("@dnd-kit/core");
15
+ const dateFns = require("date-fns");
14
16
  const RechartsPrimitive = require("recharts");
15
17
  const reactVirtual = require("@tanstack/react-virtual");
16
18
  require("@hello-pangea/dnd");
@@ -5266,6 +5268,2596 @@ const MonthYearPicker = React.forwardRef(function MonthYearPicker2({
5266
5268
  ] });
5267
5269
  });
5268
5270
  MonthYearPicker.displayName = "MonthYearPicker";
5271
+ const schedulerRoot = avatar.tv({
5272
+ base: [
5273
+ "flex h-full min-h-0 w-full flex-col gap-gp-2xl",
5274
+ "text-fg-default"
5275
+ ]
5276
+ });
5277
+ const schedulerToolbar = avatar.tv({
5278
+ base: [
5279
+ "flex flex-col gap-gp-xl",
5280
+ "lg:flex-row lg:flex-wrap lg:items-center lg:justify-between"
5281
+ ]
5282
+ });
5283
+ const schedulerToolbarSide = avatar.tv({
5284
+ base: "flex min-w-0 items-center gap-gp-md",
5285
+ variants: {
5286
+ /**
5287
+ * `leading` = título + navegação. Empilhado, ocupa a linha inteira e
5288
+ * separa os dois extremos — é o que joga `‹ Hoje ›` pro fim.
5289
+ * `trailing` = busca + controles. A busca é quem estica (`flex-1` nela),
5290
+ * então aqui basta ocupar a linha.
5291
+ */
5292
+ slot: {
5293
+ leading: "w-full justify-between lg:w-auto lg:flex-wrap lg:justify-start",
5294
+ trailing: "w-full lg:w-auto lg:flex-wrap"
5295
+ }
5296
+ },
5297
+ defaultVariants: { slot: "trailing" }
5298
+ });
5299
+ const schedulerTitle = avatar.tv({
5300
+ base: [
5301
+ "min-w-0 truncate text-title-sm font-semibold text-fg-default",
5302
+ "first-letter:uppercase"
5303
+ ]
5304
+ });
5305
+ const schedulerNavGroup = avatar.tv({
5306
+ base: [
5307
+ "inline-flex shrink-0 items-center",
5308
+ "[&>*:not(:first-child)]:-ml-px",
5309
+ // O segmento com foco sobe pra que o anel não seja cortado pelo vizinho.
5310
+ "[&>*:focus-visible]:relative [&>*:focus-visible]:z-[1]",
5311
+ "[&>*:first-child]:!rounded-r-none",
5312
+ "[&>*:last-child]:!rounded-l-none",
5313
+ "[&>*:not(:first-child):not(:last-child)]:!rounded-none"
5314
+ ]
5315
+ });
5316
+ avatar.tv({
5317
+ base: "tabular-nums"
5318
+ });
5319
+ const schedulerViewMenuItem = avatar.tv({
5320
+ base: "flex items-center justify-between gap-gp-xl"
5321
+ });
5322
+ const schedulerFilterDot = avatar.tv({
5323
+ base: [
5324
+ "pointer-events-none absolute -right-[4px] -top-[4px] z-[2]",
5325
+ "size-[13px] rounded-radius-full",
5326
+ "bg-bg-brand border-2 border-bg-canvas"
5327
+ ]
5328
+ });
5329
+ const schedulerSearch = avatar.tv({
5330
+ base: [
5331
+ "relative flex cursor-text items-center gap-gp-md",
5332
+ "h-form-lg rounded-radius-lg px-pad-lg",
5333
+ "bg-bg-surface dark:bg-bg-muted",
5334
+ "border border-border-subtle dark:border-border-input",
5335
+ "shadow-sh-sm dark:shadow-sh-none",
5336
+ "text-fg-default",
5337
+ "min-w-0 flex-1 lg:flex-initial",
5338
+ "lg:w-[200px] lg:focus-within:w-[300px]",
5339
+ "transition-[width,border-color,background-color,box-shadow] duration-200",
5340
+ "focus-within:border-border-brand focus-within:shadow-sh-ring",
5341
+ "[&_svg]:size-icon-sm [&_svg]:shrink-0 [&_svg]:text-fg-muted"
5342
+ ],
5343
+ variants: {
5344
+ /** Mantém expandido mesmo sem foco quando já há termo digitado. */
5345
+ expanded: { true: "lg:w-[300px]", false: "" }
5346
+ },
5347
+ defaultVariants: { expanded: false }
5348
+ });
5349
+ const schedulerSearchInput = avatar.tv({
5350
+ base: [
5351
+ "min-w-0 flex-1 border-0 bg-transparent outline-none",
5352
+ "text-body-sm font-normal text-fg-default",
5353
+ "placeholder:text-fg-muted"
5354
+ ]
5355
+ });
5356
+ const schedulerFilterRow = avatar.tv({
5357
+ base: [
5358
+ "flex items-center gap-gp-md",
5359
+ "flex-nowrap overflow-x-auto sm:flex-wrap sm:overflow-x-visible",
5360
+ "[scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
5361
+ ]
5362
+ });
5363
+ const schedulerFilterChip = avatar.tv({
5364
+ base: [
5365
+ "inline-flex shrink-0 cursor-pointer items-center gap-gp-sm",
5366
+ "h-form-md rounded-radius-lg pl-pad-lg pr-pad-md",
5367
+ "bg-bg-surface dark:bg-bg-muted",
5368
+ "border text-body-xs font-normal text-fg-default",
5369
+ "transition-[background-color,border-color] duration-150",
5370
+ "hover:bg-bg-muted-hover hover:border-border-default dark:hover:bg-bg-accent",
5371
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
5372
+ "[&_svg]:size-icon-xs [&_svg]:shrink-0 [&_svg]:text-fg-subtle"
5373
+ ],
5374
+ variants: {
5375
+ applied: {
5376
+ true: "border-solid border-border-brand bg-bg-brand-subtle dark:bg-bg-brand-subtle",
5377
+ false: "border-dashed border-border-input"
5378
+ }
5379
+ },
5380
+ defaultVariants: { applied: false }
5381
+ });
5382
+ const schedulerFilterChipName = avatar.tv({
5383
+ base: "font-semibold text-fg-default"
5384
+ });
5385
+ const schedulerFilterChipValue = avatar.tv({
5386
+ base: [
5387
+ "inline-flex h-comp-2xs items-center px-pad-md",
5388
+ "rounded-radius-sm bg-bg-muted dark:bg-bg-accent",
5389
+ "text-body-xs font-medium text-fg-default"
5390
+ ]
5391
+ });
5392
+ const schedulerClearLink = avatar.tv({
5393
+ base: [
5394
+ "ml-pad-sm cursor-pointer border-0 bg-transparent p-0",
5395
+ "text-body-xs font-medium text-fg-brand outline-none",
5396
+ "underline-offset-2 transition-opacity duration-150",
5397
+ "hover:underline focus-visible:underline"
5398
+ ]
5399
+ });
5400
+ const schedulerBody = avatar.tv({
5401
+ base: "flex min-h-0 flex-1 gap-gp-2xl"
5402
+ });
5403
+ const schedulerMain = avatar.tv({
5404
+ base: "flex min-h-0 min-w-0 flex-1 flex-col"
5405
+ });
5406
+ const schedulerFilterAside = avatar.tv({
5407
+ base: [
5408
+ "flex min-h-0 flex-col overflow-y-auto",
5409
+ // `scrollbar-thin` é a barra do DS (26 usos no repo contra 2 do
5410
+ // `scrollbar-default`) — trilho transparente + thumb tokenizado.
5411
+ // ⚠️ É `@utility` do tema GERADO, não do globals.css: existe nos 4 canais.
5412
+ "scrollbar-thin"
5413
+ ],
5414
+ variants: {
5415
+ /**
5416
+ * `embedded` = o painel vive DENTRO de outro contêiner que já é a
5417
+ * superfície — hoje o `FloatingPanel` do modo drawer (<1024px).
5418
+ *
5419
+ * Como coluna (`false`, o default) ele É o card: largura própria, borda,
5420
+ * radius e fundo. Embutido, os quatro viram defeito — largura fixa deixa
5421
+ * uma faixa morta à direita do drawer, e borda+radius+fundo repetem a
5422
+ * superfície do painel que já o embrulha (card dentro de card, a mesma
5423
+ * classe de erro da L-050).
5424
+ */
5425
+ embedded: {
5426
+ false: [
5427
+ "w-[280px] shrink-0",
5428
+ "rounded-radius-xl border border-border-default bg-bg-surface"
5429
+ ],
5430
+ true: "w-full"
5431
+ }
5432
+ },
5433
+ defaultVariants: { embedded: false }
5434
+ });
5435
+ const schedulerAsideSection = avatar.tv({
5436
+ base: [
5437
+ "flex shrink-0 flex-col gap-gp-md",
5438
+ "border-b border-border-default p-sp-xl last:border-b-0"
5439
+ ],
5440
+ variants: {
5441
+ /** O cabeçalho é mais raso: só uma linha de título + ações. */
5442
+ compact: { true: "py-pad-lg", false: "" },
5443
+ /**
5444
+ * Cabeçalho fixo no topo enquanto o resto rola.
5445
+ *
5446
+ * `bg-bg-surface` é **obrigatório** e não decoração: `sticky` não cria
5447
+ * fundo, então sem ele o mini-calendário passaria por baixo do título. E
5448
+ * `z-[1]` porque as caixas e o `Checkbox` do Radix criam contexto próprio
5449
+ * de empilhamento — sem o z, o conteúdo que rola sobrepõe o cabeçalho.
5450
+ */
5451
+ sticky: { true: "sticky top-0 z-[1] bg-bg-surface", false: "" }
5452
+ },
5453
+ defaultVariants: { compact: false, sticky: false }
5454
+ });
5455
+ const schedulerAsideHead = avatar.tv({
5456
+ base: "flex items-center justify-between gap-gp-md"
5457
+ });
5458
+ const schedulerAsideTitle = avatar.tv({
5459
+ base: "text-body-sm font-semibold text-fg-default"
5460
+ });
5461
+ const schedulerMiniHead = avatar.tv({
5462
+ base: "flex items-center justify-between gap-gp-sm"
5463
+ });
5464
+ const schedulerMiniTitle = avatar.tv({
5465
+ base: "min-w-0 truncate text-body-sm font-semibold text-fg-default first-letter:uppercase"
5466
+ });
5467
+ const schedulerMiniGrid = avatar.tv({
5468
+ base: "grid grid-cols-7 gap-gp-2xs"
5469
+ });
5470
+ const schedulerMiniWeekday = avatar.tv({
5471
+ base: [
5472
+ "grid size-comp-xs place-items-center",
5473
+ "text-caption-xs font-medium uppercase text-fg-subtle"
5474
+ ]
5475
+ });
5476
+ const schedulerMiniDay = avatar.tv({
5477
+ base: [
5478
+ "grid size-comp-xs cursor-pointer place-items-center rounded-radius-full",
5479
+ "border-0 bg-transparent outline-none",
5480
+ "text-caption-md tabular-nums",
5481
+ "transition-[background-color,color,box-shadow] duration-150",
5482
+ "hover:bg-bg-muted",
5483
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
5484
+ ],
5485
+ variants: {
5486
+ outside: { true: "text-fg-subtle", false: "text-fg-default" },
5487
+ hasEvents: { true: "font-semibold", false: "" },
5488
+ today: { true: "ring-1 ring-border-brand", false: "" },
5489
+ selected: {
5490
+ true: "bg-bg-brand font-semibold text-fg-on-brand hover:bg-bg-brand-hover",
5491
+ false: ""
5492
+ }
5493
+ },
5494
+ compoundVariants: [
5495
+ // Selecionado vence "hoje": um anel em volta do disco cheio não informa
5496
+ // nada e adiciona 2px de ruído.
5497
+ { selected: true, today: true, class: "ring-0" },
5498
+ { selected: true, outside: true, class: "text-fg-on-brand" }
5499
+ ],
5500
+ defaultVariants: {
5501
+ outside: false,
5502
+ hasEvents: false,
5503
+ today: false,
5504
+ selected: false
5505
+ }
5506
+ });
5507
+ const schedulerGroup = avatar.tv({
5508
+ base: "flex flex-col gap-gp-sm"
5509
+ });
5510
+ const schedulerGroupHead = avatar.tv({
5511
+ base: [
5512
+ "flex w-full cursor-pointer items-center justify-between gap-gp-md",
5513
+ "min-h-form-sm rounded-radius-sm border-0 bg-transparent -mx-pad-sm px-pad-sm outline-none",
5514
+ "text-body-sm font-semibold text-fg-default",
5515
+ "transition-colors duration-150",
5516
+ "hover:bg-bg-muted",
5517
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
5518
+ "[&_svg]:size-icon-sm [&_svg]:shrink-0 [&_svg]:text-fg-subtle"
5519
+ ]
5520
+ });
5521
+ const schedulerGroupChevron = avatar.tv({
5522
+ base: "transition-transform duration-150",
5523
+ variants: { open: { true: "", false: "-rotate-90" } },
5524
+ defaultVariants: { open: true }
5525
+ });
5526
+ const schedulerOption = avatar.tv({
5527
+ base: [
5528
+ "flex cursor-pointer items-center gap-gp-md",
5529
+ "min-h-form-sm rounded-radius-sm -mx-pad-sm px-pad-sm",
5530
+ "text-body-sm text-fg-default",
5531
+ "transition-colors duration-150",
5532
+ "hover:bg-bg-muted",
5533
+ "has-[:focus-visible]:ring-4 has-[:focus-visible]:ring-ring-brand"
5534
+ ]
5535
+ });
5536
+ const schedulerOptionLabel = avatar.tv({
5537
+ base: "min-w-0 flex-1 truncate"
5538
+ });
5539
+ const schedulerOptionCount = avatar.tv({
5540
+ base: "shrink-0 text-caption-sm tabular-nums text-fg-subtle"
5541
+ });
5542
+ const schedulerOptionBox = avatar.tv({
5543
+ base: "shrink-0",
5544
+ variants: {
5545
+ color: {
5546
+ brand: "data-[state=checked]:bg-bg-brand data-[state=checked]:border-border-brand [&_svg]:text-fg-on-brand",
5547
+ info: "data-[state=checked]:bg-bg-info data-[state=checked]:border-bg-info [&_svg]:text-fg-on-info",
5548
+ success: "data-[state=checked]:bg-bg-success data-[state=checked]:border-bg-success [&_svg]:text-fg-on-success",
5549
+ warning: "data-[state=checked]:bg-bg-warning data-[state=checked]:border-bg-warning [&_svg]:text-fg-on-warning",
5550
+ danger: "data-[state=checked]:bg-bg-danger data-[state=checked]:border-bg-danger [&_svg]:text-fg-on-danger",
5551
+ neutral: "data-[state=checked]:bg-fg-subtle data-[state=checked]:border-fg-subtle [&_svg]:text-bg-surface"
5552
+ }
5553
+ },
5554
+ defaultVariants: { color: "brand" }
5555
+ });
5556
+ const schedulerMonthFrame = avatar.tv({
5557
+ base: [
5558
+ "flex min-h-0 flex-1 flex-col overflow-hidden",
5559
+ "rounded-radius-xl border border-border-default bg-bg-surface"
5560
+ ]
5561
+ });
5562
+ const schedulerWeekdayRow = avatar.tv({
5563
+ base: [
5564
+ "grid grid-cols-7 border-b border-border-default bg-bg-subtle dark:bg-bg-canvas"
5565
+ ]
5566
+ });
5567
+ const schedulerWeekdayCell = avatar.tv({
5568
+ base: [
5569
+ "flex min-h-form-md items-center justify-center",
5570
+ "text-caption-md font-medium text-fg-muted",
5571
+ "first-letter:uppercase"
5572
+ ]
5573
+ });
5574
+ const schedulerMonthGrid = avatar.tv({
5575
+ base: "grid min-h-0 flex-1 grid-cols-7 grid-rows-6"
5576
+ });
5577
+ const schedulerMonthCell = avatar.tv({
5578
+ base: [
5579
+ "group/cell relative flex min-h-comp-4xl min-w-0 flex-col gap-gp-2xs",
5580
+ "border-b border-r border-border-subtle p-sp-2xs",
5581
+ "[&:nth-child(7n)]:border-r-0",
5582
+ "transition-colors duration-150"
5583
+ ],
5584
+ variants: {
5585
+ /** Dia de mês vizinho — recuado, não escondido: a grade é sempre 6×7. */
5586
+ outside: {
5587
+ true: "bg-bg-subtle/40 dark:bg-bg-canvas",
5588
+ false: "bg-bg-surface"
5589
+ },
5590
+ /** Última linha da grade não desenha borda inferior (o frame já fecha). */
5591
+ lastRow: { true: "border-b-0", false: "" },
5592
+ interactive: { true: "hover:bg-bg-table-row-hover", false: "" }
5593
+ },
5594
+ defaultVariants: { outside: false, lastRow: false, interactive: false }
5595
+ });
5596
+ const schedulerDayHead = avatar.tv({
5597
+ base: "flex shrink-0 items-center justify-between gap-gp-sm"
5598
+ });
5599
+ const schedulerDayNumber = avatar.tv({
5600
+ base: [
5601
+ "grid size-comp-xs shrink-0 place-items-center rounded-radius-full",
5602
+ "text-body-xs font-medium tabular-nums",
5603
+ "transition-colors duration-150"
5604
+ ],
5605
+ variants: {
5606
+ today: {
5607
+ true: "bg-bg-brand font-semibold text-fg-on-brand",
5608
+ false: "text-fg-default"
5609
+ },
5610
+ outside: { true: "text-fg-subtle", false: "" }
5611
+ },
5612
+ compoundVariants: [
5613
+ // `today` vence `outside`: o dia de hoje continua marcado mesmo quando
5614
+ // aparece como dia vizinho na grade de outro mês.
5615
+ { today: true, outside: true, class: "text-fg-on-brand" }
5616
+ ],
5617
+ defaultVariants: { today: false, outside: false }
5618
+ });
5619
+ const schedulerSlotAdd = avatar.tv({
5620
+ base: [
5621
+ "grid size-comp-3xs shrink-0 place-items-center rounded-radius-sm",
5622
+ "cursor-pointer border-0 bg-transparent text-fg-subtle outline-none",
5623
+ "opacity-0 transition-[opacity,background-color,color] duration-150",
5624
+ "group-hover/cell:opacity-100 focus-visible:opacity-100",
5625
+ "hover:bg-bg-muted hover:text-fg-default",
5626
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
5627
+ "[&_svg]:size-icon-xs"
5628
+ ]
5629
+ });
5630
+ const schedulerCellEvents = avatar.tv({
5631
+ base: "flex min-h-0 flex-col gap-gp-2xs overflow-hidden"
5632
+ });
5633
+ const schedulerOverflowButton = avatar.tv({
5634
+ base: [
5635
+ "flex min-h-comp-2xs w-full cursor-pointer items-center",
5636
+ "rounded-radius-sm border-0 bg-transparent px-pad-md",
5637
+ "text-caption-sm font-medium text-fg-muted outline-none",
5638
+ "transition-colors duration-150",
5639
+ "hover:bg-bg-muted hover:text-fg-default",
5640
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
5641
+ ]
5642
+ });
5643
+ const schedulerOverflowPanel = avatar.tv({
5644
+ base: "flex max-h-[320px] min-w-[240px] flex-col gap-gp-2xs overflow-y-auto p-sp-xs"
5645
+ });
5646
+ const schedulerOverflowPanelTitle = avatar.tv({
5647
+ base: "px-pad-md pb-pad-sm text-caption-sm font-semibold text-fg-muted first-letter:uppercase"
5648
+ });
5649
+ const schedulerTimeFrame = avatar.tv({
5650
+ base: [
5651
+ "flex min-h-0 flex-1 flex-col overflow-hidden",
5652
+ "rounded-radius-xl border border-border-default bg-bg-surface"
5653
+ ]
5654
+ });
5655
+ const schedulerTimeHead = avatar.tv({
5656
+ base: "flex shrink-0 border-b border-border-default bg-bg-subtle dark:bg-bg-canvas"
5657
+ });
5658
+ const schedulerTimeGutter = avatar.tv({
5659
+ base: "w-comp-4xl shrink-0"
5660
+ });
5661
+ const schedulerTimeHeadDay = avatar.tv({
5662
+ base: [
5663
+ "flex min-w-0 flex-1 flex-col items-center justify-center gap-gp-2xs",
5664
+ "border-l border-border-subtle py-pad-md"
5665
+ ],
5666
+ variants: {
5667
+ /** Sábado/domingo recuados — mesma leitura da grade do mês. */
5668
+ weekend: { true: "bg-bg-subtle/40 dark:bg-bg-canvas", false: "" }
5669
+ },
5670
+ defaultVariants: { weekend: false }
5671
+ });
5672
+ const schedulerTimeHeadWeekday = avatar.tv({
5673
+ base: "text-caption-md font-medium uppercase text-fg-muted"
5674
+ });
5675
+ const schedulerAllDayRow = avatar.tv({
5676
+ base: "flex shrink-0 border-b border-border-default"
5677
+ });
5678
+ const schedulerAllDayLabel = avatar.tv({
5679
+ base: [
5680
+ "flex w-comp-4xl shrink-0 items-start justify-end",
5681
+ "px-pad-md py-pad-sm text-caption-sm text-fg-subtle"
5682
+ ]
5683
+ });
5684
+ const schedulerAllDayCell = avatar.tv({
5685
+ base: [
5686
+ "flex min-w-0 flex-1 flex-col gap-gp-2xs",
5687
+ "border-l border-border-subtle p-sp-2xs"
5688
+ ]
5689
+ });
5690
+ const schedulerTimeBody = avatar.tv({
5691
+ base: "min-h-0 flex-1 overflow-y-auto overscroll-contain scrollbar-thin"
5692
+ });
5693
+ const schedulerTimeCanvas = avatar.tv({
5694
+ base: "relative flex"
5695
+ });
5696
+ const schedulerHourLabel = avatar.tv({
5697
+ base: [
5698
+ "relative flex h-comp-3xl items-start justify-end pr-pad-md",
5699
+ "text-caption-sm tabular-nums text-fg-subtle",
5700
+ "first:opacity-0"
5701
+ ]
5702
+ });
5703
+ const schedulerHourLabelText = avatar.tv({
5704
+ base: "-translate-y-1/2"
5705
+ });
5706
+ const schedulerTimeColumn = avatar.tv({
5707
+ base: "relative min-w-0 flex-1 border-l border-border-subtle",
5708
+ variants: {
5709
+ weekend: { true: "bg-bg-subtle/40 dark:bg-bg-canvas", false: "" }
5710
+ },
5711
+ defaultVariants: { weekend: false }
5712
+ });
5713
+ const schedulerHourSlot = avatar.tv({
5714
+ base: [
5715
+ "block h-comp-3xl w-full cursor-pointer",
5716
+ "border-b border-border-subtle bg-transparent p-0 outline-none",
5717
+ "transition-colors duration-150",
5718
+ "hover:bg-bg-table-row-hover",
5719
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
5720
+ "last:border-b-0"
5721
+ ]
5722
+ });
5723
+ const schedulerNowLine = avatar.tv({
5724
+ base: "pointer-events-none absolute left-0 right-0 z-[2] flex items-center"
5725
+ });
5726
+ const schedulerNowDot = avatar.tv({
5727
+ base: "-ml-[4px] size-[8px] shrink-0 rounded-radius-full bg-bg-brand"
5728
+ });
5729
+ const schedulerNowStroke = avatar.tv({
5730
+ base: "h-px flex-1 bg-bg-brand"
5731
+ });
5732
+ const schedulerNowLabel = avatar.tv({
5733
+ base: [
5734
+ "pointer-events-none absolute right-pad-md z-[2] -translate-y-1/2",
5735
+ "rounded-radius-xs bg-bg-brand px-pad-sm",
5736
+ "text-caption-xs font-semibold tabular-nums text-fg-on-brand"
5737
+ ]
5738
+ });
5739
+ const schedulerListFrame = avatar.tv({
5740
+ base: [
5741
+ "flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain scrollbar-thin",
5742
+ "rounded-radius-xl border border-border-default bg-bg-surface"
5743
+ ]
5744
+ });
5745
+ const schedulerListDay = avatar.tv({
5746
+ base: "flex flex-col gap-gp-md border-b border-border-subtle p-sp-xl last:border-b-0"
5747
+ });
5748
+ const schedulerListDayHead = avatar.tv({
5749
+ base: [
5750
+ "sticky top-0 z-[1] -mx-sp-xl -mt-sp-xl px-sp-xl pb-pad-md pt-sp-xl",
5751
+ "flex items-baseline gap-gp-md bg-bg-surface"
5752
+ ]
5753
+ });
5754
+ const schedulerListDayNumber = avatar.tv({
5755
+ base: "text-title-sm font-semibold tabular-nums",
5756
+ variants: {
5757
+ today: { true: "text-fg-brand", false: "text-fg-default" }
5758
+ },
5759
+ defaultVariants: { today: false }
5760
+ });
5761
+ const schedulerListDayName = avatar.tv({
5762
+ base: "text-body-sm text-fg-muted first-letter:uppercase"
5763
+ });
5764
+ const schedulerListEvents = avatar.tv({
5765
+ base: "flex flex-col gap-gp-sm"
5766
+ });
5767
+ const schedulerEvent = avatar.tv({
5768
+ base: [
5769
+ "group/event relative flex min-w-0 cursor-pointer items-center gap-gp-sm",
5770
+ "border text-left outline-none",
5771
+ "transition-[background-color,border-color,box-shadow] duration-150",
5772
+ "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand"
5773
+ ],
5774
+ variants: {
5775
+ color: {
5776
+ brand: "border-border-brand-subtle bg-bg-brand-subtle hover:bg-bg-brand-subtle-hover",
5777
+ info: "border-border-info-muted bg-bg-info-muted hover:bg-bg-info-muted-hover",
5778
+ success: "border-border-success-muted bg-bg-success-muted hover:bg-bg-success-muted-hover",
5779
+ warning: "border-border-warning-muted bg-bg-warning-muted hover:bg-bg-warning-muted-hover",
5780
+ danger: "border-border-danger-muted bg-bg-danger-muted hover:bg-bg-danger-muted-hover",
5781
+ neutral: "border-border-default bg-bg-muted hover:bg-bg-muted-hover"
5782
+ },
5783
+ variant: {
5784
+ /** Mês: linha de 20px, uma só, sem quebra. */
5785
+ pill: "min-h-comp-2xs rounded-radius-sm px-pad-md text-caption-sm",
5786
+ /** Week/day: bloco absoluto que ocupa a duração. */
5787
+ block: "absolute min-h-comp-3xs flex-col items-start gap-0 rounded-radius-md px-pad-md py-pad-sm text-caption-sm",
5788
+ /** List/agenda: linha larga com descrição. */
5789
+ row: "min-h-form-lg w-full rounded-radius-lg px-pad-xl py-pad-md text-body-sm"
5790
+ },
5791
+ /** Continuação de evento multi-dia: perde o canto do lado truncado. */
5792
+ truncateStart: { true: "rounded-l-none border-l-0", false: "" },
5793
+ truncateEnd: { true: "rounded-r-none border-r-0", false: "" },
5794
+ /**
5795
+ * Enquanto arrasta: sombra alta pra o bloco "sair da superfície", z acima
5796
+ * dos vizinhos, e `cursor-grabbing`.
5797
+ *
5798
+ * ⚠️ **Sem `transition` neste estado.** O `transform` do dnd-kit é atualizado
5799
+ * a cada movimento do ponteiro; uma transição em cima dele faz o bloco
5800
+ * perseguir o cursor com atraso — o gesto fica elástico e impreciso. A
5801
+ * transição vive no estado NÃO-arrastando (`movable`), que é onde ela serve:
5802
+ * o assentamento depois do soltar.
5803
+ */
5804
+ dragging: {
5805
+ true: "z-[3] cursor-grabbing shadow-sh-lg opacity-90 transition-none",
5806
+ false: ""
5807
+ },
5808
+ /** Arrastável e em repouso: mão aberta + transição de assentamento. */
5809
+ movable: {
5810
+ true: "cursor-grab transition-[top,left,height,width,background-color,border-color,box-shadow] duration-200 ease-out",
5811
+ false: ""
5812
+ },
5813
+ disabled: { true: "pointer-events-none opacity-50", false: "" }
5814
+ },
5815
+ defaultVariants: {
5816
+ color: "brand",
5817
+ variant: "pill",
5818
+ truncateStart: false,
5819
+ truncateEnd: false,
5820
+ dragging: false,
5821
+ movable: false,
5822
+ disabled: false
5823
+ }
5824
+ });
5825
+ const schedulerResizeHandle = avatar.tv({
5826
+ base: [
5827
+ "absolute inset-x-0 z-[1] h-[6px] cursor-ns-resize",
5828
+ "opacity-0 transition-opacity duration-150",
5829
+ "group-hover/event:opacity-100 focus-visible:opacity-100",
5830
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring-brand",
5831
+ // A barrinha só aparece no hover; o alvo de 6px existe sempre.
5832
+ "after:absolute after:inset-x-[25%] after:top-[2px] after:h-[2px]",
5833
+ "after:rounded-radius-full after:bg-fg-default/40"
5834
+ ],
5835
+ variants: {
5836
+ edge: {
5837
+ start: "-top-[3px]",
5838
+ end: "-bottom-[3px]"
5839
+ }
5840
+ },
5841
+ defaultVariants: { edge: "end" }
5842
+ });
5843
+ const schedulerDropTarget = avatar.tv({
5844
+ base: "bg-bg-brand-subtle shadow-[inset_0_0_0_2px_var(--color-border-brand)]"
5845
+ });
5846
+ const schedulerEventDot = avatar.tv({
5847
+ base: "shrink-0 rounded-radius-full",
5848
+ variants: {
5849
+ color: {
5850
+ brand: "bg-bg-brand",
5851
+ info: "bg-bg-info",
5852
+ success: "bg-bg-success",
5853
+ warning: "bg-bg-warning",
5854
+ danger: "bg-bg-danger",
5855
+ neutral: "bg-fg-subtle"
5856
+ },
5857
+ size: {
5858
+ sm: "size-icon-2xs",
5859
+ md: "size-icon-xs"
5860
+ }
5861
+ },
5862
+ defaultVariants: { color: "brand", size: "sm" }
5863
+ });
5864
+ const schedulerEventTitle = avatar.tv({
5865
+ base: "min-w-0 flex-1 truncate font-medium text-fg-default"
5866
+ });
5867
+ const schedulerEventTime = avatar.tv({
5868
+ base: "shrink-0 tabular-nums text-fg-muted"
5869
+ });
5870
+ const schedulerEventDescription = avatar.tv({
5871
+ base: "min-w-0 truncate text-caption-sm text-fg-muted"
5872
+ });
5873
+ const schedulerPlaceholder = avatar.tv({
5874
+ base: [
5875
+ "flex min-h-[320px] flex-1 flex-col items-center justify-center gap-gp-md",
5876
+ "rounded-radius-xl border border-dashed border-border-input bg-bg-subtle dark:bg-bg-canvas",
5877
+ "p-sp-3xl text-center"
5878
+ ]
5879
+ });
5880
+ const VIEW_ITEMS = [
5881
+ { value: "month", label: "Mês", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, {}) },
5882
+ { value: "week", label: "Semana", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Columns3, {}) },
5883
+ { value: "day", label: "Dia", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, {}) },
5884
+ { value: "list", label: "Lista", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, {}) }
5885
+ ];
5886
+ function SchedulerToolbar({
5887
+ title: title2,
5888
+ view,
5889
+ onViewChange,
5890
+ onPrev,
5891
+ onNext,
5892
+ onToday,
5893
+ searchable,
5894
+ search,
5895
+ onSearchChange,
5896
+ filterFields,
5897
+ filterModel,
5898
+ onClearField,
5899
+ onClearAll,
5900
+ appliedCount,
5901
+ filterPanelOpen,
5902
+ onToggleFilterPanel,
5903
+ toolbarActions,
5904
+ primaryAction
5905
+ }) {
5906
+ const hasFilters = (filterFields?.length ?? 0) > 0;
5907
+ const activeView = VIEW_ITEMS.find((v) => v.value === view) ?? VIEW_ITEMS[0];
5908
+ const filterEngaged = filterPanelOpen || appliedCount > 0;
5909
+ const appliedFields = (filterFields ?? []).filter(
5910
+ (field) => (filterModel[field.id] ?? []).length > 0
5911
+ );
5912
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-gp-xl", children: [
5913
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerToolbar(), children: [
5914
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerToolbarSide({ slot: "leading" }), children: [
5915
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerTitle(), children: title2 }),
5916
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": "Navegação de período", className: schedulerNavGroup(), children: [
5917
+ /* @__PURE__ */ jsxRuntime.jsx(
5918
+ avatar.Button,
5919
+ {
5920
+ variant: "outline",
5921
+ color: "secondary",
5922
+ size: "icon-sm",
5923
+ onClick: onPrev,
5924
+ "aria-label": "Período anterior",
5925
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, {})
5926
+ }
5927
+ ),
5928
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.Button, { variant: "outline", color: "secondary", size: "sm", onClick: onToday, children: "Hoje" }),
5929
+ /* @__PURE__ */ jsxRuntime.jsx(
5930
+ avatar.Button,
5931
+ {
5932
+ variant: "outline",
5933
+ color: "secondary",
5934
+ size: "icon-sm",
5935
+ onClick: onNext,
5936
+ "aria-label": "Próximo período",
5937
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, {})
5938
+ }
5939
+ )
5940
+ ] })
5941
+ ] }),
5942
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerToolbarSide({ slot: "trailing" }), children: [
5943
+ searchable ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: schedulerSearch({ expanded: search !== "" }), children: [
5944
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { "aria-hidden": "true" }),
5945
+ /* @__PURE__ */ jsxRuntime.jsx(
5946
+ "input",
5947
+ {
5948
+ type: "search",
5949
+ value: search,
5950
+ onChange: (e) => onSearchChange(e.target.value),
5951
+ placeholder: "Buscar evento…",
5952
+ "aria-label": "Buscar evento",
5953
+ className: schedulerSearchInput()
5954
+ }
5955
+ )
5956
+ ] }) : null,
5957
+ toolbarActions,
5958
+ /* @__PURE__ */ jsxRuntime.jsxs(avatar.DropdownMenu, { children: [
5959
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5960
+ avatar.Button,
5961
+ {
5962
+ variant: "outline",
5963
+ color: "secondary",
5964
+ size: "sm",
5965
+ iconLeft: activeView.icon,
5966
+ iconRight: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "hidden lg:block" }),
5967
+ "aria-label": `Visualização: ${activeView.label}`,
5968
+ className: "shrink-0 max-lg:size-form-xl max-lg:p-0",
5969
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden lg:inline", children: activeView.label })
5970
+ }
5971
+ ) }),
5972
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.DropdownMenuContent, { align: "end", className: "min-w-[168px]", children: /* @__PURE__ */ jsxRuntime.jsx(
5973
+ avatar.DropdownMenuRadioGroup,
5974
+ {
5975
+ value: view,
5976
+ 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))
5978
+ }
5979
+ ) })
5980
+ ] }),
5981
+ hasFilters ? (
5982
+ /* `relative` no wrapper e não no Button: o ponto é posicionado
5983
+ contra a caixa do botão, e o Button não expõe `position`. */
5984
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative shrink-0", children: [
5985
+ /* @__PURE__ */ jsxRuntime.jsx(
5986
+ avatar.Button,
5987
+ {
5988
+ variant: filterEngaged ? "soft" : "outline",
5989
+ color: filterEngaged ? "primary" : "secondary",
5990
+ size: "sm",
5991
+ iconLeft: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListFilter, {}),
5992
+ onClick: onToggleFilterPanel,
5993
+ "aria-expanded": filterPanelOpen,
5994
+ className: avatar.cn(
5995
+ "max-lg:size-form-xl max-lg:p-0",
5996
+ filterEngaged && "border-border-brand hover:border-border-brand"
5997
+ ),
5998
+ "aria-label": "Filtro",
5999
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden lg:inline", children: "Filtro" })
6000
+ }
6001
+ ),
6002
+ appliedCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerFilterDot(), "aria-hidden": "true" }) : null
6003
+ ] })
6004
+ ) : null,
6005
+ primaryAction
6006
+ ] })
6007
+ ] }),
6008
+ appliedFields.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerFilterRow(), children: [
6009
+ appliedFields.map((field) => {
6010
+ const selected = filterModel[field.id] ?? [];
6011
+ const selectedLabels = field.options.filter((o) => selected.includes(o.value)).map((o) => o.label);
6012
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: schedulerFilterChip({ applied: true }), children: [
6013
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerFilterChipName(), children: field.label }),
6014
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerFilterChipValue(), children: selectedLabels.length === 1 ? selectedLabels[0] : `${selectedLabels.length} selecionados` }),
6015
+ /* @__PURE__ */ jsxRuntime.jsx(
6016
+ "button",
6017
+ {
6018
+ type: "button",
6019
+ "aria-label": `Limpar filtro ${field.label}`,
6020
+ onClick: () => onClearField(field.id),
6021
+ className: "grid size-icon-sm shrink-0 place-items-center rounded-radius-full text-fg-subtle transition-colors duration-150 hover:text-fg-default focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-brand",
6022
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": "true" })
6023
+ }
6024
+ )
6025
+ ] }, field.id);
6026
+ }),
6027
+ /* @__PURE__ */ jsxRuntime.jsx(
6028
+ "button",
6029
+ {
6030
+ type: "button",
6031
+ onClick: onClearAll,
6032
+ className: avatar.cn(schedulerClearLink(), "shrink-0"),
6033
+ children: "Limpar tudo"
6034
+ }
6035
+ )
6036
+ ] }) : null
6037
+ ] });
6038
+ }
6039
+ function buildMonthMatrix(date, weekStartsOn, locale) {
6040
+ const monthStart = dateFns.startOfMonth(date);
6041
+ const gridStart = dateFns.startOfWeek(monthStart, { weekStartsOn, locale });
6042
+ const days = [];
6043
+ for (let i = 0; i < 42; i++) {
6044
+ days.push(dateFns.startOfDay(dateFns.addDays(gridStart, i)));
6045
+ }
6046
+ const weeks = [];
6047
+ for (let row = 0; row < 6; row++) {
6048
+ weeks.push(days.slice(row * 7, row * 7 + 7));
6049
+ }
6050
+ return weeks;
6051
+ }
6052
+ function segmentMultiDay(events, weekRows) {
6053
+ const segments = [];
6054
+ for (const event of events) {
6055
+ const dayStart = dateFns.startOfDay(event.start);
6056
+ const dayEnd = dateFns.startOfDay(event.end);
6057
+ weekRows.forEach((row, weekIndex) => {
6058
+ if (row.length === 0) return;
6059
+ const rowStart = row[0];
6060
+ const rowEnd = row[row.length - 1];
6061
+ if (dayEnd.getTime() < rowStart.getTime() || dayStart.getTime() > rowEnd.getTime()) {
6062
+ return;
6063
+ }
6064
+ const segStart = dayStart.getTime() > rowStart.getTime() ? dayStart : rowStart;
6065
+ const segEnd = dayEnd.getTime() < rowEnd.getTime() ? dayEnd : rowEnd;
6066
+ const colStart = dateFns.differenceInCalendarDays(segStart, rowStart);
6067
+ const colSpan = dateFns.differenceInCalendarDays(segEnd, segStart) + 1;
6068
+ segments.push({
6069
+ event,
6070
+ weekIndex,
6071
+ colStart,
6072
+ colSpan,
6073
+ isStart: segStart.getTime() === dayStart.getTime(),
6074
+ isEnd: segEnd.getTime() === dayEnd.getTime()
6075
+ });
6076
+ });
6077
+ }
6078
+ return segments;
6079
+ }
6080
+ function effectiveEndMs(event, snapMinutes) {
6081
+ return event.start.getTime() === event.end.getTime() ? event.end.getTime() + snapMinutes * 6e4 : event.end.getTime();
6082
+ }
6083
+ function packLanes(events, snapMinutes) {
6084
+ if (events.length === 0) return [];
6085
+ const sorted = [...events].sort((a, b) => {
6086
+ const startDiff = a.start.getTime() - b.start.getTime();
6087
+ if (startDiff !== 0) return startDiff;
6088
+ return effectiveEndMs(b, snapMinutes) - effectiveEndMs(a, snapMinutes);
6089
+ });
6090
+ const result = [];
6091
+ let group = [];
6092
+ let groupMaxEnd = -Infinity;
6093
+ const flushGroup = () => {
6094
+ if (group.length === 0) return;
6095
+ const laneEnds = [];
6096
+ const laneOf = /* @__PURE__ */ new Map();
6097
+ for (const ev of group) {
6098
+ const evEnd = effectiveEndMs(ev, snapMinutes);
6099
+ const freeLane = laneEnds.findIndex((end) => end <= ev.start.getTime());
6100
+ if (freeLane === -1) {
6101
+ laneOf.set(ev, laneEnds.length);
6102
+ laneEnds.push(evEnd);
6103
+ } else {
6104
+ laneOf.set(ev, freeLane);
6105
+ laneEnds[freeLane] = evEnd;
6106
+ }
6107
+ }
6108
+ const laneCount = laneEnds.length;
6109
+ for (const ev of group) {
6110
+ result.push({ event: ev, laneIndex: laneOf.get(ev), laneCount });
6111
+ }
6112
+ group = [];
6113
+ groupMaxEnd = -Infinity;
6114
+ };
6115
+ for (const ev of sorted) {
6116
+ const evStart = ev.start.getTime();
6117
+ if (group.length > 0 && evStart >= groupMaxEnd) {
6118
+ flushGroup();
6119
+ }
6120
+ group.push(ev);
6121
+ groupMaxEnd = Math.max(groupMaxEnd, effectiveEndMs(ev, snapMinutes));
6122
+ }
6123
+ flushGroup();
6124
+ return result;
6125
+ }
6126
+ const MONTH_PILL_HEIGHT_PX = 20;
6127
+ const MONTH_PILL_GAP_PX = 2;
6128
+ function computeOverflow(dayEvents, availableHeight) {
6129
+ if (dayEvents.length === 0) {
6130
+ return { visible: [], overflowCount: 0 };
6131
+ }
6132
+ const rawFit = Math.max(
6133
+ 0,
6134
+ Math.floor(
6135
+ (availableHeight + MONTH_PILL_GAP_PX) / (MONTH_PILL_HEIGHT_PX + MONTH_PILL_GAP_PX)
6136
+ )
6137
+ );
6138
+ if (rawFit >= dayEvents.length) {
6139
+ return { visible: dayEvents, overflowCount: 0 };
6140
+ }
6141
+ const visibleCount = Math.max(0, rawFit - 1);
6142
+ return {
6143
+ visible: dayEvents.slice(0, visibleCount),
6144
+ overflowCount: dayEvents.length - visibleCount
6145
+ };
6146
+ }
6147
+ function snapToGrid(date, snapMinutes, dayRange) {
6148
+ const dayStart = dateFns.startOfDay(date);
6149
+ const minutesSinceMidnight = (date.getTime() - dayStart.getTime()) / 6e4;
6150
+ const snappedMinutes = Math.round(minutesSinceMidnight / snapMinutes) * snapMinutes;
6151
+ const [rangeStartHour, rangeEndHour] = dayRange;
6152
+ const rangeStartMin = rangeStartHour * 60;
6153
+ const rangeEndMin = Math.min(rangeEndHour * 60, 24 * 60);
6154
+ const clampedMinutes = Math.min(
6155
+ Math.max(snappedMinutes, rangeStartMin),
6156
+ rangeEndMin
6157
+ );
6158
+ const snapped = dateFns.addMinutes(dayStart, clampedMinutes);
6159
+ const dayEnd = dateFns.endOfDay(date);
6160
+ return snapped.getTime() > dayEnd.getTime() ? dayEnd : snapped;
6161
+ }
6162
+ function resolveResize(event, edge, deltaMin, snapMinutes) {
6163
+ const snappedDelta = Math.round(deltaMin / snapMinutes) * snapMinutes;
6164
+ if (edge === "start") {
6165
+ const proposedStart = dateFns.addMinutes(event.start, snappedDelta);
6166
+ const maxStart = dateFns.addMinutes(event.end, -snapMinutes);
6167
+ const start = proposedStart.getTime() > maxStart.getTime() ? maxStart : proposedStart;
6168
+ return { start, end: event.end };
6169
+ }
6170
+ const proposedEnd = dateFns.addMinutes(event.end, snappedDelta);
6171
+ const minEnd = dateFns.addMinutes(event.start, snapMinutes);
6172
+ const end = proposedEnd.getTime() < minEnd.getTime() ? minEnd : proposedEnd;
6173
+ return { start: event.start, end };
6174
+ }
6175
+ function pixelsToMinutes(px, hourHeight) {
6176
+ return px / hourHeight * 60;
6177
+ }
6178
+ function resolveMonthDrop(event, targetDay) {
6179
+ const diasDeslocados = dateFns.differenceInCalendarDays(
6180
+ dateFns.startOfDay(targetDay),
6181
+ dateFns.startOfDay(event.start)
6182
+ );
6183
+ if (diasDeslocados === 0) {
6184
+ return { start: event.start, end: event.end };
6185
+ }
6186
+ return {
6187
+ start: dateFns.addDays(event.start, diasDeslocados),
6188
+ end: dateFns.addDays(event.end, diasDeslocados)
6189
+ };
6190
+ }
6191
+ function resolveTimeGridMove(event, dayDelta, minuteDelta, snapMinutes, dayRange) {
6192
+ const duracaoMs = event.end.getTime() - event.start.getTime();
6193
+ const bruto = dateFns.addMinutes(dateFns.addDays(event.start, dayDelta), minuteDelta);
6194
+ const start = snapToGrid(bruto, snapMinutes, dayRange);
6195
+ return { start, end: new Date(start.getTime() + duracaoMs) };
6196
+ }
6197
+ function minutesToOffset(date, dayRange, hourHeight) {
6198
+ const gridStart = dateFns.addHours(dateFns.startOfDay(date), dayRange[0]);
6199
+ const elapsedMinutes = (date.getTime() - gridStart.getTime()) / 6e4;
6200
+ return elapsedMinutes / 60 * hourHeight;
6201
+ }
6202
+ function MiniMonth({
6203
+ selected,
6204
+ now,
6205
+ locale,
6206
+ weekStartsOn,
6207
+ events,
6208
+ onSelect
6209
+ }) {
6210
+ const [offset, setOffset] = React.useState(0);
6211
+ const cursor = React.useMemo(
6212
+ () => dateFns.addMonths(dateFns.startOfDay(selected), offset),
6213
+ [selected, offset]
6214
+ );
6215
+ const weeks = React.useMemo(
6216
+ () => buildMonthMatrix(cursor, weekStartsOn, locale),
6217
+ [cursor, weekStartsOn, locale]
6218
+ );
6219
+ const daysWithEvents = React.useMemo(() => {
6220
+ const set = /* @__PURE__ */ new Set();
6221
+ for (const ev of events) {
6222
+ let cur = dateFns.startOfDay(ev.start);
6223
+ const last = dateFns.startOfDay(ev.end);
6224
+ let guard = 0;
6225
+ while (cur.getTime() <= last.getTime() && guard < 400) {
6226
+ set.add(cur.getTime());
6227
+ cur = new Date(cur.getFullYear(), cur.getMonth(), cur.getDate() + 1);
6228
+ guard++;
6229
+ }
6230
+ }
6231
+ return set;
6232
+ }, [events]);
6233
+ const weekdayLabels = React.useMemo(
6234
+ () => weeks[0].map((day) => dateFns.format(day, "EEEEE", { locale })),
6235
+ [weeks, locale]
6236
+ );
6237
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-gp-md", children: [
6238
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerMiniHead(), children: [
6239
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerMiniTitle(), children: dateFns.format(cursor, "MMMM yyyy", { locale }) }),
6240
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center", children: [
6241
+ /* @__PURE__ */ jsxRuntime.jsx(
6242
+ avatar.Button,
6243
+ {
6244
+ variant: "ghost",
6245
+ color: "secondary",
6246
+ size: "icon-2xs",
6247
+ "aria-label": "Mês anterior",
6248
+ onClick: () => setOffset((o) => o - 1),
6249
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, {})
6250
+ }
6251
+ ),
6252
+ /* @__PURE__ */ jsxRuntime.jsx(
6253
+ avatar.Button,
6254
+ {
6255
+ variant: "ghost",
6256
+ color: "secondary",
6257
+ size: "icon-2xs",
6258
+ "aria-label": "Próximo mês",
6259
+ onClick: () => setOffset((o) => o + 1),
6260
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, {})
6261
+ }
6262
+ )
6263
+ ] })
6264
+ ] }),
6265
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerMiniGrid(), children: [
6266
+ weekdayLabels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerMiniWeekday(), children: label }, `${label}-${i}`)),
6267
+ weeks.flat().map((day) => /* @__PURE__ */ jsxRuntime.jsx(
6268
+ "button",
6269
+ {
6270
+ type: "button",
6271
+ onClick: () => {
6272
+ onSelect(day);
6273
+ setOffset(0);
6274
+ },
6275
+ "aria-label": dateFns.format(day, "PPPP", { locale }),
6276
+ "aria-current": dateFns.isSameDay(day, selected) ? "date" : void 0,
6277
+ className: schedulerMiniDay({
6278
+ outside: !dateFns.isSameMonth(day, cursor),
6279
+ hasEvents: daysWithEvents.has(day.getTime()),
6280
+ today: dateFns.isSameDay(day, now),
6281
+ selected: dateFns.isSameDay(day, selected)
6282
+ }),
6283
+ children: dateFns.format(day, "d", { locale })
6284
+ },
6285
+ day.getTime()
6286
+ ))
6287
+ ] })
6288
+ ] });
6289
+ }
6290
+ function SchedulerFilterPanel({
6291
+ filterFields,
6292
+ filterModel,
6293
+ onToggleValue,
6294
+ onClearAll,
6295
+ appliedCount,
6296
+ onClose,
6297
+ embedded = false,
6298
+ counts,
6299
+ date,
6300
+ now,
6301
+ locale,
6302
+ weekStartsOn,
6303
+ events,
6304
+ onDateSelect
6305
+ }) {
6306
+ const [collapsed, setCollapsed] = React.useState(() => /* @__PURE__ */ new Set());
6307
+ const toggleGroup2 = (id) => setCollapsed((prev) => {
6308
+ const next = new Set(prev);
6309
+ if (next.has(id)) next.delete(id);
6310
+ else next.add(id);
6311
+ return next;
6312
+ });
6313
+ return /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: schedulerFilterAside({ embedded }), "aria-label": "Filtros do calendário", children: [
6314
+ embedded ? null : /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerAsideSection({ compact: true, sticky: true }), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerAsideHead(), children: [
6315
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerAsideTitle(), children: "Filtros" }),
6316
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-gp-sm", children: [
6317
+ appliedCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onClearAll, className: schedulerClearLink(), children: "Limpar" }) : null,
6318
+ /* @__PURE__ */ jsxRuntime.jsx(
6319
+ avatar.Button,
6320
+ {
6321
+ variant: "ghost",
6322
+ color: "secondary",
6323
+ size: "icon-2xs",
6324
+ "aria-label": "Fechar painel de filtros",
6325
+ onClick: onClose,
6326
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, {})
6327
+ }
6328
+ )
6329
+ ] })
6330
+ ] }) }),
6331
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerAsideSection(), children: /* @__PURE__ */ jsxRuntime.jsx(
6332
+ MiniMonth,
6333
+ {
6334
+ selected: date,
6335
+ now,
6336
+ locale,
6337
+ weekStartsOn,
6338
+ events,
6339
+ onSelect: onDateSelect
6340
+ }
6341
+ ) }),
6342
+ filterFields.map((field) => {
6343
+ const selected = filterModel[field.id] ?? [];
6344
+ const isOpen = !collapsed.has(field.id);
6345
+ const fieldCounts = counts[field.id] ?? {};
6346
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6347
+ "div",
6348
+ {
6349
+ className: avatar.cn(schedulerAsideSection(), schedulerGroup()),
6350
+ children: [
6351
+ /* @__PURE__ */ jsxRuntime.jsxs(
6352
+ "button",
6353
+ {
6354
+ type: "button",
6355
+ "aria-expanded": isOpen,
6356
+ onClick: () => toggleGroup2(field.id),
6357
+ className: schedulerGroupHead(),
6358
+ children: [
6359
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-baseline gap-gp-sm", children: [
6360
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: field.label }),
6361
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerOptionCount(), children: selected.length === 0 ? "Todas" : selected.length })
6362
+ ] }),
6363
+ /* @__PURE__ */ jsxRuntime.jsx(
6364
+ lucideReact.ChevronDown,
6365
+ {
6366
+ className: schedulerGroupChevron({ open: isOpen }),
6367
+ "aria-hidden": "true"
6368
+ }
6369
+ )
6370
+ ]
6371
+ }
6372
+ ),
6373
+ isOpen ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col", children: field.options.map((option) => {
6374
+ const id = `scheduler-filter-${field.id}-${option.value}`;
6375
+ const isOn = selected.includes(option.value);
6376
+ const count = fieldCounts[option.value];
6377
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: schedulerOption(), children: [
6378
+ /* @__PURE__ */ jsxRuntime.jsx(
6379
+ sheet.Checkbox,
6380
+ {
6381
+ id,
6382
+ checked: isOn,
6383
+ onCheckedChange: () => onToggleValue(field.id, option.value),
6384
+ className: schedulerOptionBox({
6385
+ color: option.color ?? "brand"
6386
+ })
6387
+ }
6388
+ ),
6389
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerOptionLabel(), children: option.label }),
6390
+ count !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerOptionCount(), children: count }) : null
6391
+ ] }, option.value);
6392
+ }) }) : null
6393
+ ]
6394
+ },
6395
+ field.id
6396
+ );
6397
+ })
6398
+ ] });
6399
+ }
6400
+ const SchedulerEventItem = React.forwardRef(function SchedulerEventItem2({
6401
+ event,
6402
+ view,
6403
+ variant,
6404
+ truncateStart = false,
6405
+ truncateEnd = false,
6406
+ dragging = false,
6407
+ disabled = false,
6408
+ timeLabel,
6409
+ style,
6410
+ renderContent,
6411
+ onClick,
6412
+ className,
6413
+ dragAttributes,
6414
+ dragListeners,
6415
+ setDragNodeRef,
6416
+ dragTransform,
6417
+ movable = false,
6418
+ resizeHandles
6419
+ }, ref) {
6420
+ const color = event.color ?? "brand";
6421
+ const mergeRefs = (el) => {
6422
+ setDragNodeRef?.(el);
6423
+ if (typeof ref === "function") ref(el);
6424
+ else if (ref) {
6425
+ ref.current = el;
6426
+ }
6427
+ };
6428
+ const estilo = {
6429
+ ...style,
6430
+ ...dragTransform ? { transform: `translate3d(${dragTransform.x}px, ${dragTransform.y}px, 0)` } : null
6431
+ };
6432
+ const ariaLabel = [
6433
+ typeof event.title === "string" ? event.title : event.searchText,
6434
+ timeLabel
6435
+ ].filter(Boolean).join(", ");
6436
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6437
+ "button",
6438
+ {
6439
+ ref: mergeRefs,
6440
+ type: "button",
6441
+ disabled,
6442
+ style: estilo,
6443
+ "aria-label": ariaLabel || void 0,
6444
+ onClick: (evt) => onClick?.(evt),
6445
+ ...dragAttributes,
6446
+ ...dragListeners,
6447
+ className: avatar.cn(
6448
+ schedulerEvent({
6449
+ color,
6450
+ variant,
6451
+ truncateStart,
6452
+ truncateEnd,
6453
+ dragging,
6454
+ // `movable` só pinta em REPOUSO: durante o arraste o `dragging` manda,
6455
+ // e ele desliga a transição de propósito (ver nota no styles).
6456
+ movable: movable && !dragging,
6457
+ disabled
6458
+ }),
6459
+ className
6460
+ ),
6461
+ children: [
6462
+ renderContent ? renderContent() : variant === "row" ? (
6463
+ /* `row` é a única forma com espaço pra 2 linhas — a descrição só
6464
+ aparece aqui. No pill de 20px ela não caberia, e no block ela
6465
+ competiria com o título quando a duração é curta. */
6466
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6467
+ /* @__PURE__ */ jsxRuntime.jsx(
6468
+ "span",
6469
+ {
6470
+ className: schedulerEventDot({ color, size: "md" }),
6471
+ "aria-hidden": "true"
6472
+ }
6473
+ ),
6474
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
6475
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerEventTitle(), children: event.title }),
6476
+ event.description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerEventDescription(), children: event.description }) : null
6477
+ ] }),
6478
+ timeLabel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerEventTime(), children: timeLabel }) : null
6479
+ ] })
6480
+ ) : variant === "block" ? (
6481
+ /* No block o horário vem ANTES do título e em linha própria: o bloco
6482
+ tem altura proporcional à duração, e num evento longo o título
6483
+ sozinho no topo deixaria o horário fora do campo de leitura. */
6484
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6485
+ timeLabel ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: avatar.cn(schedulerEventTime(), "text-caption-xs"), children: timeLabel }) : null,
6486
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: avatar.cn(schedulerEventTitle(), "w-full"), children: event.title })
6487
+ ] })
6488
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6489
+ !truncateStart ? /* @__PURE__ */ jsxRuntime.jsx(
6490
+ "span",
6491
+ {
6492
+ className: schedulerEventDot({ color, size: "sm" }),
6493
+ "aria-hidden": "true"
6494
+ }
6495
+ ) : null,
6496
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerEventTitle(), children: event.title }),
6497
+ timeLabel && view === "month" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerEventTime(), children: timeLabel }) : null
6498
+ ] }),
6499
+ resizeHandles
6500
+ ]
6501
+ }
6502
+ );
6503
+ });
6504
+ const MOVE_PREFIX = "move:";
6505
+ const RESIZE_PREFIX = "resize:";
6506
+ const DAY_PREFIX = "day:";
6507
+ const draggableIdForMove = (eventId) => `${MOVE_PREFIX}${eventId}`;
6508
+ const draggableIdForResize = (eventId, edge) => `${RESIZE_PREFIX}${eventId}:${edge}`;
6509
+ const droppableIdForDay = (day) => `${DAY_PREFIX}${dateFns.startOfDay(day).getTime()}`;
6510
+ function parseDayDroppable(overId) {
6511
+ if (!overId || !overId.startsWith(DAY_PREFIX)) return null;
6512
+ const ms = Number(overId.slice(DAY_PREFIX.length));
6513
+ return Number.isFinite(ms) ? new Date(ms) : null;
6514
+ }
6515
+ function parseDraggable(activeId) {
6516
+ if (activeId.startsWith(MOVE_PREFIX)) {
6517
+ return { kind: "move", eventId: activeId.slice(MOVE_PREFIX.length) };
6518
+ }
6519
+ if (activeId.startsWith(RESIZE_PREFIX)) {
6520
+ const resto = activeId.slice(RESIZE_PREFIX.length);
6521
+ const corte = resto.lastIndexOf(":");
6522
+ if (corte < 0) return null;
6523
+ const edge = resto.slice(corte + 1);
6524
+ if (edge !== "start" && edge !== "end") return null;
6525
+ return { kind: "resize", eventId: resto.slice(0, corte), edge };
6526
+ }
6527
+ return null;
6528
+ }
6529
+ function useSchedulerDnd({
6530
+ enabled,
6531
+ view,
6532
+ events,
6533
+ snapMinutes,
6534
+ dayRange,
6535
+ hourHeight,
6536
+ draggable,
6537
+ resizable,
6538
+ onEventMove,
6539
+ onEventResize
6540
+ }) {
6541
+ const [ativo, setAtivo] = React.useState(null);
6542
+ const [diaSobreposto, setDiaSobreposto] = React.useState(null);
6543
+ const sensors = core.useSensors(
6544
+ core.useSensor(core.PointerSensor, { activationConstraint: { distance: 6 } })
6545
+ );
6546
+ const porId = React.useMemo(() => {
6547
+ const m = /* @__PURE__ */ new Map();
6548
+ for (const e of events) m.set(e.id, e);
6549
+ return m;
6550
+ }, [events]);
6551
+ const podeMover = React.useCallback(
6552
+ (e) => enabled && (e.draggable ?? draggable),
6553
+ [enabled, draggable]
6554
+ );
6555
+ const podeRedimensionar = React.useCallback(
6556
+ (e) => (
6557
+ // Resize só existe onde há eixo de tempo. No mês a altura da pílula não
6558
+ // representa duração, então arrastar a borda não teria o que significar.
6559
+ enabled && view !== "month" && view !== "list" && (e.resizable ?? resizable) && !e.allDay
6560
+ ),
6561
+ [enabled, view, resizable]
6562
+ );
6563
+ const handleDragStart = React.useCallback((evt) => {
6564
+ const parsed = parseDraggable(String(evt.active.id));
6565
+ if (parsed) setAtivo(parsed);
6566
+ }, []);
6567
+ const handleDragOver = React.useCallback((evt) => {
6568
+ const dia = parseDayDroppable(evt.over ? String(evt.over.id) : null);
6569
+ setDiaSobreposto(dia ? dia.getTime() : null);
6570
+ }, []);
6571
+ const handleDragCancel = React.useCallback(() => {
6572
+ setAtivo(null);
6573
+ setDiaSobreposto(null);
6574
+ }, []);
6575
+ const handleDragEnd = React.useCallback(
6576
+ (evt) => {
6577
+ setAtivo(null);
6578
+ setDiaSobreposto(null);
6579
+ const atual = parseDraggable(String(evt.active.id));
6580
+ if (!atual) return;
6581
+ if (!enabled) return;
6582
+ const evento = porId.get(atual.eventId);
6583
+ if (!evento) return;
6584
+ if (atual.kind === "resize") {
6585
+ if (!podeRedimensionar(evento)) return;
6586
+ const minutos = pixelsToMinutes(evt.delta.y, hourHeight);
6587
+ if (Math.abs(minutos) < 1) return;
6588
+ const r = resolveResize(evento, atual.edge, minutos, snapMinutes);
6589
+ if (r.start.getTime() === evento.start.getTime() && r.end.getTime() === evento.end.getTime()) {
6590
+ return;
6591
+ }
6592
+ onEventResize?.({ id: evento.id, start: r.start, end: r.end });
6593
+ return;
6594
+ }
6595
+ if (!podeMover(evento)) return;
6596
+ const diaAlvo = parseDayDroppable(evt.over ? String(evt.over.id) : null);
6597
+ if (view === "month") {
6598
+ if (!diaAlvo) return;
6599
+ const r = resolveMonthDrop(evento, diaAlvo);
6600
+ if (r.start.getTime() === evento.start.getTime()) return;
6601
+ onEventMove?.({ id: evento.id, start: r.start, end: r.end });
6602
+ return;
6603
+ }
6604
+ if (view === "week" || view === "day") {
6605
+ const deslocamentoDeDias = diaAlvo ? dateFns.differenceInCalendarDays(diaAlvo, dateFns.startOfDay(evento.start)) : 0;
6606
+ const minutos = evento.allDay ? 0 : pixelsToMinutes(evt.delta.y, hourHeight);
6607
+ if (deslocamentoDeDias === 0 && Math.abs(minutos) < 1) return;
6608
+ const r = resolveTimeGridMove(
6609
+ evento,
6610
+ deslocamentoDeDias,
6611
+ minutos,
6612
+ snapMinutes,
6613
+ dayRange
6614
+ );
6615
+ if (r.start.getTime() === evento.start.getTime() && r.end.getTime() === evento.end.getTime()) {
6616
+ return;
6617
+ }
6618
+ onEventMove?.({ id: evento.id, start: r.start, end: r.end });
6619
+ }
6620
+ },
6621
+ [
6622
+ enabled,
6623
+ porId,
6624
+ view,
6625
+ hourHeight,
6626
+ snapMinutes,
6627
+ dayRange,
6628
+ podeMover,
6629
+ podeRedimensionar,
6630
+ onEventMove,
6631
+ onEventResize
6632
+ ]
6633
+ );
6634
+ return {
6635
+ sensors,
6636
+ /** `move:<id>` ou `resize:<id>:<edge>` em andamento, pra pintar o estado. */
6637
+ dragAtivo: ativo,
6638
+ /** `getTime()` do dia sob o cursor — a célula/coluna se pinta com isso. */
6639
+ diaSobreposto,
6640
+ podeMover,
6641
+ podeRedimensionar,
6642
+ handleDragStart,
6643
+ handleDragOver,
6644
+ handleDragEnd,
6645
+ handleDragCancel
6646
+ };
6647
+ }
6648
+ function ResizeHandle({
6649
+ eventId,
6650
+ edge,
6651
+ label
6652
+ }) {
6653
+ const { attributes, listeners, setNodeRef } = core.useDraggable({
6654
+ id: draggableIdForResize(eventId, edge)
6655
+ });
6656
+ return (
6657
+ /* `<span role="button">` e não `<button>`: a alça vive DENTRO do botão do
6658
+ evento, e botão dentro de botão é HTML inválido — o navegador quebra a
6659
+ árvore e o clique do evento para de funcionar. */
6660
+ /* @__PURE__ */ jsxRuntime.jsx(
6661
+ "span",
6662
+ {
6663
+ ref: setNodeRef,
6664
+ ...attributes,
6665
+ ...listeners,
6666
+ role: "button",
6667
+ tabIndex: -1,
6668
+ "aria-label": label,
6669
+ className: schedulerResizeHandle({ edge }),
6670
+ onClick: (e) => e.stopPropagation(),
6671
+ onPointerDown: (e) => {
6672
+ e.stopPropagation();
6673
+ listeners?.onPointerDown?.(e);
6674
+ }
6675
+ }
6676
+ )
6677
+ );
6678
+ }
6679
+ function DraggableEvent({
6680
+ movable = false,
6681
+ resizable = false,
6682
+ ...rest
6683
+ }) {
6684
+ const { attributes, listeners, setNodeRef, isDragging } = core.useDraggable({
6685
+ id: draggableIdForMove(rest.event.id),
6686
+ disabled: !movable
6687
+ });
6688
+ return /* @__PURE__ */ jsxRuntime.jsx(
6689
+ SchedulerEventItem,
6690
+ {
6691
+ ...rest,
6692
+ movable,
6693
+ dragging: isDragging,
6694
+ setDragNodeRef: setNodeRef,
6695
+ dragTransform: null,
6696
+ dragAttributes: movable ? { ...attributes } : void 0,
6697
+ dragListeners: movable ? { ...listeners } : void 0,
6698
+ resizeHandles: resizable && !isDragging ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6699
+ /* @__PURE__ */ jsxRuntime.jsx(
6700
+ ResizeHandle,
6701
+ {
6702
+ eventId: rest.event.id,
6703
+ edge: "start",
6704
+ label: "Ajustar início"
6705
+ }
6706
+ ),
6707
+ /* @__PURE__ */ jsxRuntime.jsx(
6708
+ ResizeHandle,
6709
+ {
6710
+ eventId: rest.event.id,
6711
+ edge: "end",
6712
+ label: "Ajustar fim"
6713
+ }
6714
+ )
6715
+ ] }) : null
6716
+ }
6717
+ );
6718
+ }
6719
+ function DroppableDay({
6720
+ day,
6721
+ disabled,
6722
+ className,
6723
+ overClassName,
6724
+ children,
6725
+ cellRef,
6726
+ ...rest
6727
+ }) {
6728
+ const { setNodeRef, isOver } = core.useDroppable({
6729
+ id: droppableIdForDay(day),
6730
+ disabled
6731
+ });
6732
+ return /* @__PURE__ */ jsxRuntime.jsx(
6733
+ "div",
6734
+ {
6735
+ ref: (el) => {
6736
+ setNodeRef(el);
6737
+ cellRef?.(el);
6738
+ },
6739
+ className: avatar.cn(className, isOver && !disabled && overClassName),
6740
+ ...rest,
6741
+ children
6742
+ }
6743
+ );
6744
+ }
6745
+ function useSchedulerKeyboard({
6746
+ count,
6747
+ columns,
6748
+ onActivate,
6749
+ enabled = true
6750
+ }) {
6751
+ const [ativo, setAtivo] = React.useState(0);
6752
+ const [moveuPorTeclado, setMoveuPorTeclado] = React.useState(false);
6753
+ const refs = React.useRef([]);
6754
+ React.useEffect(() => {
6755
+ if (ativo > count - 1) setAtivo(Math.max(0, count - 1));
6756
+ }, [count, ativo]);
6757
+ React.useEffect(() => {
6758
+ if (!moveuPorTeclado) return;
6759
+ refs.current[ativo]?.focus();
6760
+ setMoveuPorTeclado(false);
6761
+ }, [ativo, moveuPorTeclado]);
6762
+ const registrarRef = React.useCallback(
6763
+ (index) => (el) => {
6764
+ refs.current[index] = el;
6765
+ },
6766
+ []
6767
+ );
6768
+ const handleKeyDown = React.useCallback(
6769
+ (index) => (e) => {
6770
+ if (!enabled) return;
6771
+ const irPara = (proximo) => {
6772
+ e.preventDefault();
6773
+ setAtivo(Math.min(count - 1, Math.max(0, proximo)));
6774
+ setMoveuPorTeclado(true);
6775
+ };
6776
+ switch (e.key) {
6777
+ case "ArrowRight":
6778
+ return irPara(index + 1);
6779
+ case "ArrowLeft":
6780
+ return irPara(index - 1);
6781
+ case "ArrowDown":
6782
+ return irPara(index + columns);
6783
+ case "ArrowUp":
6784
+ return irPara(index - columns);
6785
+ case "Home":
6786
+ return irPara(index - index % columns);
6787
+ case "End":
6788
+ return irPara(index - index % columns + (columns - 1));
6789
+ case "PageUp":
6790
+ return irPara(0);
6791
+ case "PageDown":
6792
+ return irPara(count - 1);
6793
+ case "Enter":
6794
+ case " ":
6795
+ e.preventDefault();
6796
+ onActivate?.(index);
6797
+ return;
6798
+ default:
6799
+ return;
6800
+ }
6801
+ },
6802
+ [enabled, count, columns, onActivate]
6803
+ );
6804
+ const getCellProps = React.useCallback(
6805
+ (index) => ({
6806
+ tabIndex: enabled && index === ativo ? 0 : -1,
6807
+ onKeyDown: handleKeyDown(index),
6808
+ onFocus: () => setAtivo(index)
6809
+ }),
6810
+ [enabled, ativo, handleKeyDown]
6811
+ );
6812
+ const getCellRef = React.useCallback(
6813
+ (index) => registrarRef(index),
6814
+ [registrarRef]
6815
+ );
6816
+ return { activeIndex: ativo, getCellProps, getCellRef };
6817
+ }
6818
+ const CELL_CHROME_PX = 24 + 4 + 2;
6819
+ const FALLBACK_ROW_HEIGHT_PX = 96;
6820
+ function SchedulerMonthView({
6821
+ date,
6822
+ events,
6823
+ locale,
6824
+ weekStartsOn,
6825
+ hourFormat,
6826
+ now,
6827
+ maxPerCell,
6828
+ onEventClick,
6829
+ onSlotClick,
6830
+ renderEvent,
6831
+ dndAtivo = false,
6832
+ podeMover
6833
+ }) {
6834
+ const weeks = React.useMemo(
6835
+ () => buildMonthMatrix(date, weekStartsOn, locale),
6836
+ [date, weekStartsOn, locale]
6837
+ );
6838
+ const teclado = useSchedulerKeyboard({
6839
+ count: weeks.length * 7,
6840
+ columns: 7,
6841
+ onActivate: (index) => {
6842
+ const dia = weeks[Math.floor(index / 7)]?.[index % 7];
6843
+ if (dia) onSlotClick?.(dateFns.startOfDay(dia), dateFns.startOfDay(dia));
6844
+ }
6845
+ });
6846
+ const gridRef = React.useRef(null);
6847
+ const [rowHeight, setRowHeight] = React.useState(FALLBACK_ROW_HEIGHT_PX);
6848
+ React.useLayoutEffect(() => {
6849
+ const el = gridRef.current;
6850
+ if (!el) return;
6851
+ const medir = () => {
6852
+ const altura = el.getBoundingClientRect().height;
6853
+ if (altura > 0) setRowHeight(altura / weeks.length);
6854
+ };
6855
+ medir();
6856
+ if (typeof ResizeObserver === "undefined") return;
6857
+ const ro = new ResizeObserver(medir);
6858
+ ro.observe(el);
6859
+ return () => ro.disconnect();
6860
+ }, [weeks.length]);
6861
+ const alturaDisponivel = Math.max(0, rowHeight - CELL_CHROME_PX);
6862
+ const segments = React.useMemo(
6863
+ () => segmentMultiDay(events, weeks),
6864
+ [events, weeks]
6865
+ );
6866
+ const byDay = React.useMemo(() => {
6867
+ const map = /* @__PURE__ */ new Map();
6868
+ for (const seg of segments) {
6869
+ const row = weeks[seg.weekIndex];
6870
+ for (let i = 0; i < seg.colSpan; i++) {
6871
+ const day = row[seg.colStart + i];
6872
+ if (!day) continue;
6873
+ const key = day.getTime();
6874
+ const isFirstOfSegment = i === 0;
6875
+ const isLastOfSegment = i === seg.colSpan - 1;
6876
+ const list = map.get(key) ?? [];
6877
+ list.push({
6878
+ event: seg.event,
6879
+ // A ponta só é "de verdade" quando é a ponta do segmento E a ponta
6880
+ // do evento: um evento que vem da semana anterior chega com
6881
+ // `isStart: false` e nunca ganha canto arredondado à esquerda.
6882
+ truncateStart: !(isFirstOfSegment && seg.isStart),
6883
+ truncateEnd: !(isLastOfSegment && seg.isEnd)
6884
+ });
6885
+ map.set(key, list);
6886
+ }
6887
+ }
6888
+ for (const list of map.values()) {
6889
+ list.sort((a, b) => a.event.start.getTime() - b.event.start.getTime());
6890
+ }
6891
+ return map;
6892
+ }, [segments, weeks]);
6893
+ const weekdayLabels = React.useMemo(
6894
+ () => weeks[0].map((day) => dateFns.format(day, "EEEEEE", { locale })),
6895
+ [weeks, locale]
6896
+ );
6897
+ const timeLabelOf = (event) => {
6898
+ if (event.allDay) return void 0;
6899
+ return dateFns.format(event.start, hourFormat === "12h" ? "h:mm a" : "HH:mm", {
6900
+ locale
6901
+ });
6902
+ };
6903
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerMonthFrame(), children: [
6904
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerWeekdayRow(), children: weekdayLabels.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerWeekdayCell(), children: label }, `${label}-${i}`)) }),
6905
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: gridRef, role: "grid", className: schedulerMonthGrid(), children: weeks.map(
6906
+ (week, weekIndex) => week.map((day) => {
6907
+ const dayEvents = byDay.get(day.getTime()) ?? [];
6908
+ const todos = dayEvents.map((d) => d.event);
6909
+ const { visible, overflowCount } = maxPerCell !== void 0 ? {
6910
+ visible: todos.slice(
6911
+ 0,
6912
+ todos.length <= maxPerCell ? maxPerCell : Math.max(0, maxPerCell - 1)
6913
+ ),
6914
+ overflowCount: todos.length <= maxPerCell ? 0 : todos.length - Math.max(0, maxPerCell - 1)
6915
+ } : computeOverflow(todos, alturaDisponivel);
6916
+ const outside = !dateFns.isSameMonth(day, date);
6917
+ const today = dateFns.isSameDay(day, now);
6918
+ const indiceDaCelula = weekIndex * 7 + week.indexOf(day);
6919
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6920
+ DroppableDay,
6921
+ {
6922
+ day,
6923
+ disabled: !dndAtivo,
6924
+ className: schedulerMonthCell({
6925
+ outside,
6926
+ lastRow: weekIndex === weeks.length - 1,
6927
+ interactive: Boolean(onSlotClick)
6928
+ }),
6929
+ overClassName: schedulerDropTarget(),
6930
+ role: "gridcell",
6931
+ "aria-label": dateFns.format(day, "PPPP", { locale }),
6932
+ ...teclado.getCellProps(indiceDaCelula),
6933
+ cellRef: teclado.getCellRef(indiceDaCelula),
6934
+ children: [
6935
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerDayHead(), children: [
6936
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerDayNumber({ today, outside }), children: dateFns.format(day, "d", { locale }) }),
6937
+ onSlotClick ? /* @__PURE__ */ jsxRuntime.jsx(
6938
+ "button",
6939
+ {
6940
+ type: "button",
6941
+ className: schedulerSlotAdd(),
6942
+ "aria-label": `Criar evento em ${dateFns.format(day, "PPP", { locale })}`,
6943
+ tabIndex: -1,
6944
+ onClick: () => (
6945
+ // Dia inteiro: quem cria decide a hora no formulário.
6946
+ // Emitir 00:00–23:59 seria inventar uma duração que o
6947
+ // usuário não pediu.
6948
+ onSlotClick(dateFns.startOfDay(day), dateFns.startOfDay(day))
6949
+ ),
6950
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { "aria-hidden": "true" })
6951
+ }
6952
+ ) : null
6953
+ ] }),
6954
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerCellEvents(), children: [
6955
+ visible.map((event) => {
6956
+ const meta = dayEvents.find((d) => d.event.id === event.id);
6957
+ return /* @__PURE__ */ jsxRuntime.jsx(
6958
+ DraggableEvent,
6959
+ {
6960
+ event,
6961
+ view: "month",
6962
+ variant: "pill",
6963
+ movable: podeMover?.(event) ?? false,
6964
+ truncateStart: meta?.truncateStart,
6965
+ truncateEnd: meta?.truncateEnd,
6966
+ timeLabel: timeLabelOf(event),
6967
+ onClick: (evt) => onEventClick?.(event, evt),
6968
+ renderContent: renderEvent ? () => renderEvent({
6969
+ event,
6970
+ view: "month",
6971
+ selected: false
6972
+ }) : void 0
6973
+ },
6974
+ `${event.id}-${day.getTime()}`
6975
+ );
6976
+ }),
6977
+ overflowCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(avatar.Popover, { children: [
6978
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: schedulerOverflowButton(), children: [
6979
+ "+",
6980
+ overflowCount,
6981
+ " mais"
6982
+ ] }) }),
6983
+ /* @__PURE__ */ jsxRuntime.jsx(avatar.PopoverContent, { align: "start", className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerOverflowPanel(), children: [
6984
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerOverflowPanelTitle(), children: dateFns.format(day, "EEEE, d 'de' MMMM", { locale }) }),
6985
+ dayEvents.map((d) => /* @__PURE__ */ jsxRuntime.jsx(
6986
+ SchedulerEventItem,
6987
+ {
6988
+ event: d.event,
6989
+ view: "month",
6990
+ variant: "row",
6991
+ timeLabel: timeLabelOf(d.event),
6992
+ onClick: (evt) => onEventClick?.(d.event, evt)
6993
+ },
6994
+ `overflow-${d.event.id}`
6995
+ ))
6996
+ ] }) })
6997
+ ] }) : null
6998
+ ] })
6999
+ ]
7000
+ },
7001
+ day.getTime()
7002
+ );
7003
+ })
7004
+ ) })
7005
+ ] });
7006
+ }
7007
+ const HOUR_HEIGHT_PX = 48;
7008
+ const MIN_BLOCK_PX = 22;
7009
+ function SchedulerTimeGrid({
7010
+ date,
7011
+ view,
7012
+ events,
7013
+ locale,
7014
+ weekStartsOn,
7015
+ hourFormat,
7016
+ dayRange,
7017
+ scrollToHour,
7018
+ nowIndicator,
7019
+ now,
7020
+ snapMinutes,
7021
+ onEventClick,
7022
+ onSlotClick,
7023
+ renderEvent,
7024
+ dndAtivo = false,
7025
+ podeMover,
7026
+ podeRedimensionar
7027
+ }) {
7028
+ const bodyRef = React.useRef(null);
7029
+ const days = React.useMemo(() => {
7030
+ if (view === "day") return [dateFns.startOfDay(date)];
7031
+ const first = dateFns.startOfWeek(date, { weekStartsOn, locale });
7032
+ return Array.from({ length: 7 }, (_, i) => dateFns.startOfDay(dateFns.addDays(first, i)));
7033
+ }, [date, view, weekStartsOn, locale]);
7034
+ const [rangeStart, rangeEnd] = dayRange;
7035
+ const hours = React.useMemo(
7036
+ () => Array.from({ length: Math.max(1, rangeEnd - rangeStart) }, (_, i) => rangeStart + i),
7037
+ [rangeStart, rangeEnd]
7038
+ );
7039
+ const teclado = useSchedulerKeyboard({
7040
+ count: hours.length * days.length,
7041
+ columns: days.length,
7042
+ onActivate: (index) => {
7043
+ const hora = hours[Math.floor(index / days.length)];
7044
+ const dia = days[index % days.length];
7045
+ if (hora === void 0 || !dia) return;
7046
+ const start = new Date(dia);
7047
+ start.setHours(hora, 0, 0, 0);
7048
+ const end = new Date(start);
7049
+ end.setHours(hora + 1, 0, 0, 0);
7050
+ onSlotClick?.(start, end);
7051
+ },
7052
+ enabled: Boolean(onSlotClick)
7053
+ });
7054
+ React.useEffect(() => {
7055
+ const el = bodyRef.current;
7056
+ if (!el) return;
7057
+ const target = (scrollToHour - rangeStart) * HOUR_HEIGHT_PX;
7058
+ el.scrollTop = Math.max(0, target);
7059
+ }, [scrollToHour, rangeStart]);
7060
+ const fmtHour = (hour) => {
7061
+ const d = new Date(2e3, 0, 1, hour % 24, 0);
7062
+ return dateFns.format(d, hourFormat === "12h" ? "h a" : "HH:mm", { locale });
7063
+ };
7064
+ const fmtTime = (d) => dateFns.format(d, hourFormat === "12h" ? "h:mm a" : "HH:mm", { locale });
7065
+ const allDayByDay = React.useMemo(() => {
7066
+ const map = /* @__PURE__ */ new Map();
7067
+ for (const day of days) map.set(day.getTime(), []);
7068
+ for (const ev of events) {
7069
+ if (!ev.allDay) continue;
7070
+ for (const day of days) {
7071
+ if (dateFns.startOfDay(ev.start).getTime() <= day.getTime() && dateFns.startOfDay(ev.end).getTime() >= day.getTime()) {
7072
+ map.get(day.getTime())?.push(ev);
7073
+ }
7074
+ }
7075
+ }
7076
+ return map;
7077
+ }, [events, days]);
7078
+ const hasAllDay = React.useMemo(
7079
+ () => [...allDayByDay.values()].some((list) => list.length > 0),
7080
+ [allDayByDay]
7081
+ );
7082
+ const blocksByDay = React.useMemo(() => {
7083
+ const map = /* @__PURE__ */ new Map();
7084
+ for (const day of days) {
7085
+ const dayStart = dateFns.startOfDay(day);
7086
+ const dayEnd = dateFns.endOfDay(day);
7087
+ const gridTop = new Date(dayStart.getTime() + rangeStart * 36e5);
7088
+ const gridBottom = new Date(dayStart.getTime() + rangeEnd * 36e5);
7089
+ const doDia = events.filter(
7090
+ (ev) => !ev.allDay && ev.start.getTime() < gridBottom.getTime() && ev.end.getTime() > gridTop.getTime() && ev.start.getTime() <= dayEnd.getTime() && ev.end.getTime() >= dayStart.getTime()
7091
+ );
7092
+ const recortados = doDia.map((ev) => ({
7093
+ original: ev,
7094
+ clamped: {
7095
+ ...ev,
7096
+ start: ev.start.getTime() < gridTop.getTime() ? gridTop : ev.start,
7097
+ end: ev.end.getTime() > gridBottom.getTime() ? gridBottom : ev.end
7098
+ }
7099
+ }));
7100
+ const lanes = packLanes(
7101
+ recortados.map((r) => r.clamped),
7102
+ snapMinutes
7103
+ );
7104
+ map.set(
7105
+ day.getTime(),
7106
+ lanes.map((box) => {
7107
+ const original = recortados.find((r) => r.clamped.id === box.event.id)?.original ?? box.event;
7108
+ const top = minutesToOffset(box.event.start, dayRange, HOUR_HEIGHT_PX);
7109
+ const rawHeight = (box.event.end.getTime() - box.event.start.getTime()) / 36e5 * HOUR_HEIGHT_PX;
7110
+ return {
7111
+ event: original,
7112
+ top,
7113
+ height: Math.max(MIN_BLOCK_PX, rawHeight),
7114
+ left: `${box.laneIndex / box.laneCount * 100}%`,
7115
+ width: `${1 / box.laneCount * 100}%`,
7116
+ // O rótulo mostra o horário REAL do evento, não o recortado — quem
7117
+ // vê "22:00" num bloco que começa no topo da grade entende que ele
7118
+ // vem de antes; ver "00:00" seria mentira.
7119
+ label: `${fmtTime(original.start)}`
7120
+ };
7121
+ })
7122
+ );
7123
+ }
7124
+ return map;
7125
+ }, [events, days, dayRange, rangeStart, rangeEnd, snapMinutes, hourFormat, locale]);
7126
+ const nowOffset = React.useMemo(() => {
7127
+ if (!nowIndicator) return null;
7128
+ const visivel = days.some((d) => dateFns.isSameDay(d, now));
7129
+ if (!visivel) return null;
7130
+ const offset = minutesToOffset(now, dayRange, HOUR_HEIGHT_PX);
7131
+ const total = hours.length * HOUR_HEIGHT_PX;
7132
+ if (offset < 0 || offset > total) return null;
7133
+ return offset;
7134
+ }, [nowIndicator, days, now, dayRange, hours.length]);
7135
+ const isWeekend = (d) => d.getDay() === 0 || d.getDay() === 6;
7136
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerTimeFrame(), children: [
7137
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerTimeHead(), children: [
7138
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerTimeGutter() }),
7139
+ days.map((day) => /* @__PURE__ */ jsxRuntime.jsxs(
7140
+ "div",
7141
+ {
7142
+ className: schedulerTimeHeadDay({ weekend: isWeekend(day) }),
7143
+ children: [
7144
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerTimeHeadWeekday(), children: dateFns.format(day, view === "day" ? "EEEE" : "EEEEEE", { locale }) }),
7145
+ /* @__PURE__ */ jsxRuntime.jsx(
7146
+ "span",
7147
+ {
7148
+ className: schedulerDayNumber({ today: dateFns.isSameDay(day, now) }),
7149
+ children: dateFns.format(day, "d", { locale })
7150
+ }
7151
+ )
7152
+ ]
7153
+ },
7154
+ day.getTime()
7155
+ ))
7156
+ ] }),
7157
+ hasAllDay ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerAllDayRow(), children: [
7158
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerAllDayLabel(), children: "Dia inteiro" }),
7159
+ days.map((day) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerAllDayCell(), children: (allDayByDay.get(day.getTime()) ?? []).map((ev) => /* @__PURE__ */ jsxRuntime.jsx(
7160
+ DraggableEvent,
7161
+ {
7162
+ event: ev,
7163
+ view,
7164
+ variant: "pill",
7165
+ movable: podeMover?.(ev) ?? false,
7166
+ truncateStart: dateFns.startOfDay(ev.start).getTime() < day.getTime(),
7167
+ truncateEnd: dateFns.startOfDay(ev.end).getTime() > day.getTime(),
7168
+ onClick: (evt) => onEventClick?.(ev, evt),
7169
+ renderContent: renderEvent ? () => renderEvent({ event: ev, view, selected: false }) : void 0
7170
+ },
7171
+ `${ev.id}-${day.getTime()}`
7172
+ )) }, day.getTime()))
7173
+ ] }) : null,
7174
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: bodyRef, className: schedulerTimeBody(), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerTimeCanvas(), children: [
7175
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${schedulerTimeGutter()} relative`, children: [
7176
+ hours.map((h) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerHourLabel(), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerHourLabelText(), children: fmtHour(h) }) }, h)),
7177
+ nowOffset !== null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerNowLabel(), style: { top: nowOffset }, children: fmtTime(now) }) : null
7178
+ ] }),
7179
+ days.map((day, indiceDaColuna) => /* @__PURE__ */ jsxRuntime.jsxs(
7180
+ DroppableDay,
7181
+ {
7182
+ day,
7183
+ disabled: !dndAtivo,
7184
+ className: schedulerTimeColumn({ weekend: isWeekend(day) }),
7185
+ overClassName: schedulerDropTarget(),
7186
+ children: [
7187
+ hours.map((h, indiceHora) => /* @__PURE__ */ jsxRuntime.jsx(
7188
+ "button",
7189
+ {
7190
+ type: "button",
7191
+ className: schedulerHourSlot(),
7192
+ "aria-label": `Criar evento em ${dateFns.format(day, "PPP", { locale })} às ${fmtHour(h)}`,
7193
+ onClick: () => {
7194
+ const start = new Date(day);
7195
+ start.setHours(h, 0, 0, 0);
7196
+ const end = new Date(start);
7197
+ end.setHours(h + 1, 0, 0, 0);
7198
+ onSlotClick?.(start, end);
7199
+ },
7200
+ disabled: !onSlotClick,
7201
+ ref: teclado.getCellRef(
7202
+ indiceHora * days.length + indiceDaColuna
7203
+ ),
7204
+ ...teclado.getCellProps(
7205
+ indiceHora * days.length + indiceDaColuna
7206
+ )
7207
+ },
7208
+ h
7209
+ )),
7210
+ (blocksByDay.get(day.getTime()) ?? []).map((b) => /* @__PURE__ */ jsxRuntime.jsx(
7211
+ DraggableEvent,
7212
+ {
7213
+ event: b.event,
7214
+ view,
7215
+ variant: "block",
7216
+ movable: podeMover?.(b.event) ?? false,
7217
+ resizable: podeRedimensionar?.(b.event) ?? false,
7218
+ timeLabel: b.label,
7219
+ style: {
7220
+ top: b.top,
7221
+ height: b.height,
7222
+ left: b.left,
7223
+ width: b.width,
7224
+ // 2px de folga à direita pra dois blocos vizinhos não
7225
+ // encostarem; `width` já é a fração da lane.
7226
+ paddingRight: void 0
7227
+ },
7228
+ className: "mr-[2px]",
7229
+ onClick: (evt) => onEventClick?.(b.event, evt),
7230
+ renderContent: renderEvent ? () => renderEvent({ event: b.event, view, selected: false }) : void 0
7231
+ },
7232
+ b.event.id
7233
+ )),
7234
+ nowOffset !== null && dateFns.isSameDay(day, now) ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: schedulerNowLine(), style: { top: nowOffset }, children: [
7235
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerNowDot(), "aria-hidden": "true" }),
7236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerNowStroke(), "aria-hidden": "true" })
7237
+ ] }) : null
7238
+ ]
7239
+ },
7240
+ day.getTime()
7241
+ ))
7242
+ ] }) })
7243
+ ] });
7244
+ }
7245
+ function SchedulerListView({
7246
+ date,
7247
+ events,
7248
+ locale,
7249
+ hourFormat,
7250
+ now,
7251
+ onEventClick,
7252
+ renderEvent,
7253
+ emptyState
7254
+ }) {
7255
+ const grupos = React.useMemo(() => {
7256
+ const inicio = dateFns.startOfMonth(date).getTime();
7257
+ const fim = dateFns.endOfMonth(date).getTime();
7258
+ const map = /* @__PURE__ */ new Map();
7259
+ for (const ev of events) {
7260
+ let cursor = dateFns.startOfDay(ev.start);
7261
+ const ultimo = dateFns.startOfDay(ev.end);
7262
+ let guard = 0;
7263
+ while (cursor.getTime() <= ultimo.getTime() && guard < 400) {
7264
+ const t = cursor.getTime();
7265
+ if (t >= inicio && t <= fim) {
7266
+ const lista = map.get(t) ?? [];
7267
+ lista.push(ev);
7268
+ map.set(t, lista);
7269
+ }
7270
+ cursor = new Date(
7271
+ cursor.getFullYear(),
7272
+ cursor.getMonth(),
7273
+ cursor.getDate() + 1
7274
+ );
7275
+ guard++;
7276
+ }
7277
+ }
7278
+ return [...map.entries()].sort(([a], [b]) => a - b).map(([t, lista]) => ({
7279
+ dia: new Date(t),
7280
+ // Dia inteiro primeiro, depois por horário — é a ordem em que o dia
7281
+ // acontece: o que vale pro dia todo emoldura o resto.
7282
+ eventos: lista.sort((x, y) => {
7283
+ if (!!x.allDay !== !!y.allDay) return x.allDay ? -1 : 1;
7284
+ return x.start.getTime() - y.start.getTime();
7285
+ })
7286
+ }));
7287
+ }, [events, date]);
7288
+ const fmtTime = (d) => dateFns.format(d, hourFormat === "12h" ? "h:mm a" : "HH:mm", { locale });
7289
+ const rotuloHorario = (ev) => {
7290
+ if (ev.allDay) return "Dia inteiro";
7291
+ if (ev.start.getTime() === ev.end.getTime()) return fmtTime(ev.start);
7292
+ return `${fmtTime(ev.start)} – ${fmtTime(ev.end)}`;
7293
+ };
7294
+ if (grupos.length === 0) {
7295
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerPlaceholder(), children: emptyState ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7296
+ /* @__PURE__ */ jsxRuntime.jsx(
7297
+ lucideReact.CalendarSearch,
7298
+ {
7299
+ className: "size-icon-xl text-fg-subtle",
7300
+ "aria-hidden": "true"
7301
+ }
7302
+ ),
7303
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-body-md font-semibold text-fg-default", children: [
7304
+ "Nenhum evento em ",
7305
+ dateFns.format(date, "MMMM 'de' yyyy", { locale })
7306
+ ] }),
7307
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-[42ch] text-body-sm text-fg-muted", children: "Ajuste a busca ou os filtros, ou navegue pra outro mês." })
7308
+ ] }) });
7309
+ }
7310
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerListFrame(), children: grupos.map(({ dia, eventos }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerListDay(), children: [
7311
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerListDayHead(), children: [
7312
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerListDayNumber({ today: dateFns.isSameDay(dia, now) }), children: dateFns.format(dia, "d", { locale }) }),
7313
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: schedulerListDayName(), children: dateFns.format(dia, "EEEE, MMMM", { locale }) })
7314
+ ] }),
7315
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerListEvents(), children: eventos.map((ev) => /* @__PURE__ */ jsxRuntime.jsx(
7316
+ SchedulerEventItem,
7317
+ {
7318
+ event: ev,
7319
+ view: "list",
7320
+ variant: "row",
7321
+ timeLabel: rotuloHorario(ev),
7322
+ onClick: (evt) => onEventClick?.(ev, evt),
7323
+ renderContent: renderEvent ? () => renderEvent({ event: ev, view: "list", selected: false }) : void 0
7324
+ },
7325
+ `${ev.id}-${dia.getTime()}`
7326
+ )) })
7327
+ ] }, dia.getTime())) });
7328
+ }
7329
+ function matchesField(event, fieldId, values) {
7330
+ if (values.length === 0) return true;
7331
+ switch (fieldId) {
7332
+ case "categoryId":
7333
+ return event.categoryId !== void 0 && values.includes(event.categoryId);
7334
+ case "tagIds":
7335
+ return (event.tagIds ?? []).some((tag) => values.includes(tag));
7336
+ case "color":
7337
+ return values.includes(event.color ?? "brand");
7338
+ default:
7339
+ return true;
7340
+ }
7341
+ }
7342
+ function matchesSearch(event, term) {
7343
+ if (term === "") return true;
7344
+ const haystack = [];
7345
+ if (event.searchText) haystack.push(event.searchText);
7346
+ if (typeof event.title === "string") haystack.push(event.title);
7347
+ if (typeof event.description === "string") haystack.push(event.description);
7348
+ if (event.tagIds?.length) haystack.push(...event.tagIds);
7349
+ return haystack.some((piece) => piece.toLowerCase().includes(term));
7350
+ }
7351
+ function useSchedulerFilter({
7352
+ events,
7353
+ searchable,
7354
+ search: searchProp,
7355
+ onSearchChange,
7356
+ filterFields,
7357
+ filterModel: modelProp,
7358
+ onFilterModelChange,
7359
+ filterMode
7360
+ }) {
7361
+ const isSearchControlled = searchProp !== void 0;
7362
+ const isModelControlled = modelProp !== void 0;
7363
+ const [searchState, setSearchState] = React.useState("");
7364
+ const [modelState, setModelState] = React.useState({});
7365
+ const search = isSearchControlled ? searchProp : searchState;
7366
+ const filterModel = isModelControlled ? modelProp : modelState;
7367
+ React.useRef(false);
7368
+ React.useEffect(() => {
7369
+ return;
7370
+ }, [filterFields, filterMode]);
7371
+ const commitSearch = React.useCallback(
7372
+ (next) => {
7373
+ if (!isSearchControlled) setSearchState(next);
7374
+ onSearchChange?.(next);
7375
+ },
7376
+ [isSearchControlled, onSearchChange]
7377
+ );
7378
+ const commitModel = React.useCallback(
7379
+ (next) => {
7380
+ if (!isModelControlled) setModelState(next);
7381
+ onFilterModelChange?.(next);
7382
+ },
7383
+ [isModelControlled, onFilterModelChange]
7384
+ );
7385
+ const toggleValue = React.useCallback(
7386
+ (fieldId, value) => {
7387
+ const current = filterModel[fieldId] ?? [];
7388
+ const nextValues = current.includes(value) ? current.filter((v) => v !== value) : [...current, value];
7389
+ const next = { ...filterModel };
7390
+ if (nextValues.length === 0) {
7391
+ delete next[fieldId];
7392
+ } else {
7393
+ next[fieldId] = nextValues;
7394
+ }
7395
+ commitModel(next);
7396
+ },
7397
+ [commitModel, filterModel]
7398
+ );
7399
+ const clearField = React.useCallback(
7400
+ (fieldId) => {
7401
+ const next = { ...filterModel };
7402
+ delete next[fieldId];
7403
+ commitModel(next);
7404
+ },
7405
+ [commitModel, filterModel]
7406
+ );
7407
+ const clearAll = React.useCallback(() => {
7408
+ commitModel({});
7409
+ commitSearch("");
7410
+ }, [commitModel, commitSearch]);
7411
+ const appliedCount = React.useMemo(
7412
+ () => Object.values(filterModel).reduce(
7413
+ (total, values) => total + values.length,
7414
+ 0
7415
+ ),
7416
+ [filterModel]
7417
+ );
7418
+ const optionCounts = React.useMemo(() => {
7419
+ const term = searchable ? search.trim().toLowerCase() : "";
7420
+ const base = events.filter((e) => matchesSearch(e, term));
7421
+ const out = {};
7422
+ for (const field of filterFields ?? []) {
7423
+ const perValue = {};
7424
+ for (const option of field.options) {
7425
+ perValue[option.value] = base.filter(
7426
+ (e) => matchesField(e, field.id, [option.value])
7427
+ ).length;
7428
+ }
7429
+ out[field.id] = perValue;
7430
+ }
7431
+ return out;
7432
+ }, [events, filterFields, search, searchable]);
7433
+ const filteredEvents = React.useMemo(() => {
7434
+ if (filterMode === "server") return events;
7435
+ const term = searchable ? search.trim().toLowerCase() : "";
7436
+ const activeFields = Object.entries(filterModel).filter(
7437
+ ([, values]) => values.length > 0
7438
+ );
7439
+ if (term === "" && activeFields.length === 0) return events;
7440
+ return events.filter((event) => {
7441
+ if (!matchesSearch(event, term)) return false;
7442
+ return activeFields.every(
7443
+ ([fieldId, values]) => matchesField(event, fieldId, values)
7444
+ );
7445
+ });
7446
+ }, [events, filterMode, filterModel, search, searchable]);
7447
+ return {
7448
+ search,
7449
+ setSearch: commitSearch,
7450
+ filterModel,
7451
+ toggleValue,
7452
+ clearField,
7453
+ clearAll,
7454
+ appliedCount,
7455
+ optionCounts,
7456
+ filteredEvents
7457
+ };
7458
+ }
7459
+ function useSchedulerState({
7460
+ defaultDate,
7461
+ date: dateProp,
7462
+ onDateChange,
7463
+ defaultView,
7464
+ view: viewProp,
7465
+ onViewChange,
7466
+ weekStartsOn,
7467
+ locale,
7468
+ now
7469
+ }) {
7470
+ const isDateControlled = dateProp !== void 0;
7471
+ const isViewControlled = viewProp !== void 0;
7472
+ const [dateState, setDateState] = React.useState(
7473
+ () => defaultDate ?? dateFns.startOfDay(now)
7474
+ );
7475
+ const [viewState, setViewState] = React.useState(
7476
+ () => defaultView ?? "month"
7477
+ );
7478
+ const date = isDateControlled ? dateProp : dateState;
7479
+ const view = isViewControlled ? viewProp : viewState;
7480
+ React.useRef(false);
7481
+ React.useEffect(() => {
7482
+ return;
7483
+ }, [dateProp, defaultDate, viewProp, defaultView, onDateChange, onViewChange]);
7484
+ const commitDate = React.useCallback(
7485
+ (next2) => {
7486
+ if (!isDateControlled) setDateState(next2);
7487
+ onDateChange?.(next2);
7488
+ },
7489
+ [isDateControlled, onDateChange]
7490
+ );
7491
+ const commitView = React.useCallback(
7492
+ (next2) => {
7493
+ if (!isViewControlled) setViewState(next2);
7494
+ onViewChange?.(next2);
7495
+ },
7496
+ [isViewControlled, onViewChange]
7497
+ );
7498
+ const shift = React.useCallback(
7499
+ (direction) => {
7500
+ const step = view === "month" ? dateFns.addMonths(date, direction) : view === "week" ? dateFns.addWeeks(date, direction) : dateFns.addDays(date, direction);
7501
+ commitDate(dateFns.startOfDay(step));
7502
+ },
7503
+ [commitDate, date, view]
7504
+ );
7505
+ const next = React.useCallback(() => shift(1), [shift]);
7506
+ const prev = React.useCallback(() => shift(-1), [shift]);
7507
+ const goToToday = React.useCallback(
7508
+ () => commitDate(dateFns.startOfDay(now)),
7509
+ [commitDate, now]
7510
+ );
7511
+ const goToDate = React.useCallback(
7512
+ (target) => commitDate(dateFns.startOfDay(target)),
7513
+ [commitDate]
7514
+ );
7515
+ const visibleRange = React.useMemo(() => {
7516
+ switch (view) {
7517
+ case "month": {
7518
+ const gridStart = dateFns.startOfWeek(dateFns.startOfMonth(date), {
7519
+ weekStartsOn,
7520
+ locale
7521
+ });
7522
+ return { start: gridStart, end: dateFns.endOfDay(dateFns.addDays(gridStart, 41)) };
7523
+ }
7524
+ case "week":
7525
+ return {
7526
+ start: dateFns.startOfWeek(date, { weekStartsOn, locale }),
7527
+ end: dateFns.endOfWeek(date, { weekStartsOn, locale })
7528
+ };
7529
+ case "day":
7530
+ return { start: dateFns.startOfDay(date), end: dateFns.endOfDay(date) };
7531
+ case "list":
7532
+ default:
7533
+ return { start: dateFns.startOfMonth(date), end: dateFns.endOfMonth(date) };
7534
+ }
7535
+ }, [date, view, weekStartsOn, locale]);
7536
+ return {
7537
+ date,
7538
+ view,
7539
+ setDate: commitDate,
7540
+ setView: commitView,
7541
+ next,
7542
+ prev,
7543
+ goToToday,
7544
+ goToDate,
7545
+ visibleRange
7546
+ };
7547
+ }
7548
+ function useMediaQuery(query) {
7549
+ const [matches, setMatches] = React.useState(() => {
7550
+ if (typeof window === "undefined") return false;
7551
+ return window.matchMedia(query).matches;
7552
+ });
7553
+ React.useEffect(() => {
7554
+ if (typeof window === "undefined") return;
7555
+ const mql = window.matchMedia(query);
7556
+ const handler = (e) => setMatches(e.matches);
7557
+ setMatches(mql.matches);
7558
+ mql.addEventListener("change", handler);
7559
+ return () => mql.removeEventListener("change", handler);
7560
+ }, [query]);
7561
+ return matches;
7562
+ }
7563
+ const Scheduler = React.forwardRef(
7564
+ function Scheduler2({
7565
+ events,
7566
+ locale,
7567
+ weekStartsOn = 0,
7568
+ hourFormat = "24h",
7569
+ nowIndicator = true,
7570
+ dayRange = [0, 24],
7571
+ scrollToHour = 8,
7572
+ snapMinutes = 15,
7573
+ defaultDate,
7574
+ date,
7575
+ onDateChange,
7576
+ defaultView,
7577
+ view,
7578
+ onViewChange,
7579
+ onEventClick,
7580
+ onSlotClick,
7581
+ onEventMove,
7582
+ onEventResize,
7583
+ draggable = false,
7584
+ resizable = false,
7585
+ searchable = true,
7586
+ search,
7587
+ onSearchChange,
7588
+ filterFields,
7589
+ filterModel,
7590
+ onFilterModelChange,
7591
+ filterMode = "client",
7592
+ defaultFilterPanelOpen = false,
7593
+ toolbarActions,
7594
+ primaryAction,
7595
+ title: title2,
7596
+ renderEvent,
7597
+ emptyState,
7598
+ className,
7599
+ ...rest
7600
+ }, ref) {
7601
+ const nowRef = React.useRef(/* @__PURE__ */ new Date());
7602
+ const now = nowRef.current;
7603
+ const state = useSchedulerState({
7604
+ defaultDate,
7605
+ date,
7606
+ onDateChange,
7607
+ defaultView,
7608
+ view,
7609
+ onViewChange,
7610
+ weekStartsOn,
7611
+ locale,
7612
+ now
7613
+ });
7614
+ const filter = useSchedulerFilter({
7615
+ events,
7616
+ searchable,
7617
+ search,
7618
+ onSearchChange,
7619
+ filterFields,
7620
+ filterModel,
7621
+ onFilterModelChange,
7622
+ filterMode
7623
+ });
7624
+ const dndAtivo = Boolean(
7625
+ (draggable || resizable || events.some((e) => e.draggable || e.resizable)) && (onEventMove || onEventResize)
7626
+ );
7627
+ const dnd = useSchedulerDnd({
7628
+ enabled: dndAtivo,
7629
+ view: state.view,
7630
+ events: filter.filteredEvents,
7631
+ snapMinutes,
7632
+ dayRange,
7633
+ hourHeight: HOUR_HEIGHT_PX,
7634
+ draggable,
7635
+ resizable,
7636
+ onEventMove,
7637
+ onEventResize
7638
+ });
7639
+ React.useRef(false);
7640
+ React.useEffect(() => {
7641
+ return;
7642
+ }, [draggable, resizable, onEventMove, onEventResize]);
7643
+ const eventoArrastado = dnd.dragAtivo?.kind === "move" ? filter.filteredEvents.find((e) => e.id === dnd.dragAtivo?.eventId) ?? null : null;
7644
+ const wideLayout = useMediaQuery("(min-width: 1024px)");
7645
+ const [filterPanelOpen, setFilterPanelOpen] = React.useState(defaultFilterPanelOpen);
7646
+ const hasFilterFields = (filterFields?.length ?? 0) > 0;
7647
+ const showFilterPanel = filterPanelOpen && wideLayout && hasFilterFields;
7648
+ const showFilterDrawer = filterPanelOpen && !wideLayout && hasFilterFields;
7649
+ React.useImperativeHandle(
7650
+ ref,
7651
+ () => ({
7652
+ goToDate: state.goToDate,
7653
+ goToToday: state.goToToday,
7654
+ next: state.next,
7655
+ prev: state.prev,
7656
+ getVisibleRange: () => state.visibleRange
7657
+ }),
7658
+ [state]
7659
+ );
7660
+ const periodTitle = React.useMemo(() => {
7661
+ if (title2 !== void 0) return title2;
7662
+ const { start, end } = state.visibleRange;
7663
+ switch (state.view) {
7664
+ case "week": {
7665
+ const sameMonth = start.getMonth() === end.getMonth();
7666
+ return sameMonth ? `${dateFns.format(start, "d", { locale })}–${dateFns.format(end, "d 'de' MMMM yyyy", { locale })}` : `${dateFns.format(start, "d MMM", { locale })} – ${dateFns.format(end, "d MMM yyyy", { locale })}`;
7667
+ }
7668
+ case "day":
7669
+ return dateFns.format(state.date, "EEEE, d 'de' MMMM yyyy", { locale });
7670
+ case "month":
7671
+ case "list":
7672
+ default:
7673
+ return dateFns.format(state.date, "MMMM yyyy", { locale });
7674
+ }
7675
+ }, [title2, state.view, state.date, state.visibleRange, locale]);
7676
+ return (
7677
+ /* `role="region"` + `aria-label` com o período: é o que dá ao leitor de
7678
+ tela um ponto de navegação ("Calendário, setembro 2026") sem que o
7679
+ título do período precise se declarar `<h2>` e entrar no outline da
7680
+ página. O rótulo só é montado quando o título é string — com um
7681
+ `ReactNode` custom não dá pra derivar texto sem varrer a árvore. */
7682
+ /* @__PURE__ */ jsxRuntime.jsx(
7683
+ "div",
7684
+ {
7685
+ role: "region",
7686
+ "aria-label": typeof periodTitle === "string" ? `Calendário, ${periodTitle}` : "Calendário",
7687
+ className: avatar.cn(schedulerRoot(), className),
7688
+ ...rest,
7689
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
7690
+ core.DndContext,
7691
+ {
7692
+ sensors: dnd.sensors,
7693
+ onDragStart: dnd.handleDragStart,
7694
+ onDragOver: dnd.handleDragOver,
7695
+ onDragEnd: dnd.handleDragEnd,
7696
+ onDragCancel: dnd.handleDragCancel,
7697
+ children: [
7698
+ /* @__PURE__ */ jsxRuntime.jsx(
7699
+ SchedulerToolbar,
7700
+ {
7701
+ title: periodTitle,
7702
+ view: state.view,
7703
+ onViewChange: state.setView,
7704
+ onPrev: state.prev,
7705
+ onNext: state.next,
7706
+ onToday: state.goToToday,
7707
+ searchable,
7708
+ search: filter.search,
7709
+ onSearchChange: filter.setSearch,
7710
+ filterFields,
7711
+ filterModel: filter.filterModel,
7712
+ onClearField: filter.clearField,
7713
+ onClearAll: filter.clearAll,
7714
+ appliedCount: filter.appliedCount,
7715
+ filterPanelOpen: showFilterPanel || showFilterDrawer,
7716
+ onToggleFilterPanel: () => setFilterPanelOpen((o) => !o),
7717
+ toolbarActions,
7718
+ primaryAction
7719
+ }
7720
+ ),
7721
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: schedulerBody(), children: [
7722
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: schedulerMain(), children: state.view === "month" ? /* @__PURE__ */ jsxRuntime.jsx(
7723
+ SchedulerMonthView,
7724
+ {
7725
+ date: state.date,
7726
+ events: filter.filteredEvents,
7727
+ locale,
7728
+ weekStartsOn,
7729
+ hourFormat,
7730
+ now,
7731
+ onEventClick,
7732
+ onSlotClick,
7733
+ renderEvent,
7734
+ dndAtivo,
7735
+ podeMover: dnd.podeMover
7736
+ }
7737
+ ) : state.view === "list" ? /* @__PURE__ */ jsxRuntime.jsx(
7738
+ SchedulerListView,
7739
+ {
7740
+ date: state.date,
7741
+ events: filter.filteredEvents,
7742
+ locale,
7743
+ hourFormat,
7744
+ now,
7745
+ onEventClick,
7746
+ renderEvent,
7747
+ emptyState
7748
+ }
7749
+ ) : (
7750
+ /* `week` e `day` são a MESMA view, com 7 ou 1 coluna. */
7751
+ /* @__PURE__ */ jsxRuntime.jsx(
7752
+ SchedulerTimeGrid,
7753
+ {
7754
+ date: state.date,
7755
+ view: state.view,
7756
+ events: filter.filteredEvents,
7757
+ locale,
7758
+ weekStartsOn,
7759
+ hourFormat,
7760
+ dayRange,
7761
+ scrollToHour,
7762
+ nowIndicator,
7763
+ now,
7764
+ snapMinutes,
7765
+ onEventClick,
7766
+ onSlotClick,
7767
+ renderEvent,
7768
+ dndAtivo,
7769
+ podeMover: dnd.podeMover,
7770
+ podeRedimensionar: dnd.podeRedimensionar
7771
+ }
7772
+ )
7773
+ ) }),
7774
+ showFilterPanel && filterFields ? /* @__PURE__ */ jsxRuntime.jsx(
7775
+ SchedulerFilterPanel,
7776
+ {
7777
+ filterFields,
7778
+ filterModel: filter.filterModel,
7779
+ onToggleValue: filter.toggleValue,
7780
+ onClearAll: filter.clearAll,
7781
+ appliedCount: filter.appliedCount,
7782
+ onClose: () => setFilterPanelOpen(false),
7783
+ counts: filter.optionCounts,
7784
+ date: state.date,
7785
+ now,
7786
+ locale,
7787
+ weekStartsOn,
7788
+ events: filter.filteredEvents,
7789
+ onDateSelect: state.goToDate
7790
+ }
7791
+ ) : null
7792
+ ] }),
7793
+ showFilterDrawer && filterFields ? /* @__PURE__ */ jsxRuntime.jsx(
7794
+ panel.FloatingPanel,
7795
+ {
7796
+ open: true,
7797
+ onOpenChange: (aberto) => setFilterPanelOpen(aberto),
7798
+ side: "right",
7799
+ size: "sm",
7800
+ bodyPadded: false,
7801
+ title: "Filtros",
7802
+ titleIcon: lucideReact.ListFilter,
7803
+ description: filter.appliedCount === 1 ? "1 filtro ativo" : `${filter.appliedCount} filtros ativos`,
7804
+ footer: /* @__PURE__ */ jsxRuntime.jsx(
7805
+ avatar.Button,
7806
+ {
7807
+ variant: "ghost",
7808
+ color: "secondary",
7809
+ size: "sm",
7810
+ onClick: filter.clearAll,
7811
+ disabled: filter.appliedCount === 0,
7812
+ children: "Limpar tudo"
7813
+ }
7814
+ ),
7815
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7816
+ SchedulerFilterPanel,
7817
+ {
7818
+ embedded: true,
7819
+ filterFields,
7820
+ filterModel: filter.filterModel,
7821
+ onToggleValue: filter.toggleValue,
7822
+ onClearAll: filter.clearAll,
7823
+ appliedCount: filter.appliedCount,
7824
+ onClose: () => setFilterPanelOpen(false),
7825
+ counts: filter.optionCounts,
7826
+ date: state.date,
7827
+ now,
7828
+ locale,
7829
+ weekStartsOn,
7830
+ events: filter.filteredEvents,
7831
+ onDateSelect: (d) => {
7832
+ state.goToDate(d);
7833
+ setFilterPanelOpen(false);
7834
+ }
7835
+ }
7836
+ )
7837
+ }
7838
+ ) : null,
7839
+ /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { dropAnimation: null, children: eventoArrastado ? /* @__PURE__ */ jsxRuntime.jsx(
7840
+ SchedulerEventItem,
7841
+ {
7842
+ event: eventoArrastado,
7843
+ view: state.view,
7844
+ variant: state.view === "month" ? "pill" : "block",
7845
+ dragging: true,
7846
+ timeLabel: eventoArrastado.allDay ? void 0 : dateFns.format(
7847
+ eventoArrastado.start,
7848
+ hourFormat === "12h" ? "h:mm a" : "HH:mm",
7849
+ { locale }
7850
+ )
7851
+ }
7852
+ ) : null })
7853
+ ]
7854
+ }
7855
+ )
7856
+ }
7857
+ )
7858
+ );
7859
+ }
7860
+ );
5269
7861
  const spinnerStyles = avatar.tv({
5270
7862
  base: ["inline-block shrink-0", "animate-spin motion-reduce:animate-none"],
5271
7863
  variants: {
@@ -8160,7 +10752,7 @@ function useDataList({
8160
10752
  for (const f of filterFields ?? []) m.set(f.id, f);
8161
10753
  return m;
8162
10754
  }, [filterFields]);
8163
- const matchesSearch = React.useCallback(
10755
+ const matchesSearch2 = React.useCallback(
8164
10756
  (item) => {
8165
10757
  const s = query.search.trim().toLowerCase();
8166
10758
  if (!s) return true;
@@ -8176,9 +10768,9 @@ function useDataList({
8176
10768
  );
8177
10769
  const processedItems = React.useMemo(() => {
8178
10770
  if (mode === "server") return items;
8179
- const searched = query.search ? items.filter(matchesSearch) : items;
10771
+ const searched = query.search ? items.filter(matchesSearch2) : items;
8180
10772
  return applyFilterModel(searched, query.filterModel, fieldsById);
8181
- }, [mode, items, query.search, query.filterModel, matchesSearch, fieldsById]);
10773
+ }, [mode, items, query.search, query.filterModel, matchesSearch2, fieldsById]);
8182
10774
  return {
8183
10775
  query,
8184
10776
  activeViewId,
@@ -9090,6 +11682,8 @@ exports.MessageComposer = MessageComposer;
9090
11682
  exports.MessageVariablesPicker = MessageVariablesPicker;
9091
11683
  exports.Modal = Modal;
9092
11684
  exports.MonthYearPicker = MonthYearPicker;
11685
+ exports.Scheduler = Scheduler;
11686
+ exports.SchedulerFilterPanel = SchedulerFilterPanel;
9093
11687
  exports.ScreenLoader = ScreenLoader;
9094
11688
  exports.Spinner = Spinner;
9095
11689
  exports.TabsNavigation = TabsNavigation;