@fayz-ai/plugin-shop 0.14.1 → 0.15.1
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-6IQ7WKQG.js → ShopPage-5IERN4E5.js} +339 -412
- package/dist/ShopPage-5IERN4E5.js.map +1 -0
- package/dist/{chunk-NFFJVQCY.js → chunk-RPBKGW24.js} +4 -18
- package/dist/chunk-RPBKGW24.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +392 -380
- package/dist/index.js.map +1 -1
- package/dist/integrations/abacatepay/connectorDef.d.ts +1 -1
- package/dist/integrations/abacatepay/sync.d.ts.map +1 -1
- package/dist/integrations/appmax/connectorDef.d.ts +1 -1
- package/dist/integrations/melhor-envio/connectorDef.d.ts +1 -1
- package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -1
- package/dist/lib/onboarding.d.ts +1 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/settings/error-message.d.ts +8 -0
- package/dist/settings/error-message.d.ts.map +1 -1
- package/dist/views/NewOrderPage.d.ts.map +1 -1
- package/dist/views/OrderDetailView.d.ts.map +1 -1
- package/dist/views/ProductImagesTab.d.ts.map +1 -1
- package/dist/views/ProductPositionsTab.d.ts.map +1 -1
- package/dist/views/ProductVariantsTab.d.ts.map +1 -1
- package/dist/views/ShipOrderForm.d.ts.map +1 -1
- package/dist/views/ShopPage.d.ts.map +1 -1
- package/dist/views/productEntity.d.ts.map +1 -1
- package/dist/views/stock-agent.d.ts.map +1 -1
- package/package.json +7 -6
- package/dist/ShopPage-6IQ7WKQG.js.map +0 -1
- package/dist/chunk-NFFJVQCY.js.map +0 -1
- package/dist/views/ChannelsPage.d.ts +0 -7
- package/dist/views/ChannelsPage.d.ts.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { errorMessage,
|
|
1
|
+
import { errorMessage, listProductsDetailed, invalidateProductLists, shopCustomerLookup, buildShopCustomerEntity, buildStockLocationsRegistry, listProductsCached, resolveTerm, candidateMeta } from './chunk-RPBKGW24.js';
|
|
2
2
|
import * as React2 from 'react';
|
|
3
3
|
import React2__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, formatPhone, toast, DataTable, ConfirmDialog, SearchSelect, ModalPortal, ModalOverlay, ModalBody, Skeleton } from '@fayz-ai/ui';
|
|
5
|
+
import { RotateCcw, QrCode, Pencil, Archive, Ban, CreditCard, XCircle, ShoppingBag, Package, Wallet, Plus, WandSparkles, X, MapPin, Link2, HandCoins, Check, Copy, Share2, MessageCircle, CheckCircle2, Printer, RefreshCw, ExternalLink, Truck, AlertTriangle, Loader2 } from 'lucide-react';
|
|
6
6
|
import { CHARGEABLE_PROVIDERS, supportsPaymentLink, getShopProvider, formatDocument, resolveShippingLabelProvider, isValidDocument, documentDigits } from '@fayz-ai/shop';
|
|
7
|
-
import { useAdminPath, adminNavigateTo, OrderFormBlock,
|
|
7
|
+
import { useAdminPath, adminNavigateTo, OrderFormBlock, OrderCreateScreen, useOrderReceivable, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailRow, toOrderDetailReceivable, PersonLink, OrderDetailView as OrderDetailView$1, OrderListView, askAI, useConnectors, CrudRecordModal, useAgentSurface, duplicateWrite } from '@fayz-ai/admin';
|
|
8
|
+
import { ReceivePaymentModal } from '@fayz-ai/plugin-financial';
|
|
8
9
|
import { usePluginRuntimeOptional, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
9
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
10
11
|
import { listStockReasonCodes } from '@fayz-ai/shop/inventory';
|
|
@@ -29,13 +30,24 @@ function ShipOrderForm({ orderId, provider, onShipped }) {
|
|
|
29
30
|
trackingCode: tracking.trim() || void 0
|
|
30
31
|
});
|
|
31
32
|
} else {
|
|
32
|
-
const {
|
|
33
|
+
const { data: pending } = await db.from("v_order_fulfillment").select("item_id, pending").eq("order_id", orderId).gt("pending", 0);
|
|
34
|
+
const lines = (pending ?? []).map((i) => ({
|
|
35
|
+
item_id: i.item_id,
|
|
36
|
+
quantity: i.pending
|
|
37
|
+
}));
|
|
38
|
+
if (lines.length === 0) throw new Error("N\xE3o h\xE1 itens pendentes de envio neste pedido.");
|
|
39
|
+
const { error: rpcError } = await db.rpc("fulfillment_record", {
|
|
33
40
|
p_order_id: orderId,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
p_lines: lines,
|
|
42
|
+
p_kind: "shipment",
|
|
43
|
+
// Enviado, não entregue: quem despacha não sabe se chegou, e
|
|
44
|
+
// `delivered` é o padrão da função porque a maioria dos produtos que
|
|
45
|
+
// a usam entregam na hora. Uma loja não.
|
|
46
|
+
p_status: "in_transit",
|
|
47
|
+
p_metadata: {
|
|
48
|
+
carrier: carrier.trim() || null,
|
|
49
|
+
tracking_code: tracking.trim() || null
|
|
50
|
+
}
|
|
39
51
|
});
|
|
40
52
|
if (rpcError) throw new Error(rpcError.message);
|
|
41
53
|
}
|
|
@@ -1267,14 +1279,6 @@ var ORDER_STATUS_LABEL = {
|
|
|
1267
1279
|
cancelled: "Cancelado",
|
|
1268
1280
|
archived: "Arquivado"
|
|
1269
1281
|
};
|
|
1270
|
-
var RECEIVABLE_LABEL = {
|
|
1271
|
-
open: "Em aberto",
|
|
1272
|
-
pending: "Em aberto",
|
|
1273
|
-
partial: "Parcialmente recebido",
|
|
1274
|
-
paid: "Recebido",
|
|
1275
|
-
overdue: "Vencido",
|
|
1276
|
-
cancelled: "Cancelado"
|
|
1277
|
-
};
|
|
1278
1282
|
var SHIPMENT_LABEL = {
|
|
1279
1283
|
pending: "Aguardando coleta",
|
|
1280
1284
|
in_transit: "Em tr\xE2nsito",
|
|
@@ -1283,6 +1287,17 @@ var SHIPMENT_LABEL = {
|
|
|
1283
1287
|
returned: "Devolvido",
|
|
1284
1288
|
cancelled: "Cancelado"
|
|
1285
1289
|
};
|
|
1290
|
+
function toModalInstallment(bill) {
|
|
1291
|
+
return {
|
|
1292
|
+
id: bill.id,
|
|
1293
|
+
amount: Number(bill.amount ?? 0),
|
|
1294
|
+
paidAmount: Number(bill.paid_amount ?? 0),
|
|
1295
|
+
installmentNumber: bill.installment_number ?? bill.number ?? 1,
|
|
1296
|
+
direction: bill.direction,
|
|
1297
|
+
dueDate: bill.due_date,
|
|
1298
|
+
invoiceId: bill.financial_invoice_id ?? bill.invoice_id
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1286
1301
|
function toneOf(value) {
|
|
1287
1302
|
switch (value) {
|
|
1288
1303
|
case "paid":
|
|
@@ -1353,21 +1368,21 @@ function fromSession(session) {
|
|
|
1353
1368
|
};
|
|
1354
1369
|
}
|
|
1355
1370
|
var shopShipmentsCache = /* @__PURE__ */ new Map();
|
|
1356
|
-
var shopReceivableCache = /* @__PURE__ */ new Map();
|
|
1357
1371
|
function OrderDetailView({ orderId, provider, onBack }) {
|
|
1358
1372
|
const runtime = usePluginRuntimeOptional();
|
|
1359
|
-
const hasFinancial = (runtime?.activePlugins ?? []).some(
|
|
1373
|
+
const hasFinancial = (runtime?.activePlugins ?? []).some(
|
|
1374
|
+
(p) => p.routes?.some((r) => r.path === "/financial")
|
|
1375
|
+
);
|
|
1360
1376
|
const [order, setOrder] = useState(() => shopOrderCache.get(orderId) ?? null);
|
|
1361
1377
|
const [shipments, setShipments] = useState(() => shopShipmentsCache.get(orderId) ?? []);
|
|
1362
1378
|
const [loading, setLoading] = useState(() => !shopOrderCache.has(orderId));
|
|
1363
1379
|
const [error, setError] = useState(null);
|
|
1364
|
-
const [
|
|
1365
|
-
const [confirmError, setConfirmError] = useState(null);
|
|
1380
|
+
const [payingInstallment, setPayingInstallment] = useState(null);
|
|
1366
1381
|
const [pendingStatus, setPendingStatus] = useState(null);
|
|
1367
1382
|
const [statusSaving, setStatusSaving] = useState(false);
|
|
1368
1383
|
const [statusError, setStatusError] = useState(null);
|
|
1369
1384
|
const [cancelReason, setCancelReason] = useState("");
|
|
1370
|
-
const
|
|
1385
|
+
const { receivable, reload: loadReceivable } = useOrderReceivable(orderId);
|
|
1371
1386
|
const [charge, setCharge] = useState(null);
|
|
1372
1387
|
const [chargeOpen, setChargeOpen] = useState(false);
|
|
1373
1388
|
const [charging, setCharging] = useState(null);
|
|
@@ -1435,26 +1450,6 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1435
1450
|
cancelled = true;
|
|
1436
1451
|
};
|
|
1437
1452
|
}, [orderId]);
|
|
1438
|
-
const loadReceivable = useCallback(async (force = false) => {
|
|
1439
|
-
const db = getSupabaseClientOptional();
|
|
1440
|
-
if (!db) return;
|
|
1441
|
-
if (force) shopReceivableCache.delete(orderId);
|
|
1442
|
-
else if (shopReceivableCache.has(orderId)) {
|
|
1443
|
-
setReceivable(shopReceivableCache.get(orderId) ?? null);
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
const [balance, bills] = await Promise.all([
|
|
1447
|
-
db.from("v_invoice_balances").select("*").eq("invoice_id", orderId).maybeSingle(),
|
|
1448
|
-
db.from("plg_financial_movements").select("*").eq("invoice_id", orderId).eq("movement_kind", "bill").order("installment_number", { ascending: true })
|
|
1449
|
-
]);
|
|
1450
|
-
if (!balance?.data) return;
|
|
1451
|
-
const rec = { balance: balance.data, bills: bills?.data ?? [] };
|
|
1452
|
-
shopReceivableCache.set(orderId, rec);
|
|
1453
|
-
setReceivable(rec);
|
|
1454
|
-
}, [orderId]);
|
|
1455
|
-
useEffect(() => {
|
|
1456
|
-
void loadReceivable();
|
|
1457
|
-
}, [loadReceivable]);
|
|
1458
1453
|
useEffect(() => {
|
|
1459
1454
|
let cancelled = false;
|
|
1460
1455
|
const shop = providerRef.current();
|
|
@@ -1494,22 +1489,15 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1494
1489
|
setOrder(fresh);
|
|
1495
1490
|
}
|
|
1496
1491
|
}
|
|
1497
|
-
async function
|
|
1492
|
+
async function afterReceipt() {
|
|
1493
|
+
setPayingInstallment(null);
|
|
1498
1494
|
if (!order) return;
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
if (fresh) {
|
|
1504
|
-
shopOrderCache.set(order.id, fresh);
|
|
1505
|
-
setOrder(fresh);
|
|
1506
|
-
}
|
|
1507
|
-
await loadReceivable(true);
|
|
1508
|
-
} catch (err) {
|
|
1509
|
-
setConfirmError(errorMessage(err));
|
|
1510
|
-
} finally {
|
|
1511
|
-
setConfirming(false);
|
|
1495
|
+
const fresh = await provider().getOrder(order.id);
|
|
1496
|
+
if (fresh) {
|
|
1497
|
+
shopOrderCache.set(order.id, fresh);
|
|
1498
|
+
setOrder(fresh);
|
|
1512
1499
|
}
|
|
1500
|
+
await loadReceivable(true);
|
|
1513
1501
|
}
|
|
1514
1502
|
async function applyStatus(next) {
|
|
1515
1503
|
if (!order) return;
|
|
@@ -1651,13 +1639,6 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1651
1639
|
}
|
|
1652
1640
|
)
|
|
1653
1641
|
);
|
|
1654
|
-
const installments = receivable && receivable.bills.length > 0 ? receivable.bills.map((b) => ({
|
|
1655
|
-
number: b.installment_number ?? 0,
|
|
1656
|
-
amount: `${money2(Number(b.paid_amount ?? 0), cur)} / ${money2(Number(b.amount), cur)}`,
|
|
1657
|
-
status: RECEIVABLE_LABEL[b.status] ?? b.status,
|
|
1658
|
-
statusTone: toneOf(b.status),
|
|
1659
|
-
dueDate: `vence ${dateOnly(b.due_date)}`
|
|
1660
|
-
})) : [];
|
|
1661
1642
|
const data = {
|
|
1662
1643
|
// No topo, onde o layout compartilhado já reserva lugar para uma recusa.
|
|
1663
1644
|
errorBanner: chargeError && !chargeOpen ? /* @__PURE__ */ jsx(
|
|
@@ -1725,30 +1706,38 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1725
1706
|
paymentMethod: order.paymentMethodKind ? PAYMENT_LABEL[order.paymentMethodKind] ?? order.paymentMethodKind : void 0,
|
|
1726
1707
|
paidAt: order.paidAt ? paidAtLabel(order.paidAt) : void 0,
|
|
1727
1708
|
paymentExtra: /* @__PURE__ */ jsx(OrderPaymentDetail, { orderId: order.id }),
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1709
|
+
// Mapped by the shared helper, so the balance rows, the labels and the
|
|
1710
|
+
// instalment shape are the same three lines every product reads. What stays
|
|
1711
|
+
// here is the only part that is the shop's: the verbs, which need a writer.
|
|
1712
|
+
receivable: toOrderDetailReceivable(
|
|
1713
|
+
receivable,
|
|
1714
|
+
(value) => money2(value, cur),
|
|
1715
|
+
{
|
|
1716
|
+
dateOnly: (value) => value ? dateOnly(value) : "",
|
|
1717
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1718
|
+
receivable && receivable.balance.balance > 0 && receivable.balance.status !== "cancelled" && /* @__PURE__ */ jsx(
|
|
1719
|
+
Button,
|
|
1720
|
+
{
|
|
1721
|
+
onClick: () => {
|
|
1722
|
+
const open = receivable?.bills.find((b) => Number(b.paid_amount ?? 0) < Number(b.amount));
|
|
1723
|
+
if (open) setPayingInstallment(toModalInstallment(open));
|
|
1724
|
+
},
|
|
1725
|
+
"data-testid": "order-register-receipt",
|
|
1726
|
+
children: "Registrar recebimento"
|
|
1727
|
+
}
|
|
1728
|
+
),
|
|
1729
|
+
hasFinancial && /* @__PURE__ */ jsx(
|
|
1730
|
+
"button",
|
|
1731
|
+
{
|
|
1732
|
+
type: "button",
|
|
1733
|
+
onClick: () => adminNavigateTo("/financial"),
|
|
1734
|
+
className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
|
|
1735
|
+
children: "Abrir no Financeiro"
|
|
1736
|
+
}
|
|
1737
|
+
)
|
|
1738
|
+
] })
|
|
1739
|
+
}
|
|
1740
|
+
) ?? { rows: [], installments: [], emptyLabel: "Sem conta a receber para este pedido." },
|
|
1752
1741
|
extraSections: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1753
1742
|
/* @__PURE__ */ jsx(OrderDetailBlock, { title: "Entrega", hint: "O que a log\xEDstica precisa", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-sm", children: [
|
|
1754
1743
|
addr ? /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
@@ -1850,7 +1839,8 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1850
1839
|
},
|
|
1851
1840
|
onRegisterReceipt: () => {
|
|
1852
1841
|
setChargeOpen(false);
|
|
1853
|
-
|
|
1842
|
+
const open = receivable?.bills.find((b) => Number(b.paid_amount ?? 0) < Number(b.amount));
|
|
1843
|
+
if (open) setPayingInstallment(toModalInstallment(open));
|
|
1854
1844
|
},
|
|
1855
1845
|
total: charge?.total ?? order.total,
|
|
1856
1846
|
currency: cur,
|
|
@@ -1869,6 +1859,16 @@ function OrderDetailView({ orderId, provider, onBack }) {
|
|
|
1869
1859
|
},
|
|
1870
1860
|
onClose: () => setChargeOpen(false)
|
|
1871
1861
|
}
|
|
1862
|
+
),
|
|
1863
|
+
payingInstallment && /* @__PURE__ */ jsx(
|
|
1864
|
+
ReceivePaymentModal,
|
|
1865
|
+
{
|
|
1866
|
+
installment: payingInstallment,
|
|
1867
|
+
onClose: () => setPayingInstallment(null),
|
|
1868
|
+
onPaid: () => {
|
|
1869
|
+
void afterReceipt();
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
1872
|
)
|
|
1873
1873
|
] });
|
|
1874
1874
|
}
|
|
@@ -2086,7 +2086,7 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2086
2086
|
setOrderNumber(order.orderNumber);
|
|
2087
2087
|
setLines(order.items.map((item, index) => ({
|
|
2088
2088
|
key: `${item.productId ?? item.id}:${index}`,
|
|
2089
|
-
|
|
2089
|
+
itemId: item.productId ?? "",
|
|
2090
2090
|
name: item.name,
|
|
2091
2091
|
sku: item.sku,
|
|
2092
2092
|
imageUrl: item.imageUrl,
|
|
@@ -2170,17 +2170,19 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2170
2170
|
useEffect(() => {
|
|
2171
2171
|
void quoteFreight(postalDigits);
|
|
2172
2172
|
}, [postalDigits, quoteFreight]);
|
|
2173
|
-
|
|
2174
|
-
const
|
|
2175
|
-
const
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2173
|
+
const toLines = useCallback(async (products) => {
|
|
2174
|
+
const built = [];
|
|
2175
|
+
for (const product of products) {
|
|
2176
|
+
let variants = [];
|
|
2177
|
+
try {
|
|
2178
|
+
variants = (await provider().getProductVariants?.(product.id))?.variants ?? [];
|
|
2179
|
+
} catch {
|
|
2180
|
+
}
|
|
2181
|
+
const sellable = variants.filter((v) => v.status !== "archived");
|
|
2182
|
+
const only = sellable.length === 1 ? sellable[0] : null;
|
|
2183
|
+
built.push({
|
|
2184
|
+
key: `${product.id}:${Date.now()}:${built.length}`,
|
|
2185
|
+
itemId: product.id,
|
|
2184
2186
|
name: product.name,
|
|
2185
2187
|
sku: only?.sku ?? product.sku ?? null,
|
|
2186
2188
|
imageUrl: product.images?.[0]?.url ?? null,
|
|
@@ -2190,21 +2192,10 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2190
2192
|
variantId: only?.id ?? null,
|
|
2191
2193
|
optionsLabel: null,
|
|
2192
2194
|
stock: only?.inventoryCount ?? product.inventoryCount ?? null
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
const pickVariant = (key, variantId) => setLines((current) => current.map((line) => {
|
|
2198
|
-
if (line.key !== key) return line;
|
|
2199
|
-
const variant = line.variants.find((v) => v.id === variantId);
|
|
2200
|
-
return variant ? {
|
|
2201
|
-
...line,
|
|
2202
|
-
variantId: variant.id,
|
|
2203
|
-
sku: variant.sku ?? line.sku,
|
|
2204
|
-
unitPrice: variant.price ?? line.unitPrice,
|
|
2205
|
-
optionsLabel: Object.values(variant.options ?? {}).join(" / ") || null
|
|
2206
|
-
} : line;
|
|
2207
|
-
}));
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
return built;
|
|
2198
|
+
}, [provider]);
|
|
2208
2199
|
const shippingAddress = delivers ? {
|
|
2209
2200
|
postalCode: postalDigits,
|
|
2210
2201
|
street: address.street.trim(),
|
|
@@ -2261,7 +2252,7 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2261
2252
|
if (ending === "charge" || ending === "link") {
|
|
2262
2253
|
const opened = await provider().openCheckoutSession?.({
|
|
2263
2254
|
items: lines.map((line) => ({
|
|
2264
|
-
productId: line.
|
|
2255
|
+
productId: line.itemId,
|
|
2265
2256
|
variantId: line.variantId,
|
|
2266
2257
|
quantity: line.quantity,
|
|
2267
2258
|
optionsLabel: line.optionsLabel
|
|
@@ -2297,7 +2288,7 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2297
2288
|
customerId,
|
|
2298
2289
|
customer: { name: customer.name.trim(), email: customer.email.trim().toLowerCase() || void 0 },
|
|
2299
2290
|
items: lines.map((line) => ({
|
|
2300
|
-
productId: line.
|
|
2291
|
+
productId: line.itemId,
|
|
2301
2292
|
variantId: line.variantId ?? void 0,
|
|
2302
2293
|
quantity: line.quantity,
|
|
2303
2294
|
optionsLabel: line.optionsLabel ?? void 0
|
|
@@ -2321,29 +2312,6 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2321
2312
|
setSaving(false);
|
|
2322
2313
|
}
|
|
2323
2314
|
}
|
|
2324
|
-
useSaveBar({
|
|
2325
|
-
dirty: lines.length > 0 && !loading && !session && !settling && !charging,
|
|
2326
|
-
saving,
|
|
2327
|
-
onSave: () => {
|
|
2328
|
-
void finish("later");
|
|
2329
|
-
},
|
|
2330
|
-
onDiscard: () => {
|
|
2331
|
-
if (orderId) {
|
|
2332
|
-
onCancel();
|
|
2333
|
-
return;
|
|
2334
|
-
}
|
|
2335
|
-
setLines([]);
|
|
2336
|
-
setCustomer(EMPTY_CUSTOMER);
|
|
2337
|
-
setAddress(EMPTY_ADDRESS);
|
|
2338
|
-
setNotes("");
|
|
2339
|
-
setDiscountCode("");
|
|
2340
|
-
setCouponOpen(false);
|
|
2341
|
-
setError(null);
|
|
2342
|
-
},
|
|
2343
|
-
message: orderId ? "Altera\xE7\xF5es n\xE3o salvas" : "Pedido n\xE3o salvo",
|
|
2344
|
-
saveLabel: orderId ? "Salvar altera\xE7\xF5es" : "Salvar pedido",
|
|
2345
|
-
discardLabel: orderId ? "Sair sem salvar" : "Descartar"
|
|
2346
|
-
});
|
|
2347
2315
|
const blockedLine = useMemo(() => {
|
|
2348
2316
|
const name = insufficientStockProduct(error);
|
|
2349
2317
|
if (!name) return null;
|
|
@@ -2382,234 +2350,158 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2382
2350
|
}
|
|
2383
2351
|
) })
|
|
2384
2352
|
] });
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2353
|
+
const [fromCache, setFromCache] = useState(false);
|
|
2354
|
+
const searchProducts = useCallback(async (term) => {
|
|
2355
|
+
const answer = await listProductsDetailed(provider, { search: term || void 0, status: "active", limit: 20 });
|
|
2356
|
+
setFromCache(Boolean(answer.cached || answer.stale));
|
|
2357
|
+
return answer.rows;
|
|
2358
|
+
}, [provider]);
|
|
2359
|
+
const totalRows = useMemo(() => [
|
|
2360
|
+
{
|
|
2361
|
+
key: "coupon",
|
|
2362
|
+
label: couponOpen || discountCode ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Cupom" }) : (
|
|
2363
|
+
// Muted "+ add" rows keep the card empty until it is asked for.
|
|
2364
|
+
/* @__PURE__ */ jsx(
|
|
2365
|
+
"button",
|
|
2366
|
+
{
|
|
2367
|
+
type: "button",
|
|
2368
|
+
onClick: () => setCouponOpen(true),
|
|
2369
|
+
"data-testid": "new-order-add-coupon",
|
|
2370
|
+
className: "text-sm text-muted-foreground underline-offset-2 transition hover:text-foreground hover:underline",
|
|
2371
|
+
children: "Adicionar cupom"
|
|
2372
|
+
}
|
|
2373
|
+
)
|
|
2374
|
+
),
|
|
2375
|
+
value: couponOpen || discountCode ? /* @__PURE__ */ jsx(
|
|
2376
|
+
"input",
|
|
2377
|
+
{
|
|
2378
|
+
value: discountCode,
|
|
2379
|
+
onChange: (event) => setDiscountCode(event.target.value.toUpperCase()),
|
|
2380
|
+
placeholder: "C\xD3DIGO",
|
|
2381
|
+
autoFocus: true,
|
|
2382
|
+
"data-testid": "new-order-discount",
|
|
2383
|
+
className: "w-40 rounded-md border bg-background px-2 py-1 text-right text-sm uppercase outline-none focus:border-primary"
|
|
2384
|
+
}
|
|
2385
|
+
) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" })
|
|
2386
|
+
},
|
|
2387
|
+
{
|
|
2388
|
+
key: "shipping",
|
|
2389
|
+
label: delivers ? `Entrega${freight ? ` \xB7 ${freight.label}` : ""}` : "Entrega",
|
|
2390
|
+
muted: !delivers,
|
|
2391
|
+
value: delivers ? money3(shipping, currency2) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "sem entrega" })
|
|
2392
|
+
}
|
|
2393
|
+
], [couponOpen, discountCode, delivers, freight, shipping, currency2]);
|
|
2394
|
+
const errorActions = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2395
|
+
customer.id && error && /cliente/iu.test(error) && /* @__PURE__ */ jsxs(
|
|
2396
|
+
"button",
|
|
2388
2397
|
{
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
sideSections,
|
|
2398
|
+
type: "button",
|
|
2399
|
+
onClick: () => setEditingCustomer(true),
|
|
2400
|
+
"data-testid": "new-order-fix-customer",
|
|
2401
|
+
className: "mt-1 font-medium underline underline-offset-2",
|
|
2394
2402
|
children: [
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
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: [
|
|
2398
|
-
/* @__PURE__ */ jsx(Thumbnail, { src: line.imageUrl, size: "lg" }),
|
|
2399
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-[9rem] flex-1", children: [
|
|
2400
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: line.name }),
|
|
2401
|
-
line.variants.length > 0 ? /* @__PURE__ */ jsxs(
|
|
2402
|
-
"select",
|
|
2403
|
-
{
|
|
2404
|
-
value: line.variantId ?? "",
|
|
2405
|
-
onChange: (event) => pickVariant(line.key, event.target.value),
|
|
2406
|
-
"data-testid": "new-order-variant",
|
|
2407
|
-
className: "mt-1 rounded-md border bg-background px-2 py-1 text-xs outline-none focus:border-primary",
|
|
2408
|
-
children: [
|
|
2409
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Escolha a varia\xE7\xE3o\u2026" }),
|
|
2410
|
-
line.variants.map((variant) => /* @__PURE__ */ jsx("option", { value: variant.id, children: Object.values(variant.options ?? {}).join(" / ") || variant.sku || "Varia\xE7\xE3o" }, variant.id))
|
|
2411
|
-
]
|
|
2412
|
-
}
|
|
2413
|
-
) : (
|
|
2414
|
-
// The line shows its total; from two units on, the unit
|
|
2415
|
-
// price is the number the merchant is asked to repeat.
|
|
2416
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: line.quantity > 1 ? `${line.quantity} \xD7 ${money3(line.unitPrice, currency2)}` : line.sku })
|
|
2417
|
-
)
|
|
2418
|
-
] }),
|
|
2419
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2420
|
-
/* @__PURE__ */ jsx(
|
|
2421
|
-
"button",
|
|
2422
|
-
{
|
|
2423
|
-
type: "button",
|
|
2424
|
-
"aria-label": "Menos um",
|
|
2425
|
-
onClick: () => setQuantity(line.key, line.quantity - 1),
|
|
2426
|
-
className: "fz-tap flex items-center justify-center rounded-md border p-1.5 transition hover:bg-muted",
|
|
2427
|
-
children: /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5" })
|
|
2428
|
-
}
|
|
2429
|
-
),
|
|
2430
|
-
/* @__PURE__ */ jsx(
|
|
2431
|
-
"input",
|
|
2432
|
-
{
|
|
2433
|
-
value: line.quantity,
|
|
2434
|
-
onChange: (event) => setQuantity(line.key, Number(onlyDigits2(event.target.value) || 0)),
|
|
2435
|
-
"aria-label": "Quantidade",
|
|
2436
|
-
"data-testid": "new-order-quantity",
|
|
2437
|
-
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"
|
|
2438
|
-
}
|
|
2439
|
-
),
|
|
2440
|
-
/* @__PURE__ */ jsx(
|
|
2441
|
-
"button",
|
|
2442
|
-
{
|
|
2443
|
-
type: "button",
|
|
2444
|
-
"aria-label": "Mais um",
|
|
2445
|
-
onClick: () => setQuantity(line.key, line.quantity + 1),
|
|
2446
|
-
className: "fz-tap flex items-center justify-center rounded-md border p-1.5 transition hover:bg-muted",
|
|
2447
|
-
children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
|
|
2448
|
-
}
|
|
2449
|
-
)
|
|
2450
|
-
] }),
|
|
2451
|
-
/* @__PURE__ */ jsx("div", { className: "ml-auto w-24 shrink-0 text-right text-sm tabular-nums", children: money3(line.unitPrice * line.quantity, currency2) }),
|
|
2452
|
-
/* @__PURE__ */ jsx(
|
|
2453
|
-
"button",
|
|
2454
|
-
{
|
|
2455
|
-
type: "button",
|
|
2456
|
-
"aria-label": "Remover item",
|
|
2457
|
-
onClick: () => setQuantity(line.key, 0),
|
|
2458
|
-
className: "fz-tap flex items-center justify-center rounded-md p-1.5 text-muted-foreground transition hover:bg-muted hover:text-foreground",
|
|
2459
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
2460
|
-
}
|
|
2461
|
-
)
|
|
2462
|
-
] }, line.key)) })
|
|
2463
|
-
] }),
|
|
2464
|
-
/* @__PURE__ */ jsxs(OrderFormBlock, { title: "Pagamento", children: [
|
|
2465
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5 text-sm", children: [
|
|
2466
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2467
|
-
/* @__PURE__ */ jsx("span", { children: "Subtotal" }),
|
|
2468
|
-
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: money3(subtotal, currency2) })
|
|
2469
|
-
] }),
|
|
2470
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between gap-3", children: couponOpen || discountCode ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2471
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Cupom" }),
|
|
2472
|
-
/* @__PURE__ */ jsx(
|
|
2473
|
-
"input",
|
|
2474
|
-
{
|
|
2475
|
-
value: discountCode,
|
|
2476
|
-
onChange: (event) => setDiscountCode(event.target.value.toUpperCase()),
|
|
2477
|
-
placeholder: "C\xD3DIGO",
|
|
2478
|
-
autoFocus: true,
|
|
2479
|
-
"data-testid": "new-order-discount",
|
|
2480
|
-
className: "w-40 rounded-md border bg-background px-2 py-1 text-right text-sm uppercase outline-none focus:border-primary"
|
|
2481
|
-
}
|
|
2482
|
-
)
|
|
2483
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2484
|
-
/* @__PURE__ */ jsx(
|
|
2485
|
-
"button",
|
|
2486
|
-
{
|
|
2487
|
-
type: "button",
|
|
2488
|
-
onClick: () => setCouponOpen(true),
|
|
2489
|
-
"data-testid": "new-order-add-coupon",
|
|
2490
|
-
className: "text-sm text-muted-foreground underline-offset-2 transition hover:text-foreground hover:underline",
|
|
2491
|
-
children: "Adicionar cupom"
|
|
2492
|
-
}
|
|
2493
|
-
),
|
|
2494
|
-
/* @__PURE__ */ jsx("span", { className: "tabular-nums text-muted-foreground", children: "\u2014" })
|
|
2495
|
-
] }) }),
|
|
2496
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2497
|
-
/* @__PURE__ */ jsx("span", { className: delivers ? "" : "text-muted-foreground", children: delivers ? `Entrega${freight ? ` \xB7 ${freight.label}` : ""}` : "Entrega" }),
|
|
2498
|
-
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: delivers ? money3(shipping, currency2) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "sem entrega" }) })
|
|
2499
|
-
] }),
|
|
2500
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t pt-2 text-base", children: [
|
|
2501
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Total" }),
|
|
2502
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", "data-testid": "new-order-total", children: money3(subtotal + shipping, currency2) })
|
|
2503
|
-
] })
|
|
2504
|
-
] }),
|
|
2505
|
-
settling && /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 mt-3 gap-2 rounded-md border bg-muted/30 p-3", children: [
|
|
2506
|
-
/* @__PURE__ */ jsx(Field, { label: "Como recebeu", children: /* @__PURE__ */ jsx(
|
|
2507
|
-
"select",
|
|
2508
|
-
{
|
|
2509
|
-
value: method,
|
|
2510
|
-
onChange: (event) => setMethod(event.target.value),
|
|
2511
|
-
"data-testid": "new-order-method",
|
|
2512
|
-
className: fieldClass,
|
|
2513
|
-
children: PAYMENT_METHODS.map((option) => /* @__PURE__ */ jsx("option", { value: option.id, children: option.label }, option.id))
|
|
2514
|
-
}
|
|
2515
|
-
) }),
|
|
2516
|
-
/* @__PURE__ */ jsx(Field, { label: "Comprovante", hint: "Id da transa\xE7\xE3o, n\xFAmero do recibo \u2014 fica no pedido", children: /* @__PURE__ */ jsx(
|
|
2517
|
-
"input",
|
|
2518
|
-
{
|
|
2519
|
-
value: reference,
|
|
2520
|
-
onChange: (event) => setReference(event.target.value),
|
|
2521
|
-
placeholder: "Opcional",
|
|
2522
|
-
"data-testid": "new-order-reference",
|
|
2523
|
-
className: fieldClass
|
|
2524
|
-
}
|
|
2525
|
-
) })
|
|
2526
|
-
] }),
|
|
2527
|
-
error && /* @__PURE__ */ jsxs(
|
|
2528
|
-
"div",
|
|
2529
|
-
{
|
|
2530
|
-
role: "alert",
|
|
2531
|
-
"data-testid": "new-order-error",
|
|
2532
|
-
className: "mt-3 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800",
|
|
2533
|
-
children: [
|
|
2534
|
-
/* @__PURE__ */ jsx("p", { children: error }),
|
|
2535
|
-
customer.id && /cliente/iu.test(error) && /* @__PURE__ */ jsxs(
|
|
2536
|
-
"button",
|
|
2537
|
-
{
|
|
2538
|
-
type: "button",
|
|
2539
|
-
onClick: () => setEditingCustomer(true),
|
|
2540
|
-
"data-testid": "new-order-fix-customer",
|
|
2541
|
-
className: "mt-1 font-medium underline underline-offset-2",
|
|
2542
|
-
children: [
|
|
2543
|
-
"Editar cadastro de ",
|
|
2544
|
-
customer.name
|
|
2545
|
-
]
|
|
2546
|
-
}
|
|
2547
|
-
),
|
|
2548
|
-
blockedLine && /* @__PURE__ */ jsxs(
|
|
2549
|
-
"button",
|
|
2550
|
-
{
|
|
2551
|
-
type: "button",
|
|
2552
|
-
onClick: () => setEntryOpen(true),
|
|
2553
|
-
"data-testid": "new-order-stock-entry",
|
|
2554
|
-
className: "mt-1 font-medium underline underline-offset-2",
|
|
2555
|
-
children: [
|
|
2556
|
-
"Dar entrada no estoque de ",
|
|
2557
|
-
blockedLine.name
|
|
2558
|
-
]
|
|
2559
|
-
}
|
|
2560
|
-
)
|
|
2561
|
-
]
|
|
2562
|
-
}
|
|
2563
|
-
),
|
|
2564
|
-
lines.length > 0 && !editing && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center gap-2", children: settling ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2565
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => void finish("received"), disabled: saving, "data-testid": "new-order-confirm-received", children: saving ? "Criando\u2026" : "Confirmar pagamento" }),
|
|
2566
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setSettling(false), children: "Voltar" })
|
|
2567
|
-
] }) : charging ? (
|
|
2568
|
-
/* Pix e link são a MESMA cobrança — muda só por onde ela chega ao
|
|
2569
|
-
cliente. A escolha só aparece para quem tem as duas. */
|
|
2570
|
-
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2571
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => void finish("charge"), disabled: saving, "data-testid": "new-order-charge-pix", children: saving ? "Abrindo\u2026" : "Cobran\xE7a Pix" }),
|
|
2572
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void finish("link"), disabled: saving, "data-testid": "new-order-link", children: saving ? "Gerando\u2026" : "Link de pagamento" }),
|
|
2573
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setCharging(false), children: "Voltar" })
|
|
2574
|
-
] })
|
|
2575
|
-
) : session ? (
|
|
2576
|
-
/* Com cobrança aberta o pedido nasce do pagamento, não daqui:
|
|
2577
|
-
salvar agora criaria um segundo pedido quando o cliente pagar. */
|
|
2578
|
-
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2579
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => setChargeOpen(true), "data-testid": "new-order-charge", children: "Ver cobran\xE7a" }),
|
|
2580
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "A cobran\xE7a est\xE1 aberta \u2014 o pedido entra sozinho quando ela for paga." })
|
|
2581
|
-
] })
|
|
2582
|
-
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2583
|
-
canCharge && /* @__PURE__ */ jsx(
|
|
2584
|
-
Button,
|
|
2585
|
-
{
|
|
2586
|
-
onClick: () => canLink ? setCharging(true) : void finish("charge"),
|
|
2587
|
-
disabled: saving,
|
|
2588
|
-
"data-testid": "new-order-charge",
|
|
2589
|
-
children: saving ? "Abrindo\u2026" : canLink ? "Cobrar" : "Cobrar por Pix"
|
|
2590
|
-
}
|
|
2591
|
-
),
|
|
2592
|
-
/* @__PURE__ */ jsx(
|
|
2593
|
-
Button,
|
|
2594
|
-
{
|
|
2595
|
-
variant: canCharge ? "ghost" : "default",
|
|
2596
|
-
onClick: () => setSettling(true),
|
|
2597
|
-
"data-testid": "new-order-received",
|
|
2598
|
-
children: "J\xE1 recebi"
|
|
2599
|
-
}
|
|
2600
|
-
)
|
|
2601
|
-
] }) })
|
|
2602
|
-
] })
|
|
2403
|
+
"Editar cadastro de ",
|
|
2404
|
+
customer.name
|
|
2603
2405
|
]
|
|
2604
2406
|
}
|
|
2605
2407
|
),
|
|
2408
|
+
blockedLine && /* @__PURE__ */ jsxs(
|
|
2409
|
+
"button",
|
|
2410
|
+
{
|
|
2411
|
+
type: "button",
|
|
2412
|
+
onClick: () => setEntryOpen(true),
|
|
2413
|
+
"data-testid": "new-order-stock-entry",
|
|
2414
|
+
className: "mt-1 font-medium underline underline-offset-2",
|
|
2415
|
+
children: [
|
|
2416
|
+
"Dar entrada no estoque de ",
|
|
2417
|
+
blockedLine.name
|
|
2418
|
+
]
|
|
2419
|
+
}
|
|
2420
|
+
)
|
|
2421
|
+
] });
|
|
2422
|
+
const discard = () => {
|
|
2423
|
+
if (orderId) {
|
|
2424
|
+
onCancel();
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
setLines([]);
|
|
2428
|
+
setCustomer(EMPTY_CUSTOMER);
|
|
2429
|
+
setAddress(EMPTY_ADDRESS);
|
|
2430
|
+
setNotes("");
|
|
2431
|
+
setDiscountCode("");
|
|
2432
|
+
setCouponOpen(false);
|
|
2433
|
+
setError(null);
|
|
2434
|
+
};
|
|
2435
|
+
const endingsNode = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2436
|
+
settling && /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 mt-3 gap-2 rounded-md border bg-muted/30 p-3", children: [
|
|
2437
|
+
/* @__PURE__ */ jsx(Field, { label: "Como recebeu", children: /* @__PURE__ */ jsx(
|
|
2438
|
+
"select",
|
|
2439
|
+
{
|
|
2440
|
+
value: method,
|
|
2441
|
+
onChange: (event) => setMethod(event.target.value),
|
|
2442
|
+
"data-testid": "new-order-method",
|
|
2443
|
+
className: fieldClass,
|
|
2444
|
+
children: PAYMENT_METHODS.map((option) => /* @__PURE__ */ jsx("option", { value: option.id, children: option.label }, option.id))
|
|
2445
|
+
}
|
|
2446
|
+
) }),
|
|
2447
|
+
/* @__PURE__ */ jsx(Field, { label: "Comprovante", hint: "Id da transa\xE7\xE3o, n\xFAmero do recibo \u2014 fica no pedido", children: /* @__PURE__ */ jsx(
|
|
2448
|
+
"input",
|
|
2449
|
+
{
|
|
2450
|
+
value: reference,
|
|
2451
|
+
onChange: (event) => setReference(event.target.value),
|
|
2452
|
+
placeholder: "Opcional",
|
|
2453
|
+
"data-testid": "new-order-reference",
|
|
2454
|
+
className: fieldClass
|
|
2455
|
+
}
|
|
2456
|
+
) })
|
|
2457
|
+
] }),
|
|
2458
|
+
lines.length > 0 && !editing && /* @__PURE__ */ jsx("div", { className: "mt-4 flex flex-wrap items-center gap-2", children: settling ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2459
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void finish("received"), disabled: saving, "data-testid": "new-order-confirm-received", children: saving ? "Criando\u2026" : "Confirmar pagamento" }),
|
|
2460
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setSettling(false), children: "Voltar" })
|
|
2461
|
+
] }) : charging ? (
|
|
2462
|
+
/* Pix e link são a MESMA cobrança — muda só por onde ela chega ao
|
|
2463
|
+
cliente. A escolha só aparece para quem tem as duas. */
|
|
2464
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2465
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void finish("charge"), disabled: saving, "data-testid": "new-order-charge-pix", children: saving ? "Abrindo\u2026" : "Cobran\xE7a Pix" }),
|
|
2466
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => void finish("link"), disabled: saving, "data-testid": "new-order-link", children: saving ? "Gerando\u2026" : "Link de pagamento" }),
|
|
2467
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setCharging(false), children: "Voltar" })
|
|
2468
|
+
] })
|
|
2469
|
+
) : session ? (
|
|
2470
|
+
/* Com cobrança aberta o pedido nasce do pagamento, não daqui:
|
|
2471
|
+
salvar agora criaria um segundo pedido quando o cliente pagar. */
|
|
2472
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2473
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setChargeOpen(true), "data-testid": "new-order-charge", children: "Ver cobran\xE7a" }),
|
|
2474
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "A cobran\xE7a est\xE1 aberta \u2014 o pedido entra sozinho quando ela for paga." })
|
|
2475
|
+
] })
|
|
2476
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2477
|
+
canCharge && /* @__PURE__ */ jsx(
|
|
2478
|
+
Button,
|
|
2479
|
+
{
|
|
2480
|
+
onClick: () => canLink ? setCharging(true) : void finish("charge"),
|
|
2481
|
+
disabled: saving,
|
|
2482
|
+
"data-testid": "new-order-charge",
|
|
2483
|
+
children: saving ? "Abrindo\u2026" : canLink ? "Cobrar" : "Cobrar por Pix"
|
|
2484
|
+
}
|
|
2485
|
+
),
|
|
2486
|
+
/* @__PURE__ */ jsx(
|
|
2487
|
+
Button,
|
|
2488
|
+
{
|
|
2489
|
+
variant: canCharge ? "ghost" : "default",
|
|
2490
|
+
onClick: () => setSettling(true),
|
|
2491
|
+
"data-testid": "new-order-received",
|
|
2492
|
+
children: "J\xE1 recebi"
|
|
2493
|
+
}
|
|
2494
|
+
)
|
|
2495
|
+
] }) })
|
|
2496
|
+
] });
|
|
2497
|
+
const overlays = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2606
2498
|
blockedLine && /* @__PURE__ */ jsx(
|
|
2607
2499
|
StockEntryModal,
|
|
2608
2500
|
{
|
|
2609
2501
|
open: entryOpen,
|
|
2610
2502
|
provider,
|
|
2611
2503
|
variantId: blockedLine.variantId,
|
|
2612
|
-
productId: blockedLine.
|
|
2504
|
+
productId: blockedLine.itemId,
|
|
2613
2505
|
productName: blockedLine.name,
|
|
2614
2506
|
sku: blockedLine.sku,
|
|
2615
2507
|
available: blockedLine.stock,
|
|
@@ -2643,55 +2535,22 @@ function NewOrderPage({ provider, onDone, onCancel, orderId }) {
|
|
|
2643
2535
|
}
|
|
2644
2536
|
)
|
|
2645
2537
|
] });
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
return answer.rows;
|
|
2657
|
-
},
|
|
2658
|
-
[provider]
|
|
2659
|
-
);
|
|
2660
|
-
async function add(products) {
|
|
2661
|
-
for (const product of products) {
|
|
2662
|
-
let variants = [];
|
|
2663
|
-
try {
|
|
2664
|
-
variants = (await provider().getProductVariants?.(product.id))?.variants ?? [];
|
|
2665
|
-
} catch {
|
|
2666
|
-
}
|
|
2667
|
-
onPick(product, variants);
|
|
2668
|
-
}
|
|
2669
|
-
}
|
|
2670
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2671
|
-
/* @__PURE__ */ jsxs(
|
|
2672
|
-
"button",
|
|
2673
|
-
{
|
|
2674
|
-
type: "button",
|
|
2675
|
-
onClick: () => setOpen(true),
|
|
2676
|
-
"data-testid": "new-order-product-search",
|
|
2677
|
-
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",
|
|
2678
|
-
children: [
|
|
2679
|
-
/* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
|
|
2680
|
-
"Buscar produto por nome ou SKU"
|
|
2681
|
-
]
|
|
2682
|
-
}
|
|
2683
|
-
),
|
|
2684
|
-
/* @__PURE__ */ jsx(
|
|
2685
|
-
RecordPickerModal,
|
|
2686
|
-
{
|
|
2687
|
-
open,
|
|
2688
|
-
onClose: () => setOpen(false),
|
|
2538
|
+
return /* @__PURE__ */ jsx(
|
|
2539
|
+
OrderCreateScreen,
|
|
2540
|
+
{
|
|
2541
|
+
title: orderId ? `Editar pedido${orderNumber ? ` #${orderNumber}` : ""}` : "Novo pedido",
|
|
2542
|
+
parentLabel: "Pedidos",
|
|
2543
|
+
onBack: onCancel,
|
|
2544
|
+
subtitle: orderId ? "Enquanto ningu\xE9m pagou, o pedido ainda \xE9 rascunho" : "Venda por telefone, WhatsApp ou balc\xE3o",
|
|
2545
|
+
itemsTitle: "Produtos",
|
|
2546
|
+
itemSearch: {
|
|
2547
|
+
placeholder: "Buscar produto por nome ou SKU",
|
|
2689
2548
|
title: "Selecionar produtos",
|
|
2690
|
-
placeholder: "Buscar produtos",
|
|
2691
|
-
confirmLabel: "Adicionar",
|
|
2692
2549
|
emptyLabel: "Nenhum produto encontrado.",
|
|
2693
2550
|
columns: { product: "Produto", meta: "Estoque", value: "Pre\xE7o" },
|
|
2694
|
-
|
|
2551
|
+
// Same cache funnel as the products page: a catalogue the merchant just
|
|
2552
|
+
// browsed answers the picker without a round-trip.
|
|
2553
|
+
search: searchProducts,
|
|
2695
2554
|
rowKey: (product) => product.id,
|
|
2696
2555
|
renderRow: (product) => ({
|
|
2697
2556
|
imageUrl: product.images?.[0]?.url ?? null,
|
|
@@ -2700,14 +2559,34 @@ function ProductPicker({ provider, onPick, currency: currency2 }) {
|
|
|
2700
2559
|
meta: product.inventoryCount ?? 0,
|
|
2701
2560
|
value: money3(product.price, currency2)
|
|
2702
2561
|
}),
|
|
2703
|
-
|
|
2704
|
-
void add(products);
|
|
2705
|
-
},
|
|
2562
|
+
toLines,
|
|
2706
2563
|
onRefresh: invalidateProductLists,
|
|
2707
2564
|
fromCache
|
|
2565
|
+
},
|
|
2566
|
+
lines,
|
|
2567
|
+
onLinesChange: setLines,
|
|
2568
|
+
formatMoney: (value) => money3(value, currency2),
|
|
2569
|
+
totalsTitle: "Pagamento",
|
|
2570
|
+
totalRows,
|
|
2571
|
+
extraTotal: shipping,
|
|
2572
|
+
sideSections,
|
|
2573
|
+
error,
|
|
2574
|
+
errorActions,
|
|
2575
|
+
endings: endingsNode,
|
|
2576
|
+
overlays,
|
|
2577
|
+
saveBar: {
|
|
2578
|
+
dirty: lines.length > 0 && !loading && !session && !settling && !charging,
|
|
2579
|
+
saving,
|
|
2580
|
+
onSave: () => {
|
|
2581
|
+
void finish("later");
|
|
2582
|
+
},
|
|
2583
|
+
onDiscard: discard,
|
|
2584
|
+
message: orderId ? "Altera\xE7\xF5es n\xE3o salvas" : "Pedido n\xE3o salvo",
|
|
2585
|
+
saveLabel: orderId ? "Salvar altera\xE7\xF5es" : "Salvar pedido",
|
|
2586
|
+
discardLabel: orderId ? "Sair sem salvar" : "Descartar"
|
|
2708
2587
|
}
|
|
2709
|
-
|
|
2710
|
-
|
|
2588
|
+
}
|
|
2589
|
+
);
|
|
2711
2590
|
}
|
|
2712
2591
|
function CustomerPicker({ provider, value, onChange, editing, onEditing }) {
|
|
2713
2592
|
const [creating, setCreating] = useState(null);
|
|
@@ -3807,6 +3686,50 @@ function OrdersTab({ provider }) {
|
|
|
3807
3686
|
{ label: "Recebido", value: currency(billed), icon: Wallet, hint: "Soma dos pedidos pagos nesta lista" }
|
|
3808
3687
|
];
|
|
3809
3688
|
}, [orders]);
|
|
3689
|
+
const board = useMemo(() => {
|
|
3690
|
+
const delivered = (o) => {
|
|
3691
|
+
const shipments = (o.shipments ?? []).filter((s) => s.status !== "cancelled");
|
|
3692
|
+
return shipments.length > 0 && shipments.every((s) => s.status === "delivered");
|
|
3693
|
+
};
|
|
3694
|
+
return {
|
|
3695
|
+
columns: [
|
|
3696
|
+
{ id: "to_pay", label: "A receber", color: "bg-amber-500" },
|
|
3697
|
+
{ id: "to_ship", label: "A enviar", color: "bg-blue-500" },
|
|
3698
|
+
{ id: "shipped", label: "Enviado", color: "bg-cyan-500" },
|
|
3699
|
+
{ id: "delivered", label: "Entregue", color: "bg-emerald-500" },
|
|
3700
|
+
{ id: "cancelled", label: "Cancelado", color: "bg-red-500" }
|
|
3701
|
+
],
|
|
3702
|
+
columnOf: (o) => {
|
|
3703
|
+
if (o.status && o.status !== "open") return o.status === "cancelled" ? "cancelled" : null;
|
|
3704
|
+
if (delivered(o)) return "delivered";
|
|
3705
|
+
if (o.financialStatus === "pending") return "to_pay";
|
|
3706
|
+
if (o.fulfillmentStatus === "fulfilled") return "shipped";
|
|
3707
|
+
return "to_ship";
|
|
3708
|
+
},
|
|
3709
|
+
// No advance handler and no bulk actions on the board: advancing a
|
|
3710
|
+
// storefront order is a shipment or a receipt, both of which are their own
|
|
3711
|
+
// screens. The board is for seeing where the work is.
|
|
3712
|
+
emptyColumn: "Nada aqui",
|
|
3713
|
+
card: (o) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3714
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
3715
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm font-bold", children: [
|
|
3716
|
+
"#",
|
|
3717
|
+
o.orderNumber
|
|
3718
|
+
] }),
|
|
3719
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: new Date(o.createdAt).toLocaleDateString("pt-BR") })
|
|
3720
|
+
] }),
|
|
3721
|
+
o.customerName && /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: o.customerName }),
|
|
3722
|
+
o.customerEmail && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: o.customerEmail }),
|
|
3723
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between border-t pt-2", children: [
|
|
3724
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold", children: currency(o.total, o.currency) }),
|
|
3725
|
+
badge(
|
|
3726
|
+
ORDER_STATUS_LABEL[o.financialStatus] ?? o.financialStatus,
|
|
3727
|
+
statusColors[o.financialStatus] ?? "bg-gray-100 text-gray-700"
|
|
3728
|
+
)
|
|
3729
|
+
] })
|
|
3730
|
+
] })
|
|
3731
|
+
};
|
|
3732
|
+
}, []);
|
|
3810
3733
|
if (error) return /* @__PURE__ */ jsx(ErrorState, { message: error });
|
|
3811
3734
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3812
3735
|
/* @__PURE__ */ jsx(
|
|
@@ -3860,7 +3783,11 @@ function OrdersTab({ provider }) {
|
|
|
3860
3783
|
searchText: (o) => [o.orderNumber, o.customerName, o.customerEmail].filter(Boolean).join(" "),
|
|
3861
3784
|
searchPlaceholder: "Buscar por n\xFAmero, cliente ou e-mail\u2026",
|
|
3862
3785
|
onRowClick: (row) => adminNavigateTo(`/orders/${row.id}`),
|
|
3863
|
-
bulkActions
|
|
3786
|
+
bulkActions,
|
|
3787
|
+
viewKey: "shop-orders",
|
|
3788
|
+
views: ["list", "board"],
|
|
3789
|
+
defaultView: "list",
|
|
3790
|
+
board
|
|
3864
3791
|
}
|
|
3865
3792
|
),
|
|
3866
3793
|
/* @__PURE__ */ jsx(
|
|
@@ -4032,5 +3959,5 @@ var ShopPage = ({ provider, section }) => {
|
|
|
4032
3959
|
ShopPage.displayName = "ShopPage";
|
|
4033
3960
|
|
|
4034
3961
|
export { ShopPage };
|
|
4035
|
-
//# sourceMappingURL=ShopPage-
|
|
4036
|
-
//# sourceMappingURL=ShopPage-
|
|
3962
|
+
//# sourceMappingURL=ShopPage-5IERN4E5.js.map
|
|
3963
|
+
//# sourceMappingURL=ShopPage-5IERN4E5.js.map
|