@fayz-ai/plugin-agenda 0.1.8 → 0.2.1

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 (68) hide show
  1. package/dist/chunk-GAJVIEXM.cjs +870 -0
  2. package/dist/chunk-GAJVIEXM.cjs.map +1 -0
  3. package/dist/chunk-U6VTLOHN.js +867 -0
  4. package/dist/chunk-U6VTLOHN.js.map +1 -0
  5. package/dist/components/AgendaMobileList.d.ts +10 -0
  6. package/dist/components/AgendaMobileList.d.ts.map +1 -0
  7. package/dist/components/AppointmentModal.d.ts +4 -2
  8. package/dist/components/AppointmentModal.d.ts.map +1 -1
  9. package/dist/components/AppointmentPopover.d.ts.map +1 -1
  10. package/dist/components/EventContextMenu.d.ts.map +1 -1
  11. package/dist/config.d.ts +62 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/data/index.d.ts +1 -0
  14. package/dist/data/index.d.ts.map +1 -1
  15. package/dist/data/mock.d.ts +15 -1
  16. package/dist/data/mock.d.ts.map +1 -1
  17. package/dist/data/supabase.d.ts.map +1 -1
  18. package/dist/index.cjs +611 -1157
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +445 -994
  23. package/dist/index.js.map +1 -1
  24. package/dist/locales/en.d.ts.map +1 -1
  25. package/dist/locales/pt-BR.d.ts.map +1 -1
  26. package/dist/public/BookingWidget.d.ts +9 -0
  27. package/dist/public/BookingWidget.d.ts.map +1 -0
  28. package/dist/public/context.d.ts +37 -0
  29. package/dist/public/context.d.ts.map +1 -0
  30. package/dist/public/createPublicBookingPlugin.d.ts +45 -0
  31. package/dist/public/createPublicBookingPlugin.d.ts.map +1 -0
  32. package/dist/public/hooks.d.ts +32 -0
  33. package/dist/public/hooks.d.ts.map +1 -0
  34. package/dist/public/index.cjs +1020 -0
  35. package/dist/public/index.cjs.map +1 -0
  36. package/dist/public/index.d.ts +9 -0
  37. package/dist/public/index.d.ts.map +1 -0
  38. package/dist/public/index.js +1012 -0
  39. package/dist/public/index.js.map +1 -0
  40. package/dist/public/types.d.ts +51 -0
  41. package/dist/public/types.d.ts.map +1 -0
  42. package/dist/store.d.ts +3 -0
  43. package/dist/store.d.ts.map +1 -1
  44. package/dist/types.d.ts +8 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/views/CalendarView.d.ts.map +1 -1
  47. package/package.json +11 -8
  48. package/src/components/AgendaMobileList.tsx +103 -0
  49. package/src/components/AppointmentModal.tsx +86 -9
  50. package/src/components/AppointmentPopover.tsx +20 -10
  51. package/src/components/EventContextMenu.tsx +3 -0
  52. package/src/config.ts +104 -5
  53. package/src/data/index.ts +1 -0
  54. package/src/data/mock.ts +26 -6
  55. package/src/data/supabase.ts +13 -2
  56. package/src/index.ts +11 -3
  57. package/src/integrations/google-calendar/integration.test.ts +1 -1
  58. package/src/locales/en.ts +12 -0
  59. package/src/locales/pt-BR.ts +12 -0
  60. package/src/public/BookingWidget.tsx +892 -0
  61. package/src/public/context.tsx +43 -0
  62. package/src/public/createPublicBookingPlugin.ts +155 -0
  63. package/src/public/hooks.ts +117 -0
  64. package/src/public/index.ts +15 -0
  65. package/src/public/types.ts +63 -0
  66. package/src/store.ts +4 -2
  67. package/src/types.ts +8 -0
  68. package/src/views/CalendarView.tsx +220 -54
package/dist/index.cjs CHANGED
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var chunkGAJVIEXM_cjs = require('./chunk-GAJVIEXM.cjs');
3
4
  var chunkSYDBDI3Q_cjs = require('./chunk-SYDBDI3Q.cjs');
4
- var React11 = require('react');
5
+ var React12 = require('react');
5
6
  var core = require('@fayz-ai/core');
6
7
  var saas = require('@fayz-ai/saas');
7
8
  var FullCalendar = require('@fullcalendar/react');
@@ -19,7 +20,7 @@ var vanilla = require('zustand/vanilla');
19
20
 
20
21
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
22
 
22
- var React11__default = /*#__PURE__*/_interopDefault(React11);
23
+ var React12__default = /*#__PURE__*/_interopDefault(React12);
23
24
  var FullCalendar__default = /*#__PURE__*/_interopDefault(FullCalendar);
24
25
  var dayGridPlugin__default = /*#__PURE__*/_interopDefault(dayGridPlugin);
25
26
  var timeGridPlugin__default = /*#__PURE__*/_interopDefault(timeGridPlugin);
@@ -80,23 +81,47 @@ var DEFAULT_BOOKING_TYPES = [
80
81
  requiresClient: false
81
82
  }
82
83
  ];
84
+ var SIMPLE_EVENT_BOOKING_TYPES = [
85
+ {
86
+ id: "event",
87
+ label: "Event",
88
+ icon: "Calendar",
89
+ color: "#6366f1",
90
+ fields: { title: true, client: false, professional: false, services: false, location: false, status: false },
91
+ requiresServices: false,
92
+ requiresClient: false
93
+ }
94
+ ];
83
95
  function resolveConfig(options) {
96
+ const simple = options?.eventMode === "simple";
97
+ const bookingTypes = options?.bookingTypes ?? (simple ? SIMPLE_EVENT_BOOKING_TYPES : DEFAULT_BOOKING_TYPES);
98
+ const capabilities = {
99
+ title: bookingTypes.some((bt) => bt.fields.title === true),
100
+ client: bookingTypes.some((bt) => bt.fields.client),
101
+ professional: bookingTypes.some((bt) => bt.fields.professional),
102
+ services: bookingTypes.some((bt) => bt.fields.services),
103
+ location: bookingTypes.some((bt) => bt.fields.location),
104
+ status: bookingTypes.some((bt) => bt.fields.status)
105
+ };
84
106
  return {
85
107
  modules: {
86
- workingHours: options?.modules?.workingHours !== false,
87
- confirmations: options?.modules?.confirmations !== false,
88
- conflictDetection: options?.modules?.conflictDetection !== false,
108
+ // Off-unless-explicit in simple mode (`?? !simple`); default-on in full.
109
+ workingHours: options?.modules?.workingHours ?? !simple,
110
+ confirmations: options?.modules?.confirmations ?? !simple,
111
+ conflictDetection: options?.modules?.conflictDetection ?? !simple,
89
112
  dragAndDrop: options?.modules?.dragAndDrop !== false,
90
113
  locationSelection: options?.modules?.locationSelection === true,
91
114
  financial: options?.modules?.financial ?? !!options?.financialBridge
92
115
  },
116
+ simpleMode: simple,
117
+ capabilities,
93
118
  labels: { ...DEFAULT_LABELS, ...options?.labels },
94
119
  currency: { ...DEFAULT_CURRENCY, ...options?.currency },
95
120
  bookingKind: options?.bookingKind ?? "appointment",
96
121
  orderKind: options?.orderKind ?? "service_order",
97
122
  scheduleKind: options?.scheduleKind ?? "working_hours",
98
123
  statuses: options?.statuses ?? DEFAULT_STATUSES,
99
- bookingTypes: options?.bookingTypes ?? DEFAULT_BOOKING_TYPES,
124
+ bookingTypes,
100
125
  businessHours: options?.businessHours ?? { startTime: "07:00", endTime: "21:00" },
101
126
  slotDuration: options?.slotDuration ?? 30,
102
127
  professionalKind: options?.professionalKind ?? "staff",
@@ -104,6 +129,8 @@ function resolveConfig(options) {
104
129
  autoCreateOrder: options?.autoCreateOrder !== false,
105
130
  locations: options?.locations ?? [],
106
131
  confirmationChannels: options?.confirmationChannels ?? [],
132
+ cancellationDetails: options?.cancellationDetails,
133
+ onBookingCreated: options?.onBookingCreated,
107
134
  contactLookup: options?.contactLookup,
108
135
  clientEntityDef: options?.clientEntityDef,
109
136
  serviceLookup: options?.serviceLookup,
@@ -431,10 +458,10 @@ function toDateLocale(locale) {
431
458
  }
432
459
  function MiniCalendar({ selectedDate, onDateSelect, onMonthChange }) {
433
460
  const locale = core.getCurrentLocale();
434
- const [viewMonth, setViewMonth] = React11__default.default.useState(() => new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
461
+ const [viewMonth, setViewMonth] = React12__default.default.useState(() => new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
435
462
  const monthLabel = viewMonth.toLocaleDateString(toDateLocale(locale), { month: "long", year: "numeric" });
436
463
  const dayLabels = DAY_LABELS[locale] ?? DAY_LABELS.en;
437
- const weeks = React11.useMemo(() => {
464
+ const weeks = React12.useMemo(() => {
438
465
  const year = viewMonth.getFullYear();
439
466
  const month = viewMonth.getMonth();
440
467
  const firstDay = new Date(year, month, 1);
@@ -505,14 +532,14 @@ function SearchCombobox({
505
532
  loading,
506
533
  onFocus: onFocusProp
507
534
  }) {
508
- const [open, setOpen] = React11.useState(false);
509
- const [activeIndex, setActiveIndex] = React11.useState(-1);
510
- const [expanded, setExpanded] = React11.useState(!inlineLabel);
511
- const [dropRect, setDropRect] = React11.useState(null);
512
- const inputRef = React11.useRef(null);
513
- const wrapperRef = React11.useRef(null);
514
- const listRef = React11.useRef(null);
515
- React11.useEffect(() => {
535
+ const [open, setOpen] = React12.useState(false);
536
+ const [activeIndex, setActiveIndex] = React12.useState(-1);
537
+ const [expanded, setExpanded] = React12.useState(!inlineLabel);
538
+ const [dropRect, setDropRect] = React12.useState(null);
539
+ const inputRef = React12.useRef(null);
540
+ const wrapperRef = React12.useRef(null);
541
+ const listRef = React12.useRef(null);
542
+ React12.useEffect(() => {
516
543
  if (value && inlineLabel) setExpanded(true);
517
544
  if (!value && inlineLabel && !open) setExpanded(false);
518
545
  }, [value, open]);
@@ -521,13 +548,13 @@ function SearchCombobox({
521
548
  }
522
549
  const showCreate = open && allowCreate && onCreateNew && value.trim().length > 0 && !options.some((o) => o.label.toLowerCase() === value.trim().toLowerCase());
523
550
  const totalItems = options.length + (showCreate ? 1 : 0);
524
- React11.useEffect(() => {
551
+ React12.useEffect(() => {
525
552
  setActiveIndex(-1);
526
553
  }, [options]);
527
- React11.useEffect(() => {
554
+ React12.useEffect(() => {
528
555
  if ((options.length > 0 || showCreate || loading) && document.activeElement === inputRef.current) setOpen(true);
529
556
  }, [options, showCreate, loading]);
530
- const handleKeyDown = React11.useCallback((e) => {
557
+ const handleKeyDown = React12.useCallback((e) => {
531
558
  if (!open || totalItems === 0) {
532
559
  if (e.key === "ArrowDown" && totalItems > 0) {
533
560
  e.preventDefault();
@@ -564,7 +591,7 @@ function SearchCombobox({
564
591
  break;
565
592
  }
566
593
  }, [open, options, totalItems, activeIndex, onSelect, showCreate, onCreateNew, value]);
567
- React11.useEffect(() => {
594
+ React12.useEffect(() => {
568
595
  if (activeIndex < 0 || !listRef.current) return;
569
596
  const items = listRef.current.children;
570
597
  if (items[activeIndex]) items[activeIndex].scrollIntoView({ block: "nearest" });
@@ -720,27 +747,27 @@ var ICON_COLORS = {
720
747
  bank_transfer: "text-warning",
721
748
  check: "text-muted-foreground"
722
749
  };
723
- function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPaymentChange, formatCurrency, inline }) {
750
+ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPaymentChange, formatCurrency: formatCurrency2, inline }) {
724
751
  const t = core.useTranslation();
725
752
  const config = useAgendaConfig();
726
753
  const bridge = config.financialBridge;
727
- const [visible, setVisible] = React11.useState(false);
728
- const [loading, setLoading] = React11.useState(true);
729
- const [detail, setDetail] = React11.useState(null);
730
- const [selectedTypeId, setSelectedTypeId] = React11.useState("");
731
- const [paymentTypes, setPaymentTypes] = React11.useState([]);
732
- const [bankAccounts, setBankAccounts] = React11.useState([]);
733
- const [filteredMethods, setFilteredMethods] = React11.useState([]);
734
- const [selectedBankId, setSelectedBankId] = React11.useState("");
735
- const [paymentMethodId, setPaymentMethodId] = React11.useState("");
736
- const [cardBrand, setCardBrand] = React11.useState("");
737
- const [cardInstallments, setCardInstallments] = React11.useState(1);
738
- const [processing, setProcessing] = React11.useState(false);
739
- const [justPaid, setJustPaid] = React11.useState(false);
740
- React11.useEffect(() => {
754
+ const [visible, setVisible] = React12.useState(false);
755
+ const [loading, setLoading] = React12.useState(true);
756
+ const [detail, setDetail] = React12.useState(null);
757
+ const [selectedTypeId, setSelectedTypeId] = React12.useState("");
758
+ const [paymentTypes, setPaymentTypes] = React12.useState([]);
759
+ const [bankAccounts, setBankAccounts] = React12.useState([]);
760
+ const [filteredMethods, setFilteredMethods] = React12.useState([]);
761
+ const [selectedBankId, setSelectedBankId] = React12.useState("");
762
+ const [paymentMethodId, setPaymentMethodId] = React12.useState("");
763
+ const [cardBrand, setCardBrand] = React12.useState("");
764
+ const [cardInstallments, setCardInstallments] = React12.useState(1);
765
+ const [processing, setProcessing] = React12.useState(false);
766
+ const [justPaid, setJustPaid] = React12.useState(false);
767
+ React12.useEffect(() => {
741
768
  requestAnimationFrame(() => setVisible(true));
742
769
  }, []);
743
- React11.useEffect(() => {
770
+ React12.useEffect(() => {
744
771
  let cancelled = false;
745
772
  setLoading(true);
746
773
  Promise.all([
@@ -802,7 +829,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
802
829
  !loading && isPaid && justPaid && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-8 space-y-2 animate-in fade-in zoom-in-95 duration-300", children: [
803
830
  /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-7 w-7 text-success" }) }),
804
831
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-success", children: t("agenda.payment.checkoutComplete") }),
805
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: formatCurrency(detail.totalAmount) })
832
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: formatCurrency2(detail.totalAmount) })
806
833
  ] }),
807
834
  !loading && detail && detail.status !== "none" && !justPaid && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 animate-in fade-in duration-300", children: [
808
835
  detail.movements.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: detail.movements.map((mov) => {
@@ -834,21 +861,21 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
834
861
  ] }),
835
862
  isPartial && /* @__PURE__ */ jsxRuntime.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: [
836
863
  "! ",
837
- formatCurrency(mov.paidAmount),
864
+ formatCurrency2(mov.paidAmount),
838
865
  "/",
839
- formatCurrency(mov.amount)
866
+ formatCurrency2(mov.amount)
840
867
  ] }),
841
868
  isOverdue && /* @__PURE__ */ jsxRuntime.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: [
842
869
  "! ",
843
870
  t("agenda.payment.overdue")
844
871
  ] }),
845
872
  mov.status === "pending" && /* @__PURE__ */ jsxRuntime.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") }),
846
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: formatCurrency(mov.paidAmount || mov.amount) })
873
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: formatCurrency2(mov.paidAmount || mov.amount) })
847
874
  ] })
848
875
  ] }, mov.id);
849
876
  }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-sm px-1", children: [
850
877
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t("agenda.payment.total") }),
851
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: formatCurrency(detail.totalAmount) })
878
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: formatCurrency2(detail.totalAmount) })
852
879
  ] }),
853
880
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
854
881
  detail.referenceNumber && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] font-mono text-muted-foreground", children: [
@@ -873,12 +900,12 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
873
900
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.payment.serviceSummary") }),
874
901
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-0.5", children: services.map((svc, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-sm py-1", children: [
875
902
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: svc.name }),
876
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: formatCurrency(svc.price) })
903
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: formatCurrency2(svc.price) })
877
904
  ] }, i)) })
878
905
  ] }),
879
906
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-sm font-semibold pt-2 border-t", children: [
880
907
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("agenda.payment.total") }),
881
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatCurrency(orderTotal) })
908
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatCurrency2(orderTotal) })
882
909
  ] }),
883
910
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
884
911
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.payment.paymentMethod") }),
@@ -953,7 +980,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
953
980
  children: Array.from({ length: 12 }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: n, children: [
954
981
  n,
955
982
  "x ",
956
- n > 1 ? formatCurrency(orderTotal / n) : ""
983
+ n > 1 ? formatCurrency2(orderTotal / n) : ""
957
984
  ] }, n))
958
985
  }
959
986
  )
@@ -982,7 +1009,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
982
1009
  onClick: handleCheckout,
983
1010
  disabled: processing || !selectedTypeId,
984
1011
  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",
985
- children: processing ? t("agenda.payment.checkingOut") : `${t("agenda.payment.checkout")} ${formatCurrency(orderTotal)}`
1012
+ children: processing ? t("agenda.payment.checkingOut") : `${t("agenda.payment.checkout")} ${formatCurrency2(orderTotal)}`
986
1013
  }
987
1014
  )
988
1015
  ] })
@@ -1043,9 +1070,9 @@ function formatCompactDate(dateStr, locale) {
1043
1070
  });
1044
1071
  }
1045
1072
  function InlineNumber({ value, onChange, min = 0, step = 1, className, disabled, tooltip }) {
1046
- const [draft, setDraft] = React11.useState(String(value));
1047
- const [focused, setFocused] = React11.useState(false);
1048
- React11.useEffect(() => {
1073
+ const [draft, setDraft] = React12.useState(String(value));
1074
+ const [focused, setFocused] = React12.useState(false);
1075
+ React12.useEffect(() => {
1049
1076
  if (!focused) setDraft(String(value));
1050
1077
  }, [value, focused]);
1051
1078
  function commit() {
@@ -1089,11 +1116,11 @@ function InlineNumber({ value, onChange, min = 0, step = 1, className, disabled,
1089
1116
  ] }) });
1090
1117
  }
1091
1118
  function StatusSelect({ value, onChange, statuses, bookingDate }) {
1092
- const [open, setOpen] = React11.useState(false);
1093
- const ref = React11.useRef(null);
1094
- const dropdownRef = React11.useRef(null);
1119
+ const [open, setOpen] = React12.useState(false);
1120
+ const ref = React12.useRef(null);
1121
+ const dropdownRef = React12.useRef(null);
1095
1122
  const current = statuses.find((s) => s.value === value);
1096
- React11.useEffect(() => {
1123
+ React12.useEffect(() => {
1097
1124
  if (!open) return;
1098
1125
  const handler = (e) => {
1099
1126
  const target = e.target;
@@ -1104,8 +1131,8 @@ function StatusSelect({ value, onChange, statuses, bookingDate }) {
1104
1131
  document.addEventListener("mousedown", handler);
1105
1132
  return () => document.removeEventListener("mousedown", handler);
1106
1133
  }, [open]);
1107
- const btnRef = React11.useRef(null);
1108
- const [dropRect, setDropRect] = React11.useState(null);
1134
+ const btnRef = React12.useRef(null);
1135
+ const [dropRect, setDropRect] = React12.useState(null);
1109
1136
  function handleOpen() {
1110
1137
  if (btnRef.current) setDropRect(btnRef.current.getBoundingClientRect());
1111
1138
  setOpen((p) => !p);
@@ -1165,10 +1192,10 @@ function StatusSelect({ value, onChange, statuses, bookingDate }) {
1165
1192
  }
1166
1193
  function ProfessionalSelect({ value, onChange, professionals }) {
1167
1194
  const t = core.useTranslation();
1168
- const [open, setOpen] = React11.useState(false);
1169
- const ref = React11.useRef(null);
1195
+ const [open, setOpen] = React12.useState(false);
1196
+ const ref = React12.useRef(null);
1170
1197
  const current = professionals.find((p) => p.id === value);
1171
- React11.useEffect(() => {
1198
+ React12.useEffect(() => {
1172
1199
  if (!open) return;
1173
1200
  const handler = (e) => {
1174
1201
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -1206,10 +1233,10 @@ function ProfessionalSelect({ value, onChange, professionals }) {
1206
1233
  }
1207
1234
  function LocationSelect({ value, onChange, locations }) {
1208
1235
  const t = core.useTranslation();
1209
- const [open, setOpen] = React11.useState(false);
1210
- const ref = React11.useRef(null);
1236
+ const [open, setOpen] = React12.useState(false);
1237
+ const ref = React12.useRef(null);
1211
1238
  const current = locations.find((l) => l.id === value);
1212
- React11.useEffect(() => {
1239
+ React12.useEffect(() => {
1213
1240
  if (!open) return;
1214
1241
  const handler = (e) => {
1215
1242
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -1245,12 +1272,12 @@ function LocationSelect({ value, onChange, locations }) {
1245
1272
  )) })
1246
1273
  ] });
1247
1274
  }
1248
- function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose, onCreated }) {
1275
+ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, externalSource, onClose, onCreated }) {
1249
1276
  const t = core.useTranslation();
1250
1277
  const locale = core.getCurrentLocale();
1251
1278
  const config = useAgendaConfig();
1252
1279
  const provider2 = useAgendaProvider();
1253
- const initialSnapshot = React11.useRef("");
1280
+ const initialSnapshot = React12.useRef("");
1254
1281
  const createBooking = useAgendaStore((s) => s.createBooking);
1255
1282
  const updateBooking = useAgendaStore((s) => s.updateBooking);
1256
1283
  const deleteBooking = useAgendaStore((s) => s.deleteBooking);
@@ -1258,54 +1285,60 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1258
1285
  const agendaDateRange = useAgendaStore((s) => s.dateRange);
1259
1286
  const professionals = useAgendaStore((s) => s.professionals);
1260
1287
  const storeLocations = useAgendaStore((s) => s.locations);
1261
- const [loading, setLoading] = React11.useState(mode === "edit");
1262
- const [bookingType, setBookingType] = React11.useState(prefill?.kind ?? "appointment");
1263
- const [clientId, setClientId] = React11.useState(prefill?.clientId ?? "");
1264
- const [clientSearch, setClientSearch] = React11.useState("");
1265
- const [clientPhone, setClientPhone] = React11.useState("");
1266
- const [professionalId, setProfessionalId] = React11.useState(prefill?.professionalId ?? "");
1267
- const [locationId, setLocationId] = React11.useState(prefill?.locationId ?? "");
1268
- const [services, setServices] = React11.useState([]);
1269
- const [serviceSearch, setServiceSearch] = React11.useState("");
1270
- const [date, setDate] = React11.useState(() => {
1288
+ const defaultBookingType = config.bookingTypes[0]?.id ?? "appointment";
1289
+ const [loading, setLoading] = React12.useState(mode === "edit");
1290
+ const [bookingType, setBookingType] = React12.useState(prefill?.kind ?? defaultBookingType);
1291
+ const [title, setTitle] = React12.useState("");
1292
+ const [clientId, setClientId] = React12.useState(prefill?.clientId ?? "");
1293
+ const [clientSearch, setClientSearch] = React12.useState("");
1294
+ const [clientPhone, setClientPhone] = React12.useState("");
1295
+ const [professionalId, setProfessionalId] = React12.useState(prefill?.professionalId ?? "");
1296
+ const [locationId, setLocationId] = React12.useState(prefill?.locationId ?? "");
1297
+ const [services, setServices] = React12.useState([]);
1298
+ const [serviceSearch, setServiceSearch] = React12.useState("");
1299
+ const [date, setDate] = React12.useState(() => {
1271
1300
  if (prefill?.startsAt) return prefill.startsAt.slice(0, 10);
1272
1301
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1273
1302
  });
1274
- const [startTime, setStartTime] = React11.useState(() => {
1303
+ const [startTime, setStartTime] = React12.useState(() => {
1275
1304
  if (prefill?.startsAt) {
1276
1305
  const d = new Date(prefill.startsAt);
1277
1306
  return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
1278
1307
  }
1279
1308
  return "09:00";
1280
1309
  });
1281
- const [status, setStatus] = React11.useState("scheduled");
1282
- const [notes, setNotes] = React11.useState("");
1283
- const [showNotes, setShowNotes] = React11.useState(false);
1284
- const [saving, setSaving] = React11.useState(false);
1285
- const [conflict, setConflict] = React11.useState(false);
1286
- const [quickCreate, setQuickCreate] = React11.useState(false);
1287
- const [newClientName, setNewClientName] = React11.useState("");
1288
- const [newClientPhone, setNewClientPhone] = React11.useState("");
1289
- const [newClientEmail, setNewClientEmail] = React11.useState("");
1290
- const [creatingClient, setCreatingClient] = React11.useState(false);
1291
- const [clientOptions, setClientOptions] = React11.useState([]);
1292
- const [clientSearching, setClientSearching] = React11.useState(false);
1293
- const [serviceOptions, setServiceOptions] = React11.useState([]);
1294
- const [initialServiceOptions, setInitialServiceOptions] = React11.useState([]);
1295
- const [editingDate, setEditingDate] = React11.useState(false);
1296
- const [editingTime, setEditingTime] = React11.useState(false);
1297
- const [confirmingDelete, setConfirmingDelete] = React11.useState(false);
1298
- const [deleting, setDeleting] = React11.useState(false);
1299
- const [editOrderId, setEditOrderId] = React11.useState(null);
1300
- const [editOrderTotal, setEditOrderTotal] = React11.useState(0);
1301
- const [editPaymentStatus, setEditPaymentStatus] = React11.useState("none");
1302
- const [paymentStatusLoading, setPaymentStatusLoading] = React11.useState(false);
1303
- const [modalTab, setModalTab] = React11.useState("appointment");
1304
- const [clientData, setClientData] = React11.useState(null);
1305
- const [clientDataLoading, setClientDataLoading] = React11.useState(false);
1306
- const [clientEditing, setClientEditing] = React11.useState(false);
1310
+ const [status, setStatus] = React12.useState("scheduled");
1311
+ const [notes, setNotes] = React12.useState("");
1312
+ const [showNotes, setShowNotes] = React12.useState(false);
1313
+ const [saving, setSaving] = React12.useState(false);
1314
+ const [conflict, setConflict] = React12.useState(false);
1315
+ const [quickCreate, setQuickCreate] = React12.useState(false);
1316
+ const [newClientName, setNewClientName] = React12.useState("");
1317
+ const [newClientPhone, setNewClientPhone] = React12.useState("");
1318
+ const [newClientEmail, setNewClientEmail] = React12.useState("");
1319
+ const [creatingClient, setCreatingClient] = React12.useState(false);
1320
+ const [clientOptions, setClientOptions] = React12.useState([]);
1321
+ const [clientSearching, setClientSearching] = React12.useState(false);
1322
+ const [serviceOptions, setServiceOptions] = React12.useState([]);
1323
+ const [initialServiceOptions, setInitialServiceOptions] = React12.useState([]);
1324
+ const [editingDate, setEditingDate] = React12.useState(false);
1325
+ const [editingTime, setEditingTime] = React12.useState(false);
1326
+ const [confirmingDelete, setConfirmingDelete] = React12.useState(false);
1327
+ const [deleting, setDeleting] = React12.useState(false);
1328
+ const [editOrderId, setEditOrderId] = React12.useState(null);
1329
+ const [editOrderTotal, setEditOrderTotal] = React12.useState(0);
1330
+ const [editPaymentStatus, setEditPaymentStatus] = React12.useState("none");
1331
+ const [paymentStatusLoading, setPaymentStatusLoading] = React12.useState(false);
1332
+ const [modalTab, setModalTab] = React12.useState("appointment");
1333
+ const [clientData, setClientData] = React12.useState(null);
1334
+ const [clientDataLoading, setClientDataLoading] = React12.useState(false);
1335
+ const [clientEditing, setClientEditing] = React12.useState(false);
1336
+ const [cancellationReasons, setCancellationReasons] = React12.useState([]);
1337
+ const [cancellationReasonId, setCancellationReasonId] = React12.useState("");
1338
+ const [cancellationNotes, setCancellationNotes] = React12.useState("");
1339
+ const [cancellationReasonsLoading, setCancellationReasonsLoading] = React12.useState(false);
1307
1340
  const activeType = config.bookingTypes.find((bt) => bt.id === bookingType) ?? config.bookingTypes[0];
1308
- React11.useEffect(() => {
1341
+ React12.useEffect(() => {
1309
1342
  if (!open) return;
1310
1343
  setClientId(prefill?.clientId ?? "");
1311
1344
  setClientSearch("");
@@ -1324,7 +1357,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1324
1357
  setNewClientPhone("");
1325
1358
  setNewClientEmail("");
1326
1359
  setCreatingClient(false);
1327
- setBookingType(prefill?.kind ?? "appointment");
1360
+ setBookingType(prefill?.kind ?? defaultBookingType);
1361
+ setTitle("");
1328
1362
  setEditingDate(false);
1329
1363
  setEditingTime(false);
1330
1364
  setConfirmingDelete(false);
@@ -1336,6 +1370,9 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1336
1370
  setClientData(null);
1337
1371
  setClientDataLoading(false);
1338
1372
  setClientEditing(false);
1373
+ setCancellationReasonId("");
1374
+ setCancellationNotes("");
1375
+ setCancellationReasonsLoading(false);
1339
1376
  setModalTab(initialTab || "appointment");
1340
1377
  if (!prefill?.professionalId && professionals.length === 1) setProfessionalId(professionals[0].id);
1341
1378
  setDate(() => {
@@ -1350,7 +1387,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1350
1387
  return "09:00";
1351
1388
  });
1352
1389
  }, [open]);
1353
- React11.useEffect(() => {
1390
+ React12.useEffect(() => {
1354
1391
  if (!open || mode !== "edit" || !bookingId) return;
1355
1392
  setLoading(true);
1356
1393
  provider2.getBookingById(bookingId).then((b) => {
@@ -1358,7 +1395,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1358
1395
  setLoading(false);
1359
1396
  return;
1360
1397
  }
1361
- setBookingType(b.kind ?? "appointment");
1398
+ setBookingType(b.kind ?? defaultBookingType);
1399
+ setTitle(b.clientName ?? "");
1362
1400
  setClientId(b.clientId ?? "");
1363
1401
  setClientSearch(b.clientName ?? "");
1364
1402
  setProfessionalId(b.professionalId ?? "");
@@ -1380,6 +1418,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1380
1418
  setEditPaymentStatus(b.paymentStatus ?? "none");
1381
1419
  const svcSnap = b.services ? b.services.map((s) => ({ id: s.serviceId ?? "", dur: s.durationMinutes, price: s.price })) : [];
1382
1420
  initialSnapshot.current = JSON.stringify({
1421
+ title: b.clientName ?? "",
1383
1422
  clientId: b.clientId ?? "",
1384
1423
  professionalId: b.professionalId ?? "",
1385
1424
  locationId: b.locationId ?? "",
@@ -1387,6 +1426,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1387
1426
  startTime: `${String(new Date(b.startsAt).getHours()).padStart(2, "0")}:${String(new Date(b.startsAt).getMinutes()).padStart(2, "0")}`,
1388
1427
  status: b.status,
1389
1428
  notes: b.notes ?? "",
1429
+ cancellationReasonId: "",
1430
+ cancellationNotes: "",
1390
1431
  services: svcSnap
1391
1432
  });
1392
1433
  setLoading(false);
@@ -1403,7 +1444,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1403
1444
  }
1404
1445
  }).catch(() => setLoading(false));
1405
1446
  }, [open, mode, bookingId]);
1406
- React11.useEffect(() => {
1447
+ React12.useEffect(() => {
1407
1448
  if (modalTab !== "client" || !clientId || !config.contactLookup || clientData) return;
1408
1449
  setClientDataLoading(true);
1409
1450
  config.contactLookup.getById(clientId).then((result) => {
@@ -1411,7 +1452,12 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1411
1452
  setClientDataLoading(false);
1412
1453
  }).catch(() => setClientDataLoading(false));
1413
1454
  }, [modalTab, clientId]);
1414
- React11.useEffect(() => {
1455
+ React12.useEffect(() => {
1456
+ if (!open || status !== "cancelled" || !config.cancellationDetails || cancellationReasons.length > 0) return;
1457
+ setCancellationReasonsLoading(true);
1458
+ config.cancellationDetails.listReasons().then((reasons) => setCancellationReasons(reasons)).catch(() => setCancellationReasons([])).finally(() => setCancellationReasonsLoading(false));
1459
+ }, [open, status, config.cancellationDetails, cancellationReasons.length]);
1460
+ React12.useEffect(() => {
1415
1461
  if (!open || !config.serviceLookup) return;
1416
1462
  const lookup = config.serviceLookup;
1417
1463
  const fetch = lookup.list ? () => lookup.list() : () => lookup.search("");
@@ -1420,7 +1466,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1420
1466
  setInitialServiceOptions(opts);
1421
1467
  }).catch(() => setInitialServiceOptions([]));
1422
1468
  }, [open]);
1423
- React11.useEffect(() => {
1469
+ React12.useEffect(() => {
1424
1470
  if (!config.contactLookup || clientSearch.length < 1 || clientId) {
1425
1471
  setClientOptions([]);
1426
1472
  setClientSearching(false);
@@ -1438,7 +1484,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1438
1484
  }, 250);
1439
1485
  return () => clearTimeout(timer);
1440
1486
  }, [clientSearch, clientId]);
1441
- React11.useEffect(() => {
1487
+ React12.useEffect(() => {
1442
1488
  if (!config.serviceLookup || serviceSearch.length < 1) {
1443
1489
  setServiceOptions([]);
1444
1490
  return;
@@ -1454,19 +1500,19 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1454
1500
  return () => clearTimeout(timer);
1455
1501
  }, [serviceSearch]);
1456
1502
  const serviceDuration = services.reduce((sum, s) => sum + s.durationMinutes, 0);
1457
- const selectionDuration = React11.useMemo(() => {
1503
+ const selectionDuration = React12.useMemo(() => {
1458
1504
  if (!prefill?.startsAt || !prefill?.endsAt) return 0;
1459
1505
  return Math.round((new Date(prefill.endsAt).getTime() - new Date(prefill.startsAt).getTime()) / 6e4);
1460
1506
  }, [prefill?.startsAt, prefill?.endsAt]);
1461
1507
  const totalDuration = serviceDuration > 0 ? serviceDuration : selectionDuration;
1462
1508
  const totalPrice = services.reduce((sum, s) => sum + s.price, 0);
1463
1509
  const { currency } = config;
1464
- const endTime = React11.useMemo(() => {
1510
+ const endTime = React12.useMemo(() => {
1465
1511
  const [h, m] = startTime.split(":").map(Number);
1466
1512
  const total = h * 60 + m + totalDuration;
1467
1513
  return `${String(Math.floor(total / 60) % 24).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
1468
1514
  }, [startTime, totalDuration]);
1469
- React11.useEffect(() => {
1515
+ React12.useEffect(() => {
1470
1516
  if (!professionalId || totalDuration === 0) {
1471
1517
  setConflict(false);
1472
1518
  return;
@@ -1510,10 +1556,11 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1510
1556
  locationId: locationId || void 0,
1511
1557
  startsAt,
1512
1558
  notes: notes || void 0,
1559
+ title: activeType.fields.title ? title.trim() : void 0,
1513
1560
  services: services.map((s) => ({ serviceId: s.serviceId, name: s.name, durationMinutes: s.durationMinutes, price: s.price }))
1514
1561
  });
1515
1562
  onClose();
1516
- onCreated?.(newBooking.id);
1563
+ onCreated?.(newBooking.id, externalSource);
1517
1564
  } else if (bookingId) {
1518
1565
  await updateBooking(bookingId, {
1519
1566
  clientId,
@@ -1522,8 +1569,17 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1522
1569
  startsAt,
1523
1570
  notes: notes || void 0,
1524
1571
  status,
1572
+ title: activeType.fields.title ? title.trim() : void 0,
1525
1573
  services: services.map((s) => ({ serviceId: s.serviceId, name: s.name, durationMinutes: s.durationMinutes, price: s.price }))
1526
1574
  });
1575
+ if (status === "cancelled" && config.cancellationDetails && cancellationReasonId) {
1576
+ await config.cancellationDetails.saveCancellationDetails({
1577
+ bookingId,
1578
+ reasonId: cancellationReasonId,
1579
+ notes: cancellationNotes.trim() || void 0,
1580
+ cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
1581
+ });
1582
+ }
1527
1583
  onClose();
1528
1584
  }
1529
1585
  } catch {
@@ -1541,12 +1597,15 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1541
1597
  setDeleting(false);
1542
1598
  }
1543
1599
  const isPaidBooking = editPaymentStatus === "paid";
1544
- const formSnapshot = React11.useMemo(
1545
- () => JSON.stringify({ clientId, professionalId, locationId, date, startTime, status, notes, services: services.map((s) => ({ id: s.serviceId, dur: s.durationMinutes, price: s.price })) }),
1546
- [clientId, professionalId, locationId, date, startTime, status, notes, services]
1600
+ const selectedCancellationReason = cancellationReasons.find((reason) => reason.id === cancellationReasonId);
1601
+ const needsCancellationReason = mode === "edit" && status === "cancelled" && !!config.cancellationDetails;
1602
+ const needsCancellationNotes = needsCancellationReason && !!selectedCancellationReason?.requiresNotes;
1603
+ const formSnapshot = React12.useMemo(
1604
+ () => JSON.stringify({ title, clientId, professionalId, locationId, date, startTime, status, notes, cancellationReasonId, cancellationNotes, services: services.map((s) => ({ id: s.serviceId, dur: s.durationMinutes, price: s.price })) }),
1605
+ [title, clientId, professionalId, locationId, date, startTime, status, notes, cancellationReasonId, cancellationNotes, services]
1547
1606
  );
1548
1607
  const isDirty = mode === "create" || formSnapshot !== initialSnapshot.current;
1549
- const canSubmit = (!activeType.requiresClient || clientId) && (!activeType.fields.professional || professionalId) && (!activeType.requiresServices || services.length > 0) && !conflict && !saving;
1608
+ const canSubmit = (!activeType.fields.title || title.trim()) && (!activeType.requiresClient || clientId) && (!activeType.fields.professional || professionalId) && (!activeType.requiresServices || services.length > 0) && (!needsCancellationReason || !!cancellationReasonId) && (!needsCancellationNotes || !!cancellationNotes.trim()) && !conflict && !saving;
1550
1609
  const showTabs = config.bookingTypes.length > 1;
1551
1610
  return /* @__PURE__ */ jsxRuntime.jsx(ui.Modal, { open, onOpenChange: (v) => {
1552
1611
  if (!v) onClose();
@@ -1667,8 +1726,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1667
1726
  onCancel: () => setClientEditing(false),
1668
1727
  onSubmit: async (data) => {
1669
1728
  try {
1670
- const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
1671
- const supabase = getSupabaseClientOptional4();
1729
+ const { getSupabaseClientOptional: getSupabaseClientOptional3 } = await import('@fayz-ai/core');
1730
+ const supabase = getSupabaseClientOptional3();
1672
1731
  if (!supabase) return;
1673
1732
  const dbData = {};
1674
1733
  for (const [k, v] of Object.entries(data)) {
@@ -1721,6 +1780,20 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1721
1780
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 flex-1 rounded-lg bg-muted animate-pulse" })
1722
1781
  ] }, i)) }),
1723
1782
  !loading && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1783
+ activeType.fields.title && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 py-1", children: [
1784
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1785
+ /* @__PURE__ */ jsxRuntime.jsx(
1786
+ "input",
1787
+ {
1788
+ type: "text",
1789
+ value: title,
1790
+ onChange: (e) => setTitle(e.target.value),
1791
+ placeholder: t("agenda.appointment.addTitle"),
1792
+ autoFocus: mode === "create",
1793
+ 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"
1794
+ }
1795
+ )
1796
+ ] }),
1724
1797
  activeType.fields.client && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 py-1", children: [
1725
1798
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1726
1799
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: clientId && !quickCreate ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 rounded-md bg-muted/30 px-2.5 py-1.5 text-sm", children: [
@@ -1815,10 +1888,10 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1815
1888
  onClick: async () => {
1816
1889
  setCreatingClient(true);
1817
1890
  try {
1818
- const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
1819
- const { getAgendaTenantId: getAgendaTenantId2 } = await import('./tenant-222EHYUC.cjs');
1820
- const supabase = getSupabaseClientOptional4();
1821
- const tenantId = getAgendaTenantId2();
1891
+ const { getSupabaseClientOptional: getSupabaseClientOptional3 } = await import('@fayz-ai/core');
1892
+ const { getAgendaTenantId } = await import('./tenant-222EHYUC.cjs');
1893
+ const supabase = getSupabaseClientOptional3();
1894
+ const tenantId = getAgendaTenantId();
1822
1895
  if (!supabase || !tenantId) throw new Error("Not initialized");
1823
1896
  const { data: personRow, error: personError } = await supabase.schema("saas_core").from("persons").insert({
1824
1897
  tenant_id: tenantId,
@@ -1933,6 +2006,37 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, onClose,
1933
2006
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleDot, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1934
2007
  /* @__PURE__ */ jsxRuntime.jsx(StatusSelect, { value: status, onChange: setStatus, statuses: config.statuses, bookingDate: date })
1935
2008
  ] }),
2009
+ needsCancellationReason && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3 py-1", children: [
2010
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Ban, { className: "h-4 w-4 mt-2 text-muted-foreground shrink-0" }),
2011
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-2", children: [
2012
+ /* @__PURE__ */ jsxRuntime.jsxs(
2013
+ "select",
2014
+ {
2015
+ value: cancellationReasonId,
2016
+ onChange: (event) => setCancellationReasonId(event.target.value),
2017
+ disabled: cancellationReasonsLoading,
2018
+ 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",
2019
+ children: [
2020
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: cancellationReasonsLoading ? t("agenda.cancellation.loadingReasons") : t("agenda.cancellation.selectReason") }),
2021
+ cancellationReasons.map((reason) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: reason.id, children: [
2022
+ reason.label,
2023
+ reason.requiresNotes ? " *" : ""
2024
+ ] }, reason.id))
2025
+ ]
2026
+ }
2027
+ ),
2028
+ /* @__PURE__ */ jsxRuntime.jsx(
2029
+ "textarea",
2030
+ {
2031
+ value: cancellationNotes,
2032
+ onChange: (event) => setCancellationNotes(event.target.value),
2033
+ placeholder: needsCancellationNotes ? t("agenda.cancellation.notesRequired") : t("agenda.cancellation.notesOptional"),
2034
+ rows: 2,
2035
+ 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"
2036
+ }
2037
+ )
2038
+ ] })
2039
+ ] }),
1936
2040
  !showNotes ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 py-1", children: [
1937
2041
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1938
2042
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2060,19 +2164,19 @@ function FloatingPanel({
2060
2164
  children,
2061
2165
  zIndex = 100
2062
2166
  }) {
2063
- const ref = React11.useRef(null);
2064
- const [phase, setPhase] = React11.useState("visible");
2065
- const phaseRef = React11.useRef("visible");
2167
+ const ref = React12.useRef(null);
2168
+ const [phase, setPhase] = React12.useState("visible");
2169
+ const phaseRef = React12.useRef("visible");
2066
2170
  phaseRef.current = phase;
2067
- const onCloseRef = React11.useRef(onClose);
2171
+ const onCloseRef = React12.useRef(onClose);
2068
2172
  onCloseRef.current = onClose;
2069
- const expandCallbackRef = React11.useRef(null);
2070
- const dismiss = React11.useCallback(() => {
2173
+ const expandCallbackRef = React12.useRef(null);
2174
+ const dismiss = React12.useCallback(() => {
2071
2175
  if (phaseRef.current !== "visible") return;
2072
2176
  setPhase("dismissing");
2073
2177
  setTimeout(() => onCloseRef.current(), 150);
2074
2178
  }, []);
2075
- const expandThen = React11.useCallback((callback) => {
2179
+ const expandThen = React12.useCallback((callback) => {
2076
2180
  if (phaseRef.current !== "visible") return;
2077
2181
  expandCallbackRef.current = callback;
2078
2182
  setPhase("expanding");
@@ -2081,7 +2185,7 @@ function FloatingPanel({
2081
2185
  onCloseRef.current();
2082
2186
  }, 220);
2083
2187
  }, []);
2084
- React11.useEffect(() => {
2188
+ React12.useEffect(() => {
2085
2189
  function handleClick(e) {
2086
2190
  if (phaseRef.current !== "visible") return;
2087
2191
  if (ref.current && !ref.current.contains(e.target)) {
@@ -2132,10 +2236,10 @@ function fmtCurrency2(amount, locale, code) {
2132
2236
  return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(amount);
2133
2237
  }
2134
2238
  function PopoverStatusSelect({ value, statuses, bookingStartsAt, onChange }) {
2135
- const [open, setOpen] = React11.useState(false);
2136
- const ref = React11.useRef(null);
2239
+ const [open, setOpen] = React12.useState(false);
2240
+ const ref = React12.useRef(null);
2137
2241
  const current = statuses.find((s) => s.value === value);
2138
- React11.useEffect(() => {
2242
+ React12.useEffect(() => {
2139
2243
  if (!open) return;
2140
2244
  const h = (e) => {
2141
2245
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
@@ -2188,8 +2292,8 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2188
2292
  const deleteBooking = useAgendaStore((s) => s.deleteBooking);
2189
2293
  const updateStatus = useAgendaStore((s) => s.updateBookingStatus);
2190
2294
  const openModal = useAgendaStore((s) => s.openAppointmentModal);
2191
- const [confirmingDelete, setConfirmingDelete] = React11.useState(false);
2192
- const [deleting, setDeleting] = React11.useState(false);
2295
+ const [confirmingDelete, setConfirmingDelete] = React12.useState(false);
2296
+ const [deleting, setDeleting] = React12.useState(false);
2193
2297
  const statusConfig = config.statuses.find((s) => s.value === booking.status);
2194
2298
  const color = statusConfig?.color ?? "#6b7280";
2195
2299
  const timeRange = `${fmtTime(booking.startsAt)}${booking.endsAt ? " \u2013 " + fmtTime(booking.endsAt) : ""}`;
@@ -2247,7 +2351,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2247
2351
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
2248
2352
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1.5 h-3 w-3 rounded shrink-0", style: { backgroundColor: color } }),
2249
2353
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
2250
- /* @__PURE__ */ jsxRuntime.jsx(saas.PersonLink, { personId: booking.clientId, name: booking.clientName ?? "Unknown", className: "text-base" }),
2354
+ config.capabilities.client ? /* @__PURE__ */ jsxRuntime.jsx(saas.PersonLink, { personId: booking.clientId, name: booking.clientName ?? "Unknown", className: "text-base" }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base font-medium truncate", children: booking.clientName || t("agenda.appointment.untitled") }),
2251
2355
  /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground mt-0.5", children: [
2252
2356
  fmtDate(booking.startsAt),
2253
2357
  " \xB7 ",
@@ -2267,7 +2371,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2267
2371
  ] })
2268
2372
  ] }, svc.id)) })
2269
2373
  ] }),
2270
- booking.professionalName && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
2374
+ config.capabilities.professional && booking.professionalName && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
2271
2375
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
2272
2376
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: booking.professionalName })
2273
2377
  ] }),
@@ -2318,7 +2422,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
2318
2422
  )
2319
2423
  ] });
2320
2424
  })(),
2321
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-2 border-t fayz-glass-divider", children: /* @__PURE__ */ jsxRuntime.jsx(
2425
+ config.capabilities.status && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-2 border-t fayz-glass-divider", children: /* @__PURE__ */ jsxRuntime.jsx(
2322
2426
  PopoverStatusSelect,
2323
2427
  {
2324
2428
  value: booking.status,
@@ -2347,9 +2451,9 @@ function Tooltip2({ anchor, label }) {
2347
2451
  );
2348
2452
  }
2349
2453
  function StatusDot({ color, label, isActive, disabled, onClick }) {
2350
- const [hover, setHover] = React11.useState(false);
2351
- const btnRef = React11.useRef(null);
2352
- const [rect, setRect] = React11.useState(null);
2454
+ const [hover, setHover] = React12.useState(false);
2455
+ const btnRef = React12.useRef(null);
2456
+ const [rect, setRect] = React12.useState(null);
2353
2457
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2354
2458
  /* @__PURE__ */ jsxRuntime.jsx(
2355
2459
  "button",
@@ -2376,8 +2480,8 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
2376
2480
  const config = useAgendaConfig();
2377
2481
  const updateStatus = useAgendaStore((s) => s.updateBookingStatus);
2378
2482
  const deleteBooking = useAgendaStore((s) => s.deleteBooking);
2379
- const [confirming, setConfirming] = React11.useState(false);
2380
- const [deleting, setDeleting] = React11.useState(false);
2483
+ const [confirming, setConfirming] = React12.useState(false);
2484
+ const [deleting, setDeleting] = React12.useState(false);
2381
2485
  async function handleDelete() {
2382
2486
  setDeleting(true);
2383
2487
  try {
@@ -2435,7 +2539,7 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
2435
2539
  }
2436
2540
  )
2437
2541
  ] }),
2438
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t px-4 py-3", children: [
2542
+ config.capabilities.status && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t px-4 py-3", children: [
2439
2543
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.contextMenu.changeStatus") }),
2440
2544
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: config.statuses.map((s) => {
2441
2545
  const available = isStatusAvailable(s, booking.startsAt);
@@ -2459,6 +2563,66 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
2459
2563
  ] })
2460
2564
  ] }) });
2461
2565
  }
2566
+ function startOfDay(d) {
2567
+ const x = new Date(d);
2568
+ x.setHours(0, 0, 0, 0);
2569
+ return x;
2570
+ }
2571
+ function timeLabel(iso) {
2572
+ const d = new Date(iso);
2573
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
2574
+ }
2575
+ function AgendaMobileList({ bookings, statusColors, currencyCode, onSelect }) {
2576
+ const t = core.useTranslation();
2577
+ const locale = core.getCurrentLocale() === "pt-BR" ? "pt-BR" : "en";
2578
+ const groups = React12.useMemo(() => {
2579
+ const todayStart = startOfDay(/* @__PURE__ */ new Date()).getTime();
2580
+ const sorted = bookings.filter((b) => !!b.startsAt).slice().sort((a, b) => a.startsAt < b.startsAt ? -1 : a.startsAt > b.startsAt ? 1 : 0);
2581
+ const map = /* @__PURE__ */ new Map();
2582
+ for (const b of sorted) {
2583
+ const day = startOfDay(new Date(b.startsAt));
2584
+ if (day.getTime() < todayStart) continue;
2585
+ const key = day.toISOString().slice(0, 10);
2586
+ const list = map.get(key) ?? [];
2587
+ list.push(b);
2588
+ map.set(key, list);
2589
+ }
2590
+ return Array.from(map.entries()).map(([key, items]) => ({ key, date: /* @__PURE__ */ new Date(key + "T00:00:00"), items }));
2591
+ }, [bookings]);
2592
+ function dayLabel(date) {
2593
+ const today = startOfDay(/* @__PURE__ */ new Date());
2594
+ const diff = Math.round((startOfDay(date).getTime() - today.getTime()) / 864e5);
2595
+ if (diff === 0) return t("agenda.list.today");
2596
+ if (diff === 1) return t("agenda.list.tomorrow");
2597
+ return date.toLocaleDateString(locale, { weekday: "long", day: "numeric", month: "long" });
2598
+ }
2599
+ if (groups.length === 0) {
2600
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex flex-col items-center justify-center px-6 py-16 text-center text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: t("agenda.list.empty") }) });
2601
+ }
2602
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto px-3 py-3 space-y-5", children: groups.map((g) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2603
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "px-1 pb-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: dayLabel(g.date) }),
2604
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: g.items.map((b) => {
2605
+ const color = statusColors[b.status] ?? "#6b7280";
2606
+ const hasAmount = typeof b.orderTotal === "number" && b.orderTotal > 0;
2607
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2608
+ "button",
2609
+ {
2610
+ onClick: () => onSelect(b),
2611
+ className: "flex w-full items-center gap-3 rounded-lg border bg-card px-3 py-2.5 text-left shadow-sm transition-colors active:bg-muted/50",
2612
+ children: [
2613
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { backgroundColor: color } }),
2614
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
2615
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: b.clientName ?? "\u2014" }),
2616
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: timeLabel(b.startsAt) })
2617
+ ] }),
2618
+ hasAmount && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums text-foreground", children: core.formatCurrency(b.orderTotal, currencyCode) })
2619
+ ]
2620
+ },
2621
+ b.id
2622
+ );
2623
+ }) })
2624
+ ] }, g.key)) });
2625
+ }
2462
2626
  function bookingToEvent(booking, statusColors) {
2463
2627
  const color = statusColors[booking.status] ?? "#6b7280";
2464
2628
  const serviceNames = booking.services?.map((s) => s.name).join(", ") ?? "";
@@ -2581,18 +2745,44 @@ function CalendarSkeleton({ profCount = 2 }) {
2581
2745
  ] }, h)) })
2582
2746
  ] });
2583
2747
  }
2584
- var VIEW_OPTIONS = [
2748
+ var RESOURCE_VIEW_OPTIONS = [
2585
2749
  { key: "resourceTimeGridDay", labelKey: "agenda.calendar.viewDay" },
2586
2750
  { key: "resourceTimeGridWeek", labelKey: "agenda.calendar.viewWeek" },
2587
2751
  { key: "dayGridMonth", labelKey: "agenda.calendar.viewMonth" }
2588
2752
  ];
2753
+ var PLAIN_VIEW_OPTIONS = [
2754
+ { key: "timeGridDay", labelKey: "agenda.calendar.viewDay" },
2755
+ { key: "timeGridWeek", labelKey: "agenda.calendar.viewWeek" },
2756
+ { key: "dayGridMonth", labelKey: "agenda.calendar.viewMonth" },
2757
+ { key: "listMonth", labelKey: "agenda.calendar.viewList" }
2758
+ ];
2759
+ var MOBILE_LIST_VIEW = "__mobileList__";
2760
+ function viewForNoResources(view) {
2761
+ return view.startsWith("resource") ? "dayGridMonth" : view;
2762
+ }
2763
+ function viewForResources(view) {
2764
+ if (view === "timeGridWeek") return "resourceTimeGridWeek";
2765
+ if (view === "timeGridDay") return "resourceTimeGridDay";
2766
+ return view;
2767
+ }
2768
+ function useIsMobile() {
2769
+ const [isMobile, setIsMobile] = React12.useState(() => typeof window !== "undefined" && window.matchMedia("(max-width: 767px)").matches);
2770
+ React12.useEffect(() => {
2771
+ if (typeof window === "undefined") return;
2772
+ const mq = window.matchMedia("(max-width: 767px)");
2773
+ const onChange = () => setIsMobile(mq.matches);
2774
+ mq.addEventListener("change", onChange);
2775
+ return () => mq.removeEventListener("change", onChange);
2776
+ }, []);
2777
+ return isMobile;
2778
+ }
2589
2779
  function CalendarView() {
2590
2780
  injectCalendarStyles();
2591
2781
  const config = useAgendaConfig();
2592
2782
  const t = core.useTranslation();
2593
2783
  const currentLocale = core.getCurrentLocale();
2594
2784
  const fcLocale = currentLocale === "pt-BR" ? ptBrLocale__default.default : void 0;
2595
- const calendarRef = React11.useRef(null);
2785
+ const calendarRef = React12.useRef(null);
2596
2786
  const prefs = saas.usePluginPrefs("agenda", { calendarView: config.defaultCalendarView ?? "resourceTimeGridWeek" });
2597
2787
  const agendaSettings = useAgendaSettings();
2598
2788
  const effectiveBusinessHours = {
@@ -2618,28 +2808,40 @@ function CalendarView() {
2618
2808
  const _openModal = useAgendaStore((s) => s.openAppointmentModal);
2619
2809
  const closeModal = useAgendaStore((s) => s.closeAppointmentModal);
2620
2810
  const modalState = useAgendaStore((s) => s.appointmentModal);
2621
- const [selectedBooking, setSelectedBooking] = React11.useState(null);
2622
- const [popoverAnchor, setPopoverAnchor] = React11.useState(null);
2623
- const [popoverVisible, _setPopoverVisible] = React11.useState(false);
2624
- const popoverVisibleRef = React11.useRef(false);
2625
- const setPopoverVisible = React11.useCallback((v) => {
2811
+ const isMobile = useIsMobile();
2812
+ const resourceCapable = professionals.length > 0;
2813
+ const viewOptions = resourceCapable ? RESOURCE_VIEW_OPTIONS : PLAIN_VIEW_OPTIONS;
2814
+ const [mobileView, setMobileView] = React12.useState(MOBILE_LIST_VIEW);
2815
+ const mobileViewOptions = React12.useMemo(
2816
+ () => [
2817
+ ...viewOptions.filter((o) => o.key !== "listMonth"),
2818
+ { key: MOBILE_LIST_VIEW, labelKey: "agenda.calendar.viewList" }
2819
+ ],
2820
+ [viewOptions]
2821
+ );
2822
+ const mobileGridActive = isMobile && mobileView !== MOBILE_LIST_VIEW;
2823
+ const [selectedBooking, setSelectedBooking] = React12.useState(null);
2824
+ const [popoverAnchor, setPopoverAnchor] = React12.useState(null);
2825
+ const [popoverVisible, _setPopoverVisible] = React12.useState(false);
2826
+ const popoverVisibleRef = React12.useRef(false);
2827
+ const setPopoverVisible = React12.useCallback((v) => {
2626
2828
  popoverVisibleRef.current = v;
2627
2829
  _setPopoverVisible(v);
2628
2830
  }, []);
2629
- const [contextMenu, setContextMenu] = React11.useState(null);
2630
- const [contextMenuVisible, _setContextMenuVisible] = React11.useState(false);
2631
- const contextMenuVisibleRef = React11.useRef(false);
2632
- const setContextMenuVisible = React11.useCallback((v) => {
2831
+ const [contextMenu, setContextMenu] = React12.useState(null);
2832
+ const [contextMenuVisible, _setContextMenuVisible] = React12.useState(false);
2833
+ const contextMenuVisibleRef = React12.useRef(false);
2834
+ const setContextMenuVisible = React12.useCallback((v) => {
2633
2835
  contextMenuVisibleRef.current = v;
2634
2836
  _setContextMenuVisible(v);
2635
2837
  }, []);
2636
- const [scheduleConfirm, setScheduleConfirm] = React11.useState(null);
2637
- const [calendarTitle, setCalendarTitle] = React11.useState("");
2638
- const [selectedDate, setSelectedDate] = React11.useState(/* @__PURE__ */ new Date());
2639
- const [profSectionOpen, setProfSectionOpen] = React11.useState(true);
2640
- const [statusSectionOpen, setStatusSectionOpen] = React11.useState(false);
2641
- const [visibleRange, setVisibleRange] = React11.useState(null);
2642
- const openModal = React11.useCallback((...args) => {
2838
+ const [scheduleConfirm, setScheduleConfirm] = React12.useState(null);
2839
+ const [calendarTitle, setCalendarTitle] = React12.useState("");
2840
+ const [selectedDate, setSelectedDate] = React12.useState(/* @__PURE__ */ new Date());
2841
+ const [profSectionOpen, setProfSectionOpen] = React12.useState(true);
2842
+ const [statusSectionOpen, setStatusSectionOpen] = React12.useState(false);
2843
+ const [visibleRange, setVisibleRange] = React12.useState(null);
2844
+ const openModal = React12.useCallback((...args) => {
2643
2845
  setPopoverVisible(false);
2644
2846
  setSelectedBooking(null);
2645
2847
  setPopoverAnchor(null);
@@ -2647,17 +2849,35 @@ function CalendarView() {
2647
2849
  setContextMenu(null);
2648
2850
  _openModal(...args);
2649
2851
  }, [_openModal]);
2650
- const statusColors = React11.useMemo(() => {
2852
+ const statusColors = React12.useMemo(() => {
2651
2853
  const map = {};
2652
2854
  for (const s of config.statuses) map[s.value] = s.color;
2653
2855
  return map;
2654
2856
  }, [config.statuses]);
2655
- React11.useEffect(() => {
2857
+ React12.useEffect(() => {
2656
2858
  fetchProfessionals();
2657
2859
  fetchSchedules();
2658
2860
  if (config.modules.locationSelection) fetchLocations();
2659
2861
  }, []);
2660
- const resources = React11.useMemo(() => {
2862
+ React12.useEffect(() => {
2863
+ const api = calendarRef.current?.getApi();
2864
+ if (!api) return;
2865
+ if (!resourceCapable && currentView.startsWith("resource")) {
2866
+ api.changeView("dayGridMonth");
2867
+ } else if (resourceCapable && (currentView === "timeGridWeek" || currentView === "timeGridDay")) {
2868
+ api.changeView(viewForResources(currentView));
2869
+ }
2870
+ }, [resourceCapable, currentView]);
2871
+ React12.useEffect(() => {
2872
+ if (!isMobile) return;
2873
+ const start = /* @__PURE__ */ new Date();
2874
+ start.setHours(0, 0, 0, 0);
2875
+ start.setDate(start.getDate() - 1);
2876
+ const end = new Date(start);
2877
+ end.setDate(end.getDate() + 91);
2878
+ fetchBookings({ start: start.toISOString(), end: end.toISOString() });
2879
+ }, [isMobile, fetchBookings]);
2880
+ const resources = React12.useMemo(() => {
2661
2881
  const filtered = selectedProfIds.length > 0 ? professionals.filter((p) => selectedProfIds.includes(p.id)) : professionals;
2662
2882
  return filtered.map((p) => ({
2663
2883
  id: p.id,
@@ -2665,29 +2885,31 @@ function CalendarView() {
2665
2885
  extendedProps: { avatarUrl: p.avatarUrl, locationName: p.locationName }
2666
2886
  }));
2667
2887
  }, [professionals, selectedProfIds]);
2668
- const bookingEvents = React11.useMemo(() => bookings.map((b) => bookingToEvent(b, statusColors)), [bookings, statusColors]);
2669
- const allLocations = React11.useMemo(() => {
2888
+ const bookingEvents = React12.useMemo(() => bookings.map((b) => bookingToEvent(b, statusColors)), [bookings, statusColors]);
2889
+ const allLocations = React12.useMemo(() => {
2670
2890
  const map = /* @__PURE__ */ new Map();
2671
2891
  for (const loc of config.locations) map.set(loc.id, loc.name);
2672
2892
  for (const loc of storeLocations) map.set(loc.id, loc.name);
2673
2893
  return map;
2674
2894
  }, [config.locations, storeLocations]);
2675
- const locationMap = React11.useMemo(() => {
2895
+ const locationMap = React12.useMemo(() => {
2676
2896
  if (!config.modules.locationSelection || allLocations.size === 0) return void 0;
2677
2897
  return allLocations;
2678
2898
  }, [config.modules.locationSelection, allLocations]);
2679
- const availabilityEvents = React11.useMemo(
2899
+ const availabilityEvents = React12.useMemo(
2680
2900
  () => generateAvailabilityEvents(schedules, professionals, visibleRange, effectiveBusinessHours, locationMap),
2681
2901
  [schedules, professionals, visibleRange, effectiveBusinessHours, locationMap]
2682
2902
  );
2683
- const events = React11.useMemo(() => [...bookingEvents, ...availabilityEvents], [bookingEvents, availabilityEvents]);
2684
- const handleDatesSet = React11.useCallback((arg) => {
2903
+ const events = React12.useMemo(() => [...bookingEvents, ...availabilityEvents], [bookingEvents, availabilityEvents]);
2904
+ const storedView = prefs.get("calendarView");
2905
+ const effectiveInitialView = resourceCapable ? viewForResources(storedView) : viewForNoResources(storedView);
2906
+ const handleDatesSet = React12.useCallback((arg) => {
2685
2907
  setCalendarTitle(arg.view.title);
2686
2908
  setView(arg.view.type);
2687
2909
  setVisibleRange({ start: arg.startStr, end: arg.endStr });
2688
2910
  fetchBookings({ start: arg.startStr, end: arg.endStr });
2689
2911
  }, [fetchBookings, setView]);
2690
- const handleEventDidMount = React11.useCallback((arg) => {
2912
+ const handleEventDidMount = React12.useCallback((arg) => {
2691
2913
  const booking = arg.event.extendedProps?.booking;
2692
2914
  if (booking) arg.el.setAttribute("data-booking-id", booking.id);
2693
2915
  arg.el.addEventListener("dblclick", (e) => {
@@ -2714,9 +2936,9 @@ function CalendarView() {
2714
2936
  setPopoverAnchor(null);
2715
2937
  });
2716
2938
  }, []);
2717
- const skipClickRef = React11.useRef(false);
2718
- const hoverTimerRef = React11.useRef(null);
2719
- const handleEventMouseEnter = React11.useCallback((arg) => {
2939
+ const skipClickRef = React12.useRef(false);
2940
+ const hoverTimerRef = React12.useRef(null);
2941
+ const handleEventMouseEnter = React12.useCallback((arg) => {
2720
2942
  if (arg.event.display === "background") return;
2721
2943
  if (arg.event.extendedProps?.isLocationLabel) return;
2722
2944
  const booking = arg.event.extendedProps.booking;
@@ -2735,13 +2957,13 @@ function CalendarView() {
2735
2957
  setPopoverVisible(true);
2736
2958
  }, 3e3);
2737
2959
  }, []);
2738
- const handleEventMouseLeave = React11.useCallback(() => {
2960
+ const handleEventMouseLeave = React12.useCallback(() => {
2739
2961
  if (hoverTimerRef.current) {
2740
2962
  clearTimeout(hoverTimerRef.current);
2741
2963
  hoverTimerRef.current = null;
2742
2964
  }
2743
2965
  }, []);
2744
- const handleEventClick = React11.useCallback((arg) => {
2966
+ const handleEventClick = React12.useCallback((arg) => {
2745
2967
  if (skipClickRef.current) {
2746
2968
  skipClickRef.current = false;
2747
2969
  return;
@@ -2764,7 +2986,7 @@ function CalendarView() {
2764
2986
  setPopoverAnchor({ x, y });
2765
2987
  setPopoverVisible(true);
2766
2988
  }, []);
2767
- const findScheduleForSlot = React11.useCallback((professionalId, dateTimeStr) => {
2989
+ const findScheduleForSlot = React12.useCallback((professionalId, dateTimeStr) => {
2768
2990
  if (!professionalId) return null;
2769
2991
  const dt = new Date(dateTimeStr);
2770
2992
  const dateStr = dt.toISOString().slice(0, 10);
@@ -2781,7 +3003,7 @@ function CalendarView() {
2781
3003
  }
2782
3004
  return null;
2783
3005
  }, [schedules]);
2784
- const handleSelect = React11.useCallback((arg) => {
3006
+ const handleSelect = React12.useCallback((arg) => {
2785
3007
  calendarRef.current?.getApi().unselect();
2786
3008
  if (popoverVisibleRef.current || contextMenuVisibleRef.current) {
2787
3009
  setPopoverVisible(false);
@@ -2797,13 +3019,13 @@ function CalendarView() {
2797
3019
  const locId = schedule?.locationId ?? schedule?.metadata?.locationId;
2798
3020
  openModal("create", { prefill: { startsAt: arg.startStr, endsAt: arg.endStr, professionalId, locationId: locId || void 0 } });
2799
3021
  };
2800
- if (!schedule) {
3022
+ if (config.modules.workingHours && !schedule) {
2801
3023
  setScheduleConfirm({ onConfirm: proceed });
2802
3024
  return;
2803
3025
  }
2804
3026
  proceed();
2805
- }, [openModal, findScheduleForSlot]);
2806
- const handleEventDrop = React11.useCallback(async (arg) => {
3027
+ }, [openModal, findScheduleForSlot, config.modules.workingHours]);
3028
+ const handleEventDrop = React12.useCallback(async (arg) => {
2807
3029
  const booking = arg.event.extendedProps.booking;
2808
3030
  try {
2809
3031
  await rescheduleBooking(booking.id, arg.event.startStr, arg.event.endStr, arg.newResource?.id ?? void 0);
@@ -2811,13 +3033,19 @@ function CalendarView() {
2811
3033
  arg.revert();
2812
3034
  }
2813
3035
  }, [rescheduleBooking]);
2814
- const handleViewChange = React11.useCallback((v) => {
3036
+ const handleViewChange = React12.useCallback((v) => {
2815
3037
  calendarRef.current?.getApi().changeView(v);
2816
3038
  prefs.set("calendarView", v);
2817
3039
  }, [prefs]);
2818
- const handlePrev = React11.useCallback(() => calendarRef.current?.getApi().prev(), []);
2819
- const handleNext = React11.useCallback(() => calendarRef.current?.getApi().next(), []);
2820
- const handleToday = React11.useCallback(() => {
3040
+ const handleMobileViewChange = React12.useCallback((v) => {
3041
+ setMobileView(v);
3042
+ if (v === MOBILE_LIST_VIEW) return;
3043
+ setView(v);
3044
+ calendarRef.current?.getApi()?.changeView(v);
3045
+ }, [setView]);
3046
+ const handlePrev = React12.useCallback(() => calendarRef.current?.getApi().prev(), []);
3047
+ const handleNext = React12.useCallback(() => calendarRef.current?.getApi().next(), []);
3048
+ const handleToday = React12.useCallback(() => {
2821
3049
  calendarRef.current?.getApi().today();
2822
3050
  setSelectedDate(/* @__PURE__ */ new Date());
2823
3051
  }, []);
@@ -2833,9 +3061,9 @@ function CalendarView() {
2833
3061
  const next = selectedStatuses.includes(value) ? selectedStatuses.filter((s) => s !== value) : [...selectedStatuses, value];
2834
3062
  setFilters({ statuses: next });
2835
3063
  }
2836
- const wrapperRef = React11.useRef(null);
2837
- const [dayHeaderCompact, setDayHeaderCompact] = React11.useState(false);
2838
- React11.useEffect(() => {
3064
+ const wrapperRef = React12.useRef(null);
3065
+ const [dayHeaderCompact, setDayHeaderCompact] = React12.useState(false);
3066
+ React12.useEffect(() => {
2839
3067
  const container = wrapperRef.current;
2840
3068
  if (!container) return;
2841
3069
  let frame = 0;
@@ -2878,7 +3106,7 @@ function CalendarView() {
2878
3106
  ro.disconnect();
2879
3107
  };
2880
3108
  }, [resources, currentView, visibleRange]);
2881
- const applyResourceStriping = React11.useCallback(() => {
3109
+ const applyResourceStriping = React12.useCallback(() => {
2882
3110
  const container = wrapperRef.current;
2883
3111
  if (!container) return;
2884
3112
  const resourceIds = resources.map((r) => r.id);
@@ -2889,7 +3117,7 @@ function CalendarView() {
2889
3117
  el.classList.toggle("fc-resource-alt", idx % 2 === 1);
2890
3118
  });
2891
3119
  }, [resources]);
2892
- React11.useEffect(() => {
3120
+ React12.useEffect(() => {
2893
3121
  applyResourceStriping();
2894
3122
  const t1 = setTimeout(applyResourceStriping, 100);
2895
3123
  const t2 = setTimeout(applyResourceStriping, 300);
@@ -2898,15 +3126,28 @@ function CalendarView() {
2898
3126
  clearTimeout(t2);
2899
3127
  };
2900
3128
  }, [applyResourceStriping, currentView, visibleRange, bookings]);
2901
- React11.useEffect(() => {
3129
+ React12.useEffect(() => {
2902
3130
  function handleOpenBooking(e) {
2903
- const bookingId = e.detail?.bookingId;
2904
- if (bookingId) openModal("edit", { bookingId });
3131
+ const detail = e.detail ?? {};
3132
+ const externalSource = detail.source ? { type: String(detail.source), id: detail.waitlistId, waitlistId: detail.waitlistId } : void 0;
3133
+ if (detail.bookingId) {
3134
+ openModal("edit", { bookingId: detail.bookingId, externalSource });
3135
+ return;
3136
+ }
3137
+ openModal("create", {
3138
+ prefill: detail.prefill ?? {
3139
+ clientId: detail.clientId,
3140
+ professionalId: detail.professionalId,
3141
+ locationId: detail.locationId,
3142
+ startsAt: detail.startsAt
3143
+ },
3144
+ externalSource
3145
+ });
2905
3146
  }
2906
3147
  window.addEventListener("agenda:open-booking", handleOpenBooking);
2907
3148
  return () => window.removeEventListener("agenda:open-booking", handleOpenBooking);
2908
3149
  }, [openModal]);
2909
- const dayHeaderContent = React11.useCallback((arg) => {
3150
+ const dayHeaderContent = React12.useCallback((arg) => {
2910
3151
  const date = arg.date;
2911
3152
  const dateLocale = currentLocale === "pt-BR" ? "pt-BR" : "en";
2912
3153
  const wrapStyle = { display: "flex", flexDirection: "column", alignItems: "center", lineHeight: 1.15 };
@@ -2924,7 +3165,7 @@ function CalendarView() {
2924
3165
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: dateStyle, children: `${date.getMonth() + 1}/${date.getDate()}` })
2925
3166
  ] });
2926
3167
  }, [dayHeaderCompact, currentLocale]);
2927
- const resourceLabelContent = React11.useCallback((arg) => {
3168
+ const resourceLabelContent = React12.useCallback((arg) => {
2928
3169
  const { id, title, extendedProps } = arg.resource;
2929
3170
  const initials = title.split(" ").map((n) => n[0]).join("").slice(0, 2);
2930
3171
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-2 py-1.5", children: [
@@ -2944,7 +3185,7 @@ function CalendarView() {
2944
3185
  ] })
2945
3186
  ] });
2946
3187
  }, []);
2947
- const eventContent = React11.useCallback((arg) => {
3188
+ const eventContent = React12.useCallback((arg) => {
2948
3189
  if (arg.event.extendedProps?.isLocationLabel) {
2949
3190
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1.5 py-0.5 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-medium truncate", children: arg.event.title }) });
2950
3191
  }
@@ -2975,110 +3216,135 @@ function CalendarView() {
2975
3216
  }
2976
3217
  ),
2977
3218
  /* @__PURE__ */ jsxRuntime.jsx(MiniCalendar, { selectedDate, onDateSelect: handleMiniDateSelect }),
2978
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-border my-4" }),
2979
- /* @__PURE__ */ jsxRuntime.jsxs(
2980
- "button",
2981
- {
2982
- onClick: () => setProfSectionOpen((p) => !p),
2983
- className: "flex items-center justify-between w-full text-xs font-medium text-muted-foreground hover:text-foreground py-1 transition-colors",
2984
- children: [
2985
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Agendas" }),
2986
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-3.5 w-3.5 transition-transform ${profSectionOpen ? "" : "-rotate-90"}` })
2987
- ]
2988
- }
2989
- ),
2990
- profSectionOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5 space-y-0.5", children: professionals.map((prof) => {
2991
- const active = selectedProfIds.length === 0 || selectedProfIds.includes(prof.id);
2992
- return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2.5 rounded px-1 py-1 cursor-pointer hover:bg-muted/50 transition-colors", children: [
2993
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border-2 transition-colors ${active ? "border-primary bg-primary" : "border-muted-foreground/30"}`, children: active && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-2.5 w-2.5 text-primary-foreground", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 6l3 3 5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
2994
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs truncate ${active ? "text-foreground" : "text-muted-foreground"}`, children: prof.name }),
2995
- /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", className: "sr-only", checked: active, onChange: () => toggleProfessional(prof.id) })
2996
- ] }, prof.id);
2997
- }) }),
2998
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-border my-4" }),
2999
- /* @__PURE__ */ jsxRuntime.jsxs(
3000
- "button",
3001
- {
3002
- onClick: () => setStatusSectionOpen((p) => !p),
3003
- className: "flex items-center justify-between w-full text-xs font-medium text-muted-foreground hover:text-foreground py-1 transition-colors",
3004
- children: [
3005
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Status" }),
3006
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-3.5 w-3.5 transition-transform ${statusSectionOpen ? "" : "-rotate-90"}` })
3007
- ]
3008
- }
3009
- ),
3010
- statusSectionOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5 space-y-0.5", children: config.statuses.map((st) => {
3011
- const active = selectedStatuses.length === 0 || selectedStatuses.includes(st.value);
3012
- return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2.5 rounded px-1 py-1 cursor-pointer hover:bg-muted/50 transition-colors", children: [
3013
- /* @__PURE__ */ jsxRuntime.jsx(
3014
- "span",
3015
- {
3016
- className: "flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border-2 transition-colors",
3017
- style: { borderColor: active ? st.color : st.color + "40", backgroundColor: active ? st.color : "transparent" },
3018
- children: active && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-2.5 w-2.5 text-white", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 6l3 3 5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
3019
- }
3020
- ),
3021
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs ${active ? "text-foreground" : "text-muted-foreground"}`, children: st.label }),
3022
- /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", className: "sr-only", checked: active, onChange: () => toggleStatus(st.value) })
3023
- ] }, st.value);
3024
- }) })
3219
+ config.capabilities.professional && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3220
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-border my-4" }),
3221
+ /* @__PURE__ */ jsxRuntime.jsxs(
3222
+ "button",
3223
+ {
3224
+ onClick: () => setProfSectionOpen((p) => !p),
3225
+ className: "flex items-center justify-between w-full text-xs font-medium text-muted-foreground hover:text-foreground py-1 transition-colors",
3226
+ children: [
3227
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Agendas" }),
3228
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-3.5 w-3.5 transition-transform ${profSectionOpen ? "" : "-rotate-90"}` })
3229
+ ]
3230
+ }
3231
+ ),
3232
+ profSectionOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5 space-y-0.5", children: professionals.map((prof) => {
3233
+ const active = selectedProfIds.length === 0 || selectedProfIds.includes(prof.id);
3234
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2.5 rounded px-1 py-1 cursor-pointer hover:bg-muted/50 transition-colors", children: [
3235
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border-2 transition-colors ${active ? "border-primary bg-primary" : "border-muted-foreground/30"}`, children: active && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-2.5 w-2.5 text-primary-foreground", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 6l3 3 5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
3236
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs truncate ${active ? "text-foreground" : "text-muted-foreground"}`, children: prof.name }),
3237
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", className: "sr-only", checked: active, onChange: () => toggleProfessional(prof.id) })
3238
+ ] }, prof.id);
3239
+ }) })
3240
+ ] }),
3241
+ config.capabilities.status && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3242
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-border my-4" }),
3243
+ /* @__PURE__ */ jsxRuntime.jsxs(
3244
+ "button",
3245
+ {
3246
+ onClick: () => setStatusSectionOpen((p) => !p),
3247
+ className: "flex items-center justify-between w-full text-xs font-medium text-muted-foreground hover:text-foreground py-1 transition-colors",
3248
+ children: [
3249
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Status" }),
3250
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-3.5 w-3.5 transition-transform ${statusSectionOpen ? "" : "-rotate-90"}` })
3251
+ ]
3252
+ }
3253
+ ),
3254
+ statusSectionOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1.5 space-y-0.5", children: config.statuses.map((st) => {
3255
+ const active = selectedStatuses.length === 0 || selectedStatuses.includes(st.value);
3256
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2.5 rounded px-1 py-1 cursor-pointer hover:bg-muted/50 transition-colors", children: [
3257
+ /* @__PURE__ */ jsxRuntime.jsx(
3258
+ "span",
3259
+ {
3260
+ className: "flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border-2 transition-colors",
3261
+ style: { borderColor: active ? st.color : st.color + "40", backgroundColor: active ? st.color : "transparent" },
3262
+ children: active && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-2.5 w-2.5 text-white", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 6l3 3 5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
3263
+ }
3264
+ ),
3265
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs ${active ? "text-foreground" : "text-muted-foreground"}`, children: st.label }),
3266
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", className: "sr-only", checked: active, onChange: () => toggleStatus(st.value) })
3267
+ ] }, st.value);
3268
+ }) })
3269
+ ] })
3025
3270
  ] }),
3026
3271
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col min-w-0 overflow-hidden", children: [
3027
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 h-12 shrink-0 border-b", children: [
3028
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3029
- /* @__PURE__ */ jsxRuntime.jsx(
3030
- "button",
3031
- {
3032
- onClick: () => openModal("create"),
3033
- className: "md:hidden flex h-8 w-8 items-center justify-center rounded-full bg-primary text-primary-foreground",
3034
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" })
3035
- }
3036
- ),
3037
- /* @__PURE__ */ jsxRuntime.jsx(
3038
- "button",
3039
- {
3040
- onClick: handleToday,
3041
- 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",
3042
- children: t("agenda.toolbar.today")
3043
- }
3044
- ),
3045
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
3046
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handlePrev, className: "flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" }) }),
3047
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleNext, className: "flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" }) })
3272
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shrink-0 border-b", children: [
3273
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pl-4 pr-14 md:px-4 h-12", children: [
3274
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
3275
+ /* @__PURE__ */ jsxRuntime.jsx(
3276
+ "button",
3277
+ {
3278
+ onClick: () => openModal("create"),
3279
+ className: "md:hidden flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground",
3280
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" })
3281
+ }
3282
+ ),
3283
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${mobileGridActive ? "flex" : "hidden"} md:flex items-center gap-3`, children: [
3284
+ /* @__PURE__ */ jsxRuntime.jsx(
3285
+ "button",
3286
+ {
3287
+ onClick: handleToday,
3288
+ 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",
3289
+ children: t("agenda.toolbar.today")
3290
+ }
3291
+ ),
3292
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
3293
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handlePrev, className: "flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" }) }),
3294
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleNext, className: "flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" }) })
3295
+ ] })
3296
+ ] }),
3297
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base sm:text-lg font-normal truncate", children: isMobile && !mobileGridActive ? t("agenda.calendar.viewAgenda") : calendarTitle })
3048
3298
  ] }),
3049
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base sm:text-lg font-normal truncate", children: calendarTitle })
3299
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
3300
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:flex items-center gap-1", children: viewOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
3301
+ "button",
3302
+ {
3303
+ onClick: () => handleViewChange(opt.key),
3304
+ 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"}`,
3305
+ children: t(opt.labelKey)
3306
+ },
3307
+ opt.key
3308
+ )) }),
3309
+ /* @__PURE__ */ jsxRuntime.jsx(
3310
+ ui.Button,
3311
+ {
3312
+ variant: "outline",
3313
+ size: "icon",
3314
+ onClick: () => window.location.hash = "/settings/agenda",
3315
+ title: "Agenda Settings",
3316
+ className: "ml-1",
3317
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, { className: "h-4 w-4" })
3318
+ }
3319
+ )
3320
+ ] })
3050
3321
  ] }),
3051
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
3052
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:flex items-center gap-1", children: VIEW_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
3053
- "button",
3054
- {
3055
- onClick: () => handleViewChange(opt.key),
3056
- 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"}`,
3057
- children: t(opt.labelKey)
3058
- },
3059
- opt.key
3060
- )) }),
3061
- /* @__PURE__ */ jsxRuntime.jsx(
3062
- ui.Button,
3063
- {
3064
- variant: "outline",
3065
- size: "icon",
3066
- onClick: () => window.location.hash = "/settings/agenda",
3067
- title: "Agenda Settings",
3068
- className: "ml-1",
3069
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, { className: "h-4 w-4" })
3070
- }
3071
- )
3072
- ] })
3322
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex md:hidden items-center gap-1 overflow-x-auto px-3 pb-2", children: mobileViewOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
3323
+ "button",
3324
+ {
3325
+ onClick: () => handleMobileViewChange(opt.key),
3326
+ 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"}`,
3327
+ children: t(opt.labelKey)
3328
+ },
3329
+ opt.key
3330
+ )) })
3073
3331
  ] }),
3074
- professionalsLoading && professionals.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(CalendarSkeleton, { profCount: 2 }) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: wrapperRef, className: "flex-1 overflow-hidden agenda-calendar", children: /* @__PURE__ */ jsxRuntime.jsx(
3332
+ isMobile && !mobileGridActive ? /* @__PURE__ */ jsxRuntime.jsx(
3333
+ AgendaMobileList,
3334
+ {
3335
+ bookings,
3336
+ statusColors,
3337
+ currencyCode: config.currency.code,
3338
+ onSelect: (b) => openModal("edit", { bookingId: b.id })
3339
+ }
3340
+ ) : professionalsLoading && professionals.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(CalendarSkeleton, { profCount: 2 }) : /* @__PURE__ */ jsxRuntime.jsx("div", { ref: wrapperRef, className: "flex-1 overflow-hidden agenda-calendar", children: /* @__PURE__ */ jsxRuntime.jsx(
3075
3341
  FullCalendar__default.default,
3076
3342
  {
3077
3343
  ref: calendarRef,
3078
3344
  plugins: [dayGridPlugin__default.default, timeGridPlugin__default.default, resourceTimeGridPlugin__default.default, interactionPlugin__default.default, listPlugin__default.default],
3079
- initialView: prefs.get("calendarView") === "timeGridWeek" ? "resourceTimeGridWeek" : prefs.get("calendarView"),
3345
+ initialView: mobileGridActive ? mobileView : effectiveInitialView,
3080
3346
  schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
3081
- resources,
3347
+ resources: resourceCapable ? resources : void 0,
3082
3348
  events,
3083
3349
  headerToolbar: false,
3084
3350
  locale: fcLocale,
@@ -3168,8 +3434,14 @@ function CalendarView() {
3168
3434
  bookingId: modalState.bookingId,
3169
3435
  prefill: modalState.prefill,
3170
3436
  initialTab: modalState.initialTab,
3437
+ externalSource: modalState.externalSource,
3171
3438
  onClose: closeModal,
3172
- onCreated: (newId) => {
3439
+ onCreated: (newId, source) => {
3440
+ if (config.onBookingCreated) {
3441
+ void Promise.resolve(config.onBookingCreated({ bookingId: newId, source })).catch((error) => {
3442
+ console.error("Failed to run agenda booking-created bridge", error);
3443
+ });
3444
+ }
3173
3445
  setTimeout(() => {
3174
3446
  const booking = bookings.find((b) => b.id === newId);
3175
3447
  if (!booking) return;
@@ -3195,862 +3467,12 @@ function CalendarView() {
3195
3467
  function AgendaPage({ config, provider: provider2, store }) {
3196
3468
  const runtime = core.usePluginRuntimeOptional();
3197
3469
  const tenantId = runtime?.context.tenant?.id;
3198
- React11__default.default.useEffect(() => {
3470
+ React12__default.default.useEffect(() => {
3199
3471
  chunkSYDBDI3Q_cjs.setAgendaTenantId(tenantId);
3200
3472
  return () => chunkSYDBDI3Q_cjs.setAgendaTenantId(void 0);
3201
3473
  }, [tenantId]);
3202
3474
  return /* @__PURE__ */ jsxRuntime.jsx(AgendaContextProvider, { config, provider: provider2, store, children: /* @__PURE__ */ jsxRuntime.jsx(CalendarView, {}) });
3203
3475
  }
3204
- function getTenantId() {
3205
- return chunkSYDBDI3Q_cjs.getAgendaTenantId() ?? core.getActiveTenantId();
3206
- }
3207
- function snakeToCamel(obj) {
3208
- const result = {};
3209
- for (const [key, value] of Object.entries(obj)) {
3210
- result[key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())] = value;
3211
- }
3212
- return result;
3213
- }
3214
- function getClients() {
3215
- const supabase = core.getSupabaseClientOptional();
3216
- if (!supabase) throw new Error("Supabase not initialized");
3217
- return { core: supabase.schema("saas_core"), pub: supabase };
3218
- }
3219
- function mapBooking(row) {
3220
- return snakeToCamel(row);
3221
- }
3222
- function mapSchedule(row) {
3223
- return snakeToCamel(row);
3224
- }
3225
- function addMinutes(iso, minutes) {
3226
- const d = new Date(iso);
3227
- d.setMinutes(d.getMinutes() + minutes);
3228
- return d.toISOString();
3229
- }
3230
- function unique(values) {
3231
- return Array.from(new Set(values.filter((value) => !!value)));
3232
- }
3233
- function indexById(rows) {
3234
- return new Map((rows ?? []).filter((row) => row?.id).map((row) => [row.id, row]));
3235
- }
3236
- function groupBy(rows, getKey) {
3237
- const grouped = /* @__PURE__ */ new Map();
3238
- for (const row of rows) {
3239
- const key = getKey(row);
3240
- if (!key) continue;
3241
- grouped.set(key, [...grouped.get(key) ?? [], row]);
3242
- }
3243
- return grouped;
3244
- }
3245
- function getDurationMinutes(startsAt, endsAt) {
3246
- if (!endsAt) return null;
3247
- return Math.round((new Date(endsAt).getTime() - new Date(startsAt).getTime()) / 6e4);
3248
- }
3249
- function mergeBookings(viewBookings, canonicalBookings) {
3250
- const canonicalIds = new Set(canonicalBookings.map((booking) => booking.id));
3251
- const canonicalOrderIds = new Set(canonicalBookings.map((booking) => booking.orderId).filter(Boolean));
3252
- const merged = [
3253
- ...canonicalBookings,
3254
- ...viewBookings.filter((booking) => {
3255
- if (canonicalIds.has(booking.id)) return false;
3256
- if (booking.orderId && canonicalOrderIds.has(booking.orderId)) return false;
3257
- if (canonicalOrderIds.has(booking.id)) return false;
3258
- return true;
3259
- })
3260
- ];
3261
- return merged.sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
3262
- }
3263
- async function hydrateCanonicalBookings(core, bookings) {
3264
- const personIds = unique(bookings.flatMap((booking) => [booking.party_id, booking.assignee_id]));
3265
- const locationIds = unique(bookings.map((booking) => booking.location_id));
3266
- const orderIds = unique(bookings.map((booking) => booking.order_id));
3267
- unique(bookings.map((booking) => booking.id));
3268
- const [
3269
- { data: persons, error: personsError },
3270
- { data: locations, error: locationsError },
3271
- { data: orders, error: ordersError },
3272
- { data: items, error: itemsError }
3273
- ] = await Promise.all([
3274
- personIds.length ? core.from("persons").select("id, name, phone, email, avatar_url").in("id", personIds) : Promise.resolve({ data: [], error: null }),
3275
- locationIds.length ? core.from("locations").select("id, name").in("id", locationIds) : Promise.resolve({ data: [], error: null }),
3276
- orderIds.length ? core.from("orders").select("id, total, status").in("id", orderIds) : Promise.resolve({ data: [], error: null }),
3277
- orderIds.length ? core.from("order_items").select("id, order_id, service_id, name, duration_minutes, unit_price, assignee_id, sort_order").in("order_id", orderIds) : Promise.resolve({ data: [], error: null })
3278
- ]);
3279
- if (personsError) throw personsError;
3280
- if (locationsError) throw locationsError;
3281
- if (ordersError) throw ordersError;
3282
- if (itemsError) throw itemsError;
3283
- const personsById = indexById(persons);
3284
- const locationsById = indexById(locations);
3285
- const ordersById = indexById(orders);
3286
- const itemsByOrderId = groupBy(items ?? [], (item) => item.order_id);
3287
- return bookings.map((booking) => {
3288
- const client = booking.party_id ? personsById.get(booking.party_id) : null;
3289
- const professional = booking.assignee_id ? personsById.get(booking.assignee_id) : null;
3290
- const location = booking.location_id ? locationsById.get(booking.location_id) : null;
3291
- const order = booking.order_id ? ordersById.get(booking.order_id) : null;
3292
- const bookingItems = ((booking.order_id ? itemsByOrderId.get(booking.order_id) : null) ?? []).sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)).map((item) => ({
3293
- id: item.id,
3294
- serviceId: item.service_id,
3295
- name: item.name,
3296
- durationMinutes: item.duration_minutes,
3297
- price: item.unit_price,
3298
- assigneeId: item.assignee_id
3299
- }));
3300
- const itemDuration = bookingItems.reduce((sum, item) => sum + (item.durationMinutes ?? 0), 0);
3301
- return {
3302
- id: booking.id,
3303
- tenantId: booking.tenant_id,
3304
- kind: booking.kind,
3305
- startsAt: booking.starts_at,
3306
- endsAt: booking.ends_at,
3307
- status: booking.status,
3308
- notes: booking.notes,
3309
- orderId: booking.order_id,
3310
- locationId: booking.location_id,
3311
- metadata: booking.metadata ?? {},
3312
- clientId: booking.party_id,
3313
- clientName: client?.name ?? null,
3314
- clientPhone: client?.phone ?? null,
3315
- clientEmail: client?.email ?? null,
3316
- clientAvatarUrl: client?.avatar_url ?? null,
3317
- professionalId: booking.assignee_id,
3318
- professionalName: professional?.name ?? null,
3319
- professionalAvatarUrl: professional?.avatar_url ?? null,
3320
- locationName: location?.name ?? null,
3321
- orderTotal: order?.total ?? null,
3322
- orderStatus: order?.status ?? null,
3323
- services: bookingItems.length ? bookingItems : null,
3324
- totalDurationMinutes: itemDuration || getDurationMinutes(booking.starts_at, booking.ends_at),
3325
- createdAt: booking.created_at,
3326
- updatedAt: booking.updated_at
3327
- };
3328
- });
3329
- }
3330
- async function getCanonicalBookings(core, query) {
3331
- let q = core.from("bookings").select("id, tenant_id, kind, starts_at, ends_at, status, notes, order_id, location_id, metadata, party_id, assignee_id, created_at, updated_at").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
3332
- if (query.kind) q = q.eq("kind", query.kind);
3333
- if (query.statuses?.length) q = q.in("status", query.statuses);
3334
- if (query.professionalIds?.length) q = q.in("assignee_id", query.professionalIds);
3335
- if (query.locationId) q = q.eq("location_id", query.locationId);
3336
- const { data, error } = await q.order("starts_at", { ascending: true });
3337
- if (error) throw error;
3338
- return hydrateCanonicalBookings(core, data ?? []);
3339
- }
3340
- async function getCanonicalBookingById(core, id) {
3341
- const columns = "id, tenant_id, kind, starts_at, ends_at, status, notes, order_id, location_id, metadata, party_id, assignee_id, created_at, updated_at";
3342
- let { data, error } = await core.from("bookings").select(columns).eq("id", id).maybeSingle();
3343
- if (error) throw error;
3344
- if (!data) {
3345
- const fallback = await core.from("bookings").select(columns).eq("order_id", id).maybeSingle();
3346
- if (fallback.error) throw fallback.error;
3347
- data = fallback.data;
3348
- }
3349
- if (!data) return null;
3350
- const [booking] = await hydrateCanonicalBookings(core, [data]);
3351
- return booking ?? null;
3352
- }
3353
- function createSupabaseAgendaProvider() {
3354
- return {
3355
- // v_bookings is in public schema (PostgREST default)
3356
- async getBookings(query) {
3357
- const { core, pub } = getClients();
3358
- let q = pub.from("v_bookings").select("*").gte("starts_at", query.dateRange.start).lte("starts_at", query.dateRange.end);
3359
- if (query.kind) q = q.eq("kind", query.kind);
3360
- if (query.statuses?.length) q = q.in("status", query.statuses);
3361
- if (query.professionalIds?.length) q = q.in("professional_id", query.professionalIds);
3362
- if (query.locationId) q = q.eq("location_id", query.locationId);
3363
- q = q.order("starts_at", { ascending: true });
3364
- const { data, error } = await q;
3365
- if (error) throw error;
3366
- const viewBookings = (data ?? []).map(mapBooking);
3367
- const canonicalBookings = await getCanonicalBookings(core, query);
3368
- return mergeBookings(viewBookings, canonicalBookings);
3369
- },
3370
- async getBookingById(id) {
3371
- const { core, pub } = getClients();
3372
- const { data, error } = await pub.from("v_bookings").select("*").eq("id", id).maybeSingle();
3373
- if (error) throw error;
3374
- if (data) return mapBooking(data);
3375
- return getCanonicalBookingById(core, id);
3376
- },
3377
- async createBooking(input) {
3378
- const { core } = getClients();
3379
- const tenantId = getTenantId();
3380
- if (!tenantId) throw new Error("No active tenant");
3381
- const hasServices = input.services.length > 0;
3382
- const totalDuration = input.services.reduce((sum, s) => sum + s.durationMinutes, 0);
3383
- const totalPrice = input.services.reduce((sum, s) => sum + s.price, 0);
3384
- const endsAt = addMinutes(input.startsAt, totalDuration || 30);
3385
- const serviceNames = input.services.map((s) => s.name).join(", ");
3386
- let contactName;
3387
- if (input.clientId) {
3388
- const { data: person } = await core.from("persons").select("name").eq("id", input.clientId).single();
3389
- contactName = person?.name;
3390
- }
3391
- const { data: order, error: orderErr } = await core.from("orders").insert({
3392
- tenant_id: tenantId,
3393
- kind: input.kind ?? "appointment",
3394
- status: "scheduled",
3395
- party_id: input.clientId || null,
3396
- assignee_id: input.professionalId,
3397
- location_id: input.locationId ?? null,
3398
- subtotal: totalPrice,
3399
- total: totalPrice,
3400
- notes: input.notes ?? null,
3401
- metadata: {
3402
- source: "agenda",
3403
- ...serviceNames ? { serviceNames, itemsSummary: serviceNames } : {},
3404
- ...contactName ? { contactName } : {}
3405
- }
3406
- }).select("id").single();
3407
- if (orderErr) throw orderErr;
3408
- const { data: booking, error: bookingErr } = await core.from("bookings").insert({
3409
- tenant_id: tenantId,
3410
- kind: input.kind ?? "appointment",
3411
- party_id: input.clientId || null,
3412
- assignee_id: input.professionalId,
3413
- location_id: input.locationId ?? null,
3414
- order_id: order.id,
3415
- starts_at: input.startsAt,
3416
- ends_at: endsAt,
3417
- status: "scheduled",
3418
- notes: input.notes ?? null,
3419
- metadata: {
3420
- source: "agenda",
3421
- ...serviceNames ? { serviceNames, itemsSummary: serviceNames } : {},
3422
- ...contactName ? { contactName } : {}
3423
- }
3424
- }).select("id").single();
3425
- if (bookingErr) throw bookingErr;
3426
- if (hasServices) {
3427
- const orderItems = input.services.map((s, i) => ({
3428
- order_id: order.id,
3429
- service_id: s.serviceId,
3430
- name: s.name,
3431
- quantity: 1,
3432
- unit_price: s.price,
3433
- total: s.price,
3434
- sort_order: i,
3435
- duration_minutes: s.durationMinutes,
3436
- assignee_id: s.assigneeId ?? input.professionalId
3437
- }));
3438
- const { error: oiErr } = await core.from("order_items").insert(orderItems);
3439
- if (oiErr) throw oiErr;
3440
- }
3441
- const createdBooking = await this.getBookingById(booking.id);
3442
- if (!createdBooking) throw new Error("Created booking did not resolve from agenda read model");
3443
- return createdBooking;
3444
- },
3445
- async updateBooking(id, data) {
3446
- const { core } = getClients();
3447
- const { data: existing, error: existingErr } = await core.from("bookings").select("id, order_id, starts_at, assignee_id").eq("id", id).single();
3448
- if (existingErr) throw existingErr;
3449
- const updates = {};
3450
- if (data.startsAt) updates.starts_at = data.startsAt;
3451
- if (data.endsAt) updates.ends_at = data.endsAt;
3452
- if (data.status) {
3453
- updates.status = data.status;
3454
- }
3455
- if (data.notes !== void 0) updates.notes = data.notes;
3456
- if (data.professionalId) updates.assignee_id = data.professionalId;
3457
- if (data.clientId) updates.party_id = data.clientId;
3458
- if (data.locationId) updates.location_id = data.locationId;
3459
- if (data.services) {
3460
- const totalDuration = data.services.reduce((s, svc) => s + svc.durationMinutes, 0);
3461
- data.services.reduce((s, svc) => s + svc.price, 0);
3462
- const startsAt = data.startsAt ?? existing?.starts_at;
3463
- if (startsAt) {
3464
- const ends = new Date(new Date(startsAt).getTime() + totalDuration * 6e4);
3465
- updates.ends_at = ends.toISOString();
3466
- }
3467
- }
3468
- const effectiveAssigneeId = data.professionalId ?? existing?.assignee_id ?? null;
3469
- const { error } = await core.from("bookings").update(updates).eq("id", id);
3470
- if (error) throw error;
3471
- const orderId = existing?.order_id;
3472
- if (orderId) {
3473
- const orderUpdates = {};
3474
- if (data.notes !== void 0) orderUpdates.notes = data.notes;
3475
- if (data.professionalId) orderUpdates.assignee_id = data.professionalId;
3476
- if (data.clientId) orderUpdates.party_id = data.clientId;
3477
- if (data.locationId) orderUpdates.location_id = data.locationId;
3478
- if (data.services) {
3479
- const totalPrice = data.services.reduce((s, svc) => s + svc.price, 0);
3480
- orderUpdates.subtotal = totalPrice;
3481
- orderUpdates.total = totalPrice;
3482
- }
3483
- if (Object.keys(orderUpdates).length > 0) {
3484
- const { error: orderErr } = await core.from("orders").update(orderUpdates).eq("id", orderId);
3485
- if (orderErr) throw orderErr;
3486
- }
3487
- if (data.services) {
3488
- await core.from("order_items").delete().eq("order_id", orderId);
3489
- if (data.services.length > 0) {
3490
- const items = data.services.map((svc, idx) => ({
3491
- order_id: orderId,
3492
- service_id: svc.serviceId || null,
3493
- name: svc.name,
3494
- quantity: 1,
3495
- unit_price: svc.price,
3496
- total: svc.price,
3497
- sort_order: idx,
3498
- duration_minutes: svc.durationMinutes,
3499
- assignee_id: svc.assigneeId ?? effectiveAssigneeId
3500
- }));
3501
- const { error: itemErr } = await core.from("order_items").insert(items);
3502
- if (itemErr) throw itemErr;
3503
- }
3504
- }
3505
- }
3506
- return await this.getBookingById(id);
3507
- },
3508
- async deleteBooking(id) {
3509
- const { core, pub } = getClients();
3510
- const { data: booking, error: bookingErr } = await core.from("bookings").select("order_id").eq("id", id).single();
3511
- if (bookingErr) throw bookingErr;
3512
- const orderId = booking?.order_id;
3513
- const { count, error: movementErr } = orderId ? await pub.from("financial_movements").select("id", { count: "exact", head: true }).eq("invoice_id", orderId) : { count: 0, error: null };
3514
- if (movementErr) throw movementErr;
3515
- if (orderId && (count ?? 0) > 0) {
3516
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("invoice_id", orderId);
3517
- await core.from("orders").update({ status: "cancelled" }).eq("id", orderId);
3518
- const { error } = await core.from("bookings").update({ status: "cancelled" }).eq("id", id);
3519
- if (error) throw error;
3520
- } else {
3521
- if (orderId) {
3522
- await pub.from("financial_movements").delete().eq("invoice_id", orderId);
3523
- }
3524
- const { error } = await core.from("bookings").delete().eq("id", id);
3525
- if (error) throw error;
3526
- if (orderId) {
3527
- const { error: orderErr } = await core.from("orders").delete().eq("id", orderId);
3528
- if (orderErr) throw orderErr;
3529
- }
3530
- }
3531
- },
3532
- async updateBookingStatus(id, status) {
3533
- return this.updateBooking(id, { status });
3534
- },
3535
- async checkConflict(params) {
3536
- const { core } = getClients();
3537
- let q = core.from("bookings").select("id", { count: "exact", head: true }).eq("assignee_id", params.assigneeId).eq("kind", "appointment").not("status", "in", '("cancelled","no_show")').not("starts_at", "is", null).lt("starts_at", params.endsAt).gt("ends_at", params.startsAt);
3538
- if (params.excludeBookingId) {
3539
- q = q.neq("id", params.excludeBookingId);
3540
- }
3541
- const { count, error } = await q;
3542
- if (error) throw error;
3543
- return (count ?? 0) > 0;
3544
- },
3545
- async getAvailableSlots(params) {
3546
- const { pub } = getClients();
3547
- const { data, error } = await pub.rpc("get_available_slots", {
3548
- p_tenant_id: getTenantId(),
3549
- p_assignee_id: params.assigneeId,
3550
- p_date: params.date,
3551
- p_duration_minutes: params.durationMinutes,
3552
- p_slot_interval: params.slotInterval ?? 30
3553
- });
3554
- if (error) throw error;
3555
- return (data ?? []).map((row) => ({ start: row.slot_start, end: row.slot_end }));
3556
- },
3557
- async getSchedules(professionalId) {
3558
- const { core } = getClients();
3559
- let q = core.from("schedules").select("*").eq("kind", "working_hours");
3560
- if (professionalId) q = q.eq("assignee_id", professionalId);
3561
- q = q.order("day_of_week").order("starts_at");
3562
- const { data, error } = await q;
3563
- if (error) throw error;
3564
- return (data ?? []).map(mapSchedule);
3565
- },
3566
- async saveSchedule(input) {
3567
- const { core } = getClients();
3568
- const tenantId = getTenantId();
3569
- if (!tenantId) throw new Error("No active tenant");
3570
- const { data, error } = await core.from("schedules").insert({
3571
- tenant_id: tenantId,
3572
- kind: "working_hours",
3573
- assignee_id: input.assigneeId,
3574
- location_id: input.locationId ?? null,
3575
- day_of_week: input.dayOfWeek ?? null,
3576
- specific_date: input.specificDate ?? null,
3577
- starts_at: input.startsAt,
3578
- ends_at: input.endsAt,
3579
- is_active: input.isActive !== false,
3580
- metadata: input.metadata ?? {}
3581
- }).select("*").single();
3582
- if (error) throw error;
3583
- return mapSchedule(data);
3584
- },
3585
- async deleteSchedule(id) {
3586
- const { core } = getClients();
3587
- const { error } = await core.from("schedules").delete().eq("id", id);
3588
- if (error) throw error;
3589
- },
3590
- async getProfessionals() {
3591
- const { core } = getClients();
3592
- const { data, error } = await core.from("persons").select("id, name, avatar_url, is_active").eq("kind", "staff").eq("is_active", true).order("name");
3593
- if (error) throw error;
3594
- return (data ?? []).map((p) => ({
3595
- id: p.id,
3596
- name: p.name,
3597
- avatarUrl: p.avatar_url,
3598
- locationId: null,
3599
- locationName: null,
3600
- isActive: p.is_active
3601
- }));
3602
- },
3603
- async getLocations() {
3604
- const { core } = getClients();
3605
- const { data, error } = await core.from("locations").select("id, name").eq("is_active", true).order("name");
3606
- if (error) throw error;
3607
- return (data ?? []).map((l) => ({ id: l.id, name: l.name }));
3608
- },
3609
- async getConfirmationsPending(daysAhead = 2) {
3610
- const { pub } = getClients();
3611
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
3612
- const cutoff = /* @__PURE__ */ new Date();
3613
- cutoff.setDate(cutoff.getDate() + daysAhead);
3614
- const { data, error } = await pub.from("v_bookings").select("*").eq("status", "scheduled").gte("starts_at", now2).lte("starts_at", cutoff.toISOString()).order("starts_at");
3615
- if (error) throw error;
3616
- return (data ?? []).map(mapBooking);
3617
- },
3618
- async sendConfirmation(bookingId, channel) {
3619
- const { core } = getClients();
3620
- const { data: booking } = await core.from("bookings").select("metadata").eq("id", bookingId).single();
3621
- const metadata = booking?.metadata ?? {};
3622
- const confirmations = metadata.confirmations ?? [];
3623
- confirmations.push({ channel, sentAt: (/* @__PURE__ */ new Date()).toISOString(), status: "sent" });
3624
- await core.from("bookings").update({ metadata: { ...metadata, confirmations } }).eq("id", bookingId);
3625
- },
3626
- async completeBooking(bookingId) {
3627
- const { core } = getClients();
3628
- const { data: existing, error: existingErr } = await core.from("bookings").select("order_id").eq("id", bookingId).single();
3629
- if (existingErr) throw existingErr;
3630
- const { error } = await core.from("bookings").update({ status: "completed" }).eq("id", bookingId);
3631
- if (error) throw error;
3632
- const orderId = existing?.order_id;
3633
- const booking = await this.getBookingById(bookingId);
3634
- if (!booking) throw new Error("Booking not found");
3635
- return { booking, orderId: orderId ?? bookingId };
3636
- }
3637
- };
3638
- }
3639
-
3640
- // src/data/mock.ts
3641
- var nextId = 1;
3642
- function uid() {
3643
- return `mock-${nextId++}`;
3644
- }
3645
- function now() {
3646
- return (/* @__PURE__ */ new Date()).toISOString();
3647
- }
3648
- function addMinutes2(date, minutes) {
3649
- const d = new Date(date);
3650
- d.setMinutes(d.getMinutes() + minutes);
3651
- return d.toISOString();
3652
- }
3653
- function timeOverlaps(aStart, aEnd, bStart, bEnd) {
3654
- return aStart < bEnd && aEnd > bStart;
3655
- }
3656
- var SAMPLE_PROFESSIONALS = [
3657
- { id: "prof-1", name: "Ana Silva", avatarUrl: null, locationId: "loc-1", locationName: "Main Branch", isActive: true },
3658
- { id: "prof-2", name: "Carlos Santos", avatarUrl: null, locationId: "loc-1", locationName: "Main Branch", isActive: true },
3659
- { id: "prof-3", name: "Maria Oliveira", avatarUrl: null, locationId: "loc-1", locationName: "Main Branch", isActive: true }
3660
- ];
3661
- function createSampleBookings() {
3662
- const today = /* @__PURE__ */ new Date();
3663
- const year = today.getFullYear();
3664
- const month = today.getMonth();
3665
- const day = today.getDate();
3666
- function dt(hour, minute = 0) {
3667
- return new Date(year, month, day, hour, minute).toISOString();
3668
- }
3669
- return [
3670
- {
3671
- id: "bk-1",
3672
- tenantId: "mock",
3673
- kind: "appointment",
3674
- startsAt: dt(9, 0),
3675
- endsAt: dt(10, 30),
3676
- status: "confirmed",
3677
- notes: null,
3678
- orderId: "ord-1",
3679
- locationId: "loc-1",
3680
- metadata: {},
3681
- clientId: "cl-1",
3682
- clientName: "Sarah Johnson",
3683
- clientPhone: "(11) 99999-1111",
3684
- clientEmail: "sarah@email.com",
3685
- clientAvatarUrl: null,
3686
- professionalId: "prof-1",
3687
- professionalName: "Ana Silva",
3688
- professionalAvatarUrl: null,
3689
- locationName: "Main Branch",
3690
- orderTotal: 250,
3691
- orderStatus: "draft",
3692
- services: [{ id: "bi-1", serviceId: "svc-1", name: "Balayage", durationMinutes: 90, price: 250, assigneeId: "prof-1" }],
3693
- totalDurationMinutes: 90,
3694
- createdAt: now(),
3695
- updatedAt: now()
3696
- },
3697
- {
3698
- id: "bk-2",
3699
- tenantId: "mock",
3700
- kind: "appointment",
3701
- startsAt: dt(10, 0),
3702
- endsAt: dt(11, 0),
3703
- status: "scheduled",
3704
- notes: null,
3705
- orderId: "ord-2",
3706
- locationId: "loc-1",
3707
- metadata: {},
3708
- clientId: "cl-2",
3709
- clientName: "Emily Carter",
3710
- clientPhone: "(11) 99999-2222",
3711
- clientEmail: "emily@email.com",
3712
- clientAvatarUrl: null,
3713
- professionalId: "prof-2",
3714
- professionalName: "Carlos Santos",
3715
- professionalAvatarUrl: null,
3716
- locationName: "Main Branch",
3717
- orderTotal: 80,
3718
- orderStatus: "draft",
3719
- services: [{ id: "bi-2", serviceId: "svc-2", name: "Haircut", durationMinutes: 60, price: 80, assigneeId: "prof-2" }],
3720
- totalDurationMinutes: 60,
3721
- createdAt: now(),
3722
- updatedAt: now()
3723
- },
3724
- {
3725
- id: "bk-3",
3726
- tenantId: "mock",
3727
- kind: "appointment",
3728
- startsAt: dt(14, 0),
3729
- endsAt: dt(15, 0),
3730
- status: "confirmed",
3731
- notes: "First visit",
3732
- orderId: "ord-3",
3733
- locationId: "loc-1",
3734
- metadata: {},
3735
- clientId: "cl-3",
3736
- clientName: "Rachel Kim",
3737
- clientPhone: "(11) 99999-3333",
3738
- clientEmail: null,
3739
- clientAvatarUrl: null,
3740
- professionalId: "prof-3",
3741
- professionalName: "Maria Oliveira",
3742
- professionalAvatarUrl: null,
3743
- locationName: "Main Branch",
3744
- orderTotal: 120,
3745
- orderStatus: "draft",
3746
- services: [{ id: "bi-3", serviceId: "svc-3", name: "Facial Treatment", durationMinutes: 60, price: 120, assigneeId: "prof-3" }],
3747
- totalDurationMinutes: 60,
3748
- createdAt: now(),
3749
- updatedAt: now()
3750
- },
3751
- {
3752
- id: "bk-4",
3753
- tenantId: "mock",
3754
- kind: "appointment",
3755
- startsAt: dt(11, 0),
3756
- endsAt: dt(12, 0),
3757
- status: "scheduled",
3758
- notes: null,
3759
- orderId: "ord-4",
3760
- locationId: "loc-1",
3761
- metadata: {},
3762
- clientId: "cl-4",
3763
- clientName: "Jessica Lima",
3764
- clientPhone: "(11) 99999-4444",
3765
- clientEmail: null,
3766
- clientAvatarUrl: null,
3767
- professionalId: "prof-1",
3768
- professionalName: "Ana Silva",
3769
- professionalAvatarUrl: null,
3770
- locationName: "Main Branch",
3771
- orderTotal: 60,
3772
- orderStatus: "draft",
3773
- services: [{ id: "bi-4", serviceId: "svc-4", name: "Manicure", durationMinutes: 60, price: 60, assigneeId: "prof-1" }],
3774
- totalDurationMinutes: 60,
3775
- createdAt: now(),
3776
- updatedAt: now()
3777
- },
3778
- {
3779
- id: "bk-5",
3780
- tenantId: "mock",
3781
- kind: "appointment",
3782
- startsAt: dt(15, 30),
3783
- endsAt: dt(17, 0),
3784
- status: "confirmed",
3785
- notes: null,
3786
- orderId: "ord-5",
3787
- locationId: "loc-1",
3788
- metadata: {},
3789
- clientId: "cl-5",
3790
- clientName: "Amanda Wright",
3791
- clientPhone: "(11) 99999-5555",
3792
- clientEmail: "amanda@email.com",
3793
- clientAvatarUrl: null,
3794
- professionalId: "prof-2",
3795
- professionalName: "Carlos Santos",
3796
- professionalAvatarUrl: null,
3797
- locationName: "Main Branch",
3798
- orderTotal: 350,
3799
- orderStatus: "draft",
3800
- services: [
3801
- { id: "bi-5", serviceId: "svc-1", name: "Highlights", durationMinutes: 60, price: 200, assigneeId: "prof-2" },
3802
- { id: "bi-6", serviceId: "svc-2", name: "Haircut", durationMinutes: 30, price: 150, assigneeId: "prof-2" }
3803
- ],
3804
- totalDurationMinutes: 90,
3805
- createdAt: now(),
3806
- updatedAt: now()
3807
- }
3808
- ];
3809
- }
3810
- function createSampleSchedules() {
3811
- const schedules = [];
3812
- for (const prof of SAMPLE_PROFESSIONALS) {
3813
- for (let day = 1; day <= 5; day++) {
3814
- schedules.push({
3815
- id: uid(),
3816
- tenantId: "mock",
3817
- kind: "working_hours",
3818
- assigneeId: prof.id,
3819
- locationId: prof.locationId,
3820
- dayOfWeek: day,
3821
- specificDate: null,
3822
- startsAt: "08:00",
3823
- endsAt: "12:00",
3824
- isActive: true,
3825
- metadata: {}
3826
- });
3827
- schedules.push({
3828
- id: uid(),
3829
- tenantId: "mock",
3830
- kind: "working_hours",
3831
- assigneeId: prof.id,
3832
- locationId: prof.locationId,
3833
- dayOfWeek: day,
3834
- specificDate: null,
3835
- startsAt: "13:00",
3836
- endsAt: "18:00",
3837
- isActive: true,
3838
- metadata: {}
3839
- });
3840
- }
3841
- schedules.push({
3842
- id: uid(),
3843
- tenantId: "mock",
3844
- kind: "working_hours",
3845
- assigneeId: prof.id,
3846
- locationId: prof.locationId,
3847
- dayOfWeek: 6,
3848
- specificDate: null,
3849
- startsAt: "08:00",
3850
- endsAt: "14:00",
3851
- isActive: true,
3852
- metadata: {}
3853
- });
3854
- }
3855
- return schedules;
3856
- }
3857
- function createMockAgendaProvider() {
3858
- const bookings = createSampleBookings();
3859
- const schedules = createSampleSchedules();
3860
- const professionals = [...SAMPLE_PROFESSIONALS];
3861
- return {
3862
- async getLocations() {
3863
- return [];
3864
- },
3865
- async getBookings(query) {
3866
- return bookings.filter((b) => {
3867
- if (query.kind && b.kind !== query.kind) return false;
3868
- if (query.statuses?.length && !query.statuses.includes(b.status)) return false;
3869
- if (query.professionalIds?.length && !query.professionalIds.includes(b.professionalId ?? "")) return false;
3870
- if (query.locationId && b.locationId !== query.locationId) return false;
3871
- if (query.dateRange) {
3872
- if (b.startsAt > query.dateRange.end || (b.endsAt ?? b.startsAt) < query.dateRange.start) return false;
3873
- }
3874
- return true;
3875
- });
3876
- },
3877
- async getBookingById(id) {
3878
- return bookings.find((b) => b.id === id) ?? null;
3879
- },
3880
- async createBooking(input) {
3881
- const totalDuration = input.services.reduce((sum, s) => sum + s.durationMinutes, 0);
3882
- const totalPrice = input.services.reduce((sum, s) => sum + s.price, 0);
3883
- const booking = {
3884
- id: uid(),
3885
- tenantId: "mock",
3886
- kind: input.kind ?? "appointment",
3887
- startsAt: input.startsAt,
3888
- endsAt: addMinutes2(input.startsAt, totalDuration || 30),
3889
- status: "scheduled",
3890
- notes: input.notes ?? null,
3891
- orderId: input.services.length > 0 ? uid() : null,
3892
- locationId: input.locationId ?? null,
3893
- metadata: {},
3894
- clientId: input.clientId,
3895
- clientName: "New Client",
3896
- clientPhone: null,
3897
- clientEmail: null,
3898
- clientAvatarUrl: null,
3899
- professionalId: input.professionalId,
3900
- professionalName: professionals.find((p) => p.id === input.professionalId)?.name ?? "Unknown",
3901
- professionalAvatarUrl: null,
3902
- locationName: null,
3903
- orderTotal: totalPrice,
3904
- orderStatus: "draft",
3905
- services: input.services.map((s) => ({
3906
- id: uid(),
3907
- serviceId: s.serviceId,
3908
- name: s.name,
3909
- durationMinutes: s.durationMinutes,
3910
- price: s.price,
3911
- assigneeId: s.assigneeId ?? input.professionalId
3912
- })),
3913
- totalDurationMinutes: totalDuration,
3914
- createdAt: now(),
3915
- updatedAt: now()
3916
- };
3917
- bookings.push(booking);
3918
- return booking;
3919
- },
3920
- async updateBooking(id, data) {
3921
- const idx = bookings.findIndex((b) => b.id === id);
3922
- if (idx === -1) throw new Error("Booking not found");
3923
- const existing = bookings[idx];
3924
- const serviceUpdates = {};
3925
- if (data.services) {
3926
- serviceUpdates.services = data.services.map((s) => ({
3927
- id: uid(),
3928
- serviceId: s.serviceId,
3929
- name: s.name,
3930
- durationMinutes: s.durationMinutes,
3931
- price: s.price,
3932
- assigneeId: s.assigneeId ?? existing.professionalId ?? ""
3933
- }));
3934
- serviceUpdates.totalDurationMinutes = data.services.reduce((sum, s) => sum + s.durationMinutes, 0);
3935
- serviceUpdates.orderTotal = data.services.reduce((sum, s) => sum + s.price, 0);
3936
- }
3937
- const updated = {
3938
- ...existing,
3939
- ...data.startsAt && { startsAt: data.startsAt },
3940
- ...data.endsAt && { endsAt: data.endsAt },
3941
- ...data.status && { status: data.status },
3942
- ...data.notes !== void 0 && { notes: data.notes ?? null },
3943
- ...data.professionalId && { professionalId: data.professionalId },
3944
- ...data.clientId && { clientId: data.clientId },
3945
- ...data.locationId && { locationId: data.locationId },
3946
- ...serviceUpdates,
3947
- updatedAt: now()
3948
- };
3949
- bookings[idx] = updated;
3950
- return updated;
3951
- },
3952
- async deleteBooking(id) {
3953
- const idx = bookings.findIndex((b) => b.id === id);
3954
- if (idx !== -1) bookings.splice(idx, 1);
3955
- },
3956
- async updateBookingStatus(id, status) {
3957
- const idx = bookings.findIndex((b) => b.id === id);
3958
- if (idx === -1) throw new Error("Booking not found");
3959
- bookings[idx] = { ...bookings[idx], status, updatedAt: now() };
3960
- return bookings[idx];
3961
- },
3962
- async checkConflict(params) {
3963
- return bookings.some((b) => {
3964
- if (params.excludeBookingId && b.id === params.excludeBookingId) return false;
3965
- if (b.professionalId !== params.assigneeId) return false;
3966
- if (b.status === "cancelled" || b.status === "no_show") return false;
3967
- return timeOverlaps(b.startsAt, b.endsAt ?? b.startsAt, params.startsAt, params.endsAt);
3968
- });
3969
- },
3970
- async getAvailableSlots(params) {
3971
- const date = new Date(params.date);
3972
- const dayOfWeek = date.getDay();
3973
- const interval = params.slotInterval ?? 30;
3974
- const daySchedules = schedules.filter(
3975
- (s) => s.assigneeId === params.assigneeId && s.isActive && (s.dayOfWeek === dayOfWeek || s.specificDate === params.date)
3976
- );
3977
- const slots = [];
3978
- for (const sched of daySchedules) {
3979
- const [startH, startM] = sched.startsAt.split(":").map(Number);
3980
- const [endH, endM] = sched.endsAt.split(":").map(Number);
3981
- const schedStart = new Date(date);
3982
- schedStart.setHours(startH, startM, 0, 0);
3983
- const schedEnd = new Date(date);
3984
- schedEnd.setHours(endH, endM, 0, 0);
3985
- let slotStart = new Date(schedStart);
3986
- while (true) {
3987
- const slotEnd = new Date(slotStart.getTime() + params.durationMinutes * 6e4);
3988
- if (slotEnd > schedEnd) break;
3989
- const startIso = slotStart.toISOString();
3990
- const endIso = slotEnd.toISOString();
3991
- const hasConflict = bookings.some((b) => {
3992
- if (b.professionalId !== params.assigneeId) return false;
3993
- if (b.status === "cancelled" || b.status === "no_show") return false;
3994
- return timeOverlaps(b.startsAt, b.endsAt ?? b.startsAt, startIso, endIso);
3995
- });
3996
- if (!hasConflict) {
3997
- slots.push({ start: startIso, end: endIso });
3998
- }
3999
- slotStart = new Date(slotStart.getTime() + interval * 6e4);
4000
- }
4001
- }
4002
- return slots;
4003
- },
4004
- async getSchedules(professionalId) {
4005
- if (professionalId) return schedules.filter((s) => s.assigneeId === professionalId);
4006
- return [...schedules];
4007
- },
4008
- async saveSchedule(input) {
4009
- const schedule = {
4010
- id: uid(),
4011
- tenantId: "mock",
4012
- kind: "working_hours",
4013
- assigneeId: input.assigneeId,
4014
- locationId: input.locationId ?? null,
4015
- dayOfWeek: input.dayOfWeek ?? null,
4016
- specificDate: input.specificDate ?? null,
4017
- startsAt: input.startsAt,
4018
- endsAt: input.endsAt,
4019
- isActive: input.isActive !== false,
4020
- metadata: input.metadata ?? {}
4021
- };
4022
- schedules.push(schedule);
4023
- return schedule;
4024
- },
4025
- async deleteSchedule(id) {
4026
- const idx = schedules.findIndex((s) => s.id === id);
4027
- if (idx !== -1) schedules.splice(idx, 1);
4028
- },
4029
- async getProfessionals() {
4030
- return [...professionals];
4031
- },
4032
- async getConfirmationsPending(daysAhead = 2) {
4033
- const cutoff = /* @__PURE__ */ new Date();
4034
- cutoff.setDate(cutoff.getDate() + daysAhead);
4035
- return bookings.filter(
4036
- (b) => b.status === "scheduled" && new Date(b.startsAt) <= cutoff && new Date(b.startsAt) >= /* @__PURE__ */ new Date()
4037
- );
4038
- },
4039
- async sendConfirmation(bookingId, channel) {
4040
- const booking = bookings.find((b) => b.id === bookingId);
4041
- if (!booking) throw new Error("Booking not found");
4042
- const confirmations = booking.metadata.confirmations ?? [];
4043
- confirmations.push({ channel, sentAt: now(), status: "sent" });
4044
- booking.metadata = { ...booking.metadata, confirmations };
4045
- },
4046
- async completeBooking(bookingId) {
4047
- const idx = bookings.findIndex((b) => b.id === bookingId);
4048
- if (idx === -1) throw new Error("Booking not found");
4049
- bookings[idx] = { ...bookings[idx], status: "completed", updatedAt: now() };
4050
- return { booking: bookings[idx], orderId: bookings[idx].orderId ?? "" };
4051
- }
4052
- };
4053
- }
4054
3476
  function getCurrentBookingQuery(state, dateRange = state.dateRange) {
4055
3477
  return {
4056
3478
  dateRange,
@@ -4246,7 +3668,7 @@ function createAgendaStore(provider2, financialBridge) {
4246
3668
  set({ selectedDate: date });
4247
3669
  },
4248
3670
  openAppointmentModal(mode, options) {
4249
- set({ appointmentModal: { open: true, mode, bookingId: options?.bookingId, prefill: options?.prefill, initialTab: options?.initialTab } });
3671
+ set({ appointmentModal: { open: true, mode, bookingId: options?.bookingId, prefill: options?.prefill, initialTab: options?.initialTab, externalSource: options?.externalSource } });
4250
3672
  },
4251
3673
  closeAppointmentModal() {
4252
3674
  set((state) => ({ appointmentModal: { ...state.appointmentModal, open: false } }));
@@ -4437,8 +3859,12 @@ var en = {
4437
3859
  "agenda.calendar.today": "Today",
4438
3860
  "agenda.calendar.viewAgenda": "Agenda",
4439
3861
  "agenda.calendar.viewDay": "Day",
3862
+ "agenda.calendar.viewList": "List",
4440
3863
  "agenda.calendar.viewMonth": "Month",
4441
3864
  "agenda.calendar.viewWeek": "Week",
3865
+ "agenda.list.today": "Today",
3866
+ "agenda.list.tomorrow": "Tomorrow",
3867
+ "agenda.list.empty": "No upcoming items",
4442
3868
  "agenda.confirmations.confirmed": "Confirmed:",
4443
3869
  "agenda.confirmations.empty": "No pending confirmations",
4444
3870
  "agenda.confirmations.emptySubtitle": "All upcoming appointments have been confirmed",
@@ -4449,6 +3875,10 @@ var en = {
4449
3875
  "agenda.confirmations.call": "Call",
4450
3876
  "agenda.confirmations.markConfirmed": "Mark Confirmed",
4451
3877
  "agenda.confirmations.markCancelled": "Mark Cancelled",
3878
+ "agenda.cancellation.loadingReasons": "Loading reasons...",
3879
+ "agenda.cancellation.notesOptional": "Cancellation notes (optional)",
3880
+ "agenda.cancellation.notesRequired": "Cancellation notes required",
3881
+ "agenda.cancellation.selectReason": "Select cancellation reason",
4452
3882
  "agenda.contextMenu.changeStatus": "Change status",
4453
3883
  "agenda.contextMenu.delete": "Delete",
4454
3884
  "agenda.contextMenu.edit": "Edit",
@@ -4549,7 +3979,11 @@ var en = {
4549
3979
  "agenda.appointment.addNotes": "Add notes",
4550
3980
  "agenda.appointment.duration": "Duration (minutes)",
4551
3981
  "agenda.appointment.price": "Price",
3982
+ "agenda.appointment.title": "Title",
3983
+ "agenda.appointment.addTitle": "Add a title",
3984
+ "agenda.appointment.untitled": "(no title)",
4552
3985
  "agenda.bookingType.appointment": "Appointment",
3986
+ "agenda.bookingType.event": "Event",
4553
3987
  "agenda.bookingType.task": "Task",
4554
3988
  "agenda.bookingType.out_of_office": "Out of Office",
4555
3989
  "agenda.bookingType.block": "Block",
@@ -4645,8 +4079,12 @@ var ptBR = {
4645
4079
  "agenda.calendar.today": "Hoje",
4646
4080
  "agenda.calendar.viewAgenda": "Agenda",
4647
4081
  "agenda.calendar.viewDay": "Dia",
4082
+ "agenda.calendar.viewList": "Lista",
4648
4083
  "agenda.calendar.viewMonth": "M\xEAs",
4649
4084
  "agenda.calendar.viewWeek": "Semana",
4085
+ "agenda.list.today": "Hoje",
4086
+ "agenda.list.tomorrow": "Amanh\xE3",
4087
+ "agenda.list.empty": "Nenhum item pr\xF3ximo",
4650
4088
  "agenda.confirmations.confirmed": "Confirmado:",
4651
4089
  "agenda.confirmations.empty": "Nenhuma confirma\xE7\xE3o pendente",
4652
4090
  "agenda.confirmations.emptySubtitle": "Todos os agendamentos futuros foram confirmados",
@@ -4657,6 +4095,10 @@ var ptBR = {
4657
4095
  "agenda.confirmations.call": "Ligar",
4658
4096
  "agenda.confirmations.markConfirmed": "Marcar Confirmado",
4659
4097
  "agenda.confirmations.markCancelled": "Marcar Cancelado",
4098
+ "agenda.cancellation.loadingReasons": "Carregando motivos...",
4099
+ "agenda.cancellation.notesOptional": "Observa\xE7\xF5es do cancelamento (opcional)",
4100
+ "agenda.cancellation.notesRequired": "Observa\xE7\xF5es do cancelamento obrigat\xF3rias",
4101
+ "agenda.cancellation.selectReason": "Selecionar motivo do cancelamento",
4660
4102
  "agenda.contextMenu.changeStatus": "Alterar status",
4661
4103
  "agenda.contextMenu.delete": "Excluir",
4662
4104
  "agenda.contextMenu.edit": "Editar",
@@ -4757,7 +4199,11 @@ var ptBR = {
4757
4199
  "agenda.appointment.addNotes": "Adicionar observa\xE7\xF5es",
4758
4200
  "agenda.appointment.duration": "Dura\xE7\xE3o (minutos)",
4759
4201
  "agenda.appointment.price": "Pre\xE7o",
4202
+ "agenda.appointment.title": "T\xEDtulo",
4203
+ "agenda.appointment.addTitle": "Adicionar um t\xEDtulo",
4204
+ "agenda.appointment.untitled": "(sem t\xEDtulo)",
4760
4205
  "agenda.bookingType.appointment": "Agendamento",
4206
+ "agenda.bookingType.event": "Evento",
4761
4207
  "agenda.bookingType.task": "Tarefa",
4762
4208
  "agenda.bookingType.out_of_office": "Aus\xEAncia",
4763
4209
  "agenda.bookingType.block": "Bloqueio",
@@ -5039,11 +4485,11 @@ function createGoogleCalendarProvider() {
5039
4485
  }
5040
4486
  var provider = createGoogleCalendarProvider();
5041
4487
  function GoogleCalendarExtraPanel() {
5042
- const [connected, setConnected] = React11.useState(false);
5043
- const [calendarId, setCalendarId] = React11.useState("primary");
5044
- const [syncing, setSyncing] = React11.useState(false);
5045
- const [log, setLog] = React11.useState([]);
5046
- const [loaded, setLoaded] = React11.useState(false);
4488
+ const [connected, setConnected] = React12.useState(false);
4489
+ const [calendarId, setCalendarId] = React12.useState("primary");
4490
+ const [syncing, setSyncing] = React12.useState(false);
4491
+ const [log, setLog] = React12.useState([]);
4492
+ const [loaded, setLoaded] = React12.useState(false);
5047
4493
  async function load() {
5048
4494
  const integ = await provider.getIntegration();
5049
4495
  setConnected(!!integ?.connected);
@@ -5053,7 +4499,7 @@ function GoogleCalendarExtraPanel() {
5053
4499
  }
5054
4500
  setLoaded(true);
5055
4501
  }
5056
- React11.useEffect(() => {
4502
+ React12.useEffect(() => {
5057
4503
  void load();
5058
4504
  }, []);
5059
4505
  if (!loaded || !connected) return null;
@@ -5189,10 +4635,11 @@ function createAgendaPlugin(options) {
5189
4635
  const config = resolveConfig(options);
5190
4636
  core.registerTranslations(agendaLocales);
5191
4637
  const provider2 = options?.dataProvider ?? core.createSafeDataProvider(
5192
- () => createSupabaseAgendaProvider(),
5193
- () => createMockAgendaProvider()
4638
+ () => chunkGAJVIEXM_cjs.createSupabaseAgendaProvider(),
4639
+ () => chunkGAJVIEXM_cjs.createMockAgendaProvider()
5194
4640
  );
5195
4641
  const store = createAgendaStore(provider2, options?.financialBridge);
4642
+ const registries = [...agendaRegistries, ...options?.settingsRegistries ?? []];
5196
4643
  saas.setScheduleBlockConfig({
5197
4644
  defaults: config.scheduleBlockDefaults,
5198
4645
  showServices: !!config.serviceLookup,
@@ -5208,7 +4655,7 @@ function createAgendaPlugin(options) {
5208
4655
  return locations;
5209
4656
  } : void 0
5210
4657
  });
5211
- const PageComponent = () => React11__default.default.createElement(AgendaPage, { config, provider: provider2, store });
4658
+ const PageComponent = () => React12__default.default.createElement(AgendaPage, { config, provider: provider2, store });
5212
4659
  return {
5213
4660
  id: "agenda",
5214
4661
  name: config.labels.pageTitle,
@@ -5216,6 +4663,9 @@ function createAgendaPlugin(options) {
5216
4663
  version: "1.0.0",
5217
4664
  scope: options?.scope ?? "universal",
5218
4665
  verticalId: options?.verticalId,
4666
+ // Booking/scheduling is business-ops (clinic, salon, services) — targets
4667
+ // the saas/admin Panel world, not the ecommerce storefront.
4668
+ scaffolds: ["saas"],
5219
4669
  defaultEnabled: true,
5220
4670
  dependencies: [],
5221
4671
  declaredFeatures: [
@@ -5308,18 +4758,18 @@ function createAgendaPlugin(options) {
5308
4758
  permission: { feature: "appointments", action: "read" }
5309
4759
  }
5310
4760
  ],
5311
- registries: agendaRegistries,
4761
+ registries,
5312
4762
  settings: [
5313
4763
  {
5314
4764
  id: "agenda",
5315
4765
  label: "Agenda",
5316
4766
  icon: "Calendar",
5317
- // Standard layout: General | Integrations (Google Calendar addon) | Properties (holidays).
4767
+ // Standard layout: General | Integrations (Google Calendar addon) | Properties.
5318
4768
  component: (() => {
5319
- const AgendaSettingsTab = () => React11__default.default.createElement(saas.PluginSettingsPanel, {
4769
+ const AgendaSettingsTab = () => React12__default.default.createElement(saas.PluginSettingsPanel, {
5320
4770
  title: "Agenda",
5321
- generalSettings: React11__default.default.createElement(AgendaGeneralSettings),
5322
- registries: agendaRegistries,
4771
+ generalSettings: React12__default.default.createElement(AgendaGeneralSettings),
4772
+ registries,
5323
4773
  hostPluginId: "agenda",
5324
4774
  routeBase: "/settings/agenda"
5325
4775
  });
@@ -5334,6 +4784,10 @@ function createAgendaPlugin(options) {
5334
4784
  };
5335
4785
  }
5336
4786
 
4787
+ Object.defineProperty(exports, "createMockAgendaProvider", {
4788
+ enumerable: true,
4789
+ get: function () { return chunkGAJVIEXM_cjs.createMockAgendaProvider; }
4790
+ });
5337
4791
  exports.bookingToEvent = bookingToEvent2;
5338
4792
  exports.computeCommissionAmount = computeCommissionAmount;
5339
4793
  exports.createAgendaPlugin = createAgendaPlugin;