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