@fayz-ai/plugin-shop 0.14.0 → 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.
- package/dist/{ShopPage-QFIM2HDU.js → ShopPage-5IERN4E5.js} +339 -412
- package/dist/ShopPage-5IERN4E5.js.map +1 -0
- package/dist/{chunk-2VMTPRGC.js → chunk-RPBKGW24.js} +95 -50
- 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 +1599 -604
- 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 +1 -28
- 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/ShippingSettings.d.ts.map +1 -1
- package/dist/settings/error-message.d.ts +8 -0
- package/dist/settings/error-message.d.ts.map +1 -1
- package/dist/views/DeliveryAgentSurface.d.ts +10 -0
- package/dist/views/DeliveryAgentSurface.d.ts.map +1 -0
- package/dist/views/DeliveryAreaPicker.d.ts +9 -0
- package/dist/views/DeliveryAreaPicker.d.ts.map +1 -0
- package/dist/views/DeliveryRulesDraft.d.ts +9 -0
- package/dist/views/DeliveryRulesDraft.d.ts.map +1 -0
- 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/delivery-agent.d.ts +14 -0
- package/dist/views/delivery-agent.d.ts.map +1 -0
- package/dist/views/delivery-draft.d.ts +58 -0
- package/dist/views/delivery-draft.d.ts.map +1 -0
- package/dist/views/productEntity.d.ts.map +1 -1
- package/dist/views/shippingZoneEntity.d.ts.map +1 -1
- package/dist/views/stock-agent.d.ts.map +1 -1
- package/package.json +7 -6
- package/dist/ShopPage-QFIM2HDU.js.map +0 -1
- package/dist/chunk-2VMTPRGC.js.map +0 -1
- package/dist/views/ChannelsPage.d.ts +0 -7
- package/dist/views/ChannelsPage.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { shopCustomerLookup, buildShopCustomerEntity, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { shopCustomerLookup, buildShopCustomerEntity, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-RPBKGW24.js';
|
|
2
|
+
import * as React13 from 'react';
|
|
3
|
+
import React13__default, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
|
+
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, countByTenant, errorMessage as errorMessage$1, getActiveTenantId, emitDataChanged, resolveDataProvider } from '@fayz-ai/core';
|
|
4
5
|
import { getShopTenantId, setShopTenantResolver, buildShopMessageDefaults, registerShippingLabelProvider, getShopProvider, buildShopDomainEvents, axesFromOptions, planVariantMatrix, validateVariantAxes, hasOnlyDefaultVariant, MAX_VARIANT_AXES, leavesNothingSellable, productImageToMediaFile, formatVariantCombination, applyVariantMatrix } from '@fayz-ai/shop';
|
|
5
|
-
import { fayzConnectorOAuth, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo, useAgentSurface, askAI, AskAIButton, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
|
|
6
|
-
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff,
|
|
7
|
-
import { PageHeaderActions, PageTabs, Card, Button,
|
|
6
|
+
import { fayzConnectorOAuth, describeConnectorAccount, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo, DetailTabHeader, useAgentSurface, askAI, AskAIButton, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
|
|
7
|
+
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, AlertTriangle, X, ExternalLink, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, MapPin, Car, Truck, Info, Check, Sparkles, Ban, ListChecks, Search, Building2 } from 'lucide-react';
|
|
8
|
+
import { PageHeaderActions, PageTabs, Card, Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Thumbnail, PageHeader, toast, ConfirmDialog, Hint, ModalBody, Skeleton } from '@fayz-ai/ui';
|
|
8
9
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
10
|
import { listStockReasonCodes, resolveStockStore, createSupabaseStockStore } from '@fayz-ai/shop/inventory';
|
|
10
11
|
import { normalizeMovementType } from '@fayz-ai/plugin-inventory';
|
|
11
12
|
import { advanceWindow, createRateLimiter, sliceWindow, paginate, reconcileMirror, serializeCursor } from '@fayz-ai/core/connector-runtime';
|
|
12
13
|
|
|
13
|
-
function buildShopOnboarding() {
|
|
14
|
+
function buildShopOnboarding(siteRoute = "/site") {
|
|
14
15
|
return {
|
|
15
16
|
id: "shop",
|
|
16
17
|
title: "Sua loja no ar",
|
|
@@ -86,22 +87,22 @@ function buildShopOnboarding() {
|
|
|
86
87
|
{
|
|
87
88
|
key: "storefront",
|
|
88
89
|
title: "Configure sua loja online",
|
|
89
|
-
description: "O endere\xE7o que voc\xEA manda para o cliente \u2014 e
|
|
90
|
+
description: "O endere\xE7o que voc\xEA manda para o cliente \u2014 e como a vitrine est\xE1 hoje.",
|
|
90
91
|
icon: "Store",
|
|
91
|
-
route:
|
|
92
|
+
route: siteRoute,
|
|
92
93
|
priority: "high",
|
|
93
94
|
// A plataforma chama de "publique seu site"; numa loja é a vitrine, e
|
|
94
95
|
// quem sabe configurá-la é este módulo.
|
|
95
96
|
replaces: "publish",
|
|
96
|
-
agentHint: "Confira
|
|
97
|
-
howTo: '
|
|
97
|
+
agentHint: "Confira como a vitrine est\xE1 e o endere\xE7o dela. Publicar sem frete configurado gera pedido que ningu\xE9m consegue entregar.",
|
|
98
|
+
howTo: 'O construtor do site (o "Editar" no item "Sua loja", na barra lateral). Veja como a vitrine est\xE1, ajuste o que precisar conversando com o assistente e publique. Esse link \xE9 o que vai para a bio do Instagram e para o WhatsApp.',
|
|
98
99
|
celebrate: "Vitrine pronta \u2728",
|
|
99
100
|
script: [
|
|
100
101
|
{ kind: "say", text: "Agora a vitrine \u2014 o endere\xE7o que voc\xEA manda para o cliente." },
|
|
101
|
-
{ kind: "goto", route:
|
|
102
|
+
{ kind: "goto", route: siteRoute },
|
|
102
103
|
{
|
|
103
104
|
kind: "ask",
|
|
104
|
-
text: "Estou
|
|
105
|
+
text: "Estou vendo o site da loja. Confira comigo como ele est\xE1 e me diga o que falta para publicar."
|
|
105
106
|
}
|
|
106
107
|
]
|
|
107
108
|
},
|
|
@@ -174,7 +175,7 @@ function buildShopOnboarding() {
|
|
|
174
175
|
waitForUser: true
|
|
175
176
|
}
|
|
176
177
|
],
|
|
177
|
-
check: async () => await countByTenant("
|
|
178
|
+
check: async () => await countByTenant("v_shop_orders") > 0,
|
|
178
179
|
tour: {
|
|
179
180
|
id: "shop.orders",
|
|
180
181
|
stops: [
|
|
@@ -193,17 +194,17 @@ function buildShopOnboarding() {
|
|
|
193
194
|
|
|
194
195
|
// src/integrations/melhor-envio/labelProvider.ts
|
|
195
196
|
var MELHOR_ENVIO_CARRIER = "melhor-envio";
|
|
196
|
-
function createMelhorEnvioLabelProvider(
|
|
197
|
+
function createMelhorEnvioLabelProvider(resolve3) {
|
|
197
198
|
const unsupported = () => {
|
|
198
199
|
throw new Error("Este painel n\xE3o sabe comprar etiquetas nesta loja.");
|
|
199
200
|
};
|
|
200
201
|
return {
|
|
201
202
|
id: MELHOR_ENVIO_CARRIER,
|
|
202
203
|
label: "Melhor Envio",
|
|
203
|
-
quote: async (orderId) => await
|
|
204
|
-
buy: async (orderId, serviceId) => await
|
|
205
|
-
print: async (orderId) => await
|
|
206
|
-
track: async (orderId) => await
|
|
204
|
+
quote: async (orderId) => await resolve3().quoteShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
|
|
205
|
+
buy: async (orderId, serviceId) => await resolve3().buyShippingLabel?.(orderId, serviceId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
|
|
206
|
+
print: async (orderId) => await resolve3().printShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null,
|
|
207
|
+
track: async (orderId) => await resolve3().trackShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null
|
|
207
208
|
};
|
|
208
209
|
}
|
|
209
210
|
function ProductImagesTab({ item, getProvider }) {
|
|
@@ -255,27 +256,34 @@ function ProductImagesTab({ item, getProvider }) {
|
|
|
255
256
|
}
|
|
256
257
|
const update = getProvider().updateProductImage;
|
|
257
258
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
258
|
-
/* @__PURE__ */
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
multiple: true,
|
|
272
|
-
disabled: busy,
|
|
273
|
-
className: "hidden",
|
|
274
|
-
onChange: (event) => void upload(event.target.files)
|
|
275
|
-
}
|
|
259
|
+
/* @__PURE__ */ jsx(
|
|
260
|
+
DetailTabHeader,
|
|
261
|
+
{
|
|
262
|
+
title: "Fotos do produto",
|
|
263
|
+
description: "A primeira foto \xE9 a capa \u2014 aparece no cat\xE1logo, no carrinho e no pedido. As demais viram o carrossel na p\xE1gina do produto.",
|
|
264
|
+
actions: (
|
|
265
|
+
// The same Button as every other tab. It used to be a <label> hand-
|
|
266
|
+
// painted to look like one, which drifted the moment the real button
|
|
267
|
+
// changed — different height, no icon, no disabled state.
|
|
268
|
+
/* @__PURE__ */ jsxs(Button, { disabled: busy, onClick: () => fileRef.current?.click(), children: [
|
|
269
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
270
|
+
busy ? "Enviando\u2026" : "Adicionar fotos"
|
|
271
|
+
] })
|
|
276
272
|
)
|
|
277
|
-
|
|
278
|
-
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
/* @__PURE__ */ jsx(
|
|
276
|
+
"input",
|
|
277
|
+
{
|
|
278
|
+
ref: fileRef,
|
|
279
|
+
type: "file",
|
|
280
|
+
accept: "image/*",
|
|
281
|
+
multiple: true,
|
|
282
|
+
disabled: busy,
|
|
283
|
+
className: "hidden",
|
|
284
|
+
onChange: (event) => void upload(event.target.files)
|
|
285
|
+
}
|
|
286
|
+
),
|
|
279
287
|
error && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error }),
|
|
280
288
|
images.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground", children: "Nenhuma foto ainda. Produtos com mais de uma foto vendem melhor \u2014 mostre a embalagem, o produto pronto e uma refer\xEAncia de tamanho." }) : /* @__PURE__ */ jsx("ul", { className: "fz-cards gap-3", children: images.map((image, index) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
|
|
281
289
|
/* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted", children: [
|
|
@@ -353,20 +361,21 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
353
361
|
const product = item;
|
|
354
362
|
const shop = getProvider();
|
|
355
363
|
const supported = typeof shop.getProductVariants === "function" && typeof shop.saveProductOptions === "function" && typeof shop.createProductVariant === "function" && typeof shop.updateProductVariant === "function";
|
|
356
|
-
const [axes, setAxes] =
|
|
357
|
-
const [variants, setVariants] =
|
|
358
|
-
const [busy, setBusy] =
|
|
359
|
-
const [
|
|
360
|
-
const [
|
|
361
|
-
const [
|
|
362
|
-
const
|
|
364
|
+
const [axes, setAxes] = React13__default.useState([]);
|
|
365
|
+
const [variants, setVariants] = React13__default.useState([]);
|
|
366
|
+
const [busy, setBusy] = React13__default.useState(false);
|
|
367
|
+
const [adding, setAdding] = React13__default.useState(false);
|
|
368
|
+
const [error, setError] = React13__default.useState(null);
|
|
369
|
+
const [selected, setSelected] = React13__default.useState(/* @__PURE__ */ new Set());
|
|
370
|
+
const [dirty, setDirty] = React13__default.useState(false);
|
|
371
|
+
const reload = React13__default.useCallback(async () => {
|
|
363
372
|
const set = await getProvider().getProductVariants(product.id, { includeArchived: true });
|
|
364
373
|
setAxes(axesFromOptions(set.options));
|
|
365
374
|
setVariants(set.variants);
|
|
366
375
|
setSelected(/* @__PURE__ */ new Set());
|
|
367
376
|
setDirty(false);
|
|
368
377
|
}, [getProvider, product.id]);
|
|
369
|
-
|
|
378
|
+
React13__default.useEffect(() => {
|
|
370
379
|
if (!supported) return;
|
|
371
380
|
void reload().catch((e) => setError(message(e)));
|
|
372
381
|
}, [reload, supported]);
|
|
@@ -382,7 +391,7 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
382
391
|
setBusy(false);
|
|
383
392
|
}
|
|
384
393
|
}
|
|
385
|
-
const plan =
|
|
394
|
+
const plan = React13__default.useMemo(() => planVariantMatrix(axes, variants), [axes, variants]);
|
|
386
395
|
const axisError = validateVariantAxes(axes);
|
|
387
396
|
const onlyDefault = hasOnlyDefaultVariant(variants);
|
|
388
397
|
const rows = plan.rows;
|
|
@@ -396,25 +405,40 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
396
405
|
}
|
|
397
406
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
398
407
|
/* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
399
|
-
/* @__PURE__ */
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
408
|
+
/* @__PURE__ */ jsx(
|
|
409
|
+
DetailTabHeader,
|
|
410
|
+
{
|
|
411
|
+
title: "Op\xE7\xF5es",
|
|
412
|
+
description: "Tamanho, cor, sabor. Cada combina\xE7\xE3o vira uma variante com pre\xE7o, SKU e estoque pr\xF3prios \u2014 \xE9 assim que P e GG deixam de dividir o mesmo contador.",
|
|
413
|
+
actions: (
|
|
414
|
+
// Same Button as the other two tabs. It used to be a bare <button>
|
|
415
|
+
// at text-xs with its own border, which read as a lesser control
|
|
416
|
+
// than "Dar entrada" one tab over, for no reason anybody chose.
|
|
417
|
+
/* @__PURE__ */ jsxs(
|
|
418
|
+
Button,
|
|
419
|
+
{
|
|
420
|
+
disabled: busy || axes.length >= MAX_VARIANT_AXES,
|
|
421
|
+
onClick: () => setAdding(true),
|
|
422
|
+
children: [
|
|
423
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
424
|
+
" Adicionar op\xE7\xE3o"
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
),
|
|
431
|
+
/* @__PURE__ */ jsx(
|
|
432
|
+
NewAxisModal,
|
|
433
|
+
{
|
|
434
|
+
open: adding,
|
|
435
|
+
onClose: () => setAdding(false),
|
|
436
|
+
onCreate: (axis) => {
|
|
437
|
+
patchAxes([...axes, axis]);
|
|
438
|
+
setAdding(false);
|
|
415
439
|
}
|
|
416
|
-
|
|
417
|
-
|
|
440
|
+
}
|
|
441
|
+
),
|
|
418
442
|
axes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "Este produto \xE9 vendido de um jeito s\xF3. Adicione uma op\xE7\xE3o se ele tem tamanhos, cores ou sabores." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: axes.map((axis, index) => /* @__PURE__ */ jsx(
|
|
419
443
|
AxisEditor,
|
|
420
444
|
{
|
|
@@ -483,7 +507,7 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
483
507
|
] });
|
|
484
508
|
}
|
|
485
509
|
function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }) {
|
|
486
|
-
const [
|
|
510
|
+
const [draft2, setDraft] = React13__default.useState("");
|
|
487
511
|
function commit(raw) {
|
|
488
512
|
const values = raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
489
513
|
if (values.length === 0) return;
|
|
@@ -538,16 +562,16 @@ function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }
|
|
|
538
562
|
/* @__PURE__ */ jsx(
|
|
539
563
|
"input",
|
|
540
564
|
{
|
|
541
|
-
value:
|
|
565
|
+
value: draft2,
|
|
542
566
|
disabled,
|
|
543
567
|
onChange: (e) => setDraft(e.target.value),
|
|
544
568
|
onKeyDown: (e) => {
|
|
545
569
|
if (e.key === "Enter") {
|
|
546
570
|
e.preventDefault();
|
|
547
|
-
commit(
|
|
571
|
+
commit(draft2);
|
|
548
572
|
}
|
|
549
573
|
},
|
|
550
|
-
onBlur: () => commit(
|
|
574
|
+
onBlur: () => commit(draft2),
|
|
551
575
|
placeholder: axis.values.length === 0 ? "P, M, G" : "adicionar",
|
|
552
576
|
"aria-label": `Valores da op\xE7\xE3o ${index + 1}`,
|
|
553
577
|
className: "h-7 min-w-28 flex-1 rounded-input border border-dashed border-input bg-transparent px-2 text-xs"
|
|
@@ -567,14 +591,17 @@ function VariantGrid({ rows, axes, product, busy, dirty, selected, onSelect, onS
|
|
|
567
591
|
onSelect(next);
|
|
568
592
|
}
|
|
569
593
|
return /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
570
|
-
/* @__PURE__ */
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
594
|
+
/* @__PURE__ */ jsx(
|
|
595
|
+
DetailTabHeader,
|
|
596
|
+
{
|
|
597
|
+
title: "Variantes",
|
|
598
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
599
|
+
"Pre\xE7o vazio herda o pre\xE7o do produto \u2014 o cat\xE1logo em que todo tamanho custa igual n\xE3o precisa repetir o n\xFAmero ",
|
|
600
|
+
rows.length,
|
|
601
|
+
" vezes. Medida vazia herda a do produto, do mesmo jeito."
|
|
602
|
+
] })
|
|
603
|
+
}
|
|
604
|
+
),
|
|
578
605
|
dirty && /* @__PURE__ */ jsx(Notice, { children: "As linhas abaixo ainda refletem o que est\xE1 salvo. Salve as op\xE7\xF5es para gerar as novas combina\xE7\xF5es." }),
|
|
579
606
|
selected.size > 0 && /* @__PURE__ */ jsx(
|
|
580
607
|
BulkBar,
|
|
@@ -756,9 +783,9 @@ function VariantRow({ row, axes, product, busy, checked, onToggle, onSave, onSto
|
|
|
756
783
|
] });
|
|
757
784
|
}
|
|
758
785
|
function BulkBar({ count, disabled, onApply, onClear }) {
|
|
759
|
-
const [price, setPrice] =
|
|
760
|
-
const [weight, setWeight] =
|
|
761
|
-
const [unit, setUnit] =
|
|
786
|
+
const [price, setPrice] = React13__default.useState("");
|
|
787
|
+
const [weight, setWeight] = React13__default.useState("");
|
|
788
|
+
const [unit, setUnit] = React13__default.useState("");
|
|
762
789
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-lg border bg-muted/30 px-3 py-2 text-xs", children: [
|
|
763
790
|
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
764
791
|
count,
|
|
@@ -837,19 +864,19 @@ function parseNumber(raw) {
|
|
|
837
864
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
838
865
|
}
|
|
839
866
|
function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", integer }) {
|
|
840
|
-
const [
|
|
841
|
-
|
|
867
|
+
const [draft2, setDraft] = React13__default.useState(value == null ? "" : String(value));
|
|
868
|
+
React13__default.useEffect(() => {
|
|
842
869
|
setDraft(value == null ? "" : String(value));
|
|
843
870
|
}, [value]);
|
|
844
871
|
return /* @__PURE__ */ jsx(
|
|
845
872
|
"input",
|
|
846
873
|
{
|
|
847
|
-
value:
|
|
874
|
+
value: draft2,
|
|
848
875
|
disabled,
|
|
849
876
|
placeholder,
|
|
850
877
|
onChange: (e) => setDraft(e.target.value),
|
|
851
878
|
onBlur: () => {
|
|
852
|
-
const parsed = parseNumber(
|
|
879
|
+
const parsed = parseNumber(draft2);
|
|
853
880
|
onCommit(parsed == null ? null : integer ? Math.trunc(parsed) : parsed);
|
|
854
881
|
},
|
|
855
882
|
className: `h-7 ${width} rounded-input border border-input bg-background px-2 text-xs tabular-nums`
|
|
@@ -857,18 +884,18 @@ function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", in
|
|
|
857
884
|
);
|
|
858
885
|
}
|
|
859
886
|
function TextCell({ value, disabled, placeholder, onCommit }) {
|
|
860
|
-
const [
|
|
861
|
-
|
|
887
|
+
const [draft2, setDraft] = React13__default.useState(value);
|
|
888
|
+
React13__default.useEffect(() => {
|
|
862
889
|
setDraft(value);
|
|
863
890
|
}, [value]);
|
|
864
891
|
return /* @__PURE__ */ jsx(
|
|
865
892
|
"input",
|
|
866
893
|
{
|
|
867
|
-
value:
|
|
894
|
+
value: draft2,
|
|
868
895
|
disabled,
|
|
869
896
|
placeholder,
|
|
870
897
|
onChange: (e) => setDraft(e.target.value),
|
|
871
|
-
onBlur: () => onCommit(
|
|
898
|
+
onBlur: () => onCommit(draft2.trim()),
|
|
872
899
|
className: "h-7 w-28 rounded-input border border-input bg-background px-2 text-xs"
|
|
873
900
|
}
|
|
874
901
|
);
|
|
@@ -903,7 +930,96 @@ function describePlan(plan) {
|
|
|
903
930
|
return `${parts.join(", ")}. Combina\xE7\xF5es removidas s\xE3o arquivadas, nunca exclu\xEDdas \u2014 o estoque delas continua no hist\xF3rico.`;
|
|
904
931
|
}
|
|
905
932
|
function message(error) {
|
|
906
|
-
return
|
|
933
|
+
return errorMessage$1(error);
|
|
934
|
+
}
|
|
935
|
+
function NewAxisModal({ open, onClose, onCreate }) {
|
|
936
|
+
const [name, setName] = React13__default.useState("");
|
|
937
|
+
const [values, setValues] = React13__default.useState([""]);
|
|
938
|
+
React13__default.useEffect(() => {
|
|
939
|
+
if (open) {
|
|
940
|
+
setName("");
|
|
941
|
+
setValues([""]);
|
|
942
|
+
}
|
|
943
|
+
}, [open]);
|
|
944
|
+
function setValue(index, next) {
|
|
945
|
+
setValues((prev) => {
|
|
946
|
+
const out = [...prev];
|
|
947
|
+
out[index] = next;
|
|
948
|
+
if (index === out.length - 1 && next.trim()) out.push("");
|
|
949
|
+
return out;
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
function removeValue(index) {
|
|
953
|
+
setValues((prev) => {
|
|
954
|
+
const out = prev.filter((_, i) => i !== index);
|
|
955
|
+
return out.length === 0 || out[out.length - 1].trim() ? [...out, ""] : out;
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
const filled = values.map((v) => v.trim()).filter(Boolean);
|
|
959
|
+
const unique = [...new Set(filled)];
|
|
960
|
+
const hasDuplicate = unique.length !== filled.length;
|
|
961
|
+
const ready = name.trim().length > 0 && unique.length > 0 && !hasDuplicate;
|
|
962
|
+
return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
|
|
963
|
+
if (!o) onClose();
|
|
964
|
+
}, children: /* @__PURE__ */ jsxs(ModalContent, { className: "sm:max-w-md", children: [
|
|
965
|
+
/* @__PURE__ */ jsxs(ModalHeader, { children: [
|
|
966
|
+
/* @__PURE__ */ jsx(ModalTitle, { children: "Nova op\xE7\xE3o" }),
|
|
967
|
+
/* @__PURE__ */ jsx(ModalDescription, { children: "Cada combina\xE7\xE3o de op\xE7\xF5es vira uma variante com pre\xE7o, SKU e estoque pr\xF3prios." })
|
|
968
|
+
] }),
|
|
969
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 py-2", children: [
|
|
970
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
971
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Nome" }),
|
|
972
|
+
/* @__PURE__ */ jsx(
|
|
973
|
+
"input",
|
|
974
|
+
{
|
|
975
|
+
autoFocus: true,
|
|
976
|
+
value: name,
|
|
977
|
+
onChange: (e) => setName(e.target.value),
|
|
978
|
+
placeholder: "Tamanho",
|
|
979
|
+
className: "mt-1 h-9 w-full rounded-md border bg-background px-2.5 text-sm outline-none focus:border-primary"
|
|
980
|
+
}
|
|
981
|
+
)
|
|
982
|
+
] }),
|
|
983
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
984
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Valores" }),
|
|
985
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-1 space-y-1.5", children: values.map((value, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1.5", children: [
|
|
986
|
+
/* @__PURE__ */ jsx(
|
|
987
|
+
"input",
|
|
988
|
+
{
|
|
989
|
+
value,
|
|
990
|
+
onChange: (e) => setValue(index, e.target.value),
|
|
991
|
+
onKeyDown: (e) => {
|
|
992
|
+
if (e.key === "Enter") {
|
|
993
|
+
e.preventDefault();
|
|
994
|
+
if (index === values.length - 1 && value.trim()) setValue(index, value);
|
|
995
|
+
const rows = e.currentTarget.closest("ul")?.querySelectorAll("input");
|
|
996
|
+
window.setTimeout(() => rows?.[index + 1]?.focus(), 0);
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
placeholder: index === values.length - 1 ? "Adicionar valor" : "",
|
|
1000
|
+
"aria-label": `Valor ${index + 1}`,
|
|
1001
|
+
className: "h-9 w-full rounded-md border bg-background px-2.5 text-sm outline-none focus:border-primary"
|
|
1002
|
+
}
|
|
1003
|
+
),
|
|
1004
|
+
index < values.length - 1 && /* @__PURE__ */ jsx(
|
|
1005
|
+
"button",
|
|
1006
|
+
{
|
|
1007
|
+
type: "button",
|
|
1008
|
+
onClick: () => removeValue(index),
|
|
1009
|
+
"aria-label": `Remover valor ${index + 1}`,
|
|
1010
|
+
className: "fz-tap shrink-0 rounded-md p-1.5 text-muted-foreground transition hover:bg-muted hover:text-destructive",
|
|
1011
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
1012
|
+
}
|
|
1013
|
+
)
|
|
1014
|
+
] }, index)) }),
|
|
1015
|
+
hasDuplicate && /* @__PURE__ */ jsx("span", { className: "mt-1 block text-[11px] text-destructive", children: "H\xE1 valores repetidos \u2014 cada um vira uma variante, ent\xE3o dois iguais viram uma s\xF3." })
|
|
1016
|
+
] })
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ jsxs(ModalFooter, { children: [
|
|
1019
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onClose, children: "Cancelar" }),
|
|
1020
|
+
/* @__PURE__ */ jsx(Button, { disabled: !ready, onClick: () => onCreate({ name: name.trim(), values: unique }), children: "Adicionar" })
|
|
1021
|
+
] })
|
|
1022
|
+
] }) });
|
|
907
1023
|
}
|
|
908
1024
|
var KIND_OF = {
|
|
909
1025
|
// `sign: 0` = o razão aceita os dois lados (ajuste corrige para cima e para baixo).
|
|
@@ -1017,8 +1133,14 @@ async function commitMovement(args) {
|
|
|
1017
1133
|
// a Lista, e é a que o usuário estava vendo
|
|
1018
1134
|
"plg_shop_product_variants",
|
|
1019
1135
|
// a matriz de variantes
|
|
1020
|
-
|
|
1136
|
+
// O saldo mudou de livro: quem move o número agora é o razão do
|
|
1137
|
+
// plugin-inventory, e o saldo compartilhado é `public.stock_balances`.
|
|
1138
|
+
// Invalidar a tabela antiga deixava as Posições mostrando o valor anterior
|
|
1139
|
+
// até alguém recarregar — o cache apontando para onde ninguém mais escreve.
|
|
1140
|
+
"stock_balances",
|
|
1021
1141
|
// as Posições
|
|
1142
|
+
"plg_inventory_stock_movements",
|
|
1143
|
+
// o histórico de movimentos
|
|
1022
1144
|
"products"
|
|
1023
1145
|
// o arquétipo, que as telas de Estoque leem
|
|
1024
1146
|
]) {
|
|
@@ -1188,7 +1310,7 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1188
1310
|
const [noteOpen, setNoteOpen] = useState(false);
|
|
1189
1311
|
const [occurredOn, setOccurredOn] = useState(() => todayISO());
|
|
1190
1312
|
const [saving, setSaving] = useState(false);
|
|
1191
|
-
const quantityRef =
|
|
1313
|
+
const quantityRef = React13__default.useRef(null);
|
|
1192
1314
|
const load = useCallback(async () => {
|
|
1193
1315
|
setLoading(true);
|
|
1194
1316
|
try {
|
|
@@ -1267,7 +1389,7 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1267
1389
|
const amount = Math.abs(Number(quantity) || 0);
|
|
1268
1390
|
const outgoing = type === "saida" || type === "perda";
|
|
1269
1391
|
const nextBalance = amount > 0 ? outgoing ? total.available - amount : total.available + amount : null;
|
|
1270
|
-
const live =
|
|
1392
|
+
const live = React13__default.useRef({ product, variantId, total, getProvider, load });
|
|
1271
1393
|
live.current = { product, variantId, total, getProvider, load };
|
|
1272
1394
|
const contribution = useMemo(() => ({
|
|
1273
1395
|
id: "shop.product.positions",
|
|
@@ -1348,10 +1470,11 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1348
1470
|
return /* @__PURE__ */ jsx("p", { className: missing ? "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground" : "rounded-lg border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive", children: error });
|
|
1349
1471
|
}
|
|
1350
1472
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1351
|
-
/* @__PURE__ */
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1473
|
+
/* @__PURE__ */ jsx(
|
|
1474
|
+
DetailTabHeader,
|
|
1475
|
+
{
|
|
1476
|
+
title: "Estoque do produto",
|
|
1477
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1355
1478
|
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.onHand }),
|
|
1356
1479
|
" em m\xE3os \xB7",
|
|
1357
1480
|
" ",
|
|
@@ -1360,31 +1483,31 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1360
1483
|
" ",
|
|
1361
1484
|
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available }),
|
|
1362
1485
|
" dispon\xEDvel. O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio."
|
|
1486
|
+
] }),
|
|
1487
|
+
actions: variantId && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1488
|
+
/* @__PURE__ */ jsxs(
|
|
1489
|
+
Button,
|
|
1490
|
+
{
|
|
1491
|
+
variant: "ghost",
|
|
1492
|
+
size: "sm",
|
|
1493
|
+
onClick: () => askAI({
|
|
1494
|
+
intro: STOCK_INTRO,
|
|
1495
|
+
hint: "O que entrou ou saiu\u2026",
|
|
1496
|
+
draft: `Entrada de unidades de ${product.name}`
|
|
1497
|
+
}),
|
|
1498
|
+
children: [
|
|
1499
|
+
/* @__PURE__ */ jsx(WandSparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1500
|
+
"Com IA"
|
|
1501
|
+
]
|
|
1502
|
+
}
|
|
1503
|
+
),
|
|
1504
|
+
/* @__PURE__ */ jsxs(Button, { onClick: () => open("entrada"), children: [
|
|
1505
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
1506
|
+
"Dar entrada"
|
|
1507
|
+
] })
|
|
1363
1508
|
] })
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
/* @__PURE__ */ jsxs(
|
|
1367
|
-
Button,
|
|
1368
|
-
{
|
|
1369
|
-
variant: "ghost",
|
|
1370
|
-
size: "sm",
|
|
1371
|
-
onClick: () => askAI({
|
|
1372
|
-
intro: STOCK_INTRO,
|
|
1373
|
-
hint: "O que entrou ou saiu\u2026",
|
|
1374
|
-
draft: `Entrada de unidades de ${product.name}`
|
|
1375
|
-
}),
|
|
1376
|
-
children: [
|
|
1377
|
-
/* @__PURE__ */ jsx(WandSparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1378
|
-
"Com IA"
|
|
1379
|
-
]
|
|
1380
|
-
}
|
|
1381
|
-
),
|
|
1382
|
-
/* @__PURE__ */ jsxs(Button, { onClick: () => open("entrada"), children: [
|
|
1383
|
-
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
1384
|
-
"Dar entrada"
|
|
1385
|
-
] })
|
|
1386
|
-
] })
|
|
1387
|
-
] }),
|
|
1509
|
+
}
|
|
1510
|
+
),
|
|
1388
1511
|
!variantId && // Nunca desabilitar um botão sem dizer por quê. Sem variante o razão não
|
|
1389
1512
|
// tem onde postar, e um "Lançar" apagado sem explicação faz o lojista
|
|
1390
1513
|
// clicar três vezes e concluir que a tela está quebrada.
|
|
@@ -1397,103 +1520,110 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1397
1520
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Variante" }),
|
|
1398
1521
|
/* @__PURE__ */ jsx("select", { value: variantId, onChange: (e) => setVariantId(e.target.value), className: field, children: variants.map((v) => /* @__PURE__ */ jsx("option", { value: v.id, children: Object.values(v.options ?? {}).join(" \xB7 ") || v.sku || v.id.slice(0, 8) }, v.id)) })
|
|
1399
1522
|
] }),
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1523
|
+
/* @__PURE__ */ jsx(Modal, { open: type !== null, onOpenChange: (o) => {
|
|
1524
|
+
if (!o) close();
|
|
1525
|
+
}, children: /* @__PURE__ */ jsxs(ModalContent, { className: "sm:max-w-lg", children: [
|
|
1526
|
+
/* @__PURE__ */ jsxs(ModalHeader, { children: [
|
|
1527
|
+
/* @__PURE__ */ jsx(ModalTitle, { children: "Lan\xE7ar movimento" }),
|
|
1528
|
+
/* @__PURE__ */ jsx(ModalDescription, { children: "O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio." })
|
|
1529
|
+
] }),
|
|
1530
|
+
/* @__PURE__ */ jsxs("div", { className: "p-1", children: [
|
|
1531
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: MOVEMENT_TYPES.map((t) => /* @__PURE__ */ jsx(
|
|
1532
|
+
"button",
|
|
1533
|
+
{
|
|
1534
|
+
type: "button",
|
|
1535
|
+
onClick: () => setType(t.value),
|
|
1536
|
+
className: `fz-tap rounded-button px-3 py-1.5 text-xs font-medium transition-colors ${type === t.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
1537
|
+
children: t.label
|
|
1538
|
+
},
|
|
1539
|
+
t.value
|
|
1540
|
+
)) }),
|
|
1541
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3", children: [
|
|
1542
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1543
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Quantidade" }),
|
|
1544
|
+
/* @__PURE__ */ jsx(
|
|
1545
|
+
"input",
|
|
1546
|
+
{
|
|
1547
|
+
ref: quantityRef,
|
|
1548
|
+
type: "number",
|
|
1549
|
+
inputMode: "numeric",
|
|
1550
|
+
min: 1,
|
|
1551
|
+
value: quantity,
|
|
1552
|
+
onChange: (e) => setQuantity(e.target.value),
|
|
1553
|
+
onKeyDown: (e) => {
|
|
1554
|
+
if (e.key === "Enter") {
|
|
1555
|
+
e.preventDefault();
|
|
1556
|
+
void submit();
|
|
1557
|
+
}
|
|
1558
|
+
if (e.key === "Escape") {
|
|
1559
|
+
e.preventDefault();
|
|
1560
|
+
close();
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
placeholder: "0",
|
|
1564
|
+
className: `${field} font-semibold tabular-nums`
|
|
1565
|
+
}
|
|
1566
|
+
),
|
|
1567
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "mt-1 block text-xs text-muted-foreground", children: nextBalance === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1568
|
+
"Fica em ",
|
|
1569
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available })
|
|
1570
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1571
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.available }),
|
|
1572
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1.5", children: "\u2192" }),
|
|
1573
|
+
/* @__PURE__ */ jsx("span", { className: `text-base font-semibold tabular-nums ${nextBalance < 0 ? "text-destructive" : "text-foreground"}`, children: nextBalance }),
|
|
1574
|
+
nextBalance < 0 && /* @__PURE__ */ jsx("span", { className: "ml-2 text-destructive", children: "n\xE3o h\xE1 tudo isso" })
|
|
1575
|
+
] }) })
|
|
1576
|
+
] }),
|
|
1577
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1578
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Motivo" }),
|
|
1579
|
+
/* @__PURE__ */ jsxs("select", { value: reasonCode, onChange: (e) => setReasonCode(e.target.value), className: field, children: [
|
|
1580
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Padr\xE3o" }),
|
|
1581
|
+
reasons.map((r) => /* @__PURE__ */ jsx("option", { value: r.key, children: r.label }, r.key))
|
|
1582
|
+
] })
|
|
1583
|
+
] }),
|
|
1584
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1585
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: type === "entrada" ? "Chegou em" : "Quando" }),
|
|
1586
|
+
/* @__PURE__ */ jsx(
|
|
1587
|
+
"input",
|
|
1588
|
+
{
|
|
1589
|
+
type: "date",
|
|
1590
|
+
value: occurredOn,
|
|
1591
|
+
max: todayISO(),
|
|
1592
|
+
onChange: (e) => setOccurredOn(e.target.value || todayISO()),
|
|
1593
|
+
className: field
|
|
1594
|
+
}
|
|
1595
|
+
)
|
|
1452
1596
|
] })
|
|
1453
1597
|
] }),
|
|
1454
|
-
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1455
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children:
|
|
1598
|
+
noteOpen ? /* @__PURE__ */ jsxs("label", { className: "mt-3 block", children: [
|
|
1599
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
|
|
1456
1600
|
/* @__PURE__ */ jsx(
|
|
1457
1601
|
"input",
|
|
1458
1602
|
{
|
|
1459
|
-
|
|
1460
|
-
value:
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1603
|
+
autoFocus: true,
|
|
1604
|
+
value: note,
|
|
1605
|
+
onChange: (e) => setNote(e.target.value),
|
|
1606
|
+
className: field,
|
|
1607
|
+
placeholder: "Nota fiscal, quem entregou\u2026"
|
|
1464
1608
|
}
|
|
1465
1609
|
)
|
|
1466
|
-
] })
|
|
1467
|
-
|
|
1468
|
-
noteOpen ? /* @__PURE__ */ jsxs("label", { className: "mt-3 block", children: [
|
|
1469
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
|
|
1470
|
-
/* @__PURE__ */ jsx(
|
|
1471
|
-
"input",
|
|
1610
|
+
] }) : /* @__PURE__ */ jsx(
|
|
1611
|
+
"button",
|
|
1472
1612
|
{
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
placeholder: "Nota fiscal, quem entregou\u2026"
|
|
1613
|
+
type: "button",
|
|
1614
|
+
onClick: () => setNoteOpen(true),
|
|
1615
|
+
className: "mt-2 text-xs font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
|
|
1616
|
+
children: "+ observa\xE7\xE3o"
|
|
1478
1617
|
}
|
|
1479
1618
|
)
|
|
1480
|
-
] })
|
|
1481
|
-
|
|
1482
|
-
{
|
|
1483
|
-
type: "button",
|
|
1484
|
-
onClick: () => setNoteOpen(true),
|
|
1485
|
-
className: "mt-2 text-xs font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
|
|
1486
|
-
children: "+ observa\xE7\xE3o"
|
|
1487
|
-
}
|
|
1488
|
-
),
|
|
1489
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2", children: [
|
|
1490
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || amount === 0, children: saving ? "Lan\xE7ando\u2026" : "Lan\xE7ar" }),
|
|
1491
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: close, disabled: saving, children: "Cancelar" }),
|
|
1492
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1619
|
+
] }),
|
|
1620
|
+
/* @__PURE__ */ jsxs(ModalFooter, { children: [
|
|
1493
1621
|
amount === 0 && // O motivo de o botão estar apagado, ao lado dele.
|
|
1494
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Diga quantas unidades" })
|
|
1622
|
+
/* @__PURE__ */ jsx("span", { className: "mr-auto text-xs text-muted-foreground", children: "Diga quantas unidades" }),
|
|
1623
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: close, disabled: saving, children: "Cancelar" }),
|
|
1624
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || amount === 0, children: saving ? "Lan\xE7ando\u2026" : "Lan\xE7ar" })
|
|
1495
1625
|
] })
|
|
1496
|
-
] }),
|
|
1626
|
+
] }) }),
|
|
1497
1627
|
variantId && (rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "Nenhuma posi\xE7\xE3o ainda \u2014 a primeira entrada abre o saldo." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: rows.map((r) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card p-3", children: [
|
|
1498
1628
|
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium", children: r.location }),
|
|
1499
1629
|
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
@@ -1533,20 +1663,20 @@ function shippingRegistryIdFromHash(hash) {
|
|
|
1533
1663
|
}
|
|
1534
1664
|
var PACKAGES_ROUTE = shippingRegistryPath(PACKAGES_REGISTRY);
|
|
1535
1665
|
function PackageSelect({ value, onChange, getProvider }) {
|
|
1536
|
-
const shop =
|
|
1537
|
-
const [rows, setRows] =
|
|
1538
|
-
const [open, setOpen] =
|
|
1539
|
-
const [creating, setCreating] =
|
|
1540
|
-
const ref =
|
|
1541
|
-
const load =
|
|
1666
|
+
const shop = React13__default.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
|
|
1667
|
+
const [rows, setRows] = React13__default.useState(null);
|
|
1668
|
+
const [open, setOpen] = React13__default.useState(false);
|
|
1669
|
+
const [creating, setCreating] = React13__default.useState(false);
|
|
1670
|
+
const ref = React13__default.useRef(null);
|
|
1671
|
+
const load = React13__default.useCallback(() => {
|
|
1542
1672
|
if (typeof shop.listShippingPackages !== "function") {
|
|
1543
1673
|
setRows([]);
|
|
1544
1674
|
return;
|
|
1545
1675
|
}
|
|
1546
1676
|
void shop.listShippingPackages().then(setRows).catch(() => setRows([]));
|
|
1547
1677
|
}, [shop]);
|
|
1548
|
-
|
|
1549
|
-
|
|
1678
|
+
React13__default.useEffect(load, [load]);
|
|
1679
|
+
React13__default.useEffect(() => {
|
|
1550
1680
|
if (!open) return;
|
|
1551
1681
|
const away = (event) => {
|
|
1552
1682
|
if (ref.current && !ref.current.contains(event.target)) setOpen(false);
|
|
@@ -1641,8 +1771,8 @@ function PackageSelect({ value, onChange, getProvider }) {
|
|
|
1641
1771
|
] });
|
|
1642
1772
|
}
|
|
1643
1773
|
function NewPackageModal({ onClose, onCreated }) {
|
|
1644
|
-
const [error, setError] =
|
|
1645
|
-
const provider =
|
|
1774
|
+
const [error, setError] = React13__default.useState(null);
|
|
1775
|
+
const provider = React13__default.useMemo(
|
|
1646
1776
|
() => resolveDataProvider(packagesRegistry.entity),
|
|
1647
1777
|
[]
|
|
1648
1778
|
);
|
|
@@ -1688,9 +1818,9 @@ function Option({ label, hint, active, onClick }) {
|
|
|
1688
1818
|
);
|
|
1689
1819
|
}
|
|
1690
1820
|
function ShippingProfileSelect({ value, onChange, getProvider }) {
|
|
1691
|
-
const shop =
|
|
1692
|
-
const [rows, setRows] =
|
|
1693
|
-
|
|
1821
|
+
const shop = React13__default.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
|
|
1822
|
+
const [rows, setRows] = React13__default.useState(null);
|
|
1823
|
+
React13__default.useEffect(() => {
|
|
1694
1824
|
let cancelled = false;
|
|
1695
1825
|
if (typeof shop.listShippingProfiles !== "function") {
|
|
1696
1826
|
setRows([]);
|
|
@@ -1924,6 +2054,30 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1924
2054
|
hint: "Menor aparece primeiro.",
|
|
1925
2055
|
showInTable: false
|
|
1926
2056
|
},
|
|
2057
|
+
// — Busca (SEO) —
|
|
2058
|
+
// A coleção já tinha estes dois; o produto — que é a página onde alguém
|
|
2059
|
+
// CAI vindo do Google — não tinha nenhum, então cada página herdava o que
|
|
2060
|
+
// a vitrine inventasse a partir do nome.
|
|
2061
|
+
{
|
|
2062
|
+
key: "seoTitle",
|
|
2063
|
+
label: "T\xEDtulo da p\xE1gina",
|
|
2064
|
+
type: "text",
|
|
2065
|
+
group: "seo",
|
|
2066
|
+
span: 2,
|
|
2067
|
+
placeholder: "T\xEDtulo exibido nos resultados de busca",
|
|
2068
|
+
hint: "Vazio usa o nome do produto.",
|
|
2069
|
+
showInTable: false
|
|
2070
|
+
},
|
|
2071
|
+
{
|
|
2072
|
+
key: "seoDescription",
|
|
2073
|
+
label: "Meta descri\xE7\xE3o",
|
|
2074
|
+
type: "textarea",
|
|
2075
|
+
group: "seo",
|
|
2076
|
+
span: 2,
|
|
2077
|
+
placeholder: "O trecho que aparece embaixo do t\xEDtulo no Google",
|
|
2078
|
+
hint: "Vazio usa a descri\xE7\xE3o do produto.",
|
|
2079
|
+
showInTable: false
|
|
2080
|
+
},
|
|
1927
2081
|
// — Envio —
|
|
1928
2082
|
// O desenho é o do Shopify e a ordem importa: o interruptor primeiro, e o
|
|
1929
2083
|
// resto da seção só existe depois dele. Perguntar altura de um e-book não é
|
|
@@ -2059,6 +2213,7 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
2059
2213
|
{ id: "pricing", label: "Pre\xE7o", description: 'O pre\xE7o comparativo vira o "de" riscado na vitrine.', columns: 3 },
|
|
2060
2214
|
{ id: "inventory", label: "Estoque", columns: 2 },
|
|
2061
2215
|
{ id: "publishing", label: "Publica\xE7\xE3o", description: "Controla se o produto aparece na loja.", columns: 2 },
|
|
2216
|
+
{ id: "seo", label: "Busca (SEO)", description: "Como este produto aparece nos resultados de busca.", columns: 2 },
|
|
2062
2217
|
{
|
|
2063
2218
|
id: "shipping",
|
|
2064
2219
|
label: "Envio",
|
|
@@ -2478,7 +2633,7 @@ var STATUS_LABEL2 = {
|
|
|
2478
2633
|
disabled: "Desativado"
|
|
2479
2634
|
};
|
|
2480
2635
|
function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
2481
|
-
const
|
|
2636
|
+
const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
|
|
2482
2637
|
const fields = [
|
|
2483
2638
|
{
|
|
2484
2639
|
key: "code",
|
|
@@ -2506,7 +2661,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
|
2506
2661
|
hint: "Valor de face do cart\xE3o. N\xE3o muda depois de emitido.",
|
|
2507
2662
|
showInTable: true,
|
|
2508
2663
|
sortable: true,
|
|
2509
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children:
|
|
2664
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money3(v) })
|
|
2510
2665
|
},
|
|
2511
2666
|
{
|
|
2512
2667
|
key: "balance",
|
|
@@ -2520,7 +2675,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
|
2520
2675
|
hint: "O que ainda pode ser gasto. Cai a cada resgate.",
|
|
2521
2676
|
showInTable: true,
|
|
2522
2677
|
sortable: true,
|
|
2523
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children:
|
|
2678
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money3(v) })
|
|
2524
2679
|
},
|
|
2525
2680
|
{
|
|
2526
2681
|
key: "status",
|
|
@@ -2655,20 +2810,20 @@ function buildShopCustomerGroupEntity() {
|
|
|
2655
2810
|
fieldGroups: [{ id: "basic", label: "Grupo", columns: 2 }]
|
|
2656
2811
|
};
|
|
2657
2812
|
}
|
|
2658
|
-
function describeSegment(s,
|
|
2813
|
+
function describeSegment(s, money3) {
|
|
2659
2814
|
const parts = [];
|
|
2660
2815
|
if (s.minOrders != null && s.maxOrders != null) parts.push(`${s.minOrders}\u2013${s.maxOrders} pedidos`);
|
|
2661
2816
|
else if (s.minOrders != null) parts.push(`${s.minOrders}+ pedidos`);
|
|
2662
2817
|
else if (s.maxOrders != null) parts.push(`at\xE9 ${s.maxOrders} pedidos`);
|
|
2663
|
-
if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${
|
|
2664
|
-
else if (s.minSpent != null) parts.push(`gastou ${
|
|
2665
|
-
else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${
|
|
2818
|
+
if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${money3(s.minSpent)}\u2013${money3(s.maxSpent)}`);
|
|
2819
|
+
else if (s.minSpent != null) parts.push(`gastou ${money3(s.minSpent)}+`);
|
|
2820
|
+
else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${money3(s.maxSpent)}`);
|
|
2666
2821
|
if (s.joinedAfter) parts.push(`entrou ap\xF3s ${new Date(s.joinedAfter).toLocaleDateString("pt-BR")}`);
|
|
2667
2822
|
if (s.joinedBefore) parts.push(`entrou antes de ${new Date(s.joinedBefore).toLocaleDateString("pt-BR")}`);
|
|
2668
2823
|
return parts.length ? parts.join(" \xB7 ") : "Todos os clientes";
|
|
2669
2824
|
}
|
|
2670
2825
|
function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
|
|
2671
|
-
const
|
|
2826
|
+
const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
|
|
2672
2827
|
const fields = [
|
|
2673
2828
|
{
|
|
2674
2829
|
key: "name",
|
|
@@ -2745,7 +2900,7 @@ function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
|
|
|
2745
2900
|
showInForm: false,
|
|
2746
2901
|
showInTable: true,
|
|
2747
2902
|
sortable: false,
|
|
2748
|
-
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row,
|
|
2903
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money3) })
|
|
2749
2904
|
},
|
|
2750
2905
|
{
|
|
2751
2906
|
key: "matchCount",
|
|
@@ -3075,17 +3230,17 @@ function createShopCustomerSegmentDataProvider(provider) {
|
|
|
3075
3230
|
}
|
|
3076
3231
|
var DEBOUNCE_MS = 400;
|
|
3077
3232
|
function useStoreSettings(provider, defaults = {}) {
|
|
3078
|
-
const shop =
|
|
3233
|
+
const shop = React13__default.useMemo(
|
|
3079
3234
|
() => provider ? resolve(provider) : getShopProvider(),
|
|
3080
3235
|
[provider]
|
|
3081
3236
|
);
|
|
3082
3237
|
const supported = typeof shop.getStoreSettings === "function" && typeof shop.updateStoreSettings === "function";
|
|
3083
|
-
const [stored, setStored] =
|
|
3084
|
-
const [loading, setLoading] =
|
|
3085
|
-
const [saving, setSaving] =
|
|
3086
|
-
const [error, setError] =
|
|
3087
|
-
const [loadError, setLoadError] =
|
|
3088
|
-
|
|
3238
|
+
const [stored, setStored] = React13__default.useState(null);
|
|
3239
|
+
const [loading, setLoading] = React13__default.useState(supported);
|
|
3240
|
+
const [saving, setSaving] = React13__default.useState(false);
|
|
3241
|
+
const [error, setError] = React13__default.useState(null);
|
|
3242
|
+
const [loadError, setLoadError] = React13__default.useState(null);
|
|
3243
|
+
React13__default.useEffect(() => {
|
|
3089
3244
|
if (!supported) return;
|
|
3090
3245
|
let cancelled = false;
|
|
3091
3246
|
setLoading(true);
|
|
@@ -3101,12 +3256,12 @@ function useStoreSettings(provider, defaults = {}) {
|
|
|
3101
3256
|
cancelled = true;
|
|
3102
3257
|
};
|
|
3103
3258
|
}, [shop, supported]);
|
|
3104
|
-
const pendingRef =
|
|
3105
|
-
const timerRef =
|
|
3106
|
-
|
|
3259
|
+
const pendingRef = React13__default.useRef({});
|
|
3260
|
+
const timerRef = React13__default.useRef(null);
|
|
3261
|
+
React13__default.useEffect(() => () => {
|
|
3107
3262
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
3108
3263
|
}, []);
|
|
3109
|
-
const set =
|
|
3264
|
+
const set = React13__default.useCallback(
|
|
3110
3265
|
(patch) => {
|
|
3111
3266
|
setStored((current) => ({ ...current ?? {}, ...patch }));
|
|
3112
3267
|
if (!supported) return;
|
|
@@ -3122,7 +3277,7 @@ function useStoreSettings(provider, defaults = {}) {
|
|
|
3122
3277
|
},
|
|
3123
3278
|
[shop, supported]
|
|
3124
3279
|
);
|
|
3125
|
-
const value =
|
|
3280
|
+
const value = React13__default.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
|
|
3126
3281
|
return { value, stored, set, loading, saving, error, loadError, supported };
|
|
3127
3282
|
}
|
|
3128
3283
|
function SettingsState({
|
|
@@ -3351,25 +3506,25 @@ function PaymentsSettings({ provider }) {
|
|
|
3351
3506
|
const settings = useStoreSettings(provider);
|
|
3352
3507
|
const { value, set } = settings;
|
|
3353
3508
|
const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
|
|
3354
|
-
const [adding, setAdding] =
|
|
3355
|
-
const [selected, setSelected] =
|
|
3509
|
+
const [adding, setAdding] = React13__default.useState(false);
|
|
3510
|
+
const [selected, setSelected] = React13__default.useState(null);
|
|
3356
3511
|
const panelAvailable = useConnectorsPanelAvailable();
|
|
3357
|
-
const openJourney =
|
|
3512
|
+
const openJourney = React13__default.useCallback(
|
|
3358
3513
|
(connector) => {
|
|
3359
3514
|
if (panelAvailable) openConnectorsPanel(connector);
|
|
3360
3515
|
else setSelected(connector);
|
|
3361
3516
|
},
|
|
3362
3517
|
[panelAvailable]
|
|
3363
3518
|
);
|
|
3364
|
-
const payment =
|
|
3519
|
+
const payment = React13__default.useMemo(
|
|
3365
3520
|
() => (connectors ?? []).filter((c) => c.category && PAYMENT_CATEGORIES.includes(c.category)),
|
|
3366
3521
|
[connectors]
|
|
3367
3522
|
);
|
|
3368
|
-
const live =
|
|
3523
|
+
const live = React13__default.useMemo(() => payment.filter((c) => c.state === "connected"), [payment]);
|
|
3369
3524
|
const chosen = value.paymentMethods ?? [];
|
|
3370
3525
|
const staleProvider = !!value.paymentProvider && !live.some((c) => c.slug === value.paymentProvider) && live.length === 0;
|
|
3371
|
-
const movedFrom =
|
|
3372
|
-
|
|
3526
|
+
const movedFrom = React13__default.useRef(null);
|
|
3527
|
+
React13__default.useEffect(() => {
|
|
3373
3528
|
if (!settings.supported || settings.loading) return;
|
|
3374
3529
|
const next = resolvePaymentProvider(value.paymentProvider, live.map((c) => c.slug));
|
|
3375
3530
|
if (!next) return;
|
|
@@ -3601,11 +3756,11 @@ function PackagesSettings({ basePath = shippingRegistryPath(PACKAGES_REGISTRY) }
|
|
|
3601
3756
|
] });
|
|
3602
3757
|
}
|
|
3603
3758
|
function HandlingSettings({ settings, provider }) {
|
|
3604
|
-
const shop =
|
|
3759
|
+
const shop = React13__default.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
|
|
3605
3760
|
const { value, set, loading, supported } = settings;
|
|
3606
|
-
const [noteDraft, setNoteDraft] =
|
|
3607
|
-
const [profiles, setProfiles] =
|
|
3608
|
-
|
|
3761
|
+
const [noteDraft, setNoteDraft] = React13__default.useState(null);
|
|
3762
|
+
const [profiles, setProfiles] = React13__default.useState(null);
|
|
3763
|
+
React13__default.useEffect(() => {
|
|
3609
3764
|
let cancelled = false;
|
|
3610
3765
|
void shop.listShippingProfiles?.().then((list) => {
|
|
3611
3766
|
if (!cancelled) setProfiles(list);
|
|
@@ -3713,68 +3868,1046 @@ function ChoiceCards({
|
|
|
3713
3868
|
);
|
|
3714
3869
|
}) });
|
|
3715
3870
|
}
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3871
|
+
var EMPTY = { rules: [], gaps: [] };
|
|
3872
|
+
var draft = EMPTY;
|
|
3873
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
3874
|
+
var seq = 0;
|
|
3875
|
+
var nextId = () => `rule-${++seq}`;
|
|
3876
|
+
var identity = (rule) => [rule.matchKind, rule.cityCode ?? "", rule.districtKey ?? "", rule.postalFrom ?? "", rule.postalTo ?? ""].join("|");
|
|
3877
|
+
function emit() {
|
|
3878
|
+
for (const listener of listeners) listener();
|
|
3879
|
+
}
|
|
3880
|
+
function getDeliveryDraft() {
|
|
3881
|
+
return draft;
|
|
3882
|
+
}
|
|
3883
|
+
function setDeliveryDraft(next) {
|
|
3884
|
+
draft = { ...next, rules: next.rules.map((rule) => ({ ...rule, id: rule.id || nextId() })) };
|
|
3885
|
+
emit();
|
|
3886
|
+
}
|
|
3887
|
+
function addDeliveryDraftRules(incoming) {
|
|
3888
|
+
const seen = new Set(draft.rules.map(identity));
|
|
3889
|
+
const added = [];
|
|
3890
|
+
for (const rule of incoming) {
|
|
3891
|
+
const key = identity(rule);
|
|
3892
|
+
if (seen.has(key)) continue;
|
|
3893
|
+
seen.add(key);
|
|
3894
|
+
added.push({ ...rule, id: nextId() });
|
|
3895
|
+
}
|
|
3896
|
+
if (added.length === 0) return 0;
|
|
3897
|
+
draft = { ...draft, rules: [...draft.rules, ...added] };
|
|
3898
|
+
emit();
|
|
3899
|
+
return added.length;
|
|
3900
|
+
}
|
|
3901
|
+
function isInDeliveryDraft(rule) {
|
|
3902
|
+
const key = identity(rule);
|
|
3903
|
+
return draft.rules.some((existing) => identity(existing) === key);
|
|
3904
|
+
}
|
|
3905
|
+
function setAllDeliveryDraftRates(patch) {
|
|
3906
|
+
let touched = 0;
|
|
3907
|
+
const rules = draft.rules.map((rule) => {
|
|
3908
|
+
if (rule.outcome === "refuse") return rule;
|
|
3909
|
+
touched++;
|
|
3910
|
+
return { ...rule, ...patch };
|
|
3911
|
+
});
|
|
3912
|
+
if (touched === 0) return 0;
|
|
3913
|
+
draft = { ...draft, rules };
|
|
3914
|
+
emit();
|
|
3915
|
+
return touched;
|
|
3916
|
+
}
|
|
3917
|
+
function updateDeliveryDraftRule(id, patch) {
|
|
3918
|
+
const index = draft.rules.findIndex((rule) => rule.id === id);
|
|
3919
|
+
if (index < 0) return false;
|
|
3920
|
+
const rules = draft.rules.slice();
|
|
3921
|
+
rules[index] = { ...rules[index], ...patch, id };
|
|
3922
|
+
draft = { ...draft, rules };
|
|
3923
|
+
emit();
|
|
3924
|
+
return true;
|
|
3721
3925
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3926
|
+
function removeDeliveryDraftRule(id) {
|
|
3927
|
+
const rules = draft.rules.filter((rule) => rule.id !== id);
|
|
3928
|
+
if (rules.length === draft.rules.length) return false;
|
|
3929
|
+
draft = { ...draft, rules };
|
|
3930
|
+
emit();
|
|
3931
|
+
return true;
|
|
3932
|
+
}
|
|
3933
|
+
function clearDeliveryDraft() {
|
|
3934
|
+
draft = EMPTY;
|
|
3935
|
+
emit();
|
|
3936
|
+
}
|
|
3937
|
+
function subscribeDeliveryDraft(listener) {
|
|
3938
|
+
listeners.add(listener);
|
|
3939
|
+
return () => {
|
|
3940
|
+
listeners.delete(listener);
|
|
3941
|
+
};
|
|
3942
|
+
}
|
|
3943
|
+
function useDeliveryDraft() {
|
|
3944
|
+
return React13.useSyncExternalStore(subscribeDeliveryDraft, getDeliveryDraft, getDeliveryDraft);
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
// src/views/delivery-agent.ts
|
|
3948
|
+
var DELIVERY_AREAS_INSTRUCTIONS = `
|
|
3949
|
+
Esta \xE9 a tela de Entrega da loja. Aqui voc\xEA configura ONDE a loja entrega, ONDE ela recusa e onde precisa confirmar antes.
|
|
3950
|
+
|
|
3951
|
+
Uma regra tem duas partes: a \xC1REA (um munic\xEDpio ou um bairro) e o DESFECHO:
|
|
3952
|
+
\xB7 entrego (deliver) \xB7 confirmo antes (review) \xB7 n\xE3o entrego (refuse)
|
|
3953
|
+
Entre as regras que casam com um endere\xE7o, vence a mais estrita. Ent\xE3o "Zona Norte: entrego" + "Mar\xE9: n\xE3o entrego" s\xE3o DUAS regras, e a segunda ganha na Mar\xE9. Nunca tente recortar uma \xE1rea grande em peda\xE7os para abrir exce\xE7\xE3o \u2014 declare a exce\xE7\xE3o.
|
|
3954
|
+
|
|
3955
|
+
PREFIRA A \xC1REA MAIOR MENOS AS EXCE\xC7\xD5ES. \xC9 assim que o lojista fala, e \xE9 o que a regra mais estrita permite. Quando ele lista v\xE1rias zonas de uma mesma cidade \u2014 "zona norte, zona sul, zona oeste, zona sudoeste" \u2014 isso \xC9 a cidade inteira: proponha UMA regra de munic\xEDpio e as exce\xE7\xF5es, n\xE3o oitenta bairros. Uma whitelist de bairros \xE9 pior de duas formas: ela envelhece (bairro que faltar na lista vira venda recusada em sil\xEAncio) e ningu\xE9m confere oitenta linhas.
|
|
3956
|
+
|
|
3957
|
+
Quando a lojista colar uma mensagem descrevendo a opera\xE7\xE3o dela:
|
|
3958
|
+
1. Separe cada lugar citado e se ele \xE9 entrega, recusa ou d\xFAvida. "Evitar X", "exceto X" e "n\xE3o entregamos X" s\xE3o recusa.
|
|
3959
|
+
2. Chame \`findDeliveryAreas\` UMA vez com todos os nomes juntos.
|
|
3960
|
+
\xB7 \`resolved\` est\xE1 pronto para virar regra.
|
|
3961
|
+
\xB7 \`options\` \xE9 amb\xEDguo: pergunte qual, n\xE3o escolha.
|
|
3962
|
+
\xB7 \`notFound\` \xE9 o caso importante. Muita comunidade n\xE3o tem bairro nem CEP pr\xF3prio \u2014 divide o do bairro vizinho. N\xC3O invente faixa de CEP para ela. Diga isso em uma linha e ofere\xE7a o que existe: marcar o bairro que a cont\xE9m como "confirmo antes".
|
|
3963
|
+
\xB7 \`group\` (Zona Sul, Regi\xE3o dos Lagos) traz \`members\`. Use os membros como regras individuais S\xD3 quando ele citou uma parte da cidade. Se ele citou zonas que somam a cidade toda, jogue os grupos fora e use o munic\xEDpio.
|
|
3964
|
+
3. Chame \`proposeDeliveryRules\` com o que resolveu. Ele N\xC3O grava \u2014 desenha a tabela na tela dela.
|
|
3965
|
+
4. Pre\xE7o e prazo: s\xF3 preencha o que a mensagem disse. Se ela n\xE3o disse, deixe vazio e pergunte UMA vez, junto, no fim: "quanto voc\xEA cobra em cada regi\xE3o, e tem frete gr\xE1tis acima de algum valor?".
|
|
3966
|
+
5. Quando ela confirmar, chame \`commitDeliveryRules\`. Depois teste com \`checkDeliveryFor\` dois ou tr\xEAs CEPs (um que entrega, um que recusa) e relate o resultado em duas linhas.
|
|
3967
|
+
|
|
3968
|
+
Regras:
|
|
3969
|
+
\xB7 Voc\xEA NUNCA escreve um CEP ou um c\xF3digo de munic\xEDpio que n\xE3o tenha vindo de \`findDeliveryAreas\`.
|
|
3970
|
+
\xB7 N\xC3O proponha faixa de CEP. Faixa \xE9 como transportadora precifica, n\xE3o como uma pessoa descreve onde entrega, e ningu\xE9m no mercado pede isso ao lojista. Ela existe s\xF3 para quando a lojista estiver COPIANDO uma tabela de transportadora e disser os n\xFAmeros ela mesma. Se voc\xEA n\xE3o achar a \xE1rea pelo nome, pergunte \u2014 n\xE3o caia na faixa.
|
|
3971
|
+
\xB7 Comunidade que n\xE3o resolve pelo nome (Chapad\xE3o \xE9 o caso) n\xE3o vira recusa inventada: ofere\xE7a marcar o bairro que a cont\xE9m como "confirmo antes", que aceita o pedido e deixa a decis\xE3o com ele.
|
|
3972
|
+
\xB7 N\xE3o imprima a tabela inteira no texto: ela est\xE1 na tela. Diga quantas regras entraram, quantas s\xE3o recusa, e o que ficou em aberto.
|
|
3973
|
+
\xB7 Portugu\xEAs do Brasil, curto.
|
|
3974
|
+
`.trim();
|
|
3975
|
+
var FIND_AREAS = {
|
|
3976
|
+
id: "shop.delivery.find-areas",
|
|
3977
|
+
name: "findDeliveryAreas",
|
|
3978
|
+
description: 'Resolves place names the merchant wrote ("Angra", "Zona Sul", "Barra da Tijuca", "Itacuru\xE7\xE1") into real municipalities (IBGE code), neighbourhoods, or curated groups that expand into several of them. Send EVERY name of the message in one call. This is the ONLY source of a municipality code or a neighbourhood key \u2014 never write one that did not come from here. A name that answers `notFound` is a question for the merchant, not a rule to invent.',
|
|
3979
|
+
icon: "MapPin",
|
|
3980
|
+
mode: "read",
|
|
3981
|
+
execution: { plane: "client" },
|
|
3982
|
+
category: "Shop",
|
|
3983
|
+
role: "operations",
|
|
3984
|
+
parameters: {
|
|
3985
|
+
type: "object",
|
|
3986
|
+
properties: {
|
|
3987
|
+
queries: {
|
|
3988
|
+
type: "array",
|
|
3989
|
+
description: 'The place names as the merchant wrote them, without "n\xE3o entrego" / "exceto".',
|
|
3990
|
+
items: { type: "string" }
|
|
3991
|
+
}
|
|
3992
|
+
},
|
|
3993
|
+
required: ["queries"]
|
|
3735
3994
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
},
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3995
|
+
};
|
|
3996
|
+
var PROPOSE_RULES = {
|
|
3997
|
+
id: "shop.delivery.propose-rules",
|
|
3998
|
+
name: "proposeDeliveryRules",
|
|
3999
|
+
description: "Draws the delivery rules the message describes as an EDITABLE TABLE on the merchant's screen. Writes nothing. Every area must carry the ids findDeliveryAreas answered. A group is expanded into one rule per member here, so the merchant sees and trims what it covers. Leave rate and ETA out when the message did not state them.",
|
|
4000
|
+
icon: "ListChecks",
|
|
4001
|
+
mode: "read",
|
|
4002
|
+
execution: { plane: "client" },
|
|
4003
|
+
category: "Shop",
|
|
4004
|
+
role: "operations",
|
|
4005
|
+
parameters: {
|
|
4006
|
+
type: "object",
|
|
4007
|
+
properties: {
|
|
4008
|
+
rules: {
|
|
4009
|
+
type: "array",
|
|
4010
|
+
description: "One entry per area named in the message.",
|
|
4011
|
+
items: {
|
|
4012
|
+
type: "object",
|
|
4013
|
+
properties: {
|
|
4014
|
+
kind: {
|
|
4015
|
+
type: "string",
|
|
4016
|
+
enum: ["city", "district", "group", "postal_range"],
|
|
4017
|
+
description: "The `kind` findDeliveryAreas answered. Use postal_range ONLY when the merchant is copying a carrier freight table and dictated the numbers \u2014 never to describe where they deliver."
|
|
4018
|
+
},
|
|
4019
|
+
label: { type: "string", description: "The place name, as it should read on the table." },
|
|
4020
|
+
cityCode: { type: "string", description: "IBGE code from findDeliveryAreas." },
|
|
4021
|
+
districtKey: { type: "string", description: "Neighbourhood key from findDeliveryAreas." },
|
|
4022
|
+
postalFrom: { type: "string", description: "Only for postal_range." },
|
|
4023
|
+
postalTo: { type: "string", description: "Only for postal_range." },
|
|
4024
|
+
outcome: {
|
|
4025
|
+
type: "string",
|
|
4026
|
+
enum: ["deliver", "review", "refuse"],
|
|
4027
|
+
description: "deliver | review (confirm before dispatch) | refuse."
|
|
4028
|
+
},
|
|
4029
|
+
rate: { type: "number", description: "Only if the message stated a price. Otherwise omit." },
|
|
4030
|
+
freeAbove: { type: "number", description: "Only if the message stated a threshold." },
|
|
4031
|
+
etaMinDays: { type: "number" },
|
|
4032
|
+
etaMaxDays: { type: "number" },
|
|
4033
|
+
source: { type: "string", description: "The words of the message this rule came from." }
|
|
4034
|
+
},
|
|
4035
|
+
required: ["kind", "label", "outcome"]
|
|
4036
|
+
}
|
|
4037
|
+
},
|
|
4038
|
+
gaps: {
|
|
4039
|
+
type: "array",
|
|
4040
|
+
description: "What the message named and you could NOT resolve, so the merchant sees it too.",
|
|
4041
|
+
items: {
|
|
4042
|
+
type: "object",
|
|
4043
|
+
properties: {
|
|
4044
|
+
query: { type: "string" },
|
|
4045
|
+
reason: { type: "string", description: "Plain Portuguese, one line." }
|
|
4046
|
+
},
|
|
4047
|
+
required: ["query", "reason"]
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
3769
4050
|
},
|
|
3770
|
-
[
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
4051
|
+
required: ["rules"]
|
|
4052
|
+
}
|
|
4053
|
+
};
|
|
4054
|
+
var SET_RULE = {
|
|
4055
|
+
id: "shop.delivery.set-rule",
|
|
4056
|
+
name: "setDeliveryRule",
|
|
4057
|
+
description: "Corrects one line of the draft table already on screen \u2014 its outcome, price, free-above or ETA. Use when the merchant answers a question about the rules you proposed, instead of rebuilding the whole draft.",
|
|
4058
|
+
icon: "Pencil",
|
|
4059
|
+
mode: "read",
|
|
4060
|
+
execution: { plane: "client" },
|
|
4061
|
+
category: "Shop",
|
|
4062
|
+
role: "operations",
|
|
4063
|
+
parameters: {
|
|
4064
|
+
type: "object",
|
|
4065
|
+
properties: {
|
|
4066
|
+
id: { type: "string", description: "The rule id proposeDeliveryRules answered." },
|
|
4067
|
+
outcome: { type: "string", enum: ["deliver", "review", "refuse"] },
|
|
4068
|
+
rate: { type: "number" },
|
|
4069
|
+
freeAbove: { type: "number" },
|
|
4070
|
+
etaMinDays: { type: "number" },
|
|
4071
|
+
etaMaxDays: { type: "number" }
|
|
4072
|
+
},
|
|
4073
|
+
required: ["id"]
|
|
4074
|
+
}
|
|
4075
|
+
};
|
|
4076
|
+
var COMMIT_RULES = {
|
|
4077
|
+
id: "shop.delivery.commit-rules",
|
|
4078
|
+
name: "commitDeliveryRules",
|
|
4079
|
+
description: "Saves the draft table as the store's delivery rules. Call ONLY after the merchant said the table is right. It changes who can buy: an address outside every rule cannot check out, and a refused one is blocked server-side. Saves the draft as it stands on screen \u2014 the merchant may have edited it, and those edits win.",
|
|
4080
|
+
icon: "Save",
|
|
4081
|
+
mode: "persist",
|
|
4082
|
+
execution: { plane: "client" },
|
|
4083
|
+
category: "Shop",
|
|
4084
|
+
role: "operations",
|
|
4085
|
+
permission: { feature: "shop", action: "update" },
|
|
4086
|
+
parameters: { type: "object", properties: {} }
|
|
4087
|
+
};
|
|
4088
|
+
var CHECK_DELIVERY = {
|
|
4089
|
+
id: "shop.delivery.check",
|
|
4090
|
+
name: "checkDeliveryFor",
|
|
4091
|
+
description: 'Answers what the store does with a given postcode right now: deliver, confirm first, refuse, or outside every rule. Use it to CHECK YOUR OWN WORK after saving, and to answer "why could this customer not buy?".',
|
|
4092
|
+
icon: "Search",
|
|
4093
|
+
mode: "read",
|
|
4094
|
+
execution: { plane: "client" },
|
|
4095
|
+
category: "Shop",
|
|
4096
|
+
role: "operations",
|
|
4097
|
+
parameters: {
|
|
4098
|
+
type: "object",
|
|
4099
|
+
properties: {
|
|
4100
|
+
postalCodes: {
|
|
4101
|
+
type: "array",
|
|
4102
|
+
description: "One or more CEPs, with or without punctuation.",
|
|
4103
|
+
items: { type: "string" }
|
|
4104
|
+
}
|
|
4105
|
+
},
|
|
4106
|
+
required: ["postalCodes"]
|
|
4107
|
+
}
|
|
4108
|
+
};
|
|
4109
|
+
[
|
|
4110
|
+
FIND_AREAS.id,
|
|
4111
|
+
PROPOSE_RULES.id,
|
|
4112
|
+
SET_RULE.id,
|
|
4113
|
+
COMMIT_RULES.id,
|
|
4114
|
+
CHECK_DELIVERY.id
|
|
4115
|
+
];
|
|
4116
|
+
var text2 = (value) => typeof value === "string" ? value.trim() : "";
|
|
4117
|
+
var number = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
4118
|
+
var OUTCOMES = ["deliver", "review", "refuse"];
|
|
4119
|
+
var outcomeOf = (value) => OUTCOMES.includes(value) ? value : "deliver";
|
|
4120
|
+
function candidateOut(area) {
|
|
4121
|
+
return {
|
|
4122
|
+
kind: area.kind,
|
|
4123
|
+
label: area.label,
|
|
4124
|
+
cityCode: area.cityCode ?? void 0,
|
|
4125
|
+
cityName: area.cityName ?? void 0,
|
|
4126
|
+
state: area.state ?? void 0,
|
|
4127
|
+
districtKey: area.districtKey ?? void 0,
|
|
4128
|
+
knownPostalCodes: area.knownPostalCodes || void 0,
|
|
4129
|
+
members: area.members?.map((m) => ({
|
|
4130
|
+
kind: m.kind,
|
|
4131
|
+
label: m.label,
|
|
4132
|
+
cityCode: m.cityCode ?? void 0,
|
|
4133
|
+
districtKey: m.districtKey ?? void 0
|
|
4134
|
+
}))
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
function pickCandidate(query, areas) {
|
|
4138
|
+
if (areas.length === 0) return {};
|
|
4139
|
+
if (areas.length === 1) return { resolved: areas[0] };
|
|
4140
|
+
const key = query.trim().toLowerCase();
|
|
4141
|
+
const exact = areas.filter((a) => a.label.trim().toLowerCase() === key);
|
|
4142
|
+
if (exact.length === 1) return { resolved: exact[0] };
|
|
4143
|
+
return { options: areas.slice(0, 6) };
|
|
4144
|
+
}
|
|
4145
|
+
function ruleFromArgs(raw, index) {
|
|
4146
|
+
const kind = text2(raw.kind);
|
|
4147
|
+
const label = text2(raw.label);
|
|
4148
|
+
if (!label) return null;
|
|
4149
|
+
const base = {
|
|
4150
|
+
id: `draft-${index}`,
|
|
4151
|
+
label,
|
|
4152
|
+
outcome: outcomeOf(raw.outcome),
|
|
4153
|
+
rate: number(raw.rate),
|
|
4154
|
+
freeAbove: number(raw.freeAbove),
|
|
4155
|
+
etaMinDays: number(raw.etaMinDays),
|
|
4156
|
+
etaMaxDays: number(raw.etaMaxDays),
|
|
4157
|
+
source: text2(raw.source) || void 0
|
|
4158
|
+
};
|
|
4159
|
+
if (kind === "city") {
|
|
4160
|
+
const cityCode = text2(raw.cityCode);
|
|
4161
|
+
if (!cityCode) return null;
|
|
4162
|
+
return { ...base, matchKind: "city", cityCode };
|
|
4163
|
+
}
|
|
4164
|
+
if (kind === "district") {
|
|
4165
|
+
const districtKey = text2(raw.districtKey);
|
|
4166
|
+
if (!districtKey) return null;
|
|
4167
|
+
return {
|
|
4168
|
+
...base,
|
|
4169
|
+
matchKind: "district",
|
|
4170
|
+
districtKey,
|
|
4171
|
+
cityCode: text2(raw.cityCode) || null
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
if (kind === "postal_range") {
|
|
4175
|
+
const from = text2(raw.postalFrom).replace(/\D/g, "");
|
|
4176
|
+
const to = text2(raw.postalTo).replace(/\D/g, "");
|
|
4177
|
+
if (from.length !== 8 || to.length !== 8) return null;
|
|
4178
|
+
return { ...base, matchKind: "postal_range", postalFrom: from, postalTo: to };
|
|
4179
|
+
}
|
|
4180
|
+
return null;
|
|
4181
|
+
}
|
|
4182
|
+
function zoneInputFrom(rule) {
|
|
4183
|
+
return {
|
|
4184
|
+
name: rule.label,
|
|
4185
|
+
carrier: null,
|
|
4186
|
+
matchKind: rule.matchKind,
|
|
4187
|
+
cityCode: rule.cityCode ?? null,
|
|
4188
|
+
districtKey: rule.districtKey ?? null,
|
|
4189
|
+
outcome: rule.outcome,
|
|
4190
|
+
postalFrom: rule.postalFrom ?? null,
|
|
4191
|
+
postalTo: rule.postalTo ?? null,
|
|
4192
|
+
// Uma regra de recusa não cobra nada: o preço dela nunca é lido.
|
|
4193
|
+
rate: rule.outcome === "refuse" ? 0 : rule.rate ?? 0,
|
|
4194
|
+
freeAbove: rule.freeAbove ?? null,
|
|
4195
|
+
etaMinDays: rule.etaMinDays ?? null,
|
|
4196
|
+
etaMaxDays: rule.etaMaxDays ?? null,
|
|
4197
|
+
active: true,
|
|
4198
|
+
sortOrder: 0,
|
|
4199
|
+
excluded: rule.outcome === "refuse"
|
|
4200
|
+
};
|
|
4201
|
+
}
|
|
4202
|
+
function createDeliveryAreaTools({ getProvider, onSaved }) {
|
|
4203
|
+
const failed = (error) => ({ ok: false, error: errorMessage(error) });
|
|
4204
|
+
return [
|
|
4205
|
+
{
|
|
4206
|
+
tool: FIND_AREAS,
|
|
4207
|
+
execute: async (args) => {
|
|
4208
|
+
const queries = (Array.isArray(args.queries) ? args.queries : []).map(text2).filter(Boolean).slice(0, 30);
|
|
4209
|
+
if (queries.length === 0) return { ok: false, error: "no_queries" };
|
|
4210
|
+
try {
|
|
4211
|
+
const shop = getProvider();
|
|
4212
|
+
if (typeof shop.findAreas !== "function") {
|
|
4213
|
+
return { ok: false, error: "area_lookup_unavailable" };
|
|
4214
|
+
}
|
|
4215
|
+
const results = await Promise.all(
|
|
4216
|
+
queries.map(async (query) => {
|
|
4217
|
+
const areas = await shop.findAreas(query, 8).catch(() => []);
|
|
4218
|
+
const { resolved, options } = pickCandidate(query, areas);
|
|
4219
|
+
if (resolved) return { query, resolved: candidateOut(resolved) };
|
|
4220
|
+
if (options) return { query, options: options.map(candidateOut) };
|
|
4221
|
+
return {
|
|
4222
|
+
query,
|
|
4223
|
+
notFound: true,
|
|
4224
|
+
// Dito aqui e não deixado para o modelo deduzir: é a frase que
|
|
4225
|
+
// separa "não existe" de "existe mas não dá para separar por CEP".
|
|
4226
|
+
hint: "Sem munic\xEDpio, bairro ou grupo com esse nome. Comunidade normalmente n\xE3o tem CEP pr\xF3prio \u2014 divide o do bairro vizinho. Pergunte \xE0 lojista qual bairro a cont\xE9m."
|
|
4227
|
+
};
|
|
4228
|
+
})
|
|
4229
|
+
);
|
|
4230
|
+
return { ok: true, results };
|
|
4231
|
+
} catch (error) {
|
|
4232
|
+
return failed(error);
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
},
|
|
4236
|
+
{
|
|
4237
|
+
tool: PROPOSE_RULES,
|
|
4238
|
+
execute: async (args) => {
|
|
4239
|
+
const raw = Array.isArray(args.rules) ? args.rules : [];
|
|
4240
|
+
const rules = [];
|
|
4241
|
+
const gaps = (Array.isArray(args.gaps) ? args.gaps : []).map((g) => ({ query: text2(g?.query), reason: text2(g?.reason) })).filter((g) => g.query);
|
|
4242
|
+
raw.forEach((entry, index) => {
|
|
4243
|
+
const rule = ruleFromArgs(entry ?? {}, rules.length);
|
|
4244
|
+
if (rule) {
|
|
4245
|
+
rules.push(rule);
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4248
|
+
gaps.push({
|
|
4249
|
+
query: text2(entry?.label) || `regra ${index + 1}`,
|
|
4250
|
+
reason: "Faltou o identificador da \xE1rea. Chame findDeliveryAreas para este nome e use o que ele responder."
|
|
4251
|
+
});
|
|
4252
|
+
});
|
|
4253
|
+
if (rules.length === 0 && gaps.length === 0) return { ok: false, error: "empty_draft" };
|
|
4254
|
+
setDeliveryDraft({ rules, gaps });
|
|
4255
|
+
return {
|
|
4256
|
+
ok: true,
|
|
4257
|
+
drafted: rules.length,
|
|
4258
|
+
refusing: rules.filter((r) => r.outcome === "refuse").length,
|
|
4259
|
+
reviewing: rules.filter((r) => r.outcome === "review").length,
|
|
4260
|
+
missingPrice: rules.filter((r) => r.outcome !== "refuse" && r.rate == null).length,
|
|
4261
|
+
gaps,
|
|
4262
|
+
rules: rules.map((r) => ({ id: r.id, label: r.label, outcome: r.outcome })),
|
|
4263
|
+
nextStep: "commitDeliveryRules"
|
|
4264
|
+
};
|
|
4265
|
+
}
|
|
4266
|
+
},
|
|
4267
|
+
{
|
|
4268
|
+
tool: SET_RULE,
|
|
4269
|
+
execute: async (args) => {
|
|
4270
|
+
const id = text2(args.id);
|
|
4271
|
+
if (!id) return { ok: false, error: "no_id" };
|
|
4272
|
+
const patch = {};
|
|
4273
|
+
if (args.outcome !== void 0) patch.outcome = outcomeOf(args.outcome);
|
|
4274
|
+
if (args.rate !== void 0) patch.rate = number(args.rate);
|
|
4275
|
+
if (args.freeAbove !== void 0) patch.freeAbove = number(args.freeAbove);
|
|
4276
|
+
if (args.etaMinDays !== void 0) patch.etaMinDays = number(args.etaMinDays);
|
|
4277
|
+
if (args.etaMaxDays !== void 0) patch.etaMaxDays = number(args.etaMaxDays);
|
|
4278
|
+
if (Object.keys(patch).length === 0) return { ok: false, error: "nothing_to_change" };
|
|
4279
|
+
if (!updateDeliveryDraftRule(id, patch)) return { ok: false, error: "rule_not_in_draft" };
|
|
4280
|
+
return { ok: true, id, ...patch };
|
|
4281
|
+
}
|
|
4282
|
+
},
|
|
4283
|
+
{
|
|
4284
|
+
tool: COMMIT_RULES,
|
|
4285
|
+
execute: async () => {
|
|
4286
|
+
const { rules } = getDeliveryDraft();
|
|
4287
|
+
if (rules.length === 0) return { ok: false, error: "nothing_to_save" };
|
|
4288
|
+
try {
|
|
4289
|
+
const shop = getProvider();
|
|
4290
|
+
if (typeof shop.createShippingZone !== "function") {
|
|
4291
|
+
return { ok: false, error: "zones_unavailable" };
|
|
4292
|
+
}
|
|
4293
|
+
const saved = [];
|
|
4294
|
+
const rejected = [];
|
|
4295
|
+
for (const rule of rules) {
|
|
4296
|
+
try {
|
|
4297
|
+
await shop.createShippingZone(zoneInputFrom(rule));
|
|
4298
|
+
saved.push(rule.label);
|
|
4299
|
+
} catch (error) {
|
|
4300
|
+
rejected.push({ label: rule.label, error: errorMessage(error) });
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
if (saved.length > 0) {
|
|
4304
|
+
clearDeliveryDraft();
|
|
4305
|
+
onSaved();
|
|
4306
|
+
}
|
|
4307
|
+
return { ok: rejected.length === 0, saved: saved.length, rejected };
|
|
4308
|
+
} catch (error) {
|
|
4309
|
+
return failed(error);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
},
|
|
4313
|
+
{
|
|
4314
|
+
tool: CHECK_DELIVERY,
|
|
4315
|
+
execute: async (args) => {
|
|
4316
|
+
const codes = (Array.isArray(args.postalCodes) ? args.postalCodes : []).map((c) => text2(c).replace(/\D/g, "")).filter((c) => c.length === 8).slice(0, 10);
|
|
4317
|
+
if (codes.length === 0) return { ok: false, error: "no_postal_codes" };
|
|
4318
|
+
try {
|
|
4319
|
+
const shop = getProvider();
|
|
4320
|
+
if (typeof shop.checkDelivery !== "function") {
|
|
4321
|
+
return { ok: false, error: "delivery_check_unavailable" };
|
|
4322
|
+
}
|
|
4323
|
+
const results = await Promise.all(
|
|
4324
|
+
codes.map(async (code) => {
|
|
4325
|
+
const check = await shop.checkDelivery(code);
|
|
4326
|
+
return {
|
|
4327
|
+
postalCode: code,
|
|
4328
|
+
// null = nenhuma regra casou: fora de área, que não é o mesmo
|
|
4329
|
+
// que recusado e o texto para a lojista é outro.
|
|
4330
|
+
outcome: check.outcome ?? null,
|
|
4331
|
+
rule: check.zoneName ?? check.refusedZone ?? null,
|
|
4332
|
+
outsideEveryRule: !check.refused && !check.ownZone
|
|
4333
|
+
};
|
|
4334
|
+
})
|
|
4335
|
+
);
|
|
4336
|
+
return { ok: true, results };
|
|
4337
|
+
} catch (error) {
|
|
4338
|
+
return failed(error);
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
];
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
// src/views/DeliveryAgentSurface.tsx
|
|
4346
|
+
function DeliveryAgentSurface({ provider, zones, onSaved }) {
|
|
4347
|
+
const live = React13.useRef({ provider, zones, onSaved });
|
|
4348
|
+
live.current = { provider, zones, onSaved };
|
|
4349
|
+
const contribution = React13.useMemo(() => ({
|
|
4350
|
+
id: "shop.delivery.settings",
|
|
4351
|
+
pluginId: "shop",
|
|
4352
|
+
role: "operations",
|
|
4353
|
+
instructions: DELIVERY_AREAS_INSTRUCTIONS,
|
|
4354
|
+
describe: () => {
|
|
4355
|
+
const { zones: rows } = live.current;
|
|
4356
|
+
const draft2 = getDeliveryDraft();
|
|
4357
|
+
const byOutcome = (outcome) => rows.filter((z) => z.active && (z.outcome ?? (z.excluded ? "refuse" : "deliver")) === outcome);
|
|
4358
|
+
return {
|
|
4359
|
+
title: "Configura\xE7\xE3o \u2014 Entrega",
|
|
4360
|
+
state: {
|
|
4361
|
+
screen: "/settings/shop-shipping",
|
|
4362
|
+
savedRules: {
|
|
4363
|
+
delivering: byOutcome("deliver").length,
|
|
4364
|
+
reviewing: byOutcome("review").length,
|
|
4365
|
+
refusing: byOutcome("refuse").length,
|
|
4366
|
+
// Os nomes, não as faixas: o agente precisa saber o que JÁ existe
|
|
4367
|
+
// para não propor a mesma região duas vezes.
|
|
4368
|
+
names: rows.filter((z) => z.active).slice(0, 40).map((z) => z.name)
|
|
4369
|
+
},
|
|
4370
|
+
draft: draft2.rules.length === 0 && draft2.gaps.length === 0 ? null : {
|
|
4371
|
+
rules: draft2.rules.map((r) => ({
|
|
4372
|
+
id: r.id,
|
|
4373
|
+
label: r.label,
|
|
4374
|
+
outcome: r.outcome,
|
|
4375
|
+
rate: r.rate ?? null,
|
|
4376
|
+
freeAbove: r.freeAbove ?? null
|
|
4377
|
+
})),
|
|
4378
|
+
gaps: draft2.gaps
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
};
|
|
4382
|
+
},
|
|
4383
|
+
tools: createDeliveryAreaTools({
|
|
4384
|
+
getProvider: () => live.current.provider(),
|
|
4385
|
+
onSaved: () => live.current.onSaved()
|
|
4386
|
+
})
|
|
4387
|
+
}), []);
|
|
4388
|
+
useAgentSurface(contribution);
|
|
4389
|
+
return null;
|
|
4390
|
+
}
|
|
4391
|
+
var MIN_QUERY = 2;
|
|
4392
|
+
var DEBOUNCE_MS2 = 250;
|
|
4393
|
+
function ruleFor(area, outcome) {
|
|
4394
|
+
if (area.kind === "city" && area.cityCode) {
|
|
4395
|
+
return { matchKind: "city", label: area.label, cityCode: area.cityCode, outcome };
|
|
4396
|
+
}
|
|
4397
|
+
if (area.kind === "district" && area.districtKey) {
|
|
4398
|
+
return {
|
|
4399
|
+
matchKind: "district",
|
|
4400
|
+
label: area.label,
|
|
4401
|
+
districtKey: area.districtKey,
|
|
4402
|
+
cityCode: area.cityCode ?? null,
|
|
4403
|
+
outcome
|
|
4404
|
+
};
|
|
4405
|
+
}
|
|
4406
|
+
return null;
|
|
4407
|
+
}
|
|
4408
|
+
function rulesFor(area, outcome) {
|
|
4409
|
+
if (area.kind !== "group") {
|
|
4410
|
+
const single = ruleFor(area, outcome);
|
|
4411
|
+
return single ? [single] : [];
|
|
4412
|
+
}
|
|
4413
|
+
return (area.members ?? []).flatMap((member) => {
|
|
4414
|
+
const rule = ruleFor(
|
|
4415
|
+
{ kind: member.kind, label: member.label, cityCode: member.cityCode, districtKey: member.districtKey },
|
|
4416
|
+
outcome
|
|
4417
|
+
);
|
|
4418
|
+
return rule ? [rule] : [];
|
|
4419
|
+
});
|
|
4420
|
+
}
|
|
4421
|
+
function areaMeta(area) {
|
|
4422
|
+
if (area.matchedAs) return area.matchedAs;
|
|
4423
|
+
if (area.kind === "group") {
|
|
4424
|
+
const n = area.members?.length ?? 0;
|
|
4425
|
+
return `${n} ${n === 1 ? "regi\xE3o" : "regi\xF5es"}${area.cityName ? ` \xB7 ${area.cityName}` : ""}`;
|
|
4426
|
+
}
|
|
4427
|
+
if (area.kind === "city") return `Cidade inteira${area.state ? ` \xB7 ${area.state}` : ""}`;
|
|
4428
|
+
return `Bairro${area.cityName ? ` \xB7 ${area.cityName}` : ""}`;
|
|
4429
|
+
}
|
|
4430
|
+
var areaKeyOf = (area) => `${area.kind}:${area.districtKey ?? area.cityCode ?? area.label}`;
|
|
4431
|
+
function AreaSearch({ provider, outcome, placeholder, onlyCityCodes, emptyHint }) {
|
|
4432
|
+
const [query, setQuery] = React13__default.useState("");
|
|
4433
|
+
const [results, setResults] = React13__default.useState(null);
|
|
4434
|
+
const [loading, setLoading] = React13__default.useState(false);
|
|
4435
|
+
const [unavailable, setUnavailable] = React13__default.useState(false);
|
|
4436
|
+
const [justAdded, setJustAdded] = React13__default.useState(null);
|
|
4437
|
+
React13__default.useEffect(() => {
|
|
4438
|
+
const text3 = query.trim();
|
|
4439
|
+
if (text3.length < MIN_QUERY) {
|
|
4440
|
+
setResults(null);
|
|
4441
|
+
return void 0;
|
|
4442
|
+
}
|
|
4443
|
+
let cancelled = false;
|
|
4444
|
+
setLoading(true);
|
|
4445
|
+
const timer = setTimeout(() => {
|
|
4446
|
+
const shop = provider ? resolve(provider) : getShopProvider();
|
|
4447
|
+
if (typeof shop.findAreas !== "function") {
|
|
4448
|
+
if (!cancelled) {
|
|
4449
|
+
setUnavailable(true);
|
|
4450
|
+
setLoading(false);
|
|
4451
|
+
}
|
|
4452
|
+
return;
|
|
4453
|
+
}
|
|
4454
|
+
void shop.findAreas(text3, 10).then((list) => {
|
|
4455
|
+
if (!cancelled) {
|
|
4456
|
+
setResults(list);
|
|
4457
|
+
setUnavailable(false);
|
|
4458
|
+
}
|
|
4459
|
+
}).catch(() => {
|
|
4460
|
+
if (!cancelled) {
|
|
4461
|
+
setResults([]);
|
|
4462
|
+
setUnavailable(true);
|
|
4463
|
+
}
|
|
4464
|
+
}).finally(() => {
|
|
4465
|
+
if (!cancelled) setLoading(false);
|
|
4466
|
+
});
|
|
4467
|
+
}, DEBOUNCE_MS2);
|
|
4468
|
+
return () => {
|
|
4469
|
+
cancelled = true;
|
|
4470
|
+
clearTimeout(timer);
|
|
4471
|
+
};
|
|
4472
|
+
}, [query, provider]);
|
|
4473
|
+
const add = (area) => {
|
|
4474
|
+
const rules = rulesFor(area, outcome);
|
|
4475
|
+
if (rules.length === 0) return;
|
|
4476
|
+
const added = addDeliveryDraftRules(rules);
|
|
4477
|
+
setJustAdded(areaKeyOf(area));
|
|
4478
|
+
setTimeout(() => setJustAdded(null), 1400);
|
|
4479
|
+
if (added > 0) setQuery("");
|
|
4480
|
+
};
|
|
4481
|
+
const visible = (results ?? []).filter((area) => {
|
|
4482
|
+
if (!onlyCityCodes || onlyCityCodes.length === 0) return true;
|
|
4483
|
+
if (area.kind === "city") return true;
|
|
4484
|
+
return !area.cityCode || onlyCityCodes.includes(area.cityCode);
|
|
4485
|
+
});
|
|
4486
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4487
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
4488
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
|
|
4489
|
+
/* @__PURE__ */ jsx(
|
|
4490
|
+
"input",
|
|
4491
|
+
{
|
|
4492
|
+
value: query,
|
|
4493
|
+
onChange: (e) => setQuery(e.target.value),
|
|
4494
|
+
placeholder,
|
|
4495
|
+
className: "w-full rounded-lg border border-border bg-background py-2 pl-9 pr-9 text-sm"
|
|
4496
|
+
}
|
|
4497
|
+
),
|
|
4498
|
+
loading && /* @__PURE__ */ jsx(Loader2, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
|
|
4499
|
+
] }),
|
|
4500
|
+
results !== null && /* @__PURE__ */ jsxs("ul", { className: "mt-2 max-h-72 divide-y divide-border overflow-y-auto rounded-lg border border-border", children: [
|
|
4501
|
+
visible.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-3 py-6 text-center text-xs text-muted-foreground", children: unavailable ? "A busca de regi\xF5es ainda n\xE3o est\xE1 dispon\xEDvel neste banco." : emptyHint }),
|
|
4502
|
+
visible.map((area) => {
|
|
4503
|
+
const key = areaKeyOf(area);
|
|
4504
|
+
const already = area.kind !== "group" && isInDeliveryDraft({
|
|
4505
|
+
matchKind: area.kind === "city" ? "city" : "district",
|
|
4506
|
+
cityCode: area.cityCode ?? null,
|
|
4507
|
+
districtKey: area.districtKey ?? null
|
|
4508
|
+
});
|
|
4509
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
4510
|
+
"button",
|
|
4511
|
+
{
|
|
4512
|
+
type: "button",
|
|
4513
|
+
onClick: () => add(area),
|
|
4514
|
+
disabled: already,
|
|
4515
|
+
className: "flex w-full items-center gap-3 px-3 py-2.5 text-left transition hover:bg-muted/50 disabled:opacity-50",
|
|
4516
|
+
children: [
|
|
4517
|
+
area.kind === "city" ? /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 shrink-0 text-muted-foreground" }) : /* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
4518
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
4519
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium", children: area.label }),
|
|
4520
|
+
/* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
|
|
4521
|
+
areaMeta(area),
|
|
4522
|
+
area.kind === "district" && !area.knownPostalCodes && " \xB7 ainda sem CEP visto"
|
|
4523
|
+
] })
|
|
4524
|
+
] }),
|
|
4525
|
+
justAdded === key ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 shrink-0 text-emerald-600" }) : already ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "j\xE1 escolhido" }) : /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 shrink-0 text-muted-foreground" })
|
|
4526
|
+
]
|
|
4527
|
+
}
|
|
4528
|
+
) }, key);
|
|
4529
|
+
})
|
|
4530
|
+
] })
|
|
4531
|
+
] });
|
|
4532
|
+
}
|
|
4533
|
+
function DeliveryAreaPicker({ provider, onAskAssistant }) {
|
|
4534
|
+
const draft2 = useDeliveryDraft();
|
|
4535
|
+
const covered = draft2.rules.filter((r) => r.outcome !== "refuse");
|
|
4536
|
+
const coveredCities = covered.filter((r) => r.matchKind === "city");
|
|
4537
|
+
const exceptions = draft2.rules.filter((r) => r.outcome === "refuse");
|
|
4538
|
+
const cityCodes = covered.map((r) => r.cityCode).filter(Boolean);
|
|
4539
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card px-5 py-4 shadow-sm", children: [
|
|
4540
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
|
|
4541
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4542
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Onde voc\xEA entrega?" }),
|
|
4543
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: 'Digite uma cidade, um bairro ou uma regi\xE3o \u2014 "Rio de Janeiro", "Ipanema", "Zona Sul". Se voc\xEA atende a cidade toda, uma linha s\xF3 resolve: escolha a cidade.' })
|
|
4544
|
+
] }),
|
|
4545
|
+
onAskAssistant && /* @__PURE__ */ jsxs(
|
|
4546
|
+
"button",
|
|
4547
|
+
{
|
|
4548
|
+
type: "button",
|
|
4549
|
+
onClick: onAskAssistant,
|
|
4550
|
+
className: "inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium hover:bg-muted",
|
|
4551
|
+
children: [
|
|
4552
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-primary" }),
|
|
4553
|
+
"Prefiro contar por escrito"
|
|
4554
|
+
]
|
|
4555
|
+
}
|
|
4556
|
+
)
|
|
4557
|
+
] }),
|
|
4558
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
4559
|
+
AreaSearch,
|
|
4560
|
+
{
|
|
4561
|
+
provider,
|
|
4562
|
+
outcome: "deliver",
|
|
4563
|
+
placeholder: "Cidade, bairro ou regi\xE3o\u2026",
|
|
4564
|
+
emptyHint: "Nada com esse nome. Tente a cidade, ou conte por escrito ao assistente."
|
|
4565
|
+
}
|
|
4566
|
+
) }),
|
|
4567
|
+
covered.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-5 border-t border-border pt-4", children: [
|
|
4568
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4569
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-4 w-4 text-destructive" }),
|
|
4570
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "E onde voc\xEA n\xE3o entrega?" })
|
|
4571
|
+
] }),
|
|
4572
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: coveredCities.length > 0 ? `Recortes dentro do que voc\xEA j\xE1 cobre \u2014 comunidade, bairro de risco, regi\xE3o distante. A exce\xE7\xE3o vence a \xE1rea maior, ent\xE3o ${coveredCities[0].label} continua valendo em todo o resto.` : "Regi\xF5es que voc\xEA quer recusar mesmo estando perto do que j\xE1 cobre. A exce\xE7\xE3o vence a \xE1rea maior." }),
|
|
4573
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
4574
|
+
AreaSearch,
|
|
4575
|
+
{
|
|
4576
|
+
provider,
|
|
4577
|
+
outcome: "refuse",
|
|
4578
|
+
placeholder: "Bairro ou regi\xE3o que voc\xEA recusa\u2026",
|
|
4579
|
+
onlyCityCodes: cityCodes,
|
|
4580
|
+
emptyHint: /* @__PURE__ */ jsx(Fragment, { children: "Nada com esse nome. Comunidade normalmente n\xE3o tem CEP pr\xF3prio \u2014 divide o do bairro vizinho. Escolha o bairro que a cont\xE9m e marque como \u201Cconfirmo antes\u201D na tabela." })
|
|
4581
|
+
}
|
|
4582
|
+
) })
|
|
4583
|
+
] }),
|
|
4584
|
+
draft2.rules.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-3 text-xs text-muted-foreground", children: [
|
|
4585
|
+
covered.length,
|
|
4586
|
+
" ",
|
|
4587
|
+
covered.length === 1 ? "regi\xE3o" : "regi\xF5es",
|
|
4588
|
+
" onde voc\xEA entrega",
|
|
4589
|
+
exceptions.length > 0 && `, ${exceptions.length} ${exceptions.length === 1 ? "exce\xE7\xE3o" : "exce\xE7\xF5es"}`,
|
|
4590
|
+
" ",
|
|
4591
|
+
"\u2014 confira o pre\xE7o na tabela abaixo e salve."
|
|
4592
|
+
] })
|
|
4593
|
+
] });
|
|
4594
|
+
}
|
|
4595
|
+
var OUTCOME_LABEL = {
|
|
4596
|
+
deliver: "Entrego",
|
|
4597
|
+
review: "Confirmo antes",
|
|
4598
|
+
refuse: "N\xE3o entrego"
|
|
4599
|
+
};
|
|
4600
|
+
var OUTCOME_TONE = {
|
|
4601
|
+
deliver: "text-foreground",
|
|
4602
|
+
review: "text-amber-600 dark:text-amber-500",
|
|
4603
|
+
refuse: "text-destructive"
|
|
4604
|
+
};
|
|
4605
|
+
function parseMoney(raw) {
|
|
4606
|
+
const cleaned = raw.replace(/[^\d,.-]/g, "").replace(",", ".");
|
|
4607
|
+
if (!cleaned.trim()) return null;
|
|
4608
|
+
const n = Number(cleaned);
|
|
4609
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
|
|
4610
|
+
}
|
|
4611
|
+
var money2 = (v) => v == null ? "" : String(v).replace(".", ",");
|
|
4612
|
+
function ruleArea(rule) {
|
|
4613
|
+
if (rule.matchKind === "postal_range") {
|
|
4614
|
+
const fmt = (c) => c ? `${c.slice(0, 5)}-${c.slice(5)}` : "?";
|
|
4615
|
+
return `${fmt(rule.postalFrom)} \u2192 ${fmt(rule.postalTo)}`;
|
|
4616
|
+
}
|
|
4617
|
+
return rule.matchKind === "city" ? "Munic\xEDpio" : "Bairro";
|
|
4618
|
+
}
|
|
4619
|
+
function DeliveryRulesDraft({ provider, onSaved }) {
|
|
4620
|
+
const draft2 = useDeliveryDraft();
|
|
4621
|
+
const [saving, setSaving] = React13__default.useState(false);
|
|
4622
|
+
const [error, setError] = React13__default.useState(null);
|
|
4623
|
+
const [bulkRate, setBulkRate] = React13__default.useState("");
|
|
4624
|
+
const [bulkFree, setBulkFree] = React13__default.useState("");
|
|
4625
|
+
if (draft2.rules.length === 0 && draft2.gaps.length === 0) return null;
|
|
4626
|
+
const refusing = draft2.rules.filter((r) => r.outcome === "refuse").length;
|
|
4627
|
+
const reviewing = draft2.rules.filter((r) => r.outcome === "review").length;
|
|
4628
|
+
const delivering = draft2.rules.length - refusing - reviewing;
|
|
4629
|
+
const save = async () => {
|
|
4630
|
+
setSaving(true);
|
|
4631
|
+
setError(null);
|
|
4632
|
+
try {
|
|
4633
|
+
const shop = provider ? resolve(provider) : getShopProvider();
|
|
4634
|
+
if (typeof shop.createShippingZone !== "function") {
|
|
4635
|
+
throw new Error("Este banco ainda n\xE3o tem as regras de entrega.");
|
|
4636
|
+
}
|
|
4637
|
+
for (const rule of draft2.rules) {
|
|
4638
|
+
await shop.createShippingZone(zoneInputFrom(rule));
|
|
4639
|
+
}
|
|
4640
|
+
clearDeliveryDraft();
|
|
4641
|
+
onSaved();
|
|
4642
|
+
} catch (err) {
|
|
4643
|
+
setError(errorMessage(err));
|
|
4644
|
+
} finally {
|
|
4645
|
+
setSaving(false);
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-card border border-primary/40 bg-primary/5 shadow-sm", children: [
|
|
4649
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 border-b border-primary/20 px-5 py-4", children: [
|
|
4650
|
+
/* @__PURE__ */ jsx(ListChecks, { className: "h-4 w-4 text-primary" }),
|
|
4651
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
4652
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Regras a salvar" }),
|
|
4653
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
4654
|
+
delivering,
|
|
4655
|
+
" entrega",
|
|
4656
|
+
delivering === 1 ? "" : "s",
|
|
4657
|
+
reviewing > 0 && `, ${reviewing} a confirmar`,
|
|
4658
|
+
refusing > 0 && `, ${refusing} recusa${refusing === 1 ? "" : "s"}`,
|
|
4659
|
+
" ",
|
|
4660
|
+
"\u2014 confira e corrija antes de salvar. Nada foi gravado ainda."
|
|
4661
|
+
] })
|
|
4662
|
+
] }),
|
|
4663
|
+
/* @__PURE__ */ jsx(
|
|
4664
|
+
"button",
|
|
4665
|
+
{
|
|
4666
|
+
type: "button",
|
|
4667
|
+
onClick: clearDeliveryDraft,
|
|
4668
|
+
disabled: saving,
|
|
4669
|
+
className: "rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-muted",
|
|
4670
|
+
children: "Descartar"
|
|
4671
|
+
}
|
|
4672
|
+
),
|
|
4673
|
+
/* @__PURE__ */ jsxs(
|
|
4674
|
+
"button",
|
|
4675
|
+
{
|
|
4676
|
+
type: "button",
|
|
4677
|
+
onClick: save,
|
|
4678
|
+
disabled: saving || draft2.rules.length === 0,
|
|
4679
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-primary px-3.5 py-2 text-xs font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-50",
|
|
4680
|
+
children: [
|
|
4681
|
+
saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
4682
|
+
"Salvar ",
|
|
4683
|
+
draft2.rules.length,
|
|
4684
|
+
" regra",
|
|
4685
|
+
draft2.rules.length === 1 ? "" : "s"
|
|
4686
|
+
]
|
|
4687
|
+
}
|
|
4688
|
+
)
|
|
4689
|
+
] }),
|
|
4690
|
+
error && /* @__PURE__ */ jsx("p", { className: "px-5 pt-3 text-xs text-destructive", children: error }),
|
|
4691
|
+
delivering + reviewing > 1 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-primary/10 px-5 py-3", children: [
|
|
4692
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Mesmo pre\xE7o em todas:" }),
|
|
4693
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
4694
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "R$" }),
|
|
4695
|
+
/* @__PURE__ */ jsx(
|
|
4696
|
+
"input",
|
|
4697
|
+
{
|
|
4698
|
+
value: bulkRate,
|
|
4699
|
+
onChange: (e) => setBulkRate(e.target.value),
|
|
4700
|
+
placeholder: "15,00",
|
|
4701
|
+
"aria-label": "Frete para todas as regi\xF5es",
|
|
4702
|
+
className: "w-20 rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
4703
|
+
}
|
|
4704
|
+
)
|
|
4705
|
+
] }),
|
|
4706
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
4707
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "gr\xE1tis acima de R$" }),
|
|
4708
|
+
/* @__PURE__ */ jsx(
|
|
4709
|
+
"input",
|
|
4710
|
+
{
|
|
4711
|
+
value: bulkFree,
|
|
4712
|
+
onChange: (e) => setBulkFree(e.target.value),
|
|
4713
|
+
placeholder: "opcional",
|
|
4714
|
+
"aria-label": "Frete gr\xE1tis acima de, para todas as regi\xF5es",
|
|
4715
|
+
className: "w-24 rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
4716
|
+
}
|
|
4717
|
+
)
|
|
4718
|
+
] }),
|
|
4719
|
+
/* @__PURE__ */ jsx(
|
|
4720
|
+
"button",
|
|
4721
|
+
{
|
|
4722
|
+
type: "button",
|
|
4723
|
+
onClick: () => {
|
|
4724
|
+
setAllDeliveryDraftRates({
|
|
4725
|
+
rate: parseMoney(bulkRate),
|
|
4726
|
+
freeAbove: parseMoney(bulkFree)
|
|
4727
|
+
});
|
|
4728
|
+
setBulkRate("");
|
|
4729
|
+
setBulkFree("");
|
|
4730
|
+
},
|
|
4731
|
+
disabled: !bulkRate.trim() && !bulkFree.trim(),
|
|
4732
|
+
className: "rounded-md border border-border px-2.5 py-1 text-xs font-medium hover:bg-muted disabled:opacity-40",
|
|
4733
|
+
children: "Aplicar"
|
|
4734
|
+
}
|
|
4735
|
+
)
|
|
4736
|
+
] }),
|
|
4737
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
4738
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs text-muted-foreground", children: [
|
|
4739
|
+
/* @__PURE__ */ jsx("th", { className: "px-5 py-2.5 font-medium", children: "Regi\xE3o" }),
|
|
4740
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "O que fa\xE7o" }),
|
|
4741
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Frete" }),
|
|
4742
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Gr\xE1tis acima de" }),
|
|
4743
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Prazo (dias)" }),
|
|
4744
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2" })
|
|
4745
|
+
] }) }),
|
|
4746
|
+
/* @__PURE__ */ jsx("tbody", { children: draft2.rules.map((rule) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-primary/10", children: [
|
|
4747
|
+
/* @__PURE__ */ jsxs("td", { className: "px-5 py-2.5", children: [
|
|
4748
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: rule.label }),
|
|
4749
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: ruleArea(rule) }),
|
|
4750
|
+
rule.source && /* @__PURE__ */ jsxs("p", { className: "text-xs italic text-muted-foreground", children: [
|
|
4751
|
+
"\u201C",
|
|
4752
|
+
rule.source,
|
|
4753
|
+
"\u201D"
|
|
4754
|
+
] })
|
|
4755
|
+
] }),
|
|
4756
|
+
/* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsx(
|
|
4757
|
+
"select",
|
|
4758
|
+
{
|
|
4759
|
+
value: rule.outcome,
|
|
4760
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, { outcome: e.target.value }),
|
|
4761
|
+
className: `rounded-md border border-border bg-background px-2 py-1.5 text-xs ${OUTCOME_TONE[rule.outcome]}`,
|
|
4762
|
+
children: Object.keys(OUTCOME_LABEL).map((value) => /* @__PURE__ */ jsx("option", { value, children: OUTCOME_LABEL[value] }, value))
|
|
4763
|
+
}
|
|
4764
|
+
) }),
|
|
4765
|
+
rule.outcome === "refuse" ? /* @__PURE__ */ jsx("td", { className: "px-2 py-2.5 text-xs text-muted-foreground", colSpan: 3, children: "n\xE3o se cobra" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4766
|
+
/* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-border bg-background focus-within:ring-1 focus-within:ring-ring", children: [
|
|
4767
|
+
/* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: "R$" }),
|
|
4768
|
+
/* @__PURE__ */ jsx(
|
|
4769
|
+
"input",
|
|
4770
|
+
{
|
|
4771
|
+
value: money2(rule.rate),
|
|
4772
|
+
placeholder: "0,00",
|
|
4773
|
+
"aria-label": `Frete para ${rule.label}`,
|
|
4774
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, { rate: parseMoney(e.target.value) }),
|
|
4775
|
+
className: "w-16 bg-transparent px-1.5 py-1.5 text-xs outline-none"
|
|
4776
|
+
}
|
|
4777
|
+
)
|
|
4778
|
+
] }) }),
|
|
4779
|
+
/* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-border bg-background focus-within:ring-1 focus-within:ring-ring", children: [
|
|
4780
|
+
/* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: "R$" }),
|
|
4781
|
+
/* @__PURE__ */ jsx(
|
|
4782
|
+
"input",
|
|
4783
|
+
{
|
|
4784
|
+
value: money2(rule.freeAbove),
|
|
4785
|
+
placeholder: "nunca",
|
|
4786
|
+
"aria-label": `Frete gr\xE1tis acima de, em ${rule.label}`,
|
|
4787
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, { freeAbove: parseMoney(e.target.value) }),
|
|
4788
|
+
className: "w-20 bg-transparent px-1.5 py-1.5 text-xs outline-none"
|
|
4789
|
+
}
|
|
4790
|
+
)
|
|
4791
|
+
] }) }),
|
|
4792
|
+
/* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-md border border-border bg-background focus-within:ring-1 focus-within:ring-ring", children: [
|
|
4793
|
+
/* @__PURE__ */ jsx(
|
|
4794
|
+
"input",
|
|
4795
|
+
{
|
|
4796
|
+
value: rule.etaMinDays ?? "",
|
|
4797
|
+
placeholder: "0",
|
|
4798
|
+
inputMode: "numeric",
|
|
4799
|
+
"aria-label": `Prazo m\xEDnimo em ${rule.label}`,
|
|
4800
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, {
|
|
4801
|
+
etaMinDays: e.target.value ? Number(e.target.value) : null
|
|
4802
|
+
}),
|
|
4803
|
+
className: "w-10 bg-transparent py-1.5 pl-2 text-center text-xs outline-none"
|
|
4804
|
+
}
|
|
4805
|
+
),
|
|
4806
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "a" }),
|
|
4807
|
+
/* @__PURE__ */ jsx(
|
|
4808
|
+
"input",
|
|
4809
|
+
{
|
|
4810
|
+
value: rule.etaMaxDays ?? "",
|
|
4811
|
+
placeholder: "2",
|
|
4812
|
+
inputMode: "numeric",
|
|
4813
|
+
"aria-label": `Prazo m\xE1ximo em ${rule.label}`,
|
|
4814
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, {
|
|
4815
|
+
etaMaxDays: e.target.value ? Number(e.target.value) : null
|
|
4816
|
+
}),
|
|
4817
|
+
className: "w-10 bg-transparent py-1.5 pr-2 text-center text-xs outline-none"
|
|
4818
|
+
}
|
|
4819
|
+
)
|
|
4820
|
+
] }) })
|
|
4821
|
+
] }),
|
|
4822
|
+
/* @__PURE__ */ jsx("td", { className: "px-5 py-2.5 text-right", children: /* @__PURE__ */ jsx(
|
|
4823
|
+
"button",
|
|
4824
|
+
{
|
|
4825
|
+
type: "button",
|
|
4826
|
+
onClick: () => removeDeliveryDraftRule(rule.id),
|
|
4827
|
+
"aria-label": `Remover ${rule.label}`,
|
|
4828
|
+
className: "rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-destructive",
|
|
4829
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
4830
|
+
}
|
|
4831
|
+
) })
|
|
4832
|
+
] }, rule.id)) })
|
|
4833
|
+
] }) }),
|
|
4834
|
+
draft2.gaps.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t border-primary/20 px-5 py-3.5", children: [
|
|
4835
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "O que ficou de fora \u2014 e por qu\xEA" }),
|
|
4836
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-1 space-y-1", children: draft2.gaps.map((gap) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2 text-xs text-muted-foreground", children: [
|
|
4837
|
+
/* @__PURE__ */ jsx(X, { className: "mt-0.5 h-3 w-3 shrink-0 text-destructive" }),
|
|
4838
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
4839
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: gap.query }),
|
|
4840
|
+
" \u2014 ",
|
|
4841
|
+
gap.reason
|
|
4842
|
+
] })
|
|
4843
|
+
] }, gap.query)) })
|
|
4844
|
+
] })
|
|
4845
|
+
] });
|
|
4846
|
+
}
|
|
4847
|
+
function parseMoney2(raw) {
|
|
4848
|
+
const cleaned = raw.replace(/[^\d,.-]/g, "").replace(",", ".");
|
|
4849
|
+
if (!cleaned.trim()) return null;
|
|
4850
|
+
const n = Number(cleaned);
|
|
4851
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
|
|
4852
|
+
}
|
|
4853
|
+
var formatMoney = (v) => v == null ? "" : String(v).replace(".", ",");
|
|
4854
|
+
var FREIGHT_SOURCE = [
|
|
4855
|
+
{
|
|
4856
|
+
value: "own",
|
|
4857
|
+
label: "Eu mesmo calculo e despacho",
|
|
4858
|
+
description: "O frete sai do pre\xE7o base e das suas faixas de CEP, e voc\xEA despacha como quiser \u2014 carro pr\xF3prio, motoboy, ag\xEAncia. Nenhum gateway \xE9 consultado, mesmo que exista um conectado.",
|
|
4859
|
+
Icon: Car
|
|
4860
|
+
},
|
|
4861
|
+
{
|
|
4862
|
+
value: "gateway",
|
|
4863
|
+
label: "Perguntar a um gateway de frete",
|
|
4864
|
+
description: "O pre\xE7o e o prazo v\xEAm da transportadora, calculados pelo CEP do comprador. Quando o gateway n\xE3o responde a tempo, o checkout usa suas faixas \u2014 a compra nunca fica sem pre\xE7o.",
|
|
4865
|
+
Icon: Truck
|
|
4866
|
+
}
|
|
4867
|
+
];
|
|
4868
|
+
function ShippingSettings({ provider }) {
|
|
4869
|
+
const [registryId, setRegistryId] = React13__default.useState(shippingRegistryIdFromHash);
|
|
4870
|
+
React13__default.useEffect(() => {
|
|
4871
|
+
const handler = () => setRegistryId(shippingRegistryIdFromHash());
|
|
4872
|
+
window.addEventListener("hashchange", handler);
|
|
4873
|
+
return () => window.removeEventListener("hashchange", handler);
|
|
4874
|
+
}, []);
|
|
4875
|
+
const shop = React13__default.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
|
|
4876
|
+
const registries = React13__default.useMemo(
|
|
4877
|
+
() => buildShopRegistries(provider ?? getShopProvider),
|
|
4878
|
+
[provider]
|
|
4879
|
+
);
|
|
4880
|
+
const storeSettings = useStoreSettings(provider);
|
|
4881
|
+
const supported = typeof shop.getShippingSettings === "function" && typeof shop.updateShippingSettings === "function";
|
|
4882
|
+
const [row, setRow] = React13__default.useState(null);
|
|
4883
|
+
const [zones, setZones] = React13__default.useState(null);
|
|
4884
|
+
const [locations, setLocations] = React13__default.useState(null);
|
|
4885
|
+
const [loading, setLoading] = React13__default.useState(supported);
|
|
4886
|
+
const [saving, setSaving] = React13__default.useState(false);
|
|
4887
|
+
const [error, setError] = React13__default.useState(null);
|
|
4888
|
+
const [loadError, setLoadError] = React13__default.useState(null);
|
|
4889
|
+
const [flatDraft, setFlatDraft] = React13__default.useState("");
|
|
4890
|
+
const [freeDraft, setFreeDraft] = React13__default.useState("");
|
|
4891
|
+
const [flatBad, setFlatBad] = React13__default.useState(false);
|
|
4892
|
+
const [freeBad, setFreeBad] = React13__default.useState(false);
|
|
4893
|
+
const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
|
|
4894
|
+
const [selected, setSelected] = React13__default.useState(null);
|
|
4895
|
+
const panelAvailable = useConnectorsPanelAvailable();
|
|
4896
|
+
const openJourney = React13__default.useCallback(
|
|
4897
|
+
(connector) => {
|
|
4898
|
+
if (panelAvailable) openConnectorsPanel(connector);
|
|
4899
|
+
else setSelected(connector);
|
|
4900
|
+
},
|
|
4901
|
+
[panelAvailable]
|
|
4902
|
+
);
|
|
4903
|
+
const carriers = React13__default.useMemo(
|
|
4904
|
+
() => (connectors ?? []).filter((c) => c.category === "logistics" || c.category === "shipping"),
|
|
4905
|
+
[connectors]
|
|
4906
|
+
);
|
|
3776
4907
|
const carriersConnected = carriers.some((c) => c.state !== "ready");
|
|
3777
|
-
|
|
4908
|
+
const [zonesEpoch, setZonesEpoch] = React13__default.useState(0);
|
|
4909
|
+
const reloadZones = React13__default.useCallback(() => setZonesEpoch((n) => n + 1), []);
|
|
4910
|
+
React13__default.useEffect(() => {
|
|
3778
4911
|
let cancelled = false;
|
|
3779
4912
|
if (supported) {
|
|
3780
4913
|
setLoading(true);
|
|
@@ -3802,8 +4935,8 @@ function ShippingSettings({ provider }) {
|
|
|
3802
4935
|
return () => {
|
|
3803
4936
|
cancelled = true;
|
|
3804
4937
|
};
|
|
3805
|
-
}, [shop, supported]);
|
|
3806
|
-
const save =
|
|
4938
|
+
}, [shop, supported, zonesEpoch]);
|
|
4939
|
+
const save = React13__default.useCallback(
|
|
3807
4940
|
(patch) => {
|
|
3808
4941
|
if (!supported) return;
|
|
3809
4942
|
setSaving(true);
|
|
@@ -3813,7 +4946,7 @@ function ShippingSettings({ provider }) {
|
|
|
3813
4946
|
[shop, supported]
|
|
3814
4947
|
);
|
|
3815
4948
|
const commitFlat = () => {
|
|
3816
|
-
const parsed =
|
|
4949
|
+
const parsed = parseMoney2(flatDraft);
|
|
3817
4950
|
const next = flatDraft.trim() === "" ? 0 : parsed;
|
|
3818
4951
|
if (next == null) {
|
|
3819
4952
|
setFlatBad(true);
|
|
@@ -3829,7 +4962,7 @@ function ShippingSettings({ provider }) {
|
|
|
3829
4962
|
if (row?.freeAbove != null) save({ freeAbove: null });
|
|
3830
4963
|
return;
|
|
3831
4964
|
}
|
|
3832
|
-
const parsed =
|
|
4965
|
+
const parsed = parseMoney2(freeDraft);
|
|
3833
4966
|
if (parsed == null) {
|
|
3834
4967
|
setFreeBad(true);
|
|
3835
4968
|
return;
|
|
@@ -3838,8 +4971,10 @@ function ShippingSettings({ provider }) {
|
|
|
3838
4971
|
setFreeDraft(formatMoney(parsed));
|
|
3839
4972
|
if (parsed !== row?.freeAbove) save({ freeAbove: parsed });
|
|
3840
4973
|
};
|
|
3841
|
-
const
|
|
3842
|
-
const
|
|
4974
|
+
const outcomeOf2 = (z) => z.outcome ?? (z.excluded ? "refuse" : "deliver");
|
|
4975
|
+
const activeZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "deliver");
|
|
4976
|
+
const reviewZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "review");
|
|
4977
|
+
const excludedZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "refuse");
|
|
3843
4978
|
const usesGateway = storeSettings.value.carrierQuotes !== false;
|
|
3844
4979
|
const dispatching = (locations ?? []).filter((l) => l.status === "active" && l.shipsOnline !== false);
|
|
3845
4980
|
const originCep = dispatching.find((l) => l.postalCode)?.postalCode ?? null;
|
|
@@ -3937,11 +5072,21 @@ function ShippingSettings({ provider }) {
|
|
|
3937
5072
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Nenhum local tem CEP de origem. Sem ele a transportadora n\xE3o cota, e o checkout cai nas suas zonas \u2014 preencha o CEP do local padr\xE3o." })
|
|
3938
5073
|
] })
|
|
3939
5074
|
] }),
|
|
3940
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5075
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3941
5076
|
/* @__PURE__ */ jsxs("div", { className: "px-1 pb-1 pt-4", children: [
|
|
3942
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "
|
|
3943
|
-
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "
|
|
5077
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Onde voc\xEA entrega" }),
|
|
5078
|
+
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "As regi\xF5es onde a loja entrega, com pre\xE7o, prazo e frete gr\xE1tis pr\xF3prios. Um endere\xE7o fora de todas elas cai no pre\xE7o base acima; se a loja n\xE3o tiver pre\xE7o base nem regi\xE3o que o cubra, o checkout diz que n\xE3o entrega ali. Uma regi\xE3o tamb\xE9m pode ser uma recusa \u2014 \xABn\xE3o entrego aqui\xBB \u2014 e ela vence qualquer outra e qualquer gateway." })
|
|
3944
5079
|
] }),
|
|
5080
|
+
/* @__PURE__ */ jsx(
|
|
5081
|
+
DeliveryAgentSurface,
|
|
5082
|
+
{
|
|
5083
|
+
provider: () => shop,
|
|
5084
|
+
zones: zones ?? [],
|
|
5085
|
+
onSaved: reloadZones
|
|
5086
|
+
}
|
|
5087
|
+
),
|
|
5088
|
+
/* @__PURE__ */ jsx(DeliveryAreaPicker, { provider }),
|
|
5089
|
+
/* @__PURE__ */ jsx(DeliveryRulesDraft, { provider, onSaved: reloadZones }),
|
|
3945
5090
|
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
|
|
3946
5091
|
"button",
|
|
3947
5092
|
{
|
|
@@ -3953,10 +5098,15 @@ function ShippingSettings({ provider }) {
|
|
|
3953
5098
|
children: [
|
|
3954
5099
|
/* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
3955
5100
|
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
3956
|
-
/* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: zones == null ? "Carregando
|
|
5101
|
+
/* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: zones == null ? "Carregando regi\xF5es\u2026" : activeZones.length === 0 ? "Nenhuma regi\xE3o salva ainda" : `${activeZones.length} ${activeZones.length === 1 ? "regi\xE3o onde entrego" : "regi\xF5es onde entrego"}` }),
|
|
3957
5102
|
/* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: [
|
|
3958
|
-
activeZones.length === 0 ? "Sem
|
|
5103
|
+
activeZones.length === 0 ? "Sem regi\xE3o salva, toda entrega usa o pre\xE7o base. Escolha as suas na busca acima." : activeZones.slice(0, 3).map((z) => z.name).join(" \xB7 "),
|
|
3959
5104
|
activeZones.length > 3 && ` e ${activeZones.length - 3} mais`,
|
|
5105
|
+
reviewZones.length > 0 && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-amber-700 dark:text-amber-500", children: [
|
|
5106
|
+
reviewZones.length === 1 ? "1 regi\xE3o a confirmar antes de sair" : `${reviewZones.length} regi\xF5es a confirmar antes de sair`,
|
|
5107
|
+
" \u2014 ",
|
|
5108
|
+
reviewZones.slice(0, 2).map((z) => z.name).join(" \xB7 ")
|
|
5109
|
+
] }),
|
|
3960
5110
|
excludedZones.length > 0 && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-red-700 dark:text-red-400", children: [
|
|
3961
5111
|
excludedZones.length === 1 ? "1 regi\xE3o recusada" : `${excludedZones.length} regi\xF5es recusadas`,
|
|
3962
5112
|
" \u2014 ",
|
|
@@ -4120,7 +5270,7 @@ var melhorEnvioConnectorDef = {
|
|
|
4120
5270
|
direction: "outbound",
|
|
4121
5271
|
docsUrl: "https://docs.melhorenvio.com.br",
|
|
4122
5272
|
// A quote WRITES NOTHING — `createShippingQuoter` says so in as many words,
|
|
4123
|
-
// and 029_connections.sql is why: one row per quote would make
|
|
5273
|
+
// and 029_connections.sql is why: one row per quote would make sync_runs
|
|
4124
5274
|
// the largest table in the pool within weeks. So `order` is read-only here,
|
|
4125
5275
|
// and the only write is the label, which is an effect with an idempotency key
|
|
4126
5276
|
// (`createLabelBuyer`) because a label bought twice is money spent twice.
|
|
@@ -4191,7 +5341,7 @@ var melhorEnvioConnectorDef = {
|
|
|
4191
5341
|
),
|
|
4192
5342
|
...connectionSettings(store),
|
|
4193
5343
|
// This used to say the platform's redirect route writes `active = true`. It
|
|
4194
|
-
// does not, and says so itself: "It does not touch `
|
|
5344
|
+
// does not, and says so itself: "It does not touch `connections` in the
|
|
4195
5345
|
// tenant's pool. Per FAY-1388 ... the app writes its own." Bling shipped with
|
|
4196
5346
|
// the same belief and looped — authorize, come back, still "not connected",
|
|
4197
5347
|
// click again, forever.
|
|
@@ -4342,7 +5492,7 @@ var appmaxConnectorDef = {
|
|
|
4342
5492
|
}
|
|
4343
5493
|
],
|
|
4344
5494
|
// Read from the row the list already batched, so a screen with N connectors
|
|
4345
|
-
// costs one
|
|
5495
|
+
// costs one connections request rather than N.
|
|
4346
5496
|
...connectionStatus(
|
|
4347
5497
|
APPMAX_CONNECTOR_ID,
|
|
4348
5498
|
(connection) => ({
|
|
@@ -4618,7 +5768,7 @@ var abacatePayConnectorDef = {
|
|
|
4618
5768
|
}
|
|
4619
5769
|
],
|
|
4620
5770
|
// Read from the row the list already batched, so a screen with N connectors
|
|
4621
|
-
// costs one
|
|
5771
|
+
// costs one connections request rather than N.
|
|
4622
5772
|
...connectionStatus(
|
|
4623
5773
|
ABACATEPAY_CONNECTOR_ID,
|
|
4624
5774
|
(connection) => ({
|
|
@@ -4763,7 +5913,7 @@ var stripeConnectConnectorDef = {
|
|
|
4763
5913
|
// Reached with no values when the browser returns marked `connected`. It is
|
|
4764
5914
|
// required even though nothing is collected here: the platform callback stores
|
|
4765
5915
|
// the credential but deliberately never touches the pool, so this is the only
|
|
4766
|
-
// thing that writes the tenant's `
|
|
5916
|
+
// thing that writes the tenant's `connections` row.
|
|
4767
5917
|
saveConnection: async () => {
|
|
4768
5918
|
await store4.save({ active: true });
|
|
4769
5919
|
},
|
|
@@ -5037,7 +6187,7 @@ var vindiConnectorDef = {
|
|
|
5037
6187
|
}
|
|
5038
6188
|
],
|
|
5039
6189
|
// Read from the row the list already batched, so a screen with N connectors
|
|
5040
|
-
// costs one
|
|
6190
|
+
// costs one connections request rather than N.
|
|
5041
6191
|
...connectionStatus(
|
|
5042
6192
|
VINDI_CONNECTOR_ID,
|
|
5043
6193
|
(connection) => ({
|
|
@@ -5068,39 +6218,24 @@ var vindiConnectorDef = {
|
|
|
5068
6218
|
};
|
|
5069
6219
|
var MERCADOPAGO_CONNECTOR_ID = "mercadopago";
|
|
5070
6220
|
var MERCADOPAGO_LATENCY_BUDGET_MS = 6e3;
|
|
5071
|
-
var
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
type: "password",
|
|
5076
|
-
secret: true,
|
|
5077
|
-
placeholder: "APP_USR-\u2026 (Suas integra\xE7\xF5es \u2192 sua aplica\xE7\xE3o \u2192 Credenciais)"
|
|
5078
|
-
},
|
|
5079
|
-
{
|
|
5080
|
-
key: "publicKey",
|
|
5081
|
-
label: "Public key",
|
|
5082
|
-
// Not masked: it travels to the browser anyway, and a merchant who cannot
|
|
5083
|
-
// see it cannot tell the test key from the production one at a glance.
|
|
5084
|
-
type: "text",
|
|
5085
|
-
secret: true,
|
|
5086
|
-
placeholder: "APP_USR-\u2026"
|
|
5087
|
-
}
|
|
5088
|
-
];
|
|
5089
|
-
var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID, {
|
|
5090
|
-
fields: MERCADOPAGO_FIELDS
|
|
5091
|
-
});
|
|
5092
|
-
function connectionDetail2(settings) {
|
|
5093
|
-
const key = String(settings?.publicKey ?? "").trim();
|
|
5094
|
-
if (!key) return "Pix e cart\xE3o";
|
|
5095
|
-
return /^TEST-/i.test(key) ? "Sandbox \xB7 Pix e cart\xE3o" : "Produ\xE7\xE3o \xB7 Pix e cart\xE3o";
|
|
6221
|
+
var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID);
|
|
6222
|
+
function connectionDetail2(connection) {
|
|
6223
|
+
const account = String(connection?.displayName ?? "").trim();
|
|
6224
|
+
return account ? `${account} \xB7 Pix e cart\xE3o` : "Pix e cart\xE3o";
|
|
5096
6225
|
}
|
|
5097
6226
|
function MercadoPagoExtraPanel() {
|
|
5098
6227
|
const [connected, setConnected] = useState(false);
|
|
6228
|
+
const [account, setAccount] = useState({ name: null, id: null });
|
|
5099
6229
|
const [loaded, setLoaded] = useState(false);
|
|
5100
6230
|
useEffect(() => {
|
|
5101
6231
|
let cancelled = false;
|
|
5102
6232
|
void store6.get().then((c) => {
|
|
5103
|
-
if (
|
|
6233
|
+
if (cancelled) return;
|
|
6234
|
+
setConnected(!!c?.active);
|
|
6235
|
+
setAccount({
|
|
6236
|
+
name: String(c?.displayName ?? "").trim() || null,
|
|
6237
|
+
id: String(c?.providerAccountKey ?? "").trim() || null
|
|
6238
|
+
});
|
|
5104
6239
|
}).catch(() => {
|
|
5105
6240
|
}).finally(() => {
|
|
5106
6241
|
if (!cancelled) setLoaded(true);
|
|
@@ -5111,6 +6246,29 @@ function MercadoPagoExtraPanel() {
|
|
|
5111
6246
|
}, []);
|
|
5112
6247
|
if (!loaded) return null;
|
|
5113
6248
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
|
|
6249
|
+
connected && (account.name || account.id) && /* Named before anything else is explained. "Conectado" is not an answer
|
|
6250
|
+
to "connected to WHAT" — and a store whose money is going to the
|
|
6251
|
+
wrong Mercado Pago account looks identical to one whose money is not. */
|
|
6252
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/40 p-3", children: [
|
|
6253
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Conta conectada" }),
|
|
6254
|
+
account.name && /* @__PURE__ */ jsx("p", { className: "mt-1 break-words", children: account.name }),
|
|
6255
|
+
account.id && /* @__PURE__ */ jsxs("p", { className: "mt-0.5", children: [
|
|
6256
|
+
"ID no Mercado Pago: ",
|
|
6257
|
+
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: account.id })
|
|
6258
|
+
] }),
|
|
6259
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
|
|
6260
|
+
"N\xE3o \xE9 esta a conta? Clique em ",
|
|
6261
|
+
/* @__PURE__ */ jsx("strong", { children: "Desconectar" }),
|
|
6262
|
+
" e conecte de novo, logado na conta certa do Mercado Pago."
|
|
6263
|
+
] })
|
|
6264
|
+
] }),
|
|
6265
|
+
connected && !account.name && !account.id && /* An older connection, made before the account was recorded. Saying so
|
|
6266
|
+
beats showing nothing and letting it read as "no account". */
|
|
6267
|
+
/* @__PURE__ */ jsxs("p", { className: "rounded-md border border-dashed p-3", children: [
|
|
6268
|
+
"Esta conex\xE3o foi feita antes de a loja passar a guardar qual conta foi autorizada. Para ver o nome da conta aqui, clique em ",
|
|
6269
|
+
/* @__PURE__ */ jsx("strong", { children: "Desconectar" }),
|
|
6270
|
+
" e conecte novamente."
|
|
6271
|
+
] }),
|
|
5114
6272
|
connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5115
6273
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
5116
6274
|
"Pix e cart\xE3o acontecem ",
|
|
@@ -5120,32 +6278,35 @@ function MercadoPagoExtraPanel() {
|
|
|
5120
6278
|
/* @__PURE__ */ jsx("p", { children: "O n\xFAmero do cart\xE3o n\xE3o passa pelo seu site: ele \xE9 digitado dentro de um formul\xE1rio do pr\xF3prio Mercado Pago e vira um c\xF3digo de uso \xFAnico antes de chegar at\xE9 n\xF3s. \xC9 por isso que d\xE1 para receber cart\xE3o na sua p\xE1gina sem a auditoria que isso normalmente exige." }),
|
|
5121
6279
|
/* @__PURE__ */ jsx("p", { children: 'O valor cobrado \xE9 sempre recalculado aqui no servidor a partir do pedido. Pre\xE7o alterado no navegador n\xE3o \xE9 aceito, e clicar duas vezes em "pagar" gera uma cobran\xE7a s\xF3.' }),
|
|
5122
6280
|
/* @__PURE__ */ jsx("p", { children: "O Pix vale por 30 minutos. Se o cliente fechar a aba depois de pagar, o pedido aparece aqui do mesmo jeito \u2014 o Mercado Pago avisa a loja assim que o dinheiro cai." })
|
|
5123
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
/* @__PURE__ */
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
/* @__PURE__ */
|
|
5135
|
-
|
|
6281
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6282
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
6283
|
+
"Clique em ",
|
|
6284
|
+
/* @__PURE__ */ jsx("strong", { children: "Conectar" }),
|
|
6285
|
+
" e voc\xEA vai para o Mercado Pago autorizar a Fayz. \xC9 na tela deles, com o seu login \u2014 nenhuma senha e nenhuma chave passam por aqui."
|
|
6286
|
+
] }),
|
|
6287
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
6288
|
+
"O dinheiro continua caindo na ",
|
|
6289
|
+
/* @__PURE__ */ jsx("strong", { children: "sua" }),
|
|
6290
|
+
" conta do Mercado Pago. A autoriza\xE7\xE3o s\xF3 permite abrir cobran\xE7as em nome da sua loja, e voc\xEA pode cancel\xE1-la quando quiser no painel do Mercado Pago."
|
|
6291
|
+
] }),
|
|
6292
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
6293
|
+
"Use uma conta ",
|
|
6294
|
+
/* @__PURE__ */ jsx("strong", { children: "PJ" }),
|
|
6295
|
+
", com o CNPJ da loja. Conta pessoa f\xEDsica costuma vir com bandeiras de cart\xE3o bloqueadas at\xE9 a verifica\xE7\xE3o terminar \u2014 e o bloqueio aparece como uma compra recusada, n\xE3o como um aviso."
|
|
6296
|
+
] })
|
|
5136
6297
|
] }),
|
|
5137
6298
|
/* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed p-3", children: [
|
|
5138
6299
|
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Para testar sem cobrar de verdade" }),
|
|
5139
6300
|
/* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
|
|
5140
|
-
"
|
|
6301
|
+
"O teste \xE9 feito com ",
|
|
6302
|
+
/* @__PURE__ */ jsx("strong", { children: "usu\xE1rios de teste" }),
|
|
6303
|
+
": crie um vendedor e um comprador em Suas integra\xE7\xF5es \u2192 Contas de teste, e autorize aqui logado como o vendedor de teste. N\xE3o \xE9 dinheiro real, e \xE9 assim que o pr\xF3prio Mercado Pago recomenda. Credenciais que come\xE7am com",
|
|
6304
|
+
" ",
|
|
5141
6305
|
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: "TEST-" }),
|
|
5142
6306
|
" ",
|
|
5143
6307
|
/* @__PURE__ */ jsx("strong", { children: "n\xE3o funcionam" }),
|
|
5144
|
-
"
|
|
5145
|
-
|
|
5146
|
-
": crie um vendedor e um comprador em Suas integra\xE7\xF5es \u2192 Contas de teste, e use as chaves de ",
|
|
5147
|
-
/* @__PURE__ */ jsx("em", { children: "produ\xE7\xE3o" }),
|
|
5148
|
-
" do vendedor de teste. N\xE3o \xE9 dinheiro real, e \xE9 assim que o pr\xF3prio Mercado Pago recomenda."
|
|
6308
|
+
" ",
|
|
6309
|
+
"nesta API \u2014 o usu\xE1rio de teste usa as chaves de produ\xE7\xE3o dele."
|
|
5149
6310
|
] })
|
|
5150
6311
|
] })
|
|
5151
6312
|
] });
|
|
@@ -5163,15 +6324,25 @@ var mercadoPagoConnectorDef = {
|
|
|
5163
6324
|
// its own domain, so a path would not.
|
|
5164
6325
|
logoUrl: "https://assets.fayz.ai/connectors/mercadopago.png",
|
|
5165
6326
|
category: "payments",
|
|
5166
|
-
//
|
|
5167
|
-
//
|
|
5168
|
-
//
|
|
5169
|
-
//
|
|
5170
|
-
//
|
|
5171
|
-
//
|
|
5172
|
-
//
|
|
5173
|
-
//
|
|
5174
|
-
|
|
6327
|
+
// A real consent screen, unlike Vindi's — whose flow only LOOKS like OAuth and
|
|
6328
|
+
// is a CODE copied out of an address bar. The seller authorizes Fayz once at
|
|
6329
|
+
// auth.mercadopago.com, the money keeps landing in their own account, and
|
|
6330
|
+
// `marketplace_fee` takes our cut on the way past.
|
|
6331
|
+
//
|
|
6332
|
+
// That fee is the reason this is not pasted keys: Mercado Pago's docs are
|
|
6333
|
+
// explicit that it is "exclusivo para integrações com OAuth", so a connection
|
|
6334
|
+
// made by copying credentials out of a panel can never earn. The pasted-key
|
|
6335
|
+
// version shipped first because it could be tested the same afternoon; this
|
|
6336
|
+
// is the one that can be a product.
|
|
6337
|
+
//
|
|
6338
|
+
// A store connected the old way keeps working and does not have to be
|
|
6339
|
+
// migrated by hand: the vault is keyed by slug + tenant, and the runtime reads
|
|
6340
|
+
// `accessToken` and `access_token` alike. Reconnecting through the button
|
|
6341
|
+
// upgrades it in place — same slug, same row, now with a refresh token.
|
|
6342
|
+
authKind: "oauth",
|
|
6343
|
+
// The whole of it. `provider` defaults to the connector id, and both are
|
|
6344
|
+
// `mercadopago` — the platform registry keys on that word.
|
|
6345
|
+
startOAuth: fayzConnectorOAuth(MERCADOPAGO_CONNECTOR_ID),
|
|
5175
6346
|
// The charge is opened in the buyer's request path; the notification and the
|
|
5176
6347
|
// refund are outbound sync work.
|
|
5177
6348
|
planes: ["request", "sync"],
|
|
@@ -5203,7 +6374,6 @@ var mercadoPagoConnectorDef = {
|
|
|
5203
6374
|
// confirmed without a payment is worse than a refused checkout. Same verdict
|
|
5204
6375
|
// as the other four gateways, opposite to the carrier next door.
|
|
5205
6376
|
onFailure: "block",
|
|
5206
|
-
fields: MERCADOPAGO_FIELDS,
|
|
5207
6377
|
invokedAt: [
|
|
5208
6378
|
{
|
|
5209
6379
|
key: "shop.checkout.session_open",
|
|
@@ -5219,7 +6389,7 @@ var mercadoPagoConnectorDef = {
|
|
|
5219
6389
|
}
|
|
5220
6390
|
],
|
|
5221
6391
|
// Read from the row the list already batched, so a screen with N connectors
|
|
5222
|
-
// costs one
|
|
6392
|
+
// costs one connections request rather than N.
|
|
5223
6393
|
//
|
|
5224
6394
|
// No `settingsStatus`: this connector has nothing to configure, so a
|
|
5225
6395
|
// connection either has its credential pair or does not. "Conectado, falta
|
|
@@ -5228,7 +6398,7 @@ var mercadoPagoConnectorDef = {
|
|
|
5228
6398
|
MERCADOPAGO_CONNECTOR_ID,
|
|
5229
6399
|
(connection) => ({
|
|
5230
6400
|
connected: !!connection?.active,
|
|
5231
|
-
detail: connectionDetail2(connection
|
|
6401
|
+
detail: connectionDetail2(connection),
|
|
5232
6402
|
plane: "request"
|
|
5233
6403
|
}),
|
|
5234
6404
|
{ store: () => store6 }
|
|
@@ -5243,190 +6413,16 @@ var mercadoPagoConnectorDef = {
|
|
|
5243
6413
|
// key alone cannot open an order. Testing belongs to the edge function that
|
|
5244
6414
|
// holds the credential.
|
|
5245
6415
|
saveConnection: async () => {
|
|
5246
|
-
await
|
|
6416
|
+
const account = await describeConnectorAccount({ connectorId: MERCADOPAGO_CONNECTOR_ID });
|
|
6417
|
+
await store6.save({
|
|
6418
|
+
active: true,
|
|
6419
|
+
...account?.displayName ? { displayName: account.displayName } : {},
|
|
6420
|
+
...account?.providerAccountKey ? { providerAccountKey: account.providerAccountKey } : {}
|
|
6421
|
+
});
|
|
5247
6422
|
},
|
|
5248
6423
|
disconnect: () => store6.disconnect(),
|
|
5249
6424
|
ExtraPanel: MercadoPagoExtraPanel
|
|
5250
6425
|
};
|
|
5251
|
-
var KIND_META = {
|
|
5252
|
-
online_store: { label: "Loja Online", icon: Globe, hint: "Seu storefront fayz \u2014 a vitrine p\xFAblica da loja.", placeholder: "https://minhaloja.com.br/" },
|
|
5253
|
-
whatsapp: { label: "WhatsApp", icon: MessageCircle, hint: "Link direto para o WhatsApp Business.", placeholder: "https://wa.me/5521999999999" },
|
|
5254
|
-
instagram: { label: "Instagram", icon: Camera, hint: "Perfil ou loja no Instagram.", placeholder: "https://instagram.com/sualoja" },
|
|
5255
|
-
tiktok_shop: { label: "TikTok Shop", icon: Music, hint: "Sua vitrine no TikTok Shop.", placeholder: "https://shop.tiktok.com/@sualoja" },
|
|
5256
|
-
marketplace: { label: "Marketplace", icon: ShoppingBag, hint: "Mercado Livre, Amazon, Shopee\u2026", placeholder: "https://\u2026" },
|
|
5257
|
-
physical: { label: "Loja F\xEDsica", icon: MapPin, hint: "Endere\xE7o ou ponto de venda f\xEDsico.", placeholder: "https://maps.google.com/\u2026" },
|
|
5258
|
-
custom: { label: "Outro canal", icon: Link2, hint: "Qualquer outro ponto de venda com um link.", placeholder: "https://\u2026" }
|
|
5259
|
-
};
|
|
5260
|
-
var ADDABLE_KINDS = ["whatsapp", "instagram", "tiktok_shop", "marketplace", "physical", "custom"];
|
|
5261
|
-
function resolve2(resolver) {
|
|
5262
|
-
return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
|
|
5263
|
-
}
|
|
5264
|
-
function notifyChannelsChanged() {
|
|
5265
|
-
if (typeof window !== "undefined") window.dispatchEvent(new Event("fayz:channels-changed"));
|
|
5266
|
-
}
|
|
5267
|
-
function ChannelsPage({ resolveProvider }) {
|
|
5268
|
-
const provider = React9.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
|
|
5269
|
-
const supported = typeof provider.listSalesChannels === "function";
|
|
5270
|
-
const [channels, setChannels] = React9.useState([]);
|
|
5271
|
-
const [loading, setLoading] = React9.useState(true);
|
|
5272
|
-
const [adding, setAdding] = React9.useState(null);
|
|
5273
|
-
const reload = React9.useCallback(async () => {
|
|
5274
|
-
if (!provider.listSalesChannels) {
|
|
5275
|
-
setLoading(false);
|
|
5276
|
-
return;
|
|
5277
|
-
}
|
|
5278
|
-
setLoading(true);
|
|
5279
|
-
try {
|
|
5280
|
-
const rows = await provider.listSalesChannels();
|
|
5281
|
-
if (!rows.some((c) => c.kind === "online_store") && provider.createSalesChannel) {
|
|
5282
|
-
await provider.createSalesChannel({ kind: "online_store", label: "Loja Online", enabled: true, position: 0 });
|
|
5283
|
-
setChannels(await provider.listSalesChannels());
|
|
5284
|
-
} else {
|
|
5285
|
-
setChannels(rows);
|
|
5286
|
-
}
|
|
5287
|
-
} finally {
|
|
5288
|
-
setLoading(false);
|
|
5289
|
-
}
|
|
5290
|
-
}, [provider]);
|
|
5291
|
-
React9.useEffect(() => {
|
|
5292
|
-
void reload();
|
|
5293
|
-
}, [reload]);
|
|
5294
|
-
const patch = async (id, input) => {
|
|
5295
|
-
if (!provider.updateSalesChannel) return;
|
|
5296
|
-
const updated = await provider.updateSalesChannel(id, input);
|
|
5297
|
-
setChannels((prev) => prev.map((c) => c.id === id ? updated : c));
|
|
5298
|
-
notifyChannelsChanged();
|
|
5299
|
-
};
|
|
5300
|
-
const remove = async (id) => {
|
|
5301
|
-
if (!provider.deleteSalesChannel) return;
|
|
5302
|
-
await provider.deleteSalesChannel(id);
|
|
5303
|
-
setChannels((prev) => prev.filter((c) => c.id !== id));
|
|
5304
|
-
notifyChannelsChanged();
|
|
5305
|
-
};
|
|
5306
|
-
const add = async (kind, label, url) => {
|
|
5307
|
-
if (!provider.createSalesChannel) return;
|
|
5308
|
-
const created = await provider.createSalesChannel({ kind, label: label || KIND_META[kind].label, url: url || null, enabled: true, position: channels.length });
|
|
5309
|
-
setChannels((prev) => [...prev, created]);
|
|
5310
|
-
setAdding(null);
|
|
5311
|
-
notifyChannelsChanged();
|
|
5312
|
-
};
|
|
5313
|
-
if (!supported) {
|
|
5314
|
-
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-4xl p-6", children: [
|
|
5315
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Canais de venda" }),
|
|
5316
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Este provedor de loja ainda n\xE3o exp\xF5e canais de venda." })
|
|
5317
|
-
] });
|
|
5318
|
-
}
|
|
5319
|
-
const online = channels.find((c) => c.kind === "online_store");
|
|
5320
|
-
const others = channels.filter((c) => c.kind !== "online_store");
|
|
5321
|
-
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-4xl p-6", children: [
|
|
5322
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
|
|
5323
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Canais de venda" }),
|
|
5324
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Onde sua loja vende. A Loja Online \xE9 o seu storefront fayz; os demais s\xE3o links que aparecem no menu lateral." })
|
|
5325
|
-
] }),
|
|
5326
|
-
loading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Carregando\u2026" }) : /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
5327
|
-
online && /* @__PURE__ */ jsx(OnlineStoreCard, { channel: online, onSave: (url) => patch(online.id, { url }), onToggle: (enabled) => patch(online.id, { enabled }) }),
|
|
5328
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
5329
|
-
/* @__PURE__ */ jsx("div", { className: "mb-2 flex items-center justify-between", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: "Outros canais" }) }),
|
|
5330
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
5331
|
-
others.map((c) => /* @__PURE__ */ jsx(ChannelRow, { channel: c, onSave: (url) => patch(c.id, { url }), onToggle: (enabled) => patch(c.id, { enabled }), onRemove: () => remove(c.id) }, c.id)),
|
|
5332
|
-
others.length === 0 && /* @__PURE__ */ jsx("p", { className: "rounded-card border border-dashed border-border p-4 text-sm text-muted-foreground", children: "Nenhum canal adicional ainda. Adicione WhatsApp, Instagram, TikTok Shop e outros pontos de venda." })
|
|
5333
|
-
] }),
|
|
5334
|
-
adding ? /* @__PURE__ */ jsx(AddChannelForm, { kind: adding, onCancel: () => setAdding(null), onAdd: (label, url) => add(adding, label, url), onKind: setAdding }) : /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: ADDABLE_KINDS.map((k) => {
|
|
5335
|
-
const Icon = KIND_META[k].icon;
|
|
5336
|
-
return /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setAdding(k), children: [
|
|
5337
|
-
/* @__PURE__ */ jsx(Icon, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
5338
|
-
" ",
|
|
5339
|
-
KIND_META[k].label
|
|
5340
|
-
] }, k);
|
|
5341
|
-
}) })
|
|
5342
|
-
] })
|
|
5343
|
-
] })
|
|
5344
|
-
] });
|
|
5345
|
-
}
|
|
5346
|
-
function OnlineStoreCard({ channel, onSave, onToggle }) {
|
|
5347
|
-
const [url, setUrl] = React9.useState(channel.url ?? "");
|
|
5348
|
-
const dirty = (channel.url ?? "") !== url;
|
|
5349
|
-
const meta = KIND_META.online_store;
|
|
5350
|
-
return /* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-5", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
5351
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(meta.icon, { className: "h-5 w-5 text-primary" }) }),
|
|
5352
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
5353
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5354
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Loja Online" }),
|
|
5355
|
-
/* @__PURE__ */ jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${channel.enabled ? "bg-emerald-100 text-emerald-800" : "bg-muted text-muted-foreground"}`, children: channel.enabled ? "Ativa" : "Inativa" })
|
|
5356
|
-
] }),
|
|
5357
|
-
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: meta.hint }),
|
|
5358
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-col gap-2 sm:flex-row sm:items-center", children: [
|
|
5359
|
-
/* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "flex-1" }),
|
|
5360
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
5361
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", disabled: !dirty, onClick: () => onSave(url.trim() || null), children: "Salvar" }),
|
|
5362
|
-
/* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", disabled: !channel.url, onClick: () => channel.url && window.open(channel.url, "_blank", "noopener,noreferrer"), children: [
|
|
5363
|
-
/* @__PURE__ */ jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
5364
|
-
" Ver loja"
|
|
5365
|
-
] })
|
|
5366
|
-
] })
|
|
5367
|
-
] }),
|
|
5368
|
-
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onToggle(!channel.enabled), className: "text-xs font-medium text-muted-foreground hover:text-foreground", children: channel.enabled ? "Desativar canal" : "Ativar canal" }) })
|
|
5369
|
-
] })
|
|
5370
|
-
] }) });
|
|
5371
|
-
}
|
|
5372
|
-
function ChannelRow({ channel, onSave, onToggle, onRemove }) {
|
|
5373
|
-
const [url, setUrl] = React9.useState(channel.url ?? "");
|
|
5374
|
-
const [editing, setEditing] = React9.useState(false);
|
|
5375
|
-
const meta = KIND_META[channel.kind] ?? KIND_META.custom;
|
|
5376
|
-
const Icon = meta.icon;
|
|
5377
|
-
return /* @__PURE__ */ jsx("div", { className: "rounded-card border border-border bg-card p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
5378
|
-
/* @__PURE__ */ jsx("div", { className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full ${channel.enabled ? "bg-primary/10" : "bg-muted"}`, children: /* @__PURE__ */ jsx(Icon, { className: `h-4 w-4 ${channel.enabled ? "text-primary" : "text-muted-foreground"}` }) }),
|
|
5379
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
5380
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: channel.label }),
|
|
5381
|
-
editing ? /* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "mt-1 h-8" }) : /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: channel.url || "Sem link configurado" })
|
|
5382
|
-
] }),
|
|
5383
|
-
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-1", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5384
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
5385
|
-
onSave(url.trim() || null);
|
|
5386
|
-
setEditing(false);
|
|
5387
|
-
}, children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }),
|
|
5388
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
5389
|
-
setUrl(channel.url ?? "");
|
|
5390
|
-
setEditing(false);
|
|
5391
|
-
}, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
|
|
5392
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5393
|
-
channel.url && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => window.open(channel.url, "_blank", "noopener,noreferrer"), children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" }) }),
|
|
5394
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => onToggle(!channel.enabled), title: channel.enabled ? "Desativar" : "Ativar", className: `rounded-full px-2 py-0.5 text-xs font-medium ${channel.enabled ? "bg-emerald-100 text-emerald-800" : "bg-muted text-muted-foreground"}`, children: channel.enabled ? "Ativo" : "Inativo" }),
|
|
5395
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setEditing(true), children: "Editar" }),
|
|
5396
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onRemove, children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 text-red-500" }) })
|
|
5397
|
-
] }) })
|
|
5398
|
-
] }) });
|
|
5399
|
-
}
|
|
5400
|
-
function AddChannelForm({ kind, onKind, onCancel, onAdd }) {
|
|
5401
|
-
const meta = KIND_META[kind];
|
|
5402
|
-
const [label, setLabel] = React9.useState(meta.label);
|
|
5403
|
-
const [url, setUrl] = React9.useState("");
|
|
5404
|
-
React9.useEffect(() => {
|
|
5405
|
-
setLabel(KIND_META[kind].label);
|
|
5406
|
-
}, [kind]);
|
|
5407
|
-
return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-card border border-border bg-card p-4", children: [
|
|
5408
|
-
/* @__PURE__ */ jsx("div", { className: "mb-3 flex flex-wrap gap-2", children: ADDABLE_KINDS.map((k) => {
|
|
5409
|
-
const Icon = KIND_META[k].icon;
|
|
5410
|
-
return /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => onKind(k), className: `flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${k === kind ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: [
|
|
5411
|
-
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
5412
|
-
" ",
|
|
5413
|
-
KIND_META[k].label
|
|
5414
|
-
] }, k);
|
|
5415
|
-
}) }),
|
|
5416
|
-
/* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-muted-foreground", children: meta.hint }),
|
|
5417
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
|
|
5418
|
-
/* @__PURE__ */ jsx(Input, { value: label, placeholder: "Nome do canal", onChange: (e) => setLabel(e.target.value), className: "sm:w-48" }),
|
|
5419
|
-
/* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "flex-1" })
|
|
5420
|
-
] }),
|
|
5421
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex gap-2", children: [
|
|
5422
|
-
/* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => onAdd(label, url), children: [
|
|
5423
|
-
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
5424
|
-
" Adicionar"
|
|
5425
|
-
] }),
|
|
5426
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: onCancel, children: "Cancelar" })
|
|
5427
|
-
] })
|
|
5428
|
-
] });
|
|
5429
|
-
}
|
|
5430
6426
|
|
|
5431
6427
|
// src/views/media-links.ts
|
|
5432
6428
|
function recordRoute(reference) {
|
|
@@ -5443,7 +6439,7 @@ var TABS = [
|
|
|
5443
6439
|
{ value: "video", label: "V\xEDdeos" }
|
|
5444
6440
|
];
|
|
5445
6441
|
var LOW_RESOLUTION_PX = 800;
|
|
5446
|
-
function
|
|
6442
|
+
function resolve2(resolver) {
|
|
5447
6443
|
return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
|
|
5448
6444
|
}
|
|
5449
6445
|
function formatSize(bytes) {
|
|
@@ -5453,17 +6449,17 @@ function formatSize(bytes) {
|
|
|
5453
6449
|
return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
5454
6450
|
}
|
|
5455
6451
|
function ContentPage({ resolveProvider }) {
|
|
5456
|
-
const provider =
|
|
6452
|
+
const provider = React13__default.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
|
|
5457
6453
|
const supported = typeof provider.listMediaFiles === "function";
|
|
5458
|
-
const [files, setFiles] =
|
|
5459
|
-
const [tab, setTab] =
|
|
5460
|
-
const [uploading, setUploading] =
|
|
5461
|
-
const [pendingDelete, setPendingDelete] =
|
|
5462
|
-
const [deleting, setDeleting] =
|
|
5463
|
-
const [replacing, setReplacing] =
|
|
5464
|
-
const [openId, setOpenId] =
|
|
5465
|
-
const [dimensions, setDimensions] =
|
|
5466
|
-
const reload =
|
|
6454
|
+
const [files, setFiles] = React13__default.useState(null);
|
|
6455
|
+
const [tab, setTab] = React13__default.useState("all");
|
|
6456
|
+
const [uploading, setUploading] = React13__default.useState(false);
|
|
6457
|
+
const [pendingDelete, setPendingDelete] = React13__default.useState(null);
|
|
6458
|
+
const [deleting, setDeleting] = React13__default.useState(false);
|
|
6459
|
+
const [replacing, setReplacing] = React13__default.useState(false);
|
|
6460
|
+
const [openId, setOpenId] = React13__default.useState(null);
|
|
6461
|
+
const [dimensions, setDimensions] = React13__default.useState({});
|
|
6462
|
+
const reload = React13__default.useCallback(async () => {
|
|
5467
6463
|
if (!provider.listMediaFiles) {
|
|
5468
6464
|
setFiles([]);
|
|
5469
6465
|
return;
|
|
@@ -5475,10 +6471,10 @@ function ContentPage({ resolveProvider }) {
|
|
|
5475
6471
|
setFiles([]);
|
|
5476
6472
|
}
|
|
5477
6473
|
}, [provider]);
|
|
5478
|
-
|
|
6474
|
+
React13__default.useEffect(() => {
|
|
5479
6475
|
void reload();
|
|
5480
6476
|
}, [reload]);
|
|
5481
|
-
const measure =
|
|
6477
|
+
const measure = React13__default.useCallback((id, d) => {
|
|
5482
6478
|
setDimensions((prev) => prev[id] ? prev : { ...prev, [id]: d });
|
|
5483
6479
|
}, []);
|
|
5484
6480
|
const handleUpload = async (picked) => {
|
|
@@ -5500,19 +6496,19 @@ function ContentPage({ resolveProvider }) {
|
|
|
5500
6496
|
const handleReplace = async (file, picked) => {
|
|
5501
6497
|
setReplacing(true);
|
|
5502
6498
|
try {
|
|
5503
|
-
let
|
|
6499
|
+
let nextId2;
|
|
5504
6500
|
if (file.reference) {
|
|
5505
6501
|
if (!provider.replaceProductImage) throw new Error("unsupported");
|
|
5506
6502
|
const image = await provider.replaceProductImage(file.reference.attachmentId, picked);
|
|
5507
|
-
|
|
6503
|
+
nextId2 = productImageToMediaFile(image, file.reference.label).id;
|
|
5508
6504
|
} else {
|
|
5509
6505
|
if (!provider.uploadMediaFile) throw new Error("unsupported");
|
|
5510
6506
|
const uploaded = await provider.uploadMediaFile(picked);
|
|
5511
6507
|
if (provider.deleteMediaFile) await provider.deleteMediaFile(file.id);
|
|
5512
|
-
|
|
6508
|
+
nextId2 = uploaded.id;
|
|
5513
6509
|
}
|
|
5514
6510
|
await reload();
|
|
5515
|
-
setOpenId(
|
|
6511
|
+
setOpenId(nextId2);
|
|
5516
6512
|
toast.success("Arquivo trocado");
|
|
5517
6513
|
} catch (err) {
|
|
5518
6514
|
console.warn("[content] replace failed", err);
|
|
@@ -5668,7 +6664,7 @@ function SectionTitle({ title, count }) {
|
|
|
5668
6664
|
] });
|
|
5669
6665
|
}
|
|
5670
6666
|
function UploadButton({ uploading, onPick }) {
|
|
5671
|
-
const inputRef =
|
|
6667
|
+
const inputRef = React13__default.useRef(null);
|
|
5672
6668
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5673
6669
|
/* @__PURE__ */ jsx(
|
|
5674
6670
|
"input",
|
|
@@ -5748,8 +6744,8 @@ function MediaDetail({
|
|
|
5748
6744
|
onDelete,
|
|
5749
6745
|
replacing
|
|
5750
6746
|
}) {
|
|
5751
|
-
const replaceRef =
|
|
5752
|
-
|
|
6747
|
+
const replaceRef = React13__default.useRef(null);
|
|
6748
|
+
React13__default.useEffect(() => {
|
|
5753
6749
|
function onKey(e) {
|
|
5754
6750
|
if (e.key === "ArrowLeft") onNavigate(-1);
|
|
5755
6751
|
if (e.key === "ArrowRight") onNavigate(1);
|
|
@@ -6210,7 +7206,7 @@ var blingConnectorDef = {
|
|
|
6210
7206
|
// where the question is actually asked. It is the only thing in that panel
|
|
6211
7207
|
// that was neither status, nor history, nor an act.
|
|
6212
7208
|
disconnectNote: "Desconectar congela o espelho: nenhum produto \xE9 apagado e o estoque para de ser atualizado.",
|
|
6213
|
-
// No panel of its own. The history is `
|
|
7209
|
+
// No panel of its own. The history is `sync_runs` read through the shared
|
|
6214
7210
|
// store, and the shared surface renders it — including the "first load brings
|
|
6215
7211
|
// a sample" sentence, which it derives from `firstLoad` above rather than
|
|
6216
7212
|
// repeating the number in prose.
|
|
@@ -6781,7 +7777,7 @@ var ORDERS = "plg_shop_rep_orders";
|
|
|
6781
7777
|
var ITEMS = "plg_shop_rep_order_items";
|
|
6782
7778
|
var CUSTOMERS = "plg_shop_rep_customers";
|
|
6783
7779
|
var PRODUCTS = "plg_shop_rep_products";
|
|
6784
|
-
var CANONICAL = "public.
|
|
7780
|
+
var CANONICAL = "public.v_shop_orders";
|
|
6785
7781
|
var CAT = {
|
|
6786
7782
|
sales: tl("Sales", "Vendas"),
|
|
6787
7783
|
orders: tl("Orders", "Pedidos"),
|
|
@@ -7604,7 +8600,7 @@ function buildShopAnalyticsCards() {
|
|
|
7604
8600
|
}
|
|
7605
8601
|
|
|
7606
8602
|
// src/index.ts
|
|
7607
|
-
var ShopPage =
|
|
8603
|
+
var ShopPage = React13__default.lazy(() => import('./ShopPage-5IERN4E5.js').then((m) => ({ default: m.ShopPage })));
|
|
7608
8604
|
function createShopPlugin(options) {
|
|
7609
8605
|
const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
|
|
7610
8606
|
setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
|
|
@@ -7613,10 +8609,10 @@ function createShopPlugin(options) {
|
|
|
7613
8609
|
registerEntityPath("shop_customer", "/orders/customers");
|
|
7614
8610
|
registerPersonLookup("shop_customer", shopCustomerLookup(() => resolveProvider()));
|
|
7615
8611
|
const pageFor = (section) => {
|
|
7616
|
-
const Wrapped = () =>
|
|
7617
|
-
|
|
8612
|
+
const Wrapped = () => React13__default.createElement(
|
|
8613
|
+
React13__default.Suspense,
|
|
7618
8614
|
{ fallback: null },
|
|
7619
|
-
|
|
8615
|
+
React13__default.createElement(
|
|
7620
8616
|
ShopPage,
|
|
7621
8617
|
{ provider: options?.provider, section }
|
|
7622
8618
|
)
|
|
@@ -7795,7 +8791,7 @@ function createShopPlugin(options) {
|
|
|
7795
8791
|
dataProvider: createShopGiftCardDataProvider(resolveProvider),
|
|
7796
8792
|
feature: "shop",
|
|
7797
8793
|
stats: (items, total) => {
|
|
7798
|
-
const
|
|
8794
|
+
const money3 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
|
|
7799
8795
|
const active = items?.filter((g) => g.status === "active") ?? [];
|
|
7800
8796
|
return [
|
|
7801
8797
|
{ id: "total", label: "Cart\xF5es emitidos", value: total, icon: CreditCard },
|
|
@@ -7806,23 +8802,22 @@ function createShopPlugin(options) {
|
|
|
7806
8802
|
id: "outstanding",
|
|
7807
8803
|
label: "Saldo em aberto",
|
|
7808
8804
|
icon: CircleDollarSign,
|
|
7809
|
-
value:
|
|
8805
|
+
value: money3(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
|
|
7810
8806
|
},
|
|
7811
8807
|
{
|
|
7812
8808
|
id: "issued",
|
|
7813
8809
|
label: "Valor emitido",
|
|
7814
8810
|
icon: TicketCheck,
|
|
7815
|
-
value:
|
|
8811
|
+
value: money3(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
|
|
7816
8812
|
}
|
|
7817
8813
|
];
|
|
7818
8814
|
}
|
|
7819
8815
|
});
|
|
7820
|
-
const ContentPageWrapped = () =>
|
|
8816
|
+
const ContentPageWrapped = () => React13__default.createElement(ContentPage, { resolveProvider: options?.provider });
|
|
7821
8817
|
ContentPageWrapped.displayName = "ContentPage";
|
|
7822
|
-
const ChannelsPageWrapped = () => React9.createElement(ChannelsPage, { resolveProvider: options?.provider });
|
|
7823
|
-
ChannelsPageWrapped.displayName = "ChannelsPage";
|
|
7824
8818
|
const navSection = options?.navSection ?? "main";
|
|
7825
8819
|
const navPosition = options?.navPosition ?? 6;
|
|
8820
|
+
const siteRoute = options?.siteRoute ?? "/site";
|
|
7826
8821
|
const NAV = [
|
|
7827
8822
|
// Pedidos ▸ Pedidos · Clientes
|
|
7828
8823
|
{
|
|
@@ -7887,7 +8882,6 @@ function createShopPlugin(options) {
|
|
|
7887
8882
|
}
|
|
7888
8883
|
];
|
|
7889
8884
|
const HIDDEN_ROUTES = [
|
|
7890
|
-
{ path: "/store", component: ChannelsPageWrapped, title: label("store", "Canais de venda") },
|
|
7891
8885
|
// Reached from the Clientes tab strip, deliberately not from the menu:
|
|
7892
8886
|
// three sidebar entries for one destination is the duplication the tab
|
|
7893
8887
|
// strip exists to avoid. Nesting under /orders/customers also keeps
|
|
@@ -7909,7 +8903,7 @@ function createShopPlugin(options) {
|
|
|
7909
8903
|
];
|
|
7910
8904
|
const redirectTo = (target) => {
|
|
7911
8905
|
const Redirect = () => {
|
|
7912
|
-
|
|
8906
|
+
React13__default.useEffect(() => {
|
|
7913
8907
|
adminNavigateTo(target);
|
|
7914
8908
|
}, []);
|
|
7915
8909
|
return null;
|
|
@@ -7930,7 +8924,8 @@ function createShopPlugin(options) {
|
|
|
7930
8924
|
// rota vem do mesmo lugar que a tela usa.
|
|
7931
8925
|
{ path: "/shop/shipping", component: redirectTo(shippingRegistryPath(SHIPPING_ZONES_REGISTRY)) },
|
|
7932
8926
|
{ path: "/store/shipping", component: redirectTo(shippingRegistryPath(SHIPPING_ZONES_REGISTRY)) },
|
|
7933
|
-
{ path: "/
|
|
8927
|
+
{ path: "/store", component: redirectTo(siteRoute) },
|
|
8928
|
+
{ path: "/shop/channels", component: redirectTo(siteRoute) },
|
|
7934
8929
|
{ path: "/customers", component: redirectTo("/orders/customers") },
|
|
7935
8930
|
// A mensageria da loja virou parte de Automações — mesmos eventos, agora ao
|
|
7936
8931
|
// lado dos do CRM e da agenda, e com WhatsApp além do e-mail.
|
|
@@ -7944,7 +8939,7 @@ function createShopPlugin(options) {
|
|
|
7944
8939
|
// O que a loja pede de um tenant novo, na ordem em que faz diferença.
|
|
7945
8940
|
// Vira linha de tarefa, aparece no checklist da trilha e é o roteiro que o
|
|
7946
8941
|
// assistente segue — ver ./lib/onboarding.ts.
|
|
7947
|
-
onboarding: { program: buildShopOnboarding() },
|
|
8942
|
+
onboarding: { program: buildShopOnboarding(siteRoute) },
|
|
7948
8943
|
name: options?.navLabel ?? "Shop",
|
|
7949
8944
|
icon: "ShoppingBag",
|
|
7950
8945
|
version: "1.0.0",
|
|
@@ -8037,7 +9032,7 @@ function createShopPlugin(options) {
|
|
|
8037
9032
|
label: label("store", "Loja"),
|
|
8038
9033
|
icon: "ShoppingBag",
|
|
8039
9034
|
group: COMMERCE_GROUP,
|
|
8040
|
-
component: settingsTab("StoreGeneralSettings", () =>
|
|
9035
|
+
component: settingsTab("StoreGeneralSettings", () => React13__default.createElement(StoreGeneralSettings, { currency: curr, provider: options?.provider })),
|
|
8041
9036
|
order: 30,
|
|
8042
9037
|
permission: READ
|
|
8043
9038
|
},
|
|
@@ -8046,7 +9041,7 @@ function createShopPlugin(options) {
|
|
|
8046
9041
|
label: "Pagamentos",
|
|
8047
9042
|
icon: "CreditCard",
|
|
8048
9043
|
group: COMMERCE_GROUP,
|
|
8049
|
-
component: settingsTab("PaymentsSettings", () =>
|
|
9044
|
+
component: settingsTab("PaymentsSettings", () => React13__default.createElement(PaymentsSettings, { provider: options?.provider })),
|
|
8050
9045
|
order: 31,
|
|
8051
9046
|
permission: READ
|
|
8052
9047
|
},
|
|
@@ -8055,7 +9050,7 @@ function createShopPlugin(options) {
|
|
|
8055
9050
|
label: "Checkout",
|
|
8056
9051
|
icon: "ShoppingCart",
|
|
8057
9052
|
group: COMMERCE_GROUP,
|
|
8058
|
-
component: settingsTab("CheckoutSettings", () =>
|
|
9053
|
+
component: settingsTab("CheckoutSettings", () => React13__default.createElement(CheckoutSettings, { provider: options?.provider })),
|
|
8059
9054
|
order: 32,
|
|
8060
9055
|
permission: READ
|
|
8061
9056
|
},
|
|
@@ -8064,7 +9059,7 @@ function createShopPlugin(options) {
|
|
|
8064
9059
|
label: label("shipping", "Entrega"),
|
|
8065
9060
|
icon: "Truck",
|
|
8066
9061
|
group: COMMERCE_GROUP,
|
|
8067
|
-
component: settingsTab("ShippingSettings", () =>
|
|
9062
|
+
component: settingsTab("ShippingSettings", () => React13__default.createElement(ShippingSettings, { provider: options?.provider })),
|
|
8068
9063
|
order: 33,
|
|
8069
9064
|
permission: READ
|
|
8070
9065
|
},
|
|
@@ -8073,7 +9068,7 @@ function createShopPlugin(options) {
|
|
|
8073
9068
|
label: "Fulfillment",
|
|
8074
9069
|
icon: "PackageCheck",
|
|
8075
9070
|
group: COMMERCE_GROUP,
|
|
8076
|
-
component: settingsTab("FulfillmentSettings", () =>
|
|
9071
|
+
component: settingsTab("FulfillmentSettings", () => React13__default.createElement(FulfillmentSettings, { provider: options?.provider })),
|
|
8077
9072
|
order: 34,
|
|
8078
9073
|
permission: READ
|
|
8079
9074
|
}
|