@fayz-ai/plugin-shop 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{ShopPage-6IQ7WKQG.js → ShopPage-5IERN4E5.js} +339 -412
  2. package/dist/ShopPage-5IERN4E5.js.map +1 -0
  3. package/dist/{chunk-NFFJVQCY.js → chunk-RPBKGW24.js} +4 -18
  4. package/dist/chunk-RPBKGW24.js.map +1 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +389 -380
  8. package/dist/index.js.map +1 -1
  9. package/dist/integrations/abacatepay/connectorDef.d.ts +1 -1
  10. package/dist/integrations/abacatepay/sync.d.ts.map +1 -1
  11. package/dist/integrations/appmax/connectorDef.d.ts +1 -1
  12. package/dist/integrations/melhor-envio/connectorDef.d.ts +1 -1
  13. package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -1
  14. package/dist/lib/onboarding.d.ts +1 -1
  15. package/dist/lib/onboarding.d.ts.map +1 -1
  16. package/dist/migrations/index.d.ts +6 -0
  17. package/dist/migrations/index.d.ts.map +1 -0
  18. package/dist/settings/error-message.d.ts +8 -0
  19. package/dist/settings/error-message.d.ts.map +1 -1
  20. package/dist/views/NewOrderPage.d.ts.map +1 -1
  21. package/dist/views/OrderDetailView.d.ts.map +1 -1
  22. package/dist/views/ProductImagesTab.d.ts.map +1 -1
  23. package/dist/views/ProductPositionsTab.d.ts.map +1 -1
  24. package/dist/views/ProductVariantsTab.d.ts.map +1 -1
  25. package/dist/views/ShipOrderForm.d.ts.map +1 -1
  26. package/dist/views/ShopPage.d.ts.map +1 -1
  27. package/dist/views/productEntity.d.ts.map +1 -1
  28. package/dist/views/stock-agent.d.ts.map +1 -1
  29. package/package.json +7 -6
  30. package/dist/ShopPage-6IQ7WKQG.js.map +0 -1
  31. package/dist/chunk-NFFJVQCY.js.map +0 -1
  32. package/dist/views/ChannelsPage.d.ts +0 -7
  33. package/dist/views/ChannelsPage.d.ts.map +0 -1
@@ -1,10 +1,11 @@
1
- import { errorMessage, shopCustomerLookup, listProductsDetailed, invalidateProductLists, buildShopCustomerEntity, buildStockLocationsRegistry, listProductsCached, resolveTerm, candidateMeta } from './chunk-NFFJVQCY.js';
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 { useSaveBar, Thumbnail, Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, formatPhone, toast, DataTable, ConfirmDialog, SearchSelect, ModalPortal, ModalOverlay, ModalBody, Skeleton } from '@fayz-ai/ui';
5
- import { Minus, Plus, Trash2, RotateCcw, QrCode, Pencil, Archive, Ban, CreditCard, XCircle, ShoppingBag, Package, Wallet, WandSparkles, X, MapPin, Search, Link2, HandCoins, Check, Copy, Share2, MessageCircle, CheckCircle2, Printer, RefreshCw, ExternalLink, Truck, AlertTriangle, Loader2 } from 'lucide-react';
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, OrderFormView, OrderDetailSkeleton, PageActions, OrderDetailBlock, OrderDetailRow, PersonLink, OrderDetailView as OrderDetailView$1, OrderListView, askAI, RecordPickerModal, useConnectors, CrudRecordModal, useAgentSurface, duplicateWrite } from '@fayz-ai/admin';
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 { error: rpcError } = await db.rpc("shop_create_fulfillment", {
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
- p_location_id: null,
35
- p_carrier: carrier.trim() || null,
36
- p_tracking: tracking.trim() || null,
37
- p_items: null,
38
- p_notes: null
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((p) => p.id === "financial");
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 [confirming, setConfirming] = useState(false);
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 [receivable, setReceivable] = useState(() => shopReceivableCache.get(orderId) ?? null);
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 registerReceipt() {
1492
+ async function afterReceipt() {
1493
+ setPayingInstallment(null);
1498
1494
  if (!order) return;
1499
- setConfirming(true);
1500
- setConfirmError(null);
1501
- try {
1502
- const fresh = await provider().confirmPayment?.(order.id) ?? await provider().getOrder(order.id);
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
- receivable: receivable ? {
1729
- referenceNumber: receivable.balance.reference_number ?? void 0,
1730
- statusBadge: { label: RECEIVABLE_LABEL[receivable.balance.status] ?? receivable.balance.status, tone: toneOf(receivable.balance.status) },
1731
- rows: [
1732
- { label: "Faturado", value: money2(receivable.balance.amount, cur) },
1733
- { label: "Recebido", value: money2(receivable.balance.paid, cur) },
1734
- { label: "Saldo", value: money2(receivable.balance.balance, cur), strong: true }
1735
- ],
1736
- installments,
1737
- actions: /* @__PURE__ */ jsxs(Fragment, { children: [
1738
- receivable.balance.balance > 0 && receivable.balance.status !== "cancelled" && /* @__PURE__ */ jsx(Button, { onClick: registerReceipt, disabled: confirming, "data-testid": "order-register-receipt", children: confirming ? "Registrando\u2026" : "Registrar recebimento" }),
1739
- hasFinancial && /* @__PURE__ */ jsx(
1740
- "button",
1741
- {
1742
- type: "button",
1743
- onClick: () => adminNavigateTo("/financial"),
1744
- className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
1745
- children: "Abrir no Financeiro"
1746
- }
1747
- ),
1748
- 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 })
1749
- ] }),
1750
- emptyLabel: "Sem conta a receber para este pedido."
1751
- } : { rows: [], installments: [], emptyLabel: "Sem conta a receber para este pedido." },
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
- void registerReceipt();
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
- productId: item.productId ?? "",
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
- function addLine(product, variants) {
2174
- const sellable = variants.filter((v) => v.status !== "archived");
2175
- const only = sellable.length === 1 ? sellable[0] : null;
2176
- setLines((current) => {
2177
- const existing = current.find((line) => line.productId === product.id && !line.variants.length);
2178
- if (existing && sellable.length <= 1) {
2179
- return current.map((line) => line.key === existing.key ? { ...line, quantity: line.quantity + 1 } : line);
2180
- }
2181
- return [...current, {
2182
- key: `${product.id}:${current.length}`,
2183
- productId: product.id,
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
- const setQuantity = (key, quantity) => setLines((current) => current.map((line) => line.key === key ? { ...line, quantity: Math.max(0, quantity) } : line).filter((line) => line.quantity > 0));
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.productId,
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.productId,
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
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2386
- /* @__PURE__ */ jsxs(
2387
- OrderFormView,
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
- title: orderId ? `Editar pedido${orderNumber ? ` #${orderNumber}` : ""}` : "Novo pedido",
2390
- parentLabel: "Pedidos",
2391
- onBack: onCancel,
2392
- subtitle: orderId ? "Enquanto ningu\xE9m pagou, o pedido ainda \xE9 rascunho" : "Venda por telefone, WhatsApp ou balc\xE3o",
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
- /* @__PURE__ */ jsxs(OrderFormBlock, { title: "Produtos", children: [
2396
- /* @__PURE__ */ jsx(ProductPicker, { provider, onPick: addLine, currency: currency2 }),
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.productId,
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
- function ProductPicker({ provider, onPick, currency: currency2 }) {
2648
- const [open, setOpen] = useState(false);
2649
- const [fromCache, setFromCache] = useState(false);
2650
- const search = useCallback(
2651
- // Same cache funnel as the products page: a catalogue the merchant just
2652
- // browsed answers the picker without a round-trip.
2653
- async (term) => {
2654
- const answer = await listProductsDetailed(provider, { search: term || void 0, status: "active", limit: 20 });
2655
- setFromCache(Boolean(answer.cached || answer.stale));
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
- search,
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
- onConfirm: (products) => {
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-6IQ7WKQG.js.map
4036
- //# sourceMappingURL=ShopPage-6IQ7WKQG.js.map
3962
+ //# sourceMappingURL=ShopPage-5IERN4E5.js.map
3963
+ //# sourceMappingURL=ShopPage-5IERN4E5.js.map