@fayz-ai/plugin-shop 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ShopPage-QFIM2HDU.js → ShopPage-6IQ7WKQG.js} +3 -3
- package/dist/{ShopPage-QFIM2HDU.js.map → ShopPage-6IQ7WKQG.js.map} +1 -1
- package/dist/{chunk-2VMTPRGC.js → chunk-NFFJVQCY.js} +93 -34
- package/dist/chunk-NFFJVQCY.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1377 -206
- package/dist/index.js.map +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 +17 -0
- package/dist/lib/onboarding.d.ts.map +1 -0
- package/dist/settings/ShippingSettings.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/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/shippingZoneEntity.d.ts.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-2VMTPRGC.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,197 @@
|
|
|
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-NFFJVQCY.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, 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
6
|
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, Link2, MapPin, Music, Camera, MessageCircle, Globe, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, ExternalLink, Check, X, AlertTriangle, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, Car, Truck, Info } from 'lucide-react';
|
|
7
|
+
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Link2, MapPin, Music, Camera, MessageCircle, Globe, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, ExternalLink, Check, X, AlertTriangle, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, Car, Truck, Info, Sparkles, Ban, ListChecks, Search, Building2 } from 'lucide-react';
|
|
7
8
|
import { PageHeaderActions, PageTabs, Card, Button, Thumbnail, PageHeader, toast, ConfirmDialog, Input, Hint, Modal, ModalContent, ModalHeader, ModalTitle, 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
|
|
|
14
|
+
function buildShopOnboarding() {
|
|
15
|
+
return {
|
|
16
|
+
id: "shop",
|
|
17
|
+
title: "Sua loja no ar",
|
|
18
|
+
order: 10,
|
|
19
|
+
role: "operations",
|
|
20
|
+
instructions: `Voc\xEA est\xE1 implantando uma LOJA. A pessoa n\xE3o quer aprender um sistema: ela quer vender.
|
|
21
|
+
|
|
22
|
+
- A ordem importa e \xE9 esta: ter o que vender \u2192 onde vender \u2192 como receber \u2192 como entregar \u2192 um motivo para comprar \u2192 a primeira venda. N\xE3o pule para "divulgue" antes de a loja aceitar um pedido.
|
|
23
|
+
- Importar cat\xE1logo \xE9 o passo que trava mais gente. Ofere\xE7a planilha, integra\xE7\xE3o (Bling, Nuvemshop) ou cadastrar um produto \xE0 m\xE3o para destravar \u2014 um produto j\xE1 \xE9 o suficiente para seguir.
|
|
24
|
+
- Em pagamento, pergunte COMO ela recebe hoje (Pix, cart\xE3o, maquininha) antes de falar de gateway. O nome do provedor \xE9 detalhe nosso, n\xE3o dela.
|
|
25
|
+
- Antes de dizer que a loja est\xE1 pronta, confira: tem produto ativo, tem forma de pagamento, e a vitrine est\xE1 publicada. Se faltar uma, diga qual falta.
|
|
26
|
+
- Quando estiver pronta para receber pedidos, chame o colega de growth para a primeira campanha \u2014 n\xE3o invente plano de m\xEDdia por conta pr\xF3pria.`,
|
|
27
|
+
steps: [
|
|
28
|
+
{
|
|
29
|
+
key: "catalog",
|
|
30
|
+
title: "Cadastre seu primeiro produto",
|
|
31
|
+
description: "Seu cat\xE1logo \xE9 o que a loja vende. Um produto j\xE1 destrava o resto.",
|
|
32
|
+
icon: "Package",
|
|
33
|
+
route: "/products",
|
|
34
|
+
priority: "high",
|
|
35
|
+
// O passo genérico da plataforma ("cadastre seu primeiro produto") e
|
|
36
|
+
// este são o mesmo recado — a loja é dona dele.
|
|
37
|
+
replaces: "catalog",
|
|
38
|
+
agentHint: "Pergunte onde o cat\xE1logo est\xE1 hoje (planilha, ERP, Instagram). Ofere\xE7a importar; se travar, um produto \xE0 m\xE3o j\xE1 destrava.",
|
|
39
|
+
howTo: "Produtos \u203A Adicionar. Nome, pre\xE7o e uma foto j\xE1 bastam para o produto aparecer na loja \u2014 estoque e varia\xE7\xF5es d\xE1 para completar depois. Se voc\xEA j\xE1 tem uma planilha, use Adicionar \u203A Importar: colunas de nome, pre\xE7o e estoque viram o cat\xE1logo inteiro de uma vez.",
|
|
40
|
+
celebrate: "Produto no ar \u{1F389}",
|
|
41
|
+
// A coreografia, como dado: nenhuma dessas quatro falas varia, e cada
|
|
42
|
+
// uma delas custava um turno de modelo para ser escrita de novo.
|
|
43
|
+
script: [
|
|
44
|
+
{ kind: "say", text: "Vamos come\xE7ar pelo que a loja vende. Te levo at\xE9 os Produtos." },
|
|
45
|
+
{ kind: "goto", route: "/products" },
|
|
46
|
+
{
|
|
47
|
+
kind: "say",
|
|
48
|
+
text: "Esta \xE9 a sua lista de produtos \u2014 hoje vazia. Tudo que entrar aqui aparece na vitrine."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
// Três caminhos, e "cadastre um produto" assumia que só existia um.
|
|
52
|
+
// A pergunta é o passo: quem tem planilha não quer digitar, e quem
|
|
53
|
+
// não tem nada quer que alguém comece por ele.
|
|
54
|
+
kind: "choose",
|
|
55
|
+
id: "shop.catalog.how",
|
|
56
|
+
question: "Como voc\xEA prefere trazer seus produtos?",
|
|
57
|
+
options: [
|
|
58
|
+
{ id: "import", label: "Tenho uma planilha", meta: "Importo e mapeio as colunas com voc\xEA" },
|
|
59
|
+
{ id: "manual", label: "Vou cadastrar \xE0 m\xE3o", meta: "Te mostro onde, come\xE7ando por um" },
|
|
60
|
+
{
|
|
61
|
+
id: "suggest",
|
|
62
|
+
label: "Me sugira o cat\xE1logo",
|
|
63
|
+
meta: "Monto uma lista a partir do que j\xE1 sei do seu neg\xF3cio"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
check: async () => await countByTenant("plg_shop_products") > 0,
|
|
69
|
+
tour: {
|
|
70
|
+
id: "shop.catalog",
|
|
71
|
+
stops: [
|
|
72
|
+
{
|
|
73
|
+
anchor: "action.new",
|
|
74
|
+
title: "\xC9 aqui que nasce um produto",
|
|
75
|
+
body: "Nome, pre\xE7o e uma foto j\xE1 bastam. Pode cadastrar agora \u2014 eu vejo quando ele entrar.",
|
|
76
|
+
route: "/products",
|
|
77
|
+
waitForUser: true
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
anchor: "action.import",
|
|
81
|
+
title: "Ou traga tudo de uma vez",
|
|
82
|
+
body: "Uma planilha com nome, pre\xE7o e estoque vira o cat\xE1logo inteiro."
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: "storefront",
|
|
89
|
+
title: "Configure sua loja online",
|
|
90
|
+
description: "O endere\xE7o que voc\xEA manda para o cliente \u2014 e os canais que apontam para ela.",
|
|
91
|
+
icon: "Store",
|
|
92
|
+
route: "/store",
|
|
93
|
+
priority: "high",
|
|
94
|
+
// A plataforma chama de "publique seu site"; numa loja é a vitrine, e
|
|
95
|
+
// quem sabe configurá-la é este módulo.
|
|
96
|
+
replaces: "publish",
|
|
97
|
+
agentHint: "Confira o endere\xE7o da vitrine e os canais ativos. Publicar sem frete configurado gera pedido que ningu\xE9m consegue entregar.",
|
|
98
|
+
howTo: 'Canais de venda (o item "Sua loja" na barra lateral). A Loja Online \xE9 a sua vitrine: confira o endere\xE7o, veja como est\xE1 e publique. Esse link \xE9 o que vai para a bio do Instagram e para o WhatsApp.',
|
|
99
|
+
celebrate: "Vitrine pronta \u2728",
|
|
100
|
+
script: [
|
|
101
|
+
{ kind: "say", text: "Agora a vitrine \u2014 o endere\xE7o que voc\xEA manda para o cliente." },
|
|
102
|
+
{ kind: "goto", route: "/store" },
|
|
103
|
+
{
|
|
104
|
+
kind: "ask",
|
|
105
|
+
text: "Estou na tela de canais de venda. Confira comigo o endere\xE7o da Loja Online e me diga o que falta para publicar."
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
key: "payments",
|
|
111
|
+
title: "Decida como vai receber",
|
|
112
|
+
description: "Pix, cart\xE3o ou boleto \u2014 o dinheiro precisa de um caminho.",
|
|
113
|
+
icon: "CreditCard",
|
|
114
|
+
route: "/settings/shop-payments",
|
|
115
|
+
priority: "high",
|
|
116
|
+
replaces: "payments",
|
|
117
|
+
agentHint: "Comece por como ela j\xE1 recebe hoje. Pix costuma ser o caminho mais curto para a primeira venda; cart\xE3o vem depois.",
|
|
118
|
+
howTo: "Configura\xE7\xF5es \u203A Pagamentos. Ligue as formas que voc\xEA aceita e conecte o provedor de cada uma. Para Pix voc\xEA precisa da chave ou das credenciais do provedor; sem isso o pedido entra, mas o pagamento n\xE3o \xE9 confirmado sozinho.",
|
|
119
|
+
celebrate: "Recebimento no lugar \u{1F4B3}",
|
|
120
|
+
script: [
|
|
121
|
+
{ kind: "goto", route: "/settings/shop-payments" },
|
|
122
|
+
{
|
|
123
|
+
kind: "ask",
|
|
124
|
+
text: "Estamos em Pagamentos. Pergunte como a pessoa recebe hoje (Pix, cart\xE3o, maquininha) antes de falar de provedor, e ligue a forma que ela usar."
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: "shipping",
|
|
130
|
+
title: "Configure a entrega",
|
|
131
|
+
description: "Zonas, prazos e quanto custa o frete.",
|
|
132
|
+
icon: "Truck",
|
|
133
|
+
route: "/settings/shop-shipping",
|
|
134
|
+
agentHint: "Pergunte at\xE9 onde entregam e como (carro pr\xF3prio, transportadora, retirada). Frete errado \xE9 o motivo n\xBA 1 de carrinho abandonado.",
|
|
135
|
+
howTo: "Configura\xE7\xF5es \u203A Entrega. Crie uma zona (por CEP ou por cidade), diga o prazo e o pre\xE7o \u2014 pode ser valor fixo para come\xE7ar. Retirada no local \xE9 uma zona com frete zero.",
|
|
136
|
+
celebrate: "Entrega configurada \u{1F69A}",
|
|
137
|
+
script: [
|
|
138
|
+
{ kind: "goto", route: "/settings/shop-shipping" },
|
|
139
|
+
{
|
|
140
|
+
kind: "ask",
|
|
141
|
+
text: "Estamos em Entrega. Pergunte at\xE9 onde a pessoa entrega e como, e monte a primeira zona junto com ela."
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "discount",
|
|
147
|
+
title: "Crie um cupom de desconto",
|
|
148
|
+
description: "D\xEA um motivo para o cliente fechar a primeira compra.",
|
|
149
|
+
icon: "Tag",
|
|
150
|
+
route: "/discounts",
|
|
151
|
+
priority: "low",
|
|
152
|
+
agentHint: "Este \xE9 passo de growth: sugira um cupom de primeira compra e ajude a decidir o valor. Se houver colega de growth, passe a bola.",
|
|
153
|
+
howTo: "Descontos \u203A Adicionar. Escolha o c\xF3digo (algo curto, tipo BEMVINDO10), o tipo (percentual ou valor) e at\xE9 quando vale. O cliente digita esse c\xF3digo no checkout."
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
key: "first-order",
|
|
157
|
+
title: "Receba seu primeiro pedido",
|
|
158
|
+
description: "Fa\xE7a um pedido de teste e veja ele cair aqui dentro.",
|
|
159
|
+
icon: "ShoppingBag",
|
|
160
|
+
route: "/orders",
|
|
161
|
+
agentHint: 'Um pedido de teste \xE9 o que transforma "acho que est\xE1 no ar" em "est\xE1 no ar". Acompanhe at\xE9 ele aparecer na lista.',
|
|
162
|
+
howTo: "Abra sua vitrine como se fosse um cliente, escolha um produto e finalize a compra. O pedido aparece em Pedidos em segundos \u2014 e \xE9 assim que voc\xEA confere que pre\xE7o, frete e pagamento est\xE3o certos antes do primeiro cliente de verdade.",
|
|
163
|
+
celebrate: "Primeiro pedido na casa \u{1F6CD}\uFE0F",
|
|
164
|
+
script: [
|
|
165
|
+
{
|
|
166
|
+
kind: "say",
|
|
167
|
+
text: "\xDAltimo: um pedido de teste. Abra sua vitrine como se fosse um cliente e finalize uma compra \u2014 eu aviso aqui quando ele cair."
|
|
168
|
+
},
|
|
169
|
+
{ kind: "goto", route: "/orders" },
|
|
170
|
+
{
|
|
171
|
+
kind: "point",
|
|
172
|
+
anchor: "nav./orders",
|
|
173
|
+
title: "Todo pedido cai aqui",
|
|
174
|
+
body: "Pago, em separa\xE7\xE3o, enviado \u2014 o estado de cada venda vive nesta lista.",
|
|
175
|
+
waitForUser: true
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
check: async () => await countByTenant("plg_shop_orders") > 0,
|
|
179
|
+
tour: {
|
|
180
|
+
id: "shop.orders",
|
|
181
|
+
stops: [
|
|
182
|
+
{
|
|
183
|
+
anchor: "nav./orders",
|
|
184
|
+
title: "Todo pedido cai aqui",
|
|
185
|
+
body: "Pago, em separa\xE7\xE3o, enviado \u2014 o estado de cada venda vive nesta lista.",
|
|
186
|
+
route: "/orders"
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
13
195
|
// src/integrations/melhor-envio/labelProvider.ts
|
|
14
196
|
var MELHOR_ENVIO_CARRIER = "melhor-envio";
|
|
15
197
|
function createMelhorEnvioLabelProvider(resolve4) {
|
|
@@ -172,20 +354,20 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
172
354
|
const product = item;
|
|
173
355
|
const shop = getProvider();
|
|
174
356
|
const supported = typeof shop.getProductVariants === "function" && typeof shop.saveProductOptions === "function" && typeof shop.createProductVariant === "function" && typeof shop.updateProductVariant === "function";
|
|
175
|
-
const [axes, setAxes] =
|
|
176
|
-
const [variants, setVariants] =
|
|
177
|
-
const [busy, setBusy] =
|
|
178
|
-
const [error, setError] =
|
|
179
|
-
const [selected, setSelected] =
|
|
180
|
-
const [dirty, setDirty] =
|
|
181
|
-
const reload =
|
|
357
|
+
const [axes, setAxes] = React13__default.useState([]);
|
|
358
|
+
const [variants, setVariants] = React13__default.useState([]);
|
|
359
|
+
const [busy, setBusy] = React13__default.useState(false);
|
|
360
|
+
const [error, setError] = React13__default.useState(null);
|
|
361
|
+
const [selected, setSelected] = React13__default.useState(/* @__PURE__ */ new Set());
|
|
362
|
+
const [dirty, setDirty] = React13__default.useState(false);
|
|
363
|
+
const reload = React13__default.useCallback(async () => {
|
|
182
364
|
const set = await getProvider().getProductVariants(product.id, { includeArchived: true });
|
|
183
365
|
setAxes(axesFromOptions(set.options));
|
|
184
366
|
setVariants(set.variants);
|
|
185
367
|
setSelected(/* @__PURE__ */ new Set());
|
|
186
368
|
setDirty(false);
|
|
187
369
|
}, [getProvider, product.id]);
|
|
188
|
-
|
|
370
|
+
React13__default.useEffect(() => {
|
|
189
371
|
if (!supported) return;
|
|
190
372
|
void reload().catch((e) => setError(message(e)));
|
|
191
373
|
}, [reload, supported]);
|
|
@@ -201,7 +383,7 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
201
383
|
setBusy(false);
|
|
202
384
|
}
|
|
203
385
|
}
|
|
204
|
-
const plan =
|
|
386
|
+
const plan = React13__default.useMemo(() => planVariantMatrix(axes, variants), [axes, variants]);
|
|
205
387
|
const axisError = validateVariantAxes(axes);
|
|
206
388
|
const onlyDefault = hasOnlyDefaultVariant(variants);
|
|
207
389
|
const rows = plan.rows;
|
|
@@ -302,7 +484,7 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
302
484
|
] });
|
|
303
485
|
}
|
|
304
486
|
function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }) {
|
|
305
|
-
const [
|
|
487
|
+
const [draft2, setDraft] = React13__default.useState("");
|
|
306
488
|
function commit(raw) {
|
|
307
489
|
const values = raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
308
490
|
if (values.length === 0) return;
|
|
@@ -357,16 +539,16 @@ function AxisEditor({ axis, index, count, disabled, onChange, onMove, onRemove }
|
|
|
357
539
|
/* @__PURE__ */ jsx(
|
|
358
540
|
"input",
|
|
359
541
|
{
|
|
360
|
-
value:
|
|
542
|
+
value: draft2,
|
|
361
543
|
disabled,
|
|
362
544
|
onChange: (e) => setDraft(e.target.value),
|
|
363
545
|
onKeyDown: (e) => {
|
|
364
546
|
if (e.key === "Enter") {
|
|
365
547
|
e.preventDefault();
|
|
366
|
-
commit(
|
|
548
|
+
commit(draft2);
|
|
367
549
|
}
|
|
368
550
|
},
|
|
369
|
-
onBlur: () => commit(
|
|
551
|
+
onBlur: () => commit(draft2),
|
|
370
552
|
placeholder: axis.values.length === 0 ? "P, M, G" : "adicionar",
|
|
371
553
|
"aria-label": `Valores da op\xE7\xE3o ${index + 1}`,
|
|
372
554
|
className: "h-7 min-w-28 flex-1 rounded-input border border-dashed border-input bg-transparent px-2 text-xs"
|
|
@@ -575,9 +757,9 @@ function VariantRow({ row, axes, product, busy, checked, onToggle, onSave, onSto
|
|
|
575
757
|
] });
|
|
576
758
|
}
|
|
577
759
|
function BulkBar({ count, disabled, onApply, onClear }) {
|
|
578
|
-
const [price, setPrice] =
|
|
579
|
-
const [weight, setWeight] =
|
|
580
|
-
const [unit, setUnit] =
|
|
760
|
+
const [price, setPrice] = React13__default.useState("");
|
|
761
|
+
const [weight, setWeight] = React13__default.useState("");
|
|
762
|
+
const [unit, setUnit] = React13__default.useState("");
|
|
581
763
|
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: [
|
|
582
764
|
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
583
765
|
count,
|
|
@@ -656,19 +838,19 @@ function parseNumber(raw) {
|
|
|
656
838
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
657
839
|
}
|
|
658
840
|
function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", integer }) {
|
|
659
|
-
const [
|
|
660
|
-
|
|
841
|
+
const [draft2, setDraft] = React13__default.useState(value == null ? "" : String(value));
|
|
842
|
+
React13__default.useEffect(() => {
|
|
661
843
|
setDraft(value == null ? "" : String(value));
|
|
662
844
|
}, [value]);
|
|
663
845
|
return /* @__PURE__ */ jsx(
|
|
664
846
|
"input",
|
|
665
847
|
{
|
|
666
|
-
value:
|
|
848
|
+
value: draft2,
|
|
667
849
|
disabled,
|
|
668
850
|
placeholder,
|
|
669
851
|
onChange: (e) => setDraft(e.target.value),
|
|
670
852
|
onBlur: () => {
|
|
671
|
-
const parsed = parseNumber(
|
|
853
|
+
const parsed = parseNumber(draft2);
|
|
672
854
|
onCommit(parsed == null ? null : integer ? Math.trunc(parsed) : parsed);
|
|
673
855
|
},
|
|
674
856
|
className: `h-7 ${width} rounded-input border border-input bg-background px-2 text-xs tabular-nums`
|
|
@@ -676,18 +858,18 @@ function NumberCell({ value, disabled, placeholder, onCommit, width = "w-20", in
|
|
|
676
858
|
);
|
|
677
859
|
}
|
|
678
860
|
function TextCell({ value, disabled, placeholder, onCommit }) {
|
|
679
|
-
const [
|
|
680
|
-
|
|
861
|
+
const [draft2, setDraft] = React13__default.useState(value);
|
|
862
|
+
React13__default.useEffect(() => {
|
|
681
863
|
setDraft(value);
|
|
682
864
|
}, [value]);
|
|
683
865
|
return /* @__PURE__ */ jsx(
|
|
684
866
|
"input",
|
|
685
867
|
{
|
|
686
|
-
value:
|
|
868
|
+
value: draft2,
|
|
687
869
|
disabled,
|
|
688
870
|
placeholder,
|
|
689
871
|
onChange: (e) => setDraft(e.target.value),
|
|
690
|
-
onBlur: () => onCommit(
|
|
872
|
+
onBlur: () => onCommit(draft2.trim()),
|
|
691
873
|
className: "h-7 w-28 rounded-input border border-input bg-background px-2 text-xs"
|
|
692
874
|
}
|
|
693
875
|
);
|
|
@@ -1007,7 +1189,7 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1007
1189
|
const [noteOpen, setNoteOpen] = useState(false);
|
|
1008
1190
|
const [occurredOn, setOccurredOn] = useState(() => todayISO());
|
|
1009
1191
|
const [saving, setSaving] = useState(false);
|
|
1010
|
-
const quantityRef =
|
|
1192
|
+
const quantityRef = React13__default.useRef(null);
|
|
1011
1193
|
const load = useCallback(async () => {
|
|
1012
1194
|
setLoading(true);
|
|
1013
1195
|
try {
|
|
@@ -1086,7 +1268,7 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1086
1268
|
const amount = Math.abs(Number(quantity) || 0);
|
|
1087
1269
|
const outgoing = type === "saida" || type === "perda";
|
|
1088
1270
|
const nextBalance = amount > 0 ? outgoing ? total.available - amount : total.available + amount : null;
|
|
1089
|
-
const live =
|
|
1271
|
+
const live = React13__default.useRef({ product, variantId, total, getProvider, load });
|
|
1090
1272
|
live.current = { product, variantId, total, getProvider, load };
|
|
1091
1273
|
const contribution = useMemo(() => ({
|
|
1092
1274
|
id: "shop.product.positions",
|
|
@@ -1352,20 +1534,20 @@ function shippingRegistryIdFromHash(hash) {
|
|
|
1352
1534
|
}
|
|
1353
1535
|
var PACKAGES_ROUTE = shippingRegistryPath(PACKAGES_REGISTRY);
|
|
1354
1536
|
function PackageSelect({ value, onChange, getProvider }) {
|
|
1355
|
-
const shop =
|
|
1356
|
-
const [rows, setRows] =
|
|
1357
|
-
const [open, setOpen] =
|
|
1358
|
-
const [creating, setCreating] =
|
|
1359
|
-
const ref =
|
|
1360
|
-
const load =
|
|
1537
|
+
const shop = React13__default.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
|
|
1538
|
+
const [rows, setRows] = React13__default.useState(null);
|
|
1539
|
+
const [open, setOpen] = React13__default.useState(false);
|
|
1540
|
+
const [creating, setCreating] = React13__default.useState(false);
|
|
1541
|
+
const ref = React13__default.useRef(null);
|
|
1542
|
+
const load = React13__default.useCallback(() => {
|
|
1361
1543
|
if (typeof shop.listShippingPackages !== "function") {
|
|
1362
1544
|
setRows([]);
|
|
1363
1545
|
return;
|
|
1364
1546
|
}
|
|
1365
1547
|
void shop.listShippingPackages().then(setRows).catch(() => setRows([]));
|
|
1366
1548
|
}, [shop]);
|
|
1367
|
-
|
|
1368
|
-
|
|
1549
|
+
React13__default.useEffect(load, [load]);
|
|
1550
|
+
React13__default.useEffect(() => {
|
|
1369
1551
|
if (!open) return;
|
|
1370
1552
|
const away = (event) => {
|
|
1371
1553
|
if (ref.current && !ref.current.contains(event.target)) setOpen(false);
|
|
@@ -1460,8 +1642,8 @@ function PackageSelect({ value, onChange, getProvider }) {
|
|
|
1460
1642
|
] });
|
|
1461
1643
|
}
|
|
1462
1644
|
function NewPackageModal({ onClose, onCreated }) {
|
|
1463
|
-
const [error, setError] =
|
|
1464
|
-
const provider =
|
|
1645
|
+
const [error, setError] = React13__default.useState(null);
|
|
1646
|
+
const provider = React13__default.useMemo(
|
|
1465
1647
|
() => resolveDataProvider(packagesRegistry.entity),
|
|
1466
1648
|
[]
|
|
1467
1649
|
);
|
|
@@ -1507,9 +1689,9 @@ function Option({ label, hint, active, onClick }) {
|
|
|
1507
1689
|
);
|
|
1508
1690
|
}
|
|
1509
1691
|
function ShippingProfileSelect({ value, onChange, getProvider }) {
|
|
1510
|
-
const shop =
|
|
1511
|
-
const [rows, setRows] =
|
|
1512
|
-
|
|
1692
|
+
const shop = React13__default.useMemo(() => getProvider?.() ?? getShopProvider(), [getProvider]);
|
|
1693
|
+
const [rows, setRows] = React13__default.useState(null);
|
|
1694
|
+
React13__default.useEffect(() => {
|
|
1513
1695
|
let cancelled = false;
|
|
1514
1696
|
if (typeof shop.listShippingProfiles !== "function") {
|
|
1515
1697
|
setRows([]);
|
|
@@ -2297,7 +2479,7 @@ var STATUS_LABEL2 = {
|
|
|
2297
2479
|
disabled: "Desativado"
|
|
2298
2480
|
};
|
|
2299
2481
|
function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
2300
|
-
const
|
|
2482
|
+
const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
|
|
2301
2483
|
const fields = [
|
|
2302
2484
|
{
|
|
2303
2485
|
key: "code",
|
|
@@ -2325,7 +2507,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
|
2325
2507
|
hint: "Valor de face do cart\xE3o. N\xE3o muda depois de emitido.",
|
|
2326
2508
|
showInTable: true,
|
|
2327
2509
|
sortable: true,
|
|
2328
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children:
|
|
2510
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money3(v) })
|
|
2329
2511
|
},
|
|
2330
2512
|
{
|
|
2331
2513
|
key: "balance",
|
|
@@ -2339,7 +2521,7 @@ function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
|
2339
2521
|
hint: "O que ainda pode ser gasto. Cai a cada resgate.",
|
|
2340
2522
|
showInTable: true,
|
|
2341
2523
|
sortable: true,
|
|
2342
|
-
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children:
|
|
2524
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money3(v) })
|
|
2343
2525
|
},
|
|
2344
2526
|
{
|
|
2345
2527
|
key: "status",
|
|
@@ -2474,20 +2656,20 @@ function buildShopCustomerGroupEntity() {
|
|
|
2474
2656
|
fieldGroups: [{ id: "basic", label: "Grupo", columns: 2 }]
|
|
2475
2657
|
};
|
|
2476
2658
|
}
|
|
2477
|
-
function describeSegment(s,
|
|
2659
|
+
function describeSegment(s, money3) {
|
|
2478
2660
|
const parts = [];
|
|
2479
2661
|
if (s.minOrders != null && s.maxOrders != null) parts.push(`${s.minOrders}\u2013${s.maxOrders} pedidos`);
|
|
2480
2662
|
else if (s.minOrders != null) parts.push(`${s.minOrders}+ pedidos`);
|
|
2481
2663
|
else if (s.maxOrders != null) parts.push(`at\xE9 ${s.maxOrders} pedidos`);
|
|
2482
|
-
if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${
|
|
2483
|
-
else if (s.minSpent != null) parts.push(`gastou ${
|
|
2484
|
-
else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${
|
|
2664
|
+
if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${money3(s.minSpent)}\u2013${money3(s.maxSpent)}`);
|
|
2665
|
+
else if (s.minSpent != null) parts.push(`gastou ${money3(s.minSpent)}+`);
|
|
2666
|
+
else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${money3(s.maxSpent)}`);
|
|
2485
2667
|
if (s.joinedAfter) parts.push(`entrou ap\xF3s ${new Date(s.joinedAfter).toLocaleDateString("pt-BR")}`);
|
|
2486
2668
|
if (s.joinedBefore) parts.push(`entrou antes de ${new Date(s.joinedBefore).toLocaleDateString("pt-BR")}`);
|
|
2487
2669
|
return parts.length ? parts.join(" \xB7 ") : "Todos os clientes";
|
|
2488
2670
|
}
|
|
2489
2671
|
function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
|
|
2490
|
-
const
|
|
2672
|
+
const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
|
|
2491
2673
|
const fields = [
|
|
2492
2674
|
{
|
|
2493
2675
|
key: "name",
|
|
@@ -2564,7 +2746,7 @@ function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
|
|
|
2564
2746
|
showInForm: false,
|
|
2565
2747
|
showInTable: true,
|
|
2566
2748
|
sortable: false,
|
|
2567
|
-
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row,
|
|
2749
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money3) })
|
|
2568
2750
|
},
|
|
2569
2751
|
{
|
|
2570
2752
|
key: "matchCount",
|
|
@@ -2894,17 +3076,17 @@ function createShopCustomerSegmentDataProvider(provider) {
|
|
|
2894
3076
|
}
|
|
2895
3077
|
var DEBOUNCE_MS = 400;
|
|
2896
3078
|
function useStoreSettings(provider, defaults = {}) {
|
|
2897
|
-
const shop =
|
|
3079
|
+
const shop = React13__default.useMemo(
|
|
2898
3080
|
() => provider ? resolve(provider) : getShopProvider(),
|
|
2899
3081
|
[provider]
|
|
2900
3082
|
);
|
|
2901
3083
|
const supported = typeof shop.getStoreSettings === "function" && typeof shop.updateStoreSettings === "function";
|
|
2902
|
-
const [stored, setStored] =
|
|
2903
|
-
const [loading, setLoading] =
|
|
2904
|
-
const [saving, setSaving] =
|
|
2905
|
-
const [error, setError] =
|
|
2906
|
-
const [loadError, setLoadError] =
|
|
2907
|
-
|
|
3084
|
+
const [stored, setStored] = React13__default.useState(null);
|
|
3085
|
+
const [loading, setLoading] = React13__default.useState(supported);
|
|
3086
|
+
const [saving, setSaving] = React13__default.useState(false);
|
|
3087
|
+
const [error, setError] = React13__default.useState(null);
|
|
3088
|
+
const [loadError, setLoadError] = React13__default.useState(null);
|
|
3089
|
+
React13__default.useEffect(() => {
|
|
2908
3090
|
if (!supported) return;
|
|
2909
3091
|
let cancelled = false;
|
|
2910
3092
|
setLoading(true);
|
|
@@ -2920,12 +3102,12 @@ function useStoreSettings(provider, defaults = {}) {
|
|
|
2920
3102
|
cancelled = true;
|
|
2921
3103
|
};
|
|
2922
3104
|
}, [shop, supported]);
|
|
2923
|
-
const pendingRef =
|
|
2924
|
-
const timerRef =
|
|
2925
|
-
|
|
3105
|
+
const pendingRef = React13__default.useRef({});
|
|
3106
|
+
const timerRef = React13__default.useRef(null);
|
|
3107
|
+
React13__default.useEffect(() => () => {
|
|
2926
3108
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2927
3109
|
}, []);
|
|
2928
|
-
const set =
|
|
3110
|
+
const set = React13__default.useCallback(
|
|
2929
3111
|
(patch) => {
|
|
2930
3112
|
setStored((current) => ({ ...current ?? {}, ...patch }));
|
|
2931
3113
|
if (!supported) return;
|
|
@@ -2941,7 +3123,7 @@ function useStoreSettings(provider, defaults = {}) {
|
|
|
2941
3123
|
},
|
|
2942
3124
|
[shop, supported]
|
|
2943
3125
|
);
|
|
2944
|
-
const value =
|
|
3126
|
+
const value = React13__default.useMemo(() => ({ ...defaults, ...stored ?? {} }), [defaults, stored]);
|
|
2945
3127
|
return { value, stored, set, loading, saving, error, loadError, supported };
|
|
2946
3128
|
}
|
|
2947
3129
|
function SettingsState({
|
|
@@ -3170,25 +3352,25 @@ function PaymentsSettings({ provider }) {
|
|
|
3170
3352
|
const settings = useStoreSettings(provider);
|
|
3171
3353
|
const { value, set } = settings;
|
|
3172
3354
|
const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
|
|
3173
|
-
const [adding, setAdding] =
|
|
3174
|
-
const [selected, setSelected] =
|
|
3355
|
+
const [adding, setAdding] = React13__default.useState(false);
|
|
3356
|
+
const [selected, setSelected] = React13__default.useState(null);
|
|
3175
3357
|
const panelAvailable = useConnectorsPanelAvailable();
|
|
3176
|
-
const openJourney =
|
|
3358
|
+
const openJourney = React13__default.useCallback(
|
|
3177
3359
|
(connector) => {
|
|
3178
3360
|
if (panelAvailable) openConnectorsPanel(connector);
|
|
3179
3361
|
else setSelected(connector);
|
|
3180
3362
|
},
|
|
3181
3363
|
[panelAvailable]
|
|
3182
3364
|
);
|
|
3183
|
-
const payment =
|
|
3365
|
+
const payment = React13__default.useMemo(
|
|
3184
3366
|
() => (connectors ?? []).filter((c) => c.category && PAYMENT_CATEGORIES.includes(c.category)),
|
|
3185
3367
|
[connectors]
|
|
3186
3368
|
);
|
|
3187
|
-
const live =
|
|
3369
|
+
const live = React13__default.useMemo(() => payment.filter((c) => c.state === "connected"), [payment]);
|
|
3188
3370
|
const chosen = value.paymentMethods ?? [];
|
|
3189
3371
|
const staleProvider = !!value.paymentProvider && !live.some((c) => c.slug === value.paymentProvider) && live.length === 0;
|
|
3190
|
-
const movedFrom =
|
|
3191
|
-
|
|
3372
|
+
const movedFrom = React13__default.useRef(null);
|
|
3373
|
+
React13__default.useEffect(() => {
|
|
3192
3374
|
if (!settings.supported || settings.loading) return;
|
|
3193
3375
|
const next = resolvePaymentProvider(value.paymentProvider, live.map((c) => c.slug));
|
|
3194
3376
|
if (!next) return;
|
|
@@ -3420,11 +3602,11 @@ function PackagesSettings({ basePath = shippingRegistryPath(PACKAGES_REGISTRY) }
|
|
|
3420
3602
|
] });
|
|
3421
3603
|
}
|
|
3422
3604
|
function HandlingSettings({ settings, provider }) {
|
|
3423
|
-
const shop =
|
|
3605
|
+
const shop = React13__default.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
|
|
3424
3606
|
const { value, set, loading, supported } = settings;
|
|
3425
|
-
const [noteDraft, setNoteDraft] =
|
|
3426
|
-
const [profiles, setProfiles] =
|
|
3427
|
-
|
|
3607
|
+
const [noteDraft, setNoteDraft] = React13__default.useState(null);
|
|
3608
|
+
const [profiles, setProfiles] = React13__default.useState(null);
|
|
3609
|
+
React13__default.useEffect(() => {
|
|
3428
3610
|
let cancelled = false;
|
|
3429
3611
|
void shop.listShippingProfiles?.().then((list) => {
|
|
3430
3612
|
if (!cancelled) setProfiles(list);
|
|
@@ -3532,12 +3714,988 @@ function ChoiceCards({
|
|
|
3532
3714
|
);
|
|
3533
3715
|
}) });
|
|
3534
3716
|
}
|
|
3717
|
+
var EMPTY = { rules: [], gaps: [] };
|
|
3718
|
+
var draft = EMPTY;
|
|
3719
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
3720
|
+
var seq = 0;
|
|
3721
|
+
var nextId = () => `rule-${++seq}`;
|
|
3722
|
+
var identity = (rule) => [rule.matchKind, rule.cityCode ?? "", rule.districtKey ?? "", rule.postalFrom ?? "", rule.postalTo ?? ""].join("|");
|
|
3723
|
+
function emit() {
|
|
3724
|
+
for (const listener of listeners) listener();
|
|
3725
|
+
}
|
|
3726
|
+
function getDeliveryDraft() {
|
|
3727
|
+
return draft;
|
|
3728
|
+
}
|
|
3729
|
+
function setDeliveryDraft(next) {
|
|
3730
|
+
draft = { ...next, rules: next.rules.map((rule) => ({ ...rule, id: rule.id || nextId() })) };
|
|
3731
|
+
emit();
|
|
3732
|
+
}
|
|
3733
|
+
function addDeliveryDraftRules(incoming) {
|
|
3734
|
+
const seen = new Set(draft.rules.map(identity));
|
|
3735
|
+
const added = [];
|
|
3736
|
+
for (const rule of incoming) {
|
|
3737
|
+
const key = identity(rule);
|
|
3738
|
+
if (seen.has(key)) continue;
|
|
3739
|
+
seen.add(key);
|
|
3740
|
+
added.push({ ...rule, id: nextId() });
|
|
3741
|
+
}
|
|
3742
|
+
if (added.length === 0) return 0;
|
|
3743
|
+
draft = { ...draft, rules: [...draft.rules, ...added] };
|
|
3744
|
+
emit();
|
|
3745
|
+
return added.length;
|
|
3746
|
+
}
|
|
3747
|
+
function isInDeliveryDraft(rule) {
|
|
3748
|
+
const key = identity(rule);
|
|
3749
|
+
return draft.rules.some((existing) => identity(existing) === key);
|
|
3750
|
+
}
|
|
3751
|
+
function setAllDeliveryDraftRates(patch) {
|
|
3752
|
+
let touched = 0;
|
|
3753
|
+
const rules = draft.rules.map((rule) => {
|
|
3754
|
+
if (rule.outcome === "refuse") return rule;
|
|
3755
|
+
touched++;
|
|
3756
|
+
return { ...rule, ...patch };
|
|
3757
|
+
});
|
|
3758
|
+
if (touched === 0) return 0;
|
|
3759
|
+
draft = { ...draft, rules };
|
|
3760
|
+
emit();
|
|
3761
|
+
return touched;
|
|
3762
|
+
}
|
|
3763
|
+
function updateDeliveryDraftRule(id, patch) {
|
|
3764
|
+
const index = draft.rules.findIndex((rule) => rule.id === id);
|
|
3765
|
+
if (index < 0) return false;
|
|
3766
|
+
const rules = draft.rules.slice();
|
|
3767
|
+
rules[index] = { ...rules[index], ...patch, id };
|
|
3768
|
+
draft = { ...draft, rules };
|
|
3769
|
+
emit();
|
|
3770
|
+
return true;
|
|
3771
|
+
}
|
|
3772
|
+
function removeDeliveryDraftRule(id) {
|
|
3773
|
+
const rules = draft.rules.filter((rule) => rule.id !== id);
|
|
3774
|
+
if (rules.length === draft.rules.length) return false;
|
|
3775
|
+
draft = { ...draft, rules };
|
|
3776
|
+
emit();
|
|
3777
|
+
return true;
|
|
3778
|
+
}
|
|
3779
|
+
function clearDeliveryDraft() {
|
|
3780
|
+
draft = EMPTY;
|
|
3781
|
+
emit();
|
|
3782
|
+
}
|
|
3783
|
+
function subscribeDeliveryDraft(listener) {
|
|
3784
|
+
listeners.add(listener);
|
|
3785
|
+
return () => {
|
|
3786
|
+
listeners.delete(listener);
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
function useDeliveryDraft() {
|
|
3790
|
+
return React13.useSyncExternalStore(subscribeDeliveryDraft, getDeliveryDraft, getDeliveryDraft);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
// src/views/delivery-agent.ts
|
|
3794
|
+
var DELIVERY_AREAS_INSTRUCTIONS = `
|
|
3795
|
+
Esta \xE9 a tela de Entrega da loja. Aqui voc\xEA configura ONDE a loja entrega, ONDE ela recusa e onde precisa confirmar antes.
|
|
3796
|
+
|
|
3797
|
+
Uma regra tem duas partes: a \xC1REA (um munic\xEDpio ou um bairro) e o DESFECHO:
|
|
3798
|
+
\xB7 entrego (deliver) \xB7 confirmo antes (review) \xB7 n\xE3o entrego (refuse)
|
|
3799
|
+
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.
|
|
3800
|
+
|
|
3801
|
+
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.
|
|
3802
|
+
|
|
3803
|
+
Quando a lojista colar uma mensagem descrevendo a opera\xE7\xE3o dela:
|
|
3804
|
+
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.
|
|
3805
|
+
2. Chame \`findDeliveryAreas\` UMA vez com todos os nomes juntos.
|
|
3806
|
+
\xB7 \`resolved\` est\xE1 pronto para virar regra.
|
|
3807
|
+
\xB7 \`options\` \xE9 amb\xEDguo: pergunte qual, n\xE3o escolha.
|
|
3808
|
+
\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".
|
|
3809
|
+
\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.
|
|
3810
|
+
3. Chame \`proposeDeliveryRules\` com o que resolveu. Ele N\xC3O grava \u2014 desenha a tabela na tela dela.
|
|
3811
|
+
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?".
|
|
3812
|
+
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.
|
|
3813
|
+
|
|
3814
|
+
Regras:
|
|
3815
|
+
\xB7 Voc\xEA NUNCA escreve um CEP ou um c\xF3digo de munic\xEDpio que n\xE3o tenha vindo de \`findDeliveryAreas\`.
|
|
3816
|
+
\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.
|
|
3817
|
+
\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.
|
|
3818
|
+
\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.
|
|
3819
|
+
\xB7 Portugu\xEAs do Brasil, curto.
|
|
3820
|
+
`.trim();
|
|
3821
|
+
var FIND_AREAS = {
|
|
3822
|
+
id: "shop.delivery.find-areas",
|
|
3823
|
+
name: "findDeliveryAreas",
|
|
3824
|
+
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.',
|
|
3825
|
+
icon: "MapPin",
|
|
3826
|
+
mode: "read",
|
|
3827
|
+
execution: { plane: "client" },
|
|
3828
|
+
category: "Shop",
|
|
3829
|
+
role: "operations",
|
|
3830
|
+
parameters: {
|
|
3831
|
+
type: "object",
|
|
3832
|
+
properties: {
|
|
3833
|
+
queries: {
|
|
3834
|
+
type: "array",
|
|
3835
|
+
description: 'The place names as the merchant wrote them, without "n\xE3o entrego" / "exceto".',
|
|
3836
|
+
items: { type: "string" }
|
|
3837
|
+
}
|
|
3838
|
+
},
|
|
3839
|
+
required: ["queries"]
|
|
3840
|
+
}
|
|
3841
|
+
};
|
|
3842
|
+
var PROPOSE_RULES = {
|
|
3843
|
+
id: "shop.delivery.propose-rules",
|
|
3844
|
+
name: "proposeDeliveryRules",
|
|
3845
|
+
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.",
|
|
3846
|
+
icon: "ListChecks",
|
|
3847
|
+
mode: "read",
|
|
3848
|
+
execution: { plane: "client" },
|
|
3849
|
+
category: "Shop",
|
|
3850
|
+
role: "operations",
|
|
3851
|
+
parameters: {
|
|
3852
|
+
type: "object",
|
|
3853
|
+
properties: {
|
|
3854
|
+
rules: {
|
|
3855
|
+
type: "array",
|
|
3856
|
+
description: "One entry per area named in the message.",
|
|
3857
|
+
items: {
|
|
3858
|
+
type: "object",
|
|
3859
|
+
properties: {
|
|
3860
|
+
kind: {
|
|
3861
|
+
type: "string",
|
|
3862
|
+
enum: ["city", "district", "group", "postal_range"],
|
|
3863
|
+
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."
|
|
3864
|
+
},
|
|
3865
|
+
label: { type: "string", description: "The place name, as it should read on the table." },
|
|
3866
|
+
cityCode: { type: "string", description: "IBGE code from findDeliveryAreas." },
|
|
3867
|
+
districtKey: { type: "string", description: "Neighbourhood key from findDeliveryAreas." },
|
|
3868
|
+
postalFrom: { type: "string", description: "Only for postal_range." },
|
|
3869
|
+
postalTo: { type: "string", description: "Only for postal_range." },
|
|
3870
|
+
outcome: {
|
|
3871
|
+
type: "string",
|
|
3872
|
+
enum: ["deliver", "review", "refuse"],
|
|
3873
|
+
description: "deliver | review (confirm before dispatch) | refuse."
|
|
3874
|
+
},
|
|
3875
|
+
rate: { type: "number", description: "Only if the message stated a price. Otherwise omit." },
|
|
3876
|
+
freeAbove: { type: "number", description: "Only if the message stated a threshold." },
|
|
3877
|
+
etaMinDays: { type: "number" },
|
|
3878
|
+
etaMaxDays: { type: "number" },
|
|
3879
|
+
source: { type: "string", description: "The words of the message this rule came from." }
|
|
3880
|
+
},
|
|
3881
|
+
required: ["kind", "label", "outcome"]
|
|
3882
|
+
}
|
|
3883
|
+
},
|
|
3884
|
+
gaps: {
|
|
3885
|
+
type: "array",
|
|
3886
|
+
description: "What the message named and you could NOT resolve, so the merchant sees it too.",
|
|
3887
|
+
items: {
|
|
3888
|
+
type: "object",
|
|
3889
|
+
properties: {
|
|
3890
|
+
query: { type: "string" },
|
|
3891
|
+
reason: { type: "string", description: "Plain Portuguese, one line." }
|
|
3892
|
+
},
|
|
3893
|
+
required: ["query", "reason"]
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
},
|
|
3897
|
+
required: ["rules"]
|
|
3898
|
+
}
|
|
3899
|
+
};
|
|
3900
|
+
var SET_RULE = {
|
|
3901
|
+
id: "shop.delivery.set-rule",
|
|
3902
|
+
name: "setDeliveryRule",
|
|
3903
|
+
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.",
|
|
3904
|
+
icon: "Pencil",
|
|
3905
|
+
mode: "read",
|
|
3906
|
+
execution: { plane: "client" },
|
|
3907
|
+
category: "Shop",
|
|
3908
|
+
role: "operations",
|
|
3909
|
+
parameters: {
|
|
3910
|
+
type: "object",
|
|
3911
|
+
properties: {
|
|
3912
|
+
id: { type: "string", description: "The rule id proposeDeliveryRules answered." },
|
|
3913
|
+
outcome: { type: "string", enum: ["deliver", "review", "refuse"] },
|
|
3914
|
+
rate: { type: "number" },
|
|
3915
|
+
freeAbove: { type: "number" },
|
|
3916
|
+
etaMinDays: { type: "number" },
|
|
3917
|
+
etaMaxDays: { type: "number" }
|
|
3918
|
+
},
|
|
3919
|
+
required: ["id"]
|
|
3920
|
+
}
|
|
3921
|
+
};
|
|
3922
|
+
var COMMIT_RULES = {
|
|
3923
|
+
id: "shop.delivery.commit-rules",
|
|
3924
|
+
name: "commitDeliveryRules",
|
|
3925
|
+
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.",
|
|
3926
|
+
icon: "Save",
|
|
3927
|
+
mode: "persist",
|
|
3928
|
+
execution: { plane: "client" },
|
|
3929
|
+
category: "Shop",
|
|
3930
|
+
role: "operations",
|
|
3931
|
+
permission: { feature: "shop", action: "update" },
|
|
3932
|
+
parameters: { type: "object", properties: {} }
|
|
3933
|
+
};
|
|
3934
|
+
var CHECK_DELIVERY = {
|
|
3935
|
+
id: "shop.delivery.check",
|
|
3936
|
+
name: "checkDeliveryFor",
|
|
3937
|
+
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?".',
|
|
3938
|
+
icon: "Search",
|
|
3939
|
+
mode: "read",
|
|
3940
|
+
execution: { plane: "client" },
|
|
3941
|
+
category: "Shop",
|
|
3942
|
+
role: "operations",
|
|
3943
|
+
parameters: {
|
|
3944
|
+
type: "object",
|
|
3945
|
+
properties: {
|
|
3946
|
+
postalCodes: {
|
|
3947
|
+
type: "array",
|
|
3948
|
+
description: "One or more CEPs, with or without punctuation.",
|
|
3949
|
+
items: { type: "string" }
|
|
3950
|
+
}
|
|
3951
|
+
},
|
|
3952
|
+
required: ["postalCodes"]
|
|
3953
|
+
}
|
|
3954
|
+
};
|
|
3955
|
+
[
|
|
3956
|
+
FIND_AREAS.id,
|
|
3957
|
+
PROPOSE_RULES.id,
|
|
3958
|
+
SET_RULE.id,
|
|
3959
|
+
COMMIT_RULES.id,
|
|
3960
|
+
CHECK_DELIVERY.id
|
|
3961
|
+
];
|
|
3962
|
+
var text2 = (value) => typeof value === "string" ? value.trim() : "";
|
|
3963
|
+
var number = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3964
|
+
var OUTCOMES = ["deliver", "review", "refuse"];
|
|
3965
|
+
var outcomeOf = (value) => OUTCOMES.includes(value) ? value : "deliver";
|
|
3966
|
+
function candidateOut(area) {
|
|
3967
|
+
return {
|
|
3968
|
+
kind: area.kind,
|
|
3969
|
+
label: area.label,
|
|
3970
|
+
cityCode: area.cityCode ?? void 0,
|
|
3971
|
+
cityName: area.cityName ?? void 0,
|
|
3972
|
+
state: area.state ?? void 0,
|
|
3973
|
+
districtKey: area.districtKey ?? void 0,
|
|
3974
|
+
knownPostalCodes: area.knownPostalCodes || void 0,
|
|
3975
|
+
members: area.members?.map((m) => ({
|
|
3976
|
+
kind: m.kind,
|
|
3977
|
+
label: m.label,
|
|
3978
|
+
cityCode: m.cityCode ?? void 0,
|
|
3979
|
+
districtKey: m.districtKey ?? void 0
|
|
3980
|
+
}))
|
|
3981
|
+
};
|
|
3982
|
+
}
|
|
3983
|
+
function pickCandidate(query, areas) {
|
|
3984
|
+
if (areas.length === 0) return {};
|
|
3985
|
+
if (areas.length === 1) return { resolved: areas[0] };
|
|
3986
|
+
const key = query.trim().toLowerCase();
|
|
3987
|
+
const exact = areas.filter((a) => a.label.trim().toLowerCase() === key);
|
|
3988
|
+
if (exact.length === 1) return { resolved: exact[0] };
|
|
3989
|
+
return { options: areas.slice(0, 6) };
|
|
3990
|
+
}
|
|
3991
|
+
function ruleFromArgs(raw, index) {
|
|
3992
|
+
const kind = text2(raw.kind);
|
|
3993
|
+
const label = text2(raw.label);
|
|
3994
|
+
if (!label) return null;
|
|
3995
|
+
const base = {
|
|
3996
|
+
id: `draft-${index}`,
|
|
3997
|
+
label,
|
|
3998
|
+
outcome: outcomeOf(raw.outcome),
|
|
3999
|
+
rate: number(raw.rate),
|
|
4000
|
+
freeAbove: number(raw.freeAbove),
|
|
4001
|
+
etaMinDays: number(raw.etaMinDays),
|
|
4002
|
+
etaMaxDays: number(raw.etaMaxDays),
|
|
4003
|
+
source: text2(raw.source) || void 0
|
|
4004
|
+
};
|
|
4005
|
+
if (kind === "city") {
|
|
4006
|
+
const cityCode = text2(raw.cityCode);
|
|
4007
|
+
if (!cityCode) return null;
|
|
4008
|
+
return { ...base, matchKind: "city", cityCode };
|
|
4009
|
+
}
|
|
4010
|
+
if (kind === "district") {
|
|
4011
|
+
const districtKey = text2(raw.districtKey);
|
|
4012
|
+
if (!districtKey) return null;
|
|
4013
|
+
return {
|
|
4014
|
+
...base,
|
|
4015
|
+
matchKind: "district",
|
|
4016
|
+
districtKey,
|
|
4017
|
+
cityCode: text2(raw.cityCode) || null
|
|
4018
|
+
};
|
|
4019
|
+
}
|
|
4020
|
+
if (kind === "postal_range") {
|
|
4021
|
+
const from = text2(raw.postalFrom).replace(/\D/g, "");
|
|
4022
|
+
const to = text2(raw.postalTo).replace(/\D/g, "");
|
|
4023
|
+
if (from.length !== 8 || to.length !== 8) return null;
|
|
4024
|
+
return { ...base, matchKind: "postal_range", postalFrom: from, postalTo: to };
|
|
4025
|
+
}
|
|
4026
|
+
return null;
|
|
4027
|
+
}
|
|
4028
|
+
function zoneInputFrom(rule) {
|
|
4029
|
+
return {
|
|
4030
|
+
name: rule.label,
|
|
4031
|
+
carrier: null,
|
|
4032
|
+
matchKind: rule.matchKind,
|
|
4033
|
+
cityCode: rule.cityCode ?? null,
|
|
4034
|
+
districtKey: rule.districtKey ?? null,
|
|
4035
|
+
outcome: rule.outcome,
|
|
4036
|
+
postalFrom: rule.postalFrom ?? null,
|
|
4037
|
+
postalTo: rule.postalTo ?? null,
|
|
4038
|
+
// Uma regra de recusa não cobra nada: o preço dela nunca é lido.
|
|
4039
|
+
rate: rule.outcome === "refuse" ? 0 : rule.rate ?? 0,
|
|
4040
|
+
freeAbove: rule.freeAbove ?? null,
|
|
4041
|
+
etaMinDays: rule.etaMinDays ?? null,
|
|
4042
|
+
etaMaxDays: rule.etaMaxDays ?? null,
|
|
4043
|
+
active: true,
|
|
4044
|
+
sortOrder: 0,
|
|
4045
|
+
excluded: rule.outcome === "refuse"
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
4048
|
+
function createDeliveryAreaTools({ getProvider, onSaved }) {
|
|
4049
|
+
const failed = (error) => ({ ok: false, error: errorMessage(error) });
|
|
4050
|
+
return [
|
|
4051
|
+
{
|
|
4052
|
+
tool: FIND_AREAS,
|
|
4053
|
+
execute: async (args) => {
|
|
4054
|
+
const queries = (Array.isArray(args.queries) ? args.queries : []).map(text2).filter(Boolean).slice(0, 30);
|
|
4055
|
+
if (queries.length === 0) return { ok: false, error: "no_queries" };
|
|
4056
|
+
try {
|
|
4057
|
+
const shop = getProvider();
|
|
4058
|
+
if (typeof shop.findAreas !== "function") {
|
|
4059
|
+
return { ok: false, error: "area_lookup_unavailable" };
|
|
4060
|
+
}
|
|
4061
|
+
const results = await Promise.all(
|
|
4062
|
+
queries.map(async (query) => {
|
|
4063
|
+
const areas = await shop.findAreas(query, 8).catch(() => []);
|
|
4064
|
+
const { resolved, options } = pickCandidate(query, areas);
|
|
4065
|
+
if (resolved) return { query, resolved: candidateOut(resolved) };
|
|
4066
|
+
if (options) return { query, options: options.map(candidateOut) };
|
|
4067
|
+
return {
|
|
4068
|
+
query,
|
|
4069
|
+
notFound: true,
|
|
4070
|
+
// Dito aqui e não deixado para o modelo deduzir: é a frase que
|
|
4071
|
+
// separa "não existe" de "existe mas não dá para separar por CEP".
|
|
4072
|
+
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."
|
|
4073
|
+
};
|
|
4074
|
+
})
|
|
4075
|
+
);
|
|
4076
|
+
return { ok: true, results };
|
|
4077
|
+
} catch (error) {
|
|
4078
|
+
return failed(error);
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
},
|
|
4082
|
+
{
|
|
4083
|
+
tool: PROPOSE_RULES,
|
|
4084
|
+
execute: async (args) => {
|
|
4085
|
+
const raw = Array.isArray(args.rules) ? args.rules : [];
|
|
4086
|
+
const rules = [];
|
|
4087
|
+
const gaps = (Array.isArray(args.gaps) ? args.gaps : []).map((g) => ({ query: text2(g?.query), reason: text2(g?.reason) })).filter((g) => g.query);
|
|
4088
|
+
raw.forEach((entry, index) => {
|
|
4089
|
+
const rule = ruleFromArgs(entry ?? {}, rules.length);
|
|
4090
|
+
if (rule) {
|
|
4091
|
+
rules.push(rule);
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
gaps.push({
|
|
4095
|
+
query: text2(entry?.label) || `regra ${index + 1}`,
|
|
4096
|
+
reason: "Faltou o identificador da \xE1rea. Chame findDeliveryAreas para este nome e use o que ele responder."
|
|
4097
|
+
});
|
|
4098
|
+
});
|
|
4099
|
+
if (rules.length === 0 && gaps.length === 0) return { ok: false, error: "empty_draft" };
|
|
4100
|
+
setDeliveryDraft({ rules, gaps });
|
|
4101
|
+
return {
|
|
4102
|
+
ok: true,
|
|
4103
|
+
drafted: rules.length,
|
|
4104
|
+
refusing: rules.filter((r) => r.outcome === "refuse").length,
|
|
4105
|
+
reviewing: rules.filter((r) => r.outcome === "review").length,
|
|
4106
|
+
missingPrice: rules.filter((r) => r.outcome !== "refuse" && r.rate == null).length,
|
|
4107
|
+
gaps,
|
|
4108
|
+
rules: rules.map((r) => ({ id: r.id, label: r.label, outcome: r.outcome })),
|
|
4109
|
+
nextStep: "commitDeliveryRules"
|
|
4110
|
+
};
|
|
4111
|
+
}
|
|
4112
|
+
},
|
|
4113
|
+
{
|
|
4114
|
+
tool: SET_RULE,
|
|
4115
|
+
execute: async (args) => {
|
|
4116
|
+
const id = text2(args.id);
|
|
4117
|
+
if (!id) return { ok: false, error: "no_id" };
|
|
4118
|
+
const patch = {};
|
|
4119
|
+
if (args.outcome !== void 0) patch.outcome = outcomeOf(args.outcome);
|
|
4120
|
+
if (args.rate !== void 0) patch.rate = number(args.rate);
|
|
4121
|
+
if (args.freeAbove !== void 0) patch.freeAbove = number(args.freeAbove);
|
|
4122
|
+
if (args.etaMinDays !== void 0) patch.etaMinDays = number(args.etaMinDays);
|
|
4123
|
+
if (args.etaMaxDays !== void 0) patch.etaMaxDays = number(args.etaMaxDays);
|
|
4124
|
+
if (Object.keys(patch).length === 0) return { ok: false, error: "nothing_to_change" };
|
|
4125
|
+
if (!updateDeliveryDraftRule(id, patch)) return { ok: false, error: "rule_not_in_draft" };
|
|
4126
|
+
return { ok: true, id, ...patch };
|
|
4127
|
+
}
|
|
4128
|
+
},
|
|
4129
|
+
{
|
|
4130
|
+
tool: COMMIT_RULES,
|
|
4131
|
+
execute: async () => {
|
|
4132
|
+
const { rules } = getDeliveryDraft();
|
|
4133
|
+
if (rules.length === 0) return { ok: false, error: "nothing_to_save" };
|
|
4134
|
+
try {
|
|
4135
|
+
const shop = getProvider();
|
|
4136
|
+
if (typeof shop.createShippingZone !== "function") {
|
|
4137
|
+
return { ok: false, error: "zones_unavailable" };
|
|
4138
|
+
}
|
|
4139
|
+
const saved = [];
|
|
4140
|
+
const rejected = [];
|
|
4141
|
+
for (const rule of rules) {
|
|
4142
|
+
try {
|
|
4143
|
+
await shop.createShippingZone(zoneInputFrom(rule));
|
|
4144
|
+
saved.push(rule.label);
|
|
4145
|
+
} catch (error) {
|
|
4146
|
+
rejected.push({ label: rule.label, error: errorMessage(error) });
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
if (saved.length > 0) {
|
|
4150
|
+
clearDeliveryDraft();
|
|
4151
|
+
onSaved();
|
|
4152
|
+
}
|
|
4153
|
+
return { ok: rejected.length === 0, saved: saved.length, rejected };
|
|
4154
|
+
} catch (error) {
|
|
4155
|
+
return failed(error);
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
},
|
|
4159
|
+
{
|
|
4160
|
+
tool: CHECK_DELIVERY,
|
|
4161
|
+
execute: async (args) => {
|
|
4162
|
+
const codes = (Array.isArray(args.postalCodes) ? args.postalCodes : []).map((c) => text2(c).replace(/\D/g, "")).filter((c) => c.length === 8).slice(0, 10);
|
|
4163
|
+
if (codes.length === 0) return { ok: false, error: "no_postal_codes" };
|
|
4164
|
+
try {
|
|
4165
|
+
const shop = getProvider();
|
|
4166
|
+
if (typeof shop.checkDelivery !== "function") {
|
|
4167
|
+
return { ok: false, error: "delivery_check_unavailable" };
|
|
4168
|
+
}
|
|
4169
|
+
const results = await Promise.all(
|
|
4170
|
+
codes.map(async (code) => {
|
|
4171
|
+
const check = await shop.checkDelivery(code);
|
|
4172
|
+
return {
|
|
4173
|
+
postalCode: code,
|
|
4174
|
+
// null = nenhuma regra casou: fora de área, que não é o mesmo
|
|
4175
|
+
// que recusado e o texto para a lojista é outro.
|
|
4176
|
+
outcome: check.outcome ?? null,
|
|
4177
|
+
rule: check.zoneName ?? check.refusedZone ?? null,
|
|
4178
|
+
outsideEveryRule: !check.refused && !check.ownZone
|
|
4179
|
+
};
|
|
4180
|
+
})
|
|
4181
|
+
);
|
|
4182
|
+
return { ok: true, results };
|
|
4183
|
+
} catch (error) {
|
|
4184
|
+
return failed(error);
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
];
|
|
4189
|
+
}
|
|
4190
|
+
|
|
4191
|
+
// src/views/DeliveryAgentSurface.tsx
|
|
4192
|
+
function DeliveryAgentSurface({ provider, zones, onSaved }) {
|
|
4193
|
+
const live = React13.useRef({ provider, zones, onSaved });
|
|
4194
|
+
live.current = { provider, zones, onSaved };
|
|
4195
|
+
const contribution = React13.useMemo(() => ({
|
|
4196
|
+
id: "shop.delivery.settings",
|
|
4197
|
+
pluginId: "shop",
|
|
4198
|
+
role: "operations",
|
|
4199
|
+
instructions: DELIVERY_AREAS_INSTRUCTIONS,
|
|
4200
|
+
describe: () => {
|
|
4201
|
+
const { zones: rows } = live.current;
|
|
4202
|
+
const draft2 = getDeliveryDraft();
|
|
4203
|
+
const byOutcome = (outcome) => rows.filter((z) => z.active && (z.outcome ?? (z.excluded ? "refuse" : "deliver")) === outcome);
|
|
4204
|
+
return {
|
|
4205
|
+
title: "Configura\xE7\xE3o \u2014 Entrega",
|
|
4206
|
+
state: {
|
|
4207
|
+
screen: "/settings/shop-shipping",
|
|
4208
|
+
savedRules: {
|
|
4209
|
+
delivering: byOutcome("deliver").length,
|
|
4210
|
+
reviewing: byOutcome("review").length,
|
|
4211
|
+
refusing: byOutcome("refuse").length,
|
|
4212
|
+
// Os nomes, não as faixas: o agente precisa saber o que JÁ existe
|
|
4213
|
+
// para não propor a mesma região duas vezes.
|
|
4214
|
+
names: rows.filter((z) => z.active).slice(0, 40).map((z) => z.name)
|
|
4215
|
+
},
|
|
4216
|
+
draft: draft2.rules.length === 0 && draft2.gaps.length === 0 ? null : {
|
|
4217
|
+
rules: draft2.rules.map((r) => ({
|
|
4218
|
+
id: r.id,
|
|
4219
|
+
label: r.label,
|
|
4220
|
+
outcome: r.outcome,
|
|
4221
|
+
rate: r.rate ?? null,
|
|
4222
|
+
freeAbove: r.freeAbove ?? null
|
|
4223
|
+
})),
|
|
4224
|
+
gaps: draft2.gaps
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
};
|
|
4228
|
+
},
|
|
4229
|
+
tools: createDeliveryAreaTools({
|
|
4230
|
+
getProvider: () => live.current.provider(),
|
|
4231
|
+
onSaved: () => live.current.onSaved()
|
|
4232
|
+
})
|
|
4233
|
+
}), []);
|
|
4234
|
+
useAgentSurface(contribution);
|
|
4235
|
+
return null;
|
|
4236
|
+
}
|
|
4237
|
+
var MIN_QUERY = 2;
|
|
4238
|
+
var DEBOUNCE_MS2 = 250;
|
|
4239
|
+
function ruleFor(area, outcome) {
|
|
4240
|
+
if (area.kind === "city" && area.cityCode) {
|
|
4241
|
+
return { matchKind: "city", label: area.label, cityCode: area.cityCode, outcome };
|
|
4242
|
+
}
|
|
4243
|
+
if (area.kind === "district" && area.districtKey) {
|
|
4244
|
+
return {
|
|
4245
|
+
matchKind: "district",
|
|
4246
|
+
label: area.label,
|
|
4247
|
+
districtKey: area.districtKey,
|
|
4248
|
+
cityCode: area.cityCode ?? null,
|
|
4249
|
+
outcome
|
|
4250
|
+
};
|
|
4251
|
+
}
|
|
4252
|
+
return null;
|
|
4253
|
+
}
|
|
4254
|
+
function rulesFor(area, outcome) {
|
|
4255
|
+
if (area.kind !== "group") {
|
|
4256
|
+
const single = ruleFor(area, outcome);
|
|
4257
|
+
return single ? [single] : [];
|
|
4258
|
+
}
|
|
4259
|
+
return (area.members ?? []).flatMap((member) => {
|
|
4260
|
+
const rule = ruleFor(
|
|
4261
|
+
{ kind: member.kind, label: member.label, cityCode: member.cityCode, districtKey: member.districtKey },
|
|
4262
|
+
outcome
|
|
4263
|
+
);
|
|
4264
|
+
return rule ? [rule] : [];
|
|
4265
|
+
});
|
|
4266
|
+
}
|
|
4267
|
+
function areaMeta(area) {
|
|
4268
|
+
if (area.matchedAs) return area.matchedAs;
|
|
4269
|
+
if (area.kind === "group") {
|
|
4270
|
+
const n = area.members?.length ?? 0;
|
|
4271
|
+
return `${n} ${n === 1 ? "regi\xE3o" : "regi\xF5es"}${area.cityName ? ` \xB7 ${area.cityName}` : ""}`;
|
|
4272
|
+
}
|
|
4273
|
+
if (area.kind === "city") return `Cidade inteira${area.state ? ` \xB7 ${area.state}` : ""}`;
|
|
4274
|
+
return `Bairro${area.cityName ? ` \xB7 ${area.cityName}` : ""}`;
|
|
4275
|
+
}
|
|
4276
|
+
var areaKeyOf = (area) => `${area.kind}:${area.districtKey ?? area.cityCode ?? area.label}`;
|
|
4277
|
+
function AreaSearch({ provider, outcome, placeholder, onlyCityCodes, emptyHint }) {
|
|
4278
|
+
const [query, setQuery] = React13__default.useState("");
|
|
4279
|
+
const [results, setResults] = React13__default.useState(null);
|
|
4280
|
+
const [loading, setLoading] = React13__default.useState(false);
|
|
4281
|
+
const [unavailable, setUnavailable] = React13__default.useState(false);
|
|
4282
|
+
const [justAdded, setJustAdded] = React13__default.useState(null);
|
|
4283
|
+
React13__default.useEffect(() => {
|
|
4284
|
+
const text3 = query.trim();
|
|
4285
|
+
if (text3.length < MIN_QUERY) {
|
|
4286
|
+
setResults(null);
|
|
4287
|
+
return void 0;
|
|
4288
|
+
}
|
|
4289
|
+
let cancelled = false;
|
|
4290
|
+
setLoading(true);
|
|
4291
|
+
const timer = setTimeout(() => {
|
|
4292
|
+
const shop = provider ? resolve(provider) : getShopProvider();
|
|
4293
|
+
if (typeof shop.findAreas !== "function") {
|
|
4294
|
+
if (!cancelled) {
|
|
4295
|
+
setUnavailable(true);
|
|
4296
|
+
setLoading(false);
|
|
4297
|
+
}
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
void shop.findAreas(text3, 10).then((list) => {
|
|
4301
|
+
if (!cancelled) {
|
|
4302
|
+
setResults(list);
|
|
4303
|
+
setUnavailable(false);
|
|
4304
|
+
}
|
|
4305
|
+
}).catch(() => {
|
|
4306
|
+
if (!cancelled) {
|
|
4307
|
+
setResults([]);
|
|
4308
|
+
setUnavailable(true);
|
|
4309
|
+
}
|
|
4310
|
+
}).finally(() => {
|
|
4311
|
+
if (!cancelled) setLoading(false);
|
|
4312
|
+
});
|
|
4313
|
+
}, DEBOUNCE_MS2);
|
|
4314
|
+
return () => {
|
|
4315
|
+
cancelled = true;
|
|
4316
|
+
clearTimeout(timer);
|
|
4317
|
+
};
|
|
4318
|
+
}, [query, provider]);
|
|
4319
|
+
const add = (area) => {
|
|
4320
|
+
const rules = rulesFor(area, outcome);
|
|
4321
|
+
if (rules.length === 0) return;
|
|
4322
|
+
const added = addDeliveryDraftRules(rules);
|
|
4323
|
+
setJustAdded(areaKeyOf(area));
|
|
4324
|
+
setTimeout(() => setJustAdded(null), 1400);
|
|
4325
|
+
if (added > 0) setQuery("");
|
|
4326
|
+
};
|
|
4327
|
+
const visible = (results ?? []).filter((area) => {
|
|
4328
|
+
if (!onlyCityCodes || onlyCityCodes.length === 0) return true;
|
|
4329
|
+
if (area.kind === "city") return true;
|
|
4330
|
+
return !area.cityCode || onlyCityCodes.includes(area.cityCode);
|
|
4331
|
+
});
|
|
4332
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4333
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
4334
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }),
|
|
4335
|
+
/* @__PURE__ */ jsx(
|
|
4336
|
+
"input",
|
|
4337
|
+
{
|
|
4338
|
+
value: query,
|
|
4339
|
+
onChange: (e) => setQuery(e.target.value),
|
|
4340
|
+
placeholder,
|
|
4341
|
+
className: "w-full rounded-lg border border-border bg-background py-2 pl-9 pr-9 text-sm"
|
|
4342
|
+
}
|
|
4343
|
+
),
|
|
4344
|
+
loading && /* @__PURE__ */ jsx(Loader2, { className: "absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 animate-spin text-muted-foreground" })
|
|
4345
|
+
] }),
|
|
4346
|
+
results !== null && /* @__PURE__ */ jsxs("ul", { className: "mt-2 max-h-72 divide-y divide-border overflow-y-auto rounded-lg border border-border", children: [
|
|
4347
|
+
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 }),
|
|
4348
|
+
visible.map((area) => {
|
|
4349
|
+
const key = areaKeyOf(area);
|
|
4350
|
+
const already = area.kind !== "group" && isInDeliveryDraft({
|
|
4351
|
+
matchKind: area.kind === "city" ? "city" : "district",
|
|
4352
|
+
cityCode: area.cityCode ?? null,
|
|
4353
|
+
districtKey: area.districtKey ?? null
|
|
4354
|
+
});
|
|
4355
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
4356
|
+
"button",
|
|
4357
|
+
{
|
|
4358
|
+
type: "button",
|
|
4359
|
+
onClick: () => add(area),
|
|
4360
|
+
disabled: already,
|
|
4361
|
+
className: "flex w-full items-center gap-3 px-3 py-2.5 text-left transition hover:bg-muted/50 disabled:opacity-50",
|
|
4362
|
+
children: [
|
|
4363
|
+
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" }),
|
|
4364
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
4365
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-medium", children: area.label }),
|
|
4366
|
+
/* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
|
|
4367
|
+
areaMeta(area),
|
|
4368
|
+
area.kind === "district" && !area.knownPostalCodes && " \xB7 ainda sem CEP visto"
|
|
4369
|
+
] })
|
|
4370
|
+
] }),
|
|
4371
|
+
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" })
|
|
4372
|
+
]
|
|
4373
|
+
}
|
|
4374
|
+
) }, key);
|
|
4375
|
+
})
|
|
4376
|
+
] })
|
|
4377
|
+
] });
|
|
4378
|
+
}
|
|
4379
|
+
function DeliveryAreaPicker({ provider, onAskAssistant }) {
|
|
4380
|
+
const draft2 = useDeliveryDraft();
|
|
4381
|
+
const covered = draft2.rules.filter((r) => r.outcome !== "refuse");
|
|
4382
|
+
const coveredCities = covered.filter((r) => r.matchKind === "city");
|
|
4383
|
+
const exceptions = draft2.rules.filter((r) => r.outcome === "refuse");
|
|
4384
|
+
const cityCodes = covered.map((r) => r.cityCode).filter(Boolean);
|
|
4385
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card px-5 py-4 shadow-sm", children: [
|
|
4386
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
|
|
4387
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
4388
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Onde voc\xEA entrega?" }),
|
|
4389
|
+
/* @__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.' })
|
|
4390
|
+
] }),
|
|
4391
|
+
onAskAssistant && /* @__PURE__ */ jsxs(
|
|
4392
|
+
"button",
|
|
4393
|
+
{
|
|
4394
|
+
type: "button",
|
|
4395
|
+
onClick: onAskAssistant,
|
|
4396
|
+
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",
|
|
4397
|
+
children: [
|
|
4398
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5 text-primary" }),
|
|
4399
|
+
"Prefiro contar por escrito"
|
|
4400
|
+
]
|
|
4401
|
+
}
|
|
4402
|
+
)
|
|
4403
|
+
] }),
|
|
4404
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
4405
|
+
AreaSearch,
|
|
4406
|
+
{
|
|
4407
|
+
provider,
|
|
4408
|
+
outcome: "deliver",
|
|
4409
|
+
placeholder: "Cidade, bairro ou regi\xE3o\u2026",
|
|
4410
|
+
emptyHint: "Nada com esse nome. Tente a cidade, ou conte por escrito ao assistente."
|
|
4411
|
+
}
|
|
4412
|
+
) }),
|
|
4413
|
+
covered.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-5 border-t border-border pt-4", children: [
|
|
4414
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4415
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-4 w-4 text-destructive" }),
|
|
4416
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "E onde voc\xEA n\xE3o entrega?" })
|
|
4417
|
+
] }),
|
|
4418
|
+
/* @__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." }),
|
|
4419
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(
|
|
4420
|
+
AreaSearch,
|
|
4421
|
+
{
|
|
4422
|
+
provider,
|
|
4423
|
+
outcome: "refuse",
|
|
4424
|
+
placeholder: "Bairro ou regi\xE3o que voc\xEA recusa\u2026",
|
|
4425
|
+
onlyCityCodes: cityCodes,
|
|
4426
|
+
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." })
|
|
4427
|
+
}
|
|
4428
|
+
) })
|
|
4429
|
+
] }),
|
|
4430
|
+
draft2.rules.length > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-3 text-xs text-muted-foreground", children: [
|
|
4431
|
+
covered.length,
|
|
4432
|
+
" ",
|
|
4433
|
+
covered.length === 1 ? "regi\xE3o" : "regi\xF5es",
|
|
4434
|
+
" onde voc\xEA entrega",
|
|
4435
|
+
exceptions.length > 0 && `, ${exceptions.length} ${exceptions.length === 1 ? "exce\xE7\xE3o" : "exce\xE7\xF5es"}`,
|
|
4436
|
+
" ",
|
|
4437
|
+
"\u2014 confira o pre\xE7o na tabela abaixo e salve."
|
|
4438
|
+
] })
|
|
4439
|
+
] });
|
|
4440
|
+
}
|
|
4441
|
+
var OUTCOME_LABEL = {
|
|
4442
|
+
deliver: "Entrego",
|
|
4443
|
+
review: "Confirmo antes",
|
|
4444
|
+
refuse: "N\xE3o entrego"
|
|
4445
|
+
};
|
|
4446
|
+
var OUTCOME_TONE = {
|
|
4447
|
+
deliver: "text-foreground",
|
|
4448
|
+
review: "text-amber-600 dark:text-amber-500",
|
|
4449
|
+
refuse: "text-destructive"
|
|
4450
|
+
};
|
|
3535
4451
|
function parseMoney(raw) {
|
|
3536
4452
|
const cleaned = raw.replace(/[^\d,.-]/g, "").replace(",", ".");
|
|
3537
4453
|
if (!cleaned.trim()) return null;
|
|
3538
4454
|
const n = Number(cleaned);
|
|
3539
4455
|
return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
|
|
3540
4456
|
}
|
|
4457
|
+
var money2 = (v) => v == null ? "" : String(v).replace(".", ",");
|
|
4458
|
+
function ruleArea(rule) {
|
|
4459
|
+
if (rule.matchKind === "postal_range") {
|
|
4460
|
+
const fmt = (c) => c ? `${c.slice(0, 5)}-${c.slice(5)}` : "?";
|
|
4461
|
+
return `${fmt(rule.postalFrom)} \u2192 ${fmt(rule.postalTo)}`;
|
|
4462
|
+
}
|
|
4463
|
+
return rule.matchKind === "city" ? "Munic\xEDpio" : "Bairro";
|
|
4464
|
+
}
|
|
4465
|
+
function DeliveryRulesDraft({ provider, onSaved }) {
|
|
4466
|
+
const draft2 = useDeliveryDraft();
|
|
4467
|
+
const [saving, setSaving] = React13__default.useState(false);
|
|
4468
|
+
const [error, setError] = React13__default.useState(null);
|
|
4469
|
+
const [bulkRate, setBulkRate] = React13__default.useState("");
|
|
4470
|
+
const [bulkFree, setBulkFree] = React13__default.useState("");
|
|
4471
|
+
if (draft2.rules.length === 0 && draft2.gaps.length === 0) return null;
|
|
4472
|
+
const refusing = draft2.rules.filter((r) => r.outcome === "refuse").length;
|
|
4473
|
+
const reviewing = draft2.rules.filter((r) => r.outcome === "review").length;
|
|
4474
|
+
const delivering = draft2.rules.length - refusing - reviewing;
|
|
4475
|
+
const save = async () => {
|
|
4476
|
+
setSaving(true);
|
|
4477
|
+
setError(null);
|
|
4478
|
+
try {
|
|
4479
|
+
const shop = provider ? resolve(provider) : getShopProvider();
|
|
4480
|
+
if (typeof shop.createShippingZone !== "function") {
|
|
4481
|
+
throw new Error("Este banco ainda n\xE3o tem as regras de entrega.");
|
|
4482
|
+
}
|
|
4483
|
+
for (const rule of draft2.rules) {
|
|
4484
|
+
await shop.createShippingZone(zoneInputFrom(rule));
|
|
4485
|
+
}
|
|
4486
|
+
clearDeliveryDraft();
|
|
4487
|
+
onSaved();
|
|
4488
|
+
} catch (err) {
|
|
4489
|
+
setError(errorMessage(err));
|
|
4490
|
+
} finally {
|
|
4491
|
+
setSaving(false);
|
|
4492
|
+
}
|
|
4493
|
+
};
|
|
4494
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-card border border-primary/40 bg-primary/5 shadow-sm", children: [
|
|
4495
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 border-b border-primary/20 px-5 py-4", children: [
|
|
4496
|
+
/* @__PURE__ */ jsx(ListChecks, { className: "h-4 w-4 text-primary" }),
|
|
4497
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
4498
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Regras a salvar" }),
|
|
4499
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
4500
|
+
delivering,
|
|
4501
|
+
" entrega",
|
|
4502
|
+
delivering === 1 ? "" : "s",
|
|
4503
|
+
reviewing > 0 && `, ${reviewing} a confirmar`,
|
|
4504
|
+
refusing > 0 && `, ${refusing} recusa${refusing === 1 ? "" : "s"}`,
|
|
4505
|
+
" ",
|
|
4506
|
+
"\u2014 confira e corrija antes de salvar. Nada foi gravado ainda."
|
|
4507
|
+
] })
|
|
4508
|
+
] }),
|
|
4509
|
+
/* @__PURE__ */ jsx(
|
|
4510
|
+
"button",
|
|
4511
|
+
{
|
|
4512
|
+
type: "button",
|
|
4513
|
+
onClick: clearDeliveryDraft,
|
|
4514
|
+
disabled: saving,
|
|
4515
|
+
className: "rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-muted",
|
|
4516
|
+
children: "Descartar"
|
|
4517
|
+
}
|
|
4518
|
+
),
|
|
4519
|
+
/* @__PURE__ */ jsxs(
|
|
4520
|
+
"button",
|
|
4521
|
+
{
|
|
4522
|
+
type: "button",
|
|
4523
|
+
onClick: save,
|
|
4524
|
+
disabled: saving || draft2.rules.length === 0,
|
|
4525
|
+
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",
|
|
4526
|
+
children: [
|
|
4527
|
+
saving ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
4528
|
+
"Salvar ",
|
|
4529
|
+
draft2.rules.length,
|
|
4530
|
+
" regra",
|
|
4531
|
+
draft2.rules.length === 1 ? "" : "s"
|
|
4532
|
+
]
|
|
4533
|
+
}
|
|
4534
|
+
)
|
|
4535
|
+
] }),
|
|
4536
|
+
error && /* @__PURE__ */ jsx("p", { className: "px-5 pt-3 text-xs text-destructive", children: error }),
|
|
4537
|
+
delivering + reviewing > 1 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-primary/10 px-5 py-3", children: [
|
|
4538
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Mesmo pre\xE7o em todas:" }),
|
|
4539
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
4540
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "R$" }),
|
|
4541
|
+
/* @__PURE__ */ jsx(
|
|
4542
|
+
"input",
|
|
4543
|
+
{
|
|
4544
|
+
value: bulkRate,
|
|
4545
|
+
onChange: (e) => setBulkRate(e.target.value),
|
|
4546
|
+
placeholder: "15,00",
|
|
4547
|
+
"aria-label": "Frete para todas as regi\xF5es",
|
|
4548
|
+
className: "w-20 rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
4549
|
+
}
|
|
4550
|
+
)
|
|
4551
|
+
] }),
|
|
4552
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
4553
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "gr\xE1tis acima de R$" }),
|
|
4554
|
+
/* @__PURE__ */ jsx(
|
|
4555
|
+
"input",
|
|
4556
|
+
{
|
|
4557
|
+
value: bulkFree,
|
|
4558
|
+
onChange: (e) => setBulkFree(e.target.value),
|
|
4559
|
+
placeholder: "opcional",
|
|
4560
|
+
"aria-label": "Frete gr\xE1tis acima de, para todas as regi\xF5es",
|
|
4561
|
+
className: "w-24 rounded-md border border-border bg-background px-2 py-1 text-xs"
|
|
4562
|
+
}
|
|
4563
|
+
)
|
|
4564
|
+
] }),
|
|
4565
|
+
/* @__PURE__ */ jsx(
|
|
4566
|
+
"button",
|
|
4567
|
+
{
|
|
4568
|
+
type: "button",
|
|
4569
|
+
onClick: () => {
|
|
4570
|
+
setAllDeliveryDraftRates({
|
|
4571
|
+
rate: parseMoney(bulkRate),
|
|
4572
|
+
freeAbove: parseMoney(bulkFree)
|
|
4573
|
+
});
|
|
4574
|
+
setBulkRate("");
|
|
4575
|
+
setBulkFree("");
|
|
4576
|
+
},
|
|
4577
|
+
disabled: !bulkRate.trim() && !bulkFree.trim(),
|
|
4578
|
+
className: "rounded-md border border-border px-2.5 py-1 text-xs font-medium hover:bg-muted disabled:opacity-40",
|
|
4579
|
+
children: "Aplicar"
|
|
4580
|
+
}
|
|
4581
|
+
)
|
|
4582
|
+
] }),
|
|
4583
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
4584
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-left text-xs text-muted-foreground", children: [
|
|
4585
|
+
/* @__PURE__ */ jsx("th", { className: "px-5 py-2.5 font-medium", children: "Regi\xE3o" }),
|
|
4586
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "O que fa\xE7o" }),
|
|
4587
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Frete" }),
|
|
4588
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Gr\xE1tis acima de" }),
|
|
4589
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2.5 font-medium", children: "Prazo (dias)" }),
|
|
4590
|
+
/* @__PURE__ */ jsx("th", { className: "px-2 py-2" })
|
|
4591
|
+
] }) }),
|
|
4592
|
+
/* @__PURE__ */ jsx("tbody", { children: draft2.rules.map((rule) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-primary/10", children: [
|
|
4593
|
+
/* @__PURE__ */ jsxs("td", { className: "px-5 py-2.5", children: [
|
|
4594
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: rule.label }),
|
|
4595
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: ruleArea(rule) }),
|
|
4596
|
+
rule.source && /* @__PURE__ */ jsxs("p", { className: "text-xs italic text-muted-foreground", children: [
|
|
4597
|
+
"\u201C",
|
|
4598
|
+
rule.source,
|
|
4599
|
+
"\u201D"
|
|
4600
|
+
] })
|
|
4601
|
+
] }),
|
|
4602
|
+
/* @__PURE__ */ jsx("td", { className: "px-2 py-2.5", children: /* @__PURE__ */ jsx(
|
|
4603
|
+
"select",
|
|
4604
|
+
{
|
|
4605
|
+
value: rule.outcome,
|
|
4606
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, { outcome: e.target.value }),
|
|
4607
|
+
className: `rounded-md border border-border bg-background px-2 py-1.5 text-xs ${OUTCOME_TONE[rule.outcome]}`,
|
|
4608
|
+
children: Object.keys(OUTCOME_LABEL).map((value) => /* @__PURE__ */ jsx("option", { value, children: OUTCOME_LABEL[value] }, value))
|
|
4609
|
+
}
|
|
4610
|
+
) }),
|
|
4611
|
+
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: [
|
|
4612
|
+
/* @__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: [
|
|
4613
|
+
/* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: "R$" }),
|
|
4614
|
+
/* @__PURE__ */ jsx(
|
|
4615
|
+
"input",
|
|
4616
|
+
{
|
|
4617
|
+
value: money2(rule.rate),
|
|
4618
|
+
placeholder: "0,00",
|
|
4619
|
+
"aria-label": `Frete para ${rule.label}`,
|
|
4620
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, { rate: parseMoney(e.target.value) }),
|
|
4621
|
+
className: "w-16 bg-transparent px-1.5 py-1.5 text-xs outline-none"
|
|
4622
|
+
}
|
|
4623
|
+
)
|
|
4624
|
+
] }) }),
|
|
4625
|
+
/* @__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: [
|
|
4626
|
+
/* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: "R$" }),
|
|
4627
|
+
/* @__PURE__ */ jsx(
|
|
4628
|
+
"input",
|
|
4629
|
+
{
|
|
4630
|
+
value: money2(rule.freeAbove),
|
|
4631
|
+
placeholder: "nunca",
|
|
4632
|
+
"aria-label": `Frete gr\xE1tis acima de, em ${rule.label}`,
|
|
4633
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, { freeAbove: parseMoney(e.target.value) }),
|
|
4634
|
+
className: "w-20 bg-transparent px-1.5 py-1.5 text-xs outline-none"
|
|
4635
|
+
}
|
|
4636
|
+
)
|
|
4637
|
+
] }) }),
|
|
4638
|
+
/* @__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: [
|
|
4639
|
+
/* @__PURE__ */ jsx(
|
|
4640
|
+
"input",
|
|
4641
|
+
{
|
|
4642
|
+
value: rule.etaMinDays ?? "",
|
|
4643
|
+
placeholder: "0",
|
|
4644
|
+
inputMode: "numeric",
|
|
4645
|
+
"aria-label": `Prazo m\xEDnimo em ${rule.label}`,
|
|
4646
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, {
|
|
4647
|
+
etaMinDays: e.target.value ? Number(e.target.value) : null
|
|
4648
|
+
}),
|
|
4649
|
+
className: "w-10 bg-transparent py-1.5 pl-2 text-center text-xs outline-none"
|
|
4650
|
+
}
|
|
4651
|
+
),
|
|
4652
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "a" }),
|
|
4653
|
+
/* @__PURE__ */ jsx(
|
|
4654
|
+
"input",
|
|
4655
|
+
{
|
|
4656
|
+
value: rule.etaMaxDays ?? "",
|
|
4657
|
+
placeholder: "2",
|
|
4658
|
+
inputMode: "numeric",
|
|
4659
|
+
"aria-label": `Prazo m\xE1ximo em ${rule.label}`,
|
|
4660
|
+
onChange: (e) => updateDeliveryDraftRule(rule.id, {
|
|
4661
|
+
etaMaxDays: e.target.value ? Number(e.target.value) : null
|
|
4662
|
+
}),
|
|
4663
|
+
className: "w-10 bg-transparent py-1.5 pr-2 text-center text-xs outline-none"
|
|
4664
|
+
}
|
|
4665
|
+
)
|
|
4666
|
+
] }) })
|
|
4667
|
+
] }),
|
|
4668
|
+
/* @__PURE__ */ jsx("td", { className: "px-5 py-2.5 text-right", children: /* @__PURE__ */ jsx(
|
|
4669
|
+
"button",
|
|
4670
|
+
{
|
|
4671
|
+
type: "button",
|
|
4672
|
+
onClick: () => removeDeliveryDraftRule(rule.id),
|
|
4673
|
+
"aria-label": `Remover ${rule.label}`,
|
|
4674
|
+
className: "rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-destructive",
|
|
4675
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
4676
|
+
}
|
|
4677
|
+
) })
|
|
4678
|
+
] }, rule.id)) })
|
|
4679
|
+
] }) }),
|
|
4680
|
+
draft2.gaps.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-t border-primary/20 px-5 py-3.5", children: [
|
|
4681
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "O que ficou de fora \u2014 e por qu\xEA" }),
|
|
4682
|
+
/* @__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: [
|
|
4683
|
+
/* @__PURE__ */ jsx(X, { className: "mt-0.5 h-3 w-3 shrink-0 text-destructive" }),
|
|
4684
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
4685
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: gap.query }),
|
|
4686
|
+
" \u2014 ",
|
|
4687
|
+
gap.reason
|
|
4688
|
+
] })
|
|
4689
|
+
] }, gap.query)) })
|
|
4690
|
+
] })
|
|
4691
|
+
] });
|
|
4692
|
+
}
|
|
4693
|
+
function parseMoney2(raw) {
|
|
4694
|
+
const cleaned = raw.replace(/[^\d,.-]/g, "").replace(",", ".");
|
|
4695
|
+
if (!cleaned.trim()) return null;
|
|
4696
|
+
const n = Number(cleaned);
|
|
4697
|
+
return Number.isFinite(n) && n >= 0 ? Math.round(n * 100) / 100 : null;
|
|
4698
|
+
}
|
|
3541
4699
|
var formatMoney = (v) => v == null ? "" : String(v).replace(".", ",");
|
|
3542
4700
|
var FREIGHT_SOURCE = [
|
|
3543
4701
|
{
|
|
@@ -3554,46 +4712,48 @@ var FREIGHT_SOURCE = [
|
|
|
3554
4712
|
}
|
|
3555
4713
|
];
|
|
3556
4714
|
function ShippingSettings({ provider }) {
|
|
3557
|
-
const [registryId, setRegistryId] =
|
|
3558
|
-
|
|
4715
|
+
const [registryId, setRegistryId] = React13__default.useState(shippingRegistryIdFromHash);
|
|
4716
|
+
React13__default.useEffect(() => {
|
|
3559
4717
|
const handler = () => setRegistryId(shippingRegistryIdFromHash());
|
|
3560
4718
|
window.addEventListener("hashchange", handler);
|
|
3561
4719
|
return () => window.removeEventListener("hashchange", handler);
|
|
3562
4720
|
}, []);
|
|
3563
|
-
const shop =
|
|
3564
|
-
const registries =
|
|
4721
|
+
const shop = React13__default.useMemo(() => provider ? resolve(provider) : getShopProvider(), [provider]);
|
|
4722
|
+
const registries = React13__default.useMemo(
|
|
3565
4723
|
() => buildShopRegistries(provider ?? getShopProvider),
|
|
3566
4724
|
[provider]
|
|
3567
4725
|
);
|
|
3568
4726
|
const storeSettings = useStoreSettings(provider);
|
|
3569
4727
|
const supported = typeof shop.getShippingSettings === "function" && typeof shop.updateShippingSettings === "function";
|
|
3570
|
-
const [row, setRow] =
|
|
3571
|
-
const [zones, setZones] =
|
|
3572
|
-
const [locations, setLocations] =
|
|
3573
|
-
const [loading, setLoading] =
|
|
3574
|
-
const [saving, setSaving] =
|
|
3575
|
-
const [error, setError] =
|
|
3576
|
-
const [loadError, setLoadError] =
|
|
3577
|
-
const [flatDraft, setFlatDraft] =
|
|
3578
|
-
const [freeDraft, setFreeDraft] =
|
|
3579
|
-
const [flatBad, setFlatBad] =
|
|
3580
|
-
const [freeBad, setFreeBad] =
|
|
4728
|
+
const [row, setRow] = React13__default.useState(null);
|
|
4729
|
+
const [zones, setZones] = React13__default.useState(null);
|
|
4730
|
+
const [locations, setLocations] = React13__default.useState(null);
|
|
4731
|
+
const [loading, setLoading] = React13__default.useState(supported);
|
|
4732
|
+
const [saving, setSaving] = React13__default.useState(false);
|
|
4733
|
+
const [error, setError] = React13__default.useState(null);
|
|
4734
|
+
const [loadError, setLoadError] = React13__default.useState(null);
|
|
4735
|
+
const [flatDraft, setFlatDraft] = React13__default.useState("");
|
|
4736
|
+
const [freeDraft, setFreeDraft] = React13__default.useState("");
|
|
4737
|
+
const [flatBad, setFlatBad] = React13__default.useState(false);
|
|
4738
|
+
const [freeBad, setFreeBad] = React13__default.useState(false);
|
|
3581
4739
|
const { connectors } = useConnectors({ hostPluginId: "shop", includeGrants: false });
|
|
3582
|
-
const [selected, setSelected] =
|
|
4740
|
+
const [selected, setSelected] = React13__default.useState(null);
|
|
3583
4741
|
const panelAvailable = useConnectorsPanelAvailable();
|
|
3584
|
-
const openJourney =
|
|
4742
|
+
const openJourney = React13__default.useCallback(
|
|
3585
4743
|
(connector) => {
|
|
3586
4744
|
if (panelAvailable) openConnectorsPanel(connector);
|
|
3587
4745
|
else setSelected(connector);
|
|
3588
4746
|
},
|
|
3589
4747
|
[panelAvailable]
|
|
3590
4748
|
);
|
|
3591
|
-
const carriers =
|
|
4749
|
+
const carriers = React13__default.useMemo(
|
|
3592
4750
|
() => (connectors ?? []).filter((c) => c.category === "logistics" || c.category === "shipping"),
|
|
3593
4751
|
[connectors]
|
|
3594
4752
|
);
|
|
3595
4753
|
const carriersConnected = carriers.some((c) => c.state !== "ready");
|
|
3596
|
-
|
|
4754
|
+
const [zonesEpoch, setZonesEpoch] = React13__default.useState(0);
|
|
4755
|
+
const reloadZones = React13__default.useCallback(() => setZonesEpoch((n) => n + 1), []);
|
|
4756
|
+
React13__default.useEffect(() => {
|
|
3597
4757
|
let cancelled = false;
|
|
3598
4758
|
if (supported) {
|
|
3599
4759
|
setLoading(true);
|
|
@@ -3621,8 +4781,8 @@ function ShippingSettings({ provider }) {
|
|
|
3621
4781
|
return () => {
|
|
3622
4782
|
cancelled = true;
|
|
3623
4783
|
};
|
|
3624
|
-
}, [shop, supported]);
|
|
3625
|
-
const save =
|
|
4784
|
+
}, [shop, supported, zonesEpoch]);
|
|
4785
|
+
const save = React13__default.useCallback(
|
|
3626
4786
|
(patch) => {
|
|
3627
4787
|
if (!supported) return;
|
|
3628
4788
|
setSaving(true);
|
|
@@ -3632,7 +4792,7 @@ function ShippingSettings({ provider }) {
|
|
|
3632
4792
|
[shop, supported]
|
|
3633
4793
|
);
|
|
3634
4794
|
const commitFlat = () => {
|
|
3635
|
-
const parsed =
|
|
4795
|
+
const parsed = parseMoney2(flatDraft);
|
|
3636
4796
|
const next = flatDraft.trim() === "" ? 0 : parsed;
|
|
3637
4797
|
if (next == null) {
|
|
3638
4798
|
setFlatBad(true);
|
|
@@ -3648,7 +4808,7 @@ function ShippingSettings({ provider }) {
|
|
|
3648
4808
|
if (row?.freeAbove != null) save({ freeAbove: null });
|
|
3649
4809
|
return;
|
|
3650
4810
|
}
|
|
3651
|
-
const parsed =
|
|
4811
|
+
const parsed = parseMoney2(freeDraft);
|
|
3652
4812
|
if (parsed == null) {
|
|
3653
4813
|
setFreeBad(true);
|
|
3654
4814
|
return;
|
|
@@ -3657,8 +4817,10 @@ function ShippingSettings({ provider }) {
|
|
|
3657
4817
|
setFreeDraft(formatMoney(parsed));
|
|
3658
4818
|
if (parsed !== row?.freeAbove) save({ freeAbove: parsed });
|
|
3659
4819
|
};
|
|
3660
|
-
const
|
|
3661
|
-
const
|
|
4820
|
+
const outcomeOf2 = (z) => z.outcome ?? (z.excluded ? "refuse" : "deliver");
|
|
4821
|
+
const activeZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "deliver");
|
|
4822
|
+
const reviewZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "review");
|
|
4823
|
+
const excludedZones = (zones ?? []).filter((z) => z.active && outcomeOf2(z) === "refuse");
|
|
3662
4824
|
const usesGateway = storeSettings.value.carrierQuotes !== false;
|
|
3663
4825
|
const dispatching = (locations ?? []).filter((l) => l.status === "active" && l.shipsOnline !== false);
|
|
3664
4826
|
const originCep = dispatching.find((l) => l.postalCode)?.postalCode ?? null;
|
|
@@ -3756,11 +4918,21 @@ function ShippingSettings({ provider }) {
|
|
|
3756
4918
|
/* @__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." })
|
|
3757
4919
|
] })
|
|
3758
4920
|
] }),
|
|
3759
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
4921
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3760
4922
|
/* @__PURE__ */ jsxs("div", { className: "px-1 pb-1 pt-4", children: [
|
|
3761
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "
|
|
3762
|
-
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: "
|
|
4923
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Onde voc\xEA entrega" }),
|
|
4924
|
+
/* @__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." })
|
|
3763
4925
|
] }),
|
|
4926
|
+
/* @__PURE__ */ jsx(
|
|
4927
|
+
DeliveryAgentSurface,
|
|
4928
|
+
{
|
|
4929
|
+
provider: () => shop,
|
|
4930
|
+
zones: zones ?? [],
|
|
4931
|
+
onSaved: reloadZones
|
|
4932
|
+
}
|
|
4933
|
+
),
|
|
4934
|
+
/* @__PURE__ */ jsx(DeliveryAreaPicker, { provider }),
|
|
4935
|
+
/* @__PURE__ */ jsx(DeliveryRulesDraft, { provider, onSaved: reloadZones }),
|
|
3764
4936
|
/* @__PURE__ */ jsx("div", { className: "rounded-card border bg-card px-5 shadow-sm", children: /* @__PURE__ */ jsxs(
|
|
3765
4937
|
"button",
|
|
3766
4938
|
{
|
|
@@ -3772,10 +4944,15 @@ function ShippingSettings({ provider }) {
|
|
|
3772
4944
|
children: [
|
|
3773
4945
|
/* @__PURE__ */ jsx(MapPin, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
3774
4946
|
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
3775
|
-
/* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: zones == null ? "Carregando
|
|
4947
|
+
/* @__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"}` }),
|
|
3776
4948
|
/* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-xs text-muted-foreground", children: [
|
|
3777
|
-
activeZones.length === 0 ? "Sem
|
|
4949
|
+
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 "),
|
|
3778
4950
|
activeZones.length > 3 && ` e ${activeZones.length - 3} mais`,
|
|
4951
|
+
reviewZones.length > 0 && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-amber-700 dark:text-amber-500", children: [
|
|
4952
|
+
reviewZones.length === 1 ? "1 regi\xE3o a confirmar antes de sair" : `${reviewZones.length} regi\xF5es a confirmar antes de sair`,
|
|
4953
|
+
" \u2014 ",
|
|
4954
|
+
reviewZones.slice(0, 2).map((z) => z.name).join(" \xB7 ")
|
|
4955
|
+
] }),
|
|
3779
4956
|
excludedZones.length > 0 && /* @__PURE__ */ jsxs("span", { className: "mt-0.5 block text-red-700 dark:text-red-400", children: [
|
|
3780
4957
|
excludedZones.length === 1 ? "1 regi\xE3o recusada" : `${excludedZones.length} regi\xF5es recusadas`,
|
|
3781
4958
|
" \u2014 ",
|
|
@@ -4887,31 +6064,9 @@ var vindiConnectorDef = {
|
|
|
4887
6064
|
};
|
|
4888
6065
|
var MERCADOPAGO_CONNECTOR_ID = "mercadopago";
|
|
4889
6066
|
var MERCADOPAGO_LATENCY_BUDGET_MS = 6e3;
|
|
4890
|
-
var
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
label: "Access token",
|
|
4894
|
-
type: "password",
|
|
4895
|
-
secret: true,
|
|
4896
|
-
placeholder: "APP_USR-\u2026 (Suas integra\xE7\xF5es \u2192 sua aplica\xE7\xE3o \u2192 Credenciais)"
|
|
4897
|
-
},
|
|
4898
|
-
{
|
|
4899
|
-
key: "publicKey",
|
|
4900
|
-
label: "Public key",
|
|
4901
|
-
// Not masked: it travels to the browser anyway, and a merchant who cannot
|
|
4902
|
-
// see it cannot tell the test key from the production one at a glance.
|
|
4903
|
-
type: "text",
|
|
4904
|
-
secret: true,
|
|
4905
|
-
placeholder: "APP_USR-\u2026"
|
|
4906
|
-
}
|
|
4907
|
-
];
|
|
4908
|
-
var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID, {
|
|
4909
|
-
fields: MERCADOPAGO_FIELDS
|
|
4910
|
-
});
|
|
4911
|
-
function connectionDetail2(settings) {
|
|
4912
|
-
const key = String(settings?.publicKey ?? "").trim();
|
|
4913
|
-
if (!key) return "Pix e cart\xE3o";
|
|
4914
|
-
return /^TEST-/i.test(key) ? "Sandbox \xB7 Pix e cart\xE3o" : "Produ\xE7\xE3o \xB7 Pix e cart\xE3o";
|
|
6067
|
+
var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID);
|
|
6068
|
+
function connectionDetail2() {
|
|
6069
|
+
return "Pix e cart\xE3o";
|
|
4915
6070
|
}
|
|
4916
6071
|
function MercadoPagoExtraPanel() {
|
|
4917
6072
|
const [connected, setConnected] = useState(false);
|
|
@@ -4939,32 +6094,35 @@ function MercadoPagoExtraPanel() {
|
|
|
4939
6094
|
/* @__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." }),
|
|
4940
6095
|
/* @__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.' }),
|
|
4941
6096
|
/* @__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." })
|
|
4942
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
/* @__PURE__ */
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
/* @__PURE__ */
|
|
4954
|
-
|
|
6097
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6098
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
6099
|
+
"Clique em ",
|
|
6100
|
+
/* @__PURE__ */ jsx("strong", { children: "Conectar" }),
|
|
6101
|
+
" 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."
|
|
6102
|
+
] }),
|
|
6103
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
6104
|
+
"O dinheiro continua caindo na ",
|
|
6105
|
+
/* @__PURE__ */ jsx("strong", { children: "sua" }),
|
|
6106
|
+
" 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."
|
|
6107
|
+
] }),
|
|
6108
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
6109
|
+
"Use uma conta ",
|
|
6110
|
+
/* @__PURE__ */ jsx("strong", { children: "PJ" }),
|
|
6111
|
+
", 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."
|
|
6112
|
+
] })
|
|
4955
6113
|
] }),
|
|
4956
6114
|
/* @__PURE__ */ jsxs("div", { className: "rounded-md border border-dashed p-3", children: [
|
|
4957
6115
|
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Para testar sem cobrar de verdade" }),
|
|
4958
6116
|
/* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
|
|
4959
|
-
"
|
|
6117
|
+
"O teste \xE9 feito com ",
|
|
6118
|
+
/* @__PURE__ */ jsx("strong", { children: "usu\xE1rios de teste" }),
|
|
6119
|
+
": 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",
|
|
6120
|
+
" ",
|
|
4960
6121
|
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: "TEST-" }),
|
|
4961
6122
|
" ",
|
|
4962
6123
|
/* @__PURE__ */ jsx("strong", { children: "n\xE3o funcionam" }),
|
|
4963
|
-
"
|
|
4964
|
-
|
|
4965
|
-
": crie um vendedor e um comprador em Suas integra\xE7\xF5es \u2192 Contas de teste, e use as chaves de ",
|
|
4966
|
-
/* @__PURE__ */ jsx("em", { children: "produ\xE7\xE3o" }),
|
|
4967
|
-
" do vendedor de teste. N\xE3o \xE9 dinheiro real, e \xE9 assim que o pr\xF3prio Mercado Pago recomenda."
|
|
6124
|
+
" ",
|
|
6125
|
+
"nesta API \u2014 o usu\xE1rio de teste usa as chaves de produ\xE7\xE3o dele."
|
|
4968
6126
|
] })
|
|
4969
6127
|
] })
|
|
4970
6128
|
] });
|
|
@@ -4982,15 +6140,25 @@ var mercadoPagoConnectorDef = {
|
|
|
4982
6140
|
// its own domain, so a path would not.
|
|
4983
6141
|
logoUrl: "https://assets.fayz.ai/connectors/mercadopago.png",
|
|
4984
6142
|
category: "payments",
|
|
4985
|
-
//
|
|
4986
|
-
//
|
|
4987
|
-
//
|
|
4988
|
-
//
|
|
4989
|
-
//
|
|
4990
|
-
//
|
|
4991
|
-
//
|
|
4992
|
-
//
|
|
4993
|
-
|
|
6143
|
+
// A real consent screen, unlike Vindi's — whose flow only LOOKS like OAuth and
|
|
6144
|
+
// is a CODE copied out of an address bar. The seller authorizes Fayz once at
|
|
6145
|
+
// auth.mercadopago.com, the money keeps landing in their own account, and
|
|
6146
|
+
// `marketplace_fee` takes our cut on the way past.
|
|
6147
|
+
//
|
|
6148
|
+
// That fee is the reason this is not pasted keys: Mercado Pago's docs are
|
|
6149
|
+
// explicit that it is "exclusivo para integrações com OAuth", so a connection
|
|
6150
|
+
// made by copying credentials out of a panel can never earn. The pasted-key
|
|
6151
|
+
// version shipped first because it could be tested the same afternoon; this
|
|
6152
|
+
// is the one that can be a product.
|
|
6153
|
+
//
|
|
6154
|
+
// A store connected the old way keeps working and does not have to be
|
|
6155
|
+
// migrated by hand: the vault is keyed by slug + tenant, and the runtime reads
|
|
6156
|
+
// `accessToken` and `access_token` alike. Reconnecting through the button
|
|
6157
|
+
// upgrades it in place — same slug, same row, now with a refresh token.
|
|
6158
|
+
authKind: "oauth",
|
|
6159
|
+
// The whole of it. `provider` defaults to the connector id, and both are
|
|
6160
|
+
// `mercadopago` — the platform registry keys on that word.
|
|
6161
|
+
startOAuth: fayzConnectorOAuth(MERCADOPAGO_CONNECTOR_ID),
|
|
4994
6162
|
// The charge is opened in the buyer's request path; the notification and the
|
|
4995
6163
|
// refund are outbound sync work.
|
|
4996
6164
|
planes: ["request", "sync"],
|
|
@@ -5022,7 +6190,6 @@ var mercadoPagoConnectorDef = {
|
|
|
5022
6190
|
// confirmed without a payment is worse than a refused checkout. Same verdict
|
|
5023
6191
|
// as the other four gateways, opposite to the carrier next door.
|
|
5024
6192
|
onFailure: "block",
|
|
5025
|
-
fields: MERCADOPAGO_FIELDS,
|
|
5026
6193
|
invokedAt: [
|
|
5027
6194
|
{
|
|
5028
6195
|
key: "shop.checkout.session_open",
|
|
@@ -5047,7 +6214,7 @@ var mercadoPagoConnectorDef = {
|
|
|
5047
6214
|
MERCADOPAGO_CONNECTOR_ID,
|
|
5048
6215
|
(connection) => ({
|
|
5049
6216
|
connected: !!connection?.active,
|
|
5050
|
-
detail: connectionDetail2(
|
|
6217
|
+
detail: connectionDetail2(),
|
|
5051
6218
|
plane: "request"
|
|
5052
6219
|
}),
|
|
5053
6220
|
{ store: () => store6 }
|
|
@@ -5084,12 +6251,12 @@ function notifyChannelsChanged() {
|
|
|
5084
6251
|
if (typeof window !== "undefined") window.dispatchEvent(new Event("fayz:channels-changed"));
|
|
5085
6252
|
}
|
|
5086
6253
|
function ChannelsPage({ resolveProvider }) {
|
|
5087
|
-
const provider =
|
|
6254
|
+
const provider = React13__default.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
|
|
5088
6255
|
const supported = typeof provider.listSalesChannels === "function";
|
|
5089
|
-
const [channels, setChannels] =
|
|
5090
|
-
const [loading, setLoading] =
|
|
5091
|
-
const [adding, setAdding] =
|
|
5092
|
-
const reload =
|
|
6256
|
+
const [channels, setChannels] = React13__default.useState([]);
|
|
6257
|
+
const [loading, setLoading] = React13__default.useState(true);
|
|
6258
|
+
const [adding, setAdding] = React13__default.useState(null);
|
|
6259
|
+
const reload = React13__default.useCallback(async () => {
|
|
5093
6260
|
if (!provider.listSalesChannels) {
|
|
5094
6261
|
setLoading(false);
|
|
5095
6262
|
return;
|
|
@@ -5107,7 +6274,7 @@ function ChannelsPage({ resolveProvider }) {
|
|
|
5107
6274
|
setLoading(false);
|
|
5108
6275
|
}
|
|
5109
6276
|
}, [provider]);
|
|
5110
|
-
|
|
6277
|
+
React13__default.useEffect(() => {
|
|
5111
6278
|
void reload();
|
|
5112
6279
|
}, [reload]);
|
|
5113
6280
|
const patch = async (id, input) => {
|
|
@@ -5163,7 +6330,7 @@ function ChannelsPage({ resolveProvider }) {
|
|
|
5163
6330
|
] });
|
|
5164
6331
|
}
|
|
5165
6332
|
function OnlineStoreCard({ channel, onSave, onToggle }) {
|
|
5166
|
-
const [url, setUrl] =
|
|
6333
|
+
const [url, setUrl] = React13__default.useState(channel.url ?? "");
|
|
5167
6334
|
const dirty = (channel.url ?? "") !== url;
|
|
5168
6335
|
const meta = KIND_META.online_store;
|
|
5169
6336
|
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: [
|
|
@@ -5189,8 +6356,8 @@ function OnlineStoreCard({ channel, onSave, onToggle }) {
|
|
|
5189
6356
|
] }) });
|
|
5190
6357
|
}
|
|
5191
6358
|
function ChannelRow({ channel, onSave, onToggle, onRemove }) {
|
|
5192
|
-
const [url, setUrl] =
|
|
5193
|
-
const [editing, setEditing] =
|
|
6359
|
+
const [url, setUrl] = React13__default.useState(channel.url ?? "");
|
|
6360
|
+
const [editing, setEditing] = React13__default.useState(false);
|
|
5194
6361
|
const meta = KIND_META[channel.kind] ?? KIND_META.custom;
|
|
5195
6362
|
const Icon = meta.icon;
|
|
5196
6363
|
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: [
|
|
@@ -5218,9 +6385,9 @@ function ChannelRow({ channel, onSave, onToggle, onRemove }) {
|
|
|
5218
6385
|
}
|
|
5219
6386
|
function AddChannelForm({ kind, onKind, onCancel, onAdd }) {
|
|
5220
6387
|
const meta = KIND_META[kind];
|
|
5221
|
-
const [label, setLabel] =
|
|
5222
|
-
const [url, setUrl] =
|
|
5223
|
-
|
|
6388
|
+
const [label, setLabel] = React13__default.useState(meta.label);
|
|
6389
|
+
const [url, setUrl] = React13__default.useState("");
|
|
6390
|
+
React13__default.useEffect(() => {
|
|
5224
6391
|
setLabel(KIND_META[kind].label);
|
|
5225
6392
|
}, [kind]);
|
|
5226
6393
|
return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-card border border-border bg-card p-4", children: [
|
|
@@ -5272,17 +6439,17 @@ function formatSize(bytes) {
|
|
|
5272
6439
|
return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
5273
6440
|
}
|
|
5274
6441
|
function ContentPage({ resolveProvider }) {
|
|
5275
|
-
const provider =
|
|
6442
|
+
const provider = React13__default.useMemo(() => resolve3(resolveProvider), [resolveProvider]);
|
|
5276
6443
|
const supported = typeof provider.listMediaFiles === "function";
|
|
5277
|
-
const [files, setFiles] =
|
|
5278
|
-
const [tab, setTab] =
|
|
5279
|
-
const [uploading, setUploading] =
|
|
5280
|
-
const [pendingDelete, setPendingDelete] =
|
|
5281
|
-
const [deleting, setDeleting] =
|
|
5282
|
-
const [replacing, setReplacing] =
|
|
5283
|
-
const [openId, setOpenId] =
|
|
5284
|
-
const [dimensions, setDimensions] =
|
|
5285
|
-
const reload =
|
|
6444
|
+
const [files, setFiles] = React13__default.useState(null);
|
|
6445
|
+
const [tab, setTab] = React13__default.useState("all");
|
|
6446
|
+
const [uploading, setUploading] = React13__default.useState(false);
|
|
6447
|
+
const [pendingDelete, setPendingDelete] = React13__default.useState(null);
|
|
6448
|
+
const [deleting, setDeleting] = React13__default.useState(false);
|
|
6449
|
+
const [replacing, setReplacing] = React13__default.useState(false);
|
|
6450
|
+
const [openId, setOpenId] = React13__default.useState(null);
|
|
6451
|
+
const [dimensions, setDimensions] = React13__default.useState({});
|
|
6452
|
+
const reload = React13__default.useCallback(async () => {
|
|
5286
6453
|
if (!provider.listMediaFiles) {
|
|
5287
6454
|
setFiles([]);
|
|
5288
6455
|
return;
|
|
@@ -5294,10 +6461,10 @@ function ContentPage({ resolveProvider }) {
|
|
|
5294
6461
|
setFiles([]);
|
|
5295
6462
|
}
|
|
5296
6463
|
}, [provider]);
|
|
5297
|
-
|
|
6464
|
+
React13__default.useEffect(() => {
|
|
5298
6465
|
void reload();
|
|
5299
6466
|
}, [reload]);
|
|
5300
|
-
const measure =
|
|
6467
|
+
const measure = React13__default.useCallback((id, d) => {
|
|
5301
6468
|
setDimensions((prev) => prev[id] ? prev : { ...prev, [id]: d });
|
|
5302
6469
|
}, []);
|
|
5303
6470
|
const handleUpload = async (picked) => {
|
|
@@ -5319,19 +6486,19 @@ function ContentPage({ resolveProvider }) {
|
|
|
5319
6486
|
const handleReplace = async (file, picked) => {
|
|
5320
6487
|
setReplacing(true);
|
|
5321
6488
|
try {
|
|
5322
|
-
let
|
|
6489
|
+
let nextId2;
|
|
5323
6490
|
if (file.reference) {
|
|
5324
6491
|
if (!provider.replaceProductImage) throw new Error("unsupported");
|
|
5325
6492
|
const image = await provider.replaceProductImage(file.reference.attachmentId, picked);
|
|
5326
|
-
|
|
6493
|
+
nextId2 = productImageToMediaFile(image, file.reference.label).id;
|
|
5327
6494
|
} else {
|
|
5328
6495
|
if (!provider.uploadMediaFile) throw new Error("unsupported");
|
|
5329
6496
|
const uploaded = await provider.uploadMediaFile(picked);
|
|
5330
6497
|
if (provider.deleteMediaFile) await provider.deleteMediaFile(file.id);
|
|
5331
|
-
|
|
6498
|
+
nextId2 = uploaded.id;
|
|
5332
6499
|
}
|
|
5333
6500
|
await reload();
|
|
5334
|
-
setOpenId(
|
|
6501
|
+
setOpenId(nextId2);
|
|
5335
6502
|
toast.success("Arquivo trocado");
|
|
5336
6503
|
} catch (err) {
|
|
5337
6504
|
console.warn("[content] replace failed", err);
|
|
@@ -5487,7 +6654,7 @@ function SectionTitle({ title, count }) {
|
|
|
5487
6654
|
] });
|
|
5488
6655
|
}
|
|
5489
6656
|
function UploadButton({ uploading, onPick }) {
|
|
5490
|
-
const inputRef =
|
|
6657
|
+
const inputRef = React13__default.useRef(null);
|
|
5491
6658
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5492
6659
|
/* @__PURE__ */ jsx(
|
|
5493
6660
|
"input",
|
|
@@ -5567,8 +6734,8 @@ function MediaDetail({
|
|
|
5567
6734
|
onDelete,
|
|
5568
6735
|
replacing
|
|
5569
6736
|
}) {
|
|
5570
|
-
const replaceRef =
|
|
5571
|
-
|
|
6737
|
+
const replaceRef = React13__default.useRef(null);
|
|
6738
|
+
React13__default.useEffect(() => {
|
|
5572
6739
|
function onKey(e) {
|
|
5573
6740
|
if (e.key === "ArrowLeft") onNavigate(-1);
|
|
5574
6741
|
if (e.key === "ArrowRight") onNavigate(1);
|
|
@@ -7423,7 +8590,7 @@ function buildShopAnalyticsCards() {
|
|
|
7423
8590
|
}
|
|
7424
8591
|
|
|
7425
8592
|
// src/index.ts
|
|
7426
|
-
var ShopPage =
|
|
8593
|
+
var ShopPage = React13__default.lazy(() => import('./ShopPage-6IQ7WKQG.js').then((m) => ({ default: m.ShopPage })));
|
|
7427
8594
|
function createShopPlugin(options) {
|
|
7428
8595
|
const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
|
|
7429
8596
|
setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
|
|
@@ -7432,10 +8599,10 @@ function createShopPlugin(options) {
|
|
|
7432
8599
|
registerEntityPath("shop_customer", "/orders/customers");
|
|
7433
8600
|
registerPersonLookup("shop_customer", shopCustomerLookup(() => resolveProvider()));
|
|
7434
8601
|
const pageFor = (section) => {
|
|
7435
|
-
const Wrapped = () =>
|
|
7436
|
-
|
|
8602
|
+
const Wrapped = () => React13__default.createElement(
|
|
8603
|
+
React13__default.Suspense,
|
|
7437
8604
|
{ fallback: null },
|
|
7438
|
-
|
|
8605
|
+
React13__default.createElement(
|
|
7439
8606
|
ShopPage,
|
|
7440
8607
|
{ provider: options?.provider, section }
|
|
7441
8608
|
)
|
|
@@ -7614,7 +8781,7 @@ function createShopPlugin(options) {
|
|
|
7614
8781
|
dataProvider: createShopGiftCardDataProvider(resolveProvider),
|
|
7615
8782
|
feature: "shop",
|
|
7616
8783
|
stats: (items, total) => {
|
|
7617
|
-
const
|
|
8784
|
+
const money3 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
|
|
7618
8785
|
const active = items?.filter((g) => g.status === "active") ?? [];
|
|
7619
8786
|
return [
|
|
7620
8787
|
{ id: "total", label: "Cart\xF5es emitidos", value: total, icon: CreditCard },
|
|
@@ -7625,20 +8792,20 @@ function createShopPlugin(options) {
|
|
|
7625
8792
|
id: "outstanding",
|
|
7626
8793
|
label: "Saldo em aberto",
|
|
7627
8794
|
icon: CircleDollarSign,
|
|
7628
|
-
value:
|
|
8795
|
+
value: money3(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
|
|
7629
8796
|
},
|
|
7630
8797
|
{
|
|
7631
8798
|
id: "issued",
|
|
7632
8799
|
label: "Valor emitido",
|
|
7633
8800
|
icon: TicketCheck,
|
|
7634
|
-
value:
|
|
8801
|
+
value: money3(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
|
|
7635
8802
|
}
|
|
7636
8803
|
];
|
|
7637
8804
|
}
|
|
7638
8805
|
});
|
|
7639
|
-
const ContentPageWrapped = () =>
|
|
8806
|
+
const ContentPageWrapped = () => React13__default.createElement(ContentPage, { resolveProvider: options?.provider });
|
|
7640
8807
|
ContentPageWrapped.displayName = "ContentPage";
|
|
7641
|
-
const ChannelsPageWrapped = () =>
|
|
8808
|
+
const ChannelsPageWrapped = () => React13__default.createElement(ChannelsPage, { resolveProvider: options?.provider });
|
|
7642
8809
|
ChannelsPageWrapped.displayName = "ChannelsPage";
|
|
7643
8810
|
const navSection = options?.navSection ?? "main";
|
|
7644
8811
|
const navPosition = options?.navPosition ?? 6;
|
|
@@ -7728,7 +8895,7 @@ function createShopPlugin(options) {
|
|
|
7728
8895
|
];
|
|
7729
8896
|
const redirectTo = (target) => {
|
|
7730
8897
|
const Redirect = () => {
|
|
7731
|
-
|
|
8898
|
+
React13__default.useEffect(() => {
|
|
7732
8899
|
adminNavigateTo(target);
|
|
7733
8900
|
}, []);
|
|
7734
8901
|
return null;
|
|
@@ -7760,6 +8927,10 @@ function createShopPlugin(options) {
|
|
|
7760
8927
|
return {
|
|
7761
8928
|
id: "shop",
|
|
7762
8929
|
defaultAgentRole: "operations",
|
|
8930
|
+
// O que a loja pede de um tenant novo, na ordem em que faz diferença.
|
|
8931
|
+
// Vira linha de tarefa, aparece no checklist da trilha e é o roteiro que o
|
|
8932
|
+
// assistente segue — ver ./lib/onboarding.ts.
|
|
8933
|
+
onboarding: { program: buildShopOnboarding() },
|
|
7763
8934
|
name: options?.navLabel ?? "Shop",
|
|
7764
8935
|
icon: "ShoppingBag",
|
|
7765
8936
|
version: "1.0.0",
|
|
@@ -7852,7 +9023,7 @@ function createShopPlugin(options) {
|
|
|
7852
9023
|
label: label("store", "Loja"),
|
|
7853
9024
|
icon: "ShoppingBag",
|
|
7854
9025
|
group: COMMERCE_GROUP,
|
|
7855
|
-
component: settingsTab("StoreGeneralSettings", () =>
|
|
9026
|
+
component: settingsTab("StoreGeneralSettings", () => React13__default.createElement(StoreGeneralSettings, { currency: curr, provider: options?.provider })),
|
|
7856
9027
|
order: 30,
|
|
7857
9028
|
permission: READ
|
|
7858
9029
|
},
|
|
@@ -7861,7 +9032,7 @@ function createShopPlugin(options) {
|
|
|
7861
9032
|
label: "Pagamentos",
|
|
7862
9033
|
icon: "CreditCard",
|
|
7863
9034
|
group: COMMERCE_GROUP,
|
|
7864
|
-
component: settingsTab("PaymentsSettings", () =>
|
|
9035
|
+
component: settingsTab("PaymentsSettings", () => React13__default.createElement(PaymentsSettings, { provider: options?.provider })),
|
|
7865
9036
|
order: 31,
|
|
7866
9037
|
permission: READ
|
|
7867
9038
|
},
|
|
@@ -7870,7 +9041,7 @@ function createShopPlugin(options) {
|
|
|
7870
9041
|
label: "Checkout",
|
|
7871
9042
|
icon: "ShoppingCart",
|
|
7872
9043
|
group: COMMERCE_GROUP,
|
|
7873
|
-
component: settingsTab("CheckoutSettings", () =>
|
|
9044
|
+
component: settingsTab("CheckoutSettings", () => React13__default.createElement(CheckoutSettings, { provider: options?.provider })),
|
|
7874
9045
|
order: 32,
|
|
7875
9046
|
permission: READ
|
|
7876
9047
|
},
|
|
@@ -7879,7 +9050,7 @@ function createShopPlugin(options) {
|
|
|
7879
9050
|
label: label("shipping", "Entrega"),
|
|
7880
9051
|
icon: "Truck",
|
|
7881
9052
|
group: COMMERCE_GROUP,
|
|
7882
|
-
component: settingsTab("ShippingSettings", () =>
|
|
9053
|
+
component: settingsTab("ShippingSettings", () => React13__default.createElement(ShippingSettings, { provider: options?.provider })),
|
|
7883
9054
|
order: 33,
|
|
7884
9055
|
permission: READ
|
|
7885
9056
|
},
|
|
@@ -7888,7 +9059,7 @@ function createShopPlugin(options) {
|
|
|
7888
9059
|
label: "Fulfillment",
|
|
7889
9060
|
icon: "PackageCheck",
|
|
7890
9061
|
group: COMMERCE_GROUP,
|
|
7891
|
-
component: settingsTab("FulfillmentSettings", () =>
|
|
9062
|
+
component: settingsTab("FulfillmentSettings", () => React13__default.createElement(FulfillmentSettings, { provider: options?.provider })),
|
|
7892
9063
|
order: 34,
|
|
7893
9064
|
permission: READ
|
|
7894
9065
|
}
|