@fayz-ai/plugin-agenda 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AppointmentModal.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +456 -409
- package/dist/index.js.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 +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/CalendarView.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createMockAgendaProvider, createSupabasePublicBookingProvider } from '.
|
|
|
2
2
|
export { createMockAgendaProvider } from './chunk-ISLJXP7O.js';
|
|
3
3
|
import React13, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
4
4
|
import { connectionStatus, createConnectionStore, getSupabaseClientOptional, getActiveTenantId, registerTranslations, registerMessageDefaults, createSafeDataProvider, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, emitDataChanged, usePluginRuntimeOptional, useTranslation, emitDomainEvent, getCurrentLocale, useDataChanged, formatCurrency } from '@fayz-ai/core';
|
|
5
|
-
import { createPluginContext, fayzConnectorOAuth, setScheduleBlockConfig, getScheduleBlockConfig, PluginSettingsPanel, dedup, useTenantPluginSettings, SettingsGroup, ToggleRow, SelectRow, usePluginPrefs, PersonLink, useLimitGuard, CrudFormPage, CrudDetailPage, ContactPicker, invalidateLimit } from '@fayz-ai/admin';
|
|
5
|
+
import { createPluginContext, fayzConnectorOAuth, setScheduleBlockConfig, getScheduleBlockConfig, PluginSettingsPanel, dedup, useTenantPluginSettings, SettingsGroup, ToggleRow, SelectRow, usePluginPrefs, PersonLink, useLimitGuard, useLookupCreate, RightRailPage, CrudFormPage, CrudDetailPage, ContactPicker, invalidateLimit } from '@fayz-ai/admin';
|
|
6
6
|
import FullCalendar from '@fullcalendar/react';
|
|
7
7
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
|
8
8
|
import timeGridPlugin from '@fullcalendar/timegrid';
|
|
@@ -13,7 +13,7 @@ import ptBrLocale from '@fullcalendar/core/locales/pt-br';
|
|
|
13
13
|
import { Loader2, AlertCircle, RefreshCw, CalendarPlus, X, CheckCircle2, Plus, ChevronDown, ChevronLeft, ChevronRight, Settings, Pencil, Trash2, Clock, User, MapPin, FileText, AlertTriangle, Check, DollarSign, HandCoins, Ban, CheckSquare, Calendar, Briefcase, CircleDot, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, ExternalLink } from 'lucide-react';
|
|
14
14
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
15
15
|
import { createPortal } from 'react-dom';
|
|
16
|
-
import { toast, Button, Checkbox, Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, ConfirmDialog,
|
|
16
|
+
import { toast, Button, Checkbox, Badge, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, ConfirmDialog, SearchCombobox, cn, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '@fayz-ai/ui';
|
|
17
17
|
import { createStore } from 'zustand/vanilla';
|
|
18
18
|
|
|
19
19
|
// src/lib/message-events.ts
|
|
@@ -962,7 +962,7 @@ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPayment
|
|
|
962
962
|
children: /* @__PURE__ */ jsxs(
|
|
963
963
|
"div",
|
|
964
964
|
{
|
|
965
|
-
className: "w-full max-w-md rounded-
|
|
965
|
+
className: "w-full max-w-md rounded-modal border bg-card shadow-2xl mx-4 transition-all duration-200 max-h-[90vh] overflow-y-auto",
|
|
966
966
|
style: { opacity: visible ? 1 : 0, transform: visible ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)" },
|
|
967
967
|
onClick: (e) => e.stopPropagation(),
|
|
968
968
|
children: [
|
|
@@ -1269,6 +1269,17 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1269
1269
|
const [cancellationNotes, setCancellationNotes] = useState("");
|
|
1270
1270
|
const [cancellationReasonsLoading, setCancellationReasonsLoading] = useState(false);
|
|
1271
1271
|
const activeType = config.bookingTypes.find((bt) => bt.id === bookingType) ?? config.bookingTypes[0];
|
|
1272
|
+
const requestKey = [
|
|
1273
|
+
mode,
|
|
1274
|
+
bookingId ?? "",
|
|
1275
|
+
initialTab ?? "",
|
|
1276
|
+
prefill?.startsAt ?? "",
|
|
1277
|
+
prefill?.endsAt ?? "",
|
|
1278
|
+
prefill?.professionalId ?? "",
|
|
1279
|
+
prefill?.locationId ?? "",
|
|
1280
|
+
prefill?.clientId ?? "",
|
|
1281
|
+
prefill?.kind ?? ""
|
|
1282
|
+
].join("|");
|
|
1272
1283
|
useEffect(() => {
|
|
1273
1284
|
if (!open) return;
|
|
1274
1285
|
setClientId(prefill?.clientId ?? "");
|
|
@@ -1312,7 +1323,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1312
1323
|
}
|
|
1313
1324
|
return "09:00";
|
|
1314
1325
|
});
|
|
1315
|
-
}, [open]);
|
|
1326
|
+
}, [open, requestKey]);
|
|
1316
1327
|
useEffect(() => {
|
|
1317
1328
|
if (!open || mode !== "edit" || !bookingId) return;
|
|
1318
1329
|
setLoading(true);
|
|
@@ -1369,7 +1380,7 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1369
1380
|
}).finally(() => setPaymentStatusLoading(false));
|
|
1370
1381
|
}
|
|
1371
1382
|
}).catch(() => setLoading(false));
|
|
1372
|
-
}, [open,
|
|
1383
|
+
}, [open, requestKey]);
|
|
1373
1384
|
useEffect(() => {
|
|
1374
1385
|
if (modalTab !== "client" || !clientId || !config.contactLookup || clientData) return;
|
|
1375
1386
|
setClientDataLoading(true);
|
|
@@ -1444,6 +1455,21 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1444
1455
|
function removeService(idx) {
|
|
1445
1456
|
setServices((p) => p.filter((_, i) => i !== idx));
|
|
1446
1457
|
}
|
|
1458
|
+
const serviceCreate = useLookupCreate(
|
|
1459
|
+
config.serviceLookup,
|
|
1460
|
+
(created) => {
|
|
1461
|
+
const duration = created.data?.duration_minutes || 60;
|
|
1462
|
+
setServices((p) => [...p, {
|
|
1463
|
+
serviceId: created.id,
|
|
1464
|
+
name: created.label,
|
|
1465
|
+
durationMinutes: duration,
|
|
1466
|
+
price: created.price ?? 0
|
|
1467
|
+
}]);
|
|
1468
|
+
setServiceSearch("");
|
|
1469
|
+
setServiceOptions([]);
|
|
1470
|
+
},
|
|
1471
|
+
{ variant: "inline" }
|
|
1472
|
+
);
|
|
1447
1473
|
async function handleSubmit(e) {
|
|
1448
1474
|
e.preventDefault();
|
|
1449
1475
|
if (!canSubmit) return;
|
|
@@ -1513,431 +1539,442 @@ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, external
|
|
|
1513
1539
|
const isDirty = mode === "create" || formSnapshot !== initialSnapshot.current;
|
|
1514
1540
|
const canSubmit = (!activeType.fields.title || title.trim()) && (!activeType.requiresClient || clientId) && (!activeType.fields.professional || professionalId) && (!activeType.requiresServices || services.length > 0) && (!needsCancellationReason || !!cancellationReasonId) && (!needsCancellationNotes || !!cancellationNotes.trim()) && !conflict && !saving;
|
|
1515
1541
|
const showTabs = config.bookingTypes.length > 1;
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
value: date,
|
|
1571
|
-
autoFocus: true,
|
|
1572
|
-
onChange: (e) => {
|
|
1573
|
-
setDate(e.target.value);
|
|
1574
|
-
setEditingDate(false);
|
|
1575
|
-
},
|
|
1576
|
-
onBlur: () => setEditingDate(false),
|
|
1577
|
-
className: "bg-transparent text-sm font-medium focus:outline-none border-b border-foreground w-auto shrink-0"
|
|
1578
|
-
}
|
|
1579
|
-
) : /* @__PURE__ */ jsx(
|
|
1580
|
-
"button",
|
|
1581
|
-
{
|
|
1582
|
-
type: "button",
|
|
1583
|
-
onClick: () => setEditingDate(true),
|
|
1584
|
-
className: "px-0.5 py-0.5 font-medium border-b border-transparent hover:border-foreground transition-colors shrink-0",
|
|
1585
|
-
children: formatCompactDate(date, locale)
|
|
1586
|
-
}
|
|
1587
|
-
),
|
|
1588
|
-
editingTime ? /* @__PURE__ */ jsx(
|
|
1589
|
-
"input",
|
|
1590
|
-
{
|
|
1591
|
-
type: "time",
|
|
1592
|
-
value: startTime,
|
|
1593
|
-
autoFocus: true,
|
|
1594
|
-
onChange: (e) => {
|
|
1595
|
-
if (e.target.value) {
|
|
1596
|
-
setStartTime(e.target.value);
|
|
1597
|
-
setEditingTime(false);
|
|
1598
|
-
}
|
|
1599
|
-
},
|
|
1600
|
-
onBlur: () => setEditingTime(false),
|
|
1601
|
-
className: "bg-transparent text-xs text-muted-foreground focus:outline-none border-b border-foreground w-auto shrink-0"
|
|
1602
|
-
}
|
|
1603
|
-
) : /* @__PURE__ */ jsx(
|
|
1604
|
-
"button",
|
|
1605
|
-
{
|
|
1606
|
-
type: "button",
|
|
1607
|
-
onClick: () => setEditingTime(true),
|
|
1608
|
-
className: "px-0.5 py-0.5 text-xs text-muted-foreground border-b border-transparent hover:border-foreground transition-colors shrink-0",
|
|
1609
|
-
children: startTime
|
|
1610
|
-
}
|
|
1611
|
-
),
|
|
1612
|
-
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground/60", children: "\u2014" }),
|
|
1613
|
-
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground/60", children: endTime }),
|
|
1614
|
-
activeType.fields.professional && /* @__PURE__ */ jsx("div", { className: "ml-auto shrink-0", children: /* @__PURE__ */ jsx(ProfessionalSelect, { value: professionalId, onChange: setProfessionalId, professionals }) })
|
|
1615
|
-
] })
|
|
1616
|
-
] }),
|
|
1617
|
-
conflict && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-destructive/10 px-3 py-1.5 text-xs text-destructive", children: [
|
|
1618
|
-
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
1619
|
-
t2("agenda.appointment.conflict")
|
|
1620
|
-
] })
|
|
1621
|
-
] })
|
|
1622
|
-
] }),
|
|
1623
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-hidden", children: [
|
|
1624
|
-
modalTab === "client" && clientId && config.clientEntityDef && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-5 py-4", children: clientDataLoading ? /* @__PURE__ */ jsx("div", { className: "space-y-3 py-2", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-8 rounded-lg bg-muted animate-pulse" }, i)) }) : clientData ? clientEditing ? /* @__PURE__ */ jsx(
|
|
1625
|
-
CrudFormPage,
|
|
1626
|
-
{
|
|
1627
|
-
embedded: true,
|
|
1628
|
-
entityDef: config.clientEntityDef,
|
|
1629
|
-
mode: "edit",
|
|
1630
|
-
initialData: clientData,
|
|
1631
|
-
namePlural: "",
|
|
1632
|
-
onCancel: () => setClientEditing(false),
|
|
1633
|
-
onSubmit: async (data) => {
|
|
1634
|
-
try {
|
|
1635
|
-
const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
|
|
1636
|
-
const supabase = getSupabaseClientOptional4();
|
|
1637
|
-
if (!supabase) return;
|
|
1638
|
-
const dbData = {};
|
|
1639
|
-
for (const [k, v] of Object.entries(data)) {
|
|
1640
|
-
dbData[k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase())] = v;
|
|
1641
|
-
}
|
|
1642
|
-
await supabase.from("people").update(dbData).eq("id", clientId);
|
|
1643
|
-
setClientData({ ...clientData, ...data });
|
|
1644
|
-
setClientEditing(false);
|
|
1645
|
-
} catch {
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
) : /* @__PURE__ */ jsx(
|
|
1650
|
-
CrudDetailPage,
|
|
1651
|
-
{
|
|
1652
|
-
embedded: true,
|
|
1653
|
-
entityDef: config.clientEntityDef,
|
|
1654
|
-
item: clientData,
|
|
1655
|
-
namePlural: "",
|
|
1656
|
-
basePath: "",
|
|
1657
|
-
onBack: () => setModalTab("appointment"),
|
|
1658
|
-
onEdit: () => setClientEditing(true)
|
|
1659
|
-
}
|
|
1660
|
-
) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: t2("agenda.appointment.noClientData") }) }),
|
|
1661
|
-
modalTab === "financial" && editOrderId && config.financialBridge && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(
|
|
1662
|
-
BookingPaymentPanel,
|
|
1663
|
-
{
|
|
1664
|
-
orderId: editOrderId,
|
|
1665
|
-
orderTotal: editOrderTotal,
|
|
1666
|
-
services: services.map((s) => ({ name: s.name, price: s.price, durationMinutes: s.durationMinutes })),
|
|
1667
|
-
formatCurrency: (v) => fmtCurrency(v, currency.locale, currency.code),
|
|
1668
|
-
inline: true,
|
|
1669
|
-
onClose: () => setModalTab("appointment"),
|
|
1670
|
-
onPaymentChange: () => {
|
|
1671
|
-
config.financialBridge.getPaymentDetail(editOrderId).then((d) => {
|
|
1672
|
-
if (d) {
|
|
1673
|
-
setEditPaymentStatus(d.status);
|
|
1674
|
-
setEditOrderTotal(d.totalAmount);
|
|
1675
|
-
}
|
|
1676
|
-
}).catch(() => {
|
|
1677
|
-
});
|
|
1678
|
-
if (agendaDateRange.start) fetchBookings(agendaDateRange);
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
) }),
|
|
1682
|
-
/* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col flex-1 min-h-0 overflow-hidden", style: { display: modalTab === "appointment" ? void 0 : "none" }, children: [
|
|
1683
|
-
/* @__PURE__ */ jsxs("div", { className: "border-t fayz-glass-divider px-5 py-3 space-y-1 flex-1 overflow-y-auto", children: [
|
|
1684
|
-
loading && /* @__PURE__ */ jsx("div", { className: "space-y-2 py-2", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1.5", children: [
|
|
1685
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded bg-muted animate-pulse shrink-0" }),
|
|
1686
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 flex-1 rounded-lg bg-muted animate-pulse" })
|
|
1687
|
-
] }, i)) }),
|
|
1688
|
-
!loading && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1689
|
-
activeType.fields.title && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1690
|
-
/* @__PURE__ */ jsx(Calendar, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1691
|
-
/* @__PURE__ */ jsx(
|
|
1542
|
+
const railTitle = mode === "create" ? t2("agenda.appointment.new") : title.trim() || clientSearch || t2("agenda.appointment.edit");
|
|
1543
|
+
return (
|
|
1544
|
+
// A booking is written next to the calendar it belongs to, not on top of
|
|
1545
|
+
// it: the dialog used to dim the week you were reading while you filled in
|
|
1546
|
+
// a slot from it, and a second panel opening from the same edge as the
|
|
1547
|
+
// assistant meant two vocabularies for one side of the screen.
|
|
1548
|
+
/* @__PURE__ */ jsxs(RightRailPage, { title: railTitle, open, onClose, width: "max-w-lg", children: [
|
|
1549
|
+
/* @__PURE__ */ jsxs("div", { className: "px-5 pt-3 pb-3 shrink-0 space-y-3", children: [
|
|
1550
|
+
showTabs && mode === "create" ? /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: config.bookingTypes.map((bt) => {
|
|
1551
|
+
const label = t2(`agenda.bookingType.${bt.id}`) || bt.label;
|
|
1552
|
+
const isActive = bookingType === bt.id;
|
|
1553
|
+
const BtIcon = BOOKING_TYPE_ICONS[bt.icon] ?? Calendar;
|
|
1554
|
+
return /* @__PURE__ */ jsxs(
|
|
1555
|
+
"button",
|
|
1556
|
+
{
|
|
1557
|
+
type: "button",
|
|
1558
|
+
onClick: () => setBookingType(bt.id),
|
|
1559
|
+
className: `relative flex items-center gap-1.5 pb-2 px-2 text-xs font-medium transition-colors ${isActive ? "text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
1560
|
+
children: [
|
|
1561
|
+
/* @__PURE__ */ jsx(BtIcon, { className: "h-3.5 w-3.5" }),
|
|
1562
|
+
label,
|
|
1563
|
+
isActive && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 left-0 right-0 h-0.5 rounded-full bg-foreground" })
|
|
1564
|
+
]
|
|
1565
|
+
},
|
|
1566
|
+
bt.id
|
|
1567
|
+
);
|
|
1568
|
+
}) }) : mode === "edit" && (config.clientEntityDef && clientId || config.modules.financial && editOrderId ? /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: [
|
|
1569
|
+
{ id: "appointment", label: t2(`agenda.bookingType.${bookingType}`) || activeType.label, icon: Calendar, show: true },
|
|
1570
|
+
{ id: "client", label: t2("agenda.appointment.clientTab"), icon: User, show: !!config.clientEntityDef && !!clientId },
|
|
1571
|
+
{ id: "financial", label: t2("agenda.payment.title"), icon: DollarSign, show: config.modules.financial && !!editOrderId }
|
|
1572
|
+
].filter((tab) => tab.show).map((tab) => {
|
|
1573
|
+
const isActive = modalTab === tab.id;
|
|
1574
|
+
return /* @__PURE__ */ jsxs(
|
|
1575
|
+
"button",
|
|
1576
|
+
{
|
|
1577
|
+
type: "button",
|
|
1578
|
+
onClick: () => setModalTab(tab.id),
|
|
1579
|
+
className: `relative flex items-center gap-1.5 pb-2 px-2 text-xs font-medium transition-colors ${isActive ? "text-foreground" : "text-muted-foreground hover:text-foreground"}`,
|
|
1580
|
+
children: [
|
|
1581
|
+
/* @__PURE__ */ jsx(tab.icon, { className: "h-3.5 w-3.5" }),
|
|
1582
|
+
tab.label,
|
|
1583
|
+
tab.id === "financial" && editPaymentStatus === "paid" && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-success" }),
|
|
1584
|
+
tab.id === "financial" && editPaymentStatus !== "paid" && (editPaymentStatus !== "none" || totalPrice > 0 && date <= (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) && /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-warning" }),
|
|
1585
|
+
isActive && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 left-0 right-0 h-0.5 rounded-full bg-foreground" })
|
|
1586
|
+
]
|
|
1587
|
+
},
|
|
1588
|
+
tab.id
|
|
1589
|
+
);
|
|
1590
|
+
}) }) : null),
|
|
1591
|
+
modalTab === "appointment" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1592
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1593
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1594
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1.5 gap-y-1 text-sm flex-1 min-w-0", children: [
|
|
1595
|
+
editingDate ? /* @__PURE__ */ jsx(
|
|
1692
1596
|
"input",
|
|
1693
1597
|
{
|
|
1694
|
-
type: "
|
|
1695
|
-
value:
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1598
|
+
type: "date",
|
|
1599
|
+
value: date,
|
|
1600
|
+
autoFocus: true,
|
|
1601
|
+
onChange: (e) => {
|
|
1602
|
+
setDate(e.target.value);
|
|
1603
|
+
setEditingDate(false);
|
|
1604
|
+
},
|
|
1605
|
+
onBlur: () => setEditingDate(false),
|
|
1606
|
+
className: "bg-transparent text-sm font-medium focus:outline-none border-b border-foreground w-auto shrink-0"
|
|
1700
1607
|
}
|
|
1701
|
-
)
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1608
|
+
) : /* @__PURE__ */ jsx(
|
|
1609
|
+
"button",
|
|
1610
|
+
{
|
|
1611
|
+
type: "button",
|
|
1612
|
+
onClick: () => setEditingDate(true),
|
|
1613
|
+
className: "px-0.5 py-0.5 font-medium border-b border-transparent hover:border-foreground transition-colors shrink-0",
|
|
1614
|
+
children: formatCompactDate(date, locale)
|
|
1615
|
+
}
|
|
1616
|
+
),
|
|
1617
|
+
editingTime ? /* @__PURE__ */ jsx(
|
|
1618
|
+
"input",
|
|
1707
1619
|
{
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1620
|
+
type: "time",
|
|
1621
|
+
value: startTime,
|
|
1622
|
+
autoFocus: true,
|
|
1623
|
+
onChange: (e) => {
|
|
1624
|
+
if (e.target.value) {
|
|
1625
|
+
setStartTime(e.target.value);
|
|
1626
|
+
setEditingTime(false);
|
|
1627
|
+
}
|
|
1713
1628
|
},
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
lookup: config.contactLookup,
|
|
1717
|
-
clearable: !isPaidBooking,
|
|
1718
|
-
variant: "inline",
|
|
1719
|
-
placeholder: t2("agenda.appointment.searchClient"),
|
|
1720
|
-
createLabel: t2("agenda.appointment.newClient")
|
|
1629
|
+
onBlur: () => setEditingTime(false),
|
|
1630
|
+
className: "bg-transparent text-xs text-muted-foreground focus:outline-none border-b border-foreground w-auto shrink-0"
|
|
1721
1631
|
}
|
|
1722
|
-
)
|
|
1723
|
-
|
|
1724
|
-
activeType.fields.location && config.modules.locationSelection && (storeLocations.length > 0 || config.locations.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1725
|
-
/* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1726
|
-
/* @__PURE__ */ jsx(
|
|
1727
|
-
LocationSelect,
|
|
1632
|
+
) : /* @__PURE__ */ jsx(
|
|
1633
|
+
"button",
|
|
1728
1634
|
{
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1635
|
+
type: "button",
|
|
1636
|
+
onClick: () => setEditingTime(true),
|
|
1637
|
+
className: "px-0.5 py-0.5 text-xs text-muted-foreground border-b border-transparent hover:border-foreground transition-colors shrink-0",
|
|
1638
|
+
children: startTime
|
|
1732
1639
|
}
|
|
1733
|
-
)
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
/* @__PURE__ */ jsx(
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1640
|
+
),
|
|
1641
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground/60", children: "\u2014" }),
|
|
1642
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground/60", children: endTime }),
|
|
1643
|
+
activeType.fields.professional && /* @__PURE__ */ jsx("div", { className: "ml-auto shrink-0", children: /* @__PURE__ */ jsx(ProfessionalSelect, { value: professionalId, onChange: setProfessionalId, professionals }) })
|
|
1644
|
+
] })
|
|
1645
|
+
] }),
|
|
1646
|
+
conflict && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-destructive/10 px-3 py-1.5 text-xs text-destructive", children: [
|
|
1647
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
1648
|
+
t2("agenda.appointment.conflict")
|
|
1649
|
+
] })
|
|
1650
|
+
] })
|
|
1651
|
+
] }),
|
|
1652
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-hidden", children: [
|
|
1653
|
+
modalTab === "client" && clientId && config.clientEntityDef && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-5 py-4", children: clientDataLoading ? /* @__PURE__ */ jsx("div", { className: "space-y-3 py-2", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-8 rounded-lg bg-muted animate-pulse" }, i)) }) : clientData ? clientEditing ? /* @__PURE__ */ jsx(
|
|
1654
|
+
CrudFormPage,
|
|
1655
|
+
{
|
|
1656
|
+
embedded: true,
|
|
1657
|
+
entityDef: config.clientEntityDef,
|
|
1658
|
+
mode: "edit",
|
|
1659
|
+
initialData: clientData,
|
|
1660
|
+
namePlural: "",
|
|
1661
|
+
onCancel: () => setClientEditing(false),
|
|
1662
|
+
onSubmit: async (data) => {
|
|
1663
|
+
try {
|
|
1664
|
+
const { getSupabaseClientOptional: getSupabaseClientOptional4 } = await import('@fayz-ai/core');
|
|
1665
|
+
const supabase = getSupabaseClientOptional4();
|
|
1666
|
+
if (!supabase) return;
|
|
1667
|
+
const dbData = {};
|
|
1668
|
+
for (const [k, v] of Object.entries(data)) {
|
|
1669
|
+
dbData[k.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase())] = v;
|
|
1670
|
+
}
|
|
1671
|
+
await supabase.from("people").update(dbData).eq("id", clientId);
|
|
1672
|
+
setClientData({ ...clientData, ...data });
|
|
1673
|
+
setClientEditing(false);
|
|
1674
|
+
} catch {
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
) : /* @__PURE__ */ jsx(
|
|
1679
|
+
CrudDetailPage,
|
|
1680
|
+
{
|
|
1681
|
+
embedded: true,
|
|
1682
|
+
entityDef: config.clientEntityDef,
|
|
1683
|
+
item: clientData,
|
|
1684
|
+
namePlural: "",
|
|
1685
|
+
basePath: "",
|
|
1686
|
+
onBack: () => setModalTab("appointment"),
|
|
1687
|
+
onEdit: () => setClientEditing(true)
|
|
1688
|
+
}
|
|
1689
|
+
) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: t2("agenda.appointment.noClientData") }) }),
|
|
1690
|
+
modalTab === "financial" && editOrderId && config.financialBridge && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(
|
|
1691
|
+
BookingPaymentPanel,
|
|
1692
|
+
{
|
|
1693
|
+
orderId: editOrderId,
|
|
1694
|
+
orderTotal: editOrderTotal,
|
|
1695
|
+
services: services.map((s) => ({ name: s.name, price: s.price, durationMinutes: s.durationMinutes })),
|
|
1696
|
+
formatCurrency: (v) => fmtCurrency(v, currency.locale, currency.code),
|
|
1697
|
+
inline: true,
|
|
1698
|
+
onClose: () => setModalTab("appointment"),
|
|
1699
|
+
onPaymentChange: () => {
|
|
1700
|
+
config.financialBridge.getPaymentDetail(editOrderId).then((d) => {
|
|
1701
|
+
if (d) {
|
|
1702
|
+
setEditPaymentStatus(d.status);
|
|
1703
|
+
setEditOrderTotal(d.totalAmount);
|
|
1704
|
+
}
|
|
1705
|
+
}).catch(() => {
|
|
1706
|
+
});
|
|
1707
|
+
if (agendaDateRange.start) fetchBookings(agendaDateRange);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
) }),
|
|
1711
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col flex-1 min-h-0 overflow-hidden", style: { display: modalTab === "appointment" ? void 0 : "none" }, children: [
|
|
1712
|
+
/* @__PURE__ */ jsxs("div", { className: "border-t fayz-glass-divider px-5 py-3 space-y-1 flex-1 overflow-y-auto", children: [
|
|
1713
|
+
loading && /* @__PURE__ */ jsx("div", { className: "space-y-2 py-2", children: [...Array(4)].map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1.5", children: [
|
|
1714
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded bg-muted animate-pulse shrink-0" }),
|
|
1715
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 flex-1 rounded-lg bg-muted animate-pulse" })
|
|
1716
|
+
] }, i)) }),
|
|
1717
|
+
!loading && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1718
|
+
activeType.fields.title && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1719
|
+
/* @__PURE__ */ jsx(Calendar, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1720
|
+
/* @__PURE__ */ jsx(
|
|
1721
|
+
"input",
|
|
1774
1722
|
{
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1723
|
+
type: "text",
|
|
1724
|
+
value: title,
|
|
1725
|
+
onChange: (e) => setTitle(e.target.value),
|
|
1726
|
+
placeholder: t2("agenda.appointment.addTitle"),
|
|
1727
|
+
autoFocus: mode === "create",
|
|
1728
|
+
"aria-required": true,
|
|
1729
|
+
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"
|
|
1730
|
+
}
|
|
1731
|
+
)
|
|
1732
|
+
] }),
|
|
1733
|
+
activeType.fields.client && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1734
|
+
/* @__PURE__ */ jsx(User, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1735
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
|
|
1736
|
+
ContactPicker,
|
|
1737
|
+
{
|
|
1738
|
+
value: clientId || clientSearch ? { id: clientId || void 0, name: clientSearch, phone: clientPhone } : null,
|
|
1739
|
+
onChange: (contact) => {
|
|
1740
|
+
setClientId(contact?.id ?? "");
|
|
1741
|
+
setClientSearch(contact?.name ?? "");
|
|
1742
|
+
setClientPhone(contact?.phone ?? "");
|
|
1794
1743
|
},
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1744
|
+
kind: config.clientKind,
|
|
1745
|
+
lookup: config.contactLookup,
|
|
1746
|
+
createEntityDef: config.clientEntityDef,
|
|
1747
|
+
clearable: !isPaidBooking,
|
|
1748
|
+
variant: "inline",
|
|
1749
|
+
placeholder: t2("agenda.appointment.searchClient"),
|
|
1750
|
+
createLabel: t2("agenda.appointment.newClient"),
|
|
1751
|
+
required: activeType.requiresClient
|
|
1752
|
+
}
|
|
1753
|
+
) })
|
|
1754
|
+
] }),
|
|
1755
|
+
activeType.fields.location && config.modules.locationSelection && (storeLocations.length > 0 || config.locations.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1756
|
+
/* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1757
|
+
/* @__PURE__ */ jsx(
|
|
1758
|
+
LocationSelect,
|
|
1759
|
+
{
|
|
1760
|
+
value: locationId,
|
|
1761
|
+
onChange: setLocationId,
|
|
1762
|
+
locations: storeLocations.length > 0 ? storeLocations : config.locations
|
|
1798
1763
|
}
|
|
1799
1764
|
)
|
|
1800
|
-
] })
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1765
|
+
] }),
|
|
1766
|
+
activeType.fields.services && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 py-1", children: [
|
|
1767
|
+
/* @__PURE__ */ jsx(Briefcase, { className: "h-4 w-4 mt-1.5 text-muted-foreground shrink-0" }),
|
|
1768
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
1769
|
+
services.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-0.5 mb-1", children: services.map((svc, idx) => /* @__PURE__ */ jsxs("div", { className: "flex items-center rounded-md bg-muted/30 px-2.5 py-1.5 text-sm", children: [
|
|
1770
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate min-w-0", children: svc.name }),
|
|
1771
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 shrink-0 ml-3", children: [
|
|
1772
|
+
/* @__PURE__ */ jsx(
|
|
1773
|
+
InlineNumber,
|
|
1774
|
+
{
|
|
1775
|
+
value: svc.durationMinutes,
|
|
1776
|
+
onChange: (v) => setServices((p) => p.map((s, i) => i === idx ? { ...s, durationMinutes: Math.round(v) } : s)),
|
|
1777
|
+
min: 5,
|
|
1778
|
+
step: 5,
|
|
1779
|
+
className: "w-10 shrink-0",
|
|
1780
|
+
disabled: isPaidBooking,
|
|
1781
|
+
tooltip: t2("agenda.appointment.duration")
|
|
1782
|
+
}
|
|
1783
|
+
),
|
|
1784
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "min" })
|
|
1785
|
+
] }),
|
|
1786
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 shrink-0 ml-3", children: [
|
|
1787
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: currency.symbol }),
|
|
1788
|
+
/* @__PURE__ */ jsx(
|
|
1789
|
+
InlineNumber,
|
|
1790
|
+
{
|
|
1791
|
+
value: svc.price,
|
|
1792
|
+
onChange: (v) => setServices((p) => p.map((s, i) => i === idx ? { ...s, price: v } : s)),
|
|
1793
|
+
min: 0,
|
|
1794
|
+
step: 0.01,
|
|
1795
|
+
className: "w-14 shrink-0",
|
|
1796
|
+
disabled: isPaidBooking,
|
|
1797
|
+
tooltip: t2("agenda.appointment.price")
|
|
1798
|
+
}
|
|
1799
|
+
)
|
|
1800
|
+
] }),
|
|
1801
|
+
!isPaidBooking && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => removeService(idx), className: "p-0.5 ml-2 text-muted-foreground hover:text-destructive shrink-0", children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" }) })
|
|
1802
|
+
] }, idx)) }),
|
|
1803
|
+
serviceCreate.creating && serviceCreate.form,
|
|
1804
|
+
!isPaidBooking && !serviceCreate.creating && /* @__PURE__ */ jsx(
|
|
1805
|
+
SearchCombobox,
|
|
1806
|
+
{
|
|
1807
|
+
value: serviceSearch,
|
|
1808
|
+
onChange: setServiceSearch,
|
|
1809
|
+
onSelect: addService,
|
|
1810
|
+
options: serviceOptions,
|
|
1811
|
+
placeholder: t2("agenda.appointment.searchService"),
|
|
1812
|
+
onBlurEmpty: () => {
|
|
1813
|
+
},
|
|
1814
|
+
onFocus: () => {
|
|
1815
|
+
if (!serviceSearch && serviceOptions.length === 0) setServiceOptions(initialServiceOptions);
|
|
1816
|
+
},
|
|
1817
|
+
renderRight: (opt) => {
|
|
1818
|
+
const dur = opt.data?.duration_minutes || 0;
|
|
1819
|
+
const hasPrice = opt.price != null;
|
|
1820
|
+
if (!hasPrice && !dur) return null;
|
|
1821
|
+
return /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs whitespace-nowrap", children: [
|
|
1822
|
+
hasPrice && fmtCurrency(opt.price, currency.locale, currency.code),
|
|
1823
|
+
hasPrice && dur > 0 && " \xB7 ",
|
|
1824
|
+
dur > 0 && `${dur}min`
|
|
1825
|
+
] });
|
|
1826
|
+
},
|
|
1827
|
+
minimal: true,
|
|
1828
|
+
chevron: true,
|
|
1829
|
+
required: activeType.requiresServices && services.length === 0,
|
|
1830
|
+
allowCreate: !!serviceCreate.onCreate,
|
|
1831
|
+
onCreateNew: serviceCreate.onCreate,
|
|
1832
|
+
createLabel: t2("agenda.appointment.newService"),
|
|
1833
|
+
inlineLabel: services.length > 0 ? t2("agenda.appointment.addService") : t2("agenda.appointment.addServiceFirst"),
|
|
1834
|
+
inlineIcon: services.length > 0 ? "plus" : "search"
|
|
1835
|
+
}
|
|
1836
|
+
)
|
|
1837
|
+
] })
|
|
1838
|
+
] }),
|
|
1839
|
+
activeType.fields.status && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1840
|
+
/* @__PURE__ */ jsx(CircleDot, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1841
|
+
/* @__PURE__ */ jsx(StatusSelect, { value: status, onChange: setStatus, statuses: config.statuses, bookingDate: date })
|
|
1842
|
+
] }),
|
|
1843
|
+
needsCancellationReason && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 py-1", children: [
|
|
1844
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-4 w-4 mt-2 text-muted-foreground shrink-0" }),
|
|
1845
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
|
|
1846
|
+
/* @__PURE__ */ jsxs(
|
|
1847
|
+
"select",
|
|
1848
|
+
{
|
|
1849
|
+
value: cancellationReasonId,
|
|
1850
|
+
onChange: (event) => setCancellationReasonId(event.target.value),
|
|
1851
|
+
disabled: cancellationReasonsLoading,
|
|
1852
|
+
"aria-required": true,
|
|
1853
|
+
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",
|
|
1854
|
+
children: [
|
|
1855
|
+
/* @__PURE__ */ jsx("option", { value: "", children: cancellationReasonsLoading ? t2("agenda.cancellation.loadingReasons") : t2("agenda.cancellation.selectReason") }),
|
|
1856
|
+
cancellationReasons.map((reason) => /* @__PURE__ */ jsxs("option", { value: reason.id, children: [
|
|
1857
|
+
reason.label,
|
|
1858
|
+
reason.requiresNotes ? " *" : ""
|
|
1859
|
+
] }, reason.id))
|
|
1860
|
+
]
|
|
1861
|
+
}
|
|
1862
|
+
),
|
|
1863
|
+
/* @__PURE__ */ jsx(
|
|
1864
|
+
"textarea",
|
|
1865
|
+
{
|
|
1866
|
+
value: cancellationNotes,
|
|
1867
|
+
onChange: (event) => setCancellationNotes(event.target.value),
|
|
1868
|
+
placeholder: needsCancellationNotes ? t2("agenda.cancellation.notesRequired") : t2("agenda.cancellation.notesOptional"),
|
|
1869
|
+
rows: 2,
|
|
1870
|
+
"aria-required": needsCancellationNotes || void 0,
|
|
1871
|
+
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"
|
|
1872
|
+
}
|
|
1873
|
+
)
|
|
1874
|
+
] })
|
|
1875
|
+
] }),
|
|
1876
|
+
!showNotes ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1877
|
+
/* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1878
|
+
/* @__PURE__ */ jsx(
|
|
1879
|
+
"button",
|
|
1811
1880
|
{
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
children: [
|
|
1817
|
-
/* @__PURE__ */ jsx("option", { value: "", children: cancellationReasonsLoading ? t2("agenda.cancellation.loadingReasons") : t2("agenda.cancellation.selectReason") }),
|
|
1818
|
-
cancellationReasons.map((reason) => /* @__PURE__ */ jsxs("option", { value: reason.id, children: [
|
|
1819
|
-
reason.label,
|
|
1820
|
-
reason.requiresNotes ? " *" : ""
|
|
1821
|
-
] }, reason.id))
|
|
1822
|
-
]
|
|
1881
|
+
type: "button",
|
|
1882
|
+
onClick: () => setShowNotes(true),
|
|
1883
|
+
className: "text-sm text-muted-foreground hover:text-foreground transition-colors text-left py-1.5 pl-2",
|
|
1884
|
+
children: t2("agenda.appointment.addNotes")
|
|
1823
1885
|
}
|
|
1824
|
-
)
|
|
1886
|
+
)
|
|
1887
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 py-1", children: [
|
|
1888
|
+
/* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 mt-2 text-muted-foreground shrink-0" }),
|
|
1825
1889
|
/* @__PURE__ */ jsx(
|
|
1826
1890
|
"textarea",
|
|
1827
1891
|
{
|
|
1828
|
-
value:
|
|
1829
|
-
onChange: (
|
|
1830
|
-
placeholder:
|
|
1892
|
+
value: notes,
|
|
1893
|
+
onChange: (e) => setNotes(e.target.value),
|
|
1894
|
+
placeholder: t2("agenda.appointment.addDescription"),
|
|
1831
1895
|
rows: 2,
|
|
1832
|
-
|
|
1896
|
+
autoFocus: true,
|
|
1897
|
+
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"
|
|
1833
1898
|
}
|
|
1834
1899
|
)
|
|
1835
1900
|
] })
|
|
1836
|
-
] }),
|
|
1837
|
-
!showNotes ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 py-1", children: [
|
|
1838
|
-
/* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
|
|
1839
|
-
/* @__PURE__ */ jsx(
|
|
1840
|
-
"button",
|
|
1841
|
-
{
|
|
1842
|
-
type: "button",
|
|
1843
|
-
onClick: () => setShowNotes(true),
|
|
1844
|
-
className: "text-sm text-muted-foreground hover:text-foreground transition-colors text-left py-1.5 pl-2",
|
|
1845
|
-
children: t2("agenda.appointment.addNotes")
|
|
1846
|
-
}
|
|
1847
|
-
)
|
|
1848
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 py-1", children: [
|
|
1849
|
-
/* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 mt-2 text-muted-foreground shrink-0" }),
|
|
1850
|
-
/* @__PURE__ */ jsx(
|
|
1851
|
-
"textarea",
|
|
1852
|
-
{
|
|
1853
|
-
value: notes,
|
|
1854
|
-
onChange: (e) => setNotes(e.target.value),
|
|
1855
|
-
placeholder: t2("agenda.appointment.addDescription"),
|
|
1856
|
-
rows: 2,
|
|
1857
|
-
autoFocus: true,
|
|
1858
|
-
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"
|
|
1859
|
-
}
|
|
1860
|
-
)
|
|
1861
|
-
] })
|
|
1862
|
-
] })
|
|
1863
|
-
] }),
|
|
1864
|
-
modalTab === "appointment" && !clientEditing && /* @__PURE__ */ jsx("div", { className: "px-5 py-2.5 border-t fayz-glass-divider bg-transparent shrink-0 sm:rounded-b-2xl", children: confirmingDelete ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1865
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium", children: t2("agenda.appointment.deleteConfirm") }),
|
|
1866
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1867
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setConfirmingDelete(false), children: t2("agenda.appointment.cancel") }),
|
|
1868
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", size: "sm", onClick: handleDelete, disabled: deleting, children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete") })
|
|
1869
|
-
] })
|
|
1870
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1871
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1872
|
-
mode === "edit" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "text-xs text-destructive font-medium hover:underline", children: t2("agenda.appointment.delete") }),
|
|
1873
|
-
services.length > 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1874
|
-
/* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
|
|
1875
|
-
totalDuration,
|
|
1876
|
-
"min",
|
|
1877
|
-
config.modules.financial && totalPrice > 0 && mode === "edit" ? paymentStatusLoading ? /* @__PURE__ */ jsx("span", { className: "h-4 w-16 rounded-full bg-muted animate-pulse" }) : isPaidBooking ? /* @__PURE__ */ jsxs(
|
|
1878
|
-
"button",
|
|
1879
|
-
{
|
|
1880
|
-
type: "button",
|
|
1881
|
-
onClick: (e) => {
|
|
1882
|
-
e.stopPropagation();
|
|
1883
|
-
setModalTab("financial");
|
|
1884
|
-
},
|
|
1885
|
-
className: "inline-flex items-center gap-1 rounded-full bg-success/10 px-2 py-0.5 text-[10px] font-medium text-success hover:bg-success/20 transition-colors",
|
|
1886
|
-
children: [
|
|
1887
|
-
/* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }),
|
|
1888
|
-
fmtCurrency(totalPrice, currency.locale, currency.code),
|
|
1889
|
-
" \xB7 ",
|
|
1890
|
-
t2("agenda.payment.paid")
|
|
1891
|
-
]
|
|
1892
|
-
}
|
|
1893
|
-
) : date <= (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) ? /* @__PURE__ */ jsxs(
|
|
1894
|
-
"button",
|
|
1895
|
-
{
|
|
1896
|
-
type: "button",
|
|
1897
|
-
onClick: (e) => {
|
|
1898
|
-
e.stopPropagation();
|
|
1899
|
-
setModalTab("financial");
|
|
1900
|
-
},
|
|
1901
|
-
className: "inline-flex items-center gap-1 rounded-full bg-warning/10 px-2 py-0.5 text-[10px] font-medium text-warning hover:bg-warning/20 transition-colors",
|
|
1902
|
-
children: [
|
|
1903
|
-
/* @__PURE__ */ jsx(HandCoins, { className: "h-3 w-3" }),
|
|
1904
|
-
fmtCurrency(totalPrice, currency.locale, currency.code),
|
|
1905
|
-
" \xB7 ",
|
|
1906
|
-
t2("agenda.payment.collect")
|
|
1907
|
-
]
|
|
1908
|
-
}
|
|
1909
|
-
) : /* @__PURE__ */ jsxs("span", { children: [
|
|
1910
|
-
"\xB7 ",
|
|
1911
|
-
fmtCurrency(totalPrice, currency.locale, currency.code)
|
|
1912
|
-
] }) : /* @__PURE__ */ jsxs("span", { children: [
|
|
1913
|
-
"\xB7 ",
|
|
1914
|
-
fmtCurrency(totalPrice, currency.locale, currency.code)
|
|
1915
|
-
] })
|
|
1916
1901
|
] })
|
|
1917
1902
|
] }),
|
|
1918
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center
|
|
1919
|
-
/* @__PURE__ */ jsx(
|
|
1920
|
-
/* @__PURE__ */
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1903
|
+
modalTab === "appointment" && !clientEditing && /* @__PURE__ */ jsx("div", { className: "px-5 py-2.5 border-t fayz-glass-divider bg-transparent shrink-0", children: confirmingDelete ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1904
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium", children: t2("agenda.appointment.deleteConfirm") }),
|
|
1905
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1906
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setConfirmingDelete(false), children: t2("agenda.appointment.cancel") }),
|
|
1907
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", size: "sm", onClick: handleDelete, disabled: deleting, children: deleting ? t2("agenda.appointment.deleting") : t2("agenda.appointment.confirmDelete") })
|
|
1908
|
+
] })
|
|
1909
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1910
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1911
|
+
mode === "edit" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setConfirmingDelete(true), className: "text-xs text-destructive font-medium hover:underline", children: t2("agenda.appointment.delete") }),
|
|
1912
|
+
services.length > 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1913
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
|
|
1914
|
+
totalDuration,
|
|
1915
|
+
"min",
|
|
1916
|
+
config.modules.financial && totalPrice > 0 && mode === "edit" ? paymentStatusLoading ? /* @__PURE__ */ jsx("span", { className: "h-4 w-16 rounded-full bg-muted animate-pulse" }) : isPaidBooking ? /* @__PURE__ */ jsxs(
|
|
1917
|
+
"button",
|
|
1918
|
+
{
|
|
1919
|
+
type: "button",
|
|
1920
|
+
onClick: (e) => {
|
|
1921
|
+
e.stopPropagation();
|
|
1922
|
+
setModalTab("financial");
|
|
1923
|
+
},
|
|
1924
|
+
className: "inline-flex items-center gap-1 rounded-full bg-success/10 px-2 py-0.5 text-[10px] font-medium text-success hover:bg-success/20 transition-colors",
|
|
1925
|
+
children: [
|
|
1926
|
+
/* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }),
|
|
1927
|
+
fmtCurrency(totalPrice, currency.locale, currency.code),
|
|
1928
|
+
" \xB7 ",
|
|
1929
|
+
t2("agenda.payment.paid")
|
|
1930
|
+
]
|
|
1931
|
+
}
|
|
1932
|
+
) : date <= (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) ? /* @__PURE__ */ jsxs(
|
|
1933
|
+
"button",
|
|
1934
|
+
{
|
|
1935
|
+
type: "button",
|
|
1936
|
+
onClick: (e) => {
|
|
1937
|
+
e.stopPropagation();
|
|
1938
|
+
setModalTab("financial");
|
|
1939
|
+
},
|
|
1940
|
+
className: "inline-flex items-center gap-1 rounded-full bg-warning/10 px-2 py-0.5 text-[10px] font-medium text-warning hover:bg-warning/20 transition-colors",
|
|
1941
|
+
children: [
|
|
1942
|
+
/* @__PURE__ */ jsx(HandCoins, { className: "h-3 w-3" }),
|
|
1943
|
+
fmtCurrency(totalPrice, currency.locale, currency.code),
|
|
1944
|
+
" \xB7 ",
|
|
1945
|
+
t2("agenda.payment.collect")
|
|
1946
|
+
]
|
|
1947
|
+
}
|
|
1948
|
+
) : /* @__PURE__ */ jsxs("span", { children: [
|
|
1949
|
+
"\xB7 ",
|
|
1950
|
+
fmtCurrency(totalPrice, currency.locale, currency.code)
|
|
1951
|
+
] }) : /* @__PURE__ */ jsxs("span", { children: [
|
|
1952
|
+
"\xB7 ",
|
|
1953
|
+
fmtCurrency(totalPrice, currency.locale, currency.code)
|
|
1954
|
+
] })
|
|
1955
|
+
] })
|
|
1956
|
+
] }),
|
|
1957
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(Button, { type: "submit", variant: "default", size: "sm", disabled: !canSubmit || !isDirty, children: saving ? t2("agenda.appointment.saving") : mode === "create" ? t2("agenda.appointment.save") : t2("agenda.appointment.update") }) })
|
|
1958
|
+
] }) })
|
|
1959
|
+
] }),
|
|
1960
|
+
modalTab !== "appointment" && !clientEditing && /* @__PURE__ */ jsx("div", { className: "px-5 py-2.5 border-t fayz-glass-divider bg-transparent shrink-0", children: /* @__PURE__ */ jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxs(
|
|
1961
|
+
"button",
|
|
1962
|
+
{
|
|
1963
|
+
type: "button",
|
|
1964
|
+
onClick: () => {
|
|
1965
|
+
setModalTab("appointment");
|
|
1966
|
+
setClientEditing(false);
|
|
1967
|
+
},
|
|
1968
|
+
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
|
|
1969
|
+
children: [
|
|
1970
|
+
/* @__PURE__ */ jsx("svg", { className: "h-3 w-3", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M10 12L6 8l4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }),
|
|
1971
|
+
t2("agenda.payment.backToAppointment")
|
|
1972
|
+
]
|
|
1973
|
+
}
|
|
1974
|
+
) }) })
|
|
1975
|
+
] })
|
|
1939
1976
|
] })
|
|
1940
|
-
|
|
1977
|
+
);
|
|
1941
1978
|
}
|
|
1942
1979
|
var STYLE_ID2 = "saas-floating-panel";
|
|
1943
1980
|
if (typeof document !== "undefined" && !document.getElementById(STYLE_ID2)) {
|
|
@@ -2405,7 +2442,7 @@ function AgendaMobileList({ bookings, statusColors, currencyCode, onSelect }) {
|
|
|
2405
2442
|
"button",
|
|
2406
2443
|
{
|
|
2407
2444
|
onClick: () => onSelect(b),
|
|
2408
|
-
className: "flex w-full items-center gap-3 rounded-
|
|
2445
|
+
className: "flex w-full items-center gap-3 rounded-card border bg-card px-3 py-2.5 text-left shadow-sm transition-colors active:bg-muted/50",
|
|
2409
2446
|
children: [
|
|
2410
2447
|
/* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 shrink-0 rounded-full", style: { backgroundColor: color } }),
|
|
2411
2448
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -2646,6 +2683,7 @@ function CalendarView() {
|
|
|
2646
2683
|
const [profSectionOpen, setProfSectionOpen] = useState(true);
|
|
2647
2684
|
const [statusSectionOpen, setStatusSectionOpen] = useState(false);
|
|
2648
2685
|
const [visibleRange, setVisibleRange] = useState(null);
|
|
2686
|
+
useEffect(() => () => closeModal(), [closeModal]);
|
|
2649
2687
|
const openModal = useCallback((...args) => {
|
|
2650
2688
|
setPopoverVisible(false);
|
|
2651
2689
|
setSelectedBooking(null);
|
|
@@ -3784,7 +3822,11 @@ function createSupabaseAgendaProvider(providerOptions) {
|
|
|
3784
3822
|
},
|
|
3785
3823
|
async getProfessionals() {
|
|
3786
3824
|
const { core } = getClients();
|
|
3787
|
-
const
|
|
3825
|
+
const base = () => core.from("people").select("id, name, avatar_url, is_active").eq("kind", professionalKind).eq("is_active", true);
|
|
3826
|
+
let { data, error } = await base().eq("is_bookable", true).order("name");
|
|
3827
|
+
if (error && error.code === "42703") {
|
|
3828
|
+
({ data, error } = await base().order("name"));
|
|
3829
|
+
}
|
|
3788
3830
|
if (error) throw error;
|
|
3789
3831
|
return (data ?? []).map((p) => ({
|
|
3790
3832
|
id: p.id,
|
|
@@ -4201,6 +4243,8 @@ var en = {
|
|
|
4201
4243
|
"agenda.appointment.name": "Name",
|
|
4202
4244
|
"agenda.appointment.new": "New Appointment",
|
|
4203
4245
|
"agenda.appointment.newClient": "New client",
|
|
4246
|
+
"agenda.appointment.required": "Required",
|
|
4247
|
+
"agenda.appointment.newService": "New service",
|
|
4204
4248
|
"agenda.appointment.notes": "Notes",
|
|
4205
4249
|
"agenda.appointment.phone": "Phone",
|
|
4206
4250
|
"agenda.appointment.professional": "Professional",
|
|
@@ -4421,6 +4465,8 @@ var ptBR = {
|
|
|
4421
4465
|
"agenda.appointment.name": "Nome",
|
|
4422
4466
|
"agenda.appointment.new": "Novo Agendamento",
|
|
4423
4467
|
"agenda.appointment.newClient": "Novo cliente",
|
|
4468
|
+
"agenda.appointment.required": "Obrigat\xF3rio",
|
|
4469
|
+
"agenda.appointment.newService": "Novo servi\xE7o",
|
|
4424
4470
|
"agenda.appointment.notes": "Observa\xE7\xF5es",
|
|
4425
4471
|
"agenda.appointment.phone": "Telefone",
|
|
4426
4472
|
"agenda.appointment.professional": "Profissional",
|
|
@@ -5715,7 +5761,7 @@ function ImportEventsWizard({
|
|
|
5715
5761
|
setImporting(false);
|
|
5716
5762
|
}
|
|
5717
5763
|
}
|
|
5718
|
-
return /* @__PURE__ */ jsxs("div", { className: "rounded-
|
|
5764
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card", children: [
|
|
5719
5765
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-2", children: [
|
|
5720
5766
|
/* @__PURE__ */ jsx(CalendarPlus, { className: "h-4 w-4 text-muted-foreground" }),
|
|
5721
5767
|
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Import future events" }),
|
|
@@ -8000,6 +8046,7 @@ function createAgendaPlugin(options) {
|
|
|
8000
8046
|
const PageComponent = () => React13.createElement(AgendaPage, { config, provider: provider2, store });
|
|
8001
8047
|
return {
|
|
8002
8048
|
id: "agenda",
|
|
8049
|
+
defaultAgentRole: "frontdesk",
|
|
8003
8050
|
name: config.labels.pageTitle,
|
|
8004
8051
|
icon: "Calendar",
|
|
8005
8052
|
version: "1.0.0",
|