@fayz-ai/plugin-shop 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/ShopPage-ZBJRNMKQ.js +2942 -0
  2. package/dist/ShopPage-ZBJRNMKQ.js.map +1 -0
  3. package/dist/analytics/cards.d.ts +3 -0
  4. package/dist/analytics/cards.d.ts.map +1 -0
  5. package/dist/chunk-EW4XJRA2.js +835 -0
  6. package/dist/chunk-EW4XJRA2.js.map +1 -0
  7. package/dist/data/registries.d.ts +53 -0
  8. package/dist/data/registries.d.ts.map +1 -0
  9. package/dist/data/registry-routes.d.ts +21 -0
  10. package/dist/data/registry-routes.d.ts.map +1 -0
  11. package/dist/data/shippingProfileDataProvider.d.ts +19 -0
  12. package/dist/data/shippingProfileDataProvider.d.ts.map +1 -0
  13. package/dist/data/stockLocationDataProvider.d.ts +13 -0
  14. package/dist/data/stockLocationDataProvider.d.ts.map +1 -0
  15. package/dist/index.d.ts +15 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4446 -504
  18. package/dist/index.js.map +1 -1
  19. package/dist/integrations/abacatepay/connectorDef.d.ts +41 -0
  20. package/dist/integrations/abacatepay/connectorDef.d.ts.map +1 -0
  21. package/dist/integrations/appmax/connectorDef.d.ts +28 -0
  22. package/dist/integrations/appmax/connectorDef.d.ts.map +1 -0
  23. package/dist/integrations/bling/connectorDef.d.ts +10 -0
  24. package/dist/integrations/bling/connectorDef.d.ts.map +1 -0
  25. package/dist/integrations/bling/data.d.ts +61 -0
  26. package/dist/integrations/bling/data.d.ts.map +1 -0
  27. package/dist/integrations/bling/inbound.d.ts +97 -0
  28. package/dist/integrations/bling/inbound.d.ts.map +1 -0
  29. package/dist/integrations/bling/index.d.ts +9 -0
  30. package/dist/integrations/bling/index.d.ts.map +1 -0
  31. package/dist/integrations/bling/limits.d.ts +33 -0
  32. package/dist/integrations/bling/limits.d.ts.map +1 -0
  33. package/dist/integrations/bling/mapping.d.ts +10 -0
  34. package/dist/integrations/bling/mapping.d.ts.map +1 -0
  35. package/dist/integrations/bling/sync.d.ts +70 -0
  36. package/dist/integrations/bling/sync.d.ts.map +1 -0
  37. package/dist/integrations/bling/types.d.ts +59 -0
  38. package/dist/integrations/bling/types.d.ts.map +1 -0
  39. package/dist/integrations/melhor-envio/connectorDef.d.ts +38 -0
  40. package/dist/integrations/melhor-envio/connectorDef.d.ts.map +1 -0
  41. package/dist/integrations/melhor-envio/labelProvider.d.ts +4 -0
  42. package/dist/integrations/melhor-envio/labelProvider.d.ts.map +1 -0
  43. package/dist/integrations/stripe-connect/connectorDef.d.ts +17 -0
  44. package/dist/integrations/stripe-connect/connectorDef.d.ts.map +1 -0
  45. package/dist/settings/CheckoutSettings.d.ts +17 -0
  46. package/dist/settings/CheckoutSettings.d.ts.map +1 -0
  47. package/dist/settings/ChoiceCards.d.ts +23 -0
  48. package/dist/settings/ChoiceCards.d.ts.map +1 -0
  49. package/dist/settings/FulfillmentSettings.d.ts +23 -0
  50. package/dist/settings/FulfillmentSettings.d.ts.map +1 -0
  51. package/dist/settings/HandlingSettings.d.ts +17 -0
  52. package/dist/settings/HandlingSettings.d.ts.map +1 -0
  53. package/dist/settings/PackagesSettings.d.ts +22 -0
  54. package/dist/settings/PackagesSettings.d.ts.map +1 -0
  55. package/dist/settings/PaymentProviderRow.d.ts +28 -0
  56. package/dist/settings/PaymentProviderRow.d.ts.map +1 -0
  57. package/dist/settings/PaymentsSettings.d.ts +7 -0
  58. package/dist/settings/PaymentsSettings.d.ts.map +1 -0
  59. package/dist/settings/SettingsState.d.ts +27 -0
  60. package/dist/settings/SettingsState.d.ts.map +1 -0
  61. package/dist/settings/ShippingSettings.d.ts +27 -0
  62. package/dist/settings/ShippingSettings.d.ts.map +1 -0
  63. package/dist/settings/StoreGeneralSettings.d.ts +19 -0
  64. package/dist/settings/StoreGeneralSettings.d.ts.map +1 -0
  65. package/dist/settings/error-message.d.ts +2 -0
  66. package/dist/settings/error-message.d.ts.map +1 -0
  67. package/dist/settings/index.d.ts +12 -0
  68. package/dist/settings/index.d.ts.map +1 -0
  69. package/dist/settings/payment-provider.d.ts +13 -0
  70. package/dist/settings/payment-provider.d.ts.map +1 -0
  71. package/dist/settings/useStoreSettings.d.ts +30 -0
  72. package/dist/settings/useStoreSettings.d.ts.map +1 -0
  73. package/dist/views/AgencySelect.d.ts +17 -0
  74. package/dist/views/AgencySelect.d.ts.map +1 -0
  75. package/dist/views/ContentPage.d.ts.map +1 -1
  76. package/dist/views/FulfillmentProviderSelect.d.ts +14 -0
  77. package/dist/views/FulfillmentProviderSelect.d.ts.map +1 -0
  78. package/dist/views/NewOrderCharge.d.ts +17 -0
  79. package/dist/views/NewOrderCharge.d.ts.map +1 -0
  80. package/dist/views/NewOrderPage.d.ts +9 -0
  81. package/dist/views/NewOrderPage.d.ts.map +1 -0
  82. package/dist/views/OrderDetailView.d.ts +2 -0
  83. package/dist/views/OrderDetailView.d.ts.map +1 -1
  84. package/dist/views/OrderPaymentDetail.d.ts +5 -0
  85. package/dist/views/OrderPaymentDetail.d.ts.map +1 -0
  86. package/dist/views/OrderProviderLinks.d.ts +5 -0
  87. package/dist/views/OrderProviderLinks.d.ts.map +1 -0
  88. package/dist/views/PackageSelect.d.ts +20 -0
  89. package/dist/views/PackageSelect.d.ts.map +1 -0
  90. package/dist/views/ProductImagesTab.d.ts.map +1 -1
  91. package/dist/views/ProductVariantsTab.d.ts +29 -0
  92. package/dist/views/ProductVariantsTab.d.ts.map +1 -0
  93. package/dist/views/SenderLocationModal.d.ts +20 -0
  94. package/dist/views/SenderLocationModal.d.ts.map +1 -0
  95. package/dist/views/ShipOrderForm.d.ts +10 -0
  96. package/dist/views/ShipOrderForm.d.ts.map +1 -0
  97. package/dist/views/ShipWithCarrier.d.ts +18 -0
  98. package/dist/views/ShipWithCarrier.d.ts.map +1 -0
  99. package/dist/views/ShipmentActions.d.ts +11 -0
  100. package/dist/views/ShipmentActions.d.ts.map +1 -0
  101. package/dist/views/ShippingProfileSelect.d.ts +13 -0
  102. package/dist/views/ShippingProfileSelect.d.ts.map +1 -0
  103. package/dist/views/ShopPage.d.ts.map +1 -1
  104. package/dist/views/customerEntity.d.ts.map +1 -1
  105. package/dist/views/eta-label.d.ts +6 -0
  106. package/dist/views/eta-label.d.ts.map +1 -0
  107. package/dist/views/media-links.d.ts +12 -0
  108. package/dist/views/media-links.d.ts.map +1 -0
  109. package/dist/views/new-order-rules.d.ts +55 -0
  110. package/dist/views/new-order-rules.d.ts.map +1 -0
  111. package/dist/views/order-actions.d.ts +4 -0
  112. package/dist/views/order-actions.d.ts.map +1 -0
  113. package/dist/views/productEntity.d.ts.map +1 -1
  114. package/dist/views/shippingZoneEntity.d.ts.map +1 -1
  115. package/package.json +10 -7
  116. package/dist/ShopPage-PVHFKDF3.js +0 -690
  117. package/dist/ShopPage-PVHFKDF3.js.map +0 -1
  118. package/dist/components/ShopSettings.d.ts +0 -15
  119. package/dist/components/ShopSettings.d.ts.map +0 -1
@@ -0,0 +1,2942 @@
1
+ import { errorMessage, buildStockLocationsRegistry } from './chunk-EW4XJRA2.js';
2
+ import React2, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
3
+ import { Thumbnail, Button, DataTable, toast, ConfirmDialog, formatPhone, SearchSelect, Modal, ModalPortal, ModalOverlay, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, Skeleton, PhoneField } from '@fayz-ai/ui';
4
+ import { Minus, Plus, Trash2, CreditCard, XCircle, ShoppingBag, Package, Wallet, X, Pencil, MapPin, Search, Check, Copy, MessageCircle, CheckCircle2, Printer, RefreshCw, ExternalLink, Truck, AlertTriangle, Loader2 } from 'lucide-react';
5
+ import { getShopProvider, formatDocument, resolveShippingLabelProvider, documentDigits, isValidDocument } from '@fayz-ai/shop';
6
+ import { useAdminPath, adminNavigateTo, OrderFormBlock, OrderFormView, OrderDetailSkeleton, OrderDetailBlock, OrderDetailRow, PersonLink, OrderDetailView as OrderDetailView$1, OrderListView, RecordPickerModal, useConnectors, CrudRecordModal } from '@fayz-ai/admin';
7
+ import { usePluginRuntimeOptional, getSupabaseClientOptional } from '@fayz-ai/core';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
+
10
+ // src/views/customerLookup.ts
11
+ function shopCustomerLookup(provider) {
12
+ return {
13
+ async getById(id) {
14
+ const c = await provider().getCustomer(id);
15
+ if (!c) return null;
16
+ const name = [c.firstName, c.lastName].filter(Boolean).join(" ");
17
+ return {
18
+ id: c.id,
19
+ label: name || c.email || "Cliente",
20
+ subtitle: c.email ?? void 0,
21
+ group: "shop_customer",
22
+ // PersonLink reads contact fields off `data`, so the keys must match the
23
+ // shape it expects (email/phone), not our camelCase type.
24
+ data: {
25
+ email: c.email ?? void 0,
26
+ phone: c.phone ?? void 0,
27
+ kind: "shop_customer",
28
+ orders_count: c.ordersCount,
29
+ total_spent: c.totalSpent
30
+ }
31
+ };
32
+ }
33
+ };
34
+ }
35
+ function ShipOrderForm({ orderId, provider, onShipped }) {
36
+ const [open, setOpen] = useState(false);
37
+ const [carrier, setCarrier] = useState("");
38
+ const [tracking, setTracking] = useState("");
39
+ const [saving, setSaving] = useState(false);
40
+ const [error, setError] = useState(null);
41
+ const db = getSupabaseClientOptional();
42
+ const supported = typeof provider().createFulfillment === "function" || typeof db?.rpc === "function";
43
+ if (!supported) return null;
44
+ async function ship() {
45
+ setSaving(true);
46
+ setError(null);
47
+ try {
48
+ if (typeof provider().createFulfillment === "function") {
49
+ await provider().createFulfillment?.({
50
+ orderId,
51
+ carrier: carrier.trim() || void 0,
52
+ trackingCode: tracking.trim() || void 0
53
+ });
54
+ } else {
55
+ const { error: rpcError } = await db.rpc("shop_create_fulfillment", {
56
+ p_order_id: orderId,
57
+ p_location_id: null,
58
+ p_carrier: carrier.trim() || null,
59
+ p_tracking: tracking.trim() || null,
60
+ p_items: null,
61
+ p_notes: null
62
+ });
63
+ if (rpcError) throw new Error(rpcError.message);
64
+ }
65
+ setOpen(false);
66
+ setCarrier("");
67
+ setTracking("");
68
+ onShipped();
69
+ } catch (err) {
70
+ setError(errorMessage(err));
71
+ } finally {
72
+ setSaving(false);
73
+ }
74
+ }
75
+ if (!open) {
76
+ return /* @__PURE__ */ jsxs(
77
+ "button",
78
+ {
79
+ type: "button",
80
+ onClick: () => setOpen(true),
81
+ "data-testid": "order-ship",
82
+ className: "mt-2 inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
83
+ children: [
84
+ /* @__PURE__ */ jsx(Truck, { className: "h-3.5 w-3.5" }),
85
+ "Marcar como enviado"
86
+ ]
87
+ }
88
+ );
89
+ }
90
+ return /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2 rounded-md border bg-muted/30 p-3", children: [
91
+ /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-2", children: [
92
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
93
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Transportadora" }),
94
+ /* @__PURE__ */ jsx(
95
+ "input",
96
+ {
97
+ value: carrier,
98
+ onChange: (event) => setCarrier(event.target.value),
99
+ placeholder: "Correios, motoboy, entrega pr\xF3pria\u2026",
100
+ "data-testid": "order-ship-carrier",
101
+ className: "mt-1 w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary"
102
+ }
103
+ )
104
+ ] }),
105
+ /* @__PURE__ */ jsxs("label", { className: "block", children: [
106
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "C\xF3digo de rastreio" }),
107
+ /* @__PURE__ */ jsx(
108
+ "input",
109
+ {
110
+ value: tracking,
111
+ onChange: (event) => setTracking(event.target.value),
112
+ placeholder: "Opcional",
113
+ "data-testid": "order-ship-tracking",
114
+ className: "mt-1 w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary"
115
+ }
116
+ )
117
+ ] })
118
+ ] }),
119
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tracking.trim() ? "Com rastreio o pedido j\xE1 sai como \xABem tr\xE2nsito\xBB e o comprador v\xEA o c\xF3digo." : "Sem rastreio o envio fica como \xABaguardando coleta\xBB \u2014 d\xE1 para completar depois." }),
120
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Confirmar d\xE1 baixa no estoque destes itens." }),
121
+ error && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-red-50 px-2.5 py-1.5 text-xs text-red-800", children: error }),
122
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
123
+ /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ type: "button",
127
+ onClick: ship,
128
+ disabled: saving,
129
+ "data-testid": "order-ship-confirm",
130
+ className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
131
+ children: saving ? "Registrando\u2026" : "Confirmar envio"
132
+ }
133
+ ),
134
+ /* @__PURE__ */ jsx(
135
+ "button",
136
+ {
137
+ type: "button",
138
+ onClick: () => {
139
+ setOpen(false);
140
+ setError(null);
141
+ },
142
+ className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
143
+ children: "Cancelar"
144
+ }
145
+ )
146
+ ] })
147
+ ] });
148
+ }
149
+ function pickDispatchLocation(locations, locationId) {
150
+ if (locationId) return locations.find((place) => place.id === locationId) ?? null;
151
+ const alive = locations.filter((place) => place.status !== "archived");
152
+ const dispatching = alive.filter((place) => place.shipsOnline !== false);
153
+ const pool = dispatching.length > 0 ? dispatching : alive;
154
+ return pool.find((place) => place.isDefault) ?? pool[0] ?? null;
155
+ }
156
+ function SenderLocationModal({
157
+ open,
158
+ onClose,
159
+ onSaved,
160
+ provider,
161
+ locationId
162
+ }) {
163
+ const resolve = provider ?? (() => getShopProvider());
164
+ const registry = React2.useMemo(() => buildStockLocationsRegistry(resolve), [provider]);
165
+ const chosen = React2.useRef(null);
166
+ return /* @__PURE__ */ jsx(
167
+ CrudRecordModal,
168
+ {
169
+ open,
170
+ onClose,
171
+ onSaved,
172
+ entityDef: registry.entity,
173
+ title: "Remetente das etiquetas",
174
+ description: "\xC9 o que vai impresso na etiqueta, e \xE9 o que a transportadora confere.",
175
+ emptyLabel: "Esta loja n\xE3o tem nenhum local que despacha pedidos. Cadastre um em Entrega \u25B8 Locais.",
176
+ load: async () => {
177
+ const rows = await resolve().listStockLocations?.() ?? [];
178
+ const place = pickDispatchLocation(rows, locationId);
179
+ chosen.current = place?.id ?? null;
180
+ return place;
181
+ },
182
+ save: async (data) => {
183
+ if (!chosen.current) throw new Error("Nenhum local de despacho para salvar.");
184
+ return await resolve().updateStockLocation?.(chosen.current, data);
185
+ }
186
+ }
187
+ );
188
+ }
189
+
190
+ // src/views/new-order-rules.ts
191
+ function chargeOutcome(state) {
192
+ if (state === "paid") return "paid";
193
+ if (state === "expired") return "expired";
194
+ if (state === "failed" || state === "cancelled") return "failed";
195
+ return "waiting";
196
+ }
197
+ function validateNewOrder(draft, ending) {
198
+ if (draft.lineCount === 0) return "Adicione pelo menos um produto ao pedido.";
199
+ if (draft.unresolvedVariants > 0) {
200
+ return "Escolha a varia\xE7\xE3o de cada produto que tem mais de uma op\xE7\xE3o.";
201
+ }
202
+ if (!draft.customerName.trim()) return "Informe o cliente.";
203
+ if (draft.delivers && !draft.addressComplete) return "Complete o endere\xE7o de entrega.";
204
+ if (draft.customerDocument && !draft.documentValid) return "Esse CPF/CNPJ n\xE3o confere.";
205
+ if (ending === "charge") {
206
+ const missing = [
207
+ !draft.customerEmail.trim() && "e-mail",
208
+ !draft.customerDocument.trim() && "CPF",
209
+ !draft.customerPhone.trim() && "telefone"
210
+ ].filter(Boolean);
211
+ if (missing.length) {
212
+ return `Para gerar a cobran\xE7a o adquirente exige ${missing.join(", ")} no cadastro do cliente.`;
213
+ }
214
+ }
215
+ return null;
216
+ }
217
+ function isDuplicateEmail(error) {
218
+ const code = String(error?.code ?? "");
219
+ if (code === "23505") return true;
220
+ const message = String(error?.message ?? error ?? "");
221
+ return /duplicate key value/iu.test(message) && /email/iu.test(message);
222
+ }
223
+ function isSenderRefusal(message) {
224
+ const text = String(message ?? "");
225
+ return /\bfrom\./iu.test(text) || /remetente/iu.test(text);
226
+ }
227
+ var money = (value) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(value) || 0);
228
+ var eta = (option) => {
229
+ const min = option.etaMinDays ?? null;
230
+ const max = option.etaMaxDays ?? null;
231
+ if (!min && !max) return null;
232
+ if (min && max && min !== max) return `${min}\u2013${max} dias`;
233
+ return `${max ?? min} dia(s)`;
234
+ };
235
+ function ShipWithCarrier({ orderId, provider, preferredCarrier, onShipped }) {
236
+ const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
237
+ const carrier = useMemo(() => resolveShippingLabelProvider({
238
+ connected: (connectors ?? []).filter((c) => c.state === "connected").map((c) => c.slug),
239
+ preferred: preferredCarrier
240
+ }), [connectors, preferredCarrier]);
241
+ const [options, setOptions] = useState(null);
242
+ const [label, setLabel] = useState(null);
243
+ const [chosen, setChosen] = useState(null);
244
+ const [open, setOpen] = useState(false);
245
+ const [confirming, setConfirming] = useState(false);
246
+ const [busy, setBusy] = useState(false);
247
+ const [error, setError] = useState(null);
248
+ const [unavailable, setUnavailable] = useState(false);
249
+ const [fixingSender, setFixingSender] = useState(false);
250
+ const [celebrating, setCelebrating] = useState(false);
251
+ const load = useCallback(async () => {
252
+ setBusy(true);
253
+ setError(null);
254
+ try {
255
+ const result = await carrier?.quote(orderId);
256
+ if (!result) {
257
+ setUnavailable(true);
258
+ return;
259
+ }
260
+ setOptions(result.options);
261
+ setLabel(result.label);
262
+ setChosen(result.options[0]?.serviceId ?? null);
263
+ } catch (err) {
264
+ const message = errorMessage(err);
265
+ if (/conectada|not_connected|404/iu.test(message)) setUnavailable(true);
266
+ else setError(message);
267
+ } finally {
268
+ setBusy(false);
269
+ }
270
+ }, [orderId, carrier]);
271
+ useEffect(() => {
272
+ if (open && options === null) void load();
273
+ }, [open, options, load]);
274
+ useEffect(() => {
275
+ let cancelled = false;
276
+ carrier?.quote(orderId).then((result) => {
277
+ if (cancelled || !result?.label) return;
278
+ setLabel(result.label);
279
+ setOptions(result.options);
280
+ setOpen(true);
281
+ }).catch(() => {
282
+ });
283
+ return () => {
284
+ cancelled = true;
285
+ };
286
+ }, [orderId]);
287
+ useEffect(() => {
288
+ if (!celebrating) return;
289
+ const timer = setTimeout(() => setCelebrating(false), 8e3);
290
+ return () => clearTimeout(timer);
291
+ }, [celebrating]);
292
+ if (!carrier || unavailable) return null;
293
+ async function buy() {
294
+ const service2 = options?.find((option) => option.serviceId === chosen);
295
+ if (!chosen || !service2) {
296
+ setConfirming(false);
297
+ setError("A cota\xE7\xE3o mudou. Escolha o servi\xE7o de novo antes de comprar.");
298
+ return;
299
+ }
300
+ setBusy(true);
301
+ setError(null);
302
+ try {
303
+ const bought = await carrier?.buy(orderId, chosen);
304
+ if (!bought?.labelId) {
305
+ throw new Error("A transportadora aceitou o pedido mas n\xE3o devolveu a etiqueta. Tente de novo em instantes.");
306
+ }
307
+ setLabel(bought);
308
+ setConfirming(false);
309
+ setCelebrating(true);
310
+ onShipped();
311
+ } catch (err) {
312
+ setError(errorMessage(err));
313
+ } finally {
314
+ setBusy(false);
315
+ }
316
+ }
317
+ async function refreshTracking() {
318
+ setBusy(true);
319
+ setError(null);
320
+ try {
321
+ const tracking = await carrier?.track(orderId);
322
+ setLabel((current) => current ? { ...current, tracking: tracking ?? current.tracking } : current);
323
+ onShipped();
324
+ } catch (err) {
325
+ setError(errorMessage(err));
326
+ } finally {
327
+ setBusy(false);
328
+ }
329
+ }
330
+ async function print() {
331
+ setBusy(true);
332
+ setError(null);
333
+ try {
334
+ const url = await carrier?.print(orderId) ?? label?.labelUrl;
335
+ if (url) window.open(url, "_blank", "noopener");
336
+ else setError("A transportadora ainda n\xE3o liberou o PDF da etiqueta.");
337
+ } catch (err) {
338
+ setError(errorMessage(err));
339
+ } finally {
340
+ setBusy(false);
341
+ }
342
+ }
343
+ if (label?.labelId) {
344
+ return /* @__PURE__ */ jsxs(
345
+ "div",
346
+ {
347
+ className: `mt-2 space-y-2 rounded-md border p-3 transition-colors ${celebrating ? "border-emerald-300 bg-emerald-50/60" : "bg-muted/30"}`,
348
+ "data-testid": "carrier-label",
349
+ children: [
350
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
351
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm font-medium", children: [
352
+ celebrating && /* @__PURE__ */ jsx(CheckCircle2, { className: "h-4 w-4 shrink-0 text-emerald-600" }),
353
+ "Etiqueta comprada"
354
+ ] }),
355
+ label.tracking?.code && /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: label.tracking.code })
356
+ ] }),
357
+ celebrating && /* @__PURE__ */ jsx("p", { className: "text-xs text-emerald-800", role: "status", "data-testid": "carrier-bought", children: "Compra conclu\xEDda. Imprima a etiqueta e cole no pacote \u2014 o rastreio passa a chegar sozinho." }),
358
+ label.tracking?.status && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
359
+ "Situa\xE7\xE3o na transportadora: ",
360
+ label.tracking.status
361
+ ] }),
362
+ /* @__PURE__ */ jsx(CarrierError, { error, onFix: () => setFixingSender(true) }),
363
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
364
+ /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: print, disabled: busy, "data-testid": "carrier-print", children: [
365
+ /* @__PURE__ */ jsx(Printer, { className: "mr-1.5 h-3.5 w-3.5" }),
366
+ "Imprimir etiqueta"
367
+ ] }),
368
+ /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: refreshTracking, disabled: busy, "data-testid": "carrier-track", children: [
369
+ /* @__PURE__ */ jsx(RefreshCw, { className: "mr-1.5 h-3.5 w-3.5" }),
370
+ "Atualizar rastreio"
371
+ ] }),
372
+ label.tracking?.url && /* @__PURE__ */ jsxs(
373
+ "a",
374
+ {
375
+ href: label.tracking.url,
376
+ target: "_blank",
377
+ rel: "noreferrer",
378
+ className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
379
+ children: [
380
+ /* @__PURE__ */ jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }),
381
+ "Acompanhar"
382
+ ]
383
+ }
384
+ )
385
+ ] })
386
+ ]
387
+ }
388
+ );
389
+ }
390
+ if (!open) {
391
+ return /* @__PURE__ */ jsxs(
392
+ "button",
393
+ {
394
+ type: "button",
395
+ onClick: () => setOpen(true),
396
+ "data-testid": "carrier-open",
397
+ className: "mt-2 inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
398
+ children: [
399
+ /* @__PURE__ */ jsx(Truck, { className: "h-3.5 w-3.5" }),
400
+ "Comprar etiqueta na ",
401
+ carrier.label
402
+ ]
403
+ }
404
+ );
405
+ }
406
+ const service = options?.find((option) => option.serviceId === chosen);
407
+ const pending = confirming ? service : void 0;
408
+ return /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2 rounded-md border bg-muted/30 p-3", "data-testid": "carrier-buy", children: [
409
+ busy && options === null && /* @__PURE__ */ jsx("div", { className: "space-y-1.5", "data-testid": "carrier-quoting", "aria-busy": "true", "aria-label": "Cotando com a transportadora", children: [0, 1].map((row) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-md border p-2", children: [
410
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 shrink-0 rounded-full" }),
411
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
412
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-40" }),
413
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" })
414
+ ] }),
415
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-14" })
416
+ ] }, row)) }),
417
+ options?.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "A transportadora n\xE3o atende este endere\xE7o com os dados deste pedido." }),
418
+ options && options.length > 0 && // Locked while the confirmation is open: the panel below quotes a price,
419
+ // and a radio that still moves makes it a promise about the wrong row.
420
+ /* @__PURE__ */ jsx("div", { className: `space-y-1.5 ${pending ? "pointer-events-none opacity-50" : ""}`, children: options.map((option) => /* @__PURE__ */ jsxs(
421
+ "label",
422
+ {
423
+ className: `flex cursor-pointer items-center gap-2 rounded-md border p-2 text-sm transition ${chosen === option.serviceId ? "border-primary bg-primary/5" : "hover:bg-muted/50"}`,
424
+ children: [
425
+ /* @__PURE__ */ jsx(
426
+ "input",
427
+ {
428
+ type: "radio",
429
+ name: "carrier-service",
430
+ checked: chosen === option.serviceId,
431
+ disabled: Boolean(pending),
432
+ onChange: () => setChosen(option.serviceId),
433
+ "data-testid": "carrier-service"
434
+ }
435
+ ),
436
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
437
+ /* @__PURE__ */ jsxs("span", { className: "block truncate", children: [
438
+ option.carrier,
439
+ " \xB7 ",
440
+ option.name
441
+ ] }),
442
+ eta(option) && /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: eta(option) })
443
+ ] }),
444
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money(option.price) })
445
+ ]
446
+ },
447
+ option.serviceId
448
+ )) }),
449
+ !pending && /* @__PURE__ */ jsx(CarrierError, { error, onFix: () => setFixingSender(true) }),
450
+ /* @__PURE__ */ jsx(
451
+ SenderLocationModal,
452
+ {
453
+ provider,
454
+ open: fixingSender,
455
+ onClose: () => setFixingSender(false),
456
+ onSaved: () => {
457
+ setFixingSender(false);
458
+ setConfirming(false);
459
+ setError(null);
460
+ void load();
461
+ }
462
+ }
463
+ ),
464
+ pending ? /* @__PURE__ */ jsxs(
465
+ "div",
466
+ {
467
+ className: "space-y-2 rounded-md border border-amber-300 bg-amber-50 px-2.5 py-2",
468
+ role: "alertdialog",
469
+ "aria-label": "Confirmar compra da etiqueta",
470
+ "data-testid": "carrier-confirm-panel",
471
+ children: [
472
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
473
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" }),
474
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1.5", children: [
475
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-amber-900", children: "Confirmar compra da etiqueta" }),
476
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2 rounded border border-amber-200 bg-white/70 px-2 py-1.5", children: [
477
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
478
+ /* @__PURE__ */ jsxs("span", { className: "block truncate text-xs", children: [
479
+ pending.carrier,
480
+ " \xB7 ",
481
+ pending.name
482
+ ] }),
483
+ eta(pending) && /* @__PURE__ */ jsx("span", { className: "block text-[11px] text-muted-foreground", children: eta(pending) })
484
+ ] }),
485
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money(pending.price) })
486
+ ] }),
487
+ /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-amber-900", children: [
488
+ "O valor \xE9 debitado do saldo da sua conta na ",
489
+ carrier.label,
490
+ ". A etiqueta sai na hora e o rastreio passa a chegar sozinho."
491
+ ] })
492
+ ] })
493
+ ] }),
494
+ /* @__PURE__ */ jsx(CarrierError, { error, onFix: () => setFixingSender(true) }),
495
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
496
+ /* @__PURE__ */ jsx(
497
+ Button,
498
+ {
499
+ size: "sm",
500
+ variant: "outline",
501
+ onClick: () => {
502
+ setConfirming(false);
503
+ setError(null);
504
+ },
505
+ disabled: busy,
506
+ "data-testid": "carrier-confirm-back",
507
+ children: "Voltar"
508
+ }
509
+ ),
510
+ /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: buy, disabled: busy, "data-testid": "carrier-confirm", children: [
511
+ busy && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }),
512
+ busy ? "Comprando\u2026" : error ? "Tentar de novo" : `Comprar ${money(pending.price)}`
513
+ ] })
514
+ ] })
515
+ ]
516
+ }
517
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
518
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setOpen(false), "data-testid": "carrier-close", children: "Fechar" }),
519
+ /* @__PURE__ */ jsx(
520
+ Button,
521
+ {
522
+ size: "sm",
523
+ onClick: () => {
524
+ setError(null);
525
+ setConfirming(true);
526
+ },
527
+ disabled: busy || !chosen,
528
+ "data-testid": "carrier-buy-start",
529
+ children: "Selecionar etiqueta"
530
+ }
531
+ )
532
+ ] })
533
+ ] });
534
+ }
535
+ function CarrierError({ error, onFix }) {
536
+ if (!error) return null;
537
+ return /* @__PURE__ */ jsxs(
538
+ "div",
539
+ {
540
+ role: "alert",
541
+ "data-testid": "carrier-error",
542
+ className: "rounded-md border border-red-200 bg-red-50 px-2.5 py-1.5 text-xs text-red-800",
543
+ children: [
544
+ /* @__PURE__ */ jsx("p", { children: error }),
545
+ isSenderRefusal(error) && /* @__PURE__ */ jsx(
546
+ "button",
547
+ {
548
+ type: "button",
549
+ onClick: onFix,
550
+ "data-testid": "carrier-fix-sender",
551
+ className: "mt-1 font-medium underline underline-offset-2",
552
+ children: "Completar o cadastro do remetente"
553
+ }
554
+ )
555
+ ]
556
+ }
557
+ );
558
+ }
559
+ function ShipmentActions({ fulfillmentId, status, hasTracking, provider, onChanged }) {
560
+ const [mode, setMode] = useState("idle");
561
+ const [tracking, setTracking] = useState("");
562
+ const [trackingUrl, setTrackingUrl] = useState("");
563
+ const [carrier, setCarrier] = useState("");
564
+ const [busy, setBusy] = useState(false);
565
+ const [error, setError] = useState(null);
566
+ const db = getSupabaseClientOptional();
567
+ if (status === "cancelled" || status === "returned") return null;
568
+ if (!db && typeof provider().shipFulfillment !== "function") return null;
569
+ async function run(action) {
570
+ setBusy(true);
571
+ setError(null);
572
+ try {
573
+ await action();
574
+ setMode("idle");
575
+ setTracking("");
576
+ setTrackingUrl("");
577
+ setCarrier("");
578
+ onChanged();
579
+ } catch (err) {
580
+ setError(errorMessage(err));
581
+ } finally {
582
+ setBusy(false);
583
+ }
584
+ }
585
+ const saveTracking = () => run(async () => {
586
+ const code = tracking.trim();
587
+ if (!code) throw new Error("Informe o c\xF3digo que a transportadora deu.");
588
+ const url = trackingUrl.trim() || null;
589
+ const who = carrier.trim() || null;
590
+ if (typeof provider().shipFulfillment === "function") {
591
+ await provider().shipFulfillment?.(fulfillmentId, {
592
+ trackingCode: code,
593
+ trackingUrl: url ?? void 0,
594
+ carrier: who ?? void 0
595
+ });
596
+ return;
597
+ }
598
+ const { error: rpcError } = await db.rpc("shop_ship_fulfillment", {
599
+ p_fulfillment_id: fulfillmentId,
600
+ p_tracking: code,
601
+ p_tracking_url: url,
602
+ p_carrier: who
603
+ });
604
+ if (rpcError) throw new Error(rpcError.message);
605
+ });
606
+ const markDelivered = () => run(async () => {
607
+ if (typeof provider().deliverFulfillment === "function") {
608
+ await provider().deliverFulfillment?.(fulfillmentId);
609
+ return;
610
+ }
611
+ const { error: rpcError } = await db.rpc("shop_deliver_fulfillment", {
612
+ p_fulfillment_id: fulfillmentId,
613
+ p_delivered_at: null
614
+ });
615
+ if (rpcError) throw new Error(rpcError.message);
616
+ });
617
+ const cancelShipment = () => run(async () => {
618
+ if (typeof provider().cancelFulfillment === "function") {
619
+ await provider().cancelFulfillment?.(fulfillmentId);
620
+ return;
621
+ }
622
+ const { error: rpcError } = await db.rpc("shop_cancel_fulfillment", {
623
+ p_fulfillment_id: fulfillmentId,
624
+ p_note: null
625
+ });
626
+ if (rpcError) throw new Error(rpcError.message);
627
+ });
628
+ const link = "rounded px-1.5 py-0.5 text-[11px] font-medium transition hover:bg-muted";
629
+ if (mode === "tracking") {
630
+ return /* @__PURE__ */ jsxs("div", { className: "mt-1.5 space-y-1.5", children: [
631
+ /* @__PURE__ */ jsx(
632
+ "input",
633
+ {
634
+ value: carrier,
635
+ onChange: (event) => setCarrier(event.target.value),
636
+ placeholder: "Transportadora (Correios, motoboy\u2026)",
637
+ "data-testid": "shipment-carrier-input",
638
+ className: "w-full rounded-md border bg-background px-2 py-1 text-xs outline-none focus:border-primary"
639
+ }
640
+ ),
641
+ /* @__PURE__ */ jsx(
642
+ "input",
643
+ {
644
+ value: tracking,
645
+ onChange: (event) => setTracking(event.target.value),
646
+ placeholder: "C\xF3digo de rastreio",
647
+ "data-testid": "shipment-tracking-input",
648
+ className: "w-full rounded-md border bg-background px-2 py-1 text-xs outline-none focus:border-primary"
649
+ }
650
+ ),
651
+ /* @__PURE__ */ jsx(
652
+ "input",
653
+ {
654
+ value: trackingUrl,
655
+ onChange: (event) => setTrackingUrl(event.target.value),
656
+ placeholder: "Link de rastreio (opcional)",
657
+ "data-testid": "shipment-tracking-url-input",
658
+ className: "w-full rounded-md border bg-background px-2 py-1 text-xs outline-none focus:border-primary"
659
+ }
660
+ ),
661
+ error && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-red-700", children: error }),
662
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
663
+ /* @__PURE__ */ jsx(
664
+ "button",
665
+ {
666
+ type: "button",
667
+ onClick: saveTracking,
668
+ disabled: busy,
669
+ "data-testid": "shipment-tracking-save",
670
+ className: "rounded bg-primary px-2 py-0.5 text-[11px] font-medium text-primary-foreground disabled:opacity-60",
671
+ children: busy ? "Salvando\u2026" : "Salvar"
672
+ }
673
+ ),
674
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
675
+ setMode("idle");
676
+ setError(null);
677
+ }, className: `border ${link}`, children: "Voltar" })
678
+ ] })
679
+ ] });
680
+ }
681
+ if (mode === "cancel") {
682
+ return /* @__PURE__ */ jsxs("div", { className: "mt-1.5 space-y-1.5 rounded border border-red-200 bg-red-50 p-1.5", children: [
683
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-red-900", children: "Cancelar devolve os itens ao estoque." }),
684
+ error && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-red-700", children: error }),
685
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
686
+ /* @__PURE__ */ jsx(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ onClick: cancelShipment,
691
+ disabled: busy,
692
+ "data-testid": "shipment-cancel-confirm",
693
+ className: "rounded bg-red-600 px-2 py-0.5 text-[11px] font-medium text-white disabled:opacity-60",
694
+ children: busy ? "Cancelando\u2026" : "Cancelar envio"
695
+ }
696
+ ),
697
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => {
698
+ setMode("idle");
699
+ setError(null);
700
+ }, className: `border bg-background ${link}`, children: "Voltar" })
701
+ ] })
702
+ ] });
703
+ }
704
+ return /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-1.5", children: [
705
+ !hasTracking && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setMode("tracking"), "data-testid": "shipment-add-tracking", className: `border ${link}`, children: "Informar rastreio" }),
706
+ status !== "delivered" && /* @__PURE__ */ jsx("button", { type: "button", onClick: markDelivered, disabled: busy, "data-testid": "shipment-deliver", className: `border ${link}`, children: busy ? "Salvando\u2026" : "Marcar como entregue" }),
707
+ status !== "delivered" && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setMode("cancel"), "data-testid": "shipment-cancel", className: `border border-red-200 text-red-700 hover:bg-red-50 ${link}`, children: "Cancelar envio" }),
708
+ error && /* @__PURE__ */ jsx("p", { className: "w-full text-[11px] text-red-700", children: error })
709
+ ] });
710
+ }
711
+ var SOURCE_LABEL = {
712
+ bling: "Bling",
713
+ appmax: "AppMax",
714
+ abacatepay: "AbacatePay"
715
+ };
716
+ var KIND_LABEL = {
717
+ sales_order: "Pedido de venda",
718
+ payment: "Cobran\xE7a",
719
+ nfe: "Nota fiscal"
720
+ };
721
+ var STATUS_LABEL = {
722
+ filed: "Registrado",
723
+ pending: "Aguardando",
724
+ paid: "Pago",
725
+ failed: "Falhou",
726
+ refunded: "Estornado",
727
+ draft: "Rascunho"
728
+ };
729
+ var dateTime = (iso) => new Intl.DateTimeFormat("pt-BR", {
730
+ day: "2-digit",
731
+ month: "2-digit",
732
+ year: "numeric",
733
+ hour: "2-digit",
734
+ minute: "2-digit"
735
+ }).format(new Date(iso));
736
+ function OrderProviderLinks({ orderId }) {
737
+ const [links, setLinks] = useState(null);
738
+ useEffect(() => {
739
+ let cancelled = false;
740
+ const db = getSupabaseClientOptional();
741
+ if (!db) return;
742
+ db.from("plg_shop_order_links").select("id,source,kind,external_id,external_number,url,status,synced_at").eq("order_id", orderId).neq("kind", "payment").order("created_at", { ascending: true }).then(({ data }) => {
743
+ if (!cancelled) setLinks(data ?? []);
744
+ });
745
+ return () => {
746
+ cancelled = true;
747
+ };
748
+ }, [orderId]);
749
+ if (!links || links.length === 0) return null;
750
+ return /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Nos sistemas conectados", hint: "Onde este pedido j\xE1 chegou", children: /* @__PURE__ */ jsx("div", { className: "space-y-2 text-sm", "data-testid": "order-provider-links", children: links.map((link) => /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5", children: [
751
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
752
+ SOURCE_LABEL[link.source] ?? link.source,
753
+ " \xB7 ",
754
+ KIND_LABEL[link.kind] ?? link.kind
755
+ ] }),
756
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
757
+ link.url ? /* @__PURE__ */ jsx("a", { href: link.url, target: "_blank", rel: "noreferrer", className: "font-mono text-xs text-primary hover:underline", children: link.external_number ?? link.external_id }) : /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: link.external_number ?? link.external_id }),
758
+ link.status && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs", children: STATUS_LABEL[link.status] ?? link.status })
759
+ ] }),
760
+ link.synced_at && /* @__PURE__ */ jsxs("span", { className: "w-full text-[11px] text-muted-foreground", children: [
761
+ "atualizado em ",
762
+ dateTime(link.synced_at)
763
+ ] })
764
+ ] }, link.id)) }) });
765
+ }
766
+ var PROVIDER_LABEL = {
767
+ abacatepay: "AbacatePay",
768
+ appmax: "AppMax",
769
+ mercadopago: "Mercado Pago",
770
+ stripe: "Stripe"
771
+ };
772
+ var STATUS_LABEL2 = {
773
+ paid: "Confirmado",
774
+ pending: "Aguardando",
775
+ failed: "Recusado",
776
+ refunded: "Estornado"
777
+ };
778
+ function OrderPaymentDetail({ orderId }) {
779
+ const [link, setLink] = useState(null);
780
+ const [copied, setCopied] = useState(false);
781
+ useEffect(() => {
782
+ let cancelled = false;
783
+ const db = getSupabaseClientOptional();
784
+ if (!db) return;
785
+ db.from("plg_shop_order_links").select("source,external_id,url,status").eq("order_id", orderId).eq("kind", "payment").order("created_at", { ascending: false }).limit(1).then(({ data }) => {
786
+ if (!cancelled) setLink(data?.[0] ?? null);
787
+ });
788
+ return () => {
789
+ cancelled = true;
790
+ };
791
+ }, [orderId]);
792
+ if (!link) return null;
793
+ async function copy() {
794
+ if (!link) return;
795
+ try {
796
+ await navigator.clipboard.writeText(link.external_id);
797
+ setCopied(true);
798
+ setTimeout(() => setCopied(false), 2400);
799
+ } catch {
800
+ }
801
+ }
802
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "order-payment-detail", className: "mt-1 space-y-1.5", children: [
803
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3 text-sm", children: [
804
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Cobrado por" }),
805
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
806
+ PROVIDER_LABEL[link.source] ?? link.source,
807
+ link.status && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: STATUS_LABEL2[link.status] ?? link.status })
808
+ ] })
809
+ ] }),
810
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3 text-sm", children: [
811
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Comprovante" }),
812
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
813
+ /* @__PURE__ */ jsx("code", { className: "max-w-[16rem] truncate font-mono text-xs", title: link.external_id, children: link.external_id }),
814
+ /* @__PURE__ */ jsx(
815
+ "button",
816
+ {
817
+ type: "button",
818
+ onClick: copy,
819
+ "aria-label": "Copiar o c\xF3digo da cobran\xE7a",
820
+ "data-testid": "order-payment-copy",
821
+ className: "rounded p-1 text-muted-foreground transition hover:bg-muted hover:text-foreground",
822
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
823
+ }
824
+ ),
825
+ link.url && /* @__PURE__ */ jsx(
826
+ "a",
827
+ {
828
+ href: link.url,
829
+ target: "_blank",
830
+ rel: "noreferrer",
831
+ "aria-label": "Abrir no provedor",
832
+ className: "rounded p-1 text-muted-foreground transition hover:bg-muted hover:text-foreground",
833
+ children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5" })
834
+ }
835
+ )
836
+ ] })
837
+ ] })
838
+ ] });
839
+ }
840
+ async function cancelShopOrder(provider, orderId, reason) {
841
+ if (typeof provider.cancelOrder === "function") {
842
+ await provider.cancelOrder(orderId, reason);
843
+ return;
844
+ }
845
+ const db = getSupabaseClientOptional();
846
+ if (!db?.rpc) throw new Error("Cancelamento indispon\xEDvel nesta loja.");
847
+ const { error } = await db.rpc("shop_cancel_order", { p_order_id: orderId, p_reason: reason ?? null });
848
+ if (error) throw new Error(error.message);
849
+ }
850
+ var money2 = (v, currency2 = "BRL") => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(Number(v) || 0);
851
+ var dateTime2 = (iso) => new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
852
+ var dateOnly = (iso) => new Date(iso).toLocaleDateString("pt-BR");
853
+ var paidAtLabel = (iso) => {
854
+ const at = new Date(iso);
855
+ const midnightUtc = at.getUTCHours() === 0 && at.getUTCMinutes() === 0 && at.getUTCSeconds() === 0;
856
+ return midnightUtc ? new Intl.DateTimeFormat("pt-BR", {
857
+ day: "2-digit",
858
+ month: "2-digit",
859
+ year: "numeric",
860
+ timeZone: "UTC"
861
+ }).format(at) : dateTime2(iso);
862
+ };
863
+ var PAYMENT_LABEL = {
864
+ pix: "Pix",
865
+ credit_card: "Cart\xE3o de cr\xE9dito",
866
+ debit_card: "Cart\xE3o de d\xE9bito",
867
+ boleto: "Boleto",
868
+ cash: "Dinheiro",
869
+ other: "Outro"
870
+ };
871
+ var ORDER_STATUS_LABEL = {
872
+ paid: "Pago",
873
+ pending: "Aguardando pagamento",
874
+ refunded: "Estornado",
875
+ partially_refunded: "Estorno parcial",
876
+ voided: "Cancelado",
877
+ fulfilled: "Enviado",
878
+ unfulfilled: "N\xE3o enviado",
879
+ partially_fulfilled: "Envio parcial",
880
+ open: "Aberto",
881
+ cancelled: "Cancelado",
882
+ archived: "Arquivado"
883
+ };
884
+ var RECEIVABLE_LABEL = {
885
+ open: "Em aberto",
886
+ pending: "Em aberto",
887
+ partial: "Parcialmente recebido",
888
+ paid: "Recebido",
889
+ overdue: "Vencido",
890
+ cancelled: "Cancelado"
891
+ };
892
+ var SHIPMENT_LABEL = {
893
+ pending: "Aguardando coleta",
894
+ in_transit: "Em tr\xE2nsito",
895
+ delivered: "Entregue",
896
+ failed: "Falhou",
897
+ returned: "Devolvido",
898
+ cancelled: "Cancelado"
899
+ };
900
+ function toneOf(value) {
901
+ switch (value) {
902
+ case "paid":
903
+ case "fulfilled":
904
+ case "delivered":
905
+ return "success";
906
+ case "pending":
907
+ case "partial":
908
+ case "partially_fulfilled":
909
+ case "open":
910
+ case "in_transit":
911
+ return "warning";
912
+ case "refunded":
913
+ case "partially_refunded":
914
+ case "voided":
915
+ case "cancelled":
916
+ case "overdue":
917
+ case "failed":
918
+ case "returned":
919
+ return "destructive";
920
+ default:
921
+ return "muted";
922
+ }
923
+ }
924
+ function InlineBadge({ value }) {
925
+ const tone = toneOf(value);
926
+ const cls = {
927
+ success: "bg-emerald-100 text-emerald-800",
928
+ warning: "bg-amber-100 text-amber-800",
929
+ destructive: "bg-red-100 text-red-700",
930
+ info: "bg-sky-100 text-sky-800",
931
+ muted: "bg-muted text-muted-foreground"
932
+ };
933
+ return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls[tone]}`, children: ORDER_STATUS_LABEL[value] ?? value });
934
+ }
935
+ var LIFECYCLE_CONFIRM = {
936
+ cancelled: {
937
+ cta: "Cancelar pedido",
938
+ warning: "Cancelar encerra os envios em aberto, devolve o estoque que estava reservado para este pedido e cancela a cobran\xE7a que ainda n\xE3o foi paga."
939
+ },
940
+ archived: {
941
+ cta: "Arquivar",
942
+ warning: "Arquivar tira o pedido do dia a dia sem mudar nada do que j\xE1 aconteceu. D\xE1 para reabrir depois."
943
+ },
944
+ // Reopening is only a status change: it does not re-reserve the stock a cancel
945
+ // gave back, nor re-open the charge it closed. Saying so beats a merchant
946
+ // assuming the order came back exactly as it was.
947
+ open: {
948
+ cta: "Reabrir",
949
+ warning: "O pedido volta para a lista de abertos. O estoque e a cobran\xE7a N\xC3O voltam sozinhos \u2014 se foi cancelado, confira os dois antes de despachar."
950
+ }
951
+ };
952
+ function deliveryFromNotes(notes) {
953
+ if (!notes) return null;
954
+ const match = notes.match(/entrega:\s*(.+)/i);
955
+ return (match?.[1] ?? "").trim() || null;
956
+ }
957
+ var shopOrderCache = /* @__PURE__ */ new Map();
958
+ var shopShipmentsCache = /* @__PURE__ */ new Map();
959
+ var shopReceivableCache = /* @__PURE__ */ new Map();
960
+ function OrderDetailView({ orderId, provider, onBack }) {
961
+ const runtime = usePluginRuntimeOptional();
962
+ const hasFinancial = (runtime?.activePlugins ?? []).some((p) => p.id === "financial");
963
+ const [order, setOrder] = useState(() => shopOrderCache.get(orderId) ?? null);
964
+ const [shipments, setShipments] = useState(() => shopShipmentsCache.get(orderId) ?? []);
965
+ const [loading, setLoading] = useState(() => !shopOrderCache.has(orderId));
966
+ const [error, setError] = useState(null);
967
+ const [confirming, setConfirming] = useState(false);
968
+ const [confirmError, setConfirmError] = useState(null);
969
+ const [pendingStatus, setPendingStatus] = useState(null);
970
+ const [statusSaving, setStatusSaving] = useState(false);
971
+ const [statusError, setStatusError] = useState(null);
972
+ const [cancelReason, setCancelReason] = useState("");
973
+ const [receivable, setReceivable] = useState(() => shopReceivableCache.get(orderId) ?? null);
974
+ useEffect(() => {
975
+ let cancelled = false;
976
+ const cached = shopOrderCache.get(orderId);
977
+ if (cached) {
978
+ setOrder(cached);
979
+ setLoading(false);
980
+ return () => {
981
+ cancelled = true;
982
+ };
983
+ }
984
+ setLoading(true);
985
+ setError(null);
986
+ provider().getOrder(orderId).then((o) => {
987
+ if (!cancelled) {
988
+ if (o) shopOrderCache.set(orderId, o);
989
+ setOrder(o);
990
+ }
991
+ }).catch((err) => {
992
+ if (cancelled) return;
993
+ if (err?.code === "22P02") {
994
+ setOrder(null);
995
+ return;
996
+ }
997
+ setError(errorMessage(err));
998
+ }).finally(() => {
999
+ if (!cancelled) setLoading(false);
1000
+ });
1001
+ return () => {
1002
+ cancelled = true;
1003
+ };
1004
+ }, [orderId, provider]);
1005
+ useEffect(() => {
1006
+ let cancelled = false;
1007
+ const db = getSupabaseClientOptional();
1008
+ if (!db) return;
1009
+ if (shopShipmentsCache.has(orderId)) return () => {
1010
+ cancelled = true;
1011
+ };
1012
+ db.from("fulfillments").select("*").eq("order_id", orderId).order("created_at", { ascending: false }).then(({ data: data2 }) => {
1013
+ if (!cancelled) {
1014
+ shopShipmentsCache.set(orderId, data2 ?? []);
1015
+ setShipments(data2 ?? []);
1016
+ }
1017
+ });
1018
+ return () => {
1019
+ cancelled = true;
1020
+ };
1021
+ }, [orderId]);
1022
+ const loadReceivable = useCallback(async (force = false) => {
1023
+ const db = getSupabaseClientOptional();
1024
+ if (!db) return;
1025
+ if (force) shopReceivableCache.delete(orderId);
1026
+ else if (shopReceivableCache.has(orderId)) {
1027
+ setReceivable(shopReceivableCache.get(orderId) ?? null);
1028
+ return;
1029
+ }
1030
+ const [balance, bills] = await Promise.all([
1031
+ db.from("v_invoice_balances").select("*").eq("invoice_id", orderId).maybeSingle(),
1032
+ db.from("plg_financial_movements").select("*").eq("invoice_id", orderId).eq("movement_kind", "bill").order("installment_number", { ascending: true })
1033
+ ]);
1034
+ if (!balance?.data) return;
1035
+ const rec = { balance: balance.data, bills: bills?.data ?? [] };
1036
+ shopReceivableCache.set(orderId, rec);
1037
+ setReceivable(rec);
1038
+ }, [orderId]);
1039
+ useEffect(() => {
1040
+ void loadReceivable();
1041
+ }, [loadReceivable]);
1042
+ if (loading) return /* @__PURE__ */ jsx(OrderDetailSkeleton, {});
1043
+ if (error) {
1044
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800", children: [
1045
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: "N\xE3o foi poss\xEDvel carregar o pedido." }),
1046
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-red-700", children: error })
1047
+ ] });
1048
+ }
1049
+ if (!order) return /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-sm text-muted-foreground", children: "Pedido n\xE3o encontrado." });
1050
+ const cur = order.currency;
1051
+ const delivery = deliveryFromNotes(order.notes);
1052
+ const addr = order.shippingAddress;
1053
+ async function reloadShipments() {
1054
+ shopShipmentsCache.delete(orderId);
1055
+ shopOrderCache.delete(orderId);
1056
+ const db = getSupabaseClientOptional();
1057
+ if (db) {
1058
+ const { data: data2 } = await db.from("fulfillments").select("*").eq("order_id", orderId).order("created_at", { ascending: false });
1059
+ shopShipmentsCache.set(orderId, data2 ?? []);
1060
+ setShipments(data2 ?? []);
1061
+ }
1062
+ const fresh = await provider().getOrder(orderId);
1063
+ if (fresh) {
1064
+ shopOrderCache.set(orderId, fresh);
1065
+ setOrder(fresh);
1066
+ }
1067
+ }
1068
+ async function registerReceipt() {
1069
+ if (!order) return;
1070
+ setConfirming(true);
1071
+ setConfirmError(null);
1072
+ try {
1073
+ const fresh = await provider().confirmPayment?.(order.id) ?? await provider().getOrder(order.id);
1074
+ if (fresh) {
1075
+ shopOrderCache.set(order.id, fresh);
1076
+ setOrder(fresh);
1077
+ }
1078
+ await loadReceivable(true);
1079
+ } catch (err) {
1080
+ setConfirmError(errorMessage(err));
1081
+ } finally {
1082
+ setConfirming(false);
1083
+ }
1084
+ }
1085
+ async function applyStatus(next) {
1086
+ if (!order) return;
1087
+ setStatusSaving(true);
1088
+ setStatusError(null);
1089
+ try {
1090
+ if (next === "cancelled") {
1091
+ await cancelShopOrder(provider(), order.id, cancelReason.trim() || void 0);
1092
+ shopOrderCache.delete(order.id);
1093
+ shopShipmentsCache.delete(order.id);
1094
+ const fresh = await provider().getOrder(order.id);
1095
+ if (fresh) {
1096
+ shopOrderCache.set(order.id, fresh);
1097
+ setOrder(fresh);
1098
+ }
1099
+ await reloadShipments();
1100
+ await loadReceivable(true);
1101
+ } else {
1102
+ const updated = await provider().updateOrder(order.id, { status: next });
1103
+ if (updated) {
1104
+ shopOrderCache.set(order.id, updated);
1105
+ setOrder(updated);
1106
+ }
1107
+ }
1108
+ setPendingStatus(null);
1109
+ setCancelReason("");
1110
+ } catch (err) {
1111
+ setStatusError(errorMessage(err));
1112
+ } finally {
1113
+ setStatusSaving(false);
1114
+ }
1115
+ }
1116
+ const destructive = pendingStatus === "cancelled";
1117
+ const statusActions = pendingStatus ? /* @__PURE__ */ jsxs("div", { className: `w-full space-y-2 rounded-md border px-3 py-2 ${destructive ? "border-red-300 bg-red-50" : "border-amber-300 bg-amber-50"}`, children: [
1118
+ /* @__PURE__ */ jsx("p", { className: `text-xs ${destructive ? "text-red-900" : "text-amber-900"}`, children: LIFECYCLE_CONFIRM[pendingStatus].warning }),
1119
+ destructive && order.financialStatus === "paid" && /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-red-900", children: "Este pedido est\xE1 pago. Cancelar N\xC3O devolve o dinheiro \u2014 o estorno \xE9 feito na m\xE3o, no Financeiro ou no seu meio de pagamento." }),
1120
+ destructive && /* @__PURE__ */ jsx(
1121
+ "input",
1122
+ {
1123
+ value: cancelReason,
1124
+ onChange: (event) => setCancelReason(event.target.value),
1125
+ placeholder: "Motivo (opcional) \u2014 fica no hist\xF3rico do pedido",
1126
+ "data-testid": "order-cancel-reason",
1127
+ className: "w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary"
1128
+ }
1129
+ ),
1130
+ statusError && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-white px-2.5 py-1.5 text-xs text-red-800", children: statusError }),
1131
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1132
+ /* @__PURE__ */ jsx(
1133
+ "button",
1134
+ {
1135
+ type: "button",
1136
+ onClick: () => applyStatus(pendingStatus),
1137
+ disabled: statusSaving,
1138
+ "data-testid": "order-status-confirm",
1139
+ className: `rounded-md px-3 py-1.5 text-sm font-medium text-white transition disabled:opacity-60 ${destructive ? "bg-red-600 hover:bg-red-700" : "bg-primary hover:opacity-90"}`,
1140
+ children: statusSaving ? "Salvando\u2026" : LIFECYCLE_CONFIRM[pendingStatus].cta
1141
+ }
1142
+ ),
1143
+ /* @__PURE__ */ jsx(
1144
+ "button",
1145
+ {
1146
+ type: "button",
1147
+ onClick: () => {
1148
+ setPendingStatus(null);
1149
+ setStatusError(null);
1150
+ setCancelReason("");
1151
+ },
1152
+ className: "rounded-md border bg-background px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1153
+ children: "Voltar"
1154
+ }
1155
+ )
1156
+ ] })
1157
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1158
+ order.status !== "open" && /* @__PURE__ */ jsx(
1159
+ "button",
1160
+ {
1161
+ type: "button",
1162
+ onClick: () => setPendingStatus("open"),
1163
+ "data-testid": "order-reopen",
1164
+ className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1165
+ children: "Reabrir pedido"
1166
+ }
1167
+ ),
1168
+ order.status === "open" && /* @__PURE__ */ jsx(
1169
+ "button",
1170
+ {
1171
+ type: "button",
1172
+ onClick: () => setPendingStatus("archived"),
1173
+ "data-testid": "order-archive",
1174
+ className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1175
+ children: "Arquivar"
1176
+ }
1177
+ ),
1178
+ order.status !== "cancelled" && /* @__PURE__ */ jsx(
1179
+ "button",
1180
+ {
1181
+ type: "button",
1182
+ onClick: () => setPendingStatus("cancelled"),
1183
+ "data-testid": "order-cancel",
1184
+ className: "rounded-md border border-red-200 px-3 py-1.5 text-sm font-medium text-red-700 transition hover:bg-red-50",
1185
+ children: "Cancelar pedido"
1186
+ }
1187
+ ),
1188
+ statusError && /* @__PURE__ */ jsx("p", { className: "w-full rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-800", children: statusError })
1189
+ ] });
1190
+ const installments = receivable && receivable.bills.length > 0 ? receivable.bills.map((b) => ({
1191
+ number: b.installment_number ?? 0,
1192
+ amount: `${money2(Number(b.paid_amount ?? 0), cur)} / ${money2(Number(b.amount), cur)}`,
1193
+ status: RECEIVABLE_LABEL[b.status] ?? b.status,
1194
+ statusTone: toneOf(b.status),
1195
+ dueDate: `vence ${dateOnly(b.due_date)}`
1196
+ })) : [];
1197
+ const data = {
1198
+ title: `Pedido #${order.orderNumber}`,
1199
+ subtitle: dateTime2(order.createdAt),
1200
+ badges: [
1201
+ { label: ORDER_STATUS_LABEL[order.financialStatus] ?? order.financialStatus, tone: toneOf(order.financialStatus) },
1202
+ { label: ORDER_STATUS_LABEL[order.fulfillmentStatus] ?? order.fulfillmentStatus, tone: toneOf(order.fulfillmentStatus) }
1203
+ ],
1204
+ actions: statusActions,
1205
+ customer: { name: order.customerName, email: order.customerEmail },
1206
+ customerNode: /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-sm", children: [
1207
+ order.customerId ? /* @__PURE__ */ jsx(
1208
+ PersonLink,
1209
+ {
1210
+ personId: order.customerId,
1211
+ name: order.customerName ?? order.customerEmail ?? "Cliente",
1212
+ lookup: shopCustomerLookup(provider),
1213
+ profileHref: `#/customers/${order.customerId}`,
1214
+ className: "font-medium"
1215
+ }
1216
+ ) : /* @__PURE__ */ jsx("p", { className: "font-medium", children: order.customerName ?? "\u2014" }),
1217
+ order.customerEmail && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: order.customerEmail }),
1218
+ !order.customerId && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Compra sem cadastro \u2014 n\xE3o h\xE1 ficha de cliente para abrir." })
1219
+ ] }),
1220
+ items: order.items.map((it) => ({
1221
+ id: it.id,
1222
+ name: it.name,
1223
+ description: it.sku,
1224
+ imageUrl: it.imageUrl,
1225
+ quantity: it.quantity,
1226
+ unitPrice: money2(it.unitPrice, cur),
1227
+ total: money2(it.total, cur)
1228
+ })),
1229
+ totals: [
1230
+ { label: "Subtotal", value: money2(order.subtotal, cur) },
1231
+ ...order.discountTotal > 0 ? [{ label: `Desconto${order.discountCode ? ` (${order.discountCode})` : ""}`, value: `\u2212 ${money2(order.discountTotal, cur)}`, muted: true }] : [],
1232
+ { label: "Entrega", value: money2(order.shippingTotal, cur) },
1233
+ { label: "Impostos", value: money2(order.taxTotal, cur) },
1234
+ { label: "Total", value: money2(order.total, cur), strong: true }
1235
+ ],
1236
+ paymentMethod: order.paymentMethodKind ? PAYMENT_LABEL[order.paymentMethodKind] ?? order.paymentMethodKind : void 0,
1237
+ paidAt: order.paidAt ? paidAtLabel(order.paidAt) : void 0,
1238
+ paymentExtra: /* @__PURE__ */ jsx(OrderPaymentDetail, { orderId: order.id }),
1239
+ receivable: receivable ? {
1240
+ referenceNumber: receivable.balance.reference_number ?? void 0,
1241
+ statusBadge: { label: RECEIVABLE_LABEL[receivable.balance.status] ?? receivable.balance.status, tone: toneOf(receivable.balance.status) },
1242
+ rows: [
1243
+ { label: "Faturado", value: money2(receivable.balance.amount, cur) },
1244
+ { label: "Recebido", value: money2(receivable.balance.paid, cur) },
1245
+ { label: "Saldo", value: money2(receivable.balance.balance, cur), strong: true }
1246
+ ],
1247
+ installments,
1248
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
1249
+ receivable.balance.balance > 0 && receivable.balance.status !== "cancelled" && /* @__PURE__ */ jsx(
1250
+ "button",
1251
+ {
1252
+ type: "button",
1253
+ onClick: registerReceipt,
1254
+ disabled: confirming,
1255
+ "data-testid": "order-register-receipt",
1256
+ className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
1257
+ children: confirming ? "Registrando\u2026" : "Registrar recebimento"
1258
+ }
1259
+ ),
1260
+ hasFinancial && /* @__PURE__ */ jsx(
1261
+ "button",
1262
+ {
1263
+ type: "button",
1264
+ onClick: () => adminNavigateTo("/financial"),
1265
+ className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1266
+ children: "Abrir no Financeiro"
1267
+ }
1268
+ ),
1269
+ confirmError && /* @__PURE__ */ jsx("p", { className: "mt-2 w-full rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-800", children: confirmError })
1270
+ ] }),
1271
+ emptyLabel: "Sem conta a receber para este pedido."
1272
+ } : { rows: [], installments: [], emptyLabel: "Sem conta a receber para este pedido." },
1273
+ extraSections: /* @__PURE__ */ jsxs(Fragment, { children: [
1274
+ /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Entrega", hint: "O que a log\xEDstica precisa", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-sm", children: [
1275
+ addr ? /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
1276
+ /* @__PURE__ */ jsxs("p", { children: [
1277
+ [addr.street, addr.number].filter(Boolean).join(", "),
1278
+ addr.complement ? ` \u2014 ${addr.complement}` : ""
1279
+ ] }),
1280
+ addr.district && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: addr.district }),
1281
+ /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground", children: [
1282
+ [addr.city, addr.state].filter((v) => v && v !== "\u2014").join("/"),
1283
+ addr.postal_code ? ` \xB7 ${addr.postal_code}` : ""
1284
+ ] }),
1285
+ (addr.recipient || addr.phone) && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: [addr.recipient, addr.phone].filter(Boolean).join(" \xB7 ") })
1286
+ ] }) : /* @__PURE__ */ jsx("p", { className: delivery ? "" : "text-muted-foreground", children: delivery ?? "Nenhum endere\xE7o registrado neste pedido." }),
1287
+ /* @__PURE__ */ jsxs("div", { className: "border-t pt-2", children: [
1288
+ /* @__PURE__ */ jsx(OrderDetailRow, { label: "Status", value: /* @__PURE__ */ jsx(InlineBadge, { value: order.fulfillmentStatus }) }),
1289
+ shipments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Nenhum envio registrado." }) : /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: shipments.map((s) => /* @__PURE__ */ jsxs("div", { className: "rounded-md border px-2 py-1.5", children: [
1290
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1291
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: [s.carrier, s.service].filter(Boolean).join(" \xB7 ") || "Envio" }),
1292
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: SHIPMENT_LABEL[s.status] ?? s.status })
1293
+ ] }),
1294
+ s.tracking_code && /* @__PURE__ */ jsx("p", { className: "mt-0.5 font-mono text-xs", children: s.tracking_url ? /* @__PURE__ */ jsx("a", { href: s.tracking_url, target: "_blank", rel: "noreferrer", className: "text-primary hover:underline", children: s.tracking_code }) : s.tracking_code }),
1295
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5 text-[11px] text-muted-foreground", children: [
1296
+ s.shipped_at && /* @__PURE__ */ jsxs("p", { children: [
1297
+ "Despachado em ",
1298
+ dateTime2(s.shipped_at)
1299
+ ] }),
1300
+ s.estimated_delivery_at && /* @__PURE__ */ jsxs("p", { children: [
1301
+ "Previs\xE3o: ",
1302
+ dateTime2(s.estimated_delivery_at)
1303
+ ] }),
1304
+ s.delivered_at && /* @__PURE__ */ jsxs("p", { children: [
1305
+ "Entregue em ",
1306
+ dateTime2(s.delivered_at)
1307
+ ] })
1308
+ ] }),
1309
+ /* @__PURE__ */ jsx(
1310
+ ShipmentActions,
1311
+ {
1312
+ fulfillmentId: s.id,
1313
+ status: s.status,
1314
+ hasTracking: Boolean(s.tracking_code),
1315
+ provider,
1316
+ onChanged: reloadShipments
1317
+ }
1318
+ )
1319
+ ] }, s.id)) }),
1320
+ order.fulfillmentStatus !== "fulfilled" && /* @__PURE__ */ jsxs(Fragment, { children: [
1321
+ /* @__PURE__ */ jsx(ShipWithCarrier, { orderId: order.id, provider, onShipped: reloadShipments }),
1322
+ /* @__PURE__ */ jsx(ShipOrderForm, { orderId: order.id, provider, onShipped: reloadShipments })
1323
+ ] })
1324
+ ] })
1325
+ ] }) }),
1326
+ /* @__PURE__ */ jsx(OrderProviderLinks, { orderId: order.id }),
1327
+ /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Hist\xF3rico", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-sm", children: [
1328
+ /* @__PURE__ */ jsx(OrderDetailRow, { label: "Criado", value: dateTime2(order.createdAt) }),
1329
+ /* @__PURE__ */ jsx(OrderDetailRow, { label: "Atualizado", value: dateTime2(order.updatedAt) }),
1330
+ /* @__PURE__ */ jsx(OrderDetailRow, { label: "Situa\xE7\xE3o", value: /* @__PURE__ */ jsx(InlineBadge, { value: order.status }) })
1331
+ ] }) }),
1332
+ order.notes && /* @__PURE__ */ jsx(OrderDetailBlock, { title: "Observa\xE7\xF5es", children: /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: order.notes }) })
1333
+ ] }),
1334
+ onBack,
1335
+ backLabel: "Pedidos"
1336
+ };
1337
+ return /* @__PURE__ */ jsx(OrderDetailView$1, { ...data });
1338
+ }
1339
+ var POLL_MS = 4e3;
1340
+ function NewOrderCharge({
1341
+ open,
1342
+ provider,
1343
+ sessionId,
1344
+ emv,
1345
+ qrcodeBase64,
1346
+ total,
1347
+ currency: currency2,
1348
+ customerName,
1349
+ customerPhone,
1350
+ onPaid,
1351
+ onClose
1352
+ }) {
1353
+ const [state, setState] = useState("waiting");
1354
+ const [copied, setCopied] = useState(false);
1355
+ const [checking, setChecking] = useState(false);
1356
+ const settled = useRef(false);
1357
+ const money4 = new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(total);
1358
+ const check = useCallback(async () => {
1359
+ if (settled.current) return;
1360
+ setChecking(true);
1361
+ try {
1362
+ const result = await provider().settleCheckoutSession?.(sessionId);
1363
+ if (!result) return;
1364
+ if (result.state === "paid") {
1365
+ settled.current = true;
1366
+ setState("paid");
1367
+ setTimeout(() => onPaid(result.orderId ?? void 0), 1200);
1368
+ return;
1369
+ }
1370
+ const outcome = chargeOutcome(result.state);
1371
+ if (outcome !== "waiting") {
1372
+ settled.current = true;
1373
+ setState(outcome);
1374
+ }
1375
+ } finally {
1376
+ setChecking(false);
1377
+ }
1378
+ }, [provider, sessionId, onPaid]);
1379
+ useEffect(() => {
1380
+ if (!open) return;
1381
+ const timer = setInterval(() => {
1382
+ void check();
1383
+ }, POLL_MS);
1384
+ return () => clearInterval(timer);
1385
+ }, [open, check]);
1386
+ async function copy() {
1387
+ try {
1388
+ await navigator.clipboard.writeText(emv);
1389
+ setCopied(true);
1390
+ setTimeout(() => setCopied(false), 2400);
1391
+ } catch {
1392
+ }
1393
+ }
1394
+ const whatsapp = customerPhone ? `https://wa.me/${customerPhone.length > 11 ? customerPhone : `55${customerPhone}`}?text=${encodeURIComponent(
1395
+ `Ol\xE1${customerName ? ` ${customerName.split(" ")[0]}` : ""}! Segue o Pix de ${money4} para o seu pedido:
1396
+
1397
+ ${emv}`
1398
+ )}` : null;
1399
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
1400
+ if (!next) onClose();
1401
+ }, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", "data-testid": "new-order-charge-modal", children: [
1402
+ /* @__PURE__ */ jsxs(ModalHeader, { className: "mb-4", children: [
1403
+ /* @__PURE__ */ jsxs(ModalTitle, { children: [
1404
+ "Pix de ",
1405
+ money4
1406
+ ] }),
1407
+ state === "waiting" && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Vale por 15 minutos. O pedido \xE9 criado quando o pagamento cair." })
1408
+ ] }),
1409
+ /* @__PURE__ */ jsxs(ModalBody, { children: [
1410
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4 sm:flex-row sm:items-start", children: [
1411
+ qrcodeBase64 && /* @__PURE__ */ jsx(
1412
+ "img",
1413
+ {
1414
+ src: `data:image/png;base64,${qrcodeBase64}`,
1415
+ alt: "QR code do Pix",
1416
+ "data-testid": "new-order-qr",
1417
+ className: "h-40 w-40 shrink-0 rounded-md border bg-white p-2"
1418
+ }
1419
+ ),
1420
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-3", children: [
1421
+ /* @__PURE__ */ jsxs("div", { children: [
1422
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Pix copia e cola" }),
1423
+ /* @__PURE__ */ jsx(
1424
+ "p",
1425
+ {
1426
+ "data-testid": "new-order-emv",
1427
+ className: "mt-1 max-h-20 overflow-auto break-all rounded-md border bg-muted/30 p-2 font-mono text-[11px]",
1428
+ children: emv
1429
+ }
1430
+ )
1431
+ ] }),
1432
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
1433
+ /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: copy, "data-testid": "new-order-copy", children: [
1434
+ copied ? /* @__PURE__ */ jsx(Check, { className: "mr-1.5 h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "mr-1.5 h-3.5 w-3.5" }),
1435
+ copied ? "Copiado" : "Copiar c\xF3digo"
1436
+ ] }),
1437
+ whatsapp && /* @__PURE__ */ jsxs(
1438
+ "a",
1439
+ {
1440
+ href: whatsapp,
1441
+ target: "_blank",
1442
+ rel: "noreferrer",
1443
+ "data-testid": "new-order-whatsapp",
1444
+ className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1445
+ children: [
1446
+ /* @__PURE__ */ jsx(MessageCircle, { className: "mr-1.5 h-3.5 w-3.5" }),
1447
+ "Mandar no WhatsApp"
1448
+ ]
1449
+ }
1450
+ )
1451
+ ] })
1452
+ ] })
1453
+ ] }),
1454
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-md border bg-muted/30 px-3 py-2 text-sm", children: [
1455
+ state === "waiting" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-muted-foreground", "data-testid": "new-order-waiting", children: [
1456
+ /* @__PURE__ */ jsx("span", { className: "h-2 w-2 animate-pulse rounded-full bg-amber-500" }),
1457
+ "Aguardando o pagamento\u2026 a tela confirma sozinha."
1458
+ ] }),
1459
+ state === "paid" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 font-medium text-emerald-700", "data-testid": "new-order-paid", children: [
1460
+ /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
1461
+ " Pagamento confirmado \u2014 abrindo o pedido\u2026"
1462
+ ] }),
1463
+ (state === "expired" || state === "failed") && /* @__PURE__ */ jsx("p", { className: "text-red-700", "data-testid": "new-order-dead", children: state === "expired" ? "O c\xF3digo expirou sem pagamento. O pedido continua aqui \u2014 d\xE1 para cobrar de outro jeito." : "O pagamento n\xE3o foi conclu\xEDdo. O pedido continua aqui \u2014 d\xE1 para cobrar de outro jeito." })
1464
+ ] })
1465
+ ] }),
1466
+ /* @__PURE__ */ jsxs(ModalFooter, { className: "mt-4 gap-2", children: [
1467
+ state === "waiting" && /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void check(), disabled: checking, "data-testid": "new-order-check", children: checking ? "Conferindo\u2026" : "J\xE1 pagou? Conferir" }),
1468
+ /* @__PURE__ */ jsx(
1469
+ Button,
1470
+ {
1471
+ variant: state === "waiting" ? "ghost" : "default",
1472
+ onClick: onClose,
1473
+ "data-testid": "new-order-charge-close",
1474
+ children: state === "waiting" ? "Fechar" : "Voltar ao pedido"
1475
+ }
1476
+ )
1477
+ ] })
1478
+ ] }) }) }) });
1479
+ }
1480
+ var EMPTY_CUSTOMER = { id: null, name: "", email: "", phone: "", document: "" };
1481
+ var EMPTY_ADDRESS = {
1482
+ postalCode: "",
1483
+ street: "",
1484
+ number: "",
1485
+ complement: "",
1486
+ district: "",
1487
+ city: "",
1488
+ state: ""
1489
+ };
1490
+ var PAYMENT_METHODS = [
1491
+ { id: "pix", label: "Pix" },
1492
+ { id: "cash", label: "Dinheiro" },
1493
+ { id: "credit_card", label: "Cart\xE3o de cr\xE9dito" },
1494
+ { id: "debit_card", label: "Cart\xE3o de d\xE9bito" },
1495
+ { id: "boleto", label: "Boleto" },
1496
+ { id: "other", label: "Outro" }
1497
+ ];
1498
+ var CHARGEABLE_PROVIDERS = ["abacatepay"];
1499
+ var money3 = (value, currency2 = "BRL") => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(Number(value) || 0);
1500
+ var onlyDigits = (value) => value.replace(/\D/gu, "");
1501
+ var maskPostal = (value) => {
1502
+ const digits = onlyDigits(value).slice(0, 8);
1503
+ return digits.length > 5 ? `${digits.slice(0, 5)}-${digits.slice(5)}` : digits;
1504
+ };
1505
+ var fieldClass = "mt-1 w-full rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary";
1506
+ function Field({ label, hint, children }) {
1507
+ return /* @__PURE__ */ jsxs("label", { className: "block", children: [
1508
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
1509
+ children,
1510
+ hint && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-[11px] text-muted-foreground", children: hint })
1511
+ ] });
1512
+ }
1513
+ function NewOrderPage({ provider, onDone, onCancel }) {
1514
+ const [lines, setLines] = useState([]);
1515
+ const [customer, setCustomer] = useState(EMPTY_CUSTOMER);
1516
+ const [address, setAddress] = useState(EMPTY_ADDRESS);
1517
+ const [notes, setNotes] = useState("");
1518
+ const [discountCode, setDiscountCode] = useState("");
1519
+ const [couponOpen, setCouponOpen] = useState(false);
1520
+ const [settling, setSettling] = useState(false);
1521
+ const [method, setMethod] = useState("pix");
1522
+ const [reference, setReference] = useState("");
1523
+ const [settings, setSettings] = useState(null);
1524
+ const [freight, setFreight] = useState(null);
1525
+ const [saving, setSaving] = useState(false);
1526
+ const [error, setError] = useState(null);
1527
+ const [session, setSession] = useState(null);
1528
+ const [chargeOpen, setChargeOpen] = useState(false);
1529
+ const [editingCustomer, setEditingCustomer] = useState(false);
1530
+ useEffect(() => {
1531
+ let cancelled = false;
1532
+ provider().getStoreSettings?.().then((value) => {
1533
+ if (!cancelled) setSettings(value ?? null);
1534
+ }).catch(() => {
1535
+ });
1536
+ return () => {
1537
+ cancelled = true;
1538
+ };
1539
+ }, [provider]);
1540
+ const acquirer = settings?.paymentProvider ?? null;
1541
+ const canCharge = Boolean(acquirer && CHARGEABLE_PROVIDERS.includes(acquirer));
1542
+ const currency2 = settings?.currency ?? "BRL";
1543
+ const subtotal = useMemo(
1544
+ () => lines.reduce((sum, line) => sum + line.unitPrice * line.quantity, 0),
1545
+ [lines]
1546
+ );
1547
+ const postalDigits = onlyDigits(address.postalCode);
1548
+ const delivers = postalDigits.length === 8;
1549
+ const shipping = delivers ? freight?.price ?? 0 : 0;
1550
+ const quoteFreight = useCallback(async (postal) => {
1551
+ if (postal.length !== 8 || subtotal <= 0) {
1552
+ setFreight(null);
1553
+ return;
1554
+ }
1555
+ try {
1556
+ const options = await provider().quoteShipping?.(postal, subtotal);
1557
+ const cheapest = (options ?? []).slice().sort((a, b) => a.rate - b.rate)[0];
1558
+ setFreight(cheapest ? { price: cheapest.rate, label: cheapest.name || cheapest.carrier || "Entrega" } : null);
1559
+ } catch {
1560
+ setFreight(null);
1561
+ }
1562
+ }, [provider, subtotal]);
1563
+ useEffect(() => {
1564
+ void quoteFreight(postalDigits);
1565
+ }, [postalDigits, quoteFreight]);
1566
+ function addLine(product, variants) {
1567
+ const sellable = variants.filter((v) => v.status !== "archived");
1568
+ const only = sellable.length === 1 ? sellable[0] : null;
1569
+ setLines((current) => {
1570
+ const existing = current.find((line) => line.productId === product.id && !line.variants.length);
1571
+ if (existing && sellable.length <= 1) {
1572
+ return current.map((line) => line.key === existing.key ? { ...line, quantity: line.quantity + 1 } : line);
1573
+ }
1574
+ return [...current, {
1575
+ key: `${product.id}:${current.length}`,
1576
+ productId: product.id,
1577
+ name: product.name,
1578
+ sku: only?.sku ?? product.sku ?? null,
1579
+ imageUrl: product.images?.[0]?.url ?? null,
1580
+ unitPrice: only?.price ?? product.price,
1581
+ quantity: 1,
1582
+ variants: sellable.length > 1 ? sellable : [],
1583
+ variantId: only?.id ?? null,
1584
+ optionsLabel: null
1585
+ }];
1586
+ });
1587
+ }
1588
+ const setQuantity = (key, quantity) => setLines((current) => current.map((line) => line.key === key ? { ...line, quantity: Math.max(0, quantity) } : line).filter((line) => line.quantity > 0));
1589
+ const pickVariant = (key, variantId) => setLines((current) => current.map((line) => {
1590
+ if (line.key !== key) return line;
1591
+ const variant = line.variants.find((v) => v.id === variantId);
1592
+ return variant ? {
1593
+ ...line,
1594
+ variantId: variant.id,
1595
+ sku: variant.sku ?? line.sku,
1596
+ unitPrice: variant.price ?? line.unitPrice,
1597
+ optionsLabel: Object.values(variant.options ?? {}).join(" / ") || null
1598
+ } : line;
1599
+ }));
1600
+ const shippingAddress = delivers ? {
1601
+ postalCode: postalDigits,
1602
+ street: address.street.trim(),
1603
+ number: address.number.trim() || void 0,
1604
+ complement: address.complement.trim() || void 0,
1605
+ district: address.district.trim() || void 0,
1606
+ city: address.city.trim(),
1607
+ state: address.state.trim().toUpperCase(),
1608
+ country: "BR",
1609
+ recipient: customer.name.trim() || void 0,
1610
+ phone: onlyDigits(customer.phone) || void 0,
1611
+ document: onlyDigits(customer.document) || void 0
1612
+ } : void 0;
1613
+ function validate(ending) {
1614
+ return validateNewOrder({
1615
+ lineCount: lines.length,
1616
+ unresolvedVariants: lines.filter((line) => line.variants.length > 0 && !line.variantId).length,
1617
+ customerName: customer.name,
1618
+ customerEmail: customer.email,
1619
+ customerPhone: onlyDigits(customer.phone),
1620
+ customerDocument: customer.document.trim(),
1621
+ documentValid: isValidDocument(customer.document),
1622
+ delivers,
1623
+ addressComplete: Boolean(address.street.trim() && address.city.trim() && address.state.trim())
1624
+ }, ending);
1625
+ }
1626
+ async function resolveCustomerId() {
1627
+ if (customer.id) return customer.id;
1628
+ const email = customer.email.trim().toLowerCase();
1629
+ const [first, ...rest] = customer.name.trim().split(/\s+/u);
1630
+ if (email) {
1631
+ const found = await provider().listCustomers({ search: email, limit: 5 });
1632
+ const hit = found.find((c) => (c.email ?? "").toLowerCase() === email);
1633
+ if (hit) return hit.id;
1634
+ }
1635
+ const created = await provider().createCustomer({
1636
+ firstName: first ?? customer.name.trim(),
1637
+ lastName: rest.join(" "),
1638
+ email: email || void 0,
1639
+ phone: onlyDigits(customer.phone) || void 0
1640
+ });
1641
+ return created.id;
1642
+ }
1643
+ async function finish(ending) {
1644
+ const invalid = validate(ending);
1645
+ if (invalid) {
1646
+ setError(invalid);
1647
+ return;
1648
+ }
1649
+ setSaving(true);
1650
+ setError(null);
1651
+ try {
1652
+ if (ending === "charge") {
1653
+ const opened = await provider().openCheckoutSession?.({
1654
+ items: lines.map((line) => ({
1655
+ productId: line.productId,
1656
+ variantId: line.variantId,
1657
+ quantity: line.quantity,
1658
+ optionsLabel: line.optionsLabel
1659
+ })),
1660
+ customer: { name: customer.name.trim(), email: customer.email.trim().toLowerCase() },
1661
+ shippingAddress,
1662
+ discountCode: discountCode.trim() || null
1663
+ });
1664
+ if (!opened?.charge?.emv) throw new Error("A cobran\xE7a foi aberta sem c\xF3digo Pix. Tente de novo.");
1665
+ setSession({
1666
+ sessionId: opened.sessionId,
1667
+ emv: opened.charge.emv,
1668
+ qrcode: opened.charge.qrcodeBase64,
1669
+ total: opened.total
1670
+ });
1671
+ setChargeOpen(true);
1672
+ return;
1673
+ }
1674
+ const customerId = await resolveCustomerId();
1675
+ const order = await provider().placeOrder({
1676
+ customerId,
1677
+ customer: { name: customer.name.trim(), email: customer.email.trim().toLowerCase() || void 0 },
1678
+ items: lines.map((line) => ({
1679
+ productId: line.productId,
1680
+ variantId: line.variantId ?? void 0,
1681
+ quantity: line.quantity,
1682
+ optionsLabel: line.optionsLabel ?? void 0
1683
+ })),
1684
+ shippingAddress,
1685
+ discountCode: discountCode.trim() || void 0,
1686
+ paymentMethod: ending === "received" ? method : void 0,
1687
+ notes: notes.trim() || void 0
1688
+ });
1689
+ if (ending === "received") {
1690
+ await provider().confirmPayment?.(order.id, reference.trim() || `manual:${method}`);
1691
+ }
1692
+ onDone(order.id);
1693
+ } catch (err) {
1694
+ setError(errorMessage(err));
1695
+ } finally {
1696
+ setSaving(false);
1697
+ }
1698
+ }
1699
+ const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
1700
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: "Cliente", children: /* @__PURE__ */ jsx(
1701
+ CustomerPicker,
1702
+ {
1703
+ provider,
1704
+ value: customer,
1705
+ onChange: setCustomer,
1706
+ editing: editingCustomer,
1707
+ onEditing: setEditingCustomer
1708
+ }
1709
+ ) }),
1710
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: "Entrega", children: /* @__PURE__ */ jsx(
1711
+ DeliveryAddress,
1712
+ {
1713
+ provider,
1714
+ customerId: customer.id,
1715
+ address,
1716
+ onChange: setAddress
1717
+ }
1718
+ ) }),
1719
+ /* @__PURE__ */ jsx(OrderFormBlock, { title: "Observa\xE7\xF5es", children: /* @__PURE__ */ jsx(
1720
+ "textarea",
1721
+ {
1722
+ value: notes,
1723
+ onChange: (event) => setNotes(event.target.value),
1724
+ rows: 2,
1725
+ placeholder: "Combinado por telefone: entregar depois das 18h\u2026",
1726
+ "data-testid": "new-order-notes",
1727
+ className: "w-full resize-y rounded-md border bg-background px-2.5 py-1.5 text-sm outline-none focus:border-primary"
1728
+ }
1729
+ ) })
1730
+ ] });
1731
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1732
+ /* @__PURE__ */ jsxs(
1733
+ OrderFormView,
1734
+ {
1735
+ title: "Novo pedido",
1736
+ parentLabel: "Pedidos",
1737
+ onBack: onCancel,
1738
+ subtitle: "Venda por telefone, WhatsApp ou balc\xE3o",
1739
+ sideSections,
1740
+ children: [
1741
+ /* @__PURE__ */ jsxs(OrderFormBlock, { title: "Produtos", children: [
1742
+ /* @__PURE__ */ jsx(ProductPicker, { provider, onPick: addLine, currency: currency2 }),
1743
+ lines.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3 divide-y", children: lines.map((line) => /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 py-2", children: [
1744
+ /* @__PURE__ */ jsx(Thumbnail, { src: line.imageUrl, size: "lg" }),
1745
+ /* @__PURE__ */ jsxs("div", { className: "min-w-[9rem] flex-1", children: [
1746
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: line.name }),
1747
+ line.variants.length > 0 ? /* @__PURE__ */ jsxs(
1748
+ "select",
1749
+ {
1750
+ value: line.variantId ?? "",
1751
+ onChange: (event) => pickVariant(line.key, event.target.value),
1752
+ "data-testid": "new-order-variant",
1753
+ className: "mt-1 rounded-md border bg-background px-2 py-1 text-xs outline-none focus:border-primary",
1754
+ children: [
1755
+ /* @__PURE__ */ jsx("option", { value: "", children: "Escolha a varia\xE7\xE3o\u2026" }),
1756
+ line.variants.map((variant) => /* @__PURE__ */ jsx("option", { value: variant.id, children: Object.values(variant.options ?? {}).join(" / ") || variant.sku || "Varia\xE7\xE3o" }, variant.id))
1757
+ ]
1758
+ }
1759
+ ) : (
1760
+ // The line shows its total; from two units on, the unit
1761
+ // price is the number the merchant is asked to repeat.
1762
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: line.quantity > 1 ? `${line.quantity} \xD7 ${money3(line.unitPrice, currency2)}` : line.sku })
1763
+ )
1764
+ ] }),
1765
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1766
+ /* @__PURE__ */ jsx(
1767
+ "button",
1768
+ {
1769
+ type: "button",
1770
+ "aria-label": "Menos um",
1771
+ onClick: () => setQuantity(line.key, line.quantity - 1),
1772
+ className: "fz-tap flex items-center justify-center rounded-md border p-1.5 transition hover:bg-muted",
1773
+ children: /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5" })
1774
+ }
1775
+ ),
1776
+ /* @__PURE__ */ jsx(
1777
+ "input",
1778
+ {
1779
+ value: line.quantity,
1780
+ onChange: (event) => setQuantity(line.key, Number(onlyDigits(event.target.value) || 0)),
1781
+ "aria-label": "Quantidade",
1782
+ "data-testid": "new-order-quantity",
1783
+ className: "fz-tap w-14 rounded-md border bg-background px-1 py-1.5 text-center text-sm tabular-nums outline-none focus:border-primary"
1784
+ }
1785
+ ),
1786
+ /* @__PURE__ */ jsx(
1787
+ "button",
1788
+ {
1789
+ type: "button",
1790
+ "aria-label": "Mais um",
1791
+ onClick: () => setQuantity(line.key, line.quantity + 1),
1792
+ className: "fz-tap flex items-center justify-center rounded-md border p-1.5 transition hover:bg-muted",
1793
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
1794
+ }
1795
+ )
1796
+ ] }),
1797
+ /* @__PURE__ */ jsx("div", { className: "ml-auto w-24 shrink-0 text-right text-sm tabular-nums", children: money3(line.unitPrice * line.quantity, currency2) }),
1798
+ /* @__PURE__ */ jsx(
1799
+ "button",
1800
+ {
1801
+ type: "button",
1802
+ "aria-label": "Remover item",
1803
+ onClick: () => setQuantity(line.key, 0),
1804
+ className: "fz-tap flex items-center justify-center rounded-md p-1.5 text-muted-foreground transition hover:bg-muted hover:text-foreground",
1805
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
1806
+ }
1807
+ )
1808
+ ] }, line.key)) })
1809
+ ] }),
1810
+ /* @__PURE__ */ jsxs(OrderFormBlock, { title: "Pagamento", children: [
1811
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 text-sm", children: [
1812
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1813
+ /* @__PURE__ */ jsx("span", { children: "Subtotal" }),
1814
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money3(subtotal, currency2) })
1815
+ ] }),
1816
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between gap-3", children: couponOpen || discountCode ? /* @__PURE__ */ jsxs(Fragment, { children: [
1817
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Cupom" }),
1818
+ /* @__PURE__ */ jsx(
1819
+ "input",
1820
+ {
1821
+ value: discountCode,
1822
+ onChange: (event) => setDiscountCode(event.target.value.toUpperCase()),
1823
+ placeholder: "C\xD3DIGO",
1824
+ autoFocus: true,
1825
+ "data-testid": "new-order-discount",
1826
+ className: "w-40 rounded-md border bg-background px-2 py-1 text-right text-sm uppercase outline-none focus:border-primary"
1827
+ }
1828
+ )
1829
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1830
+ /* @__PURE__ */ jsx(
1831
+ "button",
1832
+ {
1833
+ type: "button",
1834
+ onClick: () => setCouponOpen(true),
1835
+ "data-testid": "new-order-add-coupon",
1836
+ className: "text-sm text-muted-foreground underline-offset-2 transition hover:text-foreground hover:underline",
1837
+ children: "Adicionar cupom"
1838
+ }
1839
+ ),
1840
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: "\u2014" })
1841
+ ] }) }),
1842
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1843
+ /* @__PURE__ */ jsx("span", { className: delivers ? "" : "text-muted-foreground", children: delivers ? `Entrega${freight ? ` \xB7 ${freight.label}` : ""}` : "Entrega" }),
1844
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: delivers ? money3(shipping, currency2) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "sem entrega" }) })
1845
+ ] }),
1846
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t pt-2 text-base", children: [
1847
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Total" }),
1848
+ /* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", "data-testid": "new-order-total", children: money3(subtotal + shipping, currency2) })
1849
+ ] })
1850
+ ] }),
1851
+ settling && /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 mt-3 gap-2 rounded-md border bg-muted/30 p-3", children: [
1852
+ /* @__PURE__ */ jsx(Field, { label: "Como recebeu", children: /* @__PURE__ */ jsx(
1853
+ "select",
1854
+ {
1855
+ value: method,
1856
+ onChange: (event) => setMethod(event.target.value),
1857
+ "data-testid": "new-order-method",
1858
+ className: fieldClass,
1859
+ children: PAYMENT_METHODS.map((option) => /* @__PURE__ */ jsx("option", { value: option.id, children: option.label }, option.id))
1860
+ }
1861
+ ) }),
1862
+ /* @__PURE__ */ jsx(Field, { label: "Comprovante", hint: "Id da transa\xE7\xE3o, n\xFAmero do recibo \u2014 fica no pedido", children: /* @__PURE__ */ jsx(
1863
+ "input",
1864
+ {
1865
+ value: reference,
1866
+ onChange: (event) => setReference(event.target.value),
1867
+ placeholder: "Opcional",
1868
+ "data-testid": "new-order-reference",
1869
+ className: fieldClass
1870
+ }
1871
+ ) })
1872
+ ] }),
1873
+ error && /* @__PURE__ */ jsxs(
1874
+ "div",
1875
+ {
1876
+ role: "alert",
1877
+ "data-testid": "new-order-error",
1878
+ className: "mt-3 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800",
1879
+ children: [
1880
+ /* @__PURE__ */ jsx("p", { children: error }),
1881
+ customer.id && /cliente/iu.test(error) && /* @__PURE__ */ jsxs(
1882
+ "button",
1883
+ {
1884
+ type: "button",
1885
+ onClick: () => setEditingCustomer(true),
1886
+ "data-testid": "new-order-fix-customer",
1887
+ className: "mt-1 font-medium underline underline-offset-2",
1888
+ children: [
1889
+ "Editar cadastro de ",
1890
+ customer.name
1891
+ ]
1892
+ }
1893
+ )
1894
+ ]
1895
+ }
1896
+ ),
1897
+ lines.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center gap-2", children: settling ? /* @__PURE__ */ jsxs(Fragment, { children: [
1898
+ /* @__PURE__ */ jsx(Button, { onClick: () => void finish("received"), disabled: saving, "data-testid": "new-order-confirm-received", children: saving ? "Criando\u2026" : "Confirmar pagamento" }),
1899
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setSettling(false), children: "Voltar" })
1900
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1901
+ canCharge && /* @__PURE__ */ jsx(
1902
+ Button,
1903
+ {
1904
+ onClick: () => session ? setChargeOpen(true) : void finish("charge"),
1905
+ disabled: saving,
1906
+ "data-testid": "new-order-charge",
1907
+ children: saving ? "Abrindo\u2026" : session ? "Ver cobran\xE7a" : "Gerar cobran\xE7a Pix"
1908
+ }
1909
+ ),
1910
+ /* @__PURE__ */ jsx(
1911
+ Button,
1912
+ {
1913
+ variant: canCharge ? "outline" : "default",
1914
+ onClick: () => setSettling(true),
1915
+ "data-testid": "new-order-received",
1916
+ children: "J\xE1 recebi"
1917
+ }
1918
+ ),
1919
+ /* @__PURE__ */ jsx(
1920
+ Button,
1921
+ {
1922
+ variant: "ghost",
1923
+ onClick: () => void finish("later"),
1924
+ disabled: saving,
1925
+ "data-testid": "new-order-later",
1926
+ children: "Cobrar depois"
1927
+ }
1928
+ )
1929
+ ] }) })
1930
+ ] })
1931
+ ]
1932
+ }
1933
+ ),
1934
+ session && /* @__PURE__ */ jsx(
1935
+ NewOrderCharge,
1936
+ {
1937
+ open: chargeOpen,
1938
+ provider,
1939
+ sessionId: session.sessionId,
1940
+ emv: session.emv,
1941
+ qrcodeBase64: session.qrcode,
1942
+ total: session.total,
1943
+ currency: currency2,
1944
+ customerPhone: onlyDigits(customer.phone),
1945
+ customerName: customer.name,
1946
+ onPaid: onDone,
1947
+ onClose: () => setChargeOpen(false)
1948
+ }
1949
+ )
1950
+ ] });
1951
+ }
1952
+ function ProductPicker({ provider, onPick, currency: currency2 }) {
1953
+ const [open, setOpen] = useState(false);
1954
+ const search = useCallback(
1955
+ (term) => provider().listProducts({ search: term || void 0, status: "active", limit: 20 }),
1956
+ [provider]
1957
+ );
1958
+ async function add(products) {
1959
+ for (const product of products) {
1960
+ let variants = [];
1961
+ try {
1962
+ variants = (await provider().getProductVariants?.(product.id))?.variants ?? [];
1963
+ } catch {
1964
+ }
1965
+ onPick(product, variants);
1966
+ }
1967
+ }
1968
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1969
+ /* @__PURE__ */ jsxs(
1970
+ "button",
1971
+ {
1972
+ type: "button",
1973
+ onClick: () => setOpen(true),
1974
+ "data-testid": "new-order-product-search",
1975
+ className: "fz-tap flex w-full items-center gap-2 rounded-md border bg-background px-2.5 py-2.5 text-left text-sm text-muted-foreground transition hover:border-primary",
1976
+ children: [
1977
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
1978
+ "Buscar produto por nome ou SKU"
1979
+ ]
1980
+ }
1981
+ ),
1982
+ /* @__PURE__ */ jsx(
1983
+ RecordPickerModal,
1984
+ {
1985
+ open,
1986
+ onClose: () => setOpen(false),
1987
+ title: "Selecionar produtos",
1988
+ placeholder: "Buscar produtos",
1989
+ confirmLabel: "Adicionar",
1990
+ emptyLabel: "Nenhum produto encontrado.",
1991
+ columns: { product: "Produto", meta: "Estoque", value: "Pre\xE7o" },
1992
+ search,
1993
+ rowKey: (product) => product.id,
1994
+ renderRow: (product) => ({
1995
+ imageUrl: product.images?.[0]?.url ?? null,
1996
+ title: product.name,
1997
+ subtitle: product.sku ?? void 0,
1998
+ meta: product.inventoryCount ?? 0,
1999
+ value: money3(product.price, currency2)
2000
+ }),
2001
+ onConfirm: (products) => {
2002
+ void add(products);
2003
+ }
2004
+ }
2005
+ )
2006
+ ] });
2007
+ }
2008
+ function CustomerPicker({ provider, value, onChange, editing, onEditing }) {
2009
+ const [creating, setCreating] = useState(null);
2010
+ const setEditing = onEditing;
2011
+ if (value.id) {
2012
+ const missing = [
2013
+ !value.phone && "telefone",
2014
+ !value.email && "e-mail",
2015
+ !value.document && "CPF"
2016
+ ].filter(Boolean);
2017
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2018
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-md border bg-muted/30 p-2.5", children: [
2019
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 text-sm", children: [
2020
+ /* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: value.name }),
2021
+ value.email && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: value.email }),
2022
+ value.phone && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: formatPhone(value.phone) }),
2023
+ value.document && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: formatDocument(value.document) })
2024
+ ] }),
2025
+ /* @__PURE__ */ jsx(
2026
+ "button",
2027
+ {
2028
+ type: "button",
2029
+ "aria-label": "Trocar cliente",
2030
+ "data-testid": "new-order-customer-clear",
2031
+ onClick: () => onChange(EMPTY_CUSTOMER),
2032
+ className: "fz-tap flex items-center justify-center rounded p-1 text-muted-foreground transition hover:bg-muted hover:text-foreground",
2033
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
2034
+ }
2035
+ )
2036
+ ] }),
2037
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
2038
+ /* @__PURE__ */ jsxs(
2039
+ "button",
2040
+ {
2041
+ type: "button",
2042
+ onClick: () => setEditing(true),
2043
+ "data-testid": "new-order-customer-edit",
2044
+ className: "inline-flex items-center gap-1.5 text-sm text-muted-foreground underline-offset-2 transition hover:text-foreground hover:underline",
2045
+ children: [
2046
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
2047
+ "Editar cadastro"
2048
+ ]
2049
+ }
2050
+ ),
2051
+ missing.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
2052
+ "Falta ",
2053
+ missing.join(", ")
2054
+ ] })
2055
+ ] }),
2056
+ /* @__PURE__ */ jsx(
2057
+ CustomerModal,
2058
+ {
2059
+ open: editing,
2060
+ customerId: value.id,
2061
+ initial: value,
2062
+ provider,
2063
+ onClose: () => setEditing(false),
2064
+ onSaved: (saved) => {
2065
+ onChange({
2066
+ id: saved.id,
2067
+ name: `${saved.firstName} ${saved.lastName}`.trim(),
2068
+ email: saved.email ?? "",
2069
+ phone: saved.phone ?? "",
2070
+ document: saved.document ?? ""
2071
+ });
2072
+ setEditing(false);
2073
+ }
2074
+ }
2075
+ )
2076
+ ] });
2077
+ }
2078
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2079
+ /* @__PURE__ */ jsx(
2080
+ SearchSelect,
2081
+ {
2082
+ value: "",
2083
+ placeholder: "Buscar ou criar cliente",
2084
+ noResultsLabel: "Nenhum cliente com esse nome.",
2085
+ searchingLabel: "Buscando\u2026",
2086
+ allowCreate: true,
2087
+ createLabel: "Criar cliente",
2088
+ onCreate: (query) => setCreating(query),
2089
+ onSearch: async (query) => {
2090
+ const found = await provider().listCustomers({ search: query || void 0, limit: 6 });
2091
+ return found.map((c) => ({
2092
+ id: c.id,
2093
+ label: `${c.firstName} ${c.lastName}`.trim(),
2094
+ subtitle: c.email ?? c.phone ?? void 0,
2095
+ data: c
2096
+ }));
2097
+ },
2098
+ onChange: (id, option) => {
2099
+ const found = option?.data;
2100
+ if (!found) return;
2101
+ onChange({
2102
+ id,
2103
+ name: `${found.firstName} ${found.lastName}`.trim(),
2104
+ email: found.email ?? "",
2105
+ phone: found.phone ?? "",
2106
+ document: found.document ?? ""
2107
+ });
2108
+ }
2109
+ }
2110
+ ),
2111
+ /* @__PURE__ */ jsx(
2112
+ CustomerModal,
2113
+ {
2114
+ open: creating !== null,
2115
+ initialName: creating ?? "",
2116
+ provider,
2117
+ onClose: () => setCreating(null),
2118
+ onSaved: (saved) => {
2119
+ onChange({
2120
+ id: saved.id,
2121
+ name: `${saved.firstName} ${saved.lastName}`.trim(),
2122
+ email: saved.email ?? "",
2123
+ phone: saved.phone ?? "",
2124
+ document: saved.document ?? ""
2125
+ });
2126
+ setCreating(null);
2127
+ }
2128
+ }
2129
+ )
2130
+ ] });
2131
+ }
2132
+ function CustomerModal({ open, initialName, customerId, initial, provider, onClose, onSaved }) {
2133
+ const [first, setFirst] = useState("");
2134
+ const [last, setLast] = useState("");
2135
+ const [email, setEmail] = useState("");
2136
+ const [phone, setPhone] = useState("");
2137
+ const [document, setDocument] = useState("");
2138
+ const [saving, setSaving] = useState(false);
2139
+ const [error, setError] = useState(null);
2140
+ const [duplicate, setDuplicate] = useState(null);
2141
+ useEffect(() => {
2142
+ if (!open) return;
2143
+ const [head, ...rest] = String(initial?.name ?? initialName ?? "").trim().split(/\s+/u);
2144
+ setFirst(head ?? "");
2145
+ setLast(rest.join(" "));
2146
+ setEmail(initial?.email ?? "");
2147
+ setPhone(initial?.phone ?? "");
2148
+ setDocument(initial?.document ?? "");
2149
+ setError(null);
2150
+ setDuplicate(null);
2151
+ }, [open, initialName, initial]);
2152
+ async function save() {
2153
+ if (!first.trim()) {
2154
+ setError("O nome \xE9 obrigat\xF3rio.");
2155
+ return;
2156
+ }
2157
+ if (document && !isValidDocument(document)) {
2158
+ setError("Esse CPF/CNPJ n\xE3o confere.");
2159
+ return;
2160
+ }
2161
+ setSaving(true);
2162
+ setError(null);
2163
+ setDuplicate(null);
2164
+ try {
2165
+ const fields = {
2166
+ firstName: first.trim(),
2167
+ lastName: last.trim(),
2168
+ email: email.trim().toLowerCase() || void 0,
2169
+ phone: onlyDigits(phone) || void 0,
2170
+ document: documentDigits(document) || void 0
2171
+ };
2172
+ const saved = customerId ? await provider().updateCustomer(customerId, fields) : await provider().createCustomer(fields);
2173
+ onSaved(saved);
2174
+ } catch (err) {
2175
+ if (isDuplicateEmail(err)) {
2176
+ const existing = await provider().listCustomers({ search: email.trim().toLowerCase(), limit: 5 }).catch(() => []);
2177
+ const hit = existing.find((c) => (c.email ?? "").toLowerCase() === email.trim().toLowerCase());
2178
+ if (hit) {
2179
+ setDuplicate(hit);
2180
+ return;
2181
+ }
2182
+ setError("J\xE1 existe um cliente com esse e-mail nesta loja. Procure por ele na busca.");
2183
+ return;
2184
+ }
2185
+ setError(errorMessage(err));
2186
+ } finally {
2187
+ setSaving(false);
2188
+ }
2189
+ }
2190
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (next) => {
2191
+ if (!next) onClose();
2192
+ }, children: /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(ModalContent, { size: "lg", children: [
2193
+ /* @__PURE__ */ jsx(ModalHeader, { className: "mb-4", children: /* @__PURE__ */ jsx(ModalTitle, { children: customerId ? "Cadastro do cliente" : "Novo cliente" }) }),
2194
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-3", children: [
2195
+ /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-3", children: [
2196
+ /* @__PURE__ */ jsx(Field, { label: "Nome", children: /* @__PURE__ */ jsx(
2197
+ "input",
2198
+ {
2199
+ value: first,
2200
+ onChange: (e) => setFirst(e.target.value),
2201
+ autoFocus: true,
2202
+ "data-testid": "new-customer-first",
2203
+ className: fieldClass
2204
+ }
2205
+ ) }),
2206
+ /* @__PURE__ */ jsx(Field, { label: "Sobrenome", children: /* @__PURE__ */ jsx(
2207
+ "input",
2208
+ {
2209
+ value: last,
2210
+ onChange: (e) => setLast(e.target.value),
2211
+ "data-testid": "new-customer-last",
2212
+ className: fieldClass
2213
+ }
2214
+ ) })
2215
+ ] }),
2216
+ /* @__PURE__ */ jsx(Field, { label: "Telefone", children: /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(PhoneField, { value: phone, onChange: setPhone, "data-testid": "new-customer-phone" }) }) }),
2217
+ /* @__PURE__ */ jsx(Field, { label: "E-mail", children: /* @__PURE__ */ jsx(
2218
+ "input",
2219
+ {
2220
+ type: "email",
2221
+ value: email,
2222
+ onChange: (e) => setEmail(e.target.value),
2223
+ "data-testid": "new-customer-email",
2224
+ className: fieldClass
2225
+ }
2226
+ ) }),
2227
+ /* @__PURE__ */ jsx(Field, { label: "CPF / CNPJ", hint: "Necess\xE1rio para cobran\xE7a online e para a nota fiscal", children: /* @__PURE__ */ jsx(
2228
+ "input",
2229
+ {
2230
+ value: formatDocument(document),
2231
+ onChange: (e) => setDocument(documentDigits(e.target.value)),
2232
+ placeholder: "000.000.000-00",
2233
+ "data-testid": "new-customer-document",
2234
+ className: fieldClass
2235
+ }
2236
+ ) }),
2237
+ duplicate && /* @__PURE__ */ jsxs(
2238
+ "div",
2239
+ {
2240
+ role: "alert",
2241
+ "data-testid": "new-customer-duplicate",
2242
+ className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900",
2243
+ children: [
2244
+ /* @__PURE__ */ jsxs("p", { children: [
2245
+ /* @__PURE__ */ jsxs("strong", { children: [
2246
+ duplicate.firstName,
2247
+ " ",
2248
+ duplicate.lastName
2249
+ ] }),
2250
+ " j\xE1 \xE9 cliente com esse e-mail."
2251
+ ] }),
2252
+ /* @__PURE__ */ jsx(
2253
+ "button",
2254
+ {
2255
+ type: "button",
2256
+ onClick: () => onSaved(duplicate),
2257
+ "data-testid": "new-customer-use-existing",
2258
+ className: "mt-1 font-medium underline underline-offset-2",
2259
+ children: "Usar este cliente"
2260
+ }
2261
+ )
2262
+ ]
2263
+ }
2264
+ ),
2265
+ error && /* @__PURE__ */ jsx("p", { role: "alert", className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error })
2266
+ ] }),
2267
+ /* @__PURE__ */ jsxs(ModalFooter, { className: "mt-4 gap-2", children: [
2268
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, children: "Cancelar" }),
2269
+ /* @__PURE__ */ jsx(Button, { onClick: save, disabled: saving, "data-testid": "new-customer-save", children: saving ? "Salvando\u2026" : customerId ? "Salvar" : "Criar cliente" })
2270
+ ] })
2271
+ ] }) }) }) });
2272
+ }
2273
+ function DeliveryAddress({ provider, customerId, address, onChange }) {
2274
+ const [saved, setSaved] = useState([]);
2275
+ const [chosen, setChosen] = useState(null);
2276
+ const [editing, setEditing] = useState(false);
2277
+ const use = useCallback((row) => {
2278
+ setChosen(row.id);
2279
+ setEditing(false);
2280
+ onChange({
2281
+ postalCode: row.postalCode ?? "",
2282
+ street: row.street ?? "",
2283
+ number: row.number ?? "",
2284
+ complement: row.complement ?? "",
2285
+ district: row.district ?? "",
2286
+ city: row.city ?? "",
2287
+ state: row.state ?? ""
2288
+ });
2289
+ }, [onChange]);
2290
+ useEffect(() => {
2291
+ if (!customerId) {
2292
+ setSaved([]);
2293
+ return;
2294
+ }
2295
+ let cancelled = false;
2296
+ provider().listCustomerAddresses?.(customerId).then((rows) => {
2297
+ if (cancelled || !rows?.length) return;
2298
+ setSaved(rows);
2299
+ use(rows[0]);
2300
+ }).catch(() => {
2301
+ });
2302
+ return () => {
2303
+ cancelled = true;
2304
+ };
2305
+ }, [customerId, provider]);
2306
+ const filled = Boolean(address.postalCode && address.street);
2307
+ if (!editing) {
2308
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2309
+ saved.map((row) => /* @__PURE__ */ jsxs(
2310
+ "button",
2311
+ {
2312
+ type: "button",
2313
+ onClick: () => use(row),
2314
+ "data-testid": "new-order-saved-address",
2315
+ className: `block w-full rounded-md border p-2.5 text-left text-sm transition ${chosen === row.id ? "border-primary bg-primary/5" : "hover:bg-muted/50"}`,
2316
+ children: [
2317
+ /* @__PURE__ */ jsx("span", { className: "block", children: [row.street, row.number].filter(Boolean).join(", ") }),
2318
+ /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
2319
+ [row.district, row.city, row.state].filter(Boolean).join(" \xB7 "),
2320
+ row.postalCode ? ` \xB7 ${maskPostal(row.postalCode)}` : ""
2321
+ ] })
2322
+ ]
2323
+ },
2324
+ row.id
2325
+ )),
2326
+ saved.length === 0 && filled && /* @__PURE__ */ jsxs("div", { className: "rounded-md border p-2.5 text-sm", "data-testid": "new-order-address-summary", children: [
2327
+ /* @__PURE__ */ jsx("p", { children: [address.street, address.number].filter(Boolean).join(", ") }),
2328
+ /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
2329
+ [address.district, address.city, address.state].filter(Boolean).join(" \xB7 "),
2330
+ " \xB7 ",
2331
+ maskPostal(address.postalCode)
2332
+ ] })
2333
+ ] }),
2334
+ saved.length === 0 && !filled && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Sem endere\xE7o = retirada." }),
2335
+ /* @__PURE__ */ jsxs(
2336
+ "button",
2337
+ {
2338
+ type: "button",
2339
+ onClick: () => setEditing(true),
2340
+ "data-testid": "new-order-add-address",
2341
+ className: "inline-flex items-center gap-1.5 text-sm text-muted-foreground underline-offset-2 transition hover:text-foreground hover:underline",
2342
+ children: [
2343
+ /* @__PURE__ */ jsx(MapPin, { className: "h-3.5 w-3.5" }),
2344
+ filled ? "Usar outro endere\xE7o" : "Adicionar endere\xE7o"
2345
+ ]
2346
+ }
2347
+ )
2348
+ ] });
2349
+ }
2350
+ return /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-2", children: [
2351
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
2352
+ PostalField,
2353
+ {
2354
+ value: address.postalCode,
2355
+ onChange: (postalCode) => onChange({ ...address, postalCode }),
2356
+ onResolved: (found) => onChange({ ...address, ...found })
2357
+ }
2358
+ ) }),
2359
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Field, { label: "N\xFAmero", children: /* @__PURE__ */ jsx(
2360
+ "input",
2361
+ {
2362
+ value: address.number,
2363
+ onChange: (e) => onChange({ ...address, number: e.target.value }),
2364
+ "data-testid": "new-order-number",
2365
+ className: fieldClass
2366
+ }
2367
+ ) }) }),
2368
+ /* @__PURE__ */ jsx("div", { className: "fz-field-wide", children: /* @__PURE__ */ jsx(Field, { label: "Rua", children: /* @__PURE__ */ jsx(
2369
+ "input",
2370
+ {
2371
+ value: address.street,
2372
+ onChange: (e) => onChange({ ...address, street: e.target.value }),
2373
+ "data-testid": "new-order-street",
2374
+ className: fieldClass
2375
+ }
2376
+ ) }) }),
2377
+ /* @__PURE__ */ jsx("div", { className: "fz-field-wide", children: /* @__PURE__ */ jsx(Field, { label: "Complemento", children: /* @__PURE__ */ jsx(
2378
+ "input",
2379
+ {
2380
+ value: address.complement,
2381
+ onChange: (e) => onChange({ ...address, complement: e.target.value }),
2382
+ placeholder: "Apto, bloco, ponto de refer\xEAncia",
2383
+ className: fieldClass
2384
+ }
2385
+ ) }) }),
2386
+ /* @__PURE__ */ jsx("div", { className: "fz-field-wide", children: /* @__PURE__ */ jsx(Field, { label: "Bairro", children: /* @__PURE__ */ jsx(
2387
+ "input",
2388
+ {
2389
+ value: address.district,
2390
+ onChange: (e) => onChange({ ...address, district: e.target.value }),
2391
+ className: fieldClass
2392
+ }
2393
+ ) }) }),
2394
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Field, { label: "Cidade", children: /* @__PURE__ */ jsx(
2395
+ "input",
2396
+ {
2397
+ value: address.city,
2398
+ onChange: (e) => onChange({ ...address, city: e.target.value }),
2399
+ className: fieldClass
2400
+ }
2401
+ ) }) }),
2402
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Field, { label: "UF", children: /* @__PURE__ */ jsx(
2403
+ "input",
2404
+ {
2405
+ value: address.state,
2406
+ maxLength: 2,
2407
+ onChange: (e) => onChange({ ...address, state: e.target.value.toUpperCase() }),
2408
+ className: fieldClass
2409
+ }
2410
+ ) }) }),
2411
+ /* @__PURE__ */ jsxs("div", { className: "fz-field-wide flex gap-3", children: [
2412
+ /* @__PURE__ */ jsx(
2413
+ "button",
2414
+ {
2415
+ type: "button",
2416
+ onClick: () => setEditing(false),
2417
+ "data-testid": "new-order-address-done",
2418
+ className: "text-sm font-medium text-primary underline-offset-2 hover:underline",
2419
+ children: "Pronto"
2420
+ }
2421
+ ),
2422
+ filled && /* @__PURE__ */ jsx(
2423
+ "button",
2424
+ {
2425
+ type: "button",
2426
+ "data-testid": "new-order-address-clear",
2427
+ onClick: () => {
2428
+ onChange(EMPTY_ADDRESS);
2429
+ setChosen(null);
2430
+ setEditing(false);
2431
+ },
2432
+ className: "text-sm text-muted-foreground underline-offset-2 hover:underline",
2433
+ children: "Remover entrega"
2434
+ }
2435
+ )
2436
+ ] })
2437
+ ] });
2438
+ }
2439
+ function PostalField({ value, onChange, onResolved }) {
2440
+ const [looking, setLooking] = useState(false);
2441
+ const [missing, setMissing] = useState(false);
2442
+ useEffect(() => {
2443
+ const digits = onlyDigits(value);
2444
+ if (digits.length !== 8) {
2445
+ setMissing(false);
2446
+ return;
2447
+ }
2448
+ let cancelled = false;
2449
+ setLooking(true);
2450
+ fetch(`https://viacep.com.br/ws/${digits}/json/`).then((response) => response.json()).then((found) => {
2451
+ if (cancelled) return;
2452
+ if (found?.erro) {
2453
+ setMissing(true);
2454
+ return;
2455
+ }
2456
+ setMissing(false);
2457
+ onResolved({
2458
+ street: found.logradouro || "",
2459
+ district: found.bairro || "",
2460
+ city: found.localidade || "",
2461
+ state: (found.uf || "").toUpperCase()
2462
+ });
2463
+ }).catch(() => {
2464
+ }).finally(() => {
2465
+ if (!cancelled) setLooking(false);
2466
+ });
2467
+ return () => {
2468
+ cancelled = true;
2469
+ };
2470
+ }, [value]);
2471
+ return /* @__PURE__ */ jsx(Field, { label: "CEP", hint: looking ? "Buscando\u2026" : missing ? "CEP n\xE3o encontrado \u2014 preencha \xE0 m\xE3o" : void 0, children: /* @__PURE__ */ jsx(
2472
+ "input",
2473
+ {
2474
+ value: maskPostal(value),
2475
+ onChange: (event) => onChange(event.target.value),
2476
+ placeholder: "00000-000",
2477
+ inputMode: "numeric",
2478
+ "data-testid": "new-order-postal",
2479
+ className: fieldClass
2480
+ }
2481
+ ) });
2482
+ }
2483
+ function currency(value, code = "BRL", locale = "pt-BR") {
2484
+ return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(value);
2485
+ }
2486
+ function badge(label, color) {
2487
+ return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${color}`, children: label });
2488
+ }
2489
+ var statusColors = {
2490
+ active: "bg-emerald-100 text-emerald-800",
2491
+ draft: "bg-gray-100 text-gray-700",
2492
+ archived: "bg-orange-100 text-orange-700",
2493
+ open: "bg-blue-100 text-blue-800",
2494
+ cancelled: "bg-red-100 text-red-700",
2495
+ paid: "bg-emerald-100 text-emerald-800",
2496
+ pending: "bg-yellow-100 text-yellow-800",
2497
+ fulfilled: "bg-emerald-100 text-emerald-800",
2498
+ unfulfilled: "bg-gray-100 text-gray-700",
2499
+ "partially_fulfilled": "bg-blue-100 text-blue-800"
2500
+ };
2501
+ function resolveShopProvider(provider) {
2502
+ return typeof provider === "function" ? provider() : provider ?? getShopProvider();
2503
+ }
2504
+ function useShopList(load, deps) {
2505
+ const [items, setItems] = useState([]);
2506
+ const [loading, setLoading] = useState(true);
2507
+ const [error, setError] = useState(null);
2508
+ const [nonce, setNonce] = useState(0);
2509
+ useEffect(() => {
2510
+ let cancelled = false;
2511
+ setLoading(true);
2512
+ setError(null);
2513
+ load().then((data) => {
2514
+ if (!cancelled) setItems(data);
2515
+ }).catch((err) => {
2516
+ if (!cancelled) setError(errorMessage(err));
2517
+ }).finally(() => {
2518
+ if (!cancelled) setLoading(false);
2519
+ });
2520
+ return () => {
2521
+ cancelled = true;
2522
+ };
2523
+ }, [...deps, nonce]);
2524
+ return { items, loading, error, reload: useCallback(() => setNonce((n) => n + 1), []) };
2525
+ }
2526
+ function ErrorState({ message }) {
2527
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800", children: [
2528
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: "N\xE3o foi poss\xEDvel carregar estes dados." }),
2529
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-red-700", children: message })
2530
+ ] });
2531
+ }
2532
+ function ProductsTab({ provider }) {
2533
+ const [products, setProducts] = useState([]);
2534
+ const [loading, setLoading] = useState(true);
2535
+ const [search, setSearch] = useState("");
2536
+ const [error, setError] = useState(null);
2537
+ const load = useCallback(async (opts) => {
2538
+ setLoading(true);
2539
+ setError(null);
2540
+ try {
2541
+ const data = await resolveShopProvider(provider).listProducts(opts);
2542
+ setProducts(data);
2543
+ } catch (err) {
2544
+ setError(errorMessage(err));
2545
+ } finally {
2546
+ setLoading(false);
2547
+ }
2548
+ }, [provider]);
2549
+ useEffect(() => {
2550
+ load();
2551
+ }, [load]);
2552
+ const handleSearch = (e) => {
2553
+ const q = e.target.value;
2554
+ setSearch(q);
2555
+ load(q ? { search: q } : void 0);
2556
+ };
2557
+ const columns = useMemo(() => [
2558
+ { accessorKey: "name", header: "Produto", cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: getValue() }) },
2559
+ { accessorKey: "sku", header: "SKU", cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: getValue() ?? "\u2014" }) },
2560
+ {
2561
+ accessorKey: "price",
2562
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Pre\xE7o" }),
2563
+ cell: ({ getValue, row }) => /* @__PURE__ */ jsx("div", { className: "text-right", children: currency(getValue(), row.original.currency) })
2564
+ },
2565
+ {
2566
+ accessorKey: "inventoryCount",
2567
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Estoque" }),
2568
+ cell: ({ getValue }) => {
2569
+ const count = getValue();
2570
+ return /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx("span", { className: count === 0 ? "text-red-600 font-medium" : "", children: count }) });
2571
+ }
2572
+ },
2573
+ {
2574
+ accessorKey: "status",
2575
+ header: "Status",
2576
+ cell: ({ getValue }) => {
2577
+ const status = getValue();
2578
+ return badge(status, statusColors[status] ?? "bg-gray-100 text-gray-700");
2579
+ }
2580
+ }
2581
+ ], []);
2582
+ return /* @__PURE__ */ jsxs("div", { children: [
2583
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
2584
+ /* @__PURE__ */ jsx(
2585
+ "input",
2586
+ {
2587
+ type: "search",
2588
+ value: search,
2589
+ onChange: handleSearch,
2590
+ placeholder: "Buscar produtos...",
2591
+ className: "border rounded-lg px-3 py-2 text-sm w-64 focus:outline-none focus:ring-2 focus:ring-primary/50"
2592
+ }
2593
+ ),
2594
+ /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
2595
+ products.length,
2596
+ " produtos"
2597
+ ] })
2598
+ ] }),
2599
+ error ? /* @__PURE__ */ jsx(ErrorState, { message: error }) : /* @__PURE__ */ jsx(
2600
+ DataTable,
2601
+ {
2602
+ loading,
2603
+ columns,
2604
+ data: products,
2605
+ variant: "card",
2606
+ emptyMessage: "Nenhum produto encontrado."
2607
+ }
2608
+ )
2609
+ ] });
2610
+ }
2611
+ function OrdersTab({ provider }) {
2612
+ const { items: orders, loading, error, reload } = useShopList(
2613
+ () => resolveShopProvider(provider).listOrders({ limit: 200 }),
2614
+ [provider]
2615
+ );
2616
+ const [cancelTarget, setCancelTarget] = useState(null);
2617
+ const [bulkBusy, setBulkBusy] = useState(false);
2618
+ const markPaid = useCallback(async (rows) => {
2619
+ const targets = rows.filter((o) => o.financialStatus !== "paid" && o.status !== "cancelled");
2620
+ if (targets.length === 0) {
2621
+ toast.error("Nenhum dos pedidos selecionados est\xE1 aguardando pagamento.");
2622
+ return;
2623
+ }
2624
+ const shop = resolveShopProvider(provider);
2625
+ if (typeof shop.confirmPayment !== "function") {
2626
+ toast.error("Esta loja n\xE3o registra recebimento por aqui.");
2627
+ return;
2628
+ }
2629
+ setBulkBusy(true);
2630
+ let done = 0;
2631
+ let failed = 0;
2632
+ for (const order of targets) {
2633
+ try {
2634
+ await shop.confirmPayment(order.id);
2635
+ done++;
2636
+ } catch (err) {
2637
+ failed++;
2638
+ console.warn("[shop] confirmPayment failed", order.id, err);
2639
+ }
2640
+ }
2641
+ setBulkBusy(false);
2642
+ if (done > 0) toast.success(`${done} pedido(s) marcados como pagos`);
2643
+ if (failed > 0) toast.error(`${failed} pedido(s) n\xE3o puderam ser atualizados`);
2644
+ reload();
2645
+ }, [provider, reload]);
2646
+ const confirmCancel = useCallback(async () => {
2647
+ const targets = (cancelTarget ?? []).filter((o) => o.status !== "cancelled");
2648
+ setBulkBusy(true);
2649
+ let done = 0;
2650
+ let failed = 0;
2651
+ const shop = resolveShopProvider(provider);
2652
+ for (const order of targets) {
2653
+ try {
2654
+ await cancelShopOrder(shop, order.id);
2655
+ done++;
2656
+ } catch (err) {
2657
+ failed++;
2658
+ console.warn("[shop] cancelOrder failed", order.id, err);
2659
+ }
2660
+ }
2661
+ setBulkBusy(false);
2662
+ setCancelTarget(null);
2663
+ if (done > 0) toast.success(`${done} pedido(s) cancelados`);
2664
+ if (failed > 0) toast.error(`${failed} pedido(s) n\xE3o puderam ser cancelados`);
2665
+ reload();
2666
+ }, [cancelTarget, provider, reload]);
2667
+ const bulkActions = useMemo(() => [
2668
+ {
2669
+ id: "mark-paid",
2670
+ label: "Marcar como pago",
2671
+ icon: CreditCard,
2672
+ disabled: bulkBusy,
2673
+ onSelect: markPaid
2674
+ },
2675
+ {
2676
+ id: "cancel",
2677
+ label: "Cancelar pedidos",
2678
+ icon: XCircle,
2679
+ destructive: true,
2680
+ overflow: true,
2681
+ disabled: bulkBusy,
2682
+ onSelect: (rows) => setCancelTarget(rows)
2683
+ }
2684
+ ], [bulkBusy, markPaid]);
2685
+ const columns = useMemo(() => [
2686
+ { key: "orderNumber", header: "Pedido", render: (o) => /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
2687
+ "#",
2688
+ o.orderNumber
2689
+ ] }) },
2690
+ {
2691
+ key: "customer",
2692
+ header: "Cliente",
2693
+ // Links to the customer record. The row itself opens the order, so the
2694
+ // link stops the click from doing both.
2695
+ render: (o) => {
2696
+ const label = o.customerName ?? o.customerEmail ?? "\u2014";
2697
+ if (!o.customerId) return /* @__PURE__ */ jsx("span", { children: label });
2698
+ return /* @__PURE__ */ jsx(
2699
+ PersonLink,
2700
+ {
2701
+ personId: o.customerId,
2702
+ name: label,
2703
+ lookup: shopCustomerLookup(() => resolveShopProvider(provider)),
2704
+ profileHref: `#/customers/${o.customerId}`
2705
+ }
2706
+ );
2707
+ }
2708
+ },
2709
+ { key: "total", header: "Total", align: "right", render: (o) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: currency(o.total, o.currency) }) },
2710
+ {
2711
+ key: "financialStatus",
2712
+ header: "Pagamento",
2713
+ render: (o) => badge(ORDER_STATUS_LABEL[o.financialStatus] ?? o.financialStatus, statusColors[o.financialStatus] ?? "bg-gray-100 text-gray-700")
2714
+ },
2715
+ {
2716
+ key: "fulfillmentStatus",
2717
+ header: "Entrega",
2718
+ render: (o) => {
2719
+ if (o.status && o.status !== "open") {
2720
+ return badge(ORDER_STATUS_LABEL[o.status] ?? o.status, statusColors[o.status] ?? "bg-gray-100 text-gray-700");
2721
+ }
2722
+ const shipments = (o.shipments ?? []).filter((s) => s.status !== "cancelled");
2723
+ if (shipments.length > 0 && shipments.every((s) => s.status === "delivered")) {
2724
+ return badge("Entregue", "bg-emerald-100 text-emerald-800");
2725
+ }
2726
+ return badge(ORDER_STATUS_LABEL[o.fulfillmentStatus] ?? o.fulfillmentStatus, statusColors[o.fulfillmentStatus] ?? "bg-gray-100 text-gray-700");
2727
+ }
2728
+ },
2729
+ {
2730
+ key: "createdAt",
2731
+ header: "Data",
2732
+ render: (o) => {
2733
+ const d = new Date(o.createdAt);
2734
+ return /* @__PURE__ */ jsxs("span", { className: "whitespace-nowrap text-muted-foreground", children: [
2735
+ d.toLocaleDateString("pt-BR"),
2736
+ /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-xs opacity-70", children: d.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" }) })
2737
+ ] });
2738
+ }
2739
+ }
2740
+ ], [provider]);
2741
+ const stats = useMemo(() => {
2742
+ const live = orders.filter((o) => o.status !== "cancelled");
2743
+ const delivered = (o) => {
2744
+ const shipments = (o.shipments ?? []).filter((s) => s.status !== "cancelled");
2745
+ return shipments.length > 0 && shipments.every((s) => s.status === "delivered");
2746
+ };
2747
+ const toShip = live.filter((o) => o.fulfillmentStatus !== "fulfilled" && !delivered(o));
2748
+ const toReceive = live.filter((o) => o.financialStatus === "pending");
2749
+ const billed = live.filter((o) => o.financialStatus === "paid").reduce((sum, o) => sum + o.total, 0);
2750
+ return [
2751
+ { label: "Pedidos", value: String(live.length), icon: ShoppingBag },
2752
+ { label: "A enviar", value: String(toShip.length), icon: Package },
2753
+ { label: "A receber", value: String(toReceive.length), icon: CreditCard },
2754
+ { label: "Recebido", value: currency(billed), icon: Wallet, hint: "Soma dos pedidos pagos nesta lista" }
2755
+ ];
2756
+ }, [orders]);
2757
+ if (error) return /* @__PURE__ */ jsx(ErrorState, { message: error });
2758
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2759
+ /* @__PURE__ */ jsx(
2760
+ OrderListView,
2761
+ {
2762
+ actions: /* @__PURE__ */ jsx(Button, { onClick: () => adminNavigateTo("/orders/new"), "data-testid": "orders-create", children: "+ Criar pedido" }),
2763
+ stats,
2764
+ columns,
2765
+ rows: orders,
2766
+ loading,
2767
+ empty: "Nenhum pedido ainda. Vendeu por telefone? Crie o pedido aqui.",
2768
+ searchText: (o) => [o.orderNumber, o.customerName, o.customerEmail].filter(Boolean).join(" "),
2769
+ searchPlaceholder: "Buscar por n\xFAmero, cliente ou e-mail\u2026",
2770
+ onRowClick: (row) => adminNavigateTo(`/orders/${row.id}`),
2771
+ bulkActions
2772
+ }
2773
+ ),
2774
+ /* @__PURE__ */ jsx(
2775
+ ConfirmDialog,
2776
+ {
2777
+ open: cancelTarget !== null,
2778
+ variant: "destructive",
2779
+ loading: bulkBusy,
2780
+ title: `Cancelar ${cancelTarget?.length ?? 0} pedido(s)?`,
2781
+ description: "O estoque reservado volta para a loja e o recebimento \xE9 encerrado. N\xE3o d\xE1 para desfazer.",
2782
+ confirmLabel: "Cancelar pedidos",
2783
+ cancelLabel: "Voltar",
2784
+ onCancel: () => {
2785
+ if (!bulkBusy) setCancelTarget(null);
2786
+ },
2787
+ onConfirm: confirmCancel
2788
+ }
2789
+ )
2790
+ ] });
2791
+ }
2792
+ function CustomersTab({ provider }) {
2793
+ const { items: customers, loading, error } = useShopList(
2794
+ () => resolveShopProvider(provider).listCustomers({ limit: 50 }),
2795
+ [provider]
2796
+ );
2797
+ const columns = useMemo(() => [
2798
+ {
2799
+ id: "name",
2800
+ accessorFn: (c) => `${c.firstName} ${c.lastName}`,
2801
+ header: "Nome",
2802
+ cell: ({ row }) => /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
2803
+ row.original.firstName,
2804
+ " ",
2805
+ row.original.lastName
2806
+ ] })
2807
+ },
2808
+ { accessorKey: "email", header: "Email", cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: getValue() ?? "\u2014" }) },
2809
+ { accessorKey: "phone", header: "Telefone", cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: getValue() ?? "\u2014" }) },
2810
+ {
2811
+ accessorKey: "ordersCount",
2812
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Pedidos" }),
2813
+ cell: ({ getValue }) => /* @__PURE__ */ jsx("div", { className: "text-right", children: getValue() })
2814
+ },
2815
+ {
2816
+ accessorKey: "totalSpent",
2817
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Total Gasto" }),
2818
+ cell: ({ getValue }) => /* @__PURE__ */ jsx("div", { className: "text-right", children: currency(getValue()) })
2819
+ }
2820
+ ], []);
2821
+ return /* @__PURE__ */ jsx("div", { children: error ? /* @__PURE__ */ jsx(ErrorState, { message: error }) : /* @__PURE__ */ jsx(
2822
+ DataTable,
2823
+ {
2824
+ loading,
2825
+ columns,
2826
+ data: customers,
2827
+ variant: "card",
2828
+ emptyMessage: "Nenhum cliente ainda."
2829
+ }
2830
+ ) });
2831
+ }
2832
+ function DiscountsTab({ provider }) {
2833
+ const { items: discounts, loading, error } = useShopList(
2834
+ () => resolveShopProvider(provider).listDiscounts({ limit: 50 }),
2835
+ [provider]
2836
+ );
2837
+ const columns = useMemo(() => [
2838
+ { accessorKey: "title", header: "T\xEDtulo", cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-medium", children: getValue() }) },
2839
+ { accessorKey: "code", header: "C\xF3digo", cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-mono text-sm", children: getValue() ?? "\u2014" }) },
2840
+ { accessorKey: "type", header: "Tipo", cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: getValue() }) },
2841
+ {
2842
+ accessorKey: "value",
2843
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Valor" }),
2844
+ cell: ({ row }) => /* @__PURE__ */ jsx("div", { className: "text-right", children: row.original.type === "percentage" ? `${row.original.value}%` : currency(row.original.value) })
2845
+ },
2846
+ {
2847
+ id: "usos",
2848
+ accessorKey: "timesUsed",
2849
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: "Usos" }),
2850
+ cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "text-right", children: [
2851
+ row.original.timesUsed,
2852
+ row.original.usageLimit ? ` / ${row.original.usageLimit}` : ""
2853
+ ] })
2854
+ },
2855
+ {
2856
+ accessorKey: "status",
2857
+ header: "Status",
2858
+ cell: ({ getValue }) => {
2859
+ const status = getValue();
2860
+ return badge(status, statusColors[status] ?? "bg-gray-100 text-gray-700");
2861
+ }
2862
+ }
2863
+ ], []);
2864
+ return /* @__PURE__ */ jsx("div", { children: error ? /* @__PURE__ */ jsx(ErrorState, { message: error }) : /* @__PURE__ */ jsx(
2865
+ DataTable,
2866
+ {
2867
+ loading,
2868
+ columns,
2869
+ data: discounts,
2870
+ variant: "card",
2871
+ emptyMessage: "Nenhum desconto cadastrado."
2872
+ }
2873
+ ) });
2874
+ }
2875
+ var TABS = [
2876
+ { id: "products", label: "Produtos" },
2877
+ { id: "orders", label: "Pedidos" },
2878
+ { id: "customers", label: "Clientes" },
2879
+ { id: "discounts", label: "Descontos" }
2880
+ ];
2881
+ var TAB_COPY = {
2882
+ products: { title: "Produtos", subtitle: "Cat\xE1logo da loja: pre\xE7o, estoque e status." },
2883
+ orders: { title: "Pedidos", subtitle: "Pedidos recebidos, pagamento e entrega." },
2884
+ customers: { title: "Clientes", subtitle: "Quem comprou, quanto e com que frequ\xEAncia." },
2885
+ discounts: { title: "Descontos", subtitle: "Cupons, regras de uso e desempenho." }
2886
+ };
2887
+ var ShopPage = ({ provider, section }) => {
2888
+ const [tab, setTab] = useState(section ?? "products");
2889
+ const active = section ?? tab;
2890
+ const path = useAdminPath();
2891
+ const orderId = section === "orders" ? path.match(/^\/orders\/([^/]+)$/)?.[1] : void 0;
2892
+ if (section === "orders" && orderId === "new") {
2893
+ return /* @__PURE__ */ jsx(
2894
+ NewOrderPage,
2895
+ {
2896
+ provider: () => resolveShopProvider(provider),
2897
+ onDone: (id) => adminNavigateTo(id ? `/orders/${id}` : "/orders"),
2898
+ onCancel: () => adminNavigateTo("/orders")
2899
+ }
2900
+ );
2901
+ }
2902
+ if (orderId) {
2903
+ return /* @__PURE__ */ jsx("div", { className: "max-w-7xl", children: /* @__PURE__ */ jsx(
2904
+ OrderDetailView,
2905
+ {
2906
+ orderId,
2907
+ provider: () => resolveShopProvider(provider),
2908
+ onBack: () => adminNavigateTo("/orders")
2909
+ }
2910
+ ) });
2911
+ }
2912
+ const copy = section ? TAB_COPY[section] : { title: "Loja", subtitle: "Gerencie produtos, pedidos, clientes e descontos." };
2913
+ return (
2914
+ // No padding of its own: AdminShell already wraps every page in `p-3 md:p-6`,
2915
+ // and a second p-6 here sat the orders list lower and wider than every CRUD
2916
+ // screen next to it.
2917
+ /* @__PURE__ */ jsxs("div", { children: [
2918
+ section !== "orders" && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
2919
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: copy.title }),
2920
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: copy.subtitle })
2921
+ ] }),
2922
+ !section && /* @__PURE__ */ jsx("div", { className: "border-b mb-6", children: /* @__PURE__ */ jsx("nav", { className: "flex gap-1", children: TABS.map((t) => /* @__PURE__ */ jsx(
2923
+ "button",
2924
+ {
2925
+ onClick: () => setTab(t.id),
2926
+ className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === t.id ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
2927
+ children: t.label
2928
+ },
2929
+ t.id
2930
+ )) }) }),
2931
+ active === "products" && /* @__PURE__ */ jsx(ProductsTab, { provider }),
2932
+ active === "orders" && /* @__PURE__ */ jsx(OrdersTab, { provider }),
2933
+ active === "customers" && /* @__PURE__ */ jsx(CustomersTab, { provider }),
2934
+ active === "discounts" && /* @__PURE__ */ jsx(DiscountsTab, { provider })
2935
+ ] })
2936
+ );
2937
+ };
2938
+ ShopPage.displayName = "ShopPage";
2939
+
2940
+ export { ShopPage };
2941
+ //# sourceMappingURL=ShopPage-ZBJRNMKQ.js.map
2942
+ //# sourceMappingURL=ShopPage-ZBJRNMKQ.js.map