@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.
- package/dist/ShopPage-ZBJRNMKQ.js +2942 -0
- package/dist/ShopPage-ZBJRNMKQ.js.map +1 -0
- package/dist/analytics/cards.d.ts +3 -0
- package/dist/analytics/cards.d.ts.map +1 -0
- package/dist/chunk-EW4XJRA2.js +835 -0
- package/dist/chunk-EW4XJRA2.js.map +1 -0
- package/dist/data/registries.d.ts +53 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/data/registry-routes.d.ts +21 -0
- package/dist/data/registry-routes.d.ts.map +1 -0
- package/dist/data/shippingProfileDataProvider.d.ts +19 -0
- package/dist/data/shippingProfileDataProvider.d.ts.map +1 -0
- package/dist/data/stockLocationDataProvider.d.ts +13 -0
- package/dist/data/stockLocationDataProvider.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4446 -504
- package/dist/index.js.map +1 -1
- package/dist/integrations/abacatepay/connectorDef.d.ts +41 -0
- package/dist/integrations/abacatepay/connectorDef.d.ts.map +1 -0
- package/dist/integrations/appmax/connectorDef.d.ts +28 -0
- package/dist/integrations/appmax/connectorDef.d.ts.map +1 -0
- package/dist/integrations/bling/connectorDef.d.ts +10 -0
- package/dist/integrations/bling/connectorDef.d.ts.map +1 -0
- package/dist/integrations/bling/data.d.ts +61 -0
- package/dist/integrations/bling/data.d.ts.map +1 -0
- package/dist/integrations/bling/inbound.d.ts +97 -0
- package/dist/integrations/bling/inbound.d.ts.map +1 -0
- package/dist/integrations/bling/index.d.ts +9 -0
- package/dist/integrations/bling/index.d.ts.map +1 -0
- package/dist/integrations/bling/limits.d.ts +33 -0
- package/dist/integrations/bling/limits.d.ts.map +1 -0
- package/dist/integrations/bling/mapping.d.ts +10 -0
- package/dist/integrations/bling/mapping.d.ts.map +1 -0
- package/dist/integrations/bling/sync.d.ts +70 -0
- package/dist/integrations/bling/sync.d.ts.map +1 -0
- package/dist/integrations/bling/types.d.ts +59 -0
- package/dist/integrations/bling/types.d.ts.map +1 -0
- package/dist/integrations/melhor-envio/connectorDef.d.ts +38 -0
- package/dist/integrations/melhor-envio/connectorDef.d.ts.map +1 -0
- package/dist/integrations/melhor-envio/labelProvider.d.ts +4 -0
- package/dist/integrations/melhor-envio/labelProvider.d.ts.map +1 -0
- package/dist/integrations/stripe-connect/connectorDef.d.ts +17 -0
- package/dist/integrations/stripe-connect/connectorDef.d.ts.map +1 -0
- package/dist/settings/CheckoutSettings.d.ts +17 -0
- package/dist/settings/CheckoutSettings.d.ts.map +1 -0
- package/dist/settings/ChoiceCards.d.ts +23 -0
- package/dist/settings/ChoiceCards.d.ts.map +1 -0
- package/dist/settings/FulfillmentSettings.d.ts +23 -0
- package/dist/settings/FulfillmentSettings.d.ts.map +1 -0
- package/dist/settings/HandlingSettings.d.ts +17 -0
- package/dist/settings/HandlingSettings.d.ts.map +1 -0
- package/dist/settings/PackagesSettings.d.ts +22 -0
- package/dist/settings/PackagesSettings.d.ts.map +1 -0
- package/dist/settings/PaymentProviderRow.d.ts +28 -0
- package/dist/settings/PaymentProviderRow.d.ts.map +1 -0
- package/dist/settings/PaymentsSettings.d.ts +7 -0
- package/dist/settings/PaymentsSettings.d.ts.map +1 -0
- package/dist/settings/SettingsState.d.ts +27 -0
- package/dist/settings/SettingsState.d.ts.map +1 -0
- package/dist/settings/ShippingSettings.d.ts +27 -0
- package/dist/settings/ShippingSettings.d.ts.map +1 -0
- package/dist/settings/StoreGeneralSettings.d.ts +19 -0
- package/dist/settings/StoreGeneralSettings.d.ts.map +1 -0
- package/dist/settings/error-message.d.ts +2 -0
- package/dist/settings/error-message.d.ts.map +1 -0
- package/dist/settings/index.d.ts +12 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/payment-provider.d.ts +13 -0
- package/dist/settings/payment-provider.d.ts.map +1 -0
- package/dist/settings/useStoreSettings.d.ts +30 -0
- package/dist/settings/useStoreSettings.d.ts.map +1 -0
- package/dist/views/AgencySelect.d.ts +17 -0
- package/dist/views/AgencySelect.d.ts.map +1 -0
- package/dist/views/ContentPage.d.ts.map +1 -1
- package/dist/views/FulfillmentProviderSelect.d.ts +14 -0
- package/dist/views/FulfillmentProviderSelect.d.ts.map +1 -0
- package/dist/views/NewOrderCharge.d.ts +17 -0
- package/dist/views/NewOrderCharge.d.ts.map +1 -0
- package/dist/views/NewOrderPage.d.ts +9 -0
- package/dist/views/NewOrderPage.d.ts.map +1 -0
- package/dist/views/OrderDetailView.d.ts +2 -0
- package/dist/views/OrderDetailView.d.ts.map +1 -1
- package/dist/views/OrderPaymentDetail.d.ts +5 -0
- package/dist/views/OrderPaymentDetail.d.ts.map +1 -0
- package/dist/views/OrderProviderLinks.d.ts +5 -0
- package/dist/views/OrderProviderLinks.d.ts.map +1 -0
- package/dist/views/PackageSelect.d.ts +20 -0
- package/dist/views/PackageSelect.d.ts.map +1 -0
- package/dist/views/ProductImagesTab.d.ts.map +1 -1
- package/dist/views/ProductVariantsTab.d.ts +29 -0
- package/dist/views/ProductVariantsTab.d.ts.map +1 -0
- package/dist/views/SenderLocationModal.d.ts +20 -0
- package/dist/views/SenderLocationModal.d.ts.map +1 -0
- package/dist/views/ShipOrderForm.d.ts +10 -0
- package/dist/views/ShipOrderForm.d.ts.map +1 -0
- package/dist/views/ShipWithCarrier.d.ts +18 -0
- package/dist/views/ShipWithCarrier.d.ts.map +1 -0
- package/dist/views/ShipmentActions.d.ts +11 -0
- package/dist/views/ShipmentActions.d.ts.map +1 -0
- package/dist/views/ShippingProfileSelect.d.ts +13 -0
- package/dist/views/ShippingProfileSelect.d.ts.map +1 -0
- package/dist/views/ShopPage.d.ts.map +1 -1
- package/dist/views/customerEntity.d.ts.map +1 -1
- package/dist/views/eta-label.d.ts +6 -0
- package/dist/views/eta-label.d.ts.map +1 -0
- package/dist/views/media-links.d.ts +12 -0
- package/dist/views/media-links.d.ts.map +1 -0
- package/dist/views/new-order-rules.d.ts +55 -0
- package/dist/views/new-order-rules.d.ts.map +1 -0
- package/dist/views/order-actions.d.ts +4 -0
- package/dist/views/order-actions.d.ts.map +1 -0
- package/dist/views/productEntity.d.ts.map +1 -1
- package/dist/views/shippingZoneEntity.d.ts.map +1 -1
- package/package.json +10 -7
- package/dist/ShopPage-PVHFKDF3.js +0 -690
- package/dist/ShopPage-PVHFKDF3.js.map +0 -1
- package/dist/components/ShopSettings.d.ts +0 -15
- 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
|