@fayz-ai/plugin-agenda 0.8.1 → 0.8.3

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 (117) hide show
  1. package/dist/{chunk-LFUELUIT.cjs → chunk-3KWNHV5A.cjs} +60 -2
  2. package/dist/chunk-3KWNHV5A.cjs.map +1 -0
  3. package/dist/{chunk-DRZF6CV2.js → chunk-YQO6B4G3.js} +60 -3
  4. package/dist/chunk-YQO6B4G3.js.map +1 -0
  5. package/dist/components/AgendaGeneralSettings.d.ts.map +1 -1
  6. package/dist/components/AppointmentModal.d.ts.map +1 -1
  7. package/dist/components/CalendarToolbar.d.ts.map +1 -1
  8. package/dist/data/supabase.d.ts +5 -1
  9. package/dist/data/supabase.d.ts.map +1 -1
  10. package/dist/index.cjs +1071 -766
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +914 -609
  14. package/dist/index.js.map +1 -1
  15. package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts +6 -0
  16. package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts.map +1 -0
  17. package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts +9 -0
  18. package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts.map +1 -0
  19. package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts +12 -0
  20. package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts.map +1 -0
  21. package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts +26 -0
  22. package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts.map +1 -0
  23. package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
  24. package/dist/integrations/google-calendar/data/supabase.d.ts +33 -1
  25. package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
  26. package/dist/integrations/google-calendar/data/tables.d.ts +6 -0
  27. package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -0
  28. package/dist/integrations/google-calendar/index.d.ts +1 -1
  29. package/dist/integrations/google-calendar/index.d.ts.map +1 -1
  30. package/dist/integrations/google-calendar/mapping.d.ts +56 -4
  31. package/dist/integrations/google-calendar/mapping.d.ts.map +1 -1
  32. package/dist/integrations/google-calendar/types.d.ts +101 -0
  33. package/dist/integrations/google-calendar/types.d.ts.map +1 -1
  34. package/dist/locales/en.d.ts.map +1 -1
  35. package/dist/locales/pt-BR.d.ts.map +1 -1
  36. package/dist/migrations/index.d.ts +11 -0
  37. package/dist/migrations/index.d.ts.map +1 -1
  38. package/dist/public/BookingWidget.d.ts.map +1 -1
  39. package/dist/public/context.d.ts +3 -1
  40. package/dist/public/context.d.ts.map +1 -1
  41. package/dist/public/createPublicBookingPlugin.d.ts +7 -1
  42. package/dist/public/createPublicBookingPlugin.d.ts.map +1 -1
  43. package/dist/public/data.d.ts +6 -0
  44. package/dist/public/data.d.ts.map +1 -1
  45. package/dist/public/data.supabase.d.ts.map +1 -1
  46. package/dist/public/format.d.ts +22 -1
  47. package/dist/public/format.d.ts.map +1 -1
  48. package/dist/public/hooks.d.ts.map +1 -1
  49. package/dist/public/index.cjs +31 -63
  50. package/dist/public/index.cjs.map +1 -1
  51. package/dist/public/index.d.ts +1 -1
  52. package/dist/public/index.d.ts.map +1 -1
  53. package/dist/public/index.js +27 -61
  54. package/dist/public/index.js.map +1 -1
  55. package/dist/public/steps/ContactStep.d.ts +4 -2
  56. package/dist/public/steps/ContactStep.d.ts.map +1 -1
  57. package/dist/public/types.d.ts +16 -0
  58. package/dist/public/types.d.ts.map +1 -1
  59. package/dist/views/CalendarView.d.ts.map +1 -1
  60. package/package.json +5 -5
  61. package/src/components/AgendaGeneralSettings.tsx +38 -16
  62. package/src/components/AppointmentModal.tsx +50 -108
  63. package/src/components/CalendarToolbar.tsx +10 -7
  64. package/src/data/supabase.ts +67 -13
  65. package/src/index.ts +56 -8
  66. package/src/integrations/google-calendar/components/CalendarColorDot.tsx +23 -0
  67. package/src/integrations/google-calendar/components/ChannelMappingList.tsx +329 -0
  68. package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +215 -0
  69. package/src/integrations/google-calendar/components/useAgendaLookups.ts +100 -0
  70. package/src/integrations/google-calendar/connectorDef.tsx +123 -40
  71. package/src/integrations/google-calendar/data/supabase.ts +109 -13
  72. package/src/integrations/google-calendar/data/tables.ts +12 -0
  73. package/src/integrations/google-calendar/index.ts +10 -1
  74. package/src/integrations/google-calendar/integration.test.ts +126 -1
  75. package/src/integrations/google-calendar/mapping.ts +149 -9
  76. package/src/integrations/google-calendar/migrations/001_google_calendar.sql +9 -6
  77. package/src/integrations/google-calendar/migrations/002_smart_sync.sql +517 -0
  78. package/src/integrations/google-calendar/migrations/003_plg_rename.sql +273 -0
  79. package/src/integrations/google-calendar/types.ts +111 -0
  80. package/src/locales/en.ts +1 -0
  81. package/src/locales/pt-BR.ts +1 -0
  82. package/src/migrations/003_public_client_kind.sql +175 -0
  83. package/src/migrations/004_public_booking_assignee.sql +176 -0
  84. package/src/migrations/005_agent_rpcs.sql +168 -0
  85. package/src/migrations/006_agent_rpc_localtime.sql +181 -0
  86. package/src/migrations/007_schedule_exception_precedence.sql +290 -0
  87. package/src/migrations/008_agent_rpc_exception_precedence.sql +172 -0
  88. package/src/migrations/009_public_booking_location.sql +162 -0
  89. package/src/migrations/010_public_service_curation.sql +31 -0
  90. package/src/migrations/011_default_assignee.sql +167 -0
  91. package/src/migrations/012_drop_legacy_booking_overload.sql +29 -0
  92. package/src/migrations/013_normalize_v_appointments.sql +161 -0
  93. package/src/migrations/index.ts +1746 -1
  94. package/src/public/BookingWidget.tsx +10 -1
  95. package/src/public/context.tsx +3 -0
  96. package/src/public/createPublicBookingPlugin.ts +14 -1
  97. package/src/public/data.supabase.ts +3 -0
  98. package/src/public/data.ts +6 -0
  99. package/src/public/format.ts +36 -1
  100. package/src/public/hooks.ts +7 -2
  101. package/src/public/index.ts +1 -0
  102. package/src/public/steps/ContactStep.tsx +10 -5
  103. package/src/public/types.ts +21 -0
  104. package/src/views/CalendarView.tsx +15 -1
  105. package/dist/chunk-3LJKZQK4.js +0 -12
  106. package/dist/chunk-3LJKZQK4.js.map +0 -1
  107. package/dist/chunk-DRZF6CV2.js.map +0 -1
  108. package/dist/chunk-LFUELUIT.cjs.map +0 -1
  109. package/dist/chunk-SYDBDI3Q.cjs +0 -15
  110. package/dist/chunk-SYDBDI3Q.cjs.map +0 -1
  111. package/dist/components/SearchCombobox.d.ts +0 -38
  112. package/dist/components/SearchCombobox.d.ts.map +0 -1
  113. package/dist/tenant-222EHYUC.cjs +0 -16
  114. package/dist/tenant-222EHYUC.cjs.map +0 -1
  115. package/dist/tenant-LAUWVUYB.js +0 -3
  116. package/dist/tenant-LAUWVUYB.js.map +0 -1
  117. package/src/components/SearchCombobox.tsx +0 -216
package/dist/index.js CHANGED
@@ -1,9 +1,8 @@
1
- import { createMockAgendaProvider } from './chunk-DRZF6CV2.js';
2
- export { createMockAgendaProvider } from './chunk-DRZF6CV2.js';
3
- import { setAgendaTenantId, getAgendaTenantId } from './chunk-3LJKZQK4.js';
4
- import React12, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
5
- import { getSupabaseClientOptional, registerTranslations, createSafeDataProvider, usePluginRuntimeOptional, useTranslation, getActiveTenantId, getCurrentLocale, formatCurrency } from '@fayz-ai/core';
6
- import { createPluginContext, setScheduleBlockConfig, getScheduleBlockConfig, PluginSettingsPanel, dedup, SettingsGroup, ToggleRow, SelectRow, usePluginPrefs, PersonLink, CrudFormPage, CrudDetailPage } from '@fayz-ai/saas';
1
+ import { createMockAgendaProvider, createSupabasePublicBookingProvider } from './chunk-YQO6B4G3.js';
2
+ export { createMockAgendaProvider } from './chunk-YQO6B4G3.js';
3
+ import React13, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
4
+ import { getSupabaseClientOptional, getActiveTenantId, registerTranslations, createSafeDataProvider, usePluginRuntimeOptional, useTranslation, getCurrentLocale, useDataChanged, formatCurrency } from '@fayz-ai/core';
5
+ import { createPluginContext, setScheduleBlockConfig, getScheduleBlockConfig, PluginSettingsPanel, dedup, useTenantPluginSettings, SettingsGroup, ToggleRow, SelectRow, usePluginPrefs, PersonLink, useLimitGuard, CrudFormPage, CrudDetailPage, ContactPicker, invalidateLimit } from '@fayz-ai/saas';
7
6
  import FullCalendar from '@fullcalendar/react';
8
7
  import dayGridPlugin from '@fullcalendar/daygrid';
9
8
  import timeGridPlugin from '@fullcalendar/timegrid';
@@ -11,10 +10,10 @@ import resourceTimeGridPlugin from '@fullcalendar/resource-timegrid';
11
10
  import interactionPlugin from '@fullcalendar/interaction';
12
11
  import listPlugin from '@fullcalendar/list';
13
12
  import ptBrLocale from '@fullcalendar/core/locales/pt-br';
14
- import { RefreshCw, History, CheckCircle2, AlertCircle, Plus, ChevronDown, ChevronLeft, ChevronRight, Settings, Pencil, Trash2, X, Clock, User, MapPin, FileText, AlertTriangle, Check, DollarSign, HandCoins, Ban, CheckSquare, Calendar, Briefcase, CircleDot, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, ExternalLink, Search } from 'lucide-react';
15
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
13
+ import { CalendarCog, CalendarPlus, RefreshCw, History, CheckCircle2, AlertCircle, Loader2, X, Plus, ChevronDown, ChevronLeft, ChevronRight, Settings, Pencil, Trash2, Clock, User, MapPin, FileText, AlertTriangle, Check, DollarSign, HandCoins, Ban, CheckSquare, Calendar, Briefcase, CircleDot, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, ExternalLink } from 'lucide-react';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
15
  import { createPortal } from 'react-dom';
17
- import { Button, toast, ConfirmDialog, Modal, ModalContent, ModalTitle, cn, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@fayz-ai/ui';
16
+ import { Button, toast, Checkbox, Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, ConfirmDialog, Modal, ModalContent, ModalTitle, SearchCombobox, cn, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@fayz-ai/ui';
18
17
  import { createStore } from 'zustand/vanilla';
19
18
 
20
19
  // src/config.ts
@@ -446,7 +445,7 @@ function toDateLocale(locale) {
446
445
  }
447
446
  function MiniCalendar({ selectedDate, onDateSelect, onMonthChange }) {
448
447
  const locale = getCurrentLocale();
449
- const [viewMonth, setViewMonth] = React12.useState(() => new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
448
+ const [viewMonth, setViewMonth] = React13.useState(() => new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
450
449
  const monthLabel = viewMonth.toLocaleDateString(toDateLocale(locale), { month: "long", year: "numeric" });
451
450
  const dayLabels = DAY_LABELS[locale] ?? DAY_LABELS.en;
452
451
  const weeks = useMemo(() => {
@@ -501,216 +500,6 @@ function MiniCalendar({ selectedDate, onDateSelect, onMonthChange }) {
501
500
  }) }, wi))
502
501
  ] });
503
502
  }
504
- function SearchCombobox({
505
- value,
506
- onChange,
507
- onSelect,
508
- options,
509
- placeholder,
510
- autoFocus,
511
- onBlurEmpty,
512
- renderRight,
513
- allowCreate,
514
- onCreateNew,
515
- createLabel = "Create",
516
- className,
517
- minimal,
518
- inlineLabel,
519
- inlineIcon = "search",
520
- loading,
521
- onFocus: onFocusProp
522
- }) {
523
- const [open, setOpen] = useState(false);
524
- const [activeIndex, setActiveIndex] = useState(-1);
525
- const [expanded, setExpanded] = useState(!inlineLabel);
526
- const [dropRect, setDropRect] = useState(null);
527
- const inputRef = useRef(null);
528
- const wrapperRef = useRef(null);
529
- const listRef = useRef(null);
530
- useEffect(() => {
531
- if (value && inlineLabel) setExpanded(true);
532
- if (!value && inlineLabel && !open) setExpanded(false);
533
- }, [value, open]);
534
- function updateRect() {
535
- if (wrapperRef.current) setDropRect(wrapperRef.current.getBoundingClientRect());
536
- }
537
- const showCreate = open && allowCreate && onCreateNew && value.trim().length > 0 && !options.some((o) => o.label.toLowerCase() === value.trim().toLowerCase());
538
- const totalItems = options.length + (showCreate ? 1 : 0);
539
- useEffect(() => {
540
- setActiveIndex(-1);
541
- }, [options]);
542
- useEffect(() => {
543
- if ((options.length > 0 || showCreate || loading) && document.activeElement === inputRef.current) setOpen(true);
544
- }, [options, showCreate, loading]);
545
- const handleKeyDown = useCallback((e) => {
546
- if (!open || totalItems === 0) {
547
- if (e.key === "ArrowDown" && totalItems > 0) {
548
- e.preventDefault();
549
- setOpen(true);
550
- setActiveIndex(0);
551
- }
552
- return;
553
- }
554
- switch (e.key) {
555
- case "ArrowDown":
556
- e.preventDefault();
557
- setActiveIndex((prev) => (prev + 1) % totalItems);
558
- break;
559
- case "ArrowUp":
560
- e.preventDefault();
561
- setActiveIndex((prev) => prev <= 0 ? totalItems - 1 : prev - 1);
562
- break;
563
- case "Enter":
564
- e.preventDefault();
565
- if (activeIndex >= 0 && activeIndex < options.length) {
566
- onSelect(options[activeIndex]);
567
- setOpen(false);
568
- setActiveIndex(-1);
569
- } else if (showCreate && activeIndex === options.length) {
570
- onCreateNew?.(value.trim());
571
- setOpen(false);
572
- setActiveIndex(-1);
573
- }
574
- break;
575
- case "Escape":
576
- e.preventDefault();
577
- setOpen(false);
578
- setActiveIndex(-1);
579
- break;
580
- }
581
- }, [open, options, totalItems, activeIndex, onSelect, showCreate, onCreateNew, value]);
582
- useEffect(() => {
583
- if (activeIndex < 0 || !listRef.current) return;
584
- const items = listRef.current.children;
585
- if (items[activeIndex]) items[activeIndex].scrollIntoView({ block: "nearest" });
586
- }, [activeIndex]);
587
- function handleBlur() {
588
- setTimeout(() => {
589
- setOpen(false);
590
- setActiveIndex(-1);
591
- if (!value && onBlurEmpty) onBlurEmpty();
592
- if (!value && inlineLabel) setExpanded(false);
593
- }, 200);
594
- }
595
- const hasDropdown = open && (options.length > 0 || showCreate || loading);
596
- if (!expanded && inlineLabel) {
597
- const isPlus = inlineIcon === "plus";
598
- return /* @__PURE__ */ jsxs(
599
- "button",
600
- {
601
- type: "button",
602
- onClick: () => {
603
- setExpanded(true);
604
- setTimeout(() => inputRef.current?.focus(), 50);
605
- },
606
- className: `flex items-center gap-1.5 w-full text-left text-sm py-1.5 pl-2 transition-colors ${isPlus ? "text-primary hover:text-primary/80" : "text-muted-foreground hover:text-foreground"} ${className ?? ""}`,
607
- children: [
608
- isPlus && /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 shrink-0" }),
609
- inlineLabel
610
- ]
611
- }
612
- );
613
- }
614
- return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: `relative ${className ?? ""}`, children: [
615
- !minimal && /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" }),
616
- /* @__PURE__ */ jsx(
617
- "input",
618
- {
619
- ref: inputRef,
620
- type: "text",
621
- value,
622
- onChange: (e) => {
623
- onChange(e.target.value);
624
- updateRect();
625
- setOpen(true);
626
- },
627
- onFocus: () => {
628
- updateRect();
629
- onFocusProp?.();
630
- if (options.length > 0 || showCreate || loading) setOpen(true);
631
- },
632
- onBlur: handleBlur,
633
- onKeyDown: handleKeyDown,
634
- placeholder,
635
- autoFocus,
636
- className: minimal ? "w-full bg-transparent pl-2 pr-3 py-1.5 text-sm placeholder:text-muted-foreground focus:outline-none border-b border-transparent hover:border-border focus:border-foreground transition-colors" : "w-full rounded-lg border bg-background pl-8 pr-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring",
637
- role: "combobox",
638
- "aria-expanded": hasDropdown,
639
- "aria-autocomplete": "list",
640
- "aria-activedescendant": activeIndex >= 0 ? `combobox-opt-${activeIndex}` : void 0
641
- }
642
- ),
643
- hasDropdown && dropRect && createPortal(
644
- /* @__PURE__ */ jsxs(
645
- "div",
646
- {
647
- ref: listRef,
648
- role: "listbox",
649
- "data-modal-passthrough": true,
650
- style: { position: "fixed", left: dropRect.left, top: dropRect.bottom + 4, width: dropRect.width, zIndex: 9999, pointerEvents: "auto" },
651
- className: "fayz-glass-surface max-h-52 overflow-y-auto rounded-lg border bg-popover shadow-lg py-0.5 animate-in fade-in zoom-in-95 duration-100",
652
- children: [
653
- loading && options.length === 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground", children: [
654
- /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5 animate-spin shrink-0", viewBox: "0 0 16 16", fill: "none", children: [
655
- /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "1.5", opacity: "0.25" }),
656
- /* @__PURE__ */ jsx("path", { d: "M14.5 8a6.5 6.5 0 00-6.5-6.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
657
- ] }),
658
- /* @__PURE__ */ jsx("span", { children: "Buscando\u2026" })
659
- ] }),
660
- options.map((opt, i) => /* @__PURE__ */ jsxs(
661
- "div",
662
- {
663
- id: `combobox-opt-${i}`,
664
- role: "option",
665
- "aria-selected": i === activeIndex,
666
- onMouseDown: (e) => {
667
- e.preventDefault();
668
- onSelect(opt);
669
- setOpen(false);
670
- },
671
- onMouseEnter: () => setActiveIndex(i),
672
- className: `flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors ${i === activeIndex ? "bg-primary/10 text-primary" : "hover:bg-muted/50"}`,
673
- children: [
674
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
675
- /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: opt.label }),
676
- opt.subtitle && /* @__PURE__ */ jsx("p", { className: "truncate text-[11px] text-muted-foreground", children: opt.subtitle })
677
- ] }),
678
- renderRight && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: renderRight(opt) })
679
- ]
680
- },
681
- opt.id
682
- )),
683
- showCreate && /* @__PURE__ */ jsxs(
684
- "div",
685
- {
686
- id: `combobox-opt-${options.length}`,
687
- role: "option",
688
- "aria-selected": activeIndex === options.length,
689
- onMouseDown: (e) => {
690
- e.preventDefault();
691
- onCreateNew?.(value.trim());
692
- setOpen(false);
693
- },
694
- onMouseEnter: () => setActiveIndex(options.length),
695
- className: `flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors border-t ${activeIndex === options.length ? "bg-primary/10 text-primary" : "hover:bg-muted/50"}`,
696
- children: [
697
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 shrink-0" }),
698
- /* @__PURE__ */ jsxs("span", { children: [
699
- createLabel,
700
- " \u201C",
701
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: value.trim() }),
702
- "\u201D"
703
- ] })
704
- ]
705
- }
706
- )
707
- ]
708
- }
709
- ),
710
- document.body
711
- )
712
- ] });
713
- }
714
503
  var TYPE_ICONS = {
715
504
  cash: Banknote,
716
505
  pix: QrCode,
@@ -736,7 +525,7 @@ var ICON_COLORS = {
736
525
  check: "text-muted-foreground"
737
526
  };
738
527
  function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPaymentChange, formatCurrency: formatCurrency2, inline }) {
739
- const t = useTranslation();
528
+ const t2 = useTranslation();
740
529
  const config = useAgendaConfig();
741
530
  const bridge = config.financialBridge;
742
531
  const [visible, setVisible] = useState(false);
@@ -777,7 +566,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
777
566
  setVisible(false);
778
567
  setTimeout(onClose, 200);
779
568
  }
780
- const selectedType = paymentTypes.find((t2) => t2.id === selectedTypeId);
569
+ const selectedType = paymentTypes.find((t3) => t3.id === selectedTypeId);
781
570
  const txType = selectedType?.transactionType ?? "";
782
571
  const needsBank = ["bank_transfer", "pix", "check"].includes(txType);
783
572
  const needsCard = txType === "credit_card" || txType === "debit_card";
@@ -816,7 +605,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
816
605
  loading && /* @__PURE__ */ jsx("div", { className: "space-y-3 py-2", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-8 rounded-lg bg-muted animate-pulse" }, i)) }),
817
606
  !loading && isPaid && justPaid && /* @__PURE__ */ jsxs("div", { className: "text-center py-8 space-y-2 animate-in fade-in zoom-in-95 duration-300", children: [
818
607
  /* @__PURE__ */ jsx("div", { className: "flex h-14 w-14 mx-auto items-center justify-center rounded-full bg-success/10 animate-bounce", style: { animationDuration: "600ms", animationIterationCount: "2" }, children: /* @__PURE__ */ jsx(Check, { className: "h-7 w-7 text-success" }) }),
819
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-success", children: t("agenda.payment.checkoutComplete") }),
608
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-success", children: t2("agenda.payment.checkoutComplete") }),
820
609
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatCurrency2(detail.totalAmount) })
821
610
  ] }),
822
611
  !loading && detail && detail.status !== "none" && !justPaid && /* @__PURE__ */ jsxs("div", { className: "space-y-3 animate-in fade-in duration-300", children: [
@@ -838,14 +627,14 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
838
627
  ] }),
839
628
  /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
840
629
  mov.paymentDate ?? mov.dueDate,
841
- detail.movements.length > 1 && mov.installmentNumber ? ` \xB7 ${t("agenda.payment.installment")} ${mov.installmentNumber}` : ""
630
+ detail.movements.length > 1 && mov.installmentNumber ? ` \xB7 ${t2("agenda.payment.installment")} ${mov.installmentNumber}` : ""
842
631
  ] })
843
632
  ] }),
844
633
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
845
634
  isMovPaid && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-success/10 px-2 py-0.5 text-[10px] font-medium text-success", children: [
846
635
  /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }),
847
636
  " ",
848
- t("agenda.payment.paid")
637
+ t2("agenda.payment.paid")
849
638
  ] }),
850
639
  isPartial && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-warning/10 px-2 py-0.5 text-[10px] font-medium text-warning", children: [
851
640
  "! ",
@@ -855,14 +644,14 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
855
644
  ] }),
856
645
  isOverdue && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-destructive/10 px-2 py-0.5 text-[10px] font-medium text-destructive", children: [
857
646
  "! ",
858
- t("agenda.payment.overdue")
647
+ t2("agenda.payment.overdue")
859
648
  ] }),
860
- mov.status === "pending" && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: t("agenda.payment.pending") }),
649
+ mov.status === "pending" && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: t2("agenda.payment.pending") }),
861
650
  /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: formatCurrency2(mov.paidAmount || mov.amount) })
862
651
  ] })
863
652
  ] }, mov.id);
864
653
  }) }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm px-1", children: [
865
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("agenda.payment.total") }),
654
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("agenda.payment.total") }),
866
655
  /* @__PURE__ */ jsx("span", { className: "font-semibold", children: formatCurrency2(detail.totalAmount) })
867
656
  ] }),
868
657
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
@@ -876,7 +665,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
876
665
  href: `#/financial/receivables/detail/${detail.invoiceId}`,
877
666
  className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline ml-auto",
878
667
  children: [
879
- t("agenda.payment.viewInvoice"),
668
+ t2("agenda.payment.viewInvoice"),
880
669
  /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
881
670
  ]
882
671
  }
@@ -885,22 +674,22 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
885
674
  ] }),
886
675
  !loading && (!detail || detail.movements.length === 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
887
676
  services && services.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
888
- /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.payment.serviceSummary") }),
677
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t2("agenda.payment.serviceSummary") }),
889
678
  /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: services.map((svc, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm py-1", children: [
890
679
  /* @__PURE__ */ jsx("span", { className: "truncate", children: svc.name }),
891
680
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatCurrency2(svc.price) })
892
681
  ] }, i)) })
893
682
  ] }),
894
683
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm font-semibold pt-2 border-t", children: [
895
- /* @__PURE__ */ jsx("span", { children: t("agenda.payment.total") }),
684
+ /* @__PURE__ */ jsx("span", { children: t2("agenda.payment.total") }),
896
685
  /* @__PURE__ */ jsx("span", { children: formatCurrency2(orderTotal) })
897
686
  ] }),
898
687
  /* @__PURE__ */ jsxs("div", { children: [
899
- /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.payment.paymentMethod") }),
688
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t2("agenda.payment.paymentMethod") }),
900
689
  paymentTypes.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-input bg-muted/30 px-3 py-3 text-center", children: [
901
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("agenda.payment.noMethods") }),
690
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("agenda.payment.noMethods") }),
902
691
  /* @__PURE__ */ jsxs("a", { href: "#/financial/settings", className: "mt-1.5 inline-block text-xs font-medium text-primary hover:underline", children: [
903
- t("agenda.payment.configureMethods"),
692
+ t2("agenda.payment.configureMethods"),
904
693
  " \u2192"
905
694
  ] })
906
695
  ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1.5", children: paymentTypes.map((pt) => {
@@ -927,7 +716,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
927
716
  ] }),
928
717
  selectedTypeId && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
929
718
  filteredMethods.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
930
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: needsCard ? t("agenda.payment.card") : t("agenda.payment.method") }),
719
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: needsCard ? t2("agenda.payment.card") : t2("agenda.payment.method") }),
931
720
  /* @__PURE__ */ jsxs(
932
721
  "select",
933
722
  {
@@ -935,7 +724,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
935
724
  onChange: (e) => setPaymentMethodId(e.target.value),
936
725
  className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
937
726
  children: [
938
- /* @__PURE__ */ jsx("option", { value: "", children: t("agenda.payment.select") }),
727
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("agenda.payment.select") }),
939
728
  filteredMethods.map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
940
729
  ]
941
730
  }
@@ -943,7 +732,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
943
732
  ] }),
944
733
  needsCard && /* @__PURE__ */ jsxs("div", { className: `grid gap-2.5 ${needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, children: [
945
734
  /* @__PURE__ */ jsxs("div", { children: [
946
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("agenda.payment.cardBrand") }),
735
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t2("agenda.payment.cardBrand") }),
947
736
  /* @__PURE__ */ jsxs(
948
737
  "select",
949
738
  {
@@ -951,14 +740,14 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
951
740
  onChange: (e) => setCardBrand(e.target.value),
952
741
  className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
953
742
  children: [
954
- /* @__PURE__ */ jsx("option", { value: "", children: t("agenda.payment.selectBrand") }),
743
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("agenda.payment.selectBrand") }),
955
744
  ["Visa", "Mastercard", "American Express", "Elo", "Hipercard", "Diners Club"].map((b) => /* @__PURE__ */ jsx("option", { value: b, children: b }, b))
956
745
  ]
957
746
  }
958
747
  )
959
748
  ] }),
960
749
  needsCardInstallments && /* @__PURE__ */ jsxs("div", { children: [
961
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("agenda.payment.cardInstallments") }),
750
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t2("agenda.payment.cardInstallments") }),
962
751
  /* @__PURE__ */ jsx(
963
752
  "select",
964
753
  {
@@ -975,7 +764,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
975
764
  ] })
976
765
  ] }),
977
766
  needsBank && bankAccounts.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
978
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("agenda.payment.bankAccount") }),
767
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t2("agenda.payment.bankAccount") }),
979
768
  /* @__PURE__ */ jsxs(
980
769
  "select",
981
770
  {
@@ -983,7 +772,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
983
772
  onChange: (e) => setSelectedBankId(e.target.value),
984
773
  className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
985
774
  children: [
986
- /* @__PURE__ */ jsx("option", { value: "", children: t("agenda.payment.selectAccount") }),
775
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("agenda.payment.selectAccount") }),
987
776
  bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
988
777
  ]
989
778
  }
@@ -997,7 +786,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
997
786
  onClick: handleCheckout,
998
787
  disabled: processing || !selectedTypeId,
999
788
  className: "w-full rounded-lg bg-primary border border-primary py-2.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset disabled:opacity-40 transition-colors",
1000
- children: processing ? t("agenda.payment.checkingOut") : `${t("agenda.payment.checkout")} ${formatCurrency2(orderTotal)}`
789
+ children: processing ? t2("agenda.payment.checkingOut") : `${t2("agenda.payment.checkout")} ${formatCurrency2(orderTotal)}`
1001
790
  }
1002
791
  )
1003
792
  ] })
@@ -1018,7 +807,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
1018
807
  onClick: (e) => e.stopPropagation(),
1019
808
  children: [
1020
809
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10", children: [
1021
- /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: t("agenda.payment.title") }),
810
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: t2("agenda.payment.title") }),
1022
811
  /* @__PURE__ */ jsx("button", { onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
1023
812
  ] }),
1024
813
  content
@@ -1179,7 +968,7 @@ function StatusSelect({ value, onChange, statuses, bookingDate }) {
1179
968
  ] });
1180
969
  }
1181
970
  function ProfessionalSelect({ value, onChange, professionals }) {
1182
- const t = useTranslation();
971
+ const t2 = useTranslation();
1183
972
  const [open, setOpen] = useState(false);
1184
973
  const ref = useRef(null);
1185
974
  const current = professionals.find((p) => p.id === value);
@@ -1199,7 +988,7 @@ function ProfessionalSelect({ value, onChange, professionals }) {
1199
988
  onClick: () => setOpen((p) => !p),
1200
989
  className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted/30 transition-colors text-left",
1201
990
  children: [
1202
- current ? /* @__PURE__ */ jsx("span", { children: current.name }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("agenda.appointment.selectProfessional") }),
991
+ current ? /* @__PURE__ */ jsx("span", { children: current.name }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("agenda.appointment.selectProfessional") }),
1203
992
  /* @__PURE__ */ jsx("svg", { className: "ml-auto h-3 w-3 text-muted-foreground shrink-0", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
1204
993
  ]
1205
994
  }
@@ -1220,7 +1009,7 @@ function ProfessionalSelect({ value, onChange, professionals }) {
1220
1009
  ] });
1221
1010
  }
1222
1011
  function LocationSelect({ value, onChange, locations }) {
1223
- const t = useTranslation();
1012
+ const t2 = useTranslation();
1224
1013
  const [open, setOpen] = useState(false);
1225
1014
  const ref = useRef(null);
1226
1015
  const current = locations.find((l) => l.id === value);
@@ -1240,7 +1029,7 @@ function LocationSelect({ value, onChange, locations }) {
1240
1029
  onClick: () => setOpen((p) => !p),
1241
1030
  className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted/30 transition-colors text-left",
1242
1031
  children: [
1243
- current ? /* @__PURE__ */ jsx("span", { children: current.name }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("agenda.appointment.selectLocation") }),
1032
+ current ? /* @__PURE__ */ jsx("span", { children: current.name }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("agenda.appointment.selectLocation") }),
1244
1033
  /* @__PURE__ */ jsx("svg", { className: "ml-auto h-3 w-3 text-muted-foreground shrink-0", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
1245
1034
  ]
1246
1035
  }
@@ -1261,12 +1050,13 @@ function LocationSelect({ value, onChange, locations }) {
1261
1050
  ] });
1262
1051
  }
1263
1052
  function AppointmentModal({ open, mode, bookingId, prefill, initialTab, externalSource, onClose, onCreated }) {
1264
- const t = useTranslation();
1053
+ const t2 = useTranslation();
1265
1054
  const locale = getCurrentLocale();
1266
1055
  const config = useAgendaConfig();
1267
1056
  const provider2 = useAgendaProvider();
1268
1057
  const initialSnapshot = useRef("");
1269
1058
  const createBooking = useAgendaStore((s) => s.createBooking);
1059
+ const guardBookings = useLimitGuard("bookings_month");
1270
1060
  const updateBooking = useAgendaStore((s) => s.updateBooking);
1271
1061
  const deleteBooking = useAgendaStore((s) => s.deleteBooking);
1272
1062
  const fetchBookings = useAgendaStore((s) => s.fetchBookings);
@@ -1300,13 +1090,6 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1300
1090
  const [showNotes, setShowNotes] = useState(false);
1301
1091
  const [saving, setSaving] = useState(false);
1302
1092
  const [conflict, setConflict] = useState(false);
1303
- const [quickCreate, setQuickCreate] = useState(false);
1304
- const [newClientName, setNewClientName] = useState("");
1305
- const [newClientPhone, setNewClientPhone] = useState("");
1306
- const [newClientEmail, setNewClientEmail] = useState("");
1307
- const [creatingClient, setCreatingClient] = useState(false);
1308
- const [clientOptions, setClientOptions] = useState([]);
1309
- const [clientSearching, setClientSearching] = useState(false);
1310
1093
  const [serviceOptions, setServiceOptions] = useState([]);
1311
1094
  const [initialServiceOptions, setInitialServiceOptions] = useState([]);
1312
1095
  const [editingDate, setEditingDate] = useState(false);
@@ -1340,11 +1123,6 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1340
1123
  setShowNotes(false);
1341
1124
  setConflict(false);
1342
1125
  setLoading(mode === "edit");
1343
- setQuickCreate(false);
1344
- setNewClientName("");
1345
- setNewClientPhone("");
1346
- setNewClientEmail("");
1347
- setCreatingClient(false);
1348
1126
  setBookingType(prefill?.kind ?? defaultBookingType);
1349
1127
  setTitle("");
1350
1128
  setEditingDate(false);
@@ -1454,24 +1232,6 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1454
1232
  setInitialServiceOptions(opts);
1455
1233
  }).catch(() => setInitialServiceOptions([]));
1456
1234
  }, [open]);
1457
- useEffect(() => {
1458
- if (!config.contactLookup || clientSearch.length < 1 || clientId) {
1459
- setClientOptions([]);
1460
- setClientSearching(false);
1461
- return;
1462
- }
1463
- setClientSearching(true);
1464
- const timer = setTimeout(async () => {
1465
- try {
1466
- const results = await config.contactLookup.search(clientSearch);
1467
- setClientOptions(results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, price: r.price, data: r.data })));
1468
- } catch {
1469
- setClientOptions([]);
1470
- }
1471
- setClientSearching(false);
1472
- }, 250);
1473
- return () => clearTimeout(timer);
1474
- }, [clientSearch, clientId]);
1475
1235
  useEffect(() => {
1476
1236
  if (!config.serviceLookup || serviceSearch.length < 1) {
1477
1237
  setServiceOptions([]);
@@ -1515,12 +1275,6 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1515
1275
  }, 300);
1516
1276
  return () => clearTimeout(timer);
1517
1277
  }, [professionalId, date, startTime, endTime, bookingId]);
1518
- function selectClient(opt) {
1519
- setClientId(opt.id);
1520
- setClientSearch(opt.label);
1521
- setClientOptions([]);
1522
- setClientPhone(opt.data?.phone ?? "");
1523
- }
1524
1278
  function addService(opt) {
1525
1279
  const duration = opt.data?.duration_minutes || 60;
1526
1280
  setServices((p) => [...p, { serviceId: opt.id, name: opt.label, durationMinutes: duration, price: opt.price ?? 0 }]);
@@ -1533,6 +1287,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1533
1287
  async function handleSubmit(e) {
1534
1288
  e.preventDefault();
1535
1289
  if (!canSubmit) return;
1290
+ if (mode === "create" && await guardBookings() === "blocked") return;
1536
1291
  setSaving(true);
1537
1292
  try {
1538
1293
  const startsAt = (/* @__PURE__ */ new Date(`${date}T${startTime}:00`)).toISOString();
@@ -1547,6 +1302,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1547
1302
  title: activeType.fields.title ? title.trim() : void 0,
1548
1303
  services: services.map((s) => ({ serviceId: s.serviceId, name: s.name, durationMinutes: s.durationMinutes, price: s.price }))
1549
1304
  });
1305
+ invalidateLimit("bookings_month");
1550
1306
  onClose();
1551
1307
  onCreated?.(newBooking.id, externalSource);
1552
1308
  } else if (bookingId) {
@@ -1581,8 +1337,10 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1581
1337
  await deleteBooking(bookingId);
1582
1338
  onClose();
1583
1339
  } catch {
1340
+ setConfirmingDelete(false);
1341
+ } finally {
1342
+ setDeleting(false);
1584
1343
  }
1585
- setDeleting(false);
1586
1344
  }
1587
1345
  const isPaidBooking = editPaymentStatus === "paid";
1588
1346
  const selectedCancellationReason = cancellationReasons.find((reason) => reason.id === cancellationReasonId);
@@ -1599,9 +1357,9 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1599
1357
  if (!v) onClose();
1600
1358
  }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", noPadding: true, children: [
1601
1359
  /* @__PURE__ */ jsxs("div", { className: "px-5 pt-4 pb-3 shrink-0 space-y-3", children: [
1602
- /* @__PURE__ */ jsx(ModalTitle, { className: "sr-only", children: mode === "create" ? t("agenda.appointment.new") : t("agenda.appointment.edit") }),
1360
+ /* @__PURE__ */ jsx(ModalTitle, { className: "sr-only", children: mode === "create" ? t2("agenda.appointment.new") : t2("agenda.appointment.edit") }),
1603
1361
  showTabs && mode === "create" ? /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: config.bookingTypes.map((bt) => {
1604
- const label = t(`agenda.bookingType.${bt.id}`) || bt.label;
1362
+ const label = t2(`agenda.bookingType.${bt.id}`) || bt.label;
1605
1363
  const isActive = bookingType === bt.id;
1606
1364
  const BtIcon = BOOKING_TYPE_ICONS[bt.icon] ?? Calendar;
1607
1365
  return /* @__PURE__ */ jsxs(
@@ -1619,9 +1377,9 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1619
1377
  bt.id
1620
1378
  );
1621
1379
  }) }) : mode === "edit" && (config.clientEntityDef && clientId || config.modules.financial && editOrderId ? /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: [
1622
- { id: "appointment", label: t(`agenda.bookingType.${bookingType}`) || activeType.label, icon: Calendar, show: true },
1623
- { id: "client", label: t("agenda.appointment.clientTab"), icon: User, show: !!config.clientEntityDef && !!clientId },
1624
- { id: "financial", label: t("agenda.payment.title"), icon: DollarSign, show: config.modules.financial && !!editOrderId }
1380
+ { id: "appointment", label: t2(`agenda.bookingType.${bookingType}`) || activeType.label, icon: Calendar, show: true },
1381
+ { id: "client", label: t2("agenda.appointment.clientTab"), icon: User, show: !!config.clientEntityDef && !!clientId },
1382
+ { id: "financial", label: t2("agenda.payment.title"), icon: DollarSign, show: config.modules.financial && !!editOrderId }
1625
1383
  ].filter((tab) => tab.show).map((tab) => {
1626
1384
  const isActive = modalTab === tab.id;
1627
1385
  return /* @__PURE__ */ jsxs(
@@ -1640,7 +1398,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1640
1398
  },
1641
1399
  tab.id
1642
1400
  );
1643
- }) }) : /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t(`agenda.bookingType.${bookingType}`) || activeType.label })),
1401
+ }) }) : /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t2(`agenda.bookingType.${bookingType}`) || activeType.label })),
1644
1402
  modalTab === "appointment" && /* @__PURE__ */ jsxs(Fragment, { children: [
1645
1403
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1646
1404
  /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
@@ -1698,7 +1456,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1698
1456
  ] }),
1699
1457
  conflict && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-destructive/10 px-3 py-1.5 text-xs text-destructive", children: [
1700
1458
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
1701
- t("agenda.appointment.conflict")
1459
+ t2("agenda.appointment.conflict")
1702
1460
  ] })
1703
1461
  ] })
1704
1462
  ] }),
@@ -1739,7 +1497,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1739
1497
  onBack: () => setModalTab("appointment"),
1740
1498
  onEdit: () => setClientEditing(true)
1741
1499
  }
1742
- ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: t("agenda.appointment.noClientData") }) }),
1500
+ ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: t2("agenda.appointment.noClientData") }) }),
1743
1501
  modalTab === "financial" && editOrderId && config.financialBridge && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(
1744
1502
  BookingPaymentPanel,
1745
1503
  {
@@ -1776,7 +1534,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1776
1534
  type: "text",
1777
1535
  value: title,
1778
1536
  onChange: (e) => setTitle(e.target.value),
1779
- placeholder: t("agenda.appointment.addTitle"),
1537
+ placeholder: t2("agenda.appointment.addTitle"),
1780
1538
  autoFocus: mode === "create",
1781
1539
  className: "w-full flex-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-ring"
1782
1540
  }
@@ -1784,133 +1542,25 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1784
1542
  ] }),
1785
1543
  activeType.fields.client && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
1786
1544
  /* @__PURE__ */ jsx(User, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1787
- /* @__PURE__ */ jsx("div", { className: "flex-1", children: clientId && !quickCreate ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md bg-muted/30 px-2.5 py-1.5 text-sm", children: [
1788
- /* @__PURE__ */ jsx(PersonLink, { personId: clientId, name: clientSearch, size: "default", className: "flex-1 min-w-0" }),
1789
- clientPhone && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground shrink-0", children: clientPhone }),
1790
- !isPaidBooking && /* @__PURE__ */ jsx(
1791
- "button",
1792
- {
1793
- type: "button",
1794
- onClick: () => {
1795
- setClientId("");
1796
- setClientSearch("");
1797
- setClientPhone("");
1798
- },
1799
- className: "p-0.5 text-muted-foreground hover:text-destructive shrink-0",
1800
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
1801
- }
1802
- )
1803
- ] }) : !quickCreate ? /* @__PURE__ */ jsx(
1804
- SearchCombobox,
1545
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
1546
+ ContactPicker,
1805
1547
  {
1806
- value: clientSearch,
1807
- onChange: (v) => {
1808
- setClientSearch(v);
1809
- setClientId("");
1548
+ value: clientId || clientSearch ? { id: clientId || void 0, name: clientSearch, phone: clientPhone } : null,
1549
+ onChange: (contact) => {
1550
+ setClientId(contact?.id ?? "");
1551
+ setClientSearch(contact?.name ?? "");
1552
+ setClientPhone(contact?.phone ?? "");
1810
1553
  },
1811
- onSelect: selectClient,
1812
- options: clientOptions,
1813
- loading: clientSearching,
1814
- placeholder: t("agenda.appointment.searchClient"),
1815
- allowCreate: true,
1816
- createLabel: t("agenda.appointment.newClient"),
1817
- onCreateNew: (name) => {
1818
- setQuickCreate(true);
1819
- setNewClientName(name);
1820
- setNewClientPhone("");
1821
- setNewClientEmail("");
1822
- },
1823
- minimal: true,
1824
- inlineLabel: t("agenda.appointment.addClient")
1554
+ kind: config.clientKind,
1555
+ extensionTable: "clients",
1556
+ lookup: config.contactLookup,
1557
+ clearable: !isPaidBooking,
1558
+ variant: "inline",
1559
+ placeholder: t2("agenda.appointment.searchClient"),
1560
+ createLabel: t2("agenda.appointment.newClient"),
1561
+ hint: /* @__PURE__ */ jsx("p", { className: "text-[11px] text-warning", children: t2("agenda.appointment.selectClientHint") })
1825
1562
  }
1826
- ) : /* @__PURE__ */ jsxs("div", { className: "rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] p-2.5 space-y-2", children: [
1827
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1828
- /* @__PURE__ */ jsx(
1829
- "input",
1830
- {
1831
- type: "text",
1832
- value: newClientName,
1833
- onChange: (e) => setNewClientName(e.target.value),
1834
- placeholder: t("agenda.appointment.name"),
1835
- className: "flex-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-ring",
1836
- autoFocus: true
1837
- }
1838
- ),
1839
- /* @__PURE__ */ jsx(
1840
- "button",
1841
- {
1842
- type: "button",
1843
- onClick: () => setQuickCreate(false),
1844
- className: "p-1 text-muted-foreground hover:text-foreground shrink-0",
1845
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
1846
- }
1847
- )
1848
- ] }),
1849
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
1850
- /* @__PURE__ */ jsx(
1851
- "input",
1852
- {
1853
- type: "tel",
1854
- value: newClientPhone,
1855
- onChange: (e) => setNewClientPhone(e.target.value),
1856
- placeholder: t("agenda.appointment.phone"),
1857
- className: "rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1858
- }
1859
- ),
1860
- /* @__PURE__ */ jsx(
1861
- "input",
1862
- {
1863
- type: "email",
1864
- value: newClientEmail,
1865
- onChange: (e) => setNewClientEmail(e.target.value),
1866
- placeholder: t("agenda.appointment.emailOptional"),
1867
- className: "rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1868
- }
1869
- )
1870
- ] }),
1871
- /* @__PURE__ */ jsx(
1872
- "button",
1873
- {
1874
- type: "button",
1875
- disabled: creatingClient || !newClientName.trim(),
1876
- onClick: async () => {
1877
- setCreatingClient(true);
1878
- try {
1879
- const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
1880
- const { getAgendaTenantId: getAgendaTenantId2 } = await import('./tenant-LAUWVUYB.js');
1881
- const supabase = getSupabaseClientOptional4();
1882
- const tenantId = getAgendaTenantId2();
1883
- if (!supabase || !tenantId) throw new Error("Not initialized");
1884
- const { data: personRow, error: personError } = await supabase.from("people").insert({
1885
- tenant_id: tenantId,
1886
- kind: config.clientKind,
1887
- name: newClientName.trim(),
1888
- phone: newClientPhone.trim() || null,
1889
- email: newClientEmail.trim() || null
1890
- }).select("id, name").single();
1891
- if (personError) throw personError;
1892
- const { error: clientError } = await supabase.from("clients").insert({
1893
- person_id: personRow.id,
1894
- tenant_id: tenantId
1895
- });
1896
- if (clientError) {
1897
- await supabase.from("people").delete().eq("id", personRow.id);
1898
- throw clientError;
1899
- }
1900
- setClientId(personRow.id);
1901
- setClientSearch(personRow.name);
1902
- setQuickCreate(false);
1903
- } catch (err) {
1904
- const { toast: toast3 } = await import('@fayz-ai/ui');
1905
- toast3.error(t("agenda.appointment.createClientFailed"), { description: err?.message });
1906
- }
1907
- setCreatingClient(false);
1908
- },
1909
- className: "w-full rounded-md bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset disabled:opacity-40 transition-colors",
1910
- children: creatingClient ? t("agenda.appointment.creating") : t("agenda.appointment.create")
1911
- }
1912
- )
1913
- ] }) })
1563
+ ) })
1914
1564
  ] }),
1915
1565
  activeType.fields.location && config.modules.locationSelection && (storeLocations.length > 0 || config.locations.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
1916
1566
  /* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
@@ -1938,7 +1588,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1938
1588
  step: 5,
1939
1589
  className: "w-10 shrink-0",
1940
1590
  disabled: isPaidBooking,
1941
- tooltip: t("agenda.appointment.duration")
1591
+ tooltip: t2("agenda.appointment.duration")
1942
1592
  }
1943
1593
  ),
1944
1594
  /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "min" })
@@ -1954,7 +1604,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1954
1604
  step: 0.01,
1955
1605
  className: "w-14 shrink-0",
1956
1606
  disabled: isPaidBooking,
1957
- tooltip: t("agenda.appointment.price")
1607
+ tooltip: t2("agenda.appointment.price")
1958
1608
  }
1959
1609
  )
1960
1610
  ] }),
@@ -1967,7 +1617,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1967
1617
  onChange: setServiceSearch,
1968
1618
  onSelect: addService,
1969
1619
  options: serviceOptions,
1970
- placeholder: t("agenda.appointment.searchService"),
1620
+ placeholder: t2("agenda.appointment.searchService"),
1971
1621
  onBlurEmpty: () => {
1972
1622
  },
1973
1623
  onFocus: () => {
@@ -1984,7 +1634,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
1984
1634
  ] });
1985
1635
  },
1986
1636
  minimal: true,
1987
- inlineLabel: services.length > 0 ? t("agenda.appointment.addService") : t("agenda.appointment.addServiceFirst"),
1637
+ inlineLabel: services.length > 0 ? t2("agenda.appointment.addService") : t2("agenda.appointment.addServiceFirst"),
1988
1638
  inlineIcon: services.length > 0 ? "plus" : "search"
1989
1639
  }
1990
1640
  )
@@ -2005,7 +1655,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2005
1655
  disabled: cancellationReasonsLoading,
2006
1656
  className: "w-full rounded-input border border-input bg-card px-2.5 py-1.5 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring",
2007
1657
  children: [
2008
- /* @__PURE__ */ jsx("option", { value: "", children: cancellationReasonsLoading ? t("agenda.cancellation.loadingReasons") : t("agenda.cancellation.selectReason") }),
1658
+ /* @__PURE__ */ jsx("option", { value: "", children: cancellationReasonsLoading ? t2("agenda.cancellation.loadingReasons") : t2("agenda.cancellation.selectReason") }),
2009
1659
  cancellationReasons.map((reason) => /* @__PURE__ */ jsxs("option", { value: reason.id, children: [
2010
1660
  reason.label,
2011
1661
  reason.requiresNotes ? " *" : ""
@@ -2018,7 +1668,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2018
1668
  {
2019
1669
  value: cancellationNotes,
2020
1670
  onChange: (event) => setCancellationNotes(event.target.value),
2021
- placeholder: needsCancellationNotes ? t("agenda.cancellation.notesRequired") : t("agenda.cancellation.notesOptional"),
1671
+ placeholder: needsCancellationNotes ? t2("agenda.cancellation.notesRequired") : t2("agenda.cancellation.notesOptional"),
2022
1672
  rows: 2,
2023
1673
  className: "w-full rounded-input border border-input bg-card px-2.5 py-1.5 text-sm resize-none shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring"
2024
1674
  }
@@ -2033,7 +1683,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2033
1683
  type: "button",
2034
1684
  onClick: () => setShowNotes(true),
2035
1685
  className: "text-sm text-muted-foreground hover:text-foreground transition-colors text-left py-1.5 pl-2",
2036
- children: t("agenda.appointment.addNotes")
1686
+ children: t2("agenda.appointment.addNotes")
2037
1687
  }
2038
1688
  )
2039
1689
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 py-1", children: [
@@ -2043,7 +1693,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2043
1693
  {
2044
1694
  value: notes,
2045
1695
  onChange: (e) => setNotes(e.target.value),
2046
- placeholder: t("agenda.appointment.addDescription"),
1696
+ placeholder: t2("agenda.appointment.addDescription"),
2047
1697
  rows: 2,
2048
1698
  autoFocus: true,
2049
1699
  className: "w-full flex-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-ring"
@@ -2053,14 +1703,14 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2053
1703
  ] })
2054
1704
  ] }),
2055
1705
  modalTab === "appointment" && !clientEditing && /* @__PURE__ */ jsx("div", { className: "px-5 py-2.5 border-t fayz-glass-divider bg-transparent shrink-0 sm:rounded-b-2xl", children: confirmingDelete ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2056
- /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium", children: t("agenda.appointment.deleteConfirm") }),
1706
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium", children: t2("agenda.appointment.deleteConfirm") }),
2057
1707
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2058
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setConfirmingDelete(false), children: t("agenda.appointment.cancel") }),
2059
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", size: "sm", onClick: handleDelete, disabled: deleting, children: deleting ? t("agenda.appointment.deleting") : t("agenda.appointment.confirmDelete") })
1708
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setConfirmingDelete(false), children: t2("agenda.appointment.cancel") }),
1709
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", size: "sm", onClick: handleDelete, disabled: deleting, children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete") })
2060
1710
  ] })
2061
1711
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2062
1712
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2063
- mode === "edit" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "text-xs text-destructive font-medium hover:underline", children: t("agenda.appointment.delete") }),
1713
+ mode === "edit" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "text-xs text-destructive font-medium hover:underline", children: t2("agenda.appointment.delete") }),
2064
1714
  services.length > 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
2065
1715
  /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
2066
1716
  totalDuration,
@@ -2078,7 +1728,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2078
1728
  /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }),
2079
1729
  fmtCurrency(totalPrice, currency.locale, currency.code),
2080
1730
  " \xB7 ",
2081
- t("agenda.payment.paid")
1731
+ t2("agenda.payment.paid")
2082
1732
  ]
2083
1733
  }
2084
1734
  ) : date <= (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) ? /* @__PURE__ */ jsxs(
@@ -2094,7 +1744,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2094
1744
  /* @__PURE__ */ jsx(HandCoins, { className: "h-3 w-3" }),
2095
1745
  fmtCurrency(totalPrice, currency.locale, currency.code),
2096
1746
  " \xB7 ",
2097
- t("agenda.payment.collect")
1747
+ t2("agenda.payment.collect")
2098
1748
  ]
2099
1749
  }
2100
1750
  ) : /* @__PURE__ */ jsxs("span", { children: [
@@ -2107,8 +1757,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2107
1757
  ] })
2108
1758
  ] }),
2109
1759
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2110
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: onClose, className: "hidden sm:inline-flex", children: t("agenda.appointment.cancel") }),
2111
- /* @__PURE__ */ jsx(Button, { type: "submit", variant: "default", size: "sm", disabled: !canSubmit || !isDirty, children: saving ? t("agenda.appointment.saving") : mode === "create" ? t("agenda.appointment.save") : t("agenda.appointment.update") })
1760
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: onClose, className: "hidden sm:inline-flex", children: t2("agenda.appointment.cancel") }),
1761
+ /* @__PURE__ */ jsx(Button, { type: "submit", variant: "default", size: "sm", disabled: !canSubmit || !isDirty, children: saving ? t2("agenda.appointment.saving") : mode === "create" ? t2("agenda.appointment.save") : t2("agenda.appointment.update") })
2112
1762
  ] })
2113
1763
  ] }) })
2114
1764
  ] }),
@@ -2123,7 +1773,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
2123
1773
  className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
2124
1774
  children: [
2125
1775
  /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M10 12L6 8l4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }),
2126
- t("agenda.payment.backToAppointment")
1776
+ t2("agenda.payment.backToAppointment")
2127
1777
  ]
2128
1778
  }
2129
1779
  ) }) })
@@ -2275,7 +1925,7 @@ function PopoverStatusSelect({ value, statuses, bookingStartsAt, onChange }) {
2275
1925
  ] });
2276
1926
  }
2277
1927
  function AppointmentPopover({ booking, position, onClose, onEdit }) {
2278
- const t = useTranslation();
1928
+ const t2 = useTranslation();
2279
1929
  const config = useAgendaConfig();
2280
1930
  const deleteBooking = useAgendaStore((s) => s.deleteBooking);
2281
1931
  const updateStatus = useAgendaStore((s) => s.updateBookingStatus);
@@ -2296,13 +1946,13 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2296
1946
  }
2297
1947
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(FloatingPanel, { position, width: 320, height: 340, onClose, children: (panel) => /* @__PURE__ */ jsxs(Fragment, { children: [
2298
1948
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-0.5 px-3 pt-3 pb-1", children: confirmingDelete ? /* @__PURE__ */ jsxs(Fragment, { children: [
2299
- /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium mr-auto pl-1", children: t("agenda.appointment.deleteConfirm") }),
1949
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium mr-auto pl-1", children: t2("agenda.appointment.deleteConfirm") }),
2300
1950
  /* @__PURE__ */ jsx(
2301
1951
  "button",
2302
1952
  {
2303
1953
  onClick: () => setConfirmingDelete(false),
2304
1954
  className: "rounded px-2 py-0.5 text-[11px] font-medium hover:bg-muted/50 transition-colors",
2305
- children: t("agenda.appointment.cancel")
1955
+ children: t2("agenda.appointment.cancel")
2306
1956
  }
2307
1957
  ),
2308
1958
  /* @__PURE__ */ jsx(
@@ -2311,7 +1961,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2311
1961
  onClick: handleDelete,
2312
1962
  disabled: deleting,
2313
1963
  className: "rounded bg-destructive border border-destructive px-2 py-0.5 text-[11px] font-medium text-destructive-foreground hover:bg-destructive/90 shadow-button-primary active:shadow-button-inset disabled:opacity-40 transition-colors",
2314
- children: deleting ? t("agenda.appointment.deleting") : t("agenda.appointment.confirmDelete")
1964
+ children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete")
2315
1965
  }
2316
1966
  )
2317
1967
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -2320,7 +1970,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2320
1970
  {
2321
1971
  onClick: () => panel.expandThen(() => onEdit(booking.id)),
2322
1972
  className: "p-1.5 rounded-full hover:bg-muted transition-colors",
2323
- title: t("common.edit"),
1973
+ title: t2("common.edit"),
2324
1974
  children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 text-muted-foreground" })
2325
1975
  }
2326
1976
  ),
@@ -2329,7 +1979,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2329
1979
  {
2330
1980
  onClick: () => setConfirmingDelete(true),
2331
1981
  className: "p-1.5 rounded-full hover:bg-destructive/10 transition-colors",
2332
- title: t("common.delete"),
1982
+ title: t2("common.delete"),
2333
1983
  children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 text-muted-foreground hover:text-destructive" })
2334
1984
  }
2335
1985
  ),
@@ -2339,7 +1989,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2339
1989
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2340
1990
  /* @__PURE__ */ jsx("span", { className: "mt-1.5 h-3 w-3 rounded shrink-0", style: { backgroundColor: color } }),
2341
1991
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2342
- config.capabilities.client ? /* @__PURE__ */ jsx(PersonLink, { personId: booking.clientId, name: booking.clientName ?? "Unknown", className: "text-base" }) : /* @__PURE__ */ jsx("p", { className: "text-base font-medium truncate", children: booking.clientName || t("agenda.appointment.untitled") }),
1992
+ config.capabilities.client ? /* @__PURE__ */ jsx(PersonLink, { personId: booking.clientId, name: booking.clientName ?? "Unknown", className: "text-base" }) : /* @__PURE__ */ jsx("p", { className: "text-base font-medium truncate", children: booking.clientName || t2("agenda.appointment.untitled") }),
2343
1993
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mt-0.5", children: [
2344
1994
  fmtDate(booking.startsAt),
2345
1995
  " \xB7 ",
@@ -2377,22 +2027,22 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2377
2027
  const hasFinancial = config.modules.financial && booking.orderId;
2378
2028
  if (!hasFinancial) {
2379
2029
  return /* @__PURE__ */ jsxs("div", { className: "pt-2 border-t fayz-glass-divider flex items-center justify-between text-sm", children: [
2380
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("agenda.appointment.total") }),
2030
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("agenda.appointment.total") }),
2381
2031
  /* @__PURE__ */ jsx("span", { className: "font-medium", children: total })
2382
2032
  ] });
2383
2033
  }
2384
2034
  const badgeConfig = {
2385
- none: { icon: HandCoins, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t("agenda.payment.collect")}` },
2386
- pending: { icon: Clock, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t("agenda.payment.pending")}` },
2387
- partial: { icon: DollarSign, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t("agenda.payment.partial")}` },
2388
- paid: { icon: Check, bg: "bg-success/10 hover:bg-success/15", text: "text-success", label: `${total} \xB7 ${t("agenda.payment.paid")}` },
2389
- overdue: { icon: AlertTriangle, bg: "bg-destructive/10 hover:bg-destructive/15", text: "text-destructive", label: `${total} \xB7 ${t("agenda.payment.overdue")}` },
2390
- cancelled: { icon: X, bg: "bg-muted hover:bg-muted/80", text: "text-muted-foreground", label: `${total} \xB7 ${t("agenda.payment.cancelled")}` }
2035
+ none: { icon: HandCoins, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t2("agenda.payment.collect")}` },
2036
+ pending: { icon: Clock, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t2("agenda.payment.pending")}` },
2037
+ partial: { icon: DollarSign, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t2("agenda.payment.partial")}` },
2038
+ paid: { icon: Check, bg: "bg-success/10 hover:bg-success/15", text: "text-success", label: `${total} \xB7 ${t2("agenda.payment.paid")}` },
2039
+ overdue: { icon: AlertTriangle, bg: "bg-destructive/10 hover:bg-destructive/15", text: "text-destructive", label: `${total} \xB7 ${t2("agenda.payment.overdue")}` },
2040
+ cancelled: { icon: X, bg: "bg-muted hover:bg-muted/80", text: "text-muted-foreground", label: `${total} \xB7 ${t2("agenda.payment.cancelled")}` }
2391
2041
  };
2392
2042
  const cfg = badgeConfig[ps] ?? badgeConfig.none;
2393
2043
  const StatusIcon = cfg.icon;
2394
2044
  return /* @__PURE__ */ jsxs("div", { className: "pt-2 border-t fayz-glass-divider flex items-center justify-between", children: [
2395
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("agenda.appointment.total") }),
2045
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t2("agenda.appointment.total") }),
2396
2046
  /* @__PURE__ */ jsxs(
2397
2047
  "button",
2398
2048
  {
@@ -2464,7 +2114,7 @@ function StatusDot({ color, label, isActive, disabled, onClick }) {
2464
2114
  ] });
2465
2115
  }
2466
2116
  function EventContextMenu({ booking, position, onClose, onEdit }) {
2467
- const t = useTranslation();
2117
+ const t2 = useTranslation();
2468
2118
  const config = useAgendaConfig();
2469
2119
  const updateStatus = useAgendaStore((s) => s.updateBookingStatus);
2470
2120
  const deleteBooking = useAgendaStore((s) => s.deleteBooking);
@@ -2489,19 +2139,19 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
2489
2139
  children: [
2490
2140
  /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 text-muted-foreground" }),
2491
2141
  " ",
2492
- t("agenda.contextMenu.edit")
2142
+ t2("agenda.contextMenu.edit")
2493
2143
  ]
2494
2144
  }
2495
2145
  ),
2496
2146
  confirming ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2", children: [
2497
- /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium", children: t("agenda.appointment.deleteConfirm") }),
2147
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium", children: t2("agenda.appointment.deleteConfirm") }),
2498
2148
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2499
2149
  /* @__PURE__ */ jsx(
2500
2150
  "button",
2501
2151
  {
2502
2152
  onClick: () => setConfirming(false),
2503
2153
  className: "rounded px-2 py-0.5 text-[11px] font-medium hover:bg-muted/50 transition-colors",
2504
- children: t("agenda.appointment.cancel")
2154
+ children: t2("agenda.appointment.cancel")
2505
2155
  }
2506
2156
  ),
2507
2157
  /* @__PURE__ */ jsx(
@@ -2510,7 +2160,7 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
2510
2160
  onClick: handleDelete,
2511
2161
  disabled: deleting,
2512
2162
  className: "rounded bg-destructive border border-destructive px-2 py-0.5 text-[11px] font-medium text-destructive-foreground hover:bg-destructive/90 shadow-button-primary active:shadow-button-inset disabled:opacity-40 transition-colors",
2513
- children: deleting ? t("agenda.appointment.deleting") : t("agenda.appointment.confirmDelete")
2163
+ children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete")
2514
2164
  }
2515
2165
  )
2516
2166
  ] })
@@ -2522,20 +2172,20 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
2522
2172
  children: [
2523
2173
  /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
2524
2174
  " ",
2525
- t("agenda.contextMenu.delete")
2175
+ t2("agenda.contextMenu.delete")
2526
2176
  ]
2527
2177
  }
2528
2178
  )
2529
2179
  ] }),
2530
2180
  config.capabilities.status && /* @__PURE__ */ jsxs("div", { className: "border-t px-4 py-3", children: [
2531
- /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.contextMenu.changeStatus") }),
2181
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t2("agenda.contextMenu.changeStatus") }),
2532
2182
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: config.statuses.map((s) => {
2533
2183
  const available = isStatusAvailable(s, booking.startsAt);
2534
2184
  return /* @__PURE__ */ jsx(
2535
2185
  StatusDot,
2536
2186
  {
2537
2187
  color: s.color,
2538
- label: available ? s.label : `${s.label} ${t("agenda.contextMenu.notAvailable")}`,
2188
+ label: available ? s.label : `${s.label} ${t2("agenda.contextMenu.notAvailable")}`,
2539
2189
  isActive: booking.status === s.value,
2540
2190
  disabled: !available,
2541
2191
  onClick: () => {
@@ -2561,7 +2211,7 @@ function timeLabel(iso) {
2561
2211
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
2562
2212
  }
2563
2213
  function AgendaMobileList({ bookings, statusColors, currencyCode, onSelect }) {
2564
- const t = useTranslation();
2214
+ const t2 = useTranslation();
2565
2215
  const locale = getCurrentLocale() === "pt-BR" ? "pt-BR" : "en";
2566
2216
  const groups = useMemo(() => {
2567
2217
  const todayStart = startOfDay(/* @__PURE__ */ new Date()).getTime();
@@ -2580,12 +2230,12 @@ function AgendaMobileList({ bookings, statusColors, currencyCode, onSelect }) {
2580
2230
  function dayLabel(date) {
2581
2231
  const today = startOfDay(/* @__PURE__ */ new Date());
2582
2232
  const diff = Math.round((startOfDay(date).getTime() - today.getTime()) / 864e5);
2583
- if (diff === 0) return t("agenda.list.today");
2584
- if (diff === 1) return t("agenda.list.tomorrow");
2233
+ if (diff === 0) return t2("agenda.list.today");
2234
+ if (diff === 1) return t2("agenda.list.tomorrow");
2585
2235
  return date.toLocaleDateString(locale, { weekday: "long", day: "numeric", month: "long" });
2586
2236
  }
2587
2237
  if (groups.length === 0) {
2588
- return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-16 text-center text-muted-foreground", children: /* @__PURE__ */ jsx("p", { className: "text-sm", children: t("agenda.list.empty") }) });
2238
+ return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-16 text-center text-muted-foreground", children: /* @__PURE__ */ jsx("p", { className: "text-sm", children: t2("agenda.list.empty") }) });
2589
2239
  }
2590
2240
  return /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-3 py-3 space-y-5", children: groups.map((g) => /* @__PURE__ */ jsxs("div", { children: [
2591
2241
  /* @__PURE__ */ jsx("h3", { className: "px-1 pb-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: dayLabel(g.date) }),
@@ -2641,8 +2291,8 @@ function generateAvailabilityEvents(schedules, professionals, dateRange, busines
2641
2291
  list.push(s);
2642
2292
  byAssignee.set(aid, list);
2643
2293
  }
2644
- function timeToMinutes(t) {
2645
- const [h, m] = t.split(":").map(Number);
2294
+ function timeToMinutes(t2) {
2295
+ const [h, m] = t2.split(":").map(Number);
2646
2296
  return h * 60 + m;
2647
2297
  }
2648
2298
  function minutesToTime(m) {
@@ -2767,7 +2417,7 @@ function useIsMobile() {
2767
2417
  function CalendarView() {
2768
2418
  injectCalendarStyles();
2769
2419
  const config = useAgendaConfig();
2770
- const t = useTranslation();
2420
+ const t2 = useTranslation();
2771
2421
  const currentLocale = getCurrentLocale();
2772
2422
  const fcLocale = currentLocale === "pt-BR" ? ptBrLocale : void 0;
2773
2423
  const calendarRef = useRef(null);
@@ -2865,6 +2515,18 @@ function CalendarView() {
2865
2515
  end.setDate(end.getDate() + 91);
2866
2516
  fetchBookings({ start: start.toISOString(), end: end.toISOString() });
2867
2517
  }, [isMobile, fetchBookings]);
2518
+ useDataChanged({}, () => {
2519
+ if (visibleRange) {
2520
+ fetchBookings(visibleRange);
2521
+ return;
2522
+ }
2523
+ const start = /* @__PURE__ */ new Date();
2524
+ start.setHours(0, 0, 0, 0);
2525
+ start.setDate(start.getDate() - 1);
2526
+ const end = new Date(start);
2527
+ end.setDate(end.getDate() + 91);
2528
+ fetchBookings({ start: start.toISOString(), end: end.toISOString() });
2529
+ }, [visibleRange, fetchBookings]);
2868
2530
  const resources = useMemo(() => {
2869
2531
  const filtered = selectedProfIds.length > 0 ? professionals.filter((p) => selectedProfIds.includes(p.id)) : professionals;
2870
2532
  return filtered.map((p) => ({
@@ -3108,10 +2770,10 @@ function CalendarView() {
3108
2770
  useEffect(() => {
3109
2771
  applyResourceStriping();
3110
2772
  const t1 = setTimeout(applyResourceStriping, 100);
3111
- const t2 = setTimeout(applyResourceStriping, 300);
2773
+ const t22 = setTimeout(applyResourceStriping, 300);
3112
2774
  return () => {
3113
2775
  clearTimeout(t1);
3114
- clearTimeout(t2);
2776
+ clearTimeout(t22);
3115
2777
  };
3116
2778
  }, [applyResourceStriping, currentView, visibleRange, bookings]);
3117
2779
  useEffect(() => {
@@ -3199,7 +2861,7 @@ function CalendarView() {
3199
2861
  className: "flex items-center gap-2 rounded-button bg-primary text-primary-foreground border border-primary pl-3 pr-4 py-1.5 text-sm font-semibold hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-all mb-6 w-fit",
3200
2862
  children: [
3201
2863
  /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
3202
- t("agenda.sidebar.create")
2864
+ t2("agenda.sidebar.create")
3203
2865
  ]
3204
2866
  }
3205
2867
  ),
@@ -3274,7 +2936,7 @@ function CalendarView() {
3274
2936
  {
3275
2937
  onClick: handleToday,
3276
2938
  className: "rounded-md border px-3 py-1 text-sm font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors",
3277
- children: t("agenda.toolbar.today")
2939
+ children: t2("agenda.toolbar.today")
3278
2940
  }
3279
2941
  ),
3280
2942
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
@@ -3282,7 +2944,7 @@ function CalendarView() {
3282
2944
  /* @__PURE__ */ jsx("button", { onClick: handleNext, className: "flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted transition-colors", children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
3283
2945
  ] })
3284
2946
  ] }),
3285
- /* @__PURE__ */ jsx("h1", { className: "text-base sm:text-lg font-normal truncate", children: isMobile && !mobileGridActive ? t("agenda.calendar.viewAgenda") : calendarTitle })
2947
+ /* @__PURE__ */ jsx("h1", { className: "text-base sm:text-lg font-normal truncate", children: isMobile && !mobileGridActive ? t2("agenda.calendar.viewAgenda") : calendarTitle })
3286
2948
  ] }),
3287
2949
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
3288
2950
  /* @__PURE__ */ jsx("div", { className: "hidden md:flex items-center gap-1", children: viewOptions.map((opt) => /* @__PURE__ */ jsx(
@@ -3290,7 +2952,7 @@ function CalendarView() {
3290
2952
  {
3291
2953
  onClick: () => handleViewChange(opt.key),
3292
2954
  className: `rounded-md px-3 py-1 text-sm transition-colors ${currentView === opt.key ? "bg-primary/10 text-primary font-medium" : "text-muted-foreground hover:bg-muted/50"}`,
3293
- children: t(opt.labelKey)
2955
+ children: t2(opt.labelKey)
3294
2956
  },
3295
2957
  opt.key
3296
2958
  )) }),
@@ -3312,7 +2974,7 @@ function CalendarView() {
3312
2974
  {
3313
2975
  onClick: () => handleMobileViewChange(opt.key),
3314
2976
  className: `shrink-0 rounded-md px-3 py-1 text-sm transition-colors ${mobileView === opt.key ? "bg-primary/10 text-primary font-medium" : "text-muted-foreground hover:bg-muted/50"}`,
3315
- children: t(opt.labelKey)
2977
+ children: t2(opt.labelKey)
3316
2978
  },
3317
2979
  opt.key
3318
2980
  )) })
@@ -3403,9 +3065,9 @@ function CalendarView() {
3403
3065
  {
3404
3066
  open: !!scheduleConfirm,
3405
3067
  variant: "warning",
3406
- title: t("agenda.calendar.noScheduleConfirm"),
3407
- confirmLabel: t("common.confirm"),
3408
- cancelLabel: t("common.cancel"),
3068
+ title: t2("agenda.calendar.noScheduleConfirm"),
3069
+ confirmLabel: t2("common.confirm"),
3070
+ cancelLabel: t2("common.cancel"),
3409
3071
  onConfirm: () => {
3410
3072
  const cb = scheduleConfirm?.onConfirm;
3411
3073
  setScheduleConfirm(null);
@@ -3452,10 +3114,19 @@ function CalendarView() {
3452
3114
  )
3453
3115
  ] });
3454
3116
  }
3117
+
3118
+ // src/lib/tenant.ts
3119
+ var t;
3120
+ function setAgendaTenantId(id) {
3121
+ t = id;
3122
+ }
3123
+ function getAgendaTenantId() {
3124
+ return t;
3125
+ }
3455
3126
  function AgendaPage({ config, provider: provider2, store }) {
3456
3127
  const runtime = usePluginRuntimeOptional();
3457
3128
  const tenantId = runtime?.context.tenant?.id;
3458
- React12.useEffect(() => {
3129
+ React13.useEffect(() => {
3459
3130
  setAgendaTenantId(tenantId);
3460
3131
  return () => setAgendaTenantId(void 0);
3461
3132
  }, [tenantId]);
@@ -3570,8 +3241,10 @@ async function hydrateCanonicalBookings(core, bookings) {
3570
3241
  locationId: booking.location_id,
3571
3242
  metadata: booking.metadata ?? {},
3572
3243
  clientId: booking.party_id,
3573
- // Simple events (no party) fall back to the title stored in metadata.
3574
- clientName: client?.name ?? booking.metadata?.title ?? null,
3244
+ // Simple events (no party) fall back to the title stored in metadata;
3245
+ // partyless blocks/tasks (e.g. imported Google events) get their notes or
3246
+ // a kind label so the calendar never renders an unnamed chip.
3247
+ clientName: client?.name ?? booking.metadata?.title ?? (booking.kind === "appointment" ? null : booking.notes || (booking.kind === "task" ? "Tarefa" : "Bloqueio")),
3575
3248
  clientPhone: client?.phone ?? null,
3576
3249
  clientEmail: client?.email ?? null,
3577
3250
  clientAvatarUrl: client?.avatar_url ?? null,
@@ -3611,7 +3284,8 @@ async function getCanonicalBookingById(core, id) {
3611
3284
  const [booking] = await hydrateCanonicalBookings(core, [data]);
3612
3285
  return booking ?? null;
3613
3286
  }
3614
- function createSupabaseAgendaProvider() {
3287
+ function createSupabaseAgendaProvider(providerOptions) {
3288
+ const professionalKind = providerOptions?.professionalKind ?? "staff";
3615
3289
  return {
3616
3290
  // v_appointments is in public schema (PostgREST default)
3617
3291
  async getBookings(query) {
@@ -3632,7 +3306,26 @@ function createSupabaseAgendaProvider() {
3632
3306
  const { core, pub } = getClients();
3633
3307
  const { data, error } = await pub.from("v_appointments").select("*").eq("id", id).maybeSingle();
3634
3308
  if (error) throw error;
3635
- if (data) return mapBooking(data);
3309
+ if (data) {
3310
+ const booking = mapBooking(data);
3311
+ if (booking.orderId) {
3312
+ const { data: items } = await core.from("order_items").select("id, service_id, name, duration_minutes, unit_price, assignee_id, sort_order").eq("order_id", booking.orderId).order("sort_order", { ascending: true });
3313
+ if (items && items.length) {
3314
+ const services = items.map((item) => ({
3315
+ id: item.id,
3316
+ serviceId: item.service_id,
3317
+ name: item.name,
3318
+ durationMinutes: item.duration_minutes,
3319
+ price: item.unit_price,
3320
+ assigneeId: item.assignee_id
3321
+ }));
3322
+ booking.services = services;
3323
+ const itemDuration = services.reduce((sum, s) => sum + (s.durationMinutes ?? 0), 0);
3324
+ if (itemDuration) booking.totalDurationMinutes = itemDuration;
3325
+ }
3326
+ }
3327
+ return booking;
3328
+ }
3636
3329
  return getCanonicalBookingById(core, id);
3637
3330
  },
3638
3331
  async createBooking(input) {
@@ -3655,7 +3348,7 @@ function createSupabaseAgendaProvider() {
3655
3348
  kind: input.kind ?? "appointment",
3656
3349
  status: "scheduled",
3657
3350
  party_id: input.clientId || null,
3658
- assignee_id: input.professionalId,
3351
+ assignee_id: input.professionalId || null,
3659
3352
  location_id: input.locationId ?? null,
3660
3353
  subtotal: totalPrice,
3661
3354
  total: totalPrice,
@@ -3672,7 +3365,7 @@ function createSupabaseAgendaProvider() {
3672
3365
  tenant_id: tenantId,
3673
3366
  kind: input.kind ?? "appointment",
3674
3367
  party_id: input.clientId || null,
3675
- assignee_id: input.professionalId,
3368
+ assignee_id: input.professionalId || null,
3676
3369
  location_id: input.locationId ?? null,
3677
3370
  order_id: order.id,
3678
3371
  starts_at: input.startsAt,
@@ -3697,7 +3390,7 @@ function createSupabaseAgendaProvider() {
3697
3390
  total: s.price,
3698
3391
  sort_order: i,
3699
3392
  duration_minutes: s.durationMinutes,
3700
- assignee_id: s.assigneeId ?? input.professionalId
3393
+ assignee_id: s.assigneeId || input.professionalId || null
3701
3394
  }));
3702
3395
  const { error: oiErr } = await core.from("order_items").insert(orderItems);
3703
3396
  if (oiErr) throw oiErr;
@@ -3777,9 +3470,12 @@ function createSupabaseAgendaProvider() {
3777
3470
  const { data: booking, error: bookingErr } = await core.from("appointments").select("order_id").eq("id", id).single();
3778
3471
  if (bookingErr) throw bookingErr;
3779
3472
  const orderId = booking?.order_id;
3780
- const { count, error: movementErr } = orderId ? await pub.from("plg_financial_movements").select("id", { count: "exact", head: true }).eq("invoice_id", orderId) : { count: 0, error: null };
3781
- if (movementErr) throw movementErr;
3782
- if (orderId && (count ?? 0) > 0) {
3473
+ let movementCount = 0;
3474
+ if (orderId) {
3475
+ const { count, error: movementErr } = await pub.from("plg_financial_movements").select("id", { count: "exact", head: true }).eq("invoice_id", orderId);
3476
+ if (!movementErr) movementCount = count ?? 0;
3477
+ }
3478
+ if (orderId && movementCount > 0) {
3783
3479
  await pub.from("plg_financial_movements").update({ status: "cancelled" }).eq("invoice_id", orderId);
3784
3480
  await core.from("orders").update({ status: "cancelled" }).eq("id", orderId);
3785
3481
  const { error } = await core.from("appointments").update({ status: "cancelled" }).eq("id", id);
@@ -3856,7 +3552,7 @@ function createSupabaseAgendaProvider() {
3856
3552
  },
3857
3553
  async getProfessionals() {
3858
3554
  const { core } = getClients();
3859
- const { data, error } = await core.from("people").select("id, name, avatar_url, is_active").eq("kind", "staff").eq("is_active", true).order("name");
3555
+ const { data, error } = await core.from("people").select("id, name, avatar_url, is_active").eq("kind", professionalKind).eq("is_active", true).order("name");
3860
3556
  if (error) throw error;
3861
3557
  return (data ?? []).map((p) => ({
3862
3558
  id: p.id,
@@ -4138,40 +3834,49 @@ var HOUR_OPTIONS = Array.from({ length: 18 }, (_, i) => {
4138
3834
  const h = (5 + i).toString().padStart(2, "0");
4139
3835
  return { value: `${h}:00`, label: `${h}:00` };
4140
3836
  });
3837
+ var AGENDA_TOGGLE_DEFAULTS = {
3838
+ conflictDetection: true,
3839
+ dragDrop: true,
3840
+ autoCreateOrder: true,
3841
+ enableLocation: false,
3842
+ enableWorkingHours: true,
3843
+ blockOutside: false,
3844
+ enableConfirmations: true,
3845
+ autoConfirm: false,
3846
+ reminder: true,
3847
+ showCancelled: false,
3848
+ showNoShow: true,
3849
+ compact: false,
3850
+ newBookingNotif: true,
3851
+ cancelAlerts: true,
3852
+ noShowTracking: false
3853
+ };
4141
3854
  function AgendaGeneralSettings() {
4142
- const t = useTranslation();
3855
+ const t2 = useTranslation();
4143
3856
  const { startTime, endTime, setStartTime, setEndTime } = useAgendaSettings();
3857
+ const s = useTenantPluginSettings("agenda", AGENDA_TOGGLE_DEFAULTS);
4144
3858
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
4145
- /* @__PURE__ */ jsxs(SettingsGroup, { title: t("agenda.settings.scheduling"), description: t("agenda.settings.schedulingDesc"), children: [
4146
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.conflictDetection"), description: t("agenda.settings.conflictDetectionDesc"), checked: true, onChange: () => {
4147
- } }),
4148
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.dragDrop"), description: t("agenda.settings.dragDropDesc"), checked: true, onChange: () => {
4149
- } }),
4150
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.autoCreateOrder"), description: t("agenda.settings.autoCreateOrderDesc"), checked: true, onChange: () => {
4151
- } })
3859
+ /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("agenda.settings.scheduling"), description: t2("agenda.settings.schedulingDesc"), children: [
3860
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.conflictDetection"), description: t2("agenda.settings.conflictDetectionDesc"), checked: s.get("conflictDetection"), onChange: (v) => s.set("conflictDetection", v) }),
3861
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.dragDrop"), description: t2("agenda.settings.dragDropDesc"), checked: s.get("dragDrop"), onChange: (v) => s.set("dragDrop", v) }),
3862
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.autoCreateOrder"), description: t2("agenda.settings.autoCreateOrderDesc"), checked: s.get("autoCreateOrder"), onChange: (v) => s.set("autoCreateOrder", v) })
4152
3863
  ] }),
4153
- /* @__PURE__ */ jsx(SettingsGroup, { title: t("agenda.settings.locations"), description: t("agenda.settings.locationsDesc"), children: /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.enableLocation"), description: t("agenda.settings.enableLocationDesc"), checked: false, onChange: () => {
4154
- } }) }),
4155
- /* @__PURE__ */ jsxs(SettingsGroup, { title: t("agenda.settings.workingHours"), description: t("agenda.settings.workingHoursDesc"), children: [
4156
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.enableWorkingHours"), description: t("agenda.settings.enableWorkingHoursDesc"), checked: true, onChange: () => {
4157
- } }),
4158
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.blockOutside"), description: t("agenda.settings.blockOutsideDesc"), checked: false, onChange: () => {
4159
- } })
3864
+ /* @__PURE__ */ jsx(SettingsGroup, { title: t2("agenda.settings.locations"), description: t2("agenda.settings.locationsDesc"), children: /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.enableLocation"), description: t2("agenda.settings.enableLocationDesc"), checked: s.get("enableLocation"), onChange: (v) => s.set("enableLocation", v) }) }),
3865
+ /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("agenda.settings.workingHours"), description: t2("agenda.settings.workingHoursDesc"), children: [
3866
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.enableWorkingHours"), description: t2("agenda.settings.enableWorkingHoursDesc"), checked: s.get("enableWorkingHours"), onChange: (v) => s.set("enableWorkingHours", v) }),
3867
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.blockOutside"), description: t2("agenda.settings.blockOutsideDesc"), checked: s.get("blockOutside"), onChange: (v) => s.set("blockOutside", v) })
4160
3868
  ] }),
4161
- /* @__PURE__ */ jsxs(SettingsGroup, { title: t("agenda.settings.confirmations"), description: t("agenda.settings.confirmationsDesc"), children: [
4162
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.enableConfirmations"), description: t("agenda.settings.enableConfirmationsDesc"), checked: true, onChange: () => {
4163
- } }),
4164
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.autoConfirm"), description: t("agenda.settings.autoConfirmDesc"), checked: false, onChange: () => {
4165
- } }),
4166
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.reminder"), description: t("agenda.settings.reminderDesc"), checked: true, onChange: () => {
4167
- } })
3869
+ /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("agenda.settings.confirmations"), description: t2("agenda.settings.confirmationsDesc"), children: [
3870
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.enableConfirmations"), description: t2("agenda.settings.enableConfirmationsDesc"), checked: s.get("enableConfirmations"), onChange: (v) => s.set("enableConfirmations", v) }),
3871
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.autoConfirm"), description: t2("agenda.settings.autoConfirmDesc"), checked: s.get("autoConfirm"), onChange: (v) => s.set("autoConfirm", v) }),
3872
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.reminder"), description: t2("agenda.settings.reminderDesc"), checked: s.get("reminder"), onChange: (v) => s.set("reminder", v) })
4168
3873
  ] }),
4169
- /* @__PURE__ */ jsxs(SettingsGroup, { title: t("agenda.settings.calendarDisplay"), description: t("agenda.settings.calendarDisplayDesc"), children: [
3874
+ /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("agenda.settings.calendarDisplay"), description: t2("agenda.settings.calendarDisplayDesc"), children: [
4170
3875
  /* @__PURE__ */ jsx(
4171
3876
  SelectRow,
4172
3877
  {
4173
- label: t("agenda.settings.dayStartTime"),
4174
- description: t("agenda.settings.dayStartTimeDesc"),
3878
+ label: t2("agenda.settings.dayStartTime"),
3879
+ description: t2("agenda.settings.dayStartTimeDesc"),
4175
3880
  value: startTime,
4176
3881
  options: HOUR_OPTIONS,
4177
3882
  onChange: setStartTime
@@ -4180,27 +3885,21 @@ function AgendaGeneralSettings() {
4180
3885
  /* @__PURE__ */ jsx(
4181
3886
  SelectRow,
4182
3887
  {
4183
- label: t("agenda.settings.dayEndTime"),
4184
- description: t("agenda.settings.dayEndTimeDesc"),
3888
+ label: t2("agenda.settings.dayEndTime"),
3889
+ description: t2("agenda.settings.dayEndTimeDesc"),
4185
3890
  value: endTime,
4186
3891
  options: HOUR_OPTIONS,
4187
3892
  onChange: setEndTime
4188
3893
  }
4189
3894
  ),
4190
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.showCancelled"), description: t("agenda.settings.showCancelledDesc"), checked: false, onChange: () => {
4191
- } }),
4192
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.showNoShow"), description: t("agenda.settings.showNoShowDesc"), checked: true, onChange: () => {
4193
- } }),
4194
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.compact"), description: t("agenda.settings.compactDesc"), checked: false, onChange: () => {
4195
- } })
3895
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.showCancelled"), description: t2("agenda.settings.showCancelledDesc"), checked: s.get("showCancelled"), onChange: (v) => s.set("showCancelled", v) }),
3896
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.showNoShow"), description: t2("agenda.settings.showNoShowDesc"), checked: s.get("showNoShow"), onChange: (v) => s.set("showNoShow", v) }),
3897
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.compact"), description: t2("agenda.settings.compactDesc"), checked: s.get("compact"), onChange: (v) => s.set("compact", v) })
4196
3898
  ] }),
4197
- /* @__PURE__ */ jsxs(SettingsGroup, { title: t("agenda.settings.notifications"), description: t("agenda.settings.notificationsDesc"), children: [
4198
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.newBookingNotif"), description: t("agenda.settings.newBookingNotifDesc"), checked: true, onChange: () => {
4199
- } }),
4200
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.cancelAlerts"), description: t("agenda.settings.cancelAlertsDesc"), checked: true, onChange: () => {
4201
- } }),
4202
- /* @__PURE__ */ jsx(ToggleRow, { label: t("agenda.settings.noShowTracking"), description: t("agenda.settings.noShowTrackingDesc"), checked: false, onChange: () => {
4203
- } })
3899
+ /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("agenda.settings.notifications"), description: t2("agenda.settings.notificationsDesc"), children: [
3900
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.newBookingNotif"), description: t2("agenda.settings.newBookingNotifDesc"), checked: s.get("newBookingNotif"), onChange: (v) => s.set("newBookingNotif", v) }),
3901
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.cancelAlerts"), description: t2("agenda.settings.cancelAlertsDesc"), checked: s.get("cancelAlerts"), onChange: (v) => s.set("cancelAlerts", v) }),
3902
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("agenda.settings.noShowTracking"), description: t2("agenda.settings.noShowTrackingDesc"), checked: s.get("noShowTracking"), onChange: (v) => s.set("noShowTracking", v) })
4204
3903
  ] })
4205
3904
  ] });
4206
3905
  }
@@ -4236,6 +3935,7 @@ var en = {
4236
3935
  "agenda.appointment.searchService": "Search service to add...",
4237
3936
  "agenda.appointment.selectLocation": "Select location...",
4238
3937
  "agenda.appointment.selectProfessional": "Select professional...",
3938
+ "agenda.appointment.selectClientHint": "Select the client from the list or use New client to enable saving.",
4239
3939
  "agenda.appointment.services": "Services",
4240
3940
  "agenda.appointment.status": "Status",
4241
3941
  "agenda.appointment.to": "to",
@@ -4456,6 +4156,7 @@ var ptBR = {
4456
4156
  "agenda.appointment.searchService": "Buscar servi\xE7o para adicionar...",
4457
4157
  "agenda.appointment.selectLocation": "Selecionar local...",
4458
4158
  "agenda.appointment.selectProfessional": "Selecionar profissional...",
4159
+ "agenda.appointment.selectClientHint": "Selecione o cliente na lista ou use Novo cliente para habilitar o salvamento.",
4459
4160
  "agenda.appointment.services": "Servi\xE7os",
4460
4161
  "agenda.appointment.status": "Status",
4461
4162
  "agenda.appointment.to": "at\xE9",
@@ -5010,7 +4711,7 @@ function createFinancialBridge(provider2) {
5010
4711
  let typeNames = {};
5011
4712
  if (typeIds.length > 0) {
5012
4713
  const { data: types } = await supabase.from("plg_financial_payment_method_types").select("id, name").in("id", typeIds);
5013
- typeNames = Object.fromEntries((types ?? []).map((t) => [t.id, t.name]));
4714
+ typeNames = Object.fromEntries((types ?? []).map((t2) => [t2.id, t2.name]));
5014
4715
  }
5015
4716
  const movements = (movRes.data ?? []).map((m) => ({
5016
4717
  id: String(m.id),
@@ -5111,7 +4812,7 @@ function createFinancialBridge(provider2) {
5111
4812
  },
5112
4813
  async getPaymentMethodTypes() {
5113
4814
  const types = await provider2.getPaymentMethodTypes();
5114
- return types.map((t) => ({ id: t.id, name: t.name, transactionType: t.transactionType }));
4815
+ return types.map((t2) => ({ id: t2.id, name: t2.name, transactionType: t2.transactionType }));
5115
4816
  },
5116
4817
  async getBankAccounts() {
5117
4818
  const accounts = await provider2.getBankAccounts();
@@ -5153,6 +4854,65 @@ function mapInvoiceStatus(status) {
5153
4854
  return "pending";
5154
4855
  }
5155
4856
  }
4857
+
4858
+ // src/integrations/google-calendar/mapping.ts
4859
+ function bookingSummary(b) {
4860
+ const services = (b.services ?? []).map((s) => s.name).filter(Boolean).join(", ");
4861
+ const who = b.clientName ?? "";
4862
+ if (services && who) return `${services} \u2014 ${who}`;
4863
+ return services || who || "Appointment";
4864
+ }
4865
+ function bookingToEvent2(b, timeZone) {
4866
+ if (!b.startsAt || !b.endsAt) return null;
4867
+ const notes = [b.notes, b.professionalName ? `Pro: ${b.professionalName}` : "", b.locationName ?? ""].filter(Boolean).join("\n");
4868
+ return {
4869
+ summary: bookingSummary(b),
4870
+ description: notes || void 0,
4871
+ start: { dateTime: b.startsAt, timeZone },
4872
+ end: { dateTime: b.endsAt, timeZone }
4873
+ };
4874
+ }
4875
+ function eventToBookingPatch(ev) {
4876
+ if (ev.status === "cancelled") return { status: "cancelled" };
4877
+ const t2 = eventTimes(ev);
4878
+ return { startsAt: t2.startsAt, endsAt: t2.endsAt };
4879
+ }
4880
+ function eventTimes(ev) {
4881
+ const allDay = !ev.start?.dateTime && !!ev.start?.date;
4882
+ const startsAt = ev.start?.dateTime ?? ev.start?.date ?? "";
4883
+ const endsAt = ev.end?.dateTime ?? ev.end?.date ?? startsAt;
4884
+ return { startsAt, endsAt, allDay };
4885
+ }
4886
+ function channelRowToChannel(r) {
4887
+ return {
4888
+ id: r.id,
4889
+ integrationId: r.integration_id,
4890
+ tenantId: r.tenant_id,
4891
+ googleCalendarId: r.google_calendar_id,
4892
+ summary: r.summary ?? null,
4893
+ color: r.color ?? null,
4894
+ direction: r.direction,
4895
+ targetKind: r.target_kind ?? null,
4896
+ targetId: r.target_id ?? null,
4897
+ importMode: r.import_mode ?? "appointment",
4898
+ syncToken: r.sync_token ?? null,
4899
+ channelId: r.channel_id ?? null,
4900
+ resourceId: r.resource_id ?? null,
4901
+ channelExpiresAt: r.channel_expires_at ?? null,
4902
+ isActive: r.is_active ?? true,
4903
+ createdAt: r.created_at ?? "",
4904
+ updatedAt: r.updated_at ?? ""
4905
+ };
4906
+ }
4907
+
4908
+ // src/integrations/google-calendar/data/tables.ts
4909
+ var GCAL_TABLES = {
4910
+ integrations: "plg_calendar_integrations",
4911
+ channels: "plg_calendar_channels",
4912
+ syncLog: "plg_calendar_sync_log"
4913
+ };
4914
+
4915
+ // src/integrations/google-calendar/data/supabase.ts
5156
4916
  var FUNCTION = "google-calendar-sync";
5157
4917
  function sb() {
5158
4918
  const supabase = getSupabaseClientOptional();
@@ -5162,7 +4922,7 @@ function sb() {
5162
4922
  function createGoogleCalendarProvider() {
5163
4923
  return {
5164
4924
  async getIntegration() {
5165
- const { data } = await sb().from("calendar_integrations").select("*").eq("provider", "google").limit(1).maybeSingle();
4925
+ const { data } = await sb().from(GCAL_TABLES.integrations).select("*").eq("provider", "google").limit(1).maybeSingle();
5166
4926
  if (!data) return null;
5167
4927
  return {
5168
4928
  id: data.id,
@@ -5175,24 +4935,24 @@ function createGoogleCalendarProvider() {
5175
4935
  },
5176
4936
  async getConnectUrl(redirectTo) {
5177
4937
  const { data, error } = await sb().functions.invoke(FUNCTION, {
5178
- body: { action: "oauth_start", redirectTo: redirectTo ?? window.location.href }
4938
+ body: { action: "oauth_start", tenantId: getActiveTenantId(), redirectTo: redirectTo ?? window.location.href }
5179
4939
  });
5180
4940
  if (error) throw new Error(error.message);
5181
4941
  return data.url;
5182
4942
  },
5183
4943
  async setCalendar(calendarId) {
5184
- await sb().from("calendar_integrations").update({ calendar_id: calendarId, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("provider", "google");
4944
+ await sb().from(GCAL_TABLES.integrations).update({ calendar_id: calendarId, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("provider", "google");
5185
4945
  },
5186
4946
  async disconnect() {
5187
- await sb().from("calendar_integrations").update({ active: false, oauth_refresh_token: null, oauth_access_token: null }).eq("provider", "google");
4947
+ await sb().from(GCAL_TABLES.integrations).update({ active: false, oauth_refresh_token: null, oauth_access_token: null }).eq("provider", "google");
5188
4948
  },
5189
4949
  async syncNow() {
5190
- const { data, error } = await sb().functions.invoke(FUNCTION, { body: { action: "pull_events", trigger: "manual" } });
4950
+ const { data, error } = await sb().functions.invoke(FUNCTION, { body: { action: "pull_events", tenantId: getActiveTenantId(), trigger: "manual" } });
5191
4951
  if (error) throw new Error(error.message);
5192
4952
  return data;
5193
4953
  },
5194
4954
  async getSyncLog() {
5195
- const { data } = await sb().from("calendar_sync_log").select("*").order("created_at", { ascending: false }).limit(20);
4955
+ const { data } = await sb().from(GCAL_TABLES.syncLog).select("*").order("created_at", { ascending: false }).limit(20);
5196
4956
  return (data ?? []).map((r) => ({
5197
4957
  id: r.id,
5198
4958
  direction: r.direction,
@@ -5203,32 +4963,523 @@ function createGoogleCalendarProvider() {
5203
4963
  error: r.error ?? void 0,
5204
4964
  createdAt: r.created_at
5205
4965
  }));
4966
+ },
4967
+ async listGoogleCalendars() {
4968
+ const { data, error } = await sb().functions.invoke(FUNCTION, { body: { action: "list_calendars", tenantId: getActiveTenantId() } });
4969
+ if (error) throw new Error(error.message);
4970
+ return data.calendars ?? [];
4971
+ },
4972
+ async listChannels() {
4973
+ const { data } = await sb().from(GCAL_TABLES.channels).select("*").order("created_at", { ascending: true });
4974
+ return (data ?? []).map(channelRowToChannel);
4975
+ },
4976
+ async saveChannel(input) {
4977
+ const tenantId = getActiveTenantId();
4978
+ let integrationId = input.integrationId;
4979
+ if (!integrationId) {
4980
+ const { data } = await sb().from(GCAL_TABLES.integrations).select("id").eq("provider", "google").limit(1).maybeSingle();
4981
+ integrationId = data?.id;
4982
+ }
4983
+ const row = {
4984
+ integration_id: integrationId,
4985
+ tenant_id: tenantId,
4986
+ google_calendar_id: input.googleCalendarId,
4987
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
4988
+ };
4989
+ if (input.id !== void 0) row.id = input.id;
4990
+ if (input.summary !== void 0) row.summary = input.summary;
4991
+ if (input.color !== void 0) row.color = input.color;
4992
+ if (input.direction !== void 0) row.direction = input.direction;
4993
+ if (input.targetKind !== void 0) row.target_kind = input.targetKind;
4994
+ if (input.targetId !== void 0) row.target_id = input.targetId;
4995
+ if (input.importMode !== void 0) row.import_mode = input.importMode;
4996
+ if (input.isActive !== void 0) row.is_active = input.isActive;
4997
+ const { error } = await sb().from(GCAL_TABLES.channels).upsert(row, { onConflict: "integration_id,google_calendar_id" });
4998
+ if (error) throw new Error(error.message);
4999
+ },
5000
+ async deleteChannel(id) {
5001
+ const { error } = await sb().from(GCAL_TABLES.channels).delete().eq("id", id);
5002
+ if (error) throw new Error(error.message);
5003
+ },
5004
+ async listExternalEvents(params) {
5005
+ const { data, error } = await sb().functions.invoke(FUNCTION, {
5006
+ body: { action: "list_external_events", tenantId: getActiveTenantId(), channelId: params?.channelId, timeMin: params?.timeMin }
5007
+ });
5008
+ if (error) throw new Error(error.message);
5009
+ return data.events ?? [];
5010
+ },
5011
+ async importEvents(items) {
5012
+ const { data, error } = await sb().functions.invoke(FUNCTION, {
5013
+ body: { action: "import_events", tenantId: getActiveTenantId(), items }
5014
+ });
5015
+ if (error) throw new Error(error.message);
5016
+ return data;
5206
5017
  }
5207
5018
  };
5208
5019
  }
5020
+ function CalendarColorDot({
5021
+ color,
5022
+ className = ""
5023
+ }) {
5024
+ return /* @__PURE__ */ jsx(
5025
+ "span",
5026
+ {
5027
+ className: `inline-block h-2.5 w-2.5 shrink-0 rounded-full border border-black/10 ${className}`,
5028
+ style: { backgroundColor: color || "hsl(var(--muted-foreground))" },
5029
+ "aria-hidden": true
5030
+ }
5031
+ );
5032
+ }
5033
+ var EMPTY = { assignee: [], service: [], location: [] };
5034
+ function useAgendaLookups(enabled) {
5035
+ const [lookups, setLookups] = useState(EMPTY);
5036
+ const [loading, setLoading] = useState(false);
5037
+ useEffect(() => {
5038
+ let cancelled = false;
5039
+ setLoading(true);
5040
+ void (async () => {
5041
+ const agenda = createSupabaseAgendaProvider();
5042
+ const tenantId = getActiveTenantId();
5043
+ const publicProvider = tenantId ? createSupabasePublicBookingProvider({ tenantId }) : null;
5044
+ const [assignee, location, service] = await Promise.all([
5045
+ agenda.getProfessionals().then(
5046
+ (rows) => rows.map((p) => ({ id: p.id, name: p.name })),
5047
+ () => []
5048
+ ),
5049
+ (agenda.getLocations?.() ?? Promise.resolve([])).then(
5050
+ (rows) => rows.map((l) => ({ id: l.id, name: l.name })),
5051
+ () => []
5052
+ ),
5053
+ (publicProvider?.getServices() ?? Promise.resolve([])).then(
5054
+ (rows) => rows.map((s) => ({ id: s.id, name: s.name })),
5055
+ () => []
5056
+ )
5057
+ ]);
5058
+ if (cancelled) return;
5059
+ setLookups({ assignee, service, location });
5060
+ setLoading(false);
5061
+ })();
5062
+ return () => {
5063
+ cancelled = true;
5064
+ };
5065
+ }, [enabled]);
5066
+ const hasAny = lookups.assignee.length > 0 || lookups.service.length > 0 || lookups.location.length > 0;
5067
+ return { lookups, loading, hasAny };
5068
+ }
5069
+ function optionsForKind(lookups, kind) {
5070
+ if (kind === "assignee") return lookups.assignee;
5071
+ if (kind === "service") return lookups.service;
5072
+ if (kind === "location") return lookups.location;
5073
+ return [];
5074
+ }
5075
+ var DIRECTION_LABELS = {
5076
+ bidirectional: "Two-way",
5077
+ inbound: "Import only",
5078
+ outbound: "Export only",
5079
+ off: "Off"
5080
+ };
5081
+ var IMPORT_MODE_LABELS = {
5082
+ block: "Block",
5083
+ appointment: "Appointment"
5084
+ };
5085
+ var TARGET_KIND_LABELS = {
5086
+ agenda: "Whole agenda",
5087
+ assignee: "Professional",
5088
+ service: "Service",
5089
+ location: "Location"
5090
+ };
5091
+ var NO_TARGET = "__none__";
5092
+ function defaultChannel() {
5093
+ return { direction: "bidirectional", targetKind: null, targetId: null, importMode: "block" };
5094
+ }
5095
+ function ChannelMappingList({
5096
+ provider: provider2,
5097
+ onChannelsChange
5098
+ }) {
5099
+ const [calendars, setCalendars] = useState([]);
5100
+ const [byGcalId, setByGcalId] = useState({});
5101
+ const [loading, setLoading] = useState(true);
5102
+ const [error, setError] = useState(null);
5103
+ const [savingId, setSavingId] = useState(null);
5104
+ const { lookups, hasAny: hasLookups } = useAgendaLookups(true);
5105
+ const load = useCallback(async () => {
5106
+ setLoading(true);
5107
+ setError(null);
5108
+ try {
5109
+ const [cals, channels] = await Promise.all([
5110
+ provider2.listGoogleCalendars(),
5111
+ provider2.listChannels()
5112
+ ]);
5113
+ setCalendars(cals);
5114
+ const map = {};
5115
+ for (const ch of channels) map[ch.googleCalendarId] = ch;
5116
+ setByGcalId(map);
5117
+ onChannelsChange?.(channels);
5118
+ } catch (e) {
5119
+ setError(e?.message ?? "Could not load Google calendars");
5120
+ } finally {
5121
+ setLoading(false);
5122
+ }
5123
+ }, [provider2, onChannelsChange]);
5124
+ useEffect(() => {
5125
+ void load();
5126
+ }, [load]);
5127
+ const persist = useCallback(
5128
+ async (cal, patch) => {
5129
+ const existing = byGcalId[cal.id];
5130
+ const base = existing ?? { ...defaultChannel(), isActive: true };
5131
+ setSavingId(cal.id);
5132
+ try {
5133
+ await provider2.saveChannel({
5134
+ id: existing?.id,
5135
+ googleCalendarId: cal.id,
5136
+ summary: cal.summary,
5137
+ color: cal.backgroundColor ?? null,
5138
+ direction: patch.direction ?? base.direction,
5139
+ targetKind: patch.targetKind !== void 0 ? patch.targetKind : base.targetKind,
5140
+ targetId: patch.targetId !== void 0 ? patch.targetId : base.targetId ?? null,
5141
+ importMode: patch.importMode ?? base.importMode,
5142
+ isActive: patch.isActive !== void 0 ? patch.isActive : base.isActive
5143
+ });
5144
+ const channels = await provider2.listChannels();
5145
+ const map = {};
5146
+ for (const ch of channels) map[ch.googleCalendarId] = ch;
5147
+ setByGcalId(map);
5148
+ onChannelsChange?.(channels);
5149
+ } catch (e) {
5150
+ toast.error(e?.message ?? "Could not save calendar mapping");
5151
+ } finally {
5152
+ setSavingId(null);
5153
+ }
5154
+ },
5155
+ [byGcalId, provider2, onChannelsChange]
5156
+ );
5157
+ if (loading) {
5158
+ return /* @__PURE__ */ jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin inline" }) });
5159
+ }
5160
+ if (error) {
5161
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-warning/40 bg-warning-soft/40 px-3 py-3 text-sm", children: [
5162
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-warning-soft-foreground", children: [
5163
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
5164
+ " ",
5165
+ error
5166
+ ] }),
5167
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => void load(), children: [
5168
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-3.5 w-3.5" }),
5169
+ " Retry"
5170
+ ] })
5171
+ ] });
5172
+ }
5173
+ if (calendars.length === 0) {
5174
+ return /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: "No Google calendars found on this account." });
5175
+ }
5176
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border divide-y", children: [
5177
+ calendars.map((cal) => {
5178
+ const ch = byGcalId[cal.id];
5179
+ const active = !!ch?.isActive;
5180
+ const saving = savingId === cal.id;
5181
+ const direction = ch?.direction ?? "bidirectional";
5182
+ const targetKind = ch?.targetKind ?? null;
5183
+ const importMode = ch?.importMode ?? "block";
5184
+ const canImport = direction === "inbound" || direction === "bidirectional";
5185
+ const kindOptions = optionsForKind(lookups, targetKind);
5186
+ return /* @__PURE__ */ jsxs("div", { className: "px-3 py-3 space-y-3", children: [
5187
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
5188
+ /* @__PURE__ */ jsx(
5189
+ Checkbox,
5190
+ {
5191
+ checked: active,
5192
+ disabled: saving,
5193
+ onChange: (next) => void persist(cal, { isActive: next })
5194
+ }
5195
+ ),
5196
+ /* @__PURE__ */ jsx(CalendarColorDot, { color: cal.backgroundColor }),
5197
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: cal.summary }),
5198
+ cal.primary && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "shrink-0", children: "Primary" }),
5199
+ saving && /* @__PURE__ */ jsx(Loader2, { className: "ml-auto h-3.5 w-3.5 animate-spin text-muted-foreground" })
5200
+ ] }),
5201
+ active && /* @__PURE__ */ jsxs("div", { className: "grid gap-2 pl-[26px] sm:grid-cols-2 lg:grid-cols-4", children: [
5202
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
5203
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: "Direction" }),
5204
+ /* @__PURE__ */ jsxs(
5205
+ Select,
5206
+ {
5207
+ value: direction,
5208
+ onValueChange: (v) => void persist(cal, { direction: v }),
5209
+ children: [
5210
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5211
+ /* @__PURE__ */ jsx(SelectContent, { children: Object.keys(DIRECTION_LABELS).map((d) => /* @__PURE__ */ jsx(SelectItem, { value: d, children: DIRECTION_LABELS[d] }, d)) })
5212
+ ]
5213
+ }
5214
+ )
5215
+ ] }),
5216
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
5217
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: "Target" }),
5218
+ /* @__PURE__ */ jsxs(
5219
+ Select,
5220
+ {
5221
+ value: targetKind ?? "agenda",
5222
+ onValueChange: (v) => void persist(cal, {
5223
+ targetKind: v === "agenda" ? null : v,
5224
+ // Reset the id whenever the kind changes.
5225
+ targetId: null
5226
+ }),
5227
+ children: [
5228
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5229
+ /* @__PURE__ */ jsx(SelectContent, { children: Object.keys(TARGET_KIND_LABELS).map(
5230
+ (k) => /* @__PURE__ */ jsx(SelectItem, { value: k, children: TARGET_KIND_LABELS[k] }, k)
5231
+ ) })
5232
+ ]
5233
+ }
5234
+ )
5235
+ ] }),
5236
+ targetKind && /* @__PURE__ */ jsxs("label", { className: "block", children: [
5237
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: TARGET_KIND_LABELS[targetKind] }),
5238
+ kindOptions.length > 0 ? /* @__PURE__ */ jsxs(
5239
+ Select,
5240
+ {
5241
+ value: ch?.targetId ?? NO_TARGET,
5242
+ onValueChange: (v) => void persist(cal, { targetId: v === NO_TARGET ? null : v }),
5243
+ children: [
5244
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select\u2026" }) }),
5245
+ /* @__PURE__ */ jsx(SelectContent, { children: kindOptions.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.id, children: o.name }, o.id)) })
5246
+ ]
5247
+ }
5248
+ ) : /* @__PURE__ */ jsx(
5249
+ "input",
5250
+ {
5251
+ defaultValue: ch?.targetId ?? "",
5252
+ placeholder: "uuid",
5253
+ onBlur: (e) => void persist(cal, { targetId: e.target.value.trim() || null }),
5254
+ className: "w-full mt-1 h-8 rounded-input border border-input bg-card px-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
5255
+ }
5256
+ )
5257
+ ] }),
5258
+ canImport && /* @__PURE__ */ jsxs("label", { className: "block", children: [
5259
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: "Import as" }),
5260
+ /* @__PURE__ */ jsxs(
5261
+ Select,
5262
+ {
5263
+ value: importMode,
5264
+ onValueChange: (v) => void persist(cal, { importMode: v }),
5265
+ children: [
5266
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5267
+ /* @__PURE__ */ jsx(SelectContent, { children: Object.keys(IMPORT_MODE_LABELS).map((m) => /* @__PURE__ */ jsx(SelectItem, { value: m, children: IMPORT_MODE_LABELS[m] }, m)) })
5268
+ ]
5269
+ }
5270
+ )
5271
+ ] })
5272
+ ] })
5273
+ ] }, cal.id);
5274
+ }),
5275
+ !hasLookups && /* @__PURE__ */ jsx("p", { className: "px-3 py-2 text-[11px] text-muted-foreground", children: "No professional/service/location lookups available \u2014 target ids fall back to manual uuid entry." })
5276
+ ] });
5277
+ }
5278
+ function formatWhen(ev) {
5279
+ const start = new Date(ev.startsAt);
5280
+ if (ev.allDay) {
5281
+ return start.toLocaleDateString("pt-BR", { day: "2-digit", month: "short", year: "numeric" });
5282
+ }
5283
+ return start.toLocaleString("pt-BR", {
5284
+ day: "2-digit",
5285
+ month: "short",
5286
+ hour: "2-digit",
5287
+ minute: "2-digit"
5288
+ });
5289
+ }
5290
+ function ImportEventsWizard({
5291
+ provider: provider2,
5292
+ channels,
5293
+ onClose,
5294
+ onImported
5295
+ }) {
5296
+ const [events, setEvents] = useState([]);
5297
+ const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
5298
+ const [loading, setLoading] = useState(true);
5299
+ const [error, setError] = useState(null);
5300
+ const [importing, setImporting] = useState(false);
5301
+ const [result, setResult] = useState(null);
5302
+ const channelById = useMemo(() => {
5303
+ const m = {};
5304
+ for (const c of channels) m[c.id] = c;
5305
+ return m;
5306
+ }, [channels]);
5307
+ const load = useCallback(async () => {
5308
+ setLoading(true);
5309
+ setError(null);
5310
+ setResult(null);
5311
+ try {
5312
+ const rows = await provider2.listExternalEvents();
5313
+ setEvents(rows);
5314
+ setSelected(new Set(rows.map((r) => r.eventId)));
5315
+ } catch (e) {
5316
+ setError(e?.message ?? "Could not load upcoming events");
5317
+ } finally {
5318
+ setLoading(false);
5319
+ }
5320
+ }, [provider2]);
5321
+ useEffect(() => {
5322
+ void load();
5323
+ }, [load]);
5324
+ const toggle = (eventId) => setSelected((prev) => {
5325
+ const next = new Set(prev);
5326
+ if (next.has(eventId)) next.delete(eventId);
5327
+ else next.add(eventId);
5328
+ return next;
5329
+ });
5330
+ const allSelected = events.length > 0 && selected.size === events.length;
5331
+ const toggleAll = () => setSelected(allSelected ? /* @__PURE__ */ new Set() : new Set(events.map((e) => e.eventId)));
5332
+ async function handleImport() {
5333
+ const items = events.filter((e) => selected.has(e.eventId)).map((e) => ({ channelId: e.channelId, eventId: e.eventId }));
5334
+ if (items.length === 0) return;
5335
+ setImporting(true);
5336
+ try {
5337
+ const res = await provider2.importEvents(items);
5338
+ setResult(res);
5339
+ onImported?.(res);
5340
+ toast.success(`Imported ${res.imported} \xB7 skipped ${res.skipped}`);
5341
+ await load();
5342
+ } catch (e) {
5343
+ toast.error(e?.message ?? "Import failed");
5344
+ } finally {
5345
+ setImporting(false);
5346
+ }
5347
+ }
5348
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-card", children: [
5349
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-2", children: [
5350
+ /* @__PURE__ */ jsx(CalendarPlus, { className: "h-4 w-4 text-muted-foreground" }),
5351
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Import future events" }),
5352
+ /* @__PURE__ */ jsx(
5353
+ "button",
5354
+ {
5355
+ onClick: onClose,
5356
+ className: "ml-auto rounded p-1 text-muted-foreground hover:bg-muted/50",
5357
+ "aria-label": "Close",
5358
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
5359
+ }
5360
+ )
5361
+ ] }),
5362
+ loading ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin inline" }) }) : error ? /* @__PURE__ */ jsxs("div", { className: "p-3", children: [
5363
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-warning-soft-foreground", children: [
5364
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
5365
+ " ",
5366
+ error
5367
+ ] }),
5368
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => void load(), children: [
5369
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-3.5 w-3.5" }),
5370
+ " Retry"
5371
+ ] })
5372
+ ] }) : events.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "py-8 px-4 text-center space-y-1", children: [
5373
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "You're all caught up" }),
5374
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No upcoming Google events left to import on your active calendars." }),
5375
+ result && /* @__PURE__ */ jsxs("p", { className: "text-xs text-success", children: [
5376
+ "Imported ",
5377
+ result.imported,
5378
+ " \xB7 skipped ",
5379
+ result.skipped,
5380
+ result.errors.length > 0 ? ` \xB7 ${result.errors.length} errors` : ""
5381
+ ] })
5382
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5383
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 border-b px-3 py-2", children: [
5384
+ /* @__PURE__ */ jsx(Checkbox, { checked: allSelected, onChange: toggleAll }),
5385
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
5386
+ selected.size,
5387
+ " of ",
5388
+ events.length,
5389
+ " selected"
5390
+ ] })
5391
+ ] }),
5392
+ /* @__PURE__ */ jsx("div", { className: "max-h-[320px] overflow-y-auto divide-y", children: events.map((ev) => {
5393
+ const ch = channelById[ev.channelId];
5394
+ return /* @__PURE__ */ jsxs(
5395
+ "label",
5396
+ {
5397
+ className: "flex items-start gap-2.5 px-3 py-2.5 cursor-pointer hover:bg-muted/30",
5398
+ children: [
5399
+ /* @__PURE__ */ jsx(
5400
+ Checkbox,
5401
+ {
5402
+ checked: selected.has(ev.eventId),
5403
+ onChange: () => toggle(ev.eventId)
5404
+ }
5405
+ ),
5406
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
5407
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5408
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: ev.summary || "(no title)" }),
5409
+ ev.allDay && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "shrink-0", children: "All-day" })
5410
+ ] }),
5411
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
5412
+ /* @__PURE__ */ jsx("span", { children: formatWhen(ev) }),
5413
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
5414
+ /* @__PURE__ */ jsx(CalendarColorDot, { color: ch?.color }),
5415
+ ch?.summary ?? ev.calendarId
5416
+ ] })
5417
+ ] })
5418
+ ] })
5419
+ ]
5420
+ },
5421
+ ev.eventId
5422
+ );
5423
+ }) }),
5424
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t px-3 py-2", children: [
5425
+ result && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
5426
+ "Last run: ",
5427
+ result.imported,
5428
+ " imported",
5429
+ result.errors.length > 0 ? `, ${result.errors.length} errors` : ""
5430
+ ] }),
5431
+ /* @__PURE__ */ jsxs(
5432
+ Button,
5433
+ {
5434
+ size: "sm",
5435
+ className: "ml-auto",
5436
+ disabled: importing || selected.size === 0,
5437
+ onClick: handleImport,
5438
+ children: [
5439
+ importing ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5" }),
5440
+ "Import ",
5441
+ selected.size > 0 ? selected.size : ""
5442
+ ]
5443
+ }
5444
+ )
5445
+ ] })
5446
+ ] })
5447
+ ] });
5448
+ }
5209
5449
  var provider = createGoogleCalendarProvider();
5450
+ function inboundChannels(channels) {
5451
+ return channels.filter(
5452
+ (c) => c.isActive && (c.direction === "inbound" || c.direction === "bidirectional")
5453
+ );
5454
+ }
5210
5455
  function GoogleCalendarExtraPanel() {
5211
5456
  const [connected, setConnected] = useState(false);
5212
- const [calendarId, setCalendarId] = useState("primary");
5457
+ const [lastSyncAt, setLastSyncAt] = useState();
5213
5458
  const [syncing, setSyncing] = useState(false);
5214
5459
  const [log, setLog] = useState([]);
5215
5460
  const [loaded, setLoaded] = useState(false);
5461
+ const [channels, setChannels] = useState([]);
5462
+ const [showMapping, setShowMapping] = useState(false);
5463
+ const [showWizard, setShowWizard] = useState(false);
5216
5464
  async function load() {
5217
5465
  const integ = await provider.getIntegration();
5218
5466
  setConnected(!!integ?.connected);
5219
5467
  if (integ) {
5220
- setCalendarId(integ.calendarId);
5468
+ setLastSyncAt(integ.lastSyncAt);
5221
5469
  void provider.getSyncLog().then(setLog);
5470
+ void provider.listChannels().then((ch) => {
5471
+ setChannels(ch);
5472
+ if (ch.length > 0) setShowMapping(true);
5473
+ });
5222
5474
  }
5223
5475
  setLoaded(true);
5224
5476
  }
5225
5477
  useEffect(() => {
5226
5478
  void load();
5227
5479
  }, []);
5228
- if (!loaded || !connected) return null;
5229
- async function handleSaveCalendar() {
5230
- await provider.setCalendar(calendarId);
5231
- toast.success("Saved");
5480
+ if (!loaded) return null;
5481
+ if (!connected) {
5482
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground border-t pt-4", children: "Connect a Google account above to map your calendars and import events." });
5232
5483
  }
5233
5484
  async function handleSyncNow() {
5234
5485
  setSyncing(true);
@@ -5236,48 +5487,81 @@ function GoogleCalendarExtraPanel() {
5236
5487
  const r = await provider.syncNow();
5237
5488
  toast.success(`Synced \u2014 ${r.written} updated`);
5238
5489
  void provider.getSyncLog().then(setLog);
5490
+ void provider.getIntegration().then((i) => setLastSyncAt(i?.lastSyncAt));
5239
5491
  } catch (e) {
5240
5492
  toast.error(e?.message ?? "Sync failed");
5241
5493
  } finally {
5242
5494
  setSyncing(false);
5243
5495
  }
5244
5496
  }
5245
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4 border-t pt-4", children: [
5246
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3", children: [
5247
- /* @__PURE__ */ jsxs("label", { className: "block flex-1 min-w-[200px]", children: [
5248
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Calendar ID" }),
5249
- /* @__PURE__ */ jsx(
5250
- "input",
5251
- {
5252
- value: calendarId,
5253
- onChange: (e) => setCalendarId(e.target.value),
5254
- placeholder: "primary",
5255
- className: "w-full mt-1 rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
5256
- }
5257
- )
5497
+ const canImport = inboundChannels(channels).length > 0;
5498
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-5 border-t pt-4", children: [
5499
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
5500
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5501
+ /* @__PURE__ */ jsx(CalendarCog, { className: "h-4 w-4 text-muted-foreground" }),
5502
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Calendars" })
5258
5503
  ] }),
5259
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleSaveCalendar, children: "Save" }),
5260
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleSyncNow, disabled: syncing, children: [
5261
- /* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${syncing ? "animate-spin" : ""}` }),
5262
- " Sync now"
5504
+ showMapping ? /* @__PURE__ */ jsx(ChannelMappingList, { provider, onChannelsChange: setChannels }) : /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed px-4 py-5 text-center space-y-2", children: [
5505
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Map each Google calendar to what it syncs \u2014 a professional, service, location, or your whole agenda." }),
5506
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowMapping(true), children: [
5507
+ /* @__PURE__ */ jsx(CalendarCog, { className: "h-3.5 w-3.5" }),
5508
+ " Configure calendars"
5509
+ ] })
5263
5510
  ] })
5264
5511
  ] }),
5265
- log.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
5266
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5267
- /* @__PURE__ */ jsx(History, { className: "h-4 w-4 text-muted-foreground" }),
5268
- /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "History" })
5512
+ showMapping && /* @__PURE__ */ jsx("section", { className: "space-y-2", children: showWizard ? /* @__PURE__ */ jsx(
5513
+ ImportEventsWizard,
5514
+ {
5515
+ provider,
5516
+ channels: inboundChannels(channels),
5517
+ onClose: () => setShowWizard(false),
5518
+ onImported: () => {
5519
+ void provider.getSyncLog().then(setLog);
5520
+ void provider.getIntegration().then((i) => setLastSyncAt(i?.lastSyncAt));
5521
+ }
5522
+ }
5523
+ ) : /* @__PURE__ */ jsxs(
5524
+ Button,
5525
+ {
5526
+ variant: "outline",
5527
+ size: "sm",
5528
+ disabled: !canImport,
5529
+ title: canImport ? void 0 : "Enable an importing calendar first",
5530
+ onClick: () => setShowWizard(true),
5531
+ children: [
5532
+ /* @__PURE__ */ jsx(CalendarPlus, { className: "h-3.5 w-3.5" }),
5533
+ " Import future events"
5534
+ ]
5535
+ }
5536
+ ) }),
5537
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
5538
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
5539
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleSyncNow, disabled: syncing, children: [
5540
+ /* @__PURE__ */ jsx(RefreshCw, { className: `h-3.5 w-3.5 ${syncing ? "animate-spin" : ""}` }),
5541
+ " Sync now"
5542
+ ] }),
5543
+ lastSyncAt && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
5544
+ "Last sync: ",
5545
+ new Date(lastSyncAt).toLocaleString()
5546
+ ] })
5269
5547
  ] }),
5270
- /* @__PURE__ */ jsx("div", { className: "rounded-md border divide-y text-sm", children: log.map((r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [
5271
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: new Date(r.createdAt).toLocaleString() }),
5272
- /* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide px-1 py-px rounded bg-muted text-muted-foreground", children: r.direction }),
5273
- /* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs", children: [
5274
- r.written,
5275
- " written / ",
5276
- r.fetched,
5277
- " fetched"
5548
+ log.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
5549
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
5550
+ /* @__PURE__ */ jsx(History, { className: "h-4 w-4 text-muted-foreground" }),
5551
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "History" })
5278
5552
  ] }),
5279
- r.status === "success" ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
5280
- ] }, r.id)) })
5553
+ /* @__PURE__ */ jsx("div", { className: "rounded-md border divide-y text-sm", children: log.map((r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [
5554
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: new Date(r.createdAt).toLocaleString() }),
5555
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide px-1 py-px rounded bg-muted text-muted-foreground", children: r.direction }),
5556
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs", children: [
5557
+ r.written,
5558
+ " written / ",
5559
+ r.fetched,
5560
+ " fetched"
5561
+ ] }),
5562
+ r.status === "success" ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
5563
+ ] }, r.id)) })
5564
+ ] })
5281
5565
  ] })
5282
5566
  ] });
5283
5567
  }
@@ -5312,31 +5596,6 @@ var googleCalendarConnector = {
5312
5596
  ]
5313
5597
  };
5314
5598
 
5315
- // src/integrations/google-calendar/mapping.ts
5316
- function bookingSummary(b) {
5317
- const services = (b.services ?? []).map((s) => s.name).filter(Boolean).join(", ");
5318
- const who = b.clientName ?? "";
5319
- if (services && who) return `${services} \u2014 ${who}`;
5320
- return services || who || "Appointment";
5321
- }
5322
- function bookingToEvent2(b, timeZone) {
5323
- if (!b.startsAt || !b.endsAt) return null;
5324
- const notes = [b.notes, b.professionalName ? `Pro: ${b.professionalName}` : "", b.locationName ?? ""].filter(Boolean).join("\n");
5325
- return {
5326
- summary: bookingSummary(b),
5327
- description: notes || void 0,
5328
- start: { dateTime: b.startsAt, timeZone },
5329
- end: { dateTime: b.endsAt, timeZone }
5330
- };
5331
- }
5332
- function eventToBookingPatch(ev) {
5333
- if (ev.status === "cancelled") return { status: "cancelled" };
5334
- return {
5335
- startsAt: ev.start?.dateTime,
5336
- endsAt: ev.end?.dateTime
5337
- };
5338
- }
5339
-
5340
5599
  // src/integrations/google-calendar/index.ts
5341
5600
  function createGoogleCalendarPlugin() {
5342
5601
  return {
@@ -5358,7 +5617,7 @@ function createAgendaPlugin(options) {
5358
5617
  const config = resolveConfig(options);
5359
5618
  registerTranslations(agendaLocales);
5360
5619
  const provider2 = options?.dataProvider ?? createSafeDataProvider(
5361
- () => createSupabaseAgendaProvider(),
5620
+ () => createSupabaseAgendaProvider({ professionalKind: config.professionalKind }),
5362
5621
  () => createMockAgendaProvider()
5363
5622
  );
5364
5623
  const store = createAgendaStore(provider2, options?.financialBridge);
@@ -5378,7 +5637,7 @@ function createAgendaPlugin(options) {
5378
5637
  return locations;
5379
5638
  } : void 0
5380
5639
  });
5381
- const PageComponent = () => React12.createElement(AgendaPage, { config, provider: provider2, store });
5640
+ const PageComponent = () => React13.createElement(AgendaPage, { config, provider: provider2, store });
5382
5641
  return {
5383
5642
  id: "agenda",
5384
5643
  name: config.labels.pageTitle,
@@ -5395,6 +5654,48 @@ function createAgendaPlugin(options) {
5395
5654
  { id: "appointments", label: config.labels.pageTitle, group: config.labels.pageTitle },
5396
5655
  { id: "agenda.schedules", label: "Schedule Management", group: config.labels.pageTitle }
5397
5656
  ],
5657
+ // Recurring monthly quota — counts appointment rows created this month.
5658
+ // (Clients/patients are the app's CRUD entity, so their cap is declared by
5659
+ // the app's EntityDef.limitKey, not here.)
5660
+ declaredLimits: [
5661
+ { key: "bookings_month", label: "Appointments this month", table: "appointments", period: "month" }
5662
+ ],
5663
+ queryEntities: [
5664
+ {
5665
+ key: "agenda:appointments",
5666
+ entity: {
5667
+ name: "Appointment",
5668
+ namePlural: "Appointments",
5669
+ icon: "Calendar",
5670
+ permission: { feature: "appointments", action: "read" },
5671
+ // Keys are the PROVIDER's output shape (camelCase) — what filters/
5672
+ // dateField/groupBy match against, not the raw DB columns.
5673
+ fields: [
5674
+ { key: "startsAt", label: "Starts at", type: "text" },
5675
+ { key: "endsAt", label: "Ends at", type: "text" },
5676
+ { key: "status", label: "Status", type: "text" },
5677
+ { key: "clientName", label: "Client", type: "text", searchable: true },
5678
+ { key: "professionalName", label: "Professional", type: "text", searchable: true },
5679
+ { key: "orderTotal", label: "Order total", type: "number" },
5680
+ { key: "totalDurationMinutes", label: "Duration (min)", type: "number" },
5681
+ { key: "createdAt", label: "Created at", type: "text" }
5682
+ ],
5683
+ data: {
5684
+ table: "v_appointments",
5685
+ tenantScoped: true,
5686
+ searchColumns: ["client_name", "professional_name"]
5687
+ }
5688
+ }
5689
+ }
5690
+ ],
5691
+ declaredRpcs: [
5692
+ {
5693
+ name: "agent_agenda_create_appointment",
5694
+ kind: "write",
5695
+ description: "Guarded appointment create: agent_guard (role\u2192plan\u2192bookings_month cap), working hours, race-safe conflict check, audited.",
5696
+ audits: true
5697
+ }
5698
+ ],
5398
5699
  navigation: [
5399
5700
  {
5400
5701
  section: options?.navSection ?? "main",
@@ -5409,7 +5710,9 @@ function createAgendaPlugin(options) {
5409
5710
  {
5410
5711
  path: "/agenda",
5411
5712
  component: PageComponent,
5412
- permission: { feature: "appointments", action: "read" }
5713
+ permission: { feature: "appointments", action: "read" },
5714
+ // Matches the ref agent_agenda_create_appointment returns.
5715
+ entityRoutes: ["schedule:appointment"]
5413
5716
  }
5414
5717
  ],
5415
5718
  widgets: [],
@@ -5438,20 +5741,22 @@ function createAgendaPlugin(options) {
5438
5741
  {
5439
5742
  id: "agenda.create-appointment",
5440
5743
  name: "createAppointment",
5441
- description: "Creates a new appointment for a client with a specific professional and service.",
5744
+ description: "Creates a new appointment. Takes IDS, not names: resolve the client/service/professional ids first via findAnything/searchRecords, then call this with an ISO start time in the tenant timezone. The user confirms before booking.",
5442
5745
  icon: "CalendarPlus",
5443
5746
  mode: "persist",
5747
+ limitKey: "bookings_month",
5748
+ execution: { plane: "server", kind: "rpc", rpc: "agent_agenda_create_appointment" },
5444
5749
  category: "Scheduling",
5445
5750
  parameters: {
5446
5751
  type: "object",
5447
5752
  properties: {
5448
- client: { type: "string", description: "Client name" },
5449
- professional: { type: "string", description: "Professional name" },
5450
- service: { type: "string", description: "Service name" },
5451
- date: { type: "string", description: "Date (YYYY-MM-DD)" },
5452
- time: { type: "string", description: "Time (HH:MM)" }
5753
+ client_id: { type: "string", description: "Client id (from a search)" },
5754
+ service_id: { type: "string", description: "Service id (from a search)" },
5755
+ professional_id: { type: "string", description: "Professional id (optional \u2014 auto-assigned when omitted)" },
5756
+ starts_at: { type: "string", description: "LOCAL date-time of the business, NO timezone suffix \u2014 e.g. '2026-07-27T10:00' for 10h. The system applies the tenant timezone." },
5757
+ notes: { type: "string", description: "Optional notes" }
5453
5758
  },
5454
- required: ["client", "professional", "service", "date", "time"]
5759
+ required: ["client_id", "service_id", "starts_at"]
5455
5760
  },
5456
5761
  suggestions: [
5457
5762
  { label: "Book a haircut for Sarah tomorrow at 10am" }
@@ -5489,9 +5794,9 @@ function createAgendaPlugin(options) {
5489
5794
  icon: "Calendar",
5490
5795
  // Standard layout: General | Integrations (Google Calendar addon) | Properties.
5491
5796
  component: (() => {
5492
- const AgendaSettingsTab = () => React12.createElement(PluginSettingsPanel, {
5797
+ const AgendaSettingsTab = () => React13.createElement(PluginSettingsPanel, {
5493
5798
  title: "Agenda",
5494
- generalSettings: React12.createElement(AgendaGeneralSettings),
5799
+ generalSettings: React13.createElement(AgendaGeneralSettings),
5495
5800
  registries,
5496
5801
  hostPluginId: "agenda",
5497
5802
  routeBase: "/settings/agenda"