@fayz-ai/plugin-agenda 0.8.0 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-LFUELUIT.cjs → chunk-3KWNHV5A.cjs} +60 -2
- package/dist/chunk-3KWNHV5A.cjs.map +1 -0
- package/dist/{chunk-DRZF6CV2.js → chunk-YQO6B4G3.js} +60 -3
- package/dist/chunk-YQO6B4G3.js.map +1 -0
- package/dist/components/AgendaGeneralSettings.d.ts.map +1 -1
- package/dist/components/AppointmentModal.d.ts.map +1 -1
- package/dist/components/CalendarToolbar.d.ts.map +1 -1
- package/dist/data/supabase.d.ts +5 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.cjs +1071 -766
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +914 -609
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts +6 -0
- package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts +9 -0
- package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts +12 -0
- package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts +26 -0
- package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts.map +1 -0
- package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
- package/dist/integrations/google-calendar/data/supabase.d.ts +33 -1
- package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
- package/dist/integrations/google-calendar/data/tables.d.ts +6 -0
- package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -0
- package/dist/integrations/google-calendar/index.d.ts +1 -1
- package/dist/integrations/google-calendar/index.d.ts.map +1 -1
- package/dist/integrations/google-calendar/mapping.d.ts +56 -4
- package/dist/integrations/google-calendar/mapping.d.ts.map +1 -1
- package/dist/integrations/google-calendar/types.d.ts +101 -0
- package/dist/integrations/google-calendar/types.d.ts.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +11 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/public/BookingWidget.d.ts.map +1 -1
- package/dist/public/context.d.ts +3 -1
- package/dist/public/context.d.ts.map +1 -1
- package/dist/public/createPublicBookingPlugin.d.ts +7 -1
- package/dist/public/createPublicBookingPlugin.d.ts.map +1 -1
- package/dist/public/data.d.ts +6 -0
- package/dist/public/data.d.ts.map +1 -1
- package/dist/public/data.supabase.d.ts.map +1 -1
- package/dist/public/format.d.ts +22 -1
- package/dist/public/format.d.ts.map +1 -1
- package/dist/public/hooks.d.ts.map +1 -1
- package/dist/public/index.cjs +31 -63
- package/dist/public/index.cjs.map +1 -1
- package/dist/public/index.d.ts +1 -1
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +27 -61
- package/dist/public/index.js.map +1 -1
- package/dist/public/steps/ContactStep.d.ts +4 -2
- package/dist/public/steps/ContactStep.d.ts.map +1 -1
- package/dist/public/types.d.ts +16 -0
- package/dist/public/types.d.ts.map +1 -1
- package/dist/views/CalendarView.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/components/AgendaGeneralSettings.tsx +38 -16
- package/src/components/AppointmentModal.tsx +50 -108
- package/src/components/CalendarToolbar.tsx +10 -7
- package/src/data/supabase.ts +67 -13
- package/src/index.ts +56 -8
- package/src/integrations/google-calendar/components/CalendarColorDot.tsx +23 -0
- package/src/integrations/google-calendar/components/ChannelMappingList.tsx +329 -0
- package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +215 -0
- package/src/integrations/google-calendar/components/useAgendaLookups.ts +100 -0
- package/src/integrations/google-calendar/connectorDef.tsx +123 -40
- package/src/integrations/google-calendar/data/supabase.ts +109 -13
- package/src/integrations/google-calendar/data/tables.ts +12 -0
- package/src/integrations/google-calendar/index.ts +10 -1
- package/src/integrations/google-calendar/integration.test.ts +126 -1
- package/src/integrations/google-calendar/mapping.ts +149 -9
- package/src/integrations/google-calendar/migrations/001_google_calendar.sql +9 -6
- package/src/integrations/google-calendar/migrations/002_smart_sync.sql +517 -0
- package/src/integrations/google-calendar/migrations/003_plg_rename.sql +273 -0
- package/src/integrations/google-calendar/types.ts +111 -0
- package/src/locales/en.ts +1 -0
- package/src/locales/pt-BR.ts +1 -0
- package/src/migrations/003_public_client_kind.sql +175 -0
- package/src/migrations/004_public_booking_assignee.sql +176 -0
- package/src/migrations/005_agent_rpcs.sql +168 -0
- package/src/migrations/006_agent_rpc_localtime.sql +181 -0
- package/src/migrations/007_schedule_exception_precedence.sql +290 -0
- package/src/migrations/008_agent_rpc_exception_precedence.sql +172 -0
- package/src/migrations/009_public_booking_location.sql +162 -0
- package/src/migrations/010_public_service_curation.sql +31 -0
- package/src/migrations/011_default_assignee.sql +167 -0
- package/src/migrations/012_drop_legacy_booking_overload.sql +29 -0
- package/src/migrations/013_normalize_v_appointments.sql +161 -0
- package/src/migrations/index.ts +1746 -1
- package/src/public/BookingWidget.tsx +10 -1
- package/src/public/context.tsx +3 -0
- package/src/public/createPublicBookingPlugin.ts +14 -1
- package/src/public/data.supabase.ts +3 -0
- package/src/public/data.ts +6 -0
- package/src/public/format.ts +36 -1
- package/src/public/hooks.ts +7 -2
- package/src/public/index.ts +1 -0
- package/src/public/steps/ContactStep.tsx +10 -5
- package/src/public/types.ts +21 -0
- package/src/views/CalendarView.tsx +15 -1
- package/dist/chunk-3LJKZQK4.js +0 -12
- package/dist/chunk-3LJKZQK4.js.map +0 -1
- package/dist/chunk-DRZF6CV2.js.map +0 -1
- package/dist/chunk-LFUELUIT.cjs.map +0 -1
- package/dist/chunk-SYDBDI3Q.cjs +0 -15
- package/dist/chunk-SYDBDI3Q.cjs.map +0 -1
- package/dist/components/SearchCombobox.d.ts +0 -38
- package/dist/components/SearchCombobox.d.ts.map +0 -1
- package/dist/tenant-222EHYUC.cjs +0 -16
- package/dist/tenant-222EHYUC.cjs.map +0 -1
- package/dist/tenant-LAUWVUYB.js +0 -3
- package/dist/tenant-LAUWVUYB.js.map +0 -1
- package/src/components/SearchCombobox.tsx +0 -216
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var React12 = require('react');
|
|
3
|
+
var chunk3KWNHV5A_cjs = require('./chunk-3KWNHV5A.cjs');
|
|
4
|
+
var React13 = require('react');
|
|
6
5
|
var core = require('@fayz-ai/core');
|
|
7
6
|
var saas = require('@fayz-ai/saas');
|
|
8
7
|
var FullCalendar = require('@fullcalendar/react');
|
|
@@ -20,7 +19,7 @@ var vanilla = require('zustand/vanilla');
|
|
|
20
19
|
|
|
21
20
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
22
21
|
|
|
23
|
-
var
|
|
22
|
+
var React13__default = /*#__PURE__*/_interopDefault(React13);
|
|
24
23
|
var FullCalendar__default = /*#__PURE__*/_interopDefault(FullCalendar);
|
|
25
24
|
var dayGridPlugin__default = /*#__PURE__*/_interopDefault(dayGridPlugin);
|
|
26
25
|
var timeGridPlugin__default = /*#__PURE__*/_interopDefault(timeGridPlugin);
|
|
@@ -458,10 +457,10 @@ function toDateLocale(locale) {
|
|
|
458
457
|
}
|
|
459
458
|
function MiniCalendar({ selectedDate, onDateSelect, onMonthChange }) {
|
|
460
459
|
const locale = core.getCurrentLocale();
|
|
461
|
-
const [viewMonth, setViewMonth] =
|
|
460
|
+
const [viewMonth, setViewMonth] = React13__default.default.useState(() => new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
|
|
462
461
|
const monthLabel = viewMonth.toLocaleDateString(toDateLocale(locale), { month: "long", year: "numeric" });
|
|
463
462
|
const dayLabels = DAY_LABELS[locale] ?? DAY_LABELS.en;
|
|
464
|
-
const weeks =
|
|
463
|
+
const weeks = React13.useMemo(() => {
|
|
465
464
|
const year = viewMonth.getFullYear();
|
|
466
465
|
const month = viewMonth.getMonth();
|
|
467
466
|
const firstDay = new Date(year, month, 1);
|
|
@@ -513,216 +512,6 @@ function MiniCalendar({ selectedDate, onDateSelect, onMonthChange }) {
|
|
|
513
512
|
}) }, wi))
|
|
514
513
|
] });
|
|
515
514
|
}
|
|
516
|
-
function SearchCombobox({
|
|
517
|
-
value,
|
|
518
|
-
onChange,
|
|
519
|
-
onSelect,
|
|
520
|
-
options,
|
|
521
|
-
placeholder,
|
|
522
|
-
autoFocus,
|
|
523
|
-
onBlurEmpty,
|
|
524
|
-
renderRight,
|
|
525
|
-
allowCreate,
|
|
526
|
-
onCreateNew,
|
|
527
|
-
createLabel = "Create",
|
|
528
|
-
className,
|
|
529
|
-
minimal,
|
|
530
|
-
inlineLabel,
|
|
531
|
-
inlineIcon = "search",
|
|
532
|
-
loading,
|
|
533
|
-
onFocus: onFocusProp
|
|
534
|
-
}) {
|
|
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(() => {
|
|
543
|
-
if (value && inlineLabel) setExpanded(true);
|
|
544
|
-
if (!value && inlineLabel && !open) setExpanded(false);
|
|
545
|
-
}, [value, open]);
|
|
546
|
-
function updateRect() {
|
|
547
|
-
if (wrapperRef.current) setDropRect(wrapperRef.current.getBoundingClientRect());
|
|
548
|
-
}
|
|
549
|
-
const showCreate = open && allowCreate && onCreateNew && value.trim().length > 0 && !options.some((o) => o.label.toLowerCase() === value.trim().toLowerCase());
|
|
550
|
-
const totalItems = options.length + (showCreate ? 1 : 0);
|
|
551
|
-
React12.useEffect(() => {
|
|
552
|
-
setActiveIndex(-1);
|
|
553
|
-
}, [options]);
|
|
554
|
-
React12.useEffect(() => {
|
|
555
|
-
if ((options.length > 0 || showCreate || loading) && document.activeElement === inputRef.current) setOpen(true);
|
|
556
|
-
}, [options, showCreate, loading]);
|
|
557
|
-
const handleKeyDown = React12.useCallback((e) => {
|
|
558
|
-
if (!open || totalItems === 0) {
|
|
559
|
-
if (e.key === "ArrowDown" && totalItems > 0) {
|
|
560
|
-
e.preventDefault();
|
|
561
|
-
setOpen(true);
|
|
562
|
-
setActiveIndex(0);
|
|
563
|
-
}
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
switch (e.key) {
|
|
567
|
-
case "ArrowDown":
|
|
568
|
-
e.preventDefault();
|
|
569
|
-
setActiveIndex((prev) => (prev + 1) % totalItems);
|
|
570
|
-
break;
|
|
571
|
-
case "ArrowUp":
|
|
572
|
-
e.preventDefault();
|
|
573
|
-
setActiveIndex((prev) => prev <= 0 ? totalItems - 1 : prev - 1);
|
|
574
|
-
break;
|
|
575
|
-
case "Enter":
|
|
576
|
-
e.preventDefault();
|
|
577
|
-
if (activeIndex >= 0 && activeIndex < options.length) {
|
|
578
|
-
onSelect(options[activeIndex]);
|
|
579
|
-
setOpen(false);
|
|
580
|
-
setActiveIndex(-1);
|
|
581
|
-
} else if (showCreate && activeIndex === options.length) {
|
|
582
|
-
onCreateNew?.(value.trim());
|
|
583
|
-
setOpen(false);
|
|
584
|
-
setActiveIndex(-1);
|
|
585
|
-
}
|
|
586
|
-
break;
|
|
587
|
-
case "Escape":
|
|
588
|
-
e.preventDefault();
|
|
589
|
-
setOpen(false);
|
|
590
|
-
setActiveIndex(-1);
|
|
591
|
-
break;
|
|
592
|
-
}
|
|
593
|
-
}, [open, options, totalItems, activeIndex, onSelect, showCreate, onCreateNew, value]);
|
|
594
|
-
React12.useEffect(() => {
|
|
595
|
-
if (activeIndex < 0 || !listRef.current) return;
|
|
596
|
-
const items = listRef.current.children;
|
|
597
|
-
if (items[activeIndex]) items[activeIndex].scrollIntoView({ block: "nearest" });
|
|
598
|
-
}, [activeIndex]);
|
|
599
|
-
function handleBlur() {
|
|
600
|
-
setTimeout(() => {
|
|
601
|
-
setOpen(false);
|
|
602
|
-
setActiveIndex(-1);
|
|
603
|
-
if (!value && onBlurEmpty) onBlurEmpty();
|
|
604
|
-
if (!value && inlineLabel) setExpanded(false);
|
|
605
|
-
}, 200);
|
|
606
|
-
}
|
|
607
|
-
const hasDropdown = open && (options.length > 0 || showCreate || loading);
|
|
608
|
-
if (!expanded && inlineLabel) {
|
|
609
|
-
const isPlus = inlineIcon === "plus";
|
|
610
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
611
|
-
"button",
|
|
612
|
-
{
|
|
613
|
-
type: "button",
|
|
614
|
-
onClick: () => {
|
|
615
|
-
setExpanded(true);
|
|
616
|
-
setTimeout(() => inputRef.current?.focus(), 50);
|
|
617
|
-
},
|
|
618
|
-
className: `flex items-center gap-1.5 w-full text-left text-sm py-1.5 pl-2 transition-colors ${isPlus ? "text-primary hover:text-primary/80" : "text-muted-foreground hover:text-foreground"} ${className ?? ""}`,
|
|
619
|
-
children: [
|
|
620
|
-
isPlus && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
621
|
-
inlineLabel
|
|
622
|
-
]
|
|
623
|
-
}
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: wrapperRef, className: `relative ${className ?? ""}`, children: [
|
|
627
|
-
!minimal && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" }),
|
|
628
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
629
|
-
"input",
|
|
630
|
-
{
|
|
631
|
-
ref: inputRef,
|
|
632
|
-
type: "text",
|
|
633
|
-
value,
|
|
634
|
-
onChange: (e) => {
|
|
635
|
-
onChange(e.target.value);
|
|
636
|
-
updateRect();
|
|
637
|
-
setOpen(true);
|
|
638
|
-
},
|
|
639
|
-
onFocus: () => {
|
|
640
|
-
updateRect();
|
|
641
|
-
onFocusProp?.();
|
|
642
|
-
if (options.length > 0 || showCreate || loading) setOpen(true);
|
|
643
|
-
},
|
|
644
|
-
onBlur: handleBlur,
|
|
645
|
-
onKeyDown: handleKeyDown,
|
|
646
|
-
placeholder,
|
|
647
|
-
autoFocus,
|
|
648
|
-
className: minimal ? "w-full bg-transparent pl-2 pr-3 py-1.5 text-sm placeholder:text-muted-foreground focus:outline-none border-b border-transparent hover:border-border focus:border-foreground transition-colors" : "w-full rounded-lg border bg-background pl-8 pr-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring",
|
|
649
|
-
role: "combobox",
|
|
650
|
-
"aria-expanded": hasDropdown,
|
|
651
|
-
"aria-autocomplete": "list",
|
|
652
|
-
"aria-activedescendant": activeIndex >= 0 ? `combobox-opt-${activeIndex}` : void 0
|
|
653
|
-
}
|
|
654
|
-
),
|
|
655
|
-
hasDropdown && dropRect && reactDom.createPortal(
|
|
656
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
657
|
-
"div",
|
|
658
|
-
{
|
|
659
|
-
ref: listRef,
|
|
660
|
-
role: "listbox",
|
|
661
|
-
"data-modal-passthrough": true,
|
|
662
|
-
style: { position: "fixed", left: dropRect.left, top: dropRect.bottom + 4, width: dropRect.width, zIndex: 9999, pointerEvents: "auto" },
|
|
663
|
-
className: "fayz-glass-surface max-h-52 overflow-y-auto rounded-lg border bg-popover shadow-lg py-0.5 animate-in fade-in zoom-in-95 duration-100",
|
|
664
|
-
children: [
|
|
665
|
-
loading && options.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground", children: [
|
|
666
|
-
/* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "h-3.5 w-3.5 animate-spin shrink-0", viewBox: "0 0 16 16", fill: "none", children: [
|
|
667
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeWidth: "1.5", opacity: "0.25" }),
|
|
668
|
-
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14.5 8a6.5 6.5 0 00-6.5-6.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
669
|
-
] }),
|
|
670
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Buscando\u2026" })
|
|
671
|
-
] }),
|
|
672
|
-
options.map((opt, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
673
|
-
"div",
|
|
674
|
-
{
|
|
675
|
-
id: `combobox-opt-${i}`,
|
|
676
|
-
role: "option",
|
|
677
|
-
"aria-selected": i === activeIndex,
|
|
678
|
-
onMouseDown: (e) => {
|
|
679
|
-
e.preventDefault();
|
|
680
|
-
onSelect(opt);
|
|
681
|
-
setOpen(false);
|
|
682
|
-
},
|
|
683
|
-
onMouseEnter: () => setActiveIndex(i),
|
|
684
|
-
className: `flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors ${i === activeIndex ? "bg-primary/10 text-primary" : "hover:bg-muted/50"}`,
|
|
685
|
-
children: [
|
|
686
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
687
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-medium", children: opt.label }),
|
|
688
|
-
opt.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-[11px] text-muted-foreground", children: opt.subtitle })
|
|
689
|
-
] }),
|
|
690
|
-
renderRight && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: renderRight(opt) })
|
|
691
|
-
]
|
|
692
|
-
},
|
|
693
|
-
opt.id
|
|
694
|
-
)),
|
|
695
|
-
showCreate && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
696
|
-
"div",
|
|
697
|
-
{
|
|
698
|
-
id: `combobox-opt-${options.length}`,
|
|
699
|
-
role: "option",
|
|
700
|
-
"aria-selected": activeIndex === options.length,
|
|
701
|
-
onMouseDown: (e) => {
|
|
702
|
-
e.preventDefault();
|
|
703
|
-
onCreateNew?.(value.trim());
|
|
704
|
-
setOpen(false);
|
|
705
|
-
},
|
|
706
|
-
onMouseEnter: () => setActiveIndex(options.length),
|
|
707
|
-
className: `flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors border-t ${activeIndex === options.length ? "bg-primary/10 text-primary" : "hover:bg-muted/50"}`,
|
|
708
|
-
children: [
|
|
709
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
710
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
711
|
-
createLabel,
|
|
712
|
-
" \u201C",
|
|
713
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: value.trim() }),
|
|
714
|
-
"\u201D"
|
|
715
|
-
] })
|
|
716
|
-
]
|
|
717
|
-
}
|
|
718
|
-
)
|
|
719
|
-
]
|
|
720
|
-
}
|
|
721
|
-
),
|
|
722
|
-
document.body
|
|
723
|
-
)
|
|
724
|
-
] });
|
|
725
|
-
}
|
|
726
515
|
var TYPE_ICONS = {
|
|
727
516
|
cash: lucideReact.Banknote,
|
|
728
517
|
pix: lucideReact.QrCode,
|
|
@@ -748,26 +537,26 @@ var ICON_COLORS = {
|
|
|
748
537
|
check: "text-muted-foreground"
|
|
749
538
|
};
|
|
750
539
|
function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPaymentChange, formatCurrency: formatCurrency2, inline }) {
|
|
751
|
-
const
|
|
540
|
+
const t2 = core.useTranslation();
|
|
752
541
|
const config = useAgendaConfig();
|
|
753
542
|
const bridge = config.financialBridge;
|
|
754
|
-
const [visible, setVisible] =
|
|
755
|
-
const [loading, setLoading] =
|
|
756
|
-
const [detail, setDetail] =
|
|
757
|
-
const [selectedTypeId, setSelectedTypeId] =
|
|
758
|
-
const [paymentTypes, setPaymentTypes] =
|
|
759
|
-
const [bankAccounts, setBankAccounts] =
|
|
760
|
-
const [filteredMethods, setFilteredMethods] =
|
|
761
|
-
const [selectedBankId, setSelectedBankId] =
|
|
762
|
-
const [paymentMethodId, setPaymentMethodId] =
|
|
763
|
-
const [cardBrand, setCardBrand] =
|
|
764
|
-
const [cardInstallments, setCardInstallments] =
|
|
765
|
-
const [processing, setProcessing] =
|
|
766
|
-
const [justPaid, setJustPaid] =
|
|
767
|
-
|
|
543
|
+
const [visible, setVisible] = React13.useState(false);
|
|
544
|
+
const [loading, setLoading] = React13.useState(true);
|
|
545
|
+
const [detail, setDetail] = React13.useState(null);
|
|
546
|
+
const [selectedTypeId, setSelectedTypeId] = React13.useState("");
|
|
547
|
+
const [paymentTypes, setPaymentTypes] = React13.useState([]);
|
|
548
|
+
const [bankAccounts, setBankAccounts] = React13.useState([]);
|
|
549
|
+
const [filteredMethods, setFilteredMethods] = React13.useState([]);
|
|
550
|
+
const [selectedBankId, setSelectedBankId] = React13.useState("");
|
|
551
|
+
const [paymentMethodId, setPaymentMethodId] = React13.useState("");
|
|
552
|
+
const [cardBrand, setCardBrand] = React13.useState("");
|
|
553
|
+
const [cardInstallments, setCardInstallments] = React13.useState(1);
|
|
554
|
+
const [processing, setProcessing] = React13.useState(false);
|
|
555
|
+
const [justPaid, setJustPaid] = React13.useState(false);
|
|
556
|
+
React13.useEffect(() => {
|
|
768
557
|
requestAnimationFrame(() => setVisible(true));
|
|
769
558
|
}, []);
|
|
770
|
-
|
|
559
|
+
React13.useEffect(() => {
|
|
771
560
|
let cancelled = false;
|
|
772
561
|
setLoading(true);
|
|
773
562
|
Promise.all([
|
|
@@ -789,7 +578,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
789
578
|
setVisible(false);
|
|
790
579
|
setTimeout(onClose, 200);
|
|
791
580
|
}
|
|
792
|
-
const selectedType = paymentTypes.find((
|
|
581
|
+
const selectedType = paymentTypes.find((t3) => t3.id === selectedTypeId);
|
|
793
582
|
const txType = selectedType?.transactionType ?? "";
|
|
794
583
|
const needsBank = ["bank_transfer", "pix", "check"].includes(txType);
|
|
795
584
|
const needsCard = txType === "credit_card" || txType === "debit_card";
|
|
@@ -828,7 +617,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
828
617
|
loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3 py-2", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 rounded-lg bg-muted animate-pulse" }, i)) }),
|
|
829
618
|
!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: [
|
|
830
619
|
/* @__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" }) }),
|
|
831
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-success", children:
|
|
620
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-success", children: t2("agenda.payment.checkoutComplete") }),
|
|
832
621
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: formatCurrency2(detail.totalAmount) })
|
|
833
622
|
] }),
|
|
834
623
|
!loading && detail && detail.status !== "none" && !justPaid && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 animate-in fade-in duration-300", children: [
|
|
@@ -850,14 +639,14 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
850
639
|
] }),
|
|
851
640
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
852
641
|
mov.paymentDate ?? mov.dueDate,
|
|
853
|
-
detail.movements.length > 1 && mov.installmentNumber ? ` \xB7 ${
|
|
642
|
+
detail.movements.length > 1 && mov.installmentNumber ? ` \xB7 ${t2("agenda.payment.installment")} ${mov.installmentNumber}` : ""
|
|
854
643
|
] })
|
|
855
644
|
] }),
|
|
856
645
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
857
646
|
isMovPaid && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-success/10 px-2 py-0.5 text-[10px] font-medium text-success", children: [
|
|
858
647
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-2.5 w-2.5" }),
|
|
859
648
|
" ",
|
|
860
|
-
|
|
649
|
+
t2("agenda.payment.paid")
|
|
861
650
|
] }),
|
|
862
651
|
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: [
|
|
863
652
|
"! ",
|
|
@@ -867,14 +656,14 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
867
656
|
] }),
|
|
868
657
|
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: [
|
|
869
658
|
"! ",
|
|
870
|
-
|
|
659
|
+
t2("agenda.payment.overdue")
|
|
871
660
|
] }),
|
|
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:
|
|
661
|
+
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: t2("agenda.payment.pending") }),
|
|
873
662
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: formatCurrency2(mov.paidAmount || mov.amount) })
|
|
874
663
|
] })
|
|
875
664
|
] }, mov.id);
|
|
876
665
|
}) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-sm px-1", children: [
|
|
877
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children:
|
|
666
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t2("agenda.payment.total") }),
|
|
878
667
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: formatCurrency2(detail.totalAmount) })
|
|
879
668
|
] }),
|
|
880
669
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
@@ -888,7 +677,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
888
677
|
href: `#/financial/receivables/detail/${detail.invoiceId}`,
|
|
889
678
|
className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline ml-auto",
|
|
890
679
|
children: [
|
|
891
|
-
|
|
680
|
+
t2("agenda.payment.viewInvoice"),
|
|
892
681
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3" })
|
|
893
682
|
]
|
|
894
683
|
}
|
|
@@ -897,22 +686,22 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
897
686
|
] }),
|
|
898
687
|
!loading && (!detail || detail.movements.length === 0) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
899
688
|
services && services.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
900
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children:
|
|
689
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t2("agenda.payment.serviceSummary") }),
|
|
901
690
|
/* @__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: [
|
|
902
691
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: svc.name }),
|
|
903
692
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-muted-foreground", children: formatCurrency2(svc.price) })
|
|
904
693
|
] }, i)) })
|
|
905
694
|
] }),
|
|
906
695
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-sm font-semibold pt-2 border-t", children: [
|
|
907
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
696
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("agenda.payment.total") }),
|
|
908
697
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatCurrency2(orderTotal) })
|
|
909
698
|
] }),
|
|
910
699
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
911
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children:
|
|
700
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t2("agenda.payment.paymentMethod") }),
|
|
912
701
|
paymentTypes.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-dashed border-input bg-muted/30 px-3 py-3 text-center", children: [
|
|
913
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children:
|
|
702
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("agenda.payment.noMethods") }),
|
|
914
703
|
/* @__PURE__ */ jsxRuntime.jsxs("a", { href: "#/financial/settings", className: "mt-1.5 inline-block text-xs font-medium text-primary hover:underline", children: [
|
|
915
|
-
|
|
704
|
+
t2("agenda.payment.configureMethods"),
|
|
916
705
|
" \u2192"
|
|
917
706
|
] })
|
|
918
707
|
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-1.5", children: paymentTypes.map((pt) => {
|
|
@@ -939,7 +728,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
939
728
|
] }),
|
|
940
729
|
selectedTypeId && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
941
730
|
filteredMethods.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
942
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: needsCard ?
|
|
731
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: needsCard ? t2("agenda.payment.card") : t2("agenda.payment.method") }),
|
|
943
732
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
944
733
|
"select",
|
|
945
734
|
{
|
|
@@ -947,7 +736,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
947
736
|
onChange: (e) => setPaymentMethodId(e.target.value),
|
|
948
737
|
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
949
738
|
children: [
|
|
950
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children:
|
|
739
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("agenda.payment.select") }),
|
|
951
740
|
filteredMethods.map((m) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: m.id, children: m.name }, m.id))
|
|
952
741
|
]
|
|
953
742
|
}
|
|
@@ -955,7 +744,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
955
744
|
] }),
|
|
956
745
|
needsCard && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `grid gap-2.5 ${needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, children: [
|
|
957
746
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
958
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children:
|
|
747
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t2("agenda.payment.cardBrand") }),
|
|
959
748
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
960
749
|
"select",
|
|
961
750
|
{
|
|
@@ -963,14 +752,14 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
963
752
|
onChange: (e) => setCardBrand(e.target.value),
|
|
964
753
|
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
965
754
|
children: [
|
|
966
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children:
|
|
755
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("agenda.payment.selectBrand") }),
|
|
967
756
|
["Visa", "Mastercard", "American Express", "Elo", "Hipercard", "Diners Club"].map((b) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: b, children: b }, b))
|
|
968
757
|
]
|
|
969
758
|
}
|
|
970
759
|
)
|
|
971
760
|
] }),
|
|
972
761
|
needsCardInstallments && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
973
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children:
|
|
762
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t2("agenda.payment.cardInstallments") }),
|
|
974
763
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
975
764
|
"select",
|
|
976
765
|
{
|
|
@@ -987,7 +776,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
987
776
|
] })
|
|
988
777
|
] }),
|
|
989
778
|
needsBank && bankAccounts.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
990
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children:
|
|
779
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t2("agenda.payment.bankAccount") }),
|
|
991
780
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
992
781
|
"select",
|
|
993
782
|
{
|
|
@@ -995,7 +784,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
995
784
|
onChange: (e) => setSelectedBankId(e.target.value),
|
|
996
785
|
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring",
|
|
997
786
|
children: [
|
|
998
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children:
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("agenda.payment.selectAccount") }),
|
|
999
788
|
bankAccounts.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a.id, children: a.name }, a.id))
|
|
1000
789
|
]
|
|
1001
790
|
}
|
|
@@ -1009,7 +798,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
1009
798
|
onClick: handleCheckout,
|
|
1010
799
|
disabled: processing || !selectedTypeId,
|
|
1011
800
|
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",
|
|
1012
|
-
children: processing ?
|
|
801
|
+
children: processing ? t2("agenda.payment.checkingOut") : `${t2("agenda.payment.checkout")} ${formatCurrency2(orderTotal)}`
|
|
1013
802
|
}
|
|
1014
803
|
)
|
|
1015
804
|
] })
|
|
@@ -1030,7 +819,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
1030
819
|
onClick: (e) => e.stopPropagation(),
|
|
1031
820
|
children: [
|
|
1032
821
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10", children: [
|
|
1033
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-semibold", children:
|
|
822
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-base font-semibold", children: t2("agenda.payment.title") }),
|
|
1034
823
|
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) })
|
|
1035
824
|
] }),
|
|
1036
825
|
content
|
|
@@ -1070,9 +859,9 @@ function formatCompactDate(dateStr, locale) {
|
|
|
1070
859
|
});
|
|
1071
860
|
}
|
|
1072
861
|
function InlineNumber({ value, onChange, min = 0, step = 1, className, disabled, tooltip }) {
|
|
1073
|
-
const [draft, setDraft] =
|
|
1074
|
-
const [focused, setFocused] =
|
|
1075
|
-
|
|
862
|
+
const [draft, setDraft] = React13.useState(String(value));
|
|
863
|
+
const [focused, setFocused] = React13.useState(false);
|
|
864
|
+
React13.useEffect(() => {
|
|
1076
865
|
if (!focused) setDraft(String(value));
|
|
1077
866
|
}, [value, focused]);
|
|
1078
867
|
function commit() {
|
|
@@ -1116,11 +905,11 @@ function InlineNumber({ value, onChange, min = 0, step = 1, className, disabled,
|
|
|
1116
905
|
] }) });
|
|
1117
906
|
}
|
|
1118
907
|
function StatusSelect({ value, onChange, statuses, bookingDate }) {
|
|
1119
|
-
const [open, setOpen] =
|
|
1120
|
-
const ref =
|
|
1121
|
-
const dropdownRef =
|
|
908
|
+
const [open, setOpen] = React13.useState(false);
|
|
909
|
+
const ref = React13.useRef(null);
|
|
910
|
+
const dropdownRef = React13.useRef(null);
|
|
1122
911
|
const current = statuses.find((s) => s.value === value);
|
|
1123
|
-
|
|
912
|
+
React13.useEffect(() => {
|
|
1124
913
|
if (!open) return;
|
|
1125
914
|
const handler = (e) => {
|
|
1126
915
|
const target = e.target;
|
|
@@ -1131,8 +920,8 @@ function StatusSelect({ value, onChange, statuses, bookingDate }) {
|
|
|
1131
920
|
document.addEventListener("mousedown", handler);
|
|
1132
921
|
return () => document.removeEventListener("mousedown", handler);
|
|
1133
922
|
}, [open]);
|
|
1134
|
-
const btnRef =
|
|
1135
|
-
const [dropRect, setDropRect] =
|
|
923
|
+
const btnRef = React13.useRef(null);
|
|
924
|
+
const [dropRect, setDropRect] = React13.useState(null);
|
|
1136
925
|
function handleOpen() {
|
|
1137
926
|
if (btnRef.current) setDropRect(btnRef.current.getBoundingClientRect());
|
|
1138
927
|
setOpen((p) => !p);
|
|
@@ -1191,11 +980,11 @@ function StatusSelect({ value, onChange, statuses, bookingDate }) {
|
|
|
1191
980
|
] });
|
|
1192
981
|
}
|
|
1193
982
|
function ProfessionalSelect({ value, onChange, professionals }) {
|
|
1194
|
-
const
|
|
1195
|
-
const [open, setOpen] =
|
|
1196
|
-
const ref =
|
|
983
|
+
const t2 = core.useTranslation();
|
|
984
|
+
const [open, setOpen] = React13.useState(false);
|
|
985
|
+
const ref = React13.useRef(null);
|
|
1197
986
|
const current = professionals.find((p) => p.id === value);
|
|
1198
|
-
|
|
987
|
+
React13.useEffect(() => {
|
|
1199
988
|
if (!open) return;
|
|
1200
989
|
const handler = (e) => {
|
|
1201
990
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -1211,7 +1000,7 @@ function ProfessionalSelect({ value, onChange, professionals }) {
|
|
|
1211
1000
|
onClick: () => setOpen((p) => !p),
|
|
1212
1001
|
className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted/30 transition-colors text-left",
|
|
1213
1002
|
children: [
|
|
1214
|
-
current ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: current.name }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children:
|
|
1003
|
+
current ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: current.name }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t2("agenda.appointment.selectProfessional") }),
|
|
1215
1004
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "ml-auto h-3 w-3 text-muted-foreground shrink-0", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
1216
1005
|
]
|
|
1217
1006
|
}
|
|
@@ -1232,11 +1021,11 @@ function ProfessionalSelect({ value, onChange, professionals }) {
|
|
|
1232
1021
|
] });
|
|
1233
1022
|
}
|
|
1234
1023
|
function LocationSelect({ value, onChange, locations }) {
|
|
1235
|
-
const
|
|
1236
|
-
const [open, setOpen] =
|
|
1237
|
-
const ref =
|
|
1024
|
+
const t2 = core.useTranslation();
|
|
1025
|
+
const [open, setOpen] = React13.useState(false);
|
|
1026
|
+
const ref = React13.useRef(null);
|
|
1238
1027
|
const current = locations.find((l) => l.id === value);
|
|
1239
|
-
|
|
1028
|
+
React13.useEffect(() => {
|
|
1240
1029
|
if (!open) return;
|
|
1241
1030
|
const handler = (e) => {
|
|
1242
1031
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -1252,7 +1041,7 @@ function LocationSelect({ value, onChange, locations }) {
|
|
|
1252
1041
|
onClick: () => setOpen((p) => !p),
|
|
1253
1042
|
className: "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted/30 transition-colors text-left",
|
|
1254
1043
|
children: [
|
|
1255
|
-
current ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: current.name }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children:
|
|
1044
|
+
current ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: current.name }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t2("agenda.appointment.selectLocation") }),
|
|
1256
1045
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "ml-auto h-3 w-3 text-muted-foreground shrink-0", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
|
|
1257
1046
|
]
|
|
1258
1047
|
}
|
|
@@ -1273,12 +1062,13 @@ function LocationSelect({ value, onChange, locations }) {
|
|
|
1273
1062
|
] });
|
|
1274
1063
|
}
|
|
1275
1064
|
function AppointmentModal({ open, mode, bookingId, prefill, initialTab, externalSource, onClose, onCreated }) {
|
|
1276
|
-
const
|
|
1065
|
+
const t2 = core.useTranslation();
|
|
1277
1066
|
const locale = core.getCurrentLocale();
|
|
1278
1067
|
const config = useAgendaConfig();
|
|
1279
1068
|
const provider2 = useAgendaProvider();
|
|
1280
|
-
const initialSnapshot =
|
|
1069
|
+
const initialSnapshot = React13.useRef("");
|
|
1281
1070
|
const createBooking = useAgendaStore((s) => s.createBooking);
|
|
1071
|
+
const guardBookings = saas.useLimitGuard("bookings_month");
|
|
1282
1072
|
const updateBooking = useAgendaStore((s) => s.updateBooking);
|
|
1283
1073
|
const deleteBooking = useAgendaStore((s) => s.deleteBooking);
|
|
1284
1074
|
const fetchBookings = useAgendaStore((s) => s.fetchBookings);
|
|
@@ -1286,59 +1076,52 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1286
1076
|
const professionals = useAgendaStore((s) => s.professionals);
|
|
1287
1077
|
const storeLocations = useAgendaStore((s) => s.locations);
|
|
1288
1078
|
const defaultBookingType = config.bookingTypes[0]?.id ?? "appointment";
|
|
1289
|
-
const [loading, setLoading] =
|
|
1290
|
-
const [bookingType, setBookingType] =
|
|
1291
|
-
const [title, setTitle] =
|
|
1292
|
-
const [clientId, setClientId] =
|
|
1293
|
-
const [clientSearch, setClientSearch] =
|
|
1294
|
-
const [clientPhone, setClientPhone] =
|
|
1295
|
-
const [professionalId, setProfessionalId] =
|
|
1296
|
-
const [locationId, setLocationId] =
|
|
1297
|
-
const [services, setServices] =
|
|
1298
|
-
const [serviceSearch, setServiceSearch] =
|
|
1299
|
-
const [date, setDate] =
|
|
1079
|
+
const [loading, setLoading] = React13.useState(mode === "edit");
|
|
1080
|
+
const [bookingType, setBookingType] = React13.useState(prefill?.kind ?? defaultBookingType);
|
|
1081
|
+
const [title, setTitle] = React13.useState("");
|
|
1082
|
+
const [clientId, setClientId] = React13.useState(prefill?.clientId ?? "");
|
|
1083
|
+
const [clientSearch, setClientSearch] = React13.useState("");
|
|
1084
|
+
const [clientPhone, setClientPhone] = React13.useState("");
|
|
1085
|
+
const [professionalId, setProfessionalId] = React13.useState(prefill?.professionalId ?? "");
|
|
1086
|
+
const [locationId, setLocationId] = React13.useState(prefill?.locationId ?? "");
|
|
1087
|
+
const [services, setServices] = React13.useState([]);
|
|
1088
|
+
const [serviceSearch, setServiceSearch] = React13.useState("");
|
|
1089
|
+
const [date, setDate] = React13.useState(() => {
|
|
1300
1090
|
if (prefill?.startsAt) return prefill.startsAt.slice(0, 10);
|
|
1301
1091
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1302
1092
|
});
|
|
1303
|
-
const [startTime, setStartTime] =
|
|
1093
|
+
const [startTime, setStartTime] = React13.useState(() => {
|
|
1304
1094
|
if (prefill?.startsAt) {
|
|
1305
1095
|
const d = new Date(prefill.startsAt);
|
|
1306
1096
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
1307
1097
|
}
|
|
1308
1098
|
return "09:00";
|
|
1309
1099
|
});
|
|
1310
|
-
const [status, setStatus] =
|
|
1311
|
-
const [notes, setNotes] =
|
|
1312
|
-
const [showNotes, setShowNotes] =
|
|
1313
|
-
const [saving, setSaving] =
|
|
1314
|
-
const [conflict, setConflict] =
|
|
1315
|
-
const [
|
|
1316
|
-
const [
|
|
1317
|
-
const [
|
|
1318
|
-
const [
|
|
1319
|
-
const [
|
|
1320
|
-
const [
|
|
1321
|
-
const [
|
|
1322
|
-
const [
|
|
1323
|
-
const [
|
|
1324
|
-
const [
|
|
1325
|
-
const [
|
|
1326
|
-
const [
|
|
1327
|
-
const [
|
|
1328
|
-
const [
|
|
1329
|
-
const [
|
|
1330
|
-
const [
|
|
1331
|
-
const [
|
|
1332
|
-
const [
|
|
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);
|
|
1100
|
+
const [status, setStatus] = React13.useState("scheduled");
|
|
1101
|
+
const [notes, setNotes] = React13.useState("");
|
|
1102
|
+
const [showNotes, setShowNotes] = React13.useState(false);
|
|
1103
|
+
const [saving, setSaving] = React13.useState(false);
|
|
1104
|
+
const [conflict, setConflict] = React13.useState(false);
|
|
1105
|
+
const [serviceOptions, setServiceOptions] = React13.useState([]);
|
|
1106
|
+
const [initialServiceOptions, setInitialServiceOptions] = React13.useState([]);
|
|
1107
|
+
const [editingDate, setEditingDate] = React13.useState(false);
|
|
1108
|
+
const [editingTime, setEditingTime] = React13.useState(false);
|
|
1109
|
+
const [confirmingDelete, setConfirmingDelete] = React13.useState(false);
|
|
1110
|
+
const [deleting, setDeleting] = React13.useState(false);
|
|
1111
|
+
const [editOrderId, setEditOrderId] = React13.useState(null);
|
|
1112
|
+
const [editOrderTotal, setEditOrderTotal] = React13.useState(0);
|
|
1113
|
+
const [editPaymentStatus, setEditPaymentStatus] = React13.useState("none");
|
|
1114
|
+
const [paymentStatusLoading, setPaymentStatusLoading] = React13.useState(false);
|
|
1115
|
+
const [modalTab, setModalTab] = React13.useState("appointment");
|
|
1116
|
+
const [clientData, setClientData] = React13.useState(null);
|
|
1117
|
+
const [clientDataLoading, setClientDataLoading] = React13.useState(false);
|
|
1118
|
+
const [clientEditing, setClientEditing] = React13.useState(false);
|
|
1119
|
+
const [cancellationReasons, setCancellationReasons] = React13.useState([]);
|
|
1120
|
+
const [cancellationReasonId, setCancellationReasonId] = React13.useState("");
|
|
1121
|
+
const [cancellationNotes, setCancellationNotes] = React13.useState("");
|
|
1122
|
+
const [cancellationReasonsLoading, setCancellationReasonsLoading] = React13.useState(false);
|
|
1340
1123
|
const activeType = config.bookingTypes.find((bt) => bt.id === bookingType) ?? config.bookingTypes[0];
|
|
1341
|
-
|
|
1124
|
+
React13.useEffect(() => {
|
|
1342
1125
|
if (!open) return;
|
|
1343
1126
|
setClientId(prefill?.clientId ?? "");
|
|
1344
1127
|
setClientSearch("");
|
|
@@ -1352,11 +1135,6 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1352
1135
|
setShowNotes(false);
|
|
1353
1136
|
setConflict(false);
|
|
1354
1137
|
setLoading(mode === "edit");
|
|
1355
|
-
setQuickCreate(false);
|
|
1356
|
-
setNewClientName("");
|
|
1357
|
-
setNewClientPhone("");
|
|
1358
|
-
setNewClientEmail("");
|
|
1359
|
-
setCreatingClient(false);
|
|
1360
1138
|
setBookingType(prefill?.kind ?? defaultBookingType);
|
|
1361
1139
|
setTitle("");
|
|
1362
1140
|
setEditingDate(false);
|
|
@@ -1387,7 +1165,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1387
1165
|
return "09:00";
|
|
1388
1166
|
});
|
|
1389
1167
|
}, [open]);
|
|
1390
|
-
|
|
1168
|
+
React13.useEffect(() => {
|
|
1391
1169
|
if (!open || mode !== "edit" || !bookingId) return;
|
|
1392
1170
|
setLoading(true);
|
|
1393
1171
|
provider2.getBookingById(bookingId).then((b) => {
|
|
@@ -1444,7 +1222,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1444
1222
|
}
|
|
1445
1223
|
}).catch(() => setLoading(false));
|
|
1446
1224
|
}, [open, mode, bookingId]);
|
|
1447
|
-
|
|
1225
|
+
React13.useEffect(() => {
|
|
1448
1226
|
if (modalTab !== "client" || !clientId || !config.contactLookup || clientData) return;
|
|
1449
1227
|
setClientDataLoading(true);
|
|
1450
1228
|
config.contactLookup.getById(clientId).then((result) => {
|
|
@@ -1452,12 +1230,12 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1452
1230
|
setClientDataLoading(false);
|
|
1453
1231
|
}).catch(() => setClientDataLoading(false));
|
|
1454
1232
|
}, [modalTab, clientId]);
|
|
1455
|
-
|
|
1233
|
+
React13.useEffect(() => {
|
|
1456
1234
|
if (!open || status !== "cancelled" || !config.cancellationDetails || cancellationReasons.length > 0) return;
|
|
1457
1235
|
setCancellationReasonsLoading(true);
|
|
1458
1236
|
config.cancellationDetails.listReasons().then((reasons) => setCancellationReasons(reasons)).catch(() => setCancellationReasons([])).finally(() => setCancellationReasonsLoading(false));
|
|
1459
1237
|
}, [open, status, config.cancellationDetails, cancellationReasons.length]);
|
|
1460
|
-
|
|
1238
|
+
React13.useEffect(() => {
|
|
1461
1239
|
if (!open || !config.serviceLookup) return;
|
|
1462
1240
|
const lookup = config.serviceLookup;
|
|
1463
1241
|
const fetch = lookup.list ? () => lookup.list() : () => lookup.search("");
|
|
@@ -1466,25 +1244,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1466
1244
|
setInitialServiceOptions(opts);
|
|
1467
1245
|
}).catch(() => setInitialServiceOptions([]));
|
|
1468
1246
|
}, [open]);
|
|
1469
|
-
|
|
1470
|
-
if (!config.contactLookup || clientSearch.length < 1 || clientId) {
|
|
1471
|
-
setClientOptions([]);
|
|
1472
|
-
setClientSearching(false);
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
setClientSearching(true);
|
|
1476
|
-
const timer = setTimeout(async () => {
|
|
1477
|
-
try {
|
|
1478
|
-
const results = await config.contactLookup.search(clientSearch);
|
|
1479
|
-
setClientOptions(results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, price: r.price, data: r.data })));
|
|
1480
|
-
} catch {
|
|
1481
|
-
setClientOptions([]);
|
|
1482
|
-
}
|
|
1483
|
-
setClientSearching(false);
|
|
1484
|
-
}, 250);
|
|
1485
|
-
return () => clearTimeout(timer);
|
|
1486
|
-
}, [clientSearch, clientId]);
|
|
1487
|
-
React12.useEffect(() => {
|
|
1247
|
+
React13.useEffect(() => {
|
|
1488
1248
|
if (!config.serviceLookup || serviceSearch.length < 1) {
|
|
1489
1249
|
setServiceOptions([]);
|
|
1490
1250
|
return;
|
|
@@ -1500,19 +1260,19 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1500
1260
|
return () => clearTimeout(timer);
|
|
1501
1261
|
}, [serviceSearch]);
|
|
1502
1262
|
const serviceDuration = services.reduce((sum, s) => sum + s.durationMinutes, 0);
|
|
1503
|
-
const selectionDuration =
|
|
1263
|
+
const selectionDuration = React13.useMemo(() => {
|
|
1504
1264
|
if (!prefill?.startsAt || !prefill?.endsAt) return 0;
|
|
1505
1265
|
return Math.round((new Date(prefill.endsAt).getTime() - new Date(prefill.startsAt).getTime()) / 6e4);
|
|
1506
1266
|
}, [prefill?.startsAt, prefill?.endsAt]);
|
|
1507
1267
|
const totalDuration = serviceDuration > 0 ? serviceDuration : selectionDuration;
|
|
1508
1268
|
const totalPrice = services.reduce((sum, s) => sum + s.price, 0);
|
|
1509
1269
|
const { currency } = config;
|
|
1510
|
-
const endTime =
|
|
1270
|
+
const endTime = React13.useMemo(() => {
|
|
1511
1271
|
const [h, m] = startTime.split(":").map(Number);
|
|
1512
1272
|
const total = h * 60 + m + totalDuration;
|
|
1513
1273
|
return `${String(Math.floor(total / 60) % 24).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
|
|
1514
1274
|
}, [startTime, totalDuration]);
|
|
1515
|
-
|
|
1275
|
+
React13.useEffect(() => {
|
|
1516
1276
|
if (!professionalId || totalDuration === 0) {
|
|
1517
1277
|
setConflict(false);
|
|
1518
1278
|
return;
|
|
@@ -1527,12 +1287,6 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1527
1287
|
}, 300);
|
|
1528
1288
|
return () => clearTimeout(timer);
|
|
1529
1289
|
}, [professionalId, date, startTime, endTime, bookingId]);
|
|
1530
|
-
function selectClient(opt) {
|
|
1531
|
-
setClientId(opt.id);
|
|
1532
|
-
setClientSearch(opt.label);
|
|
1533
|
-
setClientOptions([]);
|
|
1534
|
-
setClientPhone(opt.data?.phone ?? "");
|
|
1535
|
-
}
|
|
1536
1290
|
function addService(opt) {
|
|
1537
1291
|
const duration = opt.data?.duration_minutes || 60;
|
|
1538
1292
|
setServices((p) => [...p, { serviceId: opt.id, name: opt.label, durationMinutes: duration, price: opt.price ?? 0 }]);
|
|
@@ -1545,6 +1299,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1545
1299
|
async function handleSubmit(e) {
|
|
1546
1300
|
e.preventDefault();
|
|
1547
1301
|
if (!canSubmit) return;
|
|
1302
|
+
if (mode === "create" && await guardBookings() === "blocked") return;
|
|
1548
1303
|
setSaving(true);
|
|
1549
1304
|
try {
|
|
1550
1305
|
const startsAt = (/* @__PURE__ */ new Date(`${date}T${startTime}:00`)).toISOString();
|
|
@@ -1559,6 +1314,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1559
1314
|
title: activeType.fields.title ? title.trim() : void 0,
|
|
1560
1315
|
services: services.map((s) => ({ serviceId: s.serviceId, name: s.name, durationMinutes: s.durationMinutes, price: s.price }))
|
|
1561
1316
|
});
|
|
1317
|
+
saas.invalidateLimit("bookings_month");
|
|
1562
1318
|
onClose();
|
|
1563
1319
|
onCreated?.(newBooking.id, externalSource);
|
|
1564
1320
|
} else if (bookingId) {
|
|
@@ -1593,14 +1349,16 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1593
1349
|
await deleteBooking(bookingId);
|
|
1594
1350
|
onClose();
|
|
1595
1351
|
} catch {
|
|
1352
|
+
setConfirmingDelete(false);
|
|
1353
|
+
} finally {
|
|
1354
|
+
setDeleting(false);
|
|
1596
1355
|
}
|
|
1597
|
-
setDeleting(false);
|
|
1598
1356
|
}
|
|
1599
1357
|
const isPaidBooking = editPaymentStatus === "paid";
|
|
1600
1358
|
const selectedCancellationReason = cancellationReasons.find((reason) => reason.id === cancellationReasonId);
|
|
1601
1359
|
const needsCancellationReason = mode === "edit" && status === "cancelled" && !!config.cancellationDetails;
|
|
1602
1360
|
const needsCancellationNotes = needsCancellationReason && !!selectedCancellationReason?.requiresNotes;
|
|
1603
|
-
const formSnapshot =
|
|
1361
|
+
const formSnapshot = React13.useMemo(
|
|
1604
1362
|
() => 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
1363
|
[title, clientId, professionalId, locationId, date, startTime, status, notes, cancellationReasonId, cancellationNotes, services]
|
|
1606
1364
|
);
|
|
@@ -1611,9 +1369,9 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1611
1369
|
if (!v) onClose();
|
|
1612
1370
|
}, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.ModalContent, { size: "lg", noPadding: true, children: [
|
|
1613
1371
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-5 pt-4 pb-3 shrink-0 space-y-3", children: [
|
|
1614
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.ModalTitle, { className: "sr-only", children: mode === "create" ?
|
|
1372
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.ModalTitle, { className: "sr-only", children: mode === "create" ? t2("agenda.appointment.new") : t2("agenda.appointment.edit") }),
|
|
1615
1373
|
showTabs && mode === "create" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: config.bookingTypes.map((bt) => {
|
|
1616
|
-
const label =
|
|
1374
|
+
const label = t2(`agenda.bookingType.${bt.id}`) || bt.label;
|
|
1617
1375
|
const isActive = bookingType === bt.id;
|
|
1618
1376
|
const BtIcon = BOOKING_TYPE_ICONS[bt.icon] ?? lucideReact.Calendar;
|
|
1619
1377
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -1631,9 +1389,9 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1631
1389
|
bt.id
|
|
1632
1390
|
);
|
|
1633
1391
|
}) }) : mode === "edit" && (config.clientEntityDef && clientId || config.modules.financial && editOrderId ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: [
|
|
1634
|
-
{ id: "appointment", label:
|
|
1635
|
-
{ id: "client", label:
|
|
1636
|
-
{ id: "financial", label:
|
|
1392
|
+
{ id: "appointment", label: t2(`agenda.bookingType.${bookingType}`) || activeType.label, icon: lucideReact.Calendar, show: true },
|
|
1393
|
+
{ id: "client", label: t2("agenda.appointment.clientTab"), icon: lucideReact.User, show: !!config.clientEntityDef && !!clientId },
|
|
1394
|
+
{ id: "financial", label: t2("agenda.payment.title"), icon: lucideReact.DollarSign, show: config.modules.financial && !!editOrderId }
|
|
1637
1395
|
].filter((tab) => tab.show).map((tab) => {
|
|
1638
1396
|
const isActive = modalTab === tab.id;
|
|
1639
1397
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -1652,7 +1410,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1652
1410
|
},
|
|
1653
1411
|
tab.id
|
|
1654
1412
|
);
|
|
1655
|
-
}) }) : /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children:
|
|
1413
|
+
}) }) : /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children: t2(`agenda.bookingType.${bookingType}`) || activeType.label })),
|
|
1656
1414
|
modalTab === "appointment" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1657
1415
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1658
1416
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
@@ -1710,7 +1468,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1710
1468
|
] }),
|
|
1711
1469
|
conflict && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-destructive/10 px-3 py-1.5 text-xs text-destructive", children: [
|
|
1712
1470
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
1713
|
-
|
|
1471
|
+
t2("agenda.appointment.conflict")
|
|
1714
1472
|
] })
|
|
1715
1473
|
] })
|
|
1716
1474
|
] }),
|
|
@@ -1751,7 +1509,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1751
1509
|
onBack: () => setModalTab("appointment"),
|
|
1752
1510
|
onEdit: () => setClientEditing(true)
|
|
1753
1511
|
}
|
|
1754
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children:
|
|
1512
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: t2("agenda.appointment.noClientData") }) }),
|
|
1755
1513
|
modalTab === "financial" && editOrderId && config.financialBridge && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1756
1514
|
BookingPaymentPanel,
|
|
1757
1515
|
{
|
|
@@ -1788,7 +1546,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1788
1546
|
type: "text",
|
|
1789
1547
|
value: title,
|
|
1790
1548
|
onChange: (e) => setTitle(e.target.value),
|
|
1791
|
-
placeholder:
|
|
1549
|
+
placeholder: t2("agenda.appointment.addTitle"),
|
|
1792
1550
|
autoFocus: mode === "create",
|
|
1793
1551
|
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
1552
|
}
|
|
@@ -1796,133 +1554,25 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1796
1554
|
] }),
|
|
1797
1555
|
activeType.fields.client && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1798
1556
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1799
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children:
|
|
1800
|
-
|
|
1801
|
-
clientPhone && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground shrink-0", children: clientPhone }),
|
|
1802
|
-
!isPaidBooking && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1803
|
-
"button",
|
|
1804
|
-
{
|
|
1805
|
-
type: "button",
|
|
1806
|
-
onClick: () => {
|
|
1807
|
-
setClientId("");
|
|
1808
|
-
setClientSearch("");
|
|
1809
|
-
setClientPhone("");
|
|
1810
|
-
},
|
|
1811
|
-
className: "p-0.5 text-muted-foreground hover:text-destructive shrink-0",
|
|
1812
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" })
|
|
1813
|
-
}
|
|
1814
|
-
)
|
|
1815
|
-
] }) : !quickCreate ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1816
|
-
SearchCombobox,
|
|
1557
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1558
|
+
saas.ContactPicker,
|
|
1817
1559
|
{
|
|
1818
|
-
value: clientSearch,
|
|
1819
|
-
onChange: (
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
onSelect: selectClient,
|
|
1824
|
-
options: clientOptions,
|
|
1825
|
-
loading: clientSearching,
|
|
1826
|
-
placeholder: t("agenda.appointment.searchClient"),
|
|
1827
|
-
allowCreate: true,
|
|
1828
|
-
createLabel: t("agenda.appointment.newClient"),
|
|
1829
|
-
onCreateNew: (name) => {
|
|
1830
|
-
setQuickCreate(true);
|
|
1831
|
-
setNewClientName(name);
|
|
1832
|
-
setNewClientPhone("");
|
|
1833
|
-
setNewClientEmail("");
|
|
1560
|
+
value: clientId || clientSearch ? { id: clientId || void 0, name: clientSearch, phone: clientPhone } : null,
|
|
1561
|
+
onChange: (contact) => {
|
|
1562
|
+
setClientId(contact?.id ?? "");
|
|
1563
|
+
setClientSearch(contact?.name ?? "");
|
|
1564
|
+
setClientPhone(contact?.phone ?? "");
|
|
1834
1565
|
},
|
|
1835
|
-
|
|
1836
|
-
|
|
1566
|
+
kind: config.clientKind,
|
|
1567
|
+
extensionTable: "clients",
|
|
1568
|
+
lookup: config.contactLookup,
|
|
1569
|
+
clearable: !isPaidBooking,
|
|
1570
|
+
variant: "inline",
|
|
1571
|
+
placeholder: t2("agenda.appointment.searchClient"),
|
|
1572
|
+
createLabel: t2("agenda.appointment.newClient"),
|
|
1573
|
+
hint: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-warning", children: t2("agenda.appointment.selectClientHint") })
|
|
1837
1574
|
}
|
|
1838
|
-
)
|
|
1839
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1840
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1841
|
-
"input",
|
|
1842
|
-
{
|
|
1843
|
-
type: "text",
|
|
1844
|
-
value: newClientName,
|
|
1845
|
-
onChange: (e) => setNewClientName(e.target.value),
|
|
1846
|
-
placeholder: t("agenda.appointment.name"),
|
|
1847
|
-
className: "flex-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-ring",
|
|
1848
|
-
autoFocus: true
|
|
1849
|
-
}
|
|
1850
|
-
),
|
|
1851
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1852
|
-
"button",
|
|
1853
|
-
{
|
|
1854
|
-
type: "button",
|
|
1855
|
-
onClick: () => setQuickCreate(false),
|
|
1856
|
-
className: "p-1 text-muted-foreground hover:text-foreground shrink-0",
|
|
1857
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" })
|
|
1858
|
-
}
|
|
1859
|
-
)
|
|
1860
|
-
] }),
|
|
1861
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
1862
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1863
|
-
"input",
|
|
1864
|
-
{
|
|
1865
|
-
type: "tel",
|
|
1866
|
-
value: newClientPhone,
|
|
1867
|
-
onChange: (e) => setNewClientPhone(e.target.value),
|
|
1868
|
-
placeholder: t("agenda.appointment.phone"),
|
|
1869
|
-
className: "rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1870
|
-
}
|
|
1871
|
-
),
|
|
1872
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1873
|
-
"input",
|
|
1874
|
-
{
|
|
1875
|
-
type: "email",
|
|
1876
|
-
value: newClientEmail,
|
|
1877
|
-
onChange: (e) => setNewClientEmail(e.target.value),
|
|
1878
|
-
placeholder: t("agenda.appointment.emailOptional"),
|
|
1879
|
-
className: "rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1880
|
-
}
|
|
1881
|
-
)
|
|
1882
|
-
] }),
|
|
1883
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1884
|
-
"button",
|
|
1885
|
-
{
|
|
1886
|
-
type: "button",
|
|
1887
|
-
disabled: creatingClient || !newClientName.trim(),
|
|
1888
|
-
onClick: async () => {
|
|
1889
|
-
setCreatingClient(true);
|
|
1890
|
-
try {
|
|
1891
|
-
const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
|
|
1892
|
-
const { getAgendaTenantId: getAgendaTenantId2 } = await import('./tenant-222EHYUC.cjs');
|
|
1893
|
-
const supabase = getSupabaseClientOptional4();
|
|
1894
|
-
const tenantId = getAgendaTenantId2();
|
|
1895
|
-
if (!supabase || !tenantId) throw new Error("Not initialized");
|
|
1896
|
-
const { data: personRow, error: personError } = await supabase.from("people").insert({
|
|
1897
|
-
tenant_id: tenantId,
|
|
1898
|
-
kind: config.clientKind,
|
|
1899
|
-
name: newClientName.trim(),
|
|
1900
|
-
phone: newClientPhone.trim() || null,
|
|
1901
|
-
email: newClientEmail.trim() || null
|
|
1902
|
-
}).select("id, name").single();
|
|
1903
|
-
if (personError) throw personError;
|
|
1904
|
-
const { error: clientError } = await supabase.from("clients").insert({
|
|
1905
|
-
person_id: personRow.id,
|
|
1906
|
-
tenant_id: tenantId
|
|
1907
|
-
});
|
|
1908
|
-
if (clientError) {
|
|
1909
|
-
await supabase.from("people").delete().eq("id", personRow.id);
|
|
1910
|
-
throw clientError;
|
|
1911
|
-
}
|
|
1912
|
-
setClientId(personRow.id);
|
|
1913
|
-
setClientSearch(personRow.name);
|
|
1914
|
-
setQuickCreate(false);
|
|
1915
|
-
} catch (err) {
|
|
1916
|
-
const { toast: toast3 } = await import('@fayz-ai/ui');
|
|
1917
|
-
toast3.error(t("agenda.appointment.createClientFailed"), { description: err?.message });
|
|
1918
|
-
}
|
|
1919
|
-
setCreatingClient(false);
|
|
1920
|
-
},
|
|
1921
|
-
className: "w-full rounded-md bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset disabled:opacity-40 transition-colors",
|
|
1922
|
-
children: creatingClient ? t("agenda.appointment.creating") : t("agenda.appointment.create")
|
|
1923
|
-
}
|
|
1924
|
-
)
|
|
1925
|
-
] }) })
|
|
1575
|
+
) })
|
|
1926
1576
|
] }),
|
|
1927
1577
|
activeType.fields.location && config.modules.locationSelection && (storeLocations.length > 0 || config.locations.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1928
1578
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
@@ -1950,7 +1600,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1950
1600
|
step: 5,
|
|
1951
1601
|
className: "w-10 shrink-0",
|
|
1952
1602
|
disabled: isPaidBooking,
|
|
1953
|
-
tooltip:
|
|
1603
|
+
tooltip: t2("agenda.appointment.duration")
|
|
1954
1604
|
}
|
|
1955
1605
|
),
|
|
1956
1606
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-muted-foreground", children: "min" })
|
|
@@ -1966,20 +1616,20 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1966
1616
|
step: 0.01,
|
|
1967
1617
|
className: "w-14 shrink-0",
|
|
1968
1618
|
disabled: isPaidBooking,
|
|
1969
|
-
tooltip:
|
|
1619
|
+
tooltip: t2("agenda.appointment.price")
|
|
1970
1620
|
}
|
|
1971
1621
|
)
|
|
1972
1622
|
] }),
|
|
1973
1623
|
!isPaidBooking && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => removeService(idx), className: "p-0.5 ml-2 text-muted-foreground hover:text-destructive shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" }) })
|
|
1974
1624
|
] }, idx)) }),
|
|
1975
1625
|
!isPaidBooking && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1976
|
-
SearchCombobox,
|
|
1626
|
+
ui.SearchCombobox,
|
|
1977
1627
|
{
|
|
1978
1628
|
value: serviceSearch,
|
|
1979
1629
|
onChange: setServiceSearch,
|
|
1980
1630
|
onSelect: addService,
|
|
1981
1631
|
options: serviceOptions,
|
|
1982
|
-
placeholder:
|
|
1632
|
+
placeholder: t2("agenda.appointment.searchService"),
|
|
1983
1633
|
onBlurEmpty: () => {
|
|
1984
1634
|
},
|
|
1985
1635
|
onFocus: () => {
|
|
@@ -1996,7 +1646,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1996
1646
|
] });
|
|
1997
1647
|
},
|
|
1998
1648
|
minimal: true,
|
|
1999
|
-
inlineLabel: services.length > 0 ?
|
|
1649
|
+
inlineLabel: services.length > 0 ? t2("agenda.appointment.addService") : t2("agenda.appointment.addServiceFirst"),
|
|
2000
1650
|
inlineIcon: services.length > 0 ? "plus" : "search"
|
|
2001
1651
|
}
|
|
2002
1652
|
)
|
|
@@ -2017,7 +1667,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2017
1667
|
disabled: cancellationReasonsLoading,
|
|
2018
1668
|
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
1669
|
children: [
|
|
2020
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: cancellationReasonsLoading ?
|
|
1670
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: cancellationReasonsLoading ? t2("agenda.cancellation.loadingReasons") : t2("agenda.cancellation.selectReason") }),
|
|
2021
1671
|
cancellationReasons.map((reason) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: reason.id, children: [
|
|
2022
1672
|
reason.label,
|
|
2023
1673
|
reason.requiresNotes ? " *" : ""
|
|
@@ -2030,7 +1680,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2030
1680
|
{
|
|
2031
1681
|
value: cancellationNotes,
|
|
2032
1682
|
onChange: (event) => setCancellationNotes(event.target.value),
|
|
2033
|
-
placeholder: needsCancellationNotes ?
|
|
1683
|
+
placeholder: needsCancellationNotes ? t2("agenda.cancellation.notesRequired") : t2("agenda.cancellation.notesOptional"),
|
|
2034
1684
|
rows: 2,
|
|
2035
1685
|
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
1686
|
}
|
|
@@ -2045,7 +1695,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2045
1695
|
type: "button",
|
|
2046
1696
|
onClick: () => setShowNotes(true),
|
|
2047
1697
|
className: "text-sm text-muted-foreground hover:text-foreground transition-colors text-left py-1.5 pl-2",
|
|
2048
|
-
children:
|
|
1698
|
+
children: t2("agenda.appointment.addNotes")
|
|
2049
1699
|
}
|
|
2050
1700
|
)
|
|
2051
1701
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3 py-1", children: [
|
|
@@ -2055,7 +1705,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2055
1705
|
{
|
|
2056
1706
|
value: notes,
|
|
2057
1707
|
onChange: (e) => setNotes(e.target.value),
|
|
2058
|
-
placeholder:
|
|
1708
|
+
placeholder: t2("agenda.appointment.addDescription"),
|
|
2059
1709
|
rows: 2,
|
|
2060
1710
|
autoFocus: true,
|
|
2061
1711
|
className: "w-full flex-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-ring"
|
|
@@ -2065,14 +1715,14 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2065
1715
|
] })
|
|
2066
1716
|
] }),
|
|
2067
1717
|
modalTab === "appointment" && !clientEditing && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-2.5 border-t fayz-glass-divider bg-transparent shrink-0 sm:rounded-b-2xl", children: confirmingDelete ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2068
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive font-medium", children:
|
|
1718
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive font-medium", children: t2("agenda.appointment.deleteConfirm") }),
|
|
2069
1719
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2070
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", size: "sm", onClick: () => setConfirmingDelete(false), children:
|
|
2071
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "destructive", size: "sm", onClick: handleDelete, disabled: deleting, children: deleting ?
|
|
1720
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", size: "sm", onClick: () => setConfirmingDelete(false), children: t2("agenda.appointment.cancel") }),
|
|
1721
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "destructive", size: "sm", onClick: handleDelete, disabled: deleting, children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete") })
|
|
2072
1722
|
] })
|
|
2073
1723
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2074
1724
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2075
|
-
mode === "edit" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "text-xs text-destructive font-medium hover:underline", children:
|
|
1725
|
+
mode === "edit" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "text-xs text-destructive font-medium hover:underline", children: t2("agenda.appointment.delete") }),
|
|
2076
1726
|
services.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
2077
1727
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
2078
1728
|
totalDuration,
|
|
@@ -2090,7 +1740,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2090
1740
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-2.5 w-2.5" }),
|
|
2091
1741
|
fmtCurrency(totalPrice, currency.locale, currency.code),
|
|
2092
1742
|
" \xB7 ",
|
|
2093
|
-
|
|
1743
|
+
t2("agenda.payment.paid")
|
|
2094
1744
|
]
|
|
2095
1745
|
}
|
|
2096
1746
|
) : date <= (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2106,7 +1756,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2106
1756
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.HandCoins, { className: "h-3 w-3" }),
|
|
2107
1757
|
fmtCurrency(totalPrice, currency.locale, currency.code),
|
|
2108
1758
|
" \xB7 ",
|
|
2109
|
-
|
|
1759
|
+
t2("agenda.payment.collect")
|
|
2110
1760
|
]
|
|
2111
1761
|
}
|
|
2112
1762
|
) : /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
@@ -2119,8 +1769,8 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2119
1769
|
] })
|
|
2120
1770
|
] }),
|
|
2121
1771
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2122
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", size: "sm", onClick: onClose, className: "hidden sm:inline-flex", children:
|
|
2123
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", variant: "default", size: "sm", disabled: !canSubmit || !isDirty, children: saving ?
|
|
1772
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", size: "sm", onClick: onClose, className: "hidden sm:inline-flex", children: t2("agenda.appointment.cancel") }),
|
|
1773
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", variant: "default", size: "sm", disabled: !canSubmit || !isDirty, children: saving ? t2("agenda.appointment.saving") : mode === "create" ? t2("agenda.appointment.save") : t2("agenda.appointment.update") })
|
|
2124
1774
|
] })
|
|
2125
1775
|
] }) })
|
|
2126
1776
|
] }),
|
|
@@ -2135,7 +1785,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
2135
1785
|
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
|
|
2136
1786
|
children: [
|
|
2137
1787
|
/* @__PURE__ */ jsxRuntime.jsx("svg", { className: "h-3 w-3", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 12L6 8l4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }),
|
|
2138
|
-
|
|
1788
|
+
t2("agenda.payment.backToAppointment")
|
|
2139
1789
|
]
|
|
2140
1790
|
}
|
|
2141
1791
|
) }) })
|
|
@@ -2164,19 +1814,19 @@ function FloatingPanel({
|
|
|
2164
1814
|
children,
|
|
2165
1815
|
zIndex = 100
|
|
2166
1816
|
}) {
|
|
2167
|
-
const ref =
|
|
2168
|
-
const [phase, setPhase] =
|
|
2169
|
-
const phaseRef =
|
|
1817
|
+
const ref = React13.useRef(null);
|
|
1818
|
+
const [phase, setPhase] = React13.useState("visible");
|
|
1819
|
+
const phaseRef = React13.useRef("visible");
|
|
2170
1820
|
phaseRef.current = phase;
|
|
2171
|
-
const onCloseRef =
|
|
1821
|
+
const onCloseRef = React13.useRef(onClose);
|
|
2172
1822
|
onCloseRef.current = onClose;
|
|
2173
|
-
const expandCallbackRef =
|
|
2174
|
-
const dismiss =
|
|
1823
|
+
const expandCallbackRef = React13.useRef(null);
|
|
1824
|
+
const dismiss = React13.useCallback(() => {
|
|
2175
1825
|
if (phaseRef.current !== "visible") return;
|
|
2176
1826
|
setPhase("dismissing");
|
|
2177
1827
|
setTimeout(() => onCloseRef.current(), 150);
|
|
2178
1828
|
}, []);
|
|
2179
|
-
const expandThen =
|
|
1829
|
+
const expandThen = React13.useCallback((callback) => {
|
|
2180
1830
|
if (phaseRef.current !== "visible") return;
|
|
2181
1831
|
expandCallbackRef.current = callback;
|
|
2182
1832
|
setPhase("expanding");
|
|
@@ -2185,7 +1835,7 @@ function FloatingPanel({
|
|
|
2185
1835
|
onCloseRef.current();
|
|
2186
1836
|
}, 220);
|
|
2187
1837
|
}, []);
|
|
2188
|
-
|
|
1838
|
+
React13.useEffect(() => {
|
|
2189
1839
|
function handleClick(e) {
|
|
2190
1840
|
if (phaseRef.current !== "visible") return;
|
|
2191
1841
|
if (ref.current && !ref.current.contains(e.target)) {
|
|
@@ -2236,10 +1886,10 @@ function fmtCurrency2(amount, locale, code) {
|
|
|
2236
1886
|
return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(amount);
|
|
2237
1887
|
}
|
|
2238
1888
|
function PopoverStatusSelect({ value, statuses, bookingStartsAt, onChange }) {
|
|
2239
|
-
const [open, setOpen] =
|
|
2240
|
-
const ref =
|
|
1889
|
+
const [open, setOpen] = React13.useState(false);
|
|
1890
|
+
const ref = React13.useRef(null);
|
|
2241
1891
|
const current = statuses.find((s) => s.value === value);
|
|
2242
|
-
|
|
1892
|
+
React13.useEffect(() => {
|
|
2243
1893
|
if (!open) return;
|
|
2244
1894
|
const h = (e) => {
|
|
2245
1895
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -2287,13 +1937,13 @@ function PopoverStatusSelect({ value, statuses, bookingStartsAt, onChange }) {
|
|
|
2287
1937
|
] });
|
|
2288
1938
|
}
|
|
2289
1939
|
function AppointmentPopover({ booking, position, onClose, onEdit }) {
|
|
2290
|
-
const
|
|
1940
|
+
const t2 = core.useTranslation();
|
|
2291
1941
|
const config = useAgendaConfig();
|
|
2292
1942
|
const deleteBooking = useAgendaStore((s) => s.deleteBooking);
|
|
2293
1943
|
const updateStatus = useAgendaStore((s) => s.updateBookingStatus);
|
|
2294
1944
|
const openModal = useAgendaStore((s) => s.openAppointmentModal);
|
|
2295
|
-
const [confirmingDelete, setConfirmingDelete] =
|
|
2296
|
-
const [deleting, setDeleting] =
|
|
1945
|
+
const [confirmingDelete, setConfirmingDelete] = React13.useState(false);
|
|
1946
|
+
const [deleting, setDeleting] = React13.useState(false);
|
|
2297
1947
|
const statusConfig = config.statuses.find((s) => s.value === booking.status);
|
|
2298
1948
|
const color = statusConfig?.color ?? "#6b7280";
|
|
2299
1949
|
const timeRange = `${fmtTime(booking.startsAt)}${booking.endsAt ? " \u2013 " + fmtTime(booking.endsAt) : ""}`;
|
|
@@ -2308,13 +1958,13 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
|
|
|
2308
1958
|
}
|
|
2309
1959
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(FloatingPanel, { position, width: 320, height: 340, onClose, children: (panel) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2310
1960
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end gap-0.5 px-3 pt-3 pb-1", children: confirmingDelete ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2311
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive font-medium mr-auto pl-1", children:
|
|
1961
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive font-medium mr-auto pl-1", children: t2("agenda.appointment.deleteConfirm") }),
|
|
2312
1962
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2313
1963
|
"button",
|
|
2314
1964
|
{
|
|
2315
1965
|
onClick: () => setConfirmingDelete(false),
|
|
2316
1966
|
className: "rounded px-2 py-0.5 text-[11px] font-medium hover:bg-muted/50 transition-colors",
|
|
2317
|
-
children:
|
|
1967
|
+
children: t2("agenda.appointment.cancel")
|
|
2318
1968
|
}
|
|
2319
1969
|
),
|
|
2320
1970
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2323,7 +1973,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
|
|
|
2323
1973
|
onClick: handleDelete,
|
|
2324
1974
|
disabled: deleting,
|
|
2325
1975
|
className: "rounded bg-destructive border border-destructive px-2 py-0.5 text-[11px] font-medium text-destructive-foreground hover:bg-destructive/90 shadow-button-primary active:shadow-button-inset disabled:opacity-40 transition-colors",
|
|
2326
|
-
children: deleting ?
|
|
1976
|
+
children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete")
|
|
2327
1977
|
}
|
|
2328
1978
|
)
|
|
2329
1979
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -2332,7 +1982,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
|
|
|
2332
1982
|
{
|
|
2333
1983
|
onClick: () => panel.expandThen(() => onEdit(booking.id)),
|
|
2334
1984
|
className: "p-1.5 rounded-full hover:bg-muted transition-colors",
|
|
2335
|
-
title:
|
|
1985
|
+
title: t2("common.edit"),
|
|
2336
1986
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-4 w-4 text-muted-foreground" })
|
|
2337
1987
|
}
|
|
2338
1988
|
),
|
|
@@ -2341,7 +1991,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
|
|
|
2341
1991
|
{
|
|
2342
1992
|
onClick: () => setConfirmingDelete(true),
|
|
2343
1993
|
className: "p-1.5 rounded-full hover:bg-destructive/10 transition-colors",
|
|
2344
|
-
title:
|
|
1994
|
+
title: t2("common.delete"),
|
|
2345
1995
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4 text-muted-foreground hover:text-destructive" })
|
|
2346
1996
|
}
|
|
2347
1997
|
),
|
|
@@ -2351,7 +2001,7 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
|
|
|
2351
2001
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [
|
|
2352
2002
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1.5 h-3 w-3 rounded shrink-0", style: { backgroundColor: color } }),
|
|
2353
2003
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
|
|
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 ||
|
|
2004
|
+
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 || t2("agenda.appointment.untitled") }),
|
|
2355
2005
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground mt-0.5", children: [
|
|
2356
2006
|
fmtDate(booking.startsAt),
|
|
2357
2007
|
" \xB7 ",
|
|
@@ -2389,22 +2039,22 @@ function AppointmentPopover({ booking, position, onClose, onEdit }) {
|
|
|
2389
2039
|
const hasFinancial = config.modules.financial && booking.orderId;
|
|
2390
2040
|
if (!hasFinancial) {
|
|
2391
2041
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-2 border-t fayz-glass-divider flex items-center justify-between text-sm", children: [
|
|
2392
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children:
|
|
2042
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: t2("agenda.appointment.total") }),
|
|
2393
2043
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: total })
|
|
2394
2044
|
] });
|
|
2395
2045
|
}
|
|
2396
2046
|
const badgeConfig = {
|
|
2397
|
-
none: { icon: lucideReact.HandCoins, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${
|
|
2398
|
-
pending: { icon: lucideReact.Clock, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${
|
|
2399
|
-
partial: { icon: lucideReact.DollarSign, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${
|
|
2400
|
-
paid: { icon: lucideReact.Check, bg: "bg-success/10 hover:bg-success/15", text: "text-success", label: `${total} \xB7 ${
|
|
2401
|
-
overdue: { icon: lucideReact.AlertTriangle, bg: "bg-destructive/10 hover:bg-destructive/15", text: "text-destructive", label: `${total} \xB7 ${
|
|
2402
|
-
cancelled: { icon: lucideReact.X, bg: "bg-muted hover:bg-muted/80", text: "text-muted-foreground", label: `${total} \xB7 ${
|
|
2047
|
+
none: { icon: lucideReact.HandCoins, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t2("agenda.payment.collect")}` },
|
|
2048
|
+
pending: { icon: lucideReact.Clock, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t2("agenda.payment.pending")}` },
|
|
2049
|
+
partial: { icon: lucideReact.DollarSign, bg: "bg-warning/10 hover:bg-warning/15", text: "text-warning", label: `${total} \xB7 ${t2("agenda.payment.partial")}` },
|
|
2050
|
+
paid: { icon: lucideReact.Check, bg: "bg-success/10 hover:bg-success/15", text: "text-success", label: `${total} \xB7 ${t2("agenda.payment.paid")}` },
|
|
2051
|
+
overdue: { icon: lucideReact.AlertTriangle, bg: "bg-destructive/10 hover:bg-destructive/15", text: "text-destructive", label: `${total} \xB7 ${t2("agenda.payment.overdue")}` },
|
|
2052
|
+
cancelled: { icon: lucideReact.X, bg: "bg-muted hover:bg-muted/80", text: "text-muted-foreground", label: `${total} \xB7 ${t2("agenda.payment.cancelled")}` }
|
|
2403
2053
|
};
|
|
2404
2054
|
const cfg = badgeConfig[ps] ?? badgeConfig.none;
|
|
2405
2055
|
const StatusIcon = cfg.icon;
|
|
2406
2056
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-2 border-t fayz-glass-divider flex items-center justify-between", children: [
|
|
2407
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children:
|
|
2057
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: t2("agenda.appointment.total") }),
|
|
2408
2058
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2409
2059
|
"button",
|
|
2410
2060
|
{
|
|
@@ -2451,9 +2101,9 @@ function Tooltip2({ anchor, label }) {
|
|
|
2451
2101
|
);
|
|
2452
2102
|
}
|
|
2453
2103
|
function StatusDot({ color, label, isActive, disabled, onClick }) {
|
|
2454
|
-
const [hover, setHover] =
|
|
2455
|
-
const btnRef =
|
|
2456
|
-
const [rect, setRect] =
|
|
2104
|
+
const [hover, setHover] = React13.useState(false);
|
|
2105
|
+
const btnRef = React13.useRef(null);
|
|
2106
|
+
const [rect, setRect] = React13.useState(null);
|
|
2457
2107
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2458
2108
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2459
2109
|
"button",
|
|
@@ -2476,12 +2126,12 @@ function StatusDot({ color, label, isActive, disabled, onClick }) {
|
|
|
2476
2126
|
] });
|
|
2477
2127
|
}
|
|
2478
2128
|
function EventContextMenu({ booking, position, onClose, onEdit }) {
|
|
2479
|
-
const
|
|
2129
|
+
const t2 = core.useTranslation();
|
|
2480
2130
|
const config = useAgendaConfig();
|
|
2481
2131
|
const updateStatus = useAgendaStore((s) => s.updateBookingStatus);
|
|
2482
2132
|
const deleteBooking = useAgendaStore((s) => s.deleteBooking);
|
|
2483
|
-
const [confirming, setConfirming] =
|
|
2484
|
-
const [deleting, setDeleting] =
|
|
2133
|
+
const [confirming, setConfirming] = React13.useState(false);
|
|
2134
|
+
const [deleting, setDeleting] = React13.useState(false);
|
|
2485
2135
|
async function handleDelete() {
|
|
2486
2136
|
setDeleting(true);
|
|
2487
2137
|
try {
|
|
@@ -2501,19 +2151,19 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
|
|
|
2501
2151
|
children: [
|
|
2502
2152
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-4 w-4 text-muted-foreground" }),
|
|
2503
2153
|
" ",
|
|
2504
|
-
|
|
2154
|
+
t2("agenda.contextMenu.edit")
|
|
2505
2155
|
]
|
|
2506
2156
|
}
|
|
2507
2157
|
),
|
|
2508
2158
|
confirming ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-2", children: [
|
|
2509
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive font-medium", children:
|
|
2159
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-destructive font-medium", children: t2("agenda.appointment.deleteConfirm") }),
|
|
2510
2160
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
2511
2161
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2512
2162
|
"button",
|
|
2513
2163
|
{
|
|
2514
2164
|
onClick: () => setConfirming(false),
|
|
2515
2165
|
className: "rounded px-2 py-0.5 text-[11px] font-medium hover:bg-muted/50 transition-colors",
|
|
2516
|
-
children:
|
|
2166
|
+
children: t2("agenda.appointment.cancel")
|
|
2517
2167
|
}
|
|
2518
2168
|
),
|
|
2519
2169
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2522,7 +2172,7 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
|
|
|
2522
2172
|
onClick: handleDelete,
|
|
2523
2173
|
disabled: deleting,
|
|
2524
2174
|
className: "rounded bg-destructive border border-destructive px-2 py-0.5 text-[11px] font-medium text-destructive-foreground hover:bg-destructive/90 shadow-button-primary active:shadow-button-inset disabled:opacity-40 transition-colors",
|
|
2525
|
-
children: deleting ?
|
|
2175
|
+
children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete")
|
|
2526
2176
|
}
|
|
2527
2177
|
)
|
|
2528
2178
|
] })
|
|
@@ -2534,20 +2184,20 @@ function EventContextMenu({ booking, position, onClose, onEdit }) {
|
|
|
2534
2184
|
children: [
|
|
2535
2185
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" }),
|
|
2536
2186
|
" ",
|
|
2537
|
-
|
|
2187
|
+
t2("agenda.contextMenu.delete")
|
|
2538
2188
|
]
|
|
2539
2189
|
}
|
|
2540
2190
|
)
|
|
2541
2191
|
] }),
|
|
2542
2192
|
config.capabilities.status && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t px-4 py-3", children: [
|
|
2543
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children:
|
|
2193
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t2("agenda.contextMenu.changeStatus") }),
|
|
2544
2194
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: config.statuses.map((s) => {
|
|
2545
2195
|
const available = isStatusAvailable(s, booking.startsAt);
|
|
2546
2196
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2547
2197
|
StatusDot,
|
|
2548
2198
|
{
|
|
2549
2199
|
color: s.color,
|
|
2550
|
-
label: available ? s.label : `${s.label} ${
|
|
2200
|
+
label: available ? s.label : `${s.label} ${t2("agenda.contextMenu.notAvailable")}`,
|
|
2551
2201
|
isActive: booking.status === s.value,
|
|
2552
2202
|
disabled: !available,
|
|
2553
2203
|
onClick: () => {
|
|
@@ -2573,9 +2223,9 @@ function timeLabel(iso) {
|
|
|
2573
2223
|
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
2574
2224
|
}
|
|
2575
2225
|
function AgendaMobileList({ bookings, statusColors, currencyCode, onSelect }) {
|
|
2576
|
-
const
|
|
2226
|
+
const t2 = core.useTranslation();
|
|
2577
2227
|
const locale = core.getCurrentLocale() === "pt-BR" ? "pt-BR" : "en";
|
|
2578
|
-
const groups =
|
|
2228
|
+
const groups = React13.useMemo(() => {
|
|
2579
2229
|
const todayStart = startOfDay(/* @__PURE__ */ new Date()).getTime();
|
|
2580
2230
|
const sorted = bookings.filter((b) => !!b.startsAt).slice().sort((a, b) => a.startsAt < b.startsAt ? -1 : a.startsAt > b.startsAt ? 1 : 0);
|
|
2581
2231
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -2592,12 +2242,12 @@ function AgendaMobileList({ bookings, statusColors, currencyCode, onSelect }) {
|
|
|
2592
2242
|
function dayLabel(date) {
|
|
2593
2243
|
const today = startOfDay(/* @__PURE__ */ new Date());
|
|
2594
2244
|
const diff = Math.round((startOfDay(date).getTime() - today.getTime()) / 864e5);
|
|
2595
|
-
if (diff === 0) return
|
|
2596
|
-
if (diff === 1) return
|
|
2245
|
+
if (diff === 0) return t2("agenda.list.today");
|
|
2246
|
+
if (diff === 1) return t2("agenda.list.tomorrow");
|
|
2597
2247
|
return date.toLocaleDateString(locale, { weekday: "long", day: "numeric", month: "long" });
|
|
2598
2248
|
}
|
|
2599
2249
|
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:
|
|
2250
|
+
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: t2("agenda.list.empty") }) });
|
|
2601
2251
|
}
|
|
2602
2252
|
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
2253
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "px-1 pb-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: dayLabel(g.date) }),
|
|
@@ -2653,8 +2303,8 @@ function generateAvailabilityEvents(schedules, professionals, dateRange, busines
|
|
|
2653
2303
|
list.push(s);
|
|
2654
2304
|
byAssignee.set(aid, list);
|
|
2655
2305
|
}
|
|
2656
|
-
function timeToMinutes(
|
|
2657
|
-
const [h, m] =
|
|
2306
|
+
function timeToMinutes(t2) {
|
|
2307
|
+
const [h, m] = t2.split(":").map(Number);
|
|
2658
2308
|
return h * 60 + m;
|
|
2659
2309
|
}
|
|
2660
2310
|
function minutesToTime(m) {
|
|
@@ -2766,8 +2416,8 @@ function viewForResources(view) {
|
|
|
2766
2416
|
return view;
|
|
2767
2417
|
}
|
|
2768
2418
|
function useIsMobile() {
|
|
2769
|
-
const [isMobile, setIsMobile] =
|
|
2770
|
-
|
|
2419
|
+
const [isMobile, setIsMobile] = React13.useState(() => typeof window !== "undefined" && window.matchMedia("(max-width: 767px)").matches);
|
|
2420
|
+
React13.useEffect(() => {
|
|
2771
2421
|
if (typeof window === "undefined") return;
|
|
2772
2422
|
const mq = window.matchMedia("(max-width: 767px)");
|
|
2773
2423
|
const onChange = () => setIsMobile(mq.matches);
|
|
@@ -2779,10 +2429,10 @@ function useIsMobile() {
|
|
|
2779
2429
|
function CalendarView() {
|
|
2780
2430
|
injectCalendarStyles();
|
|
2781
2431
|
const config = useAgendaConfig();
|
|
2782
|
-
const
|
|
2432
|
+
const t2 = core.useTranslation();
|
|
2783
2433
|
const currentLocale = core.getCurrentLocale();
|
|
2784
2434
|
const fcLocale = currentLocale === "pt-BR" ? ptBrLocale__default.default : void 0;
|
|
2785
|
-
const calendarRef =
|
|
2435
|
+
const calendarRef = React13.useRef(null);
|
|
2786
2436
|
const prefs = saas.usePluginPrefs("agenda", { calendarView: config.defaultCalendarView ?? "resourceTimeGridWeek" });
|
|
2787
2437
|
const agendaSettings = useAgendaSettings();
|
|
2788
2438
|
const effectiveBusinessHours = {
|
|
@@ -2811,8 +2461,8 @@ function CalendarView() {
|
|
|
2811
2461
|
const isMobile = useIsMobile();
|
|
2812
2462
|
const resourceCapable = professionals.length > 0;
|
|
2813
2463
|
const viewOptions = resourceCapable ? RESOURCE_VIEW_OPTIONS : PLAIN_VIEW_OPTIONS;
|
|
2814
|
-
const [mobileView, setMobileView] =
|
|
2815
|
-
const mobileViewOptions =
|
|
2464
|
+
const [mobileView, setMobileView] = React13.useState(MOBILE_LIST_VIEW);
|
|
2465
|
+
const mobileViewOptions = React13.useMemo(
|
|
2816
2466
|
() => [
|
|
2817
2467
|
...viewOptions.filter((o) => o.key !== "listMonth"),
|
|
2818
2468
|
{ key: MOBILE_LIST_VIEW, labelKey: "agenda.calendar.viewList" }
|
|
@@ -2820,28 +2470,28 @@ function CalendarView() {
|
|
|
2820
2470
|
[viewOptions]
|
|
2821
2471
|
);
|
|
2822
2472
|
const mobileGridActive = isMobile && mobileView !== MOBILE_LIST_VIEW;
|
|
2823
|
-
const [selectedBooking, setSelectedBooking] =
|
|
2824
|
-
const [popoverAnchor, setPopoverAnchor] =
|
|
2825
|
-
const [popoverVisible, _setPopoverVisible] =
|
|
2826
|
-
const popoverVisibleRef =
|
|
2827
|
-
const setPopoverVisible =
|
|
2473
|
+
const [selectedBooking, setSelectedBooking] = React13.useState(null);
|
|
2474
|
+
const [popoverAnchor, setPopoverAnchor] = React13.useState(null);
|
|
2475
|
+
const [popoverVisible, _setPopoverVisible] = React13.useState(false);
|
|
2476
|
+
const popoverVisibleRef = React13.useRef(false);
|
|
2477
|
+
const setPopoverVisible = React13.useCallback((v) => {
|
|
2828
2478
|
popoverVisibleRef.current = v;
|
|
2829
2479
|
_setPopoverVisible(v);
|
|
2830
2480
|
}, []);
|
|
2831
|
-
const [contextMenu, setContextMenu] =
|
|
2832
|
-
const [contextMenuVisible, _setContextMenuVisible] =
|
|
2833
|
-
const contextMenuVisibleRef =
|
|
2834
|
-
const setContextMenuVisible =
|
|
2481
|
+
const [contextMenu, setContextMenu] = React13.useState(null);
|
|
2482
|
+
const [contextMenuVisible, _setContextMenuVisible] = React13.useState(false);
|
|
2483
|
+
const contextMenuVisibleRef = React13.useRef(false);
|
|
2484
|
+
const setContextMenuVisible = React13.useCallback((v) => {
|
|
2835
2485
|
contextMenuVisibleRef.current = v;
|
|
2836
2486
|
_setContextMenuVisible(v);
|
|
2837
2487
|
}, []);
|
|
2838
|
-
const [scheduleConfirm, setScheduleConfirm] =
|
|
2839
|
-
const [calendarTitle, setCalendarTitle] =
|
|
2840
|
-
const [selectedDate, setSelectedDate] =
|
|
2841
|
-
const [profSectionOpen, setProfSectionOpen] =
|
|
2842
|
-
const [statusSectionOpen, setStatusSectionOpen] =
|
|
2843
|
-
const [visibleRange, setVisibleRange] =
|
|
2844
|
-
const openModal =
|
|
2488
|
+
const [scheduleConfirm, setScheduleConfirm] = React13.useState(null);
|
|
2489
|
+
const [calendarTitle, setCalendarTitle] = React13.useState("");
|
|
2490
|
+
const [selectedDate, setSelectedDate] = React13.useState(/* @__PURE__ */ new Date());
|
|
2491
|
+
const [profSectionOpen, setProfSectionOpen] = React13.useState(true);
|
|
2492
|
+
const [statusSectionOpen, setStatusSectionOpen] = React13.useState(false);
|
|
2493
|
+
const [visibleRange, setVisibleRange] = React13.useState(null);
|
|
2494
|
+
const openModal = React13.useCallback((...args) => {
|
|
2845
2495
|
setPopoverVisible(false);
|
|
2846
2496
|
setSelectedBooking(null);
|
|
2847
2497
|
setPopoverAnchor(null);
|
|
@@ -2849,17 +2499,17 @@ function CalendarView() {
|
|
|
2849
2499
|
setContextMenu(null);
|
|
2850
2500
|
_openModal(...args);
|
|
2851
2501
|
}, [_openModal]);
|
|
2852
|
-
const statusColors =
|
|
2502
|
+
const statusColors = React13.useMemo(() => {
|
|
2853
2503
|
const map = {};
|
|
2854
2504
|
for (const s of config.statuses) map[s.value] = s.color;
|
|
2855
2505
|
return map;
|
|
2856
2506
|
}, [config.statuses]);
|
|
2857
|
-
|
|
2507
|
+
React13.useEffect(() => {
|
|
2858
2508
|
fetchProfessionals();
|
|
2859
2509
|
fetchSchedules();
|
|
2860
2510
|
if (config.modules.locationSelection) fetchLocations();
|
|
2861
2511
|
}, []);
|
|
2862
|
-
|
|
2512
|
+
React13.useEffect(() => {
|
|
2863
2513
|
const api = calendarRef.current?.getApi();
|
|
2864
2514
|
if (!api) return;
|
|
2865
2515
|
if (!resourceCapable && currentView.startsWith("resource")) {
|
|
@@ -2868,7 +2518,7 @@ function CalendarView() {
|
|
|
2868
2518
|
api.changeView(viewForResources(currentView));
|
|
2869
2519
|
}
|
|
2870
2520
|
}, [resourceCapable, currentView]);
|
|
2871
|
-
|
|
2521
|
+
React13.useEffect(() => {
|
|
2872
2522
|
if (!isMobile) return;
|
|
2873
2523
|
const start = /* @__PURE__ */ new Date();
|
|
2874
2524
|
start.setHours(0, 0, 0, 0);
|
|
@@ -2877,7 +2527,19 @@ function CalendarView() {
|
|
|
2877
2527
|
end.setDate(end.getDate() + 91);
|
|
2878
2528
|
fetchBookings({ start: start.toISOString(), end: end.toISOString() });
|
|
2879
2529
|
}, [isMobile, fetchBookings]);
|
|
2880
|
-
|
|
2530
|
+
core.useDataChanged({}, () => {
|
|
2531
|
+
if (visibleRange) {
|
|
2532
|
+
fetchBookings(visibleRange);
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2535
|
+
const start = /* @__PURE__ */ new Date();
|
|
2536
|
+
start.setHours(0, 0, 0, 0);
|
|
2537
|
+
start.setDate(start.getDate() - 1);
|
|
2538
|
+
const end = new Date(start);
|
|
2539
|
+
end.setDate(end.getDate() + 91);
|
|
2540
|
+
fetchBookings({ start: start.toISOString(), end: end.toISOString() });
|
|
2541
|
+
}, [visibleRange, fetchBookings]);
|
|
2542
|
+
const resources = React13.useMemo(() => {
|
|
2881
2543
|
const filtered = selectedProfIds.length > 0 ? professionals.filter((p) => selectedProfIds.includes(p.id)) : professionals;
|
|
2882
2544
|
return filtered.map((p) => ({
|
|
2883
2545
|
id: p.id,
|
|
@@ -2885,31 +2547,31 @@ function CalendarView() {
|
|
|
2885
2547
|
extendedProps: { avatarUrl: p.avatarUrl, locationName: p.locationName }
|
|
2886
2548
|
}));
|
|
2887
2549
|
}, [professionals, selectedProfIds]);
|
|
2888
|
-
const bookingEvents =
|
|
2889
|
-
const allLocations =
|
|
2550
|
+
const bookingEvents = React13.useMemo(() => bookings.map((b) => bookingToEvent(b, statusColors)), [bookings, statusColors]);
|
|
2551
|
+
const allLocations = React13.useMemo(() => {
|
|
2890
2552
|
const map = /* @__PURE__ */ new Map();
|
|
2891
2553
|
for (const loc of config.locations) map.set(loc.id, loc.name);
|
|
2892
2554
|
for (const loc of storeLocations) map.set(loc.id, loc.name);
|
|
2893
2555
|
return map;
|
|
2894
2556
|
}, [config.locations, storeLocations]);
|
|
2895
|
-
const locationMap =
|
|
2557
|
+
const locationMap = React13.useMemo(() => {
|
|
2896
2558
|
if (!config.modules.locationSelection || allLocations.size === 0) return void 0;
|
|
2897
2559
|
return allLocations;
|
|
2898
2560
|
}, [config.modules.locationSelection, allLocations]);
|
|
2899
|
-
const availabilityEvents =
|
|
2561
|
+
const availabilityEvents = React13.useMemo(
|
|
2900
2562
|
() => generateAvailabilityEvents(schedules, professionals, visibleRange, effectiveBusinessHours, locationMap),
|
|
2901
2563
|
[schedules, professionals, visibleRange, effectiveBusinessHours, locationMap]
|
|
2902
2564
|
);
|
|
2903
|
-
const events =
|
|
2565
|
+
const events = React13.useMemo(() => [...bookingEvents, ...availabilityEvents], [bookingEvents, availabilityEvents]);
|
|
2904
2566
|
const storedView = prefs.get("calendarView");
|
|
2905
2567
|
const effectiveInitialView = resourceCapable ? viewForResources(storedView) : viewForNoResources(storedView);
|
|
2906
|
-
const handleDatesSet =
|
|
2568
|
+
const handleDatesSet = React13.useCallback((arg) => {
|
|
2907
2569
|
setCalendarTitle(arg.view.title);
|
|
2908
2570
|
setView(arg.view.type);
|
|
2909
2571
|
setVisibleRange({ start: arg.startStr, end: arg.endStr });
|
|
2910
2572
|
fetchBookings({ start: arg.startStr, end: arg.endStr });
|
|
2911
2573
|
}, [fetchBookings, setView]);
|
|
2912
|
-
const handleEventDidMount =
|
|
2574
|
+
const handleEventDidMount = React13.useCallback((arg) => {
|
|
2913
2575
|
const booking = arg.event.extendedProps?.booking;
|
|
2914
2576
|
if (booking) arg.el.setAttribute("data-booking-id", booking.id);
|
|
2915
2577
|
arg.el.addEventListener("dblclick", (e) => {
|
|
@@ -2936,9 +2598,9 @@ function CalendarView() {
|
|
|
2936
2598
|
setPopoverAnchor(null);
|
|
2937
2599
|
});
|
|
2938
2600
|
}, []);
|
|
2939
|
-
const skipClickRef =
|
|
2940
|
-
const hoverTimerRef =
|
|
2941
|
-
const handleEventMouseEnter =
|
|
2601
|
+
const skipClickRef = React13.useRef(false);
|
|
2602
|
+
const hoverTimerRef = React13.useRef(null);
|
|
2603
|
+
const handleEventMouseEnter = React13.useCallback((arg) => {
|
|
2942
2604
|
if (arg.event.display === "background") return;
|
|
2943
2605
|
if (arg.event.extendedProps?.isLocationLabel) return;
|
|
2944
2606
|
const booking = arg.event.extendedProps.booking;
|
|
@@ -2957,13 +2619,13 @@ function CalendarView() {
|
|
|
2957
2619
|
setPopoverVisible(true);
|
|
2958
2620
|
}, 3e3);
|
|
2959
2621
|
}, []);
|
|
2960
|
-
const handleEventMouseLeave =
|
|
2622
|
+
const handleEventMouseLeave = React13.useCallback(() => {
|
|
2961
2623
|
if (hoverTimerRef.current) {
|
|
2962
2624
|
clearTimeout(hoverTimerRef.current);
|
|
2963
2625
|
hoverTimerRef.current = null;
|
|
2964
2626
|
}
|
|
2965
2627
|
}, []);
|
|
2966
|
-
const handleEventClick =
|
|
2628
|
+
const handleEventClick = React13.useCallback((arg) => {
|
|
2967
2629
|
if (skipClickRef.current) {
|
|
2968
2630
|
skipClickRef.current = false;
|
|
2969
2631
|
return;
|
|
@@ -2986,7 +2648,7 @@ function CalendarView() {
|
|
|
2986
2648
|
setPopoverAnchor({ x, y });
|
|
2987
2649
|
setPopoverVisible(true);
|
|
2988
2650
|
}, []);
|
|
2989
|
-
const findScheduleForSlot =
|
|
2651
|
+
const findScheduleForSlot = React13.useCallback((professionalId, dateTimeStr) => {
|
|
2990
2652
|
if (!professionalId) return null;
|
|
2991
2653
|
const dt = new Date(dateTimeStr);
|
|
2992
2654
|
const dateStr = dt.toISOString().slice(0, 10);
|
|
@@ -3003,7 +2665,7 @@ function CalendarView() {
|
|
|
3003
2665
|
}
|
|
3004
2666
|
return null;
|
|
3005
2667
|
}, [schedules]);
|
|
3006
|
-
const handleSelect =
|
|
2668
|
+
const handleSelect = React13.useCallback((arg) => {
|
|
3007
2669
|
calendarRef.current?.getApi().unselect();
|
|
3008
2670
|
if (popoverVisibleRef.current || contextMenuVisibleRef.current) {
|
|
3009
2671
|
setPopoverVisible(false);
|
|
@@ -3025,7 +2687,7 @@ function CalendarView() {
|
|
|
3025
2687
|
}
|
|
3026
2688
|
proceed();
|
|
3027
2689
|
}, [openModal, findScheduleForSlot, config.modules.workingHours]);
|
|
3028
|
-
const handleEventDrop =
|
|
2690
|
+
const handleEventDrop = React13.useCallback(async (arg) => {
|
|
3029
2691
|
const booking = arg.event.extendedProps.booking;
|
|
3030
2692
|
try {
|
|
3031
2693
|
await rescheduleBooking(booking.id, arg.event.startStr, arg.event.endStr, arg.newResource?.id ?? void 0);
|
|
@@ -3033,19 +2695,19 @@ function CalendarView() {
|
|
|
3033
2695
|
arg.revert();
|
|
3034
2696
|
}
|
|
3035
2697
|
}, [rescheduleBooking]);
|
|
3036
|
-
const handleViewChange =
|
|
2698
|
+
const handleViewChange = React13.useCallback((v) => {
|
|
3037
2699
|
calendarRef.current?.getApi().changeView(v);
|
|
3038
2700
|
prefs.set("calendarView", v);
|
|
3039
2701
|
}, [prefs]);
|
|
3040
|
-
const handleMobileViewChange =
|
|
2702
|
+
const handleMobileViewChange = React13.useCallback((v) => {
|
|
3041
2703
|
setMobileView(v);
|
|
3042
2704
|
if (v === MOBILE_LIST_VIEW) return;
|
|
3043
2705
|
setView(v);
|
|
3044
2706
|
calendarRef.current?.getApi()?.changeView(v);
|
|
3045
2707
|
}, [setView]);
|
|
3046
|
-
const handlePrev =
|
|
3047
|
-
const handleNext =
|
|
3048
|
-
const handleToday =
|
|
2708
|
+
const handlePrev = React13.useCallback(() => calendarRef.current?.getApi().prev(), []);
|
|
2709
|
+
const handleNext = React13.useCallback(() => calendarRef.current?.getApi().next(), []);
|
|
2710
|
+
const handleToday = React13.useCallback(() => {
|
|
3049
2711
|
calendarRef.current?.getApi().today();
|
|
3050
2712
|
setSelectedDate(/* @__PURE__ */ new Date());
|
|
3051
2713
|
}, []);
|
|
@@ -3061,9 +2723,9 @@ function CalendarView() {
|
|
|
3061
2723
|
const next = selectedStatuses.includes(value) ? selectedStatuses.filter((s) => s !== value) : [...selectedStatuses, value];
|
|
3062
2724
|
setFilters({ statuses: next });
|
|
3063
2725
|
}
|
|
3064
|
-
const wrapperRef =
|
|
3065
|
-
const [dayHeaderCompact, setDayHeaderCompact] =
|
|
3066
|
-
|
|
2726
|
+
const wrapperRef = React13.useRef(null);
|
|
2727
|
+
const [dayHeaderCompact, setDayHeaderCompact] = React13.useState(false);
|
|
2728
|
+
React13.useEffect(() => {
|
|
3067
2729
|
const container = wrapperRef.current;
|
|
3068
2730
|
if (!container) return;
|
|
3069
2731
|
let frame = 0;
|
|
@@ -3106,7 +2768,7 @@ function CalendarView() {
|
|
|
3106
2768
|
ro.disconnect();
|
|
3107
2769
|
};
|
|
3108
2770
|
}, [resources, currentView, visibleRange]);
|
|
3109
|
-
const applyResourceStriping =
|
|
2771
|
+
const applyResourceStriping = React13.useCallback(() => {
|
|
3110
2772
|
const container = wrapperRef.current;
|
|
3111
2773
|
if (!container) return;
|
|
3112
2774
|
const resourceIds = resources.map((r) => r.id);
|
|
@@ -3117,16 +2779,16 @@ function CalendarView() {
|
|
|
3117
2779
|
el.classList.toggle("fc-resource-alt", idx % 2 === 1);
|
|
3118
2780
|
});
|
|
3119
2781
|
}, [resources]);
|
|
3120
|
-
|
|
2782
|
+
React13.useEffect(() => {
|
|
3121
2783
|
applyResourceStriping();
|
|
3122
2784
|
const t1 = setTimeout(applyResourceStriping, 100);
|
|
3123
|
-
const
|
|
2785
|
+
const t22 = setTimeout(applyResourceStriping, 300);
|
|
3124
2786
|
return () => {
|
|
3125
2787
|
clearTimeout(t1);
|
|
3126
|
-
clearTimeout(
|
|
2788
|
+
clearTimeout(t22);
|
|
3127
2789
|
};
|
|
3128
2790
|
}, [applyResourceStriping, currentView, visibleRange, bookings]);
|
|
3129
|
-
|
|
2791
|
+
React13.useEffect(() => {
|
|
3130
2792
|
function handleOpenBooking(e) {
|
|
3131
2793
|
const detail = e.detail ?? {};
|
|
3132
2794
|
const externalSource = detail.source ? { type: String(detail.source), id: detail.waitlistId, waitlistId: detail.waitlistId } : void 0;
|
|
@@ -3147,7 +2809,7 @@ function CalendarView() {
|
|
|
3147
2809
|
window.addEventListener("agenda:open-booking", handleOpenBooking);
|
|
3148
2810
|
return () => window.removeEventListener("agenda:open-booking", handleOpenBooking);
|
|
3149
2811
|
}, [openModal]);
|
|
3150
|
-
const dayHeaderContent =
|
|
2812
|
+
const dayHeaderContent = React13.useCallback((arg) => {
|
|
3151
2813
|
const date = arg.date;
|
|
3152
2814
|
const dateLocale = currentLocale === "pt-BR" ? "pt-BR" : "en";
|
|
3153
2815
|
const wrapStyle = { display: "flex", flexDirection: "column", alignItems: "center", lineHeight: 1.15 };
|
|
@@ -3165,7 +2827,7 @@ function CalendarView() {
|
|
|
3165
2827
|
/* @__PURE__ */ jsxRuntime.jsx("span", { style: dateStyle, children: `${date.getMonth() + 1}/${date.getDate()}` })
|
|
3166
2828
|
] });
|
|
3167
2829
|
}, [dayHeaderCompact, currentLocale]);
|
|
3168
|
-
const resourceLabelContent =
|
|
2830
|
+
const resourceLabelContent = React13.useCallback((arg) => {
|
|
3169
2831
|
const { id, title, extendedProps } = arg.resource;
|
|
3170
2832
|
const initials = title.split(" ").map((n) => n[0]).join("").slice(0, 2);
|
|
3171
2833
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-2 py-1.5", children: [
|
|
@@ -3185,7 +2847,7 @@ function CalendarView() {
|
|
|
3185
2847
|
] })
|
|
3186
2848
|
] });
|
|
3187
2849
|
}, []);
|
|
3188
|
-
const eventContent =
|
|
2850
|
+
const eventContent = React13.useCallback((arg) => {
|
|
3189
2851
|
if (arg.event.extendedProps?.isLocationLabel) {
|
|
3190
2852
|
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 }) });
|
|
3191
2853
|
}
|
|
@@ -3211,7 +2873,7 @@ function CalendarView() {
|
|
|
3211
2873
|
className: "flex items-center gap-2 rounded-button bg-primary text-primary-foreground border border-primary pl-3 pr-4 py-1.5 text-sm font-semibold hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-all mb-6 w-fit",
|
|
3212
2874
|
children: [
|
|
3213
2875
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
|
|
3214
|
-
|
|
2876
|
+
t2("agenda.sidebar.create")
|
|
3215
2877
|
]
|
|
3216
2878
|
}
|
|
3217
2879
|
),
|
|
@@ -3286,7 +2948,7 @@ function CalendarView() {
|
|
|
3286
2948
|
{
|
|
3287
2949
|
onClick: handleToday,
|
|
3288
2950
|
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:
|
|
2951
|
+
children: t2("agenda.toolbar.today")
|
|
3290
2952
|
}
|
|
3291
2953
|
),
|
|
3292
2954
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
|
|
@@ -3294,7 +2956,7 @@ function CalendarView() {
|
|
|
3294
2956
|
/* @__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
2957
|
] })
|
|
3296
2958
|
] }),
|
|
3297
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base sm:text-lg font-normal truncate", children: isMobile && !mobileGridActive ?
|
|
2959
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-base sm:text-lg font-normal truncate", children: isMobile && !mobileGridActive ? t2("agenda.calendar.viewAgenda") : calendarTitle })
|
|
3298
2960
|
] }),
|
|
3299
2961
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
3300
2962
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:flex items-center gap-1", children: viewOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3302,7 +2964,7 @@ function CalendarView() {
|
|
|
3302
2964
|
{
|
|
3303
2965
|
onClick: () => handleViewChange(opt.key),
|
|
3304
2966
|
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:
|
|
2967
|
+
children: t2(opt.labelKey)
|
|
3306
2968
|
},
|
|
3307
2969
|
opt.key
|
|
3308
2970
|
)) }),
|
|
@@ -3324,7 +2986,7 @@ function CalendarView() {
|
|
|
3324
2986
|
{
|
|
3325
2987
|
onClick: () => handleMobileViewChange(opt.key),
|
|
3326
2988
|
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:
|
|
2989
|
+
children: t2(opt.labelKey)
|
|
3328
2990
|
},
|
|
3329
2991
|
opt.key
|
|
3330
2992
|
)) })
|
|
@@ -3415,9 +3077,9 @@ function CalendarView() {
|
|
|
3415
3077
|
{
|
|
3416
3078
|
open: !!scheduleConfirm,
|
|
3417
3079
|
variant: "warning",
|
|
3418
|
-
title:
|
|
3419
|
-
confirmLabel:
|
|
3420
|
-
cancelLabel:
|
|
3080
|
+
title: t2("agenda.calendar.noScheduleConfirm"),
|
|
3081
|
+
confirmLabel: t2("common.confirm"),
|
|
3082
|
+
cancelLabel: t2("common.cancel"),
|
|
3421
3083
|
onConfirm: () => {
|
|
3422
3084
|
const cb = scheduleConfirm?.onConfirm;
|
|
3423
3085
|
setScheduleConfirm(null);
|
|
@@ -3464,17 +3126,26 @@ function CalendarView() {
|
|
|
3464
3126
|
)
|
|
3465
3127
|
] });
|
|
3466
3128
|
}
|
|
3129
|
+
|
|
3130
|
+
// src/lib/tenant.ts
|
|
3131
|
+
var t;
|
|
3132
|
+
function setAgendaTenantId(id) {
|
|
3133
|
+
t = id;
|
|
3134
|
+
}
|
|
3135
|
+
function getAgendaTenantId() {
|
|
3136
|
+
return t;
|
|
3137
|
+
}
|
|
3467
3138
|
function AgendaPage({ config, provider: provider2, store }) {
|
|
3468
3139
|
const runtime = core.usePluginRuntimeOptional();
|
|
3469
3140
|
const tenantId = runtime?.context.tenant?.id;
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
return () =>
|
|
3141
|
+
React13__default.default.useEffect(() => {
|
|
3142
|
+
setAgendaTenantId(tenantId);
|
|
3143
|
+
return () => setAgendaTenantId(void 0);
|
|
3473
3144
|
}, [tenantId]);
|
|
3474
3145
|
return /* @__PURE__ */ jsxRuntime.jsx(AgendaContextProvider, { config, provider: provider2, store, children: /* @__PURE__ */ jsxRuntime.jsx(CalendarView, {}) });
|
|
3475
3146
|
}
|
|
3476
3147
|
function getTenantId() {
|
|
3477
|
-
return
|
|
3148
|
+
return getAgendaTenantId() ?? core.getActiveTenantId();
|
|
3478
3149
|
}
|
|
3479
3150
|
function snakeToCamel(obj) {
|
|
3480
3151
|
const result = {};
|
|
@@ -3582,8 +3253,10 @@ async function hydrateCanonicalBookings(core, bookings) {
|
|
|
3582
3253
|
locationId: booking.location_id,
|
|
3583
3254
|
metadata: booking.metadata ?? {},
|
|
3584
3255
|
clientId: booking.party_id,
|
|
3585
|
-
// Simple events (no party) fall back to the title stored in metadata
|
|
3586
|
-
|
|
3256
|
+
// Simple events (no party) fall back to the title stored in metadata;
|
|
3257
|
+
// partyless blocks/tasks (e.g. imported Google events) get their notes or
|
|
3258
|
+
// a kind label so the calendar never renders an unnamed chip.
|
|
3259
|
+
clientName: client?.name ?? booking.metadata?.title ?? (booking.kind === "appointment" ? null : booking.notes || (booking.kind === "task" ? "Tarefa" : "Bloqueio")),
|
|
3587
3260
|
clientPhone: client?.phone ?? null,
|
|
3588
3261
|
clientEmail: client?.email ?? null,
|
|
3589
3262
|
clientAvatarUrl: client?.avatar_url ?? null,
|
|
@@ -3623,7 +3296,8 @@ async function getCanonicalBookingById(core, id) {
|
|
|
3623
3296
|
const [booking] = await hydrateCanonicalBookings(core, [data]);
|
|
3624
3297
|
return booking ?? null;
|
|
3625
3298
|
}
|
|
3626
|
-
function createSupabaseAgendaProvider() {
|
|
3299
|
+
function createSupabaseAgendaProvider(providerOptions) {
|
|
3300
|
+
const professionalKind = providerOptions?.professionalKind ?? "staff";
|
|
3627
3301
|
return {
|
|
3628
3302
|
// v_appointments is in public schema (PostgREST default)
|
|
3629
3303
|
async getBookings(query) {
|
|
@@ -3644,7 +3318,26 @@ function createSupabaseAgendaProvider() {
|
|
|
3644
3318
|
const { core, pub } = getClients();
|
|
3645
3319
|
const { data, error } = await pub.from("v_appointments").select("*").eq("id", id).maybeSingle();
|
|
3646
3320
|
if (error) throw error;
|
|
3647
|
-
if (data)
|
|
3321
|
+
if (data) {
|
|
3322
|
+
const booking = mapBooking(data);
|
|
3323
|
+
if (booking.orderId) {
|
|
3324
|
+
const { data: items } = await core.from("order_items").select("id, service_id, name, duration_minutes, unit_price, assignee_id, sort_order").eq("order_id", booking.orderId).order("sort_order", { ascending: true });
|
|
3325
|
+
if (items && items.length) {
|
|
3326
|
+
const services = items.map((item) => ({
|
|
3327
|
+
id: item.id,
|
|
3328
|
+
serviceId: item.service_id,
|
|
3329
|
+
name: item.name,
|
|
3330
|
+
durationMinutes: item.duration_minutes,
|
|
3331
|
+
price: item.unit_price,
|
|
3332
|
+
assigneeId: item.assignee_id
|
|
3333
|
+
}));
|
|
3334
|
+
booking.services = services;
|
|
3335
|
+
const itemDuration = services.reduce((sum, s) => sum + (s.durationMinutes ?? 0), 0);
|
|
3336
|
+
if (itemDuration) booking.totalDurationMinutes = itemDuration;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return booking;
|
|
3340
|
+
}
|
|
3648
3341
|
return getCanonicalBookingById(core, id);
|
|
3649
3342
|
},
|
|
3650
3343
|
async createBooking(input) {
|
|
@@ -3667,7 +3360,7 @@ function createSupabaseAgendaProvider() {
|
|
|
3667
3360
|
kind: input.kind ?? "appointment",
|
|
3668
3361
|
status: "scheduled",
|
|
3669
3362
|
party_id: input.clientId || null,
|
|
3670
|
-
assignee_id: input.professionalId,
|
|
3363
|
+
assignee_id: input.professionalId || null,
|
|
3671
3364
|
location_id: input.locationId ?? null,
|
|
3672
3365
|
subtotal: totalPrice,
|
|
3673
3366
|
total: totalPrice,
|
|
@@ -3684,7 +3377,7 @@ function createSupabaseAgendaProvider() {
|
|
|
3684
3377
|
tenant_id: tenantId,
|
|
3685
3378
|
kind: input.kind ?? "appointment",
|
|
3686
3379
|
party_id: input.clientId || null,
|
|
3687
|
-
assignee_id: input.professionalId,
|
|
3380
|
+
assignee_id: input.professionalId || null,
|
|
3688
3381
|
location_id: input.locationId ?? null,
|
|
3689
3382
|
order_id: order.id,
|
|
3690
3383
|
starts_at: input.startsAt,
|
|
@@ -3709,7 +3402,7 @@ function createSupabaseAgendaProvider() {
|
|
|
3709
3402
|
total: s.price,
|
|
3710
3403
|
sort_order: i,
|
|
3711
3404
|
duration_minutes: s.durationMinutes,
|
|
3712
|
-
assignee_id: s.assigneeId
|
|
3405
|
+
assignee_id: s.assigneeId || input.professionalId || null
|
|
3713
3406
|
}));
|
|
3714
3407
|
const { error: oiErr } = await core.from("order_items").insert(orderItems);
|
|
3715
3408
|
if (oiErr) throw oiErr;
|
|
@@ -3789,9 +3482,12 @@ function createSupabaseAgendaProvider() {
|
|
|
3789
3482
|
const { data: booking, error: bookingErr } = await core.from("appointments").select("order_id").eq("id", id).single();
|
|
3790
3483
|
if (bookingErr) throw bookingErr;
|
|
3791
3484
|
const orderId = booking?.order_id;
|
|
3792
|
-
|
|
3793
|
-
if (
|
|
3794
|
-
|
|
3485
|
+
let movementCount = 0;
|
|
3486
|
+
if (orderId) {
|
|
3487
|
+
const { count, error: movementErr } = await pub.from("plg_financial_movements").select("id", { count: "exact", head: true }).eq("invoice_id", orderId);
|
|
3488
|
+
if (!movementErr) movementCount = count ?? 0;
|
|
3489
|
+
}
|
|
3490
|
+
if (orderId && movementCount > 0) {
|
|
3795
3491
|
await pub.from("plg_financial_movements").update({ status: "cancelled" }).eq("invoice_id", orderId);
|
|
3796
3492
|
await core.from("orders").update({ status: "cancelled" }).eq("id", orderId);
|
|
3797
3493
|
const { error } = await core.from("appointments").update({ status: "cancelled" }).eq("id", id);
|
|
@@ -3868,7 +3564,7 @@ function createSupabaseAgendaProvider() {
|
|
|
3868
3564
|
},
|
|
3869
3565
|
async getProfessionals() {
|
|
3870
3566
|
const { core } = getClients();
|
|
3871
|
-
const { data, error } = await core.from("people").select("id, name, avatar_url, is_active").eq("kind",
|
|
3567
|
+
const { data, error } = await core.from("people").select("id, name, avatar_url, is_active").eq("kind", professionalKind).eq("is_active", true).order("name");
|
|
3872
3568
|
if (error) throw error;
|
|
3873
3569
|
return (data ?? []).map((p) => ({
|
|
3874
3570
|
id: p.id,
|
|
@@ -4150,40 +3846,49 @@ var HOUR_OPTIONS = Array.from({ length: 18 }, (_, i) => {
|
|
|
4150
3846
|
const h = (5 + i).toString().padStart(2, "0");
|
|
4151
3847
|
return { value: `${h}:00`, label: `${h}:00` };
|
|
4152
3848
|
});
|
|
3849
|
+
var AGENDA_TOGGLE_DEFAULTS = {
|
|
3850
|
+
conflictDetection: true,
|
|
3851
|
+
dragDrop: true,
|
|
3852
|
+
autoCreateOrder: true,
|
|
3853
|
+
enableLocation: false,
|
|
3854
|
+
enableWorkingHours: true,
|
|
3855
|
+
blockOutside: false,
|
|
3856
|
+
enableConfirmations: true,
|
|
3857
|
+
autoConfirm: false,
|
|
3858
|
+
reminder: true,
|
|
3859
|
+
showCancelled: false,
|
|
3860
|
+
showNoShow: true,
|
|
3861
|
+
compact: false,
|
|
3862
|
+
newBookingNotif: true,
|
|
3863
|
+
cancelAlerts: true,
|
|
3864
|
+
noShowTracking: false
|
|
3865
|
+
};
|
|
4153
3866
|
function AgendaGeneralSettings() {
|
|
4154
|
-
const
|
|
3867
|
+
const t2 = core.useTranslation();
|
|
4155
3868
|
const { startTime, endTime, setStartTime, setEndTime } = useAgendaSettings();
|
|
3869
|
+
const s = saas.useTenantPluginSettings("agenda", AGENDA_TOGGLE_DEFAULTS);
|
|
4156
3870
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
4157
|
-
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title:
|
|
4158
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4159
|
-
|
|
4160
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4161
|
-
} }),
|
|
4162
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t("agenda.settings.autoCreateOrder"), description: t("agenda.settings.autoCreateOrderDesc"), checked: true, onChange: () => {
|
|
4163
|
-
} })
|
|
3871
|
+
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("agenda.settings.scheduling"), description: t2("agenda.settings.schedulingDesc"), children: [
|
|
3872
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.conflictDetection"), description: t2("agenda.settings.conflictDetectionDesc"), checked: s.get("conflictDetection"), onChange: (v) => s.set("conflictDetection", v) }),
|
|
3873
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.dragDrop"), description: t2("agenda.settings.dragDropDesc"), checked: s.get("dragDrop"), onChange: (v) => s.set("dragDrop", v) }),
|
|
3874
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.autoCreateOrder"), description: t2("agenda.settings.autoCreateOrderDesc"), checked: s.get("autoCreateOrder"), onChange: (v) => s.set("autoCreateOrder", v) })
|
|
4164
3875
|
] }),
|
|
4165
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.SettingsGroup, { title:
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4169
|
-
} }),
|
|
4170
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t("agenda.settings.blockOutside"), description: t("agenda.settings.blockOutsideDesc"), checked: false, onChange: () => {
|
|
4171
|
-
} })
|
|
3876
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.SettingsGroup, { title: t2("agenda.settings.locations"), description: t2("agenda.settings.locationsDesc"), children: /* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.enableLocation"), description: t2("agenda.settings.enableLocationDesc"), checked: s.get("enableLocation"), onChange: (v) => s.set("enableLocation", v) }) }),
|
|
3877
|
+
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("agenda.settings.workingHours"), description: t2("agenda.settings.workingHoursDesc"), children: [
|
|
3878
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.enableWorkingHours"), description: t2("agenda.settings.enableWorkingHoursDesc"), checked: s.get("enableWorkingHours"), onChange: (v) => s.set("enableWorkingHours", v) }),
|
|
3879
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.blockOutside"), description: t2("agenda.settings.blockOutsideDesc"), checked: s.get("blockOutside"), onChange: (v) => s.set("blockOutside", v) })
|
|
4172
3880
|
] }),
|
|
4173
|
-
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title:
|
|
4174
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4175
|
-
|
|
4176
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4177
|
-
} }),
|
|
4178
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t("agenda.settings.reminder"), description: t("agenda.settings.reminderDesc"), checked: true, onChange: () => {
|
|
4179
|
-
} })
|
|
3881
|
+
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("agenda.settings.confirmations"), description: t2("agenda.settings.confirmationsDesc"), children: [
|
|
3882
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.enableConfirmations"), description: t2("agenda.settings.enableConfirmationsDesc"), checked: s.get("enableConfirmations"), onChange: (v) => s.set("enableConfirmations", v) }),
|
|
3883
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.autoConfirm"), description: t2("agenda.settings.autoConfirmDesc"), checked: s.get("autoConfirm"), onChange: (v) => s.set("autoConfirm", v) }),
|
|
3884
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.reminder"), description: t2("agenda.settings.reminderDesc"), checked: s.get("reminder"), onChange: (v) => s.set("reminder", v) })
|
|
4180
3885
|
] }),
|
|
4181
|
-
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title:
|
|
3886
|
+
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("agenda.settings.calendarDisplay"), description: t2("agenda.settings.calendarDisplayDesc"), children: [
|
|
4182
3887
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4183
3888
|
saas.SelectRow,
|
|
4184
3889
|
{
|
|
4185
|
-
label:
|
|
4186
|
-
description:
|
|
3890
|
+
label: t2("agenda.settings.dayStartTime"),
|
|
3891
|
+
description: t2("agenda.settings.dayStartTimeDesc"),
|
|
4187
3892
|
value: startTime,
|
|
4188
3893
|
options: HOUR_OPTIONS,
|
|
4189
3894
|
onChange: setStartTime
|
|
@@ -4192,27 +3897,21 @@ function AgendaGeneralSettings() {
|
|
|
4192
3897
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4193
3898
|
saas.SelectRow,
|
|
4194
3899
|
{
|
|
4195
|
-
label:
|
|
4196
|
-
description:
|
|
3900
|
+
label: t2("agenda.settings.dayEndTime"),
|
|
3901
|
+
description: t2("agenda.settings.dayEndTimeDesc"),
|
|
4197
3902
|
value: endTime,
|
|
4198
3903
|
options: HOUR_OPTIONS,
|
|
4199
3904
|
onChange: setEndTime
|
|
4200
3905
|
}
|
|
4201
3906
|
),
|
|
4202
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4203
|
-
|
|
4204
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4205
|
-
} }),
|
|
4206
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t("agenda.settings.compact"), description: t("agenda.settings.compactDesc"), checked: false, onChange: () => {
|
|
4207
|
-
} })
|
|
3907
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.showCancelled"), description: t2("agenda.settings.showCancelledDesc"), checked: s.get("showCancelled"), onChange: (v) => s.set("showCancelled", v) }),
|
|
3908
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.showNoShow"), description: t2("agenda.settings.showNoShowDesc"), checked: s.get("showNoShow"), onChange: (v) => s.set("showNoShow", v) }),
|
|
3909
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.compact"), description: t2("agenda.settings.compactDesc"), checked: s.get("compact"), onChange: (v) => s.set("compact", v) })
|
|
4208
3910
|
] }),
|
|
4209
|
-
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title:
|
|
4210
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4211
|
-
|
|
4212
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label:
|
|
4213
|
-
} }),
|
|
4214
|
-
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t("agenda.settings.noShowTracking"), description: t("agenda.settings.noShowTrackingDesc"), checked: false, onChange: () => {
|
|
4215
|
-
} })
|
|
3911
|
+
/* @__PURE__ */ jsxRuntime.jsxs(saas.SettingsGroup, { title: t2("agenda.settings.notifications"), description: t2("agenda.settings.notificationsDesc"), children: [
|
|
3912
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.newBookingNotif"), description: t2("agenda.settings.newBookingNotifDesc"), checked: s.get("newBookingNotif"), onChange: (v) => s.set("newBookingNotif", v) }),
|
|
3913
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.cancelAlerts"), description: t2("agenda.settings.cancelAlertsDesc"), checked: s.get("cancelAlerts"), onChange: (v) => s.set("cancelAlerts", v) }),
|
|
3914
|
+
/* @__PURE__ */ jsxRuntime.jsx(saas.ToggleRow, { label: t2("agenda.settings.noShowTracking"), description: t2("agenda.settings.noShowTrackingDesc"), checked: s.get("noShowTracking"), onChange: (v) => s.set("noShowTracking", v) })
|
|
4216
3915
|
] })
|
|
4217
3916
|
] });
|
|
4218
3917
|
}
|
|
@@ -4248,6 +3947,7 @@ var en = {
|
|
|
4248
3947
|
"agenda.appointment.searchService": "Search service to add...",
|
|
4249
3948
|
"agenda.appointment.selectLocation": "Select location...",
|
|
4250
3949
|
"agenda.appointment.selectProfessional": "Select professional...",
|
|
3950
|
+
"agenda.appointment.selectClientHint": "Select the client from the list or use New client to enable saving.",
|
|
4251
3951
|
"agenda.appointment.services": "Services",
|
|
4252
3952
|
"agenda.appointment.status": "Status",
|
|
4253
3953
|
"agenda.appointment.to": "to",
|
|
@@ -4468,6 +4168,7 @@ var ptBR = {
|
|
|
4468
4168
|
"agenda.appointment.searchService": "Buscar servi\xE7o para adicionar...",
|
|
4469
4169
|
"agenda.appointment.selectLocation": "Selecionar local...",
|
|
4470
4170
|
"agenda.appointment.selectProfessional": "Selecionar profissional...",
|
|
4171
|
+
"agenda.appointment.selectClientHint": "Selecione o cliente na lista ou use Novo cliente para habilitar o salvamento.",
|
|
4471
4172
|
"agenda.appointment.services": "Servi\xE7os",
|
|
4472
4173
|
"agenda.appointment.status": "Status",
|
|
4473
4174
|
"agenda.appointment.to": "at\xE9",
|
|
@@ -5022,7 +4723,7 @@ function createFinancialBridge(provider2) {
|
|
|
5022
4723
|
let typeNames = {};
|
|
5023
4724
|
if (typeIds.length > 0) {
|
|
5024
4725
|
const { data: types } = await supabase.from("plg_financial_payment_method_types").select("id, name").in("id", typeIds);
|
|
5025
|
-
typeNames = Object.fromEntries((types ?? []).map((
|
|
4726
|
+
typeNames = Object.fromEntries((types ?? []).map((t2) => [t2.id, t2.name]));
|
|
5026
4727
|
}
|
|
5027
4728
|
const movements = (movRes.data ?? []).map((m) => ({
|
|
5028
4729
|
id: String(m.id),
|
|
@@ -5123,7 +4824,7 @@ function createFinancialBridge(provider2) {
|
|
|
5123
4824
|
},
|
|
5124
4825
|
async getPaymentMethodTypes() {
|
|
5125
4826
|
const types = await provider2.getPaymentMethodTypes();
|
|
5126
|
-
return types.map((
|
|
4827
|
+
return types.map((t2) => ({ id: t2.id, name: t2.name, transactionType: t2.transactionType }));
|
|
5127
4828
|
},
|
|
5128
4829
|
async getBankAccounts() {
|
|
5129
4830
|
const accounts = await provider2.getBankAccounts();
|
|
@@ -5165,6 +4866,65 @@ function mapInvoiceStatus(status) {
|
|
|
5165
4866
|
return "pending";
|
|
5166
4867
|
}
|
|
5167
4868
|
}
|
|
4869
|
+
|
|
4870
|
+
// src/integrations/google-calendar/mapping.ts
|
|
4871
|
+
function bookingSummary(b) {
|
|
4872
|
+
const services = (b.services ?? []).map((s) => s.name).filter(Boolean).join(", ");
|
|
4873
|
+
const who = b.clientName ?? "";
|
|
4874
|
+
if (services && who) return `${services} \u2014 ${who}`;
|
|
4875
|
+
return services || who || "Appointment";
|
|
4876
|
+
}
|
|
4877
|
+
function bookingToEvent2(b, timeZone) {
|
|
4878
|
+
if (!b.startsAt || !b.endsAt) return null;
|
|
4879
|
+
const notes = [b.notes, b.professionalName ? `Pro: ${b.professionalName}` : "", b.locationName ?? ""].filter(Boolean).join("\n");
|
|
4880
|
+
return {
|
|
4881
|
+
summary: bookingSummary(b),
|
|
4882
|
+
description: notes || void 0,
|
|
4883
|
+
start: { dateTime: b.startsAt, timeZone },
|
|
4884
|
+
end: { dateTime: b.endsAt, timeZone }
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
function eventToBookingPatch(ev) {
|
|
4888
|
+
if (ev.status === "cancelled") return { status: "cancelled" };
|
|
4889
|
+
const t2 = eventTimes(ev);
|
|
4890
|
+
return { startsAt: t2.startsAt, endsAt: t2.endsAt };
|
|
4891
|
+
}
|
|
4892
|
+
function eventTimes(ev) {
|
|
4893
|
+
const allDay = !ev.start?.dateTime && !!ev.start?.date;
|
|
4894
|
+
const startsAt = ev.start?.dateTime ?? ev.start?.date ?? "";
|
|
4895
|
+
const endsAt = ev.end?.dateTime ?? ev.end?.date ?? startsAt;
|
|
4896
|
+
return { startsAt, endsAt, allDay };
|
|
4897
|
+
}
|
|
4898
|
+
function channelRowToChannel(r) {
|
|
4899
|
+
return {
|
|
4900
|
+
id: r.id,
|
|
4901
|
+
integrationId: r.integration_id,
|
|
4902
|
+
tenantId: r.tenant_id,
|
|
4903
|
+
googleCalendarId: r.google_calendar_id,
|
|
4904
|
+
summary: r.summary ?? null,
|
|
4905
|
+
color: r.color ?? null,
|
|
4906
|
+
direction: r.direction,
|
|
4907
|
+
targetKind: r.target_kind ?? null,
|
|
4908
|
+
targetId: r.target_id ?? null,
|
|
4909
|
+
importMode: r.import_mode ?? "appointment",
|
|
4910
|
+
syncToken: r.sync_token ?? null,
|
|
4911
|
+
channelId: r.channel_id ?? null,
|
|
4912
|
+
resourceId: r.resource_id ?? null,
|
|
4913
|
+
channelExpiresAt: r.channel_expires_at ?? null,
|
|
4914
|
+
isActive: r.is_active ?? true,
|
|
4915
|
+
createdAt: r.created_at ?? "",
|
|
4916
|
+
updatedAt: r.updated_at ?? ""
|
|
4917
|
+
};
|
|
4918
|
+
}
|
|
4919
|
+
|
|
4920
|
+
// src/integrations/google-calendar/data/tables.ts
|
|
4921
|
+
var GCAL_TABLES = {
|
|
4922
|
+
integrations: "plg_calendar_integrations",
|
|
4923
|
+
channels: "plg_calendar_channels",
|
|
4924
|
+
syncLog: "plg_calendar_sync_log"
|
|
4925
|
+
};
|
|
4926
|
+
|
|
4927
|
+
// src/integrations/google-calendar/data/supabase.ts
|
|
5168
4928
|
var FUNCTION = "google-calendar-sync";
|
|
5169
4929
|
function sb() {
|
|
5170
4930
|
const supabase = core.getSupabaseClientOptional();
|
|
@@ -5174,7 +4934,7 @@ function sb() {
|
|
|
5174
4934
|
function createGoogleCalendarProvider() {
|
|
5175
4935
|
return {
|
|
5176
4936
|
async getIntegration() {
|
|
5177
|
-
const { data } = await sb().from(
|
|
4937
|
+
const { data } = await sb().from(GCAL_TABLES.integrations).select("*").eq("provider", "google").limit(1).maybeSingle();
|
|
5178
4938
|
if (!data) return null;
|
|
5179
4939
|
return {
|
|
5180
4940
|
id: data.id,
|
|
@@ -5187,24 +4947,24 @@ function createGoogleCalendarProvider() {
|
|
|
5187
4947
|
},
|
|
5188
4948
|
async getConnectUrl(redirectTo) {
|
|
5189
4949
|
const { data, error } = await sb().functions.invoke(FUNCTION, {
|
|
5190
|
-
body: { action: "oauth_start", redirectTo: redirectTo ?? window.location.href }
|
|
4950
|
+
body: { action: "oauth_start", tenantId: core.getActiveTenantId(), redirectTo: redirectTo ?? window.location.href }
|
|
5191
4951
|
});
|
|
5192
4952
|
if (error) throw new Error(error.message);
|
|
5193
4953
|
return data.url;
|
|
5194
4954
|
},
|
|
5195
4955
|
async setCalendar(calendarId) {
|
|
5196
|
-
await sb().from(
|
|
4956
|
+
await sb().from(GCAL_TABLES.integrations).update({ calendar_id: calendarId, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("provider", "google");
|
|
5197
4957
|
},
|
|
5198
4958
|
async disconnect() {
|
|
5199
|
-
await sb().from(
|
|
4959
|
+
await sb().from(GCAL_TABLES.integrations).update({ active: false, oauth_refresh_token: null, oauth_access_token: null }).eq("provider", "google");
|
|
5200
4960
|
},
|
|
5201
4961
|
async syncNow() {
|
|
5202
|
-
const { data, error } = await sb().functions.invoke(FUNCTION, { body: { action: "pull_events", trigger: "manual" } });
|
|
4962
|
+
const { data, error } = await sb().functions.invoke(FUNCTION, { body: { action: "pull_events", tenantId: core.getActiveTenantId(), trigger: "manual" } });
|
|
5203
4963
|
if (error) throw new Error(error.message);
|
|
5204
4964
|
return data;
|
|
5205
4965
|
},
|
|
5206
4966
|
async getSyncLog() {
|
|
5207
|
-
const { data } = await sb().from(
|
|
4967
|
+
const { data } = await sb().from(GCAL_TABLES.syncLog).select("*").order("created_at", { ascending: false }).limit(20);
|
|
5208
4968
|
return (data ?? []).map((r) => ({
|
|
5209
4969
|
id: r.id,
|
|
5210
4970
|
direction: r.direction,
|
|
@@ -5215,32 +4975,523 @@ function createGoogleCalendarProvider() {
|
|
|
5215
4975
|
error: r.error ?? void 0,
|
|
5216
4976
|
createdAt: r.created_at
|
|
5217
4977
|
}));
|
|
4978
|
+
},
|
|
4979
|
+
async listGoogleCalendars() {
|
|
4980
|
+
const { data, error } = await sb().functions.invoke(FUNCTION, { body: { action: "list_calendars", tenantId: core.getActiveTenantId() } });
|
|
4981
|
+
if (error) throw new Error(error.message);
|
|
4982
|
+
return data.calendars ?? [];
|
|
4983
|
+
},
|
|
4984
|
+
async listChannels() {
|
|
4985
|
+
const { data } = await sb().from(GCAL_TABLES.channels).select("*").order("created_at", { ascending: true });
|
|
4986
|
+
return (data ?? []).map(channelRowToChannel);
|
|
4987
|
+
},
|
|
4988
|
+
async saveChannel(input) {
|
|
4989
|
+
const tenantId = core.getActiveTenantId();
|
|
4990
|
+
let integrationId = input.integrationId;
|
|
4991
|
+
if (!integrationId) {
|
|
4992
|
+
const { data } = await sb().from(GCAL_TABLES.integrations).select("id").eq("provider", "google").limit(1).maybeSingle();
|
|
4993
|
+
integrationId = data?.id;
|
|
4994
|
+
}
|
|
4995
|
+
const row = {
|
|
4996
|
+
integration_id: integrationId,
|
|
4997
|
+
tenant_id: tenantId,
|
|
4998
|
+
google_calendar_id: input.googleCalendarId,
|
|
4999
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5000
|
+
};
|
|
5001
|
+
if (input.id !== void 0) row.id = input.id;
|
|
5002
|
+
if (input.summary !== void 0) row.summary = input.summary;
|
|
5003
|
+
if (input.color !== void 0) row.color = input.color;
|
|
5004
|
+
if (input.direction !== void 0) row.direction = input.direction;
|
|
5005
|
+
if (input.targetKind !== void 0) row.target_kind = input.targetKind;
|
|
5006
|
+
if (input.targetId !== void 0) row.target_id = input.targetId;
|
|
5007
|
+
if (input.importMode !== void 0) row.import_mode = input.importMode;
|
|
5008
|
+
if (input.isActive !== void 0) row.is_active = input.isActive;
|
|
5009
|
+
const { error } = await sb().from(GCAL_TABLES.channels).upsert(row, { onConflict: "integration_id,google_calendar_id" });
|
|
5010
|
+
if (error) throw new Error(error.message);
|
|
5011
|
+
},
|
|
5012
|
+
async deleteChannel(id) {
|
|
5013
|
+
const { error } = await sb().from(GCAL_TABLES.channels).delete().eq("id", id);
|
|
5014
|
+
if (error) throw new Error(error.message);
|
|
5015
|
+
},
|
|
5016
|
+
async listExternalEvents(params) {
|
|
5017
|
+
const { data, error } = await sb().functions.invoke(FUNCTION, {
|
|
5018
|
+
body: { action: "list_external_events", tenantId: core.getActiveTenantId(), channelId: params?.channelId, timeMin: params?.timeMin }
|
|
5019
|
+
});
|
|
5020
|
+
if (error) throw new Error(error.message);
|
|
5021
|
+
return data.events ?? [];
|
|
5022
|
+
},
|
|
5023
|
+
async importEvents(items) {
|
|
5024
|
+
const { data, error } = await sb().functions.invoke(FUNCTION, {
|
|
5025
|
+
body: { action: "import_events", tenantId: core.getActiveTenantId(), items }
|
|
5026
|
+
});
|
|
5027
|
+
if (error) throw new Error(error.message);
|
|
5028
|
+
return data;
|
|
5218
5029
|
}
|
|
5219
5030
|
};
|
|
5220
5031
|
}
|
|
5032
|
+
function CalendarColorDot({
|
|
5033
|
+
color,
|
|
5034
|
+
className = ""
|
|
5035
|
+
}) {
|
|
5036
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5037
|
+
"span",
|
|
5038
|
+
{
|
|
5039
|
+
className: `inline-block h-2.5 w-2.5 shrink-0 rounded-full border border-black/10 ${className}`,
|
|
5040
|
+
style: { backgroundColor: color || "hsl(var(--muted-foreground))" },
|
|
5041
|
+
"aria-hidden": true
|
|
5042
|
+
}
|
|
5043
|
+
);
|
|
5044
|
+
}
|
|
5045
|
+
var EMPTY = { assignee: [], service: [], location: [] };
|
|
5046
|
+
function useAgendaLookups(enabled) {
|
|
5047
|
+
const [lookups, setLookups] = React13.useState(EMPTY);
|
|
5048
|
+
const [loading, setLoading] = React13.useState(false);
|
|
5049
|
+
React13.useEffect(() => {
|
|
5050
|
+
let cancelled = false;
|
|
5051
|
+
setLoading(true);
|
|
5052
|
+
void (async () => {
|
|
5053
|
+
const agenda = createSupabaseAgendaProvider();
|
|
5054
|
+
const tenantId = core.getActiveTenantId();
|
|
5055
|
+
const publicProvider = tenantId ? chunk3KWNHV5A_cjs.createSupabasePublicBookingProvider({ tenantId }) : null;
|
|
5056
|
+
const [assignee, location, service] = await Promise.all([
|
|
5057
|
+
agenda.getProfessionals().then(
|
|
5058
|
+
(rows) => rows.map((p) => ({ id: p.id, name: p.name })),
|
|
5059
|
+
() => []
|
|
5060
|
+
),
|
|
5061
|
+
(agenda.getLocations?.() ?? Promise.resolve([])).then(
|
|
5062
|
+
(rows) => rows.map((l) => ({ id: l.id, name: l.name })),
|
|
5063
|
+
() => []
|
|
5064
|
+
),
|
|
5065
|
+
(publicProvider?.getServices() ?? Promise.resolve([])).then(
|
|
5066
|
+
(rows) => rows.map((s) => ({ id: s.id, name: s.name })),
|
|
5067
|
+
() => []
|
|
5068
|
+
)
|
|
5069
|
+
]);
|
|
5070
|
+
if (cancelled) return;
|
|
5071
|
+
setLookups({ assignee, service, location });
|
|
5072
|
+
setLoading(false);
|
|
5073
|
+
})();
|
|
5074
|
+
return () => {
|
|
5075
|
+
cancelled = true;
|
|
5076
|
+
};
|
|
5077
|
+
}, [enabled]);
|
|
5078
|
+
const hasAny = lookups.assignee.length > 0 || lookups.service.length > 0 || lookups.location.length > 0;
|
|
5079
|
+
return { lookups, loading, hasAny };
|
|
5080
|
+
}
|
|
5081
|
+
function optionsForKind(lookups, kind) {
|
|
5082
|
+
if (kind === "assignee") return lookups.assignee;
|
|
5083
|
+
if (kind === "service") return lookups.service;
|
|
5084
|
+
if (kind === "location") return lookups.location;
|
|
5085
|
+
return [];
|
|
5086
|
+
}
|
|
5087
|
+
var DIRECTION_LABELS = {
|
|
5088
|
+
bidirectional: "Two-way",
|
|
5089
|
+
inbound: "Import only",
|
|
5090
|
+
outbound: "Export only",
|
|
5091
|
+
off: "Off"
|
|
5092
|
+
};
|
|
5093
|
+
var IMPORT_MODE_LABELS = {
|
|
5094
|
+
block: "Block",
|
|
5095
|
+
appointment: "Appointment"
|
|
5096
|
+
};
|
|
5097
|
+
var TARGET_KIND_LABELS = {
|
|
5098
|
+
agenda: "Whole agenda",
|
|
5099
|
+
assignee: "Professional",
|
|
5100
|
+
service: "Service",
|
|
5101
|
+
location: "Location"
|
|
5102
|
+
};
|
|
5103
|
+
var NO_TARGET = "__none__";
|
|
5104
|
+
function defaultChannel() {
|
|
5105
|
+
return { direction: "bidirectional", targetKind: null, targetId: null, importMode: "block" };
|
|
5106
|
+
}
|
|
5107
|
+
function ChannelMappingList({
|
|
5108
|
+
provider: provider2,
|
|
5109
|
+
onChannelsChange
|
|
5110
|
+
}) {
|
|
5111
|
+
const [calendars, setCalendars] = React13.useState([]);
|
|
5112
|
+
const [byGcalId, setByGcalId] = React13.useState({});
|
|
5113
|
+
const [loading, setLoading] = React13.useState(true);
|
|
5114
|
+
const [error, setError] = React13.useState(null);
|
|
5115
|
+
const [savingId, setSavingId] = React13.useState(null);
|
|
5116
|
+
const { lookups, hasAny: hasLookups } = useAgendaLookups(true);
|
|
5117
|
+
const load = React13.useCallback(async () => {
|
|
5118
|
+
setLoading(true);
|
|
5119
|
+
setError(null);
|
|
5120
|
+
try {
|
|
5121
|
+
const [cals, channels] = await Promise.all([
|
|
5122
|
+
provider2.listGoogleCalendars(),
|
|
5123
|
+
provider2.listChannels()
|
|
5124
|
+
]);
|
|
5125
|
+
setCalendars(cals);
|
|
5126
|
+
const map = {};
|
|
5127
|
+
for (const ch of channels) map[ch.googleCalendarId] = ch;
|
|
5128
|
+
setByGcalId(map);
|
|
5129
|
+
onChannelsChange?.(channels);
|
|
5130
|
+
} catch (e) {
|
|
5131
|
+
setError(e?.message ?? "Could not load Google calendars");
|
|
5132
|
+
} finally {
|
|
5133
|
+
setLoading(false);
|
|
5134
|
+
}
|
|
5135
|
+
}, [provider2, onChannelsChange]);
|
|
5136
|
+
React13.useEffect(() => {
|
|
5137
|
+
void load();
|
|
5138
|
+
}, [load]);
|
|
5139
|
+
const persist = React13.useCallback(
|
|
5140
|
+
async (cal, patch) => {
|
|
5141
|
+
const existing = byGcalId[cal.id];
|
|
5142
|
+
const base = existing ?? { ...defaultChannel(), isActive: true };
|
|
5143
|
+
setSavingId(cal.id);
|
|
5144
|
+
try {
|
|
5145
|
+
await provider2.saveChannel({
|
|
5146
|
+
id: existing?.id,
|
|
5147
|
+
googleCalendarId: cal.id,
|
|
5148
|
+
summary: cal.summary,
|
|
5149
|
+
color: cal.backgroundColor ?? null,
|
|
5150
|
+
direction: patch.direction ?? base.direction,
|
|
5151
|
+
targetKind: patch.targetKind !== void 0 ? patch.targetKind : base.targetKind,
|
|
5152
|
+
targetId: patch.targetId !== void 0 ? patch.targetId : base.targetId ?? null,
|
|
5153
|
+
importMode: patch.importMode ?? base.importMode,
|
|
5154
|
+
isActive: patch.isActive !== void 0 ? patch.isActive : base.isActive
|
|
5155
|
+
});
|
|
5156
|
+
const channels = await provider2.listChannels();
|
|
5157
|
+
const map = {};
|
|
5158
|
+
for (const ch of channels) map[ch.googleCalendarId] = ch;
|
|
5159
|
+
setByGcalId(map);
|
|
5160
|
+
onChannelsChange?.(channels);
|
|
5161
|
+
} catch (e) {
|
|
5162
|
+
ui.toast.error(e?.message ?? "Could not save calendar mapping");
|
|
5163
|
+
} finally {
|
|
5164
|
+
setSavingId(null);
|
|
5165
|
+
}
|
|
5166
|
+
},
|
|
5167
|
+
[byGcalId, provider2, onChannelsChange]
|
|
5168
|
+
);
|
|
5169
|
+
if (loading) {
|
|
5170
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-6 text-center text-sm text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin inline" }) });
|
|
5171
|
+
}
|
|
5172
|
+
if (error) {
|
|
5173
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border border-warning/40 bg-warning-soft/40 px-3 py-3 text-sm", children: [
|
|
5174
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-warning-soft-foreground", children: [
|
|
5175
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-4 w-4" }),
|
|
5176
|
+
" ",
|
|
5177
|
+
error
|
|
5178
|
+
] }),
|
|
5179
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => void load(), children: [
|
|
5180
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-3.5 w-3.5" }),
|
|
5181
|
+
" Retry"
|
|
5182
|
+
] })
|
|
5183
|
+
] });
|
|
5184
|
+
}
|
|
5185
|
+
if (calendars.length === 0) {
|
|
5186
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: "No Google calendars found on this account." });
|
|
5187
|
+
}
|
|
5188
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border divide-y", children: [
|
|
5189
|
+
calendars.map((cal) => {
|
|
5190
|
+
const ch = byGcalId[cal.id];
|
|
5191
|
+
const active = !!ch?.isActive;
|
|
5192
|
+
const saving = savingId === cal.id;
|
|
5193
|
+
const direction = ch?.direction ?? "bidirectional";
|
|
5194
|
+
const targetKind = ch?.targetKind ?? null;
|
|
5195
|
+
const importMode = ch?.importMode ?? "block";
|
|
5196
|
+
const canImport = direction === "inbound" || direction === "bidirectional";
|
|
5197
|
+
const kindOptions = optionsForKind(lookups, targetKind);
|
|
5198
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-3 space-y-3", children: [
|
|
5199
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
5200
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5201
|
+
ui.Checkbox,
|
|
5202
|
+
{
|
|
5203
|
+
checked: active,
|
|
5204
|
+
disabled: saving,
|
|
5205
|
+
onChange: (next) => void persist(cal, { isActive: next })
|
|
5206
|
+
}
|
|
5207
|
+
),
|
|
5208
|
+
/* @__PURE__ */ jsxRuntime.jsx(CalendarColorDot, { color: cal.backgroundColor }),
|
|
5209
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: cal.summary }),
|
|
5210
|
+
cal.primary && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "outline", className: "shrink-0", children: "Primary" }),
|
|
5211
|
+
saving && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "ml-auto h-3.5 w-3.5 animate-spin text-muted-foreground" })
|
|
5212
|
+
] }),
|
|
5213
|
+
active && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2 pl-[26px] sm:grid-cols-2 lg:grid-cols-4", children: [
|
|
5214
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
|
|
5215
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: "Direction" }),
|
|
5216
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5217
|
+
ui.Select,
|
|
5218
|
+
{
|
|
5219
|
+
value: direction,
|
|
5220
|
+
onValueChange: (v) => void persist(cal, { direction: v }),
|
|
5221
|
+
children: [
|
|
5222
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
|
|
5223
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: Object.keys(DIRECTION_LABELS).map((d) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: d, children: DIRECTION_LABELS[d] }, d)) })
|
|
5224
|
+
]
|
|
5225
|
+
}
|
|
5226
|
+
)
|
|
5227
|
+
] }),
|
|
5228
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
|
|
5229
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: "Target" }),
|
|
5230
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5231
|
+
ui.Select,
|
|
5232
|
+
{
|
|
5233
|
+
value: targetKind ?? "agenda",
|
|
5234
|
+
onValueChange: (v) => void persist(cal, {
|
|
5235
|
+
targetKind: v === "agenda" ? null : v,
|
|
5236
|
+
// Reset the id whenever the kind changes.
|
|
5237
|
+
targetId: null
|
|
5238
|
+
}),
|
|
5239
|
+
children: [
|
|
5240
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
|
|
5241
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: Object.keys(TARGET_KIND_LABELS).map(
|
|
5242
|
+
(k) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: k, children: TARGET_KIND_LABELS[k] }, k)
|
|
5243
|
+
) })
|
|
5244
|
+
]
|
|
5245
|
+
}
|
|
5246
|
+
)
|
|
5247
|
+
] }),
|
|
5248
|
+
targetKind && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
|
|
5249
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: TARGET_KIND_LABELS[targetKind] }),
|
|
5250
|
+
kindOptions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5251
|
+
ui.Select,
|
|
5252
|
+
{
|
|
5253
|
+
value: ch?.targetId ?? NO_TARGET,
|
|
5254
|
+
onValueChange: (v) => void persist(cal, { targetId: v === NO_TARGET ? null : v }),
|
|
5255
|
+
children: [
|
|
5256
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, { placeholder: "Select\u2026" }) }),
|
|
5257
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: kindOptions.map((o) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: o.id, children: o.name }, o.id)) })
|
|
5258
|
+
]
|
|
5259
|
+
}
|
|
5260
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
5261
|
+
"input",
|
|
5262
|
+
{
|
|
5263
|
+
defaultValue: ch?.targetId ?? "",
|
|
5264
|
+
placeholder: "uuid",
|
|
5265
|
+
onBlur: (e) => void persist(cal, { targetId: e.target.value.trim() || null }),
|
|
5266
|
+
className: "w-full mt-1 h-8 rounded-input border border-input bg-card px-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
5267
|
+
}
|
|
5268
|
+
)
|
|
5269
|
+
] }),
|
|
5270
|
+
canImport && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
|
|
5271
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium text-muted-foreground", children: "Import as" }),
|
|
5272
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5273
|
+
ui.Select,
|
|
5274
|
+
{
|
|
5275
|
+
value: importMode,
|
|
5276
|
+
onValueChange: (v) => void persist(cal, { importMode: v }),
|
|
5277
|
+
children: [
|
|
5278
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { className: "mt-1 h-8 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
|
|
5279
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: Object.keys(IMPORT_MODE_LABELS).map((m) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: m, children: IMPORT_MODE_LABELS[m] }, m)) })
|
|
5280
|
+
]
|
|
5281
|
+
}
|
|
5282
|
+
)
|
|
5283
|
+
] })
|
|
5284
|
+
] })
|
|
5285
|
+
] }, cal.id);
|
|
5286
|
+
}),
|
|
5287
|
+
!hasLookups && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 py-2 text-[11px] text-muted-foreground", children: "No professional/service/location lookups available \u2014 target ids fall back to manual uuid entry." })
|
|
5288
|
+
] });
|
|
5289
|
+
}
|
|
5290
|
+
function formatWhen(ev) {
|
|
5291
|
+
const start = new Date(ev.startsAt);
|
|
5292
|
+
if (ev.allDay) {
|
|
5293
|
+
return start.toLocaleDateString("pt-BR", { day: "2-digit", month: "short", year: "numeric" });
|
|
5294
|
+
}
|
|
5295
|
+
return start.toLocaleString("pt-BR", {
|
|
5296
|
+
day: "2-digit",
|
|
5297
|
+
month: "short",
|
|
5298
|
+
hour: "2-digit",
|
|
5299
|
+
minute: "2-digit"
|
|
5300
|
+
});
|
|
5301
|
+
}
|
|
5302
|
+
function ImportEventsWizard({
|
|
5303
|
+
provider: provider2,
|
|
5304
|
+
channels,
|
|
5305
|
+
onClose,
|
|
5306
|
+
onImported
|
|
5307
|
+
}) {
|
|
5308
|
+
const [events, setEvents] = React13.useState([]);
|
|
5309
|
+
const [selected, setSelected] = React13.useState(/* @__PURE__ */ new Set());
|
|
5310
|
+
const [loading, setLoading] = React13.useState(true);
|
|
5311
|
+
const [error, setError] = React13.useState(null);
|
|
5312
|
+
const [importing, setImporting] = React13.useState(false);
|
|
5313
|
+
const [result, setResult] = React13.useState(null);
|
|
5314
|
+
const channelById = React13.useMemo(() => {
|
|
5315
|
+
const m = {};
|
|
5316
|
+
for (const c of channels) m[c.id] = c;
|
|
5317
|
+
return m;
|
|
5318
|
+
}, [channels]);
|
|
5319
|
+
const load = React13.useCallback(async () => {
|
|
5320
|
+
setLoading(true);
|
|
5321
|
+
setError(null);
|
|
5322
|
+
setResult(null);
|
|
5323
|
+
try {
|
|
5324
|
+
const rows = await provider2.listExternalEvents();
|
|
5325
|
+
setEvents(rows);
|
|
5326
|
+
setSelected(new Set(rows.map((r) => r.eventId)));
|
|
5327
|
+
} catch (e) {
|
|
5328
|
+
setError(e?.message ?? "Could not load upcoming events");
|
|
5329
|
+
} finally {
|
|
5330
|
+
setLoading(false);
|
|
5331
|
+
}
|
|
5332
|
+
}, [provider2]);
|
|
5333
|
+
React13.useEffect(() => {
|
|
5334
|
+
void load();
|
|
5335
|
+
}, [load]);
|
|
5336
|
+
const toggle = (eventId) => setSelected((prev) => {
|
|
5337
|
+
const next = new Set(prev);
|
|
5338
|
+
if (next.has(eventId)) next.delete(eventId);
|
|
5339
|
+
else next.add(eventId);
|
|
5340
|
+
return next;
|
|
5341
|
+
});
|
|
5342
|
+
const allSelected = events.length > 0 && selected.size === events.length;
|
|
5343
|
+
const toggleAll = () => setSelected(allSelected ? /* @__PURE__ */ new Set() : new Set(events.map((e) => e.eventId)));
|
|
5344
|
+
async function handleImport() {
|
|
5345
|
+
const items = events.filter((e) => selected.has(e.eventId)).map((e) => ({ channelId: e.channelId, eventId: e.eventId }));
|
|
5346
|
+
if (items.length === 0) return;
|
|
5347
|
+
setImporting(true);
|
|
5348
|
+
try {
|
|
5349
|
+
const res = await provider2.importEvents(items);
|
|
5350
|
+
setResult(res);
|
|
5351
|
+
onImported?.(res);
|
|
5352
|
+
ui.toast.success(`Imported ${res.imported} \xB7 skipped ${res.skipped}`);
|
|
5353
|
+
await load();
|
|
5354
|
+
} catch (e) {
|
|
5355
|
+
ui.toast.error(e?.message ?? "Import failed");
|
|
5356
|
+
} finally {
|
|
5357
|
+
setImporting(false);
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border bg-card", children: [
|
|
5361
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-2", children: [
|
|
5362
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarPlus, { className: "h-4 w-4 text-muted-foreground" }),
|
|
5363
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold", children: "Import future events" }),
|
|
5364
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5365
|
+
"button",
|
|
5366
|
+
{
|
|
5367
|
+
onClick: onClose,
|
|
5368
|
+
className: "ml-auto rounded p-1 text-muted-foreground hover:bg-muted/50",
|
|
5369
|
+
"aria-label": "Close",
|
|
5370
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" })
|
|
5371
|
+
}
|
|
5372
|
+
)
|
|
5373
|
+
] }),
|
|
5374
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin inline" }) }) : error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3", children: [
|
|
5375
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm text-warning-soft-foreground", children: [
|
|
5376
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-4 w-4" }),
|
|
5377
|
+
" ",
|
|
5378
|
+
error
|
|
5379
|
+
] }),
|
|
5380
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", className: "mt-2", onClick: () => void load(), children: [
|
|
5381
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-3.5 w-3.5" }),
|
|
5382
|
+
" Retry"
|
|
5383
|
+
] })
|
|
5384
|
+
] }) : events.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-8 px-4 text-center space-y-1", children: [
|
|
5385
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: "You're all caught up" }),
|
|
5386
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No upcoming Google events left to import on your active calendars." }),
|
|
5387
|
+
result && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-success", children: [
|
|
5388
|
+
"Imported ",
|
|
5389
|
+
result.imported,
|
|
5390
|
+
" \xB7 skipped ",
|
|
5391
|
+
result.skipped,
|
|
5392
|
+
result.errors.length > 0 ? ` \xB7 ${result.errors.length} errors` : ""
|
|
5393
|
+
] })
|
|
5394
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5395
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5 border-b px-3 py-2", children: [
|
|
5396
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: allSelected, onChange: toggleAll }),
|
|
5397
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
5398
|
+
selected.size,
|
|
5399
|
+
" of ",
|
|
5400
|
+
events.length,
|
|
5401
|
+
" selected"
|
|
5402
|
+
] })
|
|
5403
|
+
] }),
|
|
5404
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-[320px] overflow-y-auto divide-y", children: events.map((ev) => {
|
|
5405
|
+
const ch = channelById[ev.channelId];
|
|
5406
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5407
|
+
"label",
|
|
5408
|
+
{
|
|
5409
|
+
className: "flex items-start gap-2.5 px-3 py-2.5 cursor-pointer hover:bg-muted/30",
|
|
5410
|
+
children: [
|
|
5411
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5412
|
+
ui.Checkbox,
|
|
5413
|
+
{
|
|
5414
|
+
checked: selected.has(ev.eventId),
|
|
5415
|
+
onChange: () => toggle(ev.eventId)
|
|
5416
|
+
}
|
|
5417
|
+
),
|
|
5418
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
|
|
5419
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5420
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium truncate", children: ev.summary || "(no title)" }),
|
|
5421
|
+
ev.allDay && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "secondary", className: "shrink-0", children: "All-day" })
|
|
5422
|
+
] }),
|
|
5423
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
5424
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatWhen(ev) }),
|
|
5425
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
5426
|
+
/* @__PURE__ */ jsxRuntime.jsx(CalendarColorDot, { color: ch?.color }),
|
|
5427
|
+
ch?.summary ?? ev.calendarId
|
|
5428
|
+
] })
|
|
5429
|
+
] })
|
|
5430
|
+
] })
|
|
5431
|
+
]
|
|
5432
|
+
},
|
|
5433
|
+
ev.eventId
|
|
5434
|
+
);
|
|
5435
|
+
}) }),
|
|
5436
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-t px-3 py-2", children: [
|
|
5437
|
+
result && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
5438
|
+
"Last run: ",
|
|
5439
|
+
result.imported,
|
|
5440
|
+
" imported",
|
|
5441
|
+
result.errors.length > 0 ? `, ${result.errors.length} errors` : ""
|
|
5442
|
+
] }),
|
|
5443
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5444
|
+
ui.Button,
|
|
5445
|
+
{
|
|
5446
|
+
size: "sm",
|
|
5447
|
+
className: "ml-auto",
|
|
5448
|
+
disabled: importing || selected.size === 0,
|
|
5449
|
+
onClick: handleImport,
|
|
5450
|
+
children: [
|
|
5451
|
+
importing ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "h-3.5 w-3.5" }),
|
|
5452
|
+
"Import ",
|
|
5453
|
+
selected.size > 0 ? selected.size : ""
|
|
5454
|
+
]
|
|
5455
|
+
}
|
|
5456
|
+
)
|
|
5457
|
+
] })
|
|
5458
|
+
] })
|
|
5459
|
+
] });
|
|
5460
|
+
}
|
|
5221
5461
|
var provider = createGoogleCalendarProvider();
|
|
5462
|
+
function inboundChannels(channels) {
|
|
5463
|
+
return channels.filter(
|
|
5464
|
+
(c) => c.isActive && (c.direction === "inbound" || c.direction === "bidirectional")
|
|
5465
|
+
);
|
|
5466
|
+
}
|
|
5222
5467
|
function GoogleCalendarExtraPanel() {
|
|
5223
|
-
const [connected, setConnected] =
|
|
5224
|
-
const [
|
|
5225
|
-
const [syncing, setSyncing] =
|
|
5226
|
-
const [log, setLog] =
|
|
5227
|
-
const [loaded, setLoaded] =
|
|
5468
|
+
const [connected, setConnected] = React13.useState(false);
|
|
5469
|
+
const [lastSyncAt, setLastSyncAt] = React13.useState();
|
|
5470
|
+
const [syncing, setSyncing] = React13.useState(false);
|
|
5471
|
+
const [log, setLog] = React13.useState([]);
|
|
5472
|
+
const [loaded, setLoaded] = React13.useState(false);
|
|
5473
|
+
const [channels, setChannels] = React13.useState([]);
|
|
5474
|
+
const [showMapping, setShowMapping] = React13.useState(false);
|
|
5475
|
+
const [showWizard, setShowWizard] = React13.useState(false);
|
|
5228
5476
|
async function load() {
|
|
5229
5477
|
const integ = await provider.getIntegration();
|
|
5230
5478
|
setConnected(!!integ?.connected);
|
|
5231
5479
|
if (integ) {
|
|
5232
|
-
|
|
5480
|
+
setLastSyncAt(integ.lastSyncAt);
|
|
5233
5481
|
void provider.getSyncLog().then(setLog);
|
|
5482
|
+
void provider.listChannels().then((ch) => {
|
|
5483
|
+
setChannels(ch);
|
|
5484
|
+
if (ch.length > 0) setShowMapping(true);
|
|
5485
|
+
});
|
|
5234
5486
|
}
|
|
5235
5487
|
setLoaded(true);
|
|
5236
5488
|
}
|
|
5237
|
-
|
|
5489
|
+
React13.useEffect(() => {
|
|
5238
5490
|
void load();
|
|
5239
5491
|
}, []);
|
|
5240
|
-
if (!loaded
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
ui.toast.success("Saved");
|
|
5492
|
+
if (!loaded) return null;
|
|
5493
|
+
if (!connected) {
|
|
5494
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground border-t pt-4", children: "Connect a Google account above to map your calendars and import events." });
|
|
5244
5495
|
}
|
|
5245
5496
|
async function handleSyncNow() {
|
|
5246
5497
|
setSyncing(true);
|
|
@@ -5248,48 +5499,81 @@ function GoogleCalendarExtraPanel() {
|
|
|
5248
5499
|
const r = await provider.syncNow();
|
|
5249
5500
|
ui.toast.success(`Synced \u2014 ${r.written} updated`);
|
|
5250
5501
|
void provider.getSyncLog().then(setLog);
|
|
5502
|
+
void provider.getIntegration().then((i) => setLastSyncAt(i?.lastSyncAt));
|
|
5251
5503
|
} catch (e) {
|
|
5252
5504
|
ui.toast.error(e?.message ?? "Sync failed");
|
|
5253
5505
|
} finally {
|
|
5254
5506
|
setSyncing(false);
|
|
5255
5507
|
}
|
|
5256
5508
|
}
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5262
|
-
|
|
5263
|
-
{
|
|
5264
|
-
value: calendarId,
|
|
5265
|
-
onChange: (e) => setCalendarId(e.target.value),
|
|
5266
|
-
placeholder: "primary",
|
|
5267
|
-
className: "w-full mt-1 rounded-input border border-input bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
5268
|
-
}
|
|
5269
|
-
)
|
|
5509
|
+
const canImport = inboundChannels(channels).length > 0;
|
|
5510
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-5 border-t pt-4", children: [
|
|
5511
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-2", children: [
|
|
5512
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5513
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarCog, { className: "h-4 w-4 text-muted-foreground" }),
|
|
5514
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold", children: "Calendars" })
|
|
5270
5515
|
] }),
|
|
5271
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5272
|
-
|
|
5273
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5274
|
-
|
|
5516
|
+
showMapping ? /* @__PURE__ */ jsxRuntime.jsx(ChannelMappingList, { provider, onChannelsChange: setChannels }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border border-dashed px-4 py-5 text-center space-y-2", children: [
|
|
5517
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Map each Google calendar to what it syncs \u2014 a professional, service, location, or your whole agenda." }),
|
|
5518
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: () => setShowMapping(true), children: [
|
|
5519
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarCog, { className: "h-3.5 w-3.5" }),
|
|
5520
|
+
" Configure calendars"
|
|
5521
|
+
] })
|
|
5275
5522
|
] })
|
|
5276
5523
|
] }),
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5524
|
+
showMapping && /* @__PURE__ */ jsxRuntime.jsx("section", { className: "space-y-2", children: showWizard ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5525
|
+
ImportEventsWizard,
|
|
5526
|
+
{
|
|
5527
|
+
provider,
|
|
5528
|
+
channels: inboundChannels(channels),
|
|
5529
|
+
onClose: () => setShowWizard(false),
|
|
5530
|
+
onImported: () => {
|
|
5531
|
+
void provider.getSyncLog().then(setLog);
|
|
5532
|
+
void provider.getIntegration().then((i) => setLastSyncAt(i?.lastSyncAt));
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5536
|
+
ui.Button,
|
|
5537
|
+
{
|
|
5538
|
+
variant: "outline",
|
|
5539
|
+
size: "sm",
|
|
5540
|
+
disabled: !canImport,
|
|
5541
|
+
title: canImport ? void 0 : "Enable an importing calendar first",
|
|
5542
|
+
onClick: () => setShowWizard(true),
|
|
5543
|
+
children: [
|
|
5544
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarPlus, { className: "h-3.5 w-3.5" }),
|
|
5545
|
+
" Import future events"
|
|
5546
|
+
]
|
|
5547
|
+
}
|
|
5548
|
+
) }),
|
|
5549
|
+
/* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-2", children: [
|
|
5550
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
5551
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: handleSyncNow, disabled: syncing, children: [
|
|
5552
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `h-3.5 w-3.5 ${syncing ? "animate-spin" : ""}` }),
|
|
5553
|
+
" Sync now"
|
|
5554
|
+
] }),
|
|
5555
|
+
lastSyncAt && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
|
|
5556
|
+
"Last sync: ",
|
|
5557
|
+
new Date(lastSyncAt).toLocaleString()
|
|
5558
|
+
] })
|
|
5281
5559
|
] }),
|
|
5282
|
-
|
|
5283
|
-
/* @__PURE__ */ jsxRuntime.
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
r.written,
|
|
5287
|
-
" written / ",
|
|
5288
|
-
r.fetched,
|
|
5289
|
-
" fetched"
|
|
5560
|
+
log.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
5561
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5562
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.History, { className: "h-4 w-4 text-muted-foreground" }),
|
|
5563
|
+
/* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold", children: "History" })
|
|
5290
5564
|
] }),
|
|
5291
|
-
|
|
5292
|
-
|
|
5565
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border divide-y text-sm", children: log.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-3 py-2", children: [
|
|
5566
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: new Date(r.createdAt).toLocaleString() }),
|
|
5567
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] uppercase tracking-wide px-1 py-px rounded bg-muted text-muted-foreground", children: r.direction }),
|
|
5568
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-auto text-xs", children: [
|
|
5569
|
+
r.written,
|
|
5570
|
+
" written / ",
|
|
5571
|
+
r.fetched,
|
|
5572
|
+
" fetched"
|
|
5573
|
+
] }),
|
|
5574
|
+
r.status === "success" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
|
|
5575
|
+
] }, r.id)) })
|
|
5576
|
+
] })
|
|
5293
5577
|
] })
|
|
5294
5578
|
] });
|
|
5295
5579
|
}
|
|
@@ -5324,31 +5608,6 @@ var googleCalendarConnector = {
|
|
|
5324
5608
|
]
|
|
5325
5609
|
};
|
|
5326
5610
|
|
|
5327
|
-
// src/integrations/google-calendar/mapping.ts
|
|
5328
|
-
function bookingSummary(b) {
|
|
5329
|
-
const services = (b.services ?? []).map((s) => s.name).filter(Boolean).join(", ");
|
|
5330
|
-
const who = b.clientName ?? "";
|
|
5331
|
-
if (services && who) return `${services} \u2014 ${who}`;
|
|
5332
|
-
return services || who || "Appointment";
|
|
5333
|
-
}
|
|
5334
|
-
function bookingToEvent2(b, timeZone) {
|
|
5335
|
-
if (!b.startsAt || !b.endsAt) return null;
|
|
5336
|
-
const notes = [b.notes, b.professionalName ? `Pro: ${b.professionalName}` : "", b.locationName ?? ""].filter(Boolean).join("\n");
|
|
5337
|
-
return {
|
|
5338
|
-
summary: bookingSummary(b),
|
|
5339
|
-
description: notes || void 0,
|
|
5340
|
-
start: { dateTime: b.startsAt, timeZone },
|
|
5341
|
-
end: { dateTime: b.endsAt, timeZone }
|
|
5342
|
-
};
|
|
5343
|
-
}
|
|
5344
|
-
function eventToBookingPatch(ev) {
|
|
5345
|
-
if (ev.status === "cancelled") return { status: "cancelled" };
|
|
5346
|
-
return {
|
|
5347
|
-
startsAt: ev.start?.dateTime,
|
|
5348
|
-
endsAt: ev.end?.dateTime
|
|
5349
|
-
};
|
|
5350
|
-
}
|
|
5351
|
-
|
|
5352
5611
|
// src/integrations/google-calendar/index.ts
|
|
5353
5612
|
function createGoogleCalendarPlugin() {
|
|
5354
5613
|
return {
|
|
@@ -5370,8 +5629,8 @@ function createAgendaPlugin(options) {
|
|
|
5370
5629
|
const config = resolveConfig(options);
|
|
5371
5630
|
core.registerTranslations(agendaLocales);
|
|
5372
5631
|
const provider2 = options?.dataProvider ?? core.createSafeDataProvider(
|
|
5373
|
-
() => createSupabaseAgendaProvider(),
|
|
5374
|
-
() =>
|
|
5632
|
+
() => createSupabaseAgendaProvider({ professionalKind: config.professionalKind }),
|
|
5633
|
+
() => chunk3KWNHV5A_cjs.createMockAgendaProvider()
|
|
5375
5634
|
);
|
|
5376
5635
|
const store = createAgendaStore(provider2, options?.financialBridge);
|
|
5377
5636
|
const registries = [...agendaRegistries, ...options?.settingsRegistries ?? []];
|
|
@@ -5390,7 +5649,7 @@ function createAgendaPlugin(options) {
|
|
|
5390
5649
|
return locations;
|
|
5391
5650
|
} : void 0
|
|
5392
5651
|
});
|
|
5393
|
-
const PageComponent = () =>
|
|
5652
|
+
const PageComponent = () => React13__default.default.createElement(AgendaPage, { config, provider: provider2, store });
|
|
5394
5653
|
return {
|
|
5395
5654
|
id: "agenda",
|
|
5396
5655
|
name: config.labels.pageTitle,
|
|
@@ -5407,6 +5666,48 @@ function createAgendaPlugin(options) {
|
|
|
5407
5666
|
{ id: "appointments", label: config.labels.pageTitle, group: config.labels.pageTitle },
|
|
5408
5667
|
{ id: "agenda.schedules", label: "Schedule Management", group: config.labels.pageTitle }
|
|
5409
5668
|
],
|
|
5669
|
+
// Recurring monthly quota — counts appointment rows created this month.
|
|
5670
|
+
// (Clients/patients are the app's CRUD entity, so their cap is declared by
|
|
5671
|
+
// the app's EntityDef.limitKey, not here.)
|
|
5672
|
+
declaredLimits: [
|
|
5673
|
+
{ key: "bookings_month", label: "Appointments this month", table: "appointments", period: "month" }
|
|
5674
|
+
],
|
|
5675
|
+
queryEntities: [
|
|
5676
|
+
{
|
|
5677
|
+
key: "agenda:appointments",
|
|
5678
|
+
entity: {
|
|
5679
|
+
name: "Appointment",
|
|
5680
|
+
namePlural: "Appointments",
|
|
5681
|
+
icon: "Calendar",
|
|
5682
|
+
permission: { feature: "appointments", action: "read" },
|
|
5683
|
+
// Keys are the PROVIDER's output shape (camelCase) — what filters/
|
|
5684
|
+
// dateField/groupBy match against, not the raw DB columns.
|
|
5685
|
+
fields: [
|
|
5686
|
+
{ key: "startsAt", label: "Starts at", type: "text" },
|
|
5687
|
+
{ key: "endsAt", label: "Ends at", type: "text" },
|
|
5688
|
+
{ key: "status", label: "Status", type: "text" },
|
|
5689
|
+
{ key: "clientName", label: "Client", type: "text", searchable: true },
|
|
5690
|
+
{ key: "professionalName", label: "Professional", type: "text", searchable: true },
|
|
5691
|
+
{ key: "orderTotal", label: "Order total", type: "number" },
|
|
5692
|
+
{ key: "totalDurationMinutes", label: "Duration (min)", type: "number" },
|
|
5693
|
+
{ key: "createdAt", label: "Created at", type: "text" }
|
|
5694
|
+
],
|
|
5695
|
+
data: {
|
|
5696
|
+
table: "v_appointments",
|
|
5697
|
+
tenantScoped: true,
|
|
5698
|
+
searchColumns: ["client_name", "professional_name"]
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
],
|
|
5703
|
+
declaredRpcs: [
|
|
5704
|
+
{
|
|
5705
|
+
name: "agent_agenda_create_appointment",
|
|
5706
|
+
kind: "write",
|
|
5707
|
+
description: "Guarded appointment create: agent_guard (role\u2192plan\u2192bookings_month cap), working hours, race-safe conflict check, audited.",
|
|
5708
|
+
audits: true
|
|
5709
|
+
}
|
|
5710
|
+
],
|
|
5410
5711
|
navigation: [
|
|
5411
5712
|
{
|
|
5412
5713
|
section: options?.navSection ?? "main",
|
|
@@ -5421,7 +5722,9 @@ function createAgendaPlugin(options) {
|
|
|
5421
5722
|
{
|
|
5422
5723
|
path: "/agenda",
|
|
5423
5724
|
component: PageComponent,
|
|
5424
|
-
permission: { feature: "appointments", action: "read" }
|
|
5725
|
+
permission: { feature: "appointments", action: "read" },
|
|
5726
|
+
// Matches the ref agent_agenda_create_appointment returns.
|
|
5727
|
+
entityRoutes: ["schedule:appointment"]
|
|
5425
5728
|
}
|
|
5426
5729
|
],
|
|
5427
5730
|
widgets: [],
|
|
@@ -5450,20 +5753,22 @@ function createAgendaPlugin(options) {
|
|
|
5450
5753
|
{
|
|
5451
5754
|
id: "agenda.create-appointment",
|
|
5452
5755
|
name: "createAppointment",
|
|
5453
|
-
description: "Creates a new appointment
|
|
5756
|
+
description: "Creates a new appointment. Takes IDS, not names: resolve the client/service/professional ids first via findAnything/searchRecords, then call this with an ISO start time in the tenant timezone. The user confirms before booking.",
|
|
5454
5757
|
icon: "CalendarPlus",
|
|
5455
5758
|
mode: "persist",
|
|
5759
|
+
limitKey: "bookings_month",
|
|
5760
|
+
execution: { plane: "server", kind: "rpc", rpc: "agent_agenda_create_appointment" },
|
|
5456
5761
|
category: "Scheduling",
|
|
5457
5762
|
parameters: {
|
|
5458
5763
|
type: "object",
|
|
5459
5764
|
properties: {
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5765
|
+
client_id: { type: "string", description: "Client id (from a search)" },
|
|
5766
|
+
service_id: { type: "string", description: "Service id (from a search)" },
|
|
5767
|
+
professional_id: { type: "string", description: "Professional id (optional \u2014 auto-assigned when omitted)" },
|
|
5768
|
+
starts_at: { type: "string", description: "LOCAL date-time of the business, NO timezone suffix \u2014 e.g. '2026-07-27T10:00' for 10h. The system applies the tenant timezone." },
|
|
5769
|
+
notes: { type: "string", description: "Optional notes" }
|
|
5465
5770
|
},
|
|
5466
|
-
required: ["
|
|
5771
|
+
required: ["client_id", "service_id", "starts_at"]
|
|
5467
5772
|
},
|
|
5468
5773
|
suggestions: [
|
|
5469
5774
|
{ label: "Book a haircut for Sarah tomorrow at 10am" }
|
|
@@ -5501,9 +5806,9 @@ function createAgendaPlugin(options) {
|
|
|
5501
5806
|
icon: "Calendar",
|
|
5502
5807
|
// Standard layout: General | Integrations (Google Calendar addon) | Properties.
|
|
5503
5808
|
component: (() => {
|
|
5504
|
-
const AgendaSettingsTab = () =>
|
|
5809
|
+
const AgendaSettingsTab = () => React13__default.default.createElement(saas.PluginSettingsPanel, {
|
|
5505
5810
|
title: "Agenda",
|
|
5506
|
-
generalSettings:
|
|
5811
|
+
generalSettings: React13__default.default.createElement(AgendaGeneralSettings),
|
|
5507
5812
|
registries,
|
|
5508
5813
|
hostPluginId: "agenda",
|
|
5509
5814
|
routeBase: "/settings/agenda"
|
|
@@ -5529,7 +5834,7 @@ function createAgendaPlugin(options) {
|
|
|
5529
5834
|
|
|
5530
5835
|
Object.defineProperty(exports, "createMockAgendaProvider", {
|
|
5531
5836
|
enumerable: true,
|
|
5532
|
-
get: function () { return
|
|
5837
|
+
get: function () { return chunk3KWNHV5A_cjs.createMockAgendaProvider; }
|
|
5533
5838
|
});
|
|
5534
5839
|
exports.bookingToEvent = bookingToEvent2;
|
|
5535
5840
|
exports.computeCommissionAmount = computeCommissionAmount;
|