@fayz-ai/plugin-agenda 0.27.11 → 0.27.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/AgendaGeneralSettings-YBI43RB6.js +96 -0
  2. package/dist/AgendaGeneralSettings-YBI43RB6.js.map +1 -0
  3. package/dist/AgendaMessageTemplates-46ZLTFLM.js +31 -0
  4. package/dist/AgendaMessageTemplates-46ZLTFLM.js.map +1 -0
  5. package/dist/AgendaPage-43OXQDZE.js +3174 -0
  6. package/dist/AgendaPage-43OXQDZE.js.map +1 -0
  7. package/dist/CheckinPage-SGWK2TQZ.js +904 -0
  8. package/dist/CheckinPage-SGWK2TQZ.js.map +1 -0
  9. package/dist/ConfirmationsView-DZJSYBXA.js +361 -0
  10. package/dist/ConfirmationsView-DZJSYBXA.js.map +1 -0
  11. package/dist/MyClientsTodayWidget-ZGTXJJKN.js +127 -0
  12. package/dist/MyClientsTodayWidget-ZGTXJJKN.js.map +1 -0
  13. package/dist/PersonAttendanceBlock-PHZVHFQ4.js +105 -0
  14. package/dist/PersonAttendanceBlock-PHZVHFQ4.js.map +1 -0
  15. package/dist/PersonAttendancesWidget-3NTWBGCU.js +1278 -0
  16. package/dist/PersonAttendancesWidget-3NTWBGCU.js.map +1 -0
  17. package/dist/PersonServicesWidget-4RJGKX3K.js +80 -0
  18. package/dist/PersonServicesWidget-4RJGKX3K.js.map +1 -0
  19. package/dist/chunk-37INOUNW.js +20 -0
  20. package/dist/chunk-37INOUNW.js.map +1 -0
  21. package/dist/chunk-A2VD5DVN.js +12 -0
  22. package/dist/chunk-A2VD5DVN.js.map +1 -0
  23. package/dist/chunk-HGJI2JR6.js +169 -0
  24. package/dist/chunk-HGJI2JR6.js.map +1 -0
  25. package/dist/chunk-VFQPXKXP.js +11 -0
  26. package/dist/chunk-VFQPXKXP.js.map +1 -0
  27. package/dist/chunk-WNANVT4F.js +2386 -0
  28. package/dist/chunk-WNANVT4F.js.map +1 -0
  29. package/dist/chunk-X7PKHXT6.js +18 -0
  30. package/dist/chunk-X7PKHXT6.js.map +1 -0
  31. package/dist/chunk-XTNSK4RJ.js +12 -0
  32. package/dist/chunk-XTNSK4RJ.js.map +1 -0
  33. package/dist/components/PersonAttendanceBlock.d.ts.map +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +2530 -19553
  36. package/dist/index.js.map +1 -1
  37. package/dist/migrations/integrations-google-calendar.browser.js +15 -0
  38. package/dist/migrations/integrations-google-calendar.browser.js.map +1 -0
  39. package/dist/migrations/integrations-google-calendar.js +1992 -0
  40. package/dist/migrations/integrations-google-calendar.js.map +1 -0
  41. package/dist/migrations/main.browser.js +29 -0
  42. package/dist/migrations/main.browser.js.map +1 -0
  43. package/dist/migrations/main.js +6440 -0
  44. package/dist/migrations/main.js.map +1 -0
  45. package/package.json +13 -7
@@ -0,0 +1,2386 @@
1
+ import { BookingCard } from './chunk-HGJI2JR6.js';
2
+ import { useAgendaConfig, useAgendaProvider, useAgendaStore } from './chunk-XTNSK4RJ.js';
3
+ import { FilterCheckRow, toast, Modal, ModalContent, ModalBody, ModalTaskHost, ModalTask, Button, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DatePicker, SearchCombobox, Textarea, ModalTaskHeader, PersonPicker, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, Skeleton, DotBadge } from '@fayz-ai/ui';
4
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
+ import React3, { useRef, useState, useEffect, useMemo } from 'react';
6
+ import { createPortal } from 'react-dom';
7
+ import { Ban, CheckSquare, Calendar, User, History, DollarSign, Check, AlertTriangle, Clock, ExternalLink, Users, X, MapPin, DoorOpen, Briefcase, CircleDot, FileText, HandCoins, LogIn, FileCheck, ArrowRightLeft, CreditCard, QrCode, Banknote, PackageCheck, Plus, Link2, Sparkles } from 'lucide-react';
8
+ import { FORMS_ZONES } from '@fayz-ai/forms/views/zones';
9
+ import { FINANCIAL_ZONES } from '@fayz-ai/financial/views/zones';
10
+ import { useLimitGuard, useLookupCreate, useTenantPluginSettings, WidgetSlot, CrudFormPage, CrudDetailPage, ContactPicker, invalidateLimit } from '@fayz-ai/admin';
11
+ import { todayLocal, useTranslation, getCurrentLocale, useActiveUnitId, resolveDataProvider, navigateApp, formatCurrency, appHref } from '@fayz-ai/core';
12
+
13
+ function StatusFilterList({
14
+ statuses,
15
+ selected,
16
+ onToggle
17
+ }) {
18
+ return /* @__PURE__ */ jsx(Fragment, { children: statuses.map((st) => /* @__PURE__ */ jsx(
19
+ FilterCheckRow,
20
+ {
21
+ option: { value: st.value, label: st.label, color: st.color },
22
+ checked: selected.length === 0 || selected.includes(st.value),
23
+ onToggle: () => onToggle(st.value)
24
+ },
25
+ st.value
26
+ )) });
27
+ }
28
+
29
+ // src/lib/professional-scope.ts
30
+ var atendeEm = (p, unitId) => !p.unitIds || p.unitIds.length === 0 || p.unitIds.includes(unitId);
31
+ function professionalsForLocation(people, locationId, selectedId) {
32
+ const doLocal = locationId ? people.filter((p) => atendeEm(p, locationId)) : people;
33
+ if (!selectedId || doLocal.some((p) => p.id === selectedId)) return doLocal;
34
+ const escolhido = people.find((p) => p.id === selectedId);
35
+ return escolhido ? [...doLocal, escolhido] : doLocal;
36
+ }
37
+ function shouldClearProfessional(people, locationId, selectedId) {
38
+ if (!locationId || !selectedId) return null;
39
+ const escolhido = people.find((p) => p.id === selectedId);
40
+ if (!escolhido || atendeEm(escolhido, locationId)) return null;
41
+ return escolhido;
42
+ }
43
+
44
+ // src/lib/recurrence.ts
45
+ var RECURRENCE_MAX_OCCURRENCES = 60;
46
+ var RECURRENCE_HORIZON_DAYS = 730;
47
+ function parseDay(key) {
48
+ const [y, m, d] = key.slice(0, 10).split("-").map(Number);
49
+ return new Date(Date.UTC(y, (m ?? 1) - 1, d ?? 1));
50
+ }
51
+ function formatDay(date) {
52
+ const y = date.getUTCFullYear();
53
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
54
+ const d = String(date.getUTCDate()).padStart(2, "0");
55
+ return `${y}-${m}-${d}`;
56
+ }
57
+ function addDays(date, days) {
58
+ return new Date(date.getTime() + days * 864e5);
59
+ }
60
+ function weekdayOrdinalInMonth(key) {
61
+ return Math.floor((parseDay(key).getUTCDate() - 1) / 7) + 1;
62
+ }
63
+ function nthWeekdayOfMonth(year, month, weekday, nth) {
64
+ const first = new Date(Date.UTC(year, month, 1));
65
+ const offset = (weekday - first.getUTCDay() + 7) % 7;
66
+ const candidate = new Date(Date.UTC(year, month, 1 + offset + (nth - 1) * 7));
67
+ if (candidate.getUTCMonth() !== (month % 12 + 12) % 12) return null;
68
+ return candidate;
69
+ }
70
+ function ruleForPreset(preset) {
71
+ switch (preset) {
72
+ case "daily":
73
+ return { freq: "daily", interval: 1 };
74
+ case "weekly":
75
+ return { freq: "weekly", interval: 1 };
76
+ case "biweekly":
77
+ return { freq: "weekly", interval: 2 };
78
+ case "monthly":
79
+ return { freq: "monthly_weekday", interval: 1 };
80
+ default:
81
+ return null;
82
+ }
83
+ }
84
+ function presetForRule(rule) {
85
+ if (!rule) return "none";
86
+ if (rule.freq === "daily" && rule.interval === 1) return "daily";
87
+ if (rule.freq === "weekly" && rule.interval === 1) return "weekly";
88
+ if (rule.freq === "weekly" && rule.interval === 2) return "biweekly";
89
+ if (rule.freq === "monthly_weekday" && rule.interval === 1) return "monthly";
90
+ return "none";
91
+ }
92
+ function expandRecurrence(startDate, rule) {
93
+ const interval = Math.max(1, Math.floor(rule.interval || 1));
94
+ const hardCount = rule.count != null ? Math.max(1, Math.floor(rule.count)) : null;
95
+ const until = rule.until || null;
96
+ const maxCount = Math.min(hardCount ?? RECURRENCE_MAX_OCCURRENCES, RECURRENCE_MAX_OCCURRENCES);
97
+ const horizon = formatDay(addDays(parseDay(startDate), RECURRENCE_HORIZON_DAYS));
98
+ const dates = [];
99
+ let reachedEndCondition = false;
100
+ const push = (key) => {
101
+ if (until && key > until) {
102
+ reachedEndCondition = true;
103
+ return false;
104
+ }
105
+ if (key > horizon) return false;
106
+ dates.push(key);
107
+ if (hardCount != null && dates.length >= hardCount) {
108
+ reachedEndCondition = true;
109
+ return false;
110
+ }
111
+ return dates.length < maxCount;
112
+ };
113
+ dates.push(startDate);
114
+ if (hardCount === 1) return { dates, truncated: false };
115
+ if (until && startDate >= until) return { dates, truncated: false };
116
+ if (rule.freq === "monthly_weekday") {
117
+ const start = parseDay(startDate);
118
+ const weekday = start.getUTCDay();
119
+ const nth = weekdayOrdinalInMonth(startDate);
120
+ let step = 1;
121
+ while (true) {
122
+ const target = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + step * interval, 1));
123
+ const day = nthWeekdayOfMonth(target.getUTCFullYear(), target.getUTCMonth(), weekday, nth);
124
+ step += 1;
125
+ if (!day) {
126
+ if (step > RECURRENCE_MAX_OCCURRENCES * 2) return { dates, truncated: true };
127
+ const probe = formatDay(target);
128
+ if (until && probe > until) return { dates, truncated: false };
129
+ if (probe > horizon) return { dates, truncated: !reachedEndCondition };
130
+ continue;
131
+ }
132
+ if (!push(formatDay(day))) return { dates, truncated: !reachedEndCondition };
133
+ }
134
+ }
135
+ const stepDays = rule.freq === "daily" ? interval : interval * 7;
136
+ let cursor = parseDay(startDate);
137
+ while (true) {
138
+ cursor = addDays(cursor, stepDays);
139
+ if (!push(formatDay(cursor))) return { dates, truncated: !reachedEndCondition };
140
+ }
141
+ }
142
+ function resolveSeriesTarget(booking, scope) {
143
+ if (scope === "series" && booking.seriesId) {
144
+ return { kind: "series", seriesId: booking.seriesId, bookingId: booking.id };
145
+ }
146
+ return { kind: "single", bookingId: booking.id };
147
+ }
148
+ function planSeriesEdit(args) {
149
+ const target = resolveSeriesTarget(args.booking, args.scope);
150
+ if (target.kind === "single") {
151
+ return { single: args.patch, series: null, dateStaysLocal: false };
152
+ }
153
+ const { startsAt, endsAt, ...shared } = args.patch;
154
+ return {
155
+ single: args.dateChanged ? { startsAt, endsAt } : null,
156
+ series: { seriesId: target.seriesId, patch: shared, timeOfDay: args.timeOfDay },
157
+ dateStaysLocal: args.dateChanged
158
+ };
159
+ }
160
+ var TYPE_ICONS = {
161
+ cash: Banknote,
162
+ pix: QrCode,
163
+ credit_card: CreditCard,
164
+ debit_card: CreditCard,
165
+ bank_transfer: ArrowRightLeft,
166
+ check: FileCheck
167
+ };
168
+ var TYPE_COLORS = {
169
+ cash: "border-success/30 bg-success/5 hover:bg-success/10 data-[active=true]:border-success data-[active=true]:bg-success/15",
170
+ pix: "border-teal-500/30 bg-teal-500/5 hover:bg-teal-500/10 data-[active=true]:border-teal-500 data-[active=true]:bg-teal-500/15",
171
+ credit_card: "border-info/30 bg-info/5 hover:bg-info/10 data-[active=true]:border-info data-[active=true]:bg-info/15",
172
+ debit_card: "border-magic/30 bg-magic/5 hover:bg-magic/10 data-[active=true]:border-magic data-[active=true]:bg-magic/15",
173
+ bank_transfer: "border-warning/30 bg-warning/5 hover:bg-warning/10 data-[active=true]:border-warning data-[active=true]:bg-warning/15",
174
+ check: "border-input/30 bg-muted/5 hover:bg-muted/10 data-[active=true]:border-input data-[active=true]:bg-muted/15"
175
+ };
176
+ var ICON_COLORS = {
177
+ cash: "text-success",
178
+ pix: "text-teal-500",
179
+ credit_card: "text-info",
180
+ debit_card: "text-magic",
181
+ bank_transfer: "text-warning",
182
+ check: "text-muted-foreground"
183
+ };
184
+ function BookingPaymentPanel({ orderId, orderTotal, services, onClose, onPaymentChange, formatCurrency: formatCurrency2, inline }) {
185
+ const t = useTranslation();
186
+ const config = useAgendaConfig();
187
+ const bridge = config.financialBridge;
188
+ const [visible, setVisible] = useState(false);
189
+ const [loading, setLoading] = useState(true);
190
+ const [detail, setDetail] = useState(null);
191
+ const [selectedTypeId, setSelectedTypeId] = useState("");
192
+ const [paymentTypes, setPaymentTypes] = useState([]);
193
+ const [bankAccounts, setBankAccounts] = useState([]);
194
+ const [filteredMethods, setFilteredMethods] = useState([]);
195
+ const [selectedBankId, setSelectedBankId] = useState("");
196
+ const [paymentMethodId, setPaymentMethodId] = useState("");
197
+ const [cardBrand, setCardBrand] = useState("");
198
+ const [cardInstallments, setCardInstallments] = useState(1);
199
+ const [processing, setProcessing] = useState(false);
200
+ const [justPaid, setJustPaid] = useState(false);
201
+ useEffect(() => {
202
+ requestAnimationFrame(() => setVisible(true));
203
+ }, []);
204
+ useEffect(() => {
205
+ let cancelled = false;
206
+ setLoading(true);
207
+ Promise.all([
208
+ bridge.getPaymentDetail(orderId).catch(() => null),
209
+ bridge.getPaymentMethodTypes().catch(() => []),
210
+ bridge.getBankAccounts().catch(() => [])
211
+ ]).then(([d, types, accounts]) => {
212
+ if (cancelled) return;
213
+ setDetail(d);
214
+ setPaymentTypes(types);
215
+ setBankAccounts(accounts);
216
+ setLoading(false);
217
+ });
218
+ return () => {
219
+ cancelled = true;
220
+ };
221
+ }, [orderId]);
222
+ function handleClose() {
223
+ setVisible(false);
224
+ setTimeout(onClose, 200);
225
+ }
226
+ const selectedType = paymentTypes.find((t2) => t2.id === selectedTypeId);
227
+ const txType = selectedType?.transactionType ?? "";
228
+ const needsBank = ["bank_transfer", "pix", "check"].includes(txType);
229
+ const needsCard = txType === "credit_card" || txType === "debit_card";
230
+ const needsCardInstallments = txType === "credit_card";
231
+ function handleSelectType(typeId) {
232
+ setSelectedTypeId(typeId);
233
+ setSelectedBankId("");
234
+ setPaymentMethodId("");
235
+ setCardBrand("");
236
+ setCardInstallments(1);
237
+ if (bridge.getPaymentMethods) {
238
+ bridge.getPaymentMethods(typeId).then(setFilteredMethods).catch(() => setFilteredMethods([]));
239
+ }
240
+ }
241
+ async function handleCheckout() {
242
+ if (!selectedTypeId) return;
243
+ setProcessing(true);
244
+ try {
245
+ const result = await bridge.checkout(orderId, {
246
+ paymentMethodTypeId: selectedTypeId,
247
+ paymentMethodId: paymentMethodId || void 0,
248
+ bankAccountId: needsBank ? selectedBankId || void 0 : void 0,
249
+ cardBrand: needsCard ? cardBrand || void 0 : void 0,
250
+ cardInstallments: needsCardInstallments ? cardInstallments : void 0
251
+ });
252
+ setDetail(result);
253
+ setJustPaid(true);
254
+ onPaymentChange?.();
255
+ setTimeout(() => setJustPaid(false), 2200);
256
+ } catch {
257
+ }
258
+ setProcessing(false);
259
+ }
260
+ const isPaid = detail?.status === "paid";
261
+ const content = /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 space-y-4", children: [
262
+ loading && /* @__PURE__ */ jsx("div", { className: "space-y-3 py-2", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-8 rounded-lg bg-muted animate-pulse" }, i)) }),
263
+ !loading && isPaid && justPaid && /* @__PURE__ */ jsxs("div", { className: "text-center py-8 space-y-2 animate-in fade-in zoom-in-95 duration-300", children: [
264
+ /* @__PURE__ */ jsx("div", { className: "flex h-14 w-14 mx-auto items-center justify-center rounded-full bg-success/10 animate-bounce", style: { animationDuration: "600ms", animationIterationCount: "2" }, children: /* @__PURE__ */ jsx(Check, { className: "h-7 w-7 text-success" }) }),
265
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-success", children: t("agenda.payment.checkoutComplete") }),
266
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: formatCurrency2(detail.totalAmount) })
267
+ ] }),
268
+ !loading && detail && detail.status !== "none" && !justPaid && /* @__PURE__ */ jsxs("div", { className: "space-y-3 animate-in fade-in duration-300", children: [
269
+ detail.movements.length > 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: detail.movements.map((mov) => {
270
+ const methodName = mov.paymentMethodTypeName;
271
+ const txKey = (methodName ?? "").toLowerCase().replace(/\s+/g, "_");
272
+ const Icon = TYPE_ICONS[txKey] ?? Banknote;
273
+ const iconColor = ICON_COLORS[txKey] ?? "text-muted-foreground";
274
+ const isMovPaid = mov.status === "paid";
275
+ const isPartial = mov.status === "partial";
276
+ const isOverdue = mov.status === "overdue";
277
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-lg border px-3 py-2.5", children: [
278
+ /* @__PURE__ */ jsx(Icon, { className: `h-4 w-4 shrink-0 ${isMovPaid ? iconColor : "text-muted-foreground"}` }),
279
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
280
+ /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium truncate", children: [
281
+ methodName ?? "\u2014",
282
+ mov.cardBrand ? ` \xB7 ${mov.cardBrand}` : "",
283
+ mov.cardInstallments && mov.cardInstallments > 1 ? ` ${mov.cardInstallments}x` : ""
284
+ ] }),
285
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
286
+ mov.paymentDate ?? mov.dueDate,
287
+ detail.movements.length > 1 && mov.installmentNumber ? ` \xB7 ${t("agenda.payment.installment")} ${mov.installmentNumber}` : ""
288
+ ] })
289
+ ] }),
290
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
291
+ isMovPaid && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-success/10 px-2 py-0.5 text-[10px] font-medium text-success", children: [
292
+ /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }),
293
+ " ",
294
+ t("agenda.payment.paid")
295
+ ] }),
296
+ isPartial && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-warning/10 px-2 py-0.5 text-[10px] font-medium text-warning", children: [
297
+ "! ",
298
+ formatCurrency2(mov.paidAmount),
299
+ "/",
300
+ formatCurrency2(mov.amount)
301
+ ] }),
302
+ isOverdue && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-destructive/10 px-2 py-0.5 text-[10px] font-medium text-destructive", children: [
303
+ "! ",
304
+ t("agenda.payment.overdue")
305
+ ] }),
306
+ mov.status === "pending" && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: t("agenda.payment.pending") }),
307
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: formatCurrency2(mov.paidAmount || mov.amount) })
308
+ ] })
309
+ ] }, mov.id);
310
+ }) }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm px-1", children: [
311
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("agenda.payment.total") }),
312
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: formatCurrency2(detail.totalAmount) })
313
+ ] }),
314
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
315
+ detail.referenceNumber && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono text-muted-foreground", children: [
316
+ "#",
317
+ detail.referenceNumber
318
+ ] }),
319
+ /* @__PURE__ */ jsxs(
320
+ "a",
321
+ {
322
+ href: appHref(detail.status === "paid" || detail.status === "cancelled" ? `/financial/receivables/detail/${detail.invoiceId}` : `/financial/receivables/${detail.invoiceId}/edit`),
323
+ className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary hover:underline ml-auto",
324
+ children: [
325
+ t("agenda.payment.viewInvoice"),
326
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
327
+ ]
328
+ }
329
+ )
330
+ ] })
331
+ ] }),
332
+ !loading && (!detail || detail.movements.length === 0) && /* @__PURE__ */ jsxs(Fragment, { children: [
333
+ services && services.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
334
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.payment.serviceSummary") }),
335
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: services.map((svc, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm py-1", children: [
336
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: svc.name }),
337
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatCurrency2(svc.price) })
338
+ ] }, i)) })
339
+ ] }),
340
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm font-semibold pt-2 border-t", children: [
341
+ /* @__PURE__ */ jsx("span", { children: t("agenda.payment.total") }),
342
+ /* @__PURE__ */ jsx("span", { children: formatCurrency2(orderTotal) })
343
+ ] }),
344
+ /* @__PURE__ */ jsxs("div", { children: [
345
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground mb-2", children: t("agenda.payment.paymentMethod") }),
346
+ paymentTypes.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-input bg-muted/30 px-3 py-3 text-center", children: [
347
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("agenda.payment.noMethods") }),
348
+ /* @__PURE__ */ jsxs("a", { href: appHref("/financial/settings"), className: "mt-1.5 inline-block text-xs font-medium text-primary hover:underline", children: [
349
+ t("agenda.payment.configureMethods"),
350
+ " \u2192"
351
+ ] })
352
+ ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1.5", children: paymentTypes.map((pt) => {
353
+ const ptTx = pt.transactionType ?? pt.name.toLowerCase().replace(/\s+/g, "_");
354
+ const Icon = TYPE_ICONS[ptTx] ?? Banknote;
355
+ const colorClass = TYPE_COLORS[ptTx] ?? TYPE_COLORS.cash;
356
+ const iconColor = ICON_COLORS[ptTx] ?? ICON_COLORS.cash;
357
+ const isActive = selectedTypeId === pt.id;
358
+ return /* @__PURE__ */ jsxs(
359
+ Button,
360
+ {
361
+ variant: "bare",
362
+ size: "bare",
363
+ type: "button",
364
+ "data-active": isActive,
365
+ onClick: () => handleSelectType(pt.id),
366
+ className: `flex flex-col items-center justify-start gap-1 rounded-lg border p-2.5 text-[10px] font-medium transition-all ${colorClass}`,
367
+ children: [
368
+ /* @__PURE__ */ jsx(Icon, { className: `h-4 w-4 ${iconColor}` }),
369
+ /* @__PURE__ */ jsx("span", { className: "truncate w-full text-center", children: pt.name })
370
+ ]
371
+ },
372
+ pt.id
373
+ );
374
+ }) })
375
+ ] }),
376
+ selectedTypeId && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
377
+ filteredMethods.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
378
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: needsCard ? t("agenda.payment.card") : t("agenda.payment.method") }),
379
+ /* @__PURE__ */ jsxs(Select, { value: paymentMethodId, onValueChange: setPaymentMethodId, children: [
380
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 bg-card px-2.5", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("agenda.payment.select") }) }),
381
+ /* @__PURE__ */ jsx(SelectContent, { children: filteredMethods.map((m) => /* @__PURE__ */ jsx(SelectItem, { value: m.id, children: m.name }, m.id)) })
382
+ ] })
383
+ ] }),
384
+ needsCard && /* @__PURE__ */ jsxs("div", { className: `grid gap-2.5 ${needsCardInstallments ? "grid-cols-2" : "grid-cols-1"}`, children: [
385
+ /* @__PURE__ */ jsxs("div", { children: [
386
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("agenda.payment.cardBrand") }),
387
+ /* @__PURE__ */ jsxs(Select, { value: cardBrand, onValueChange: setCardBrand, children: [
388
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 bg-card px-2.5", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("agenda.payment.selectBrand") }) }),
389
+ /* @__PURE__ */ jsx(SelectContent, { children: ["Visa", "Mastercard", "American Express", "Elo", "Hipercard", "Diners Club"].map((b) => /* @__PURE__ */ jsx(SelectItem, { value: b, children: b }, b)) })
390
+ ] })
391
+ ] }),
392
+ needsCardInstallments && /* @__PURE__ */ jsxs("div", { children: [
393
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("agenda.payment.cardInstallments") }),
394
+ /* @__PURE__ */ jsxs(Select, { value: String(cardInstallments), onValueChange: (v) => setCardInstallments(Number(v)), children: [
395
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 bg-card px-2.5", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
396
+ /* @__PURE__ */ jsx(SelectContent, { children: Array.from({ length: 12 }, (_, i) => i + 1).map((n) => /* @__PURE__ */ jsxs(SelectItem, { value: String(n), children: [
397
+ n,
398
+ "x ",
399
+ n > 1 ? formatCurrency2(orderTotal / n) : ""
400
+ ] }, n)) })
401
+ ] })
402
+ ] })
403
+ ] }),
404
+ needsBank && bankAccounts.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
405
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("agenda.payment.bankAccount") }),
406
+ /* @__PURE__ */ jsxs(Select, { value: selectedBankId, onValueChange: setSelectedBankId, children: [
407
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "mt-1 bg-card px-2.5", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("agenda.payment.selectAccount") }) }),
408
+ /* @__PURE__ */ jsx(SelectContent, { children: bankAccounts.map((a) => /* @__PURE__ */ jsx(SelectItem, { value: a.id, children: a.name }, a.id)) })
409
+ ] })
410
+ ] })
411
+ ] }),
412
+ /* @__PURE__ */ jsx(
413
+ Button,
414
+ {
415
+ variant: "bare",
416
+ size: "bare",
417
+ type: "button",
418
+ onClick: handleCheckout,
419
+ disabled: processing || !selectedTypeId,
420
+ className: "whitespace-normal 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",
421
+ children: processing ? t("agenda.payment.checkingOut") : `${t("agenda.payment.checkout")} ${formatCurrency2(orderTotal)}`
422
+ }
423
+ )
424
+ ] })
425
+ ] });
426
+ if (inline) return content;
427
+ return createPortal(
428
+ /* @__PURE__ */ jsx(
429
+ "div",
430
+ {
431
+ className: "fixed inset-0 z-50 flex items-center justify-center transition-colors duration-200",
432
+ style: { backgroundColor: visible ? "rgba(0,0,0,0.5)" : "rgba(0,0,0,0)" },
433
+ onClick: handleClose,
434
+ children: /* @__PURE__ */ jsxs(
435
+ "div",
436
+ {
437
+ 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",
438
+ style: { opacity: visible ? 1 : 0, transform: visible ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)" },
439
+ onClick: (e) => e.stopPropagation(),
440
+ children: [
441
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b sticky top-0 bg-card z-10", children: [
442
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-semibold", children: t("agenda.payment.title") }),
443
+ /* @__PURE__ */ jsx(Button, { variant: "bare", size: "bare", onClick: handleClose, className: "p-1 text-muted-foreground hover:text-foreground transition-colors", children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
444
+ ] }),
445
+ content
446
+ ]
447
+ }
448
+ )
449
+ }
450
+ ),
451
+ document.body
452
+ );
453
+ }
454
+ function StatusBadge({ status }) {
455
+ const config = useAgendaConfig();
456
+ const statusConfig = config.statuses.find((s) => s.value === status);
457
+ return /* @__PURE__ */ jsx(DotBadge, { label: statusConfig?.label ?? status, color: statusConfig?.color ?? "#6b7280" });
458
+ }
459
+ function ClientHistoryPanel({ clientId, currentBookingId, formatCurrency: formatCurrency2 }) {
460
+ const t = useTranslation();
461
+ const config = useAgendaConfig();
462
+ const provider = useAgendaProvider();
463
+ const [bookings, setBookings] = React3.useState(null);
464
+ const [failed, setFailed] = React3.useState(false);
465
+ React3.useEffect(() => {
466
+ let alive = true;
467
+ if (!provider.getClientHistory) {
468
+ setBookings([]);
469
+ return;
470
+ }
471
+ provider.getClientHistory(clientId, 20).then((rows) => {
472
+ if (alive) setBookings(rows);
473
+ }).catch(() => {
474
+ if (alive) {
475
+ setBookings([]);
476
+ setFailed(true);
477
+ }
478
+ });
479
+ return () => {
480
+ alive = false;
481
+ };
482
+ }, [clientId, provider]);
483
+ if (bookings === null) {
484
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2 px-5 py-4", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-16 rounded-lg bg-muted animate-pulse" }, i)) });
485
+ }
486
+ if (failed) {
487
+ return /* @__PURE__ */ jsx("p", { className: "px-5 py-8 text-center text-sm text-muted-foreground", children: t("agenda.history.failed") });
488
+ }
489
+ if (bookings.length === 0) {
490
+ return /* @__PURE__ */ jsx("p", { className: "px-5 py-8 text-center text-sm text-muted-foreground", children: t("agenda.history.empty") });
491
+ }
492
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2 px-5 py-4", children: bookings.map((booking) => {
493
+ (booking.services ?? []).map((s) => s.name).filter(Boolean);
494
+ const isCurrent = booking.id === currentBookingId;
495
+ return (
496
+ /* O MESMO cartão da fila e das confirmações. O cliente não aparece:
497
+ esta lista já está dentro da ficha dele, e repetir o nome em cada
498
+ linha é ruído. O atendimento ABERTO fica marcado — é o único da
499
+ lista que ainda pode mudar. */
500
+ /* @__PURE__ */ jsx(
501
+ BookingCard,
502
+ {
503
+ booking,
504
+ hideClient: true,
505
+ showDate: true,
506
+ formatCurrency: formatCurrency2,
507
+ className: isCurrent ? "border-primary/40 bg-primary/5" : "",
508
+ badge: config.capabilities.status ? /* @__PURE__ */ jsx(StatusBadge, { status: booking.status }) : void 0
509
+ },
510
+ booking.id
511
+ )
512
+ );
513
+ }) });
514
+ }
515
+ function BookingInterestsPanel({ clientId, bookingId, value, onChange }) {
516
+ const provider = useAgendaProvider();
517
+ const [crmInterests, setCrmInterests] = React3.useState([]);
518
+ const [term, setTerm] = React3.useState("");
519
+ const [options, setOptions] = React3.useState([]);
520
+ const initializedFor = React3.useRef(null);
521
+ const onChangeRef = React3.useRef(onChange);
522
+ onChangeRef.current = onChange;
523
+ React3.useEffect(() => {
524
+ if (!clientId) {
525
+ setCrmInterests([]);
526
+ initializedFor.current = null;
527
+ onChangeRef.current([]);
528
+ return;
529
+ }
530
+ let vivo = true;
531
+ provider.getClientInterests?.(clientId, bookingId).then((rows) => {
532
+ if (!vivo) return;
533
+ const list = rows.map((r) => ({
534
+ key: `crm-${r.interestId}`,
535
+ interestId: r.interestId,
536
+ productId: r.productId,
537
+ name: r.name
538
+ }));
539
+ setCrmInterests(list);
540
+ if (initializedFor.current !== clientId) {
541
+ initializedFor.current = clientId;
542
+ onChangeRef.current(bookingId ? rows.filter((r) => r.linked).map((r) => ({
543
+ key: `crm-${r.interestId}`,
544
+ interestId: r.interestId,
545
+ productId: r.productId,
546
+ name: r.name
547
+ })) : list);
548
+ }
549
+ }).catch(() => {
550
+ if (vivo) setCrmInterests([]);
551
+ });
552
+ return () => {
553
+ vivo = false;
554
+ };
555
+ }, [clientId, bookingId, provider]);
556
+ React3.useEffect(() => {
557
+ if (!term.trim()) {
558
+ setOptions([]);
559
+ return;
560
+ }
561
+ const timer = setTimeout(() => {
562
+ provider.searchServices?.(term).then((rows) => setOptions(rows.map((r) => ({ id: r.id, label: r.name, price: r.price })))).catch(() => setOptions([]));
563
+ }, 250);
564
+ return () => clearTimeout(timer);
565
+ }, [term, provider]);
566
+ const remove = (key) => onChange(value.filter((i) => i.key !== key));
567
+ const addBack = (i) => {
568
+ if (value.some((v) => v.key === i.key)) return;
569
+ onChange([...value, i]);
570
+ };
571
+ const add = (opt) => {
572
+ setTerm("");
573
+ setOptions([]);
574
+ if (value.some((v) => v.productId === opt.id)) return;
575
+ const existing = crmInterests.find((c) => c.productId === opt.id);
576
+ if (existing) {
577
+ addBack(existing);
578
+ return;
579
+ }
580
+ onChange([...value, { key: `new-${opt.id}`, interestId: null, productId: opt.id, name: opt.label }]);
581
+ };
582
+ const removedCrm = crmInterests.filter((c) => !value.some((i) => i.key === c.key));
583
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded-lg border border-primary/25 bg-primary/5 px-2.5 py-2", children: [
584
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs font-medium text-primary", children: [
585
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
586
+ "Interesses",
587
+ value.length > 0 ? ` (${value.length})` : ""
588
+ ] }),
589
+ value.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: value.map((i) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs", children: [
590
+ i.name,
591
+ !i.interestId && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-primary", children: "novo" }),
592
+ /* @__PURE__ */ jsx(
593
+ Button,
594
+ {
595
+ variant: "bare",
596
+ size: "bare",
597
+ type: "button",
598
+ onClick: () => remove(i.key),
599
+ "aria-label": `Remover ${i.name} deste agendamento`,
600
+ className: "rounded-full p-0.5 hover:bg-muted-foreground/20",
601
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3", "aria-hidden": true })
602
+ }
603
+ )
604
+ ] }, i.key)) }),
605
+ removedCrm.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
606
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Fora deste agendamento:" }),
607
+ removedCrm.map((i) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-border px-2 py-0.5 text-xs opacity-70", children: [
608
+ i.name,
609
+ /* @__PURE__ */ jsx(
610
+ Button,
611
+ {
612
+ variant: "bare",
613
+ size: "bare",
614
+ type: "button",
615
+ onClick: () => addBack(i),
616
+ "aria-label": `Vincular ${i.name} a este agendamento`,
617
+ className: "rounded-full p-0.5 hover:bg-muted-foreground/20",
618
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3", "aria-hidden": true })
619
+ }
620
+ )
621
+ ] }, i.key))
622
+ ] }),
623
+ /* @__PURE__ */ jsx(
624
+ SearchCombobox,
625
+ {
626
+ value: term,
627
+ onChange: setTerm,
628
+ onSelect: add,
629
+ options,
630
+ placeholder: "+ Interesse em servi\xE7o\u2026",
631
+ onBlurEmpty: () => {
632
+ },
633
+ minimal: true
634
+ }
635
+ )
636
+ ] });
637
+ }
638
+ function BookingPrepaidPanel({ clientId, addedServiceIds, onAdd }) {
639
+ const provider = useAgendaProvider();
640
+ const [data, setData] = React3.useState(null);
641
+ React3.useEffect(() => {
642
+ let alive = true;
643
+ setData(null);
644
+ provider.getPrepaidItems?.(clientId).then((r) => {
645
+ if (alive) setData(r);
646
+ }).catch(() => {
647
+ if (alive) setData({ open: [], scheduledCount: 0 });
648
+ });
649
+ return () => {
650
+ alive = false;
651
+ };
652
+ }, [clientId, provider]);
653
+ if (!data || data.open.length === 0) return null;
654
+ const grouped = /* @__PURE__ */ new Map();
655
+ for (const it of data.open) {
656
+ const g = grouped.get(it.serviceId);
657
+ if (g) g.count += 1;
658
+ else grouped.set(it.serviceId, { item: it, count: 1 });
659
+ }
660
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded-lg border border-primary/25 bg-primary/5 px-2.5 py-2", children: [
661
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs font-medium text-primary", children: [
662
+ /* @__PURE__ */ jsx(PackageCheck, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
663
+ data.open.length,
664
+ " servi\xE7o(s) pr\xE9-contratado(s)",
665
+ data.scheduledCount > 0 && /* @__PURE__ */ jsxs("span", { className: "font-normal text-muted-foreground", children: [
666
+ "\xB7 ",
667
+ data.scheduledCount,
668
+ " j\xE1 agendado(s)"
669
+ ] })
670
+ ] }),
671
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "J\xE1 contratados e n\xE3o executados \u2014 clique para adicionar a este atendimento." }),
672
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [...grouped.values()].map(({ item, count }) => {
673
+ const added = addedServiceIds.includes(item.serviceId);
674
+ return /* @__PURE__ */ jsxs(
675
+ Button,
676
+ {
677
+ variant: "bare",
678
+ size: "bare",
679
+ type: "button",
680
+ disabled: added,
681
+ onClick: () => onAdd(item),
682
+ className: `font-normal ${`inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs transition-colors ${added ? "border-primary/40 bg-card text-muted-foreground" : "border-transparent bg-card hover:border-primary/40"}`}`,
683
+ children: [
684
+ added ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3 text-primary", "aria-hidden": true }) : /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3", "aria-hidden": true }),
685
+ item.serviceName,
686
+ count > 1 ? ` \xD7${count}` : ""
687
+ ]
688
+ },
689
+ item.serviceId
690
+ );
691
+ }) })
692
+ ] });
693
+ }
694
+ var fmtDate = (iso) => iso ? new Date(iso).toLocaleDateString("pt-BR") : "";
695
+ function BookingRevisionPanel({ clientId, value, onChange, onState }) {
696
+ const provider = useAgendaProvider();
697
+ const [rows, setRows] = React3.useState(null);
698
+ const [state, setState] = React3.useState("loading");
699
+ React3.useEffect(() => {
700
+ let alive = true;
701
+ setRows(null);
702
+ setState("loading");
703
+ if (!provider.getRevisionCandidatesForPerson) {
704
+ setState("unavailable");
705
+ return;
706
+ }
707
+ provider.getRevisionCandidatesForPerson(clientId).then((r) => {
708
+ if (alive) {
709
+ setRows(r);
710
+ setState(r.length ? "ready" : "empty");
711
+ }
712
+ }).catch(() => {
713
+ if (alive) {
714
+ setRows([]);
715
+ setState("unavailable");
716
+ }
717
+ });
718
+ return () => {
719
+ alive = false;
720
+ };
721
+ }, [clientId, provider]);
722
+ React3.useEffect(() => {
723
+ onState?.(state);
724
+ }, [state]);
725
+ if (state === "unavailable") return null;
726
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded-lg border border-primary/25 bg-primary/5 px-2.5 py-2", children: [
727
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs font-medium text-primary", children: [
728
+ /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
729
+ "De qual servi\xE7o \xE9 esta revis\xE3o?"
730
+ ] }),
731
+ state === "loading" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
732
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" }),
733
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-full" })
734
+ ] }),
735
+ state === "empty" && /* @__PURE__ */ jsx("p", { className: "text-xs text-warning", children: "Este cliente n\xE3o tem direito a revis\xE3o agora \u2014 n\xE3o h\xE1 servi\xE7o executado que possa receber revis\xE3o. Para atender, agende uma nova avalia\xE7\xE3o." }),
736
+ state === "ready" && rows && /* @__PURE__ */ jsxs(Fragment, { children: [
737
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "A revis\xE3o s\xF3 entra apontando para um servi\xE7o j\xE1 executado. Sem esse v\xEDnculo o atendimento n\xE3o finaliza." }),
738
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", role: "radiogroup", "aria-label": "Servi\xE7o revisado", children: rows.map((c) => {
739
+ const on = value?.sessionId === c.sessionId;
740
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
741
+ Button,
742
+ {
743
+ variant: "bare",
744
+ size: "bare",
745
+ type: "button",
746
+ role: "radio",
747
+ "aria-checked": on,
748
+ onClick: () => onChange(on ? null : {
749
+ sessionId: c.sessionId,
750
+ revisionNumber: c.nextRevisionNumber,
751
+ serviceName: c.serviceName
752
+ }),
753
+ className: `font-normal whitespace-normal justify-start ${`flex w-full items-center gap-2 rounded-md border px-2.5 py-1.5 text-left text-xs transition-colors ${on ? "border-primary bg-card" : "border-transparent bg-card/60 hover:bg-card"}`}`,
754
+ children: [
755
+ /* @__PURE__ */ jsx("span", { className: `flex h-4 w-4 shrink-0 items-center justify-center rounded-full border ${on ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/40"}`, children: on && /* @__PURE__ */ jsx(Check, { className: "h-3 w-3", "aria-hidden": true }) }),
756
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
757
+ /* @__PURE__ */ jsx("span", { className: "block truncate font-medium text-foreground", children: c.serviceName ?? "Servi\xE7o" }),
758
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
759
+ "executado em ",
760
+ fmtDate(c.executedOn),
761
+ c.deadlineDays != null && (c.withinDeadline ? ` \xB7 no prazo (${c.deadlineDays} dias)` : ` \xB7 fora do prazo de ${c.deadlineDays} dias`)
762
+ ] })
763
+ ] }),
764
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-foreground", children: [
765
+ c.nextRevisionNumber,
766
+ "\xAA Revis\xE3o"
767
+ ] })
768
+ ]
769
+ }
770
+ ) }, c.sessionId);
771
+ }) })
772
+ ] })
773
+ ] });
774
+ }
775
+ function isStatusAvailable(status, bookingStartsAt) {
776
+ const when = status.availableWhen ?? "always";
777
+ if (when === "always") return true;
778
+ const bookingDate = bookingStartsAt.slice(0, 10);
779
+ const today = todayLocal();
780
+ if (when === "today_or_past") return bookingDate <= today;
781
+ if (when === "today_only") return bookingDate === today;
782
+ return true;
783
+ }
784
+ var BOOKING_TYPE_ICONS = {
785
+ Calendar,
786
+ CheckSquare,
787
+ Ban
788
+ };
789
+ function formatCompactDate(dateStr, locale) {
790
+ const d = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
791
+ return d.toLocaleDateString(locale === "pt-BR" ? "pt-BR" : "en-US", {
792
+ weekday: "short",
793
+ month: "short",
794
+ day: "numeric"
795
+ });
796
+ }
797
+ var ROW = "-mx-2 flex cursor-pointer gap-3 rounded-lg px-2 transition-colors hover:bg-muted/40 [&_input]:cursor-text [&_textarea]:cursor-text";
798
+ var ROW_CENTER = `${ROW} items-center`;
799
+ var ROW_TOP = `${ROW} items-start`;
800
+ function InlineNumber({ value, onChange, min = 0, step = 1, className, disabled, tooltip }) {
801
+ const [draft, setDraft] = useState(String(value));
802
+ const [focused, setFocused] = useState(false);
803
+ useEffect(() => {
804
+ if (!focused) setDraft(String(value));
805
+ }, [value, focused]);
806
+ function commit() {
807
+ setFocused(false);
808
+ const v = parseFloat(draft);
809
+ if (!isNaN(v) && v >= min) {
810
+ onChange(v);
811
+ } else {
812
+ setDraft(String(value));
813
+ }
814
+ }
815
+ const input = /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(
816
+ Input,
817
+ {
818
+ type: "text",
819
+ inputMode: "decimal",
820
+ value: draft,
821
+ onChange: (e) => setDraft(e.target.value),
822
+ onFocus: (e) => {
823
+ setFocused(true);
824
+ e.target.select();
825
+ },
826
+ onBlur: commit,
827
+ onKeyDown: (e) => {
828
+ if (e.key === "Enter") {
829
+ e.preventDefault();
830
+ commit();
831
+ }
832
+ },
833
+ disabled,
834
+ className: "h-auto rounded-none border-0 border-b border-transparent bg-transparent p-0 text-right text-[11px] text-muted-foreground tabular-nums shadow-none hover:border-muted-foreground/40 focus:border-foreground focus-visible:ring-0 focus-visible:ring-offset-0"
835
+ }
836
+ ) });
837
+ if (!tooltip) return input;
838
+ return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 400, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
839
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: input }),
840
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "text-xs", children: tooltip })
841
+ ] }) });
842
+ }
843
+ function StatusSelect({ value, onChange, statuses, bookingDate }) {
844
+ const [open, setOpen] = useState(false);
845
+ const ref = useRef(null);
846
+ const dropdownRef = useRef(null);
847
+ const current = statuses.find((s) => s.value === value);
848
+ useEffect(() => {
849
+ if (!open) return;
850
+ const handler = (e) => {
851
+ const target = e.target;
852
+ if (ref.current?.contains(target)) return;
853
+ if (dropdownRef.current?.contains(target)) return;
854
+ setOpen(false);
855
+ };
856
+ document.addEventListener("mousedown", handler);
857
+ return () => document.removeEventListener("mousedown", handler);
858
+ }, [open]);
859
+ const btnRef = useRef(null);
860
+ const [dropRect, setDropRect] = useState(null);
861
+ function handleOpen() {
862
+ if (btnRef.current) setDropRect(btnRef.current.getBoundingClientRect());
863
+ setOpen((p) => !p);
864
+ }
865
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative flex-1", children: [
866
+ /* @__PURE__ */ jsxs(
867
+ Button,
868
+ {
869
+ variant: "bare",
870
+ size: "bare",
871
+ ref: btnRef,
872
+ type: "button",
873
+ onClick: handleOpen,
874
+ className: "font-normal justify-start whitespace-normal flex items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted/30 transition-colors text-left w-full",
875
+ children: [
876
+ current && /* @__PURE__ */ jsxs(Fragment, { children: [
877
+ /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full shrink-0", style: { backgroundColor: current.color } }),
878
+ /* @__PURE__ */ jsx("span", { children: current.label })
879
+ ] }),
880
+ /* @__PURE__ */ jsx("svg", { className: "ml-auto h-3 w-3 text-muted-foreground shrink-0", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
881
+ ]
882
+ }
883
+ ),
884
+ open && dropRect && createPortal(
885
+ /* @__PURE__ */ jsx(
886
+ "div",
887
+ {
888
+ ref: dropdownRef,
889
+ "data-modal-passthrough": true,
890
+ style: { position: "fixed", left: dropRect.left, top: dropRect.bottom + 4, width: dropRect.width, zIndex: 9999, pointerEvents: "auto" },
891
+ className: "fayz-glass-surface rounded-lg border bg-popover shadow-lg py-1 animate-in fade-in zoom-in-95 duration-100",
892
+ children: statuses.map((s) => {
893
+ const available = !bookingDate || isStatusAvailable(s, bookingDate);
894
+ return /* @__PURE__ */ jsxs(
895
+ Button,
896
+ {
897
+ variant: "bare",
898
+ size: "bare",
899
+ type: "button",
900
+ disabled: !available,
901
+ onClick: () => {
902
+ if (available) {
903
+ onChange(s.value);
904
+ setOpen(false);
905
+ }
906
+ },
907
+ className: `font-normal justify-start whitespace-normal flex w-full items-center gap-2.5 px-3 py-1.5 text-sm transition-colors ${!available ? "disabled:pointer-events-auto disabled:cursor-not-allowed disabled:opacity-30" : s.value === value ? "bg-muted/30" : "hover:bg-muted/50"}`,
908
+ children: [
909
+ /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full shrink-0", style: { backgroundColor: s.color } }),
910
+ /* @__PURE__ */ jsx("span", { children: s.label })
911
+ ]
912
+ },
913
+ s.value
914
+ );
915
+ })
916
+ }
917
+ ),
918
+ document.body
919
+ )
920
+ ] });
921
+ }
922
+ function ProfessionalSelect({ value, onChange, professionals }) {
923
+ const t = useTranslation();
924
+ const options = useMemo(
925
+ () => professionals.map((p) => ({ id: p.id, name: p.name, avatarUrl: p.avatarUrl })),
926
+ [professionals]
927
+ );
928
+ return /* @__PURE__ */ jsx(
929
+ PersonPicker,
930
+ {
931
+ variant: "inline",
932
+ options,
933
+ value,
934
+ onChange,
935
+ placeholder: t("agenda.appointment.selectProfessional"),
936
+ searchPlaceholder: t("agenda.appointment.searchProfessional"),
937
+ emptyLabel: t("agenda.appointment.noProfessional"),
938
+ className: "min-w-[11rem]"
939
+ }
940
+ );
941
+ }
942
+ function LocationSelect({ value, onChange, locations, placeholder }) {
943
+ const t = useTranslation();
944
+ const [open, setOpen] = useState(false);
945
+ const ref = useRef(null);
946
+ const current = locations.find((l) => l.id === value);
947
+ useEffect(() => {
948
+ if (!open) return;
949
+ const handler = (e) => {
950
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
951
+ };
952
+ document.addEventListener("mousedown", handler);
953
+ return () => document.removeEventListener("mousedown", handler);
954
+ }, [open]);
955
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative flex-1", children: [
956
+ /* @__PURE__ */ jsxs(
957
+ Button,
958
+ {
959
+ variant: "bare",
960
+ size: "bare",
961
+ type: "button",
962
+ onClick: () => setOpen((p) => !p),
963
+ className: "font-normal justify-start whitespace-normal flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted/30 transition-colors text-left",
964
+ children: [
965
+ current ? /* @__PURE__ */ jsx("span", { children: current.name }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder ?? t("agenda.appointment.selectLocation") }),
966
+ /* @__PURE__ */ jsx("svg", { className: "ml-auto h-3 w-3 text-muted-foreground shrink-0", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M4 6l4 4 4-4", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
967
+ ]
968
+ }
969
+ ),
970
+ open && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg py-1 animate-in fade-in zoom-in-95 duration-100 max-h-48 overflow-y-auto", children: locations.map((l) => /* @__PURE__ */ jsx(
971
+ Button,
972
+ {
973
+ variant: "bare",
974
+ size: "bare",
975
+ type: "button",
976
+ onClick: () => {
977
+ onChange(l.id);
978
+ setOpen(false);
979
+ },
980
+ className: `font-normal justify-start rounded-none whitespace-normal flex w-full items-center gap-2 px-3 py-1.5 text-sm hover:bg-muted/50 transition-colors ${l.id === value ? "bg-muted/30" : ""}`,
981
+ children: /* @__PURE__ */ jsx("span", { children: l.name })
982
+ },
983
+ l.id
984
+ )) })
985
+ ] });
986
+ }
987
+ function rotuloDoTipo(t, id, fallback) {
988
+ const chave = `agenda.bookingType.${id}`;
989
+ const traduzido = t(chave);
990
+ return !traduzido || traduzido === chave ? fallback : traduzido;
991
+ }
992
+ function AppointmentModal({ open, mode, bookingId, prefill, initialTab, externalSource, onClose, onCreated }) {
993
+ const t = useTranslation();
994
+ const locale = getCurrentLocale();
995
+ const config = useAgendaConfig();
996
+ const provider = useAgendaProvider();
997
+ const activeUnitId = useActiveUnitId();
998
+ const initialSnapshot = useRef("");
999
+ const createBooking = useAgendaStore((s) => s.createBooking);
1000
+ const guardBookings = useLimitGuard("bookings_month");
1001
+ const updateBooking = useAgendaStore((s) => s.updateBooking);
1002
+ const deleteBooking = useAgendaStore((s) => s.deleteBooking);
1003
+ const createBookingSeries = useAgendaStore((s) => s.createBookingSeries);
1004
+ const updateBookingSeries = useAgendaStore((s) => s.updateBookingSeries);
1005
+ const deleteBookingSeries = useAgendaStore((s) => s.deleteBookingSeries);
1006
+ const updateBookingStatus = useAgendaStore((s) => s.updateBookingStatus);
1007
+ const fetchBookings = useAgendaStore((s) => s.fetchBookings);
1008
+ const agendaDateRange = useAgendaStore((s) => s.dateRange);
1009
+ const professionals = useAgendaStore((s) => s.professionals);
1010
+ const storeLocations = useAgendaStore((s) => s.locations);
1011
+ const loadedBookings = useAgendaStore((s) => s.bookings);
1012
+ const defaultBookingType = config.bookingTypes[0]?.id ?? "appointment";
1013
+ const [loading, setLoading] = useState(mode === "edit");
1014
+ const [bookingType, setBookingType] = useState(prefill?.kind ?? defaultBookingType);
1015
+ const [title, setTitle] = useState("");
1016
+ const [clientId, setClientId] = useState(prefill?.clientId ?? "");
1017
+ const [clientSearch, setClientSearch] = useState("");
1018
+ const [clientPhone, setClientPhone] = useState("");
1019
+ const [professionalId, setProfessionalId] = useState(prefill?.professionalId ?? "");
1020
+ const [locationId, setLocationId] = useState(prefill?.locationId ?? "");
1021
+ const [resourceId, setResourceId] = useState(prefill?.resourceId ?? "");
1022
+ const [services, setServices] = useState([]);
1023
+ const [attendees, setAttendees] = useState([]);
1024
+ const [attendeeSeq, setAttendeeSeq] = useState(0);
1025
+ const [serviceSearch, setServiceSearch] = useState("");
1026
+ const [interests, setInterests] = useState([]);
1027
+ const [serviceKinds, setServiceKinds] = useState({});
1028
+ const [revisionChoice, setRevisionChoice] = useState(null);
1029
+ const [revisionState, setRevisionState] = useState("loading");
1030
+ const [date, setDate] = useState(() => {
1031
+ if (prefill?.startsAt) return prefill.startsAt.slice(0, 10);
1032
+ return todayLocal();
1033
+ });
1034
+ const [startTime, setStartTime] = useState(() => {
1035
+ if (prefill?.startsAt) {
1036
+ const d = new Date(prefill.startsAt);
1037
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
1038
+ }
1039
+ return "09:00";
1040
+ });
1041
+ const [status, setStatus] = useState("scheduled");
1042
+ const [durationOverride, setDurationOverride] = useState(null);
1043
+ const [editingEnd, setEditingEnd] = useState(false);
1044
+ const [endDraft, setEndDraft] = useState(null);
1045
+ const [recurrencePreset, setRecurrencePreset] = useState("none");
1046
+ const [recurrenceEndMode, setRecurrenceEndMode] = useState("count");
1047
+ const [recurrenceUntil, setRecurrenceUntil] = useState("");
1048
+ const [recurrenceCount, setRecurrenceCount] = useState(4);
1049
+ const [seriesId, setSeriesId] = useState(null);
1050
+ const [seriesSize, setSeriesSize] = useState(0);
1051
+ const [seriesConflicts, setSeriesConflicts] = useState(null);
1052
+ const [checkingSeries, setCheckingSeries] = useState(false);
1053
+ const [scopeAsk, setScopeAsk] = useState(null);
1054
+ const [scopeChoice, setScopeChoice] = useState("this");
1055
+ const initialDate = useRef("");
1056
+ const [markingWaiting, setMarkingWaiting] = useState(false);
1057
+ const [preenchendoForm, setPreenchendoForm] = useState(null);
1058
+ const [formsVersao, setFormsVersao] = useState(0);
1059
+ const professionalOptions = React3.useMemo(() => {
1060
+ const doLocal = professionalsForLocation(professionals, locationId, professionalId);
1061
+ if (!professionalId || doLocal.some((p) => p.id === professionalId)) return doLocal;
1062
+ const nome = loadedBookings.find((b) => b.id === bookingId)?.professionalName;
1063
+ return [...doLocal, { id: professionalId, name: nome ?? "\u2014", avatarUrl: null }];
1064
+ }, [professionals, professionalId, locationId, loadedBookings, bookingId]);
1065
+ React3.useEffect(() => {
1066
+ const fora = shouldClearProfessional(professionals, locationId, professionalId);
1067
+ if (!fora) return;
1068
+ setProfessionalId("");
1069
+ toast.info(`${fora.name} n\xE3o atende neste local`);
1070
+ }, [locationId, professionalId, professionals]);
1071
+ const [notes, setNotes] = useState("");
1072
+ const [showNotes, setShowNotes] = useState(false);
1073
+ const [saving, setSaving] = useState(false);
1074
+ const [conflict, setConflict] = useState(false);
1075
+ const [roomConflict, setRoomConflict] = useState(false);
1076
+ const [serviceOptions, setServiceOptions] = useState([]);
1077
+ const [initialServiceOptions, setInitialServiceOptions] = useState([]);
1078
+ const [editingDate, setEditingDate] = useState(false);
1079
+ const [editingTime, setEditingTime] = useState(false);
1080
+ const [confirmingDelete, setConfirmingDelete] = useState(false);
1081
+ const [deleting, setDeleting] = useState(false);
1082
+ const [editOrderId, setEditOrderId] = useState(null);
1083
+ const [editOrderTotal, setEditOrderTotal] = useState(0);
1084
+ const [editPaymentStatus, setEditPaymentStatus] = useState("none");
1085
+ const [paymentStatusLoading, setPaymentStatusLoading] = useState(false);
1086
+ const [modalTab, setModalTab] = useState("appointment");
1087
+ useEffect(() => {
1088
+ if (!clientId && modalTab !== "appointment") setModalTab("appointment");
1089
+ }, [clientId, modalTab]);
1090
+ const [clientData, setClientData] = useState(null);
1091
+ const [clientDataLoading, setClientDataLoading] = useState(false);
1092
+ const [clientEditing, setClientEditing] = useState(false);
1093
+ const [cancellationReasons, setCancellationReasons] = useState([]);
1094
+ const [cancellationReasonId, setCancellationReasonId] = useState("");
1095
+ const [cancellationNotes, setCancellationNotes] = useState("");
1096
+ const [cancellationReasonsLoading, setCancellationReasonsLoading] = useState(false);
1097
+ const clientProvider = useMemo(
1098
+ () => config.clientEntityDef ? resolveDataProvider(config.clientEntityDef) : null,
1099
+ [config.clientEntityDef]
1100
+ );
1101
+ const saveClientField = async (key, value) => {
1102
+ if (!clientProvider || !clientId) return;
1103
+ await clientProvider.update(clientId, { [key]: value });
1104
+ setClientData((cur) => cur ? { ...cur, [key]: value } : cur);
1105
+ };
1106
+ const activeType = config.bookingTypes.find((bt) => bt.id === bookingType) ?? config.bookingTypes[0];
1107
+ const requestKey = [
1108
+ mode,
1109
+ bookingId ?? "",
1110
+ initialTab ?? "",
1111
+ prefill?.startsAt ?? "",
1112
+ prefill?.endsAt ?? "",
1113
+ prefill?.professionalId ?? "",
1114
+ prefill?.locationId ?? "",
1115
+ prefill?.clientId ?? "",
1116
+ prefill?.kind ?? ""
1117
+ ].join("|");
1118
+ useEffect(() => {
1119
+ if (!open) return;
1120
+ setClientId(prefill?.clientId ?? "");
1121
+ setClientSearch("");
1122
+ setClientPhone("");
1123
+ setProfessionalId(prefill?.professionalId ?? "");
1124
+ setLocationId(prefill?.locationId ?? "");
1125
+ setResourceId("");
1126
+ setServices([]);
1127
+ setAttendees([]);
1128
+ setDurationOverride(null);
1129
+ setRevisionChoice(null);
1130
+ setServiceSearch("");
1131
+ setNotes("");
1132
+ setStatus("scheduled");
1133
+ setShowNotes(false);
1134
+ setConflict(false);
1135
+ setLoading(mode === "edit");
1136
+ setBookingType(prefill?.kind ?? defaultBookingType);
1137
+ setTitle("");
1138
+ setEditingDate(false);
1139
+ setEditingTime(false);
1140
+ setConfirmingDelete(false);
1141
+ setDeleting(false);
1142
+ setEditOrderId(null);
1143
+ setEditOrderTotal(0);
1144
+ setEditPaymentStatus("none");
1145
+ setPaymentStatusLoading(false);
1146
+ setClientData(null);
1147
+ setClientDataLoading(false);
1148
+ setClientEditing(false);
1149
+ setCancellationReasonId("");
1150
+ setCancellationNotes("");
1151
+ setCancellationReasonsLoading(false);
1152
+ setRecurrencePreset("none");
1153
+ setRecurrenceEndMode("count");
1154
+ setRecurrenceUntil("");
1155
+ setRecurrenceCount(4);
1156
+ setSeriesId(null);
1157
+ setSeriesSize(0);
1158
+ setSeriesConflicts(null);
1159
+ setCheckingSeries(false);
1160
+ setScopeAsk(null);
1161
+ setScopeChoice("this");
1162
+ initialDate.current = prefill?.startsAt ? prefill.startsAt.slice(0, 10) : todayLocal();
1163
+ setModalTab(initialTab || "appointment");
1164
+ if (!prefill?.professionalId && professionals.length === 1) setProfessionalId(professionals[0].id);
1165
+ setDate(() => {
1166
+ if (prefill?.startsAt) return prefill.startsAt.slice(0, 10);
1167
+ return todayLocal();
1168
+ });
1169
+ setStartTime(() => {
1170
+ if (prefill?.startsAt) {
1171
+ const d = new Date(prefill.startsAt);
1172
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
1173
+ }
1174
+ return "09:00";
1175
+ });
1176
+ }, [open, requestKey]);
1177
+ useEffect(() => {
1178
+ if (!open || locationId) return;
1179
+ const opts = storeLocations.length > 0 ? storeLocations : config.locations;
1180
+ if (opts.length === 1) setLocationId(opts[0].id);
1181
+ }, [open, locationId, storeLocations, config.locations]);
1182
+ const [storeResources, setStoreResources] = useState([]);
1183
+ useEffect(() => {
1184
+ if (!open || !provider.getResources) return;
1185
+ provider.getResources().then(setStoreResources).catch(() => setStoreResources([]));
1186
+ }, [open, provider]);
1187
+ useEffect(() => {
1188
+ if (!open || mode !== "edit" || !bookingId) return;
1189
+ const hydrate = (b) => {
1190
+ setBookingType(b.kind ?? defaultBookingType);
1191
+ setTitle(b.clientName ?? "");
1192
+ setClientId(b.clientId ?? "");
1193
+ setClientSearch(b.clientName ?? "");
1194
+ setProfessionalId(b.professionalId ?? "");
1195
+ setLocationId(b.locationId ?? "");
1196
+ setResourceId(b.resourceId ?? "");
1197
+ setDate(b.startsAt.slice(0, 10));
1198
+ initialDate.current = b.startsAt.slice(0, 10);
1199
+ const d = new Date(b.startsAt);
1200
+ setStartTime(`${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`);
1201
+ const serie = b.seriesId ?? null;
1202
+ setSeriesId(serie);
1203
+ if (serie) {
1204
+ Promise.resolve(provider.countSeriesOccurrences?.(serie)).then((n) => setSeriesSize(n ?? 0)).catch(() => setSeriesSize(0));
1205
+ provider.getRecurrence?.(serie).then((regra) => {
1206
+ if (regra) setRecurrencePreset(presetForRule(regra));
1207
+ }).catch(() => {
1208
+ });
1209
+ }
1210
+ setStatus(b.status);
1211
+ setNotes(b.notes ?? "");
1212
+ if (b.notes) setShowNotes(true);
1213
+ if (b.services) setServices(b.services.map((s) => ({
1214
+ serviceId: s.serviceId ?? "",
1215
+ name: s.name,
1216
+ durationMinutes: s.durationMinutes,
1217
+ price: s.price
1218
+ })));
1219
+ if (b.attendees) setAttendees(b.attendees.map((a) => ({ personId: a.personId, name: a.name })));
1220
+ const somaServicos = (b.services ?? []).reduce((acc, s) => acc + (s.durationMinutes ?? 0), 0);
1221
+ const gravada = b.endsAt ? Math.round((new Date(b.endsAt).getTime() - new Date(b.startsAt).getTime()) / 6e4) : 0;
1222
+ const override = somaServicos > 0 && gravada > 0 && gravada !== somaServicos ? gravada : null;
1223
+ setDurationOverride(override);
1224
+ setEditOrderId(b.orderId ?? null);
1225
+ setEditOrderTotal(b.orderTotal ?? 0);
1226
+ setEditPaymentStatus(b.paymentStatus ?? "none");
1227
+ const svcSnap = b.services ? b.services.map((s) => ({ id: s.serviceId ?? "", dur: s.durationMinutes, price: s.price })) : [];
1228
+ initialSnapshot.current = JSON.stringify({
1229
+ title: b.clientName ?? "",
1230
+ clientId: b.clientId ?? "",
1231
+ professionalId: b.professionalId ?? "",
1232
+ locationId: b.locationId ?? "",
1233
+ date: b.startsAt.slice(0, 10),
1234
+ startTime: `${String(new Date(b.startsAt).getHours()).padStart(2, "0")}:${String(new Date(b.startsAt).getMinutes()).padStart(2, "0")}`,
1235
+ status: b.status,
1236
+ notes: b.notes ?? "",
1237
+ cancellationReasonId: "",
1238
+ cancellationNotes: "",
1239
+ services: svcSnap,
1240
+ dur: override,
1241
+ // Sem isto, uma edição que SÓ acrescenta um convidado não conta como
1242
+ // alteração e o botão "Atualizar" fica desabilitado para sempre — o
1243
+ // mesmo defeito que os serviços tiveram e que fez a lista vazia travar
1244
+ // o formulário.
1245
+ attendees: (b.attendees ?? []).map((a) => a.personId).sort()
1246
+ });
1247
+ setLoading(false);
1248
+ if (b.orderId && config.financialBridge) {
1249
+ setPaymentStatusLoading(true);
1250
+ config.financialBridge.resolvePaymentStatuses([b.orderId]).then((map) => {
1251
+ const summary = map.get(b.orderId);
1252
+ if (summary) {
1253
+ setEditPaymentStatus(summary.status);
1254
+ setEditOrderTotal(summary.totalAmount);
1255
+ }
1256
+ }).catch(() => {
1257
+ }).finally(() => setPaymentStatusLoading(false));
1258
+ }
1259
+ };
1260
+ const cached = loadedBookings.find((b) => b.id === bookingId);
1261
+ if (cached) {
1262
+ hydrate(cached);
1263
+ return;
1264
+ }
1265
+ setLoading(true);
1266
+ provider.getBookingById(bookingId).then((b) => {
1267
+ if (b) hydrate(b);
1268
+ else setLoading(false);
1269
+ }).catch(() => setLoading(false));
1270
+ }, [open, requestKey]);
1271
+ useEffect(() => {
1272
+ if (modalTab !== "client" || !clientId || clientData) return;
1273
+ setClientDataLoading(true);
1274
+ const load = clientProvider ? clientProvider.list({ filters: { id: clientId }, pageSize: 1, countMode: "none" }).then((r) => r.data?.[0] ?? null) : config.contactLookup ? config.contactLookup.getById(clientId).then((result) => result?.data ?? null) : Promise.resolve(null);
1275
+ load.then((row) => {
1276
+ setClientData(row);
1277
+ setClientDataLoading(false);
1278
+ }).catch(() => setClientDataLoading(false));
1279
+ }, [modalTab, clientId]);
1280
+ useEffect(() => {
1281
+ if (!open || status !== "cancelled" || !config.cancellationDetails || cancellationReasons.length > 0) return;
1282
+ setCancellationReasonsLoading(true);
1283
+ config.cancellationDetails.listReasons().then((reasons) => setCancellationReasons(reasons)).catch(() => setCancellationReasons([])).finally(() => setCancellationReasonsLoading(false));
1284
+ }, [open, status, config.cancellationDetails, cancellationReasons.length]);
1285
+ useEffect(() => {
1286
+ if (!open || !config.serviceLookup) return;
1287
+ const lookup = config.serviceLookup;
1288
+ const fetch = lookup.list ? () => lookup.list() : () => lookup.search("");
1289
+ fetch().then((results) => {
1290
+ const opts = results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, price: r.price, data: r.data }));
1291
+ setInitialServiceOptions(opts);
1292
+ }).catch(() => setInitialServiceOptions([]));
1293
+ }, [open]);
1294
+ useEffect(() => {
1295
+ if (!config.serviceLookup || serviceSearch.length < 1) {
1296
+ setServiceOptions([]);
1297
+ return;
1298
+ }
1299
+ const timer = setTimeout(async () => {
1300
+ try {
1301
+ const results = await config.serviceLookup.search(serviceSearch);
1302
+ setServiceOptions(results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, price: r.price, data: r.data })));
1303
+ } catch {
1304
+ setServiceOptions([]);
1305
+ }
1306
+ }, 250);
1307
+ return () => clearTimeout(timer);
1308
+ }, [serviceSearch]);
1309
+ const serviceDuration = services.reduce((sum, s) => sum + s.durationMinutes, 0);
1310
+ const selectionDuration = useMemo(() => {
1311
+ if (!prefill?.startsAt || !prefill?.endsAt) return 0;
1312
+ return Math.round((new Date(prefill.endsAt).getTime() - new Date(prefill.startsAt).getTime()) / 6e4);
1313
+ }, [prefill?.startsAt, prefill?.endsAt]);
1314
+ const totalDuration = durationOverride ?? (serviceDuration > 0 ? serviceDuration : selectionDuration);
1315
+ const totalPrice = services.reduce((sum, s) => sum + s.price, 0);
1316
+ const { currency } = config;
1317
+ const endTime = useMemo(() => {
1318
+ const [h, m] = startTime.split(":").map(Number);
1319
+ const total = h * 60 + m + totalDuration;
1320
+ return `${String(Math.floor(total / 60) % 24).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
1321
+ }, [startTime, totalDuration]);
1322
+ const janela = useMemo(() => ({
1323
+ startsAt: (/* @__PURE__ */ new Date(`${date}T${startTime}:00`)).toISOString(),
1324
+ endsAt: (/* @__PURE__ */ new Date(`${date}T${endTime}:00`)).toISOString()
1325
+ }), [date, startTime, endTime]);
1326
+ useEffect(() => {
1327
+ if (!professionalId || totalDuration === 0) {
1328
+ setConflict(false);
1329
+ return;
1330
+ }
1331
+ const timer = setTimeout(() => {
1332
+ provider.checkConflict({
1333
+ assigneeId: professionalId,
1334
+ ...janela,
1335
+ excludeBookingId: bookingId
1336
+ }).then(setConflict).catch(() => setConflict(false));
1337
+ }, 300);
1338
+ return () => clearTimeout(timer);
1339
+ }, [professionalId, janela, bookingId, totalDuration]);
1340
+ const recurrenceRule = useMemo(() => {
1341
+ const base = ruleForPreset(recurrencePreset);
1342
+ if (!base) return null;
1343
+ if (recurrenceEndMode === "until") {
1344
+ if (!recurrenceUntil) return null;
1345
+ return { ...base, until: recurrenceUntil, count: null };
1346
+ }
1347
+ return { ...base, until: null, count: Math.max(1, Math.min(RECURRENCE_MAX_OCCURRENCES, recurrenceCount)) };
1348
+ }, [recurrencePreset, recurrenceEndMode, recurrenceUntil, recurrenceCount]);
1349
+ const expansion = useMemo(
1350
+ () => recurrenceRule ? expandRecurrence(date, recurrenceRule) : null,
1351
+ [date, recurrenceRule]
1352
+ );
1353
+ const seriesDates = expansion && expansion.dates.length > 1 ? expansion.dates : null;
1354
+ useEffect(() => {
1355
+ if (!open || !seriesDates || !professionalId || totalDuration === 0 || !provider.checkSeriesConflicts) {
1356
+ setSeriesConflicts(null);
1357
+ setCheckingSeries(false);
1358
+ return;
1359
+ }
1360
+ setCheckingSeries(true);
1361
+ const timer = setTimeout(() => {
1362
+ const windows = seriesDates.map((dia) => {
1363
+ const inicio = /* @__PURE__ */ new Date(`${dia}T${startTime}:00`);
1364
+ return {
1365
+ startsAt: inicio.toISOString(),
1366
+ endsAt: new Date(inicio.getTime() + totalDuration * 6e4).toISOString()
1367
+ };
1368
+ });
1369
+ provider.checkSeriesConflicts({
1370
+ assigneeId: professionalId,
1371
+ windows,
1372
+ excludeBookingId: bookingId,
1373
+ unitId: activeUnitId
1374
+ }).then(setSeriesConflicts).catch(() => setSeriesConflicts(null)).finally(() => setCheckingSeries(false));
1375
+ }, 400);
1376
+ return () => {
1377
+ clearTimeout(timer);
1378
+ setCheckingSeries(false);
1379
+ };
1380
+ }, [open, seriesDates, professionalId, startTime, totalDuration, bookingId, activeUnitId, provider]);
1381
+ const [feriado, setFeriado] = React3.useState(null);
1382
+ useEffect(() => {
1383
+ if (!open || !date || !provider.getHolidays) {
1384
+ setFeriado(null);
1385
+ return;
1386
+ }
1387
+ provider.getHolidays({ from: date, to: date }, activeUnitId).then((rows) => {
1388
+ const fecha = rows.filter((h) => h.closes && h.date === date);
1389
+ setFeriado(fecha.length ? fecha.map((h) => h.name).join(" \xB7 ") : null);
1390
+ }).catch(() => setFeriado(null));
1391
+ }, [open, date, activeUnitId, provider]);
1392
+ useEffect(() => {
1393
+ if (!resourceId || totalDuration === 0 || !provider.checkResourceConflict) {
1394
+ setRoomConflict(false);
1395
+ return;
1396
+ }
1397
+ const timer = setTimeout(() => {
1398
+ provider.checkResourceConflict({
1399
+ resourceId,
1400
+ ...janela,
1401
+ excludeBookingId: bookingId
1402
+ }).then(setRoomConflict).catch(() => setRoomConflict(false));
1403
+ }, 300);
1404
+ return () => clearTimeout(timer);
1405
+ }, [resourceId, janela, bookingId, totalDuration]);
1406
+ function addService(opt) {
1407
+ const duration = opt.data?.duration_minutes || 60;
1408
+ setServices((p) => [...p, { serviceId: opt.id, name: opt.label, durationMinutes: duration, price: opt.price ?? 0 }]);
1409
+ setDurationOverride(null);
1410
+ setServiceSearch("");
1411
+ setServiceOptions([]);
1412
+ }
1413
+ function removeService(idx) {
1414
+ setServices((p) => p.filter((_, i) => i !== idx));
1415
+ setDurationOverride(null);
1416
+ }
1417
+ function changeEndTime(value) {
1418
+ if (!value) return;
1419
+ const [sh, sm] = startTime.split(":").map(Number);
1420
+ const [eh, em] = value.split(":").map(Number);
1421
+ const minutes = eh * 60 + em - (sh * 60 + sm);
1422
+ if (minutes <= 0) return;
1423
+ setDurationOverride(minutes === (serviceDuration > 0 ? serviceDuration : selectionDuration) ? null : minutes);
1424
+ }
1425
+ const serviceCreate = useLookupCreate(
1426
+ config.serviceLookup,
1427
+ (created) => {
1428
+ const duration = created.data?.duration_minutes || 60;
1429
+ setServices((p) => [...p, {
1430
+ serviceId: created.id,
1431
+ name: created.label,
1432
+ durationMinutes: duration,
1433
+ price: created.price ?? 0
1434
+ }]);
1435
+ setServiceSearch("");
1436
+ setServiceOptions([]);
1437
+ },
1438
+ { variant: "inline" }
1439
+ );
1440
+ const agendaPrefs = useTenantPluginSettings("agenda", { interestsOnBooking: true, recurringBookings: true });
1441
+ const recurrenceEnabled = agendaPrefs.get("recurringBookings");
1442
+ useEffect(() => {
1443
+ const ids = services.map((s) => s.serviceId).filter(Boolean);
1444
+ if (ids.length === 0 || !provider.getServiceKinds) {
1445
+ setServiceKinds({});
1446
+ return;
1447
+ }
1448
+ let vivo = true;
1449
+ provider.getServiceKinds(ids).then((m) => {
1450
+ if (vivo) setServiceKinds(m);
1451
+ }).catch(() => {
1452
+ if (vivo) setServiceKinds({});
1453
+ });
1454
+ return () => {
1455
+ vivo = false;
1456
+ };
1457
+ }, [services, provider]);
1458
+ const showRevision = mode === "create" && !!clientId && services.some((s) => serviceKinds[s.serviceId] === "return");
1459
+ useEffect(() => {
1460
+ setRevisionChoice(null);
1461
+ }, [clientId]);
1462
+ const showInterests = agendaPrefs.get("interestsOnBooking") && !!provider.getClientInterests && !!provider.setBookingInterests && !!clientId && services.some((s) => serviceKinds[s.serviceId] === "evaluation");
1463
+ async function persistInterests(id) {
1464
+ if (!showInterests) return;
1465
+ try {
1466
+ const r = await provider.setBookingInterests(id, interests.map((i) => ({ productId: i.productId })));
1467
+ if (!r.ok) throw new Error(r.reason);
1468
+ } catch {
1469
+ toast.error("Interesses n\xE3o vinculados a este agendamento");
1470
+ }
1471
+ }
1472
+ function bookingPayload() {
1473
+ return {
1474
+ clientId,
1475
+ professionalId,
1476
+ locationId: locationId || void 0,
1477
+ resourceId: resourceId || null,
1478
+ notes: notes || void 0,
1479
+ title: activeType.fields.title ? title.trim() : void 0,
1480
+ services: services.map((s) => ({ serviceId: s.serviceId, name: s.name, durationMinutes: s.durationMinutes, price: s.price })),
1481
+ ...showRevision && revisionChoice ? { revisionOf: revisionChoice } : {},
1482
+ attendees: activeType.fields.attendees ? attendees.map((a) => ({ personId: a.personId })) : void 0
1483
+ };
1484
+ }
1485
+ async function handleSubmit(e) {
1486
+ e.preventDefault();
1487
+ if (!canSubmit) return;
1488
+ if (mode === "edit" && seriesId && seriesSize > 1) {
1489
+ setScopeAsk("save");
1490
+ return;
1491
+ }
1492
+ await commitSave("this");
1493
+ }
1494
+ async function commitSave(scope) {
1495
+ setScopeAsk(null);
1496
+ if (mode === "create" && await guardBookings() === "blocked") return;
1497
+ setSaving(true);
1498
+ try {
1499
+ const startsAt = (/* @__PURE__ */ new Date(`${date}T${startTime}:00`)).toISOString();
1500
+ const endsAt = durationOverride != null ? new Date(new Date(startsAt).getTime() + durationOverride * 6e4).toISOString() : void 0;
1501
+ if (mode === "create") {
1502
+ const input = { kind: bookingType, startsAt, endsAt, ...bookingPayload() };
1503
+ const firstId = seriesDates && recurrenceRule ? (await createBookingSeries(input, recurrenceRule, seriesDates)).bookingIds[0] : (await createBooking(input)).id;
1504
+ invalidateLimit("bookings_month");
1505
+ if (firstId) {
1506
+ await persistInterests(firstId);
1507
+ onClose();
1508
+ onCreated?.(firstId, externalSource);
1509
+ } else {
1510
+ onClose();
1511
+ }
1512
+ } else if (bookingId) {
1513
+ const plano = planSeriesEdit({
1514
+ booking: { id: bookingId, seriesId },
1515
+ scope,
1516
+ patch: { startsAt, ...endsAt ? { endsAt } : {}, status, ...bookingPayload() },
1517
+ timeOfDay: startTime,
1518
+ dateChanged: date !== initialDate.current
1519
+ });
1520
+ if (plano.series) await updateBookingSeries(plano.series.seriesId, plano.series.patch, plano.series.timeOfDay);
1521
+ if (plano.single) await updateBooking(bookingId, plano.single);
1522
+ await persistInterests(bookingId);
1523
+ if (status === "cancelled" && config.cancellationDetails && cancellationReasonId) {
1524
+ await config.cancellationDetails.saveCancellationDetails({
1525
+ bookingId,
1526
+ reasonId: cancellationReasonId,
1527
+ notes: cancellationNotes.trim() || void 0,
1528
+ cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
1529
+ });
1530
+ }
1531
+ if (plano.dateStaysLocal) toast.info(t("agenda.recurrence.dateStaysLocal").replace("{{time}}", startTime));
1532
+ onClose();
1533
+ }
1534
+ } catch {
1535
+ }
1536
+ setSaving(false);
1537
+ }
1538
+ async function handleDelete() {
1539
+ if (!bookingId) return;
1540
+ if (seriesId && seriesSize > 1) {
1541
+ setScopeAsk("delete");
1542
+ return;
1543
+ }
1544
+ await commitDelete("this");
1545
+ }
1546
+ async function commitDelete(scope) {
1547
+ if (!bookingId) return;
1548
+ setScopeAsk(null);
1549
+ setDeleting(true);
1550
+ try {
1551
+ if (scope === "series" && seriesId) await deleteBookingSeries(seriesId);
1552
+ else await deleteBooking(bookingId);
1553
+ onClose();
1554
+ } catch {
1555
+ setConfirmingDelete(false);
1556
+ } finally {
1557
+ setDeleting(false);
1558
+ }
1559
+ }
1560
+ const isPaidBooking = editPaymentStatus === "paid";
1561
+ const selectedCancellationReason = cancellationReasons.find((reason) => reason.id === cancellationReasonId);
1562
+ const needsCancellationReason = mode === "edit" && status === "cancelled" && !!config.cancellationDetails;
1563
+ const needsCancellationNotes = needsCancellationReason && !!selectedCancellationReason?.requiresNotes;
1564
+ const formSnapshot = useMemo(
1565
+ () => JSON.stringify({ title, clientId, professionalId, locationId, resourceId, date, startTime, status, notes, cancellationReasonId, cancellationNotes, services: services.map((s) => ({ id: s.serviceId, dur: s.durationMinutes, price: s.price })), dur: durationOverride, attendees: attendees.map((a) => a.personId).sort() }),
1566
+ [title, clientId, professionalId, locationId, resourceId, date, startTime, status, notes, cancellationReasonId, cancellationNotes, services, durationOverride, attendees]
1567
+ );
1568
+ const isDirty = mode === "create" || formSnapshot !== initialSnapshot.current;
1569
+ const canSubmit = (!activeType.fields.title || title.trim()) && (!activeType.requiresClient || clientId) && (!activeType.fields.professional || professionalId) && (!activeType.requiresServices || services.length > 0) && (!needsCancellationReason || !!cancellationReasonId) && (!needsCancellationNotes || !!cancellationNotes.trim()) && // V1: a Revisão only goes in pointing at an executed service.
1570
+ (!showRevision || revisionState === "unavailable" || !!revisionChoice) && !conflict && !saving;
1571
+ const showTabs = config.bookingTypes.length > 1;
1572
+ const modalTitle = mode === "create" ? t("agenda.appointment.new") : title.trim() || clientSearch || t("agenda.appointment.edit");
1573
+ return (
1574
+ // MODAL POR CIMA DO CALENDÁRIO — a mesma moldura do card de negócio do CRM.
1575
+ //
1576
+ // A agenda é tela de TRABALHO EM LOTE: a recepção abre um cartão, corrige o
1577
+ // horário, fecha, abre o próximo — dezenas de vezes por manhã. O painel
1578
+ // vinha na coluna da direita, que é a coluna do assistente: duas coisas
1579
+ // diferentes entrando pela mesma borda, e o calendário espremido a cada
1580
+ // abertura. O modal deixa a semana montada atrás, intacta, e devolve a
1581
+ // largura para o formulário.
1582
+ //
1583
+ // `ModalContent` e não um overlay à mão: é ele que dá Esc, clique fora,
1584
+ // armadilha de foco e o PORTAL — sem o portal o diálogo herda a cor da
1585
+ // barra de onde foi aberto e sai quase-branco sobre cartão branco.
1586
+ /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
1587
+ if (!o) onClose();
1588
+ }, children: /* @__PURE__ */ jsx(ModalContent, { size: "3xl", noPadding: true, className: "gap-0", hideClose: true, children: /* @__PURE__ */ jsxs(ModalBody, { className: "sm:max-h-[75vh] overflow-hidden p-0", children: [
1589
+ /* @__PURE__ */ jsxs(
1590
+ ModalTaskHost,
1591
+ {
1592
+ className: "flex min-h-0 flex-1 flex-col overflow-hidden",
1593
+ header: (stack) => /* @__PURE__ */ jsx(ModalTaskHeader, { title: modalTitle, stack, onClose }),
1594
+ children: [
1595
+ preenchendoForm && clientId && /* @__PURE__ */ jsx(
1596
+ ModalTask,
1597
+ {
1598
+ open: true,
1599
+ title: "Formul\xE1rio do cliente",
1600
+ hint: clientSearch || void 0,
1601
+ onClose: () => setPreenchendoForm(null),
1602
+ children: /* @__PURE__ */ jsx(
1603
+ WidgetSlot,
1604
+ {
1605
+ zone: FORMS_ZONES.formFill,
1606
+ frame: "inline",
1607
+ templateId: preenchendoForm,
1608
+ personId: clientId,
1609
+ personName: clientSearch || void 0,
1610
+ onSaved: () => setFormsVersao((n) => n + 1),
1611
+ onClose: () => setPreenchendoForm(null)
1612
+ }
1613
+ )
1614
+ }
1615
+ ),
1616
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1617
+ /* @__PURE__ */ jsxs("div", { className: "px-5 pt-3 pb-3 shrink-0 space-y-3", children: [
1618
+ showTabs && mode === "create" && !clientId ? /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: config.bookingTypes.map((bt) => {
1619
+ const label = rotuloDoTipo(t, bt.id, bt.label);
1620
+ const isActive = bookingType === bt.id;
1621
+ const BtIcon = BOOKING_TYPE_ICONS[bt.icon] ?? Calendar;
1622
+ return /* @__PURE__ */ jsxs(
1623
+ Button,
1624
+ {
1625
+ variant: "bare",
1626
+ size: "bare",
1627
+ type: "button",
1628
+ onClick: () => setBookingType(bt.id),
1629
+ 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"}`,
1630
+ children: [
1631
+ /* @__PURE__ */ jsx(BtIcon, { className: "h-3.5 w-3.5" }),
1632
+ label,
1633
+ isActive && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 left-0 right-0 h-0.5 rounded-full bg-foreground" })
1634
+ ]
1635
+ },
1636
+ bt.id
1637
+ );
1638
+ }) }) : (mode === "edit" || !!clientId) && (config.clientEntityDef && clientId || clientId || config.modules.financial && editOrderId ? /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b fayz-glass-divider", children: [
1639
+ { id: "appointment", label: rotuloDoTipo(t, bookingType, activeType.label), icon: Calendar, show: true },
1640
+ { id: "client", label: t("agenda.appointment.clientTab"), icon: User, show: !!config.clientEntityDef && !!clientId },
1641
+ // Entre a ficha e o dinheiro, como no V1: "quem é" → "o que
1642
+ // já fez" → "o que deve". Só com o provider sabendo contar.
1643
+ { id: "history", label: t("agenda.history.tab"), icon: History, show: !!clientId && !!provider.getClientHistory },
1644
+ // "Financeiro", como no V1 — e é a CONTA DO CLIENTE inteira,
1645
+ // não só a fatura deste agendamento; por isso basta o cliente.
1646
+ { id: "financial", label: t("agenda.appointment.financialTab"), icon: DollarSign, show: config.modules.financial && (!!clientId || !!editOrderId) }
1647
+ ].filter((tab) => tab.show).map((tab) => {
1648
+ const isActive = modalTab === tab.id;
1649
+ return /* @__PURE__ */ jsxs(
1650
+ Button,
1651
+ {
1652
+ variant: "bare",
1653
+ size: "bare",
1654
+ type: "button",
1655
+ onClick: () => setModalTab(tab.id),
1656
+ 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"}`,
1657
+ children: [
1658
+ /* @__PURE__ */ jsx(tab.icon, { className: "h-3.5 w-3.5" }),
1659
+ tab.label,
1660
+ tab.id === "financial" && !paymentStatusLoading && (() => {
1661
+ const negativo = status === "cancelled" || status === "no_show";
1662
+ const hoje = date === todayLocal();
1663
+ if (editPaymentStatus === "paid") {
1664
+ return /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-success", "aria-label": "Quitado" });
1665
+ }
1666
+ if (editPaymentStatus !== "none" && editPaymentStatus !== "cancelled") {
1667
+ return hoje && !negativo ? /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 text-destructive", "aria-label": "Pendente hoje" }) : /* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5 text-warning", "aria-label": "Em aberto" });
1668
+ }
1669
+ if (editPaymentStatus === "none" && hoje && !negativo && totalPrice > 0) {
1670
+ return /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 text-destructive", "aria-label": "Sem cobran\xE7a levantada" });
1671
+ }
1672
+ return null;
1673
+ })(),
1674
+ isActive && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 left-0 right-0 h-0.5 rounded-full bg-foreground" })
1675
+ ]
1676
+ },
1677
+ tab.id
1678
+ );
1679
+ }) }) : null),
1680
+ modalTab === "appointment" && /* @__PURE__ */ jsxs(Fragment, { children: [
1681
+ /* @__PURE__ */ jsxs("div", { className: `${ROW_TOP} py-1`, children: [
1682
+ /* @__PURE__ */ jsx(Clock, { className: "mt-0.5 h-4 w-4 text-muted-foreground shrink-0" }),
1683
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1684
+ /* @__PURE__ */ jsxs(
1685
+ "div",
1686
+ {
1687
+ className: "flex flex-wrap items-center gap-x-1.5 gap-y-1 text-sm min-w-0",
1688
+ onClick: (e) => {
1689
+ if (e.target.closest('button, input, select, [role="combobox"]')) return;
1690
+ setEditingDate(true);
1691
+ setEditingTime(true);
1692
+ },
1693
+ children: [
1694
+ editingDate ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsx(
1695
+ Input,
1696
+ {
1697
+ type: "date",
1698
+ value: date,
1699
+ autoFocus: true,
1700
+ onChange: (e) => setDate(e.target.value),
1701
+ onBlur: () => setEditingDate(false),
1702
+ className: "h-auto w-auto rounded-none border-0 border-b border-foreground bg-transparent p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 font-medium"
1703
+ }
1704
+ ) }) : /* @__PURE__ */ jsx(
1705
+ Button,
1706
+ {
1707
+ variant: "bare",
1708
+ size: "bare",
1709
+ type: "button",
1710
+ onClick: () => setEditingDate(true),
1711
+ className: "rounded-none shrink-0 border-b border-transparent px-0.5 py-0.5 font-medium transition-colors hover:border-foreground",
1712
+ children: formatCompactDate(date, locale)
1713
+ }
1714
+ ),
1715
+ editingTime ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsx(
1716
+ Input,
1717
+ {
1718
+ type: "time",
1719
+ value: startTime,
1720
+ autoFocus: !editingDate,
1721
+ onChange: (e) => {
1722
+ if (e.target.value) setStartTime(e.target.value);
1723
+ },
1724
+ onBlur: () => setEditingTime(false),
1725
+ className: "h-auto w-auto rounded-none border-0 border-b border-foreground bg-transparent p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 text-xs text-muted-foreground"
1726
+ }
1727
+ ) }) : /* @__PURE__ */ jsx(
1728
+ Button,
1729
+ {
1730
+ variant: "bare",
1731
+ size: "bare",
1732
+ type: "button",
1733
+ onClick: () => setEditingTime(true),
1734
+ className: "font-normal rounded-none shrink-0 border-b border-transparent px-0.5 py-0.5 text-xs text-muted-foreground transition-colors hover:border-foreground",
1735
+ children: startTime
1736
+ }
1737
+ ),
1738
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground/60", children: "\u2014" }),
1739
+ editingEnd ? /* @__PURE__ */ jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsx(
1740
+ Input,
1741
+ {
1742
+ type: "time",
1743
+ value: endDraft ?? endTime,
1744
+ autoFocus: true,
1745
+ "aria-label": t("agenda.appointment.endTime"),
1746
+ onChange: (e) => {
1747
+ setEndDraft(e.target.value);
1748
+ changeEndTime(e.target.value);
1749
+ },
1750
+ onBlur: () => {
1751
+ setEndDraft(null);
1752
+ setEditingEnd(false);
1753
+ },
1754
+ className: "h-auto w-auto rounded-none border-0 border-b border-foreground bg-transparent p-0 shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 text-xs text-muted-foreground"
1755
+ }
1756
+ ) }) : /* @__PURE__ */ jsx(
1757
+ Button,
1758
+ {
1759
+ variant: "bare",
1760
+ size: "bare",
1761
+ type: "button",
1762
+ onClick: () => setEditingEnd(true),
1763
+ "aria-label": t("agenda.appointment.endTime"),
1764
+ className: "font-normal shrink-0 border-b border-transparent px-0.5 py-0.5 text-xs text-muted-foreground transition-colors hover:border-foreground",
1765
+ children: endTime
1766
+ }
1767
+ ),
1768
+ totalDuration > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground/60", children: [
1769
+ "\xB7 ",
1770
+ totalDuration,
1771
+ " min"
1772
+ ] }),
1773
+ activeType.fields.professional && /* @__PURE__ */ jsx("div", { className: "ml-auto shrink-0", children: /* @__PURE__ */ jsx(ProfessionalSelect, { value: professionalId, onChange: setProfessionalId, professionals: professionalOptions }) })
1774
+ ]
1775
+ }
1776
+ ),
1777
+ provider.createRecurringBookings && recurrenceEnabled && /* COLADA na linha de cima: as duas são a mesma frase, e um
1778
+ respiro entre elas as separa em dois assuntos de novo. */
1779
+ /* @__PURE__ */ jsx("div", { className: "-mt-0.5", children: mode === "edit" ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: seriesId && seriesSize > 1 ? t("agenda.recurrence.partOfSeries").replace("{{count}}", String(seriesSize)) + (recurrencePreset !== "none" ? ` \xB7 ${t(`agenda.recurrence.preset.${recurrencePreset}`)}` : "") : t("agenda.recurrence.preset.none") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1780
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [
1781
+ /* @__PURE__ */ jsxs(Select, { value: recurrencePreset, onValueChange: (v) => setRecurrencePreset(v), children: [
1782
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-auto w-auto gap-1 border-0 bg-transparent p-0 text-xs text-muted-foreground shadow-none transition-colors hover:text-foreground focus:ring-0", "aria-label": t("agenda.recurrence.label"), children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1783
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
1784
+ /* @__PURE__ */ jsx(SelectItem, { value: "none", children: t("agenda.recurrence.preset.none") }),
1785
+ /* @__PURE__ */ jsx(SelectItem, { value: "daily", children: t("agenda.recurrence.preset.daily") }),
1786
+ /* @__PURE__ */ jsx(SelectItem, { value: "weekly", children: t("agenda.recurrence.preset.weekly") }),
1787
+ /* @__PURE__ */ jsx(SelectItem, { value: "biweekly", children: t("agenda.recurrence.preset.biweekly") }),
1788
+ /* @__PURE__ */ jsx(SelectItem, { value: "monthly", children: t("agenda.recurrence.preset.monthly") })
1789
+ ] })
1790
+ ] }),
1791
+ recurrencePreset !== "none" && /* @__PURE__ */ jsxs(Fragment, { children: [
1792
+ /* @__PURE__ */ jsxs(Select, { value: recurrenceEndMode, onValueChange: (v) => setRecurrenceEndMode(v), children: [
1793
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-auto w-auto gap-1 border-0 bg-transparent p-0 text-xs text-muted-foreground shadow-none transition-colors hover:text-foreground focus:ring-0", "aria-label": t("agenda.recurrence.ends"), children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1794
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
1795
+ /* @__PURE__ */ jsx(SelectItem, { value: "count", children: t("agenda.recurrence.endCountShort") }),
1796
+ /* @__PURE__ */ jsx(SelectItem, { value: "until", children: t("agenda.recurrence.endUntilShort") })
1797
+ ] })
1798
+ ] }),
1799
+ recurrenceEndMode === "count" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1800
+ /* @__PURE__ */ jsx("div", { className: "w-10 shrink-0", children: /* @__PURE__ */ jsx(
1801
+ Input,
1802
+ {
1803
+ type: "number",
1804
+ min: 1,
1805
+ max: RECURRENCE_MAX_OCCURRENCES,
1806
+ value: recurrenceCount,
1807
+ onChange: (e) => setRecurrenceCount(Number(e.target.value) || 1),
1808
+ "aria-label": t("agenda.recurrence.countLabel"),
1809
+ className: "h-auto rounded-none border-0 border-b border-foreground bg-transparent p-0 text-center text-xs shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
1810
+ }
1811
+ ) }),
1812
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("agenda.recurrence.times") })
1813
+ ] }) : /* @__PURE__ */ jsx(DatePicker, { value: recurrenceUntil, onChange: setRecurrenceUntil })
1814
+ ] })
1815
+ ] }),
1816
+ recurrencePreset !== "none" && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 space-y-1", children: [
1817
+ seriesDates && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("agenda.recurrence.summary").replace("{{count}}", String(seriesDates.length)).replace("{{last}}", formatCompactDate(seriesDates[seriesDates.length - 1], locale)) }),
1818
+ expansion?.truncated && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-warning", children: t("agenda.recurrence.capped").replace("{{max}}", String(RECURRENCE_MAX_OCCURRENCES)) }),
1819
+ !professionalId && seriesDates && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("agenda.recurrence.needsProfessional") }),
1820
+ checkingSeries && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t("agenda.recurrence.checking") }),
1821
+ !checkingSeries && seriesConflicts && seriesDates && (seriesConflicts.busyStarts.length > 0 || seriesConflicts.closedDates.length > 0) && /* @__PURE__ */ jsxs("div", { className: "space-y-0.5 rounded-md bg-warning/10 px-2 py-1.5", children: [
1822
+ seriesConflicts.busyStarts.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-warning", children: t("agenda.recurrence.conflicts").replace("{{busy}}", String(seriesConflicts.busyStarts.length)).replace("{{total}}", String(seriesDates.length)) }),
1823
+ seriesConflicts.closedDates.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-warning", children: t("agenda.recurrence.closedDays").replace("{{closed}}", String(seriesConflicts.closedDates.length)) }),
1824
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t("agenda.recurrence.createsAnyway") })
1825
+ ] })
1826
+ ] })
1827
+ ] }) })
1828
+ ] })
1829
+ ] }),
1830
+ 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: [
1831
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
1832
+ t("agenda.appointment.conflict")
1833
+ ] }),
1834
+ feriado && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-warning/10 px-3 py-1.5 text-xs text-warning-foreground", children: [
1835
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
1836
+ t("agenda.appointment.holidayWarning"),
1837
+ " ",
1838
+ feriado
1839
+ ] }),
1840
+ roomConflict && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg bg-warning/10 px-3 py-1.5 text-xs text-warning-foreground", children: [
1841
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
1842
+ t("agenda.appointment.roomConflict")
1843
+ ] })
1844
+ ] })
1845
+ ] }),
1846
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col flex-1 min-h-0 overflow-hidden", children: [
1847
+ 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(
1848
+ CrudFormPage,
1849
+ {
1850
+ embedded: true,
1851
+ entityDef: config.clientEntityDef,
1852
+ mode: "edit",
1853
+ initialData: clientData,
1854
+ namePlural: "",
1855
+ onCancel: () => setClientEditing(false),
1856
+ onSubmit: async (data) => {
1857
+ try {
1858
+ if (!clientProvider) return;
1859
+ await clientProvider.update(clientId, data);
1860
+ setClientData({ ...clientData, ...data });
1861
+ setClientEditing(false);
1862
+ } catch {
1863
+ }
1864
+ }
1865
+ }
1866
+ ) : /* @__PURE__ */ jsx(
1867
+ CrudDetailPage,
1868
+ {
1869
+ embedded: true,
1870
+ entityDef: config.clientEntityDef,
1871
+ item: clientData,
1872
+ namePlural: "",
1873
+ basePath: "",
1874
+ onBack: () => setModalTab("appointment"),
1875
+ onEdit: () => setClientEditing(true),
1876
+ onFieldSave: clientProvider ? saveClientField : void 0,
1877
+ extraActions: config.clientRecordPath ? [{
1878
+ id: "full-record",
1879
+ label: t("agenda.appointment.fullRecord"),
1880
+ icon: ExternalLink,
1881
+ // `utility` fica AO LADO do Editar (ícone + tooltip);
1882
+ // secondary sumiria no caret.
1883
+ intent: "utility",
1884
+ onSelect: () => {
1885
+ navigateApp(`#${config.clientRecordPath}/${clientId}`);
1886
+ onClose();
1887
+ }
1888
+ }] : void 0
1889
+ }
1890
+ ) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: t("agenda.appointment.noClientData") }) }),
1891
+ modalTab === "history" && clientId && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(
1892
+ ClientHistoryPanel,
1893
+ {
1894
+ clientId,
1895
+ currentBookingId: bookingId,
1896
+ formatCurrency: (v) => formatCurrency(v, currency.code)
1897
+ }
1898
+ ) }),
1899
+ modalTab === "financial" && clientId && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto px-5 py-4", children: /* @__PURE__ */ jsx(
1900
+ WidgetSlot,
1901
+ {
1902
+ zone: FINANCIAL_ZONES.clientAccountInline,
1903
+ personId: clientId,
1904
+ orderId: editOrderId,
1905
+ clientName: clientSearch || void 0,
1906
+ currency,
1907
+ onGenerate: bookingId && provider.chargeCheckout ? async () => {
1908
+ const r = await provider.chargeCheckout(bookingId);
1909
+ if (!r.ok) {
1910
+ toast.error({
1911
+ not_found: "Este agendamento n\xE3o existe mais.",
1912
+ sem_sessao: "Sess\xE3o expirada. Entre de novo para cobrar.",
1913
+ nada_contratado: "N\xE3o h\xE1 servi\xE7o contratado para cobrar neste agendamento."
1914
+ }[r.reason ?? ""] ?? "N\xE3o foi poss\xEDvel gerar o recebimento.");
1915
+ return;
1916
+ }
1917
+ if (!r.total) {
1918
+ toast.success("Atendimento sem valor a cobrar \u2014 nada foi levantado.");
1919
+ }
1920
+ let orderNow = editOrderId;
1921
+ try {
1922
+ const fresh = await provider.getBookingById(bookingId);
1923
+ if (fresh?.orderId) {
1924
+ orderNow = fresh.orderId;
1925
+ setEditOrderId(fresh.orderId);
1926
+ }
1927
+ } catch {
1928
+ }
1929
+ config.financialBridge?.getPaymentDetail(orderNow ?? "").then((d) => {
1930
+ if (d) {
1931
+ setEditPaymentStatus(d.status);
1932
+ setEditOrderTotal(d.totalAmount);
1933
+ }
1934
+ }).catch(() => {
1935
+ });
1936
+ if (agendaDateRange.start) fetchBookings(agendaDateRange);
1937
+ } : void 0
1938
+ }
1939
+ ) }),
1940
+ modalTab === "financial" && !clientId && editOrderId && config.financialBridge && /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(
1941
+ BookingPaymentPanel,
1942
+ {
1943
+ orderId: editOrderId,
1944
+ orderTotal: editOrderTotal,
1945
+ services: services.map((s) => ({ name: s.name, price: s.price, durationMinutes: s.durationMinutes })),
1946
+ formatCurrency: (v) => formatCurrency(v, currency.code),
1947
+ inline: true,
1948
+ onClose: () => setModalTab("appointment"),
1949
+ onPaymentChange: () => {
1950
+ config.financialBridge.getPaymentDetail(editOrderId).then((d) => {
1951
+ if (d) {
1952
+ setEditPaymentStatus(d.status);
1953
+ setEditOrderTotal(d.totalAmount);
1954
+ }
1955
+ }).catch(() => {
1956
+ });
1957
+ if (agendaDateRange.start) fetchBookings(agendaDateRange);
1958
+ }
1959
+ }
1960
+ ) }),
1961
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col flex-1 min-h-0 overflow-hidden", style: { display: modalTab === "appointment" ? void 0 : "none" }, children: [
1962
+ /* @__PURE__ */ jsxs("div", { className: "border-t fayz-glass-divider px-5 py-3 space-y-1 flex-1 overflow-y-auto", children: [
1963
+ 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: [
1964
+ /* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded bg-muted animate-pulse shrink-0" }),
1965
+ /* @__PURE__ */ jsx("div", { className: "h-8 flex-1 rounded-lg bg-muted animate-pulse" })
1966
+ ] }, i)) }),
1967
+ !loading && /* @__PURE__ */ jsxs(Fragment, { children: [
1968
+ activeType.fields.title && /* @__PURE__ */ jsxs("div", { className: `${ROW_CENTER} py-1`, children: [
1969
+ /* @__PURE__ */ jsx(Calendar, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1970
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
1971
+ Input,
1972
+ {
1973
+ type: "text",
1974
+ value: title,
1975
+ onChange: (e) => setTitle(e.target.value),
1976
+ placeholder: t("agenda.appointment.addTitle"),
1977
+ autoFocus: mode === "create",
1978
+ "aria-required": true,
1979
+ className: "px-2.5 font-medium"
1980
+ }
1981
+ ) })
1982
+ ] }),
1983
+ activeType.fields.client && /* @__PURE__ */ jsxs("div", { className: `${ROW_CENTER} py-1`, children: [
1984
+ /* @__PURE__ */ jsx(User, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1985
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
1986
+ ContactPicker,
1987
+ {
1988
+ value: clientId || clientSearch ? { id: clientId || void 0, name: clientSearch, phone: clientPhone } : null,
1989
+ onChange: (contact) => {
1990
+ setClientId(contact?.id ?? "");
1991
+ setClientSearch(contact?.name ?? "");
1992
+ setClientPhone(contact?.phone ?? "");
1993
+ },
1994
+ kind: config.clientKind,
1995
+ lookup: config.contactLookup,
1996
+ createEntityDef: config.clientEntityDef,
1997
+ clearable: !isPaidBooking,
1998
+ variant: "inline",
1999
+ placeholder: t("agenda.appointment.searchClient"),
2000
+ createLabel: t("agenda.appointment.newClient"),
2001
+ required: activeType.requiresClient
2002
+ }
2003
+ ) })
2004
+ ] }),
2005
+ activeType.fields.client && clientId && /* @__PURE__ */ jsxs("div", { className: "pl-7", children: [
2006
+ /* @__PURE__ */ jsx(WidgetSlot, { zone: FORMS_ZONES.personChecklist, personId: clientId, version: formsVersao, onFill: (t2) => setPreenchendoForm(t2) }),
2007
+ activeType.fields.services && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
2008
+ BookingPrepaidPanel,
2009
+ {
2010
+ clientId,
2011
+ addedServiceIds: services.map((s) => s.serviceId),
2012
+ onAdd: (it) => {
2013
+ setServices((p) => [...p, { serviceId: it.serviceId, name: it.serviceName, durationMinutes: it.durationMinutes, price: 0 }]);
2014
+ setDurationOverride(null);
2015
+ }
2016
+ }
2017
+ ) })
2018
+ ] }),
2019
+ activeType.fields.attendees && /* @__PURE__ */ jsxs("div", { className: `${ROW_TOP} py-1`, children: [
2020
+ /* @__PURE__ */ jsx(Users, { className: "h-4 w-4 mt-1.5 text-muted-foreground shrink-0" }),
2021
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2022
+ attendees.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 mb-1.5", children: attendees.map((a) => /* @__PURE__ */ jsxs(
2023
+ "span",
2024
+ {
2025
+ className: "inline-flex items-center gap-1 rounded-full bg-muted/40 pl-2.5 pr-1 py-0.5 text-sm",
2026
+ children: [
2027
+ a.name,
2028
+ /* @__PURE__ */ jsx(
2029
+ Button,
2030
+ {
2031
+ variant: "bare",
2032
+ size: "bare",
2033
+ type: "button",
2034
+ "aria-label": t("agenda.attendees.remove"),
2035
+ onClick: () => setAttendees((p) => p.filter((x) => x.personId !== a.personId)),
2036
+ className: "rounded-full p-0.5 hover:bg-muted transition-colors",
2037
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
2038
+ }
2039
+ )
2040
+ ]
2041
+ },
2042
+ a.personId
2043
+ )) }),
2044
+ /* @__PURE__ */ jsx(
2045
+ ContactPicker,
2046
+ {
2047
+ value: null,
2048
+ onChange: (contact) => {
2049
+ if (!contact?.id) return;
2050
+ setAttendees((p) => p.some((x) => x.personId === contact.id) ? p : [...p, { personId: contact.id, name: contact.name ?? "" }]);
2051
+ setAttendeeSeq((n) => n + 1);
2052
+ },
2053
+ lookup: config.attendeeLookup,
2054
+ createEntityDef: config.clientEntityDef,
2055
+ variant: "inline",
2056
+ placeholder: t("agenda.attendees.add"),
2057
+ createLabel: t("agenda.appointment.newClient")
2058
+ },
2059
+ attendeeSeq
2060
+ )
2061
+ ] })
2062
+ ] }),
2063
+ activeType.fields.location && config.modules.locationSelection && (storeLocations.length > 0 || config.locations.length > 0) && !(mode === "create" && prefill?.locationId && locationId === prefill.locationId) && /* @__PURE__ */ jsxs("div", { className: `${ROW_CENTER} py-1`, children: [
2064
+ /* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
2065
+ /* @__PURE__ */ jsx(
2066
+ LocationSelect,
2067
+ {
2068
+ value: locationId,
2069
+ onChange: setLocationId,
2070
+ locations: storeLocations.length > 0 ? storeLocations : config.locations
2071
+ }
2072
+ )
2073
+ ] }),
2074
+ storeResources.length > 0 && /* @__PURE__ */ jsxs("div", { className: `${ROW_CENTER} py-1`, children: [
2075
+ /* @__PURE__ */ jsx(DoorOpen, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
2076
+ /* @__PURE__ */ jsx(
2077
+ LocationSelect,
2078
+ {
2079
+ value: resourceId,
2080
+ onChange: setResourceId,
2081
+ locations: storeResources,
2082
+ placeholder: t("agenda.appointment.selectRoom")
2083
+ }
2084
+ )
2085
+ ] }),
2086
+ activeType.fields.services && /* @__PURE__ */ jsxs("div", { className: `${ROW_TOP} py-1`, children: [
2087
+ /* @__PURE__ */ jsx(Briefcase, { className: "h-4 w-4 mt-1.5 text-muted-foreground shrink-0" }),
2088
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
2089
+ 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: [
2090
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate min-w-0", children: svc.name }),
2091
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 shrink-0 ml-3", children: [
2092
+ /* @__PURE__ */ jsx(
2093
+ InlineNumber,
2094
+ {
2095
+ value: svc.durationMinutes,
2096
+ onChange: (v) => setServices((p) => p.map((s, i) => i === idx ? { ...s, durationMinutes: Math.round(v) } : s)),
2097
+ min: 5,
2098
+ step: 5,
2099
+ className: "w-10 shrink-0",
2100
+ disabled: isPaidBooking,
2101
+ tooltip: t("agenda.appointment.duration")
2102
+ }
2103
+ ),
2104
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "min" })
2105
+ ] }),
2106
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 shrink-0 ml-3", children: [
2107
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: currency.symbol }),
2108
+ /* @__PURE__ */ jsx(
2109
+ InlineNumber,
2110
+ {
2111
+ value: svc.price,
2112
+ onChange: (v) => setServices((p) => p.map((s, i) => i === idx ? { ...s, price: v } : s)),
2113
+ min: 0,
2114
+ step: 0.01,
2115
+ className: "w-14 shrink-0",
2116
+ disabled: isPaidBooking,
2117
+ tooltip: t("agenda.appointment.price")
2118
+ }
2119
+ )
2120
+ ] }),
2121
+ !isPaidBooking && /* @__PURE__ */ jsx(Button, { variant: "bare", size: "bare", 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" }) })
2122
+ ] }, idx)) }),
2123
+ serviceCreate.creating && serviceCreate.form,
2124
+ !isPaidBooking && !serviceCreate.creating && /* @__PURE__ */ jsx(
2125
+ SearchCombobox,
2126
+ {
2127
+ value: serviceSearch,
2128
+ onChange: setServiceSearch,
2129
+ onSelect: addService,
2130
+ options: serviceOptions,
2131
+ placeholder: t("agenda.appointment.searchService"),
2132
+ onBlurEmpty: () => {
2133
+ },
2134
+ onFocus: () => {
2135
+ if (!serviceSearch && serviceOptions.length === 0) setServiceOptions(initialServiceOptions);
2136
+ },
2137
+ renderRight: (opt) => {
2138
+ const dur = opt.data?.duration_minutes || 0;
2139
+ const hasPrice = opt.price != null;
2140
+ if (!hasPrice && !dur) return null;
2141
+ return /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs whitespace-nowrap", children: [
2142
+ hasPrice && formatCurrency(opt.price, currency.code),
2143
+ hasPrice && dur > 0 && " \xB7 ",
2144
+ dur > 0 && `${dur}min`
2145
+ ] });
2146
+ },
2147
+ minimal: true,
2148
+ chevron: true,
2149
+ required: activeType.requiresServices && services.length === 0,
2150
+ allowCreate: !!serviceCreate.onCreate,
2151
+ onCreateNew: serviceCreate.onCreate,
2152
+ createLabel: t("agenda.appointment.newService"),
2153
+ inlineLabel: services.length > 0 ? t("agenda.appointment.addService") : t("agenda.appointment.addServiceFirst"),
2154
+ inlineIcon: services.length > 0 ? "plus" : "search"
2155
+ }
2156
+ ),
2157
+ showRevision && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
2158
+ BookingRevisionPanel,
2159
+ {
2160
+ clientId,
2161
+ value: revisionChoice,
2162
+ onChange: setRevisionChoice,
2163
+ onState: setRevisionState
2164
+ }
2165
+ ) }),
2166
+ showInterests && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
2167
+ BookingInterestsPanel,
2168
+ {
2169
+ clientId: clientId || null,
2170
+ bookingId: mode === "edit" ? bookingId : void 0,
2171
+ value: interests,
2172
+ onChange: setInterests
2173
+ }
2174
+ ) })
2175
+ ] })
2176
+ ] }),
2177
+ activeType.fields.status && /* @__PURE__ */ jsxs("div", { className: `${ROW_CENTER} py-1`, children: [
2178
+ /* @__PURE__ */ jsx(CircleDot, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
2179
+ /* @__PURE__ */ jsx(StatusSelect, { value: status, onChange: setStatus, statuses: config.statuses, bookingDate: date })
2180
+ ] }),
2181
+ needsCancellationReason && /* @__PURE__ */ jsxs("div", { className: `${ROW_TOP} py-1`, children: [
2182
+ /* @__PURE__ */ jsx(Ban, { className: "h-4 w-4 mt-2 text-muted-foreground shrink-0" }),
2183
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
2184
+ /* @__PURE__ */ jsxs(
2185
+ Select,
2186
+ {
2187
+ value: cancellationReasonId,
2188
+ onValueChange: setCancellationReasonId,
2189
+ disabled: cancellationReasonsLoading,
2190
+ children: [
2191
+ /* @__PURE__ */ jsx(SelectTrigger, { "aria-required": true, className: "bg-card px-2.5", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: cancellationReasonsLoading ? t("agenda.cancellation.loadingReasons") : t("agenda.cancellation.selectReason") }) }),
2192
+ /* @__PURE__ */ jsx(SelectContent, { children: cancellationReasons.map((reason) => /* @__PURE__ */ jsxs(SelectItem, { value: reason.id, children: [
2193
+ reason.label,
2194
+ reason.requiresNotes ? " *" : ""
2195
+ ] }, reason.id)) })
2196
+ ]
2197
+ }
2198
+ ),
2199
+ /* @__PURE__ */ jsx(
2200
+ Textarea,
2201
+ {
2202
+ value: cancellationNotes,
2203
+ onChange: (event) => setCancellationNotes(event.target.value),
2204
+ placeholder: needsCancellationNotes ? t("agenda.cancellation.notesRequired") : t("agenda.cancellation.notesOptional"),
2205
+ rows: 2,
2206
+ "aria-required": needsCancellationNotes || void 0,
2207
+ className: "resize-none px-2.5 py-1.5"
2208
+ }
2209
+ )
2210
+ ] })
2211
+ ] }),
2212
+ !showNotes ? /* @__PURE__ */ jsxs("div", { className: `${ROW_CENTER} py-1`, children: [
2213
+ /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
2214
+ /* @__PURE__ */ jsx(
2215
+ Button,
2216
+ {
2217
+ variant: "bare",
2218
+ size: "bare",
2219
+ type: "button",
2220
+ onClick: () => setShowNotes(true),
2221
+ className: "font-normal whitespace-normal text-sm text-muted-foreground hover:text-foreground transition-colors text-left py-1.5 pl-2",
2222
+ children: t("agenda.appointment.addNotes")
2223
+ }
2224
+ )
2225
+ ] }) : /* @__PURE__ */ jsxs("div", { className: `${ROW_TOP} py-1`, children: [
2226
+ /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4 mt-2 text-muted-foreground shrink-0" }),
2227
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx(
2228
+ Textarea,
2229
+ {
2230
+ value: notes,
2231
+ onChange: (e) => setNotes(e.target.value),
2232
+ placeholder: t("agenda.appointment.addDescription"),
2233
+ rows: 2,
2234
+ autoFocus: true,
2235
+ className: "min-h-0 resize-none px-2.5 py-1.5"
2236
+ }
2237
+ ) })
2238
+ ] })
2239
+ ] })
2240
+ ] }),
2241
+ modalTab === "appointment" && !clientEditing && /* @__PURE__ */ jsx("div", { className: "px-5 py-2.5 pb-[calc(0.625rem+env(safe-area-inset-bottom))] sm:pb-2.5 border-t fayz-glass-divider bg-transparent shrink-0", children: confirmingDelete ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2242
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive font-medium", children: t("agenda.appointment.deleteConfirm") }),
2243
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2244
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setConfirmingDelete(false), children: t("agenda.appointment.cancel") }),
2245
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", size: "sm", onClick: handleDelete, disabled: deleting, children: deleting ? t("agenda.appointment.deleting") : t("agenda.appointment.confirmDelete") })
2246
+ ] })
2247
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2248
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2249
+ mode === "edit" && /* @__PURE__ */ jsx(Button, { variant: "bare", size: "bare", type: "button", onClick: () => setConfirmingDelete(true), className: "text-xs text-destructive font-medium hover:underline", children: t("agenda.appointment.delete") }),
2250
+ services.length > 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
2251
+ /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
2252
+ totalDuration,
2253
+ "min",
2254
+ 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(
2255
+ Button,
2256
+ {
2257
+ variant: "bare",
2258
+ size: "bare",
2259
+ type: "button",
2260
+ onClick: (e) => {
2261
+ e.stopPropagation();
2262
+ setModalTab("financial");
2263
+ },
2264
+ 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",
2265
+ children: [
2266
+ /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }),
2267
+ formatCurrency(totalPrice, currency.code),
2268
+ " \xB7 ",
2269
+ t("agenda.payment.paid")
2270
+ ]
2271
+ }
2272
+ ) : date <= todayLocal() ? /* @__PURE__ */ jsxs(
2273
+ Button,
2274
+ {
2275
+ variant: "bare",
2276
+ size: "bare",
2277
+ type: "button",
2278
+ onClick: (e) => {
2279
+ e.stopPropagation();
2280
+ setModalTab("financial");
2281
+ },
2282
+ 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",
2283
+ children: [
2284
+ /* @__PURE__ */ jsx(HandCoins, { className: "h-3 w-3" }),
2285
+ formatCurrency(totalPrice, currency.code),
2286
+ " \xB7 ",
2287
+ t("agenda.payment.collect")
2288
+ ]
2289
+ }
2290
+ ) : /* @__PURE__ */ jsxs("span", { children: [
2291
+ "\xB7 ",
2292
+ formatCurrency(totalPrice, currency.code)
2293
+ ] }) : /* @__PURE__ */ jsxs("span", { children: [
2294
+ "\xB7 ",
2295
+ formatCurrency(totalPrice, currency.code)
2296
+ ] })
2297
+ ] })
2298
+ ] }),
2299
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2300
+ mode === "edit" && date === todayLocal() && (status === "scheduled" || status === "confirmed") && bookingId && /* @__PURE__ */ jsxs(
2301
+ Button,
2302
+ {
2303
+ variant: "bare",
2304
+ size: "bare",
2305
+ type: "button",
2306
+ disabled: markingWaiting,
2307
+ onClick: async () => {
2308
+ setMarkingWaiting(true);
2309
+ try {
2310
+ await updateBookingStatus(bookingId, "waiting");
2311
+ setStatus("waiting");
2312
+ } finally {
2313
+ setMarkingWaiting(false);
2314
+ }
2315
+ },
2316
+ className: "inline-flex items-center gap-1.5 rounded-button border border-input px-2.5 py-1 text-xs font-medium transition-colors hover:bg-muted disabled:opacity-60",
2317
+ children: [
2318
+ /* @__PURE__ */ jsx(LogIn, { className: "h-3 w-3", "aria-hidden": true }),
2319
+ markingWaiting ? t("agenda.appointment.saving") : "Cliente aguardando"
2320
+ ]
2321
+ }
2322
+ ),
2323
+ /* @__PURE__ */ jsx(Button, { type: "submit", variant: "default", size: "sm", disabled: !canSubmit || !isDirty, children: saving ? t("agenda.appointment.saving") : mode === "create" ? t("agenda.appointment.save") : t("agenda.appointment.update") })
2324
+ ] })
2325
+ ] }) })
2326
+ ] }),
2327
+ modalTab !== "appointment" && !clientEditing && /* @__PURE__ */ jsx("div", { className: "px-5 py-2.5 pb-[calc(0.625rem+env(safe-area-inset-bottom))] sm:pb-2.5 border-t fayz-glass-divider bg-transparent shrink-0", children: /* @__PURE__ */ jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxs(
2328
+ Button,
2329
+ {
2330
+ variant: "bare",
2331
+ size: "bare",
2332
+ type: "button",
2333
+ onClick: () => {
2334
+ setModalTab("appointment");
2335
+ setClientEditing(false);
2336
+ },
2337
+ className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors",
2338
+ children: [
2339
+ /* @__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" }) }),
2340
+ t("agenda.payment.backToAppointment")
2341
+ ]
2342
+ }
2343
+ ) }) })
2344
+ ] })
2345
+ ] })
2346
+ ]
2347
+ }
2348
+ ),
2349
+ scopeAsk && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-20 flex items-end justify-center bg-background/70 p-4 sm:items-center", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-xs rounded-card border bg-card p-5 shadow-xl", children: [
2350
+ /* @__PURE__ */ jsx("p", { className: "text-base font-semibold", children: scopeAsk === "delete" ? t("agenda.recurrence.scopeDeleteTitle") : t("agenda.recurrence.scopeSaveTitle") }),
2351
+ /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-3", children: ["this", "series"].map((op) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-3 text-sm", children: [
2352
+ /* @__PURE__ */ jsx(
2353
+ "input",
2354
+ {
2355
+ type: "radio",
2356
+ name: "series-scope",
2357
+ value: op,
2358
+ checked: scopeChoice === op,
2359
+ onChange: () => setScopeChoice(op),
2360
+ className: "h-4 w-4 accent-primary"
2361
+ }
2362
+ ),
2363
+ op === "this" ? t("agenda.recurrence.scopeThis") : t("agenda.recurrence.scopeSeries").replace("{{count}}", String(seriesSize))
2364
+ ] }, op)) }),
2365
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex items-center justify-end gap-2", children: [
2366
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setScopeAsk(null), children: t("agenda.appointment.cancel") }),
2367
+ /* @__PURE__ */ jsx(
2368
+ Button,
2369
+ {
2370
+ type: "button",
2371
+ size: "sm",
2372
+ onClick: () => {
2373
+ void (scopeAsk === "delete" ? commitDelete(scopeChoice) : commitSave(scopeChoice));
2374
+ },
2375
+ children: t("common.confirm")
2376
+ }
2377
+ )
2378
+ ] })
2379
+ ] }) })
2380
+ ] }) }) })
2381
+ );
2382
+ }
2383
+
2384
+ export { AppointmentModal, StatusFilterList, isStatusAvailable };
2385
+ //# sourceMappingURL=chunk-WNANVT4F.js.map
2386
+ //# sourceMappingURL=chunk-WNANVT4F.js.map