@fayz-ai/plugin-shop 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ShopPage-6IQ7WKQG.js → ShopPage-5IERN4E5.js} +339 -412
- package/dist/ShopPage-5IERN4E5.js.map +1 -0
- package/dist/{chunk-NFFJVQCY.js → chunk-RPBKGW24.js} +4 -18
- package/dist/chunk-RPBKGW24.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +389 -380
- package/dist/index.js.map +1 -1
- package/dist/integrations/abacatepay/connectorDef.d.ts +1 -1
- package/dist/integrations/abacatepay/sync.d.ts.map +1 -1
- package/dist/integrations/appmax/connectorDef.d.ts +1 -1
- package/dist/integrations/melhor-envio/connectorDef.d.ts +1 -1
- package/dist/integrations/mercadopago/connectorDef.d.ts.map +1 -1
- package/dist/lib/onboarding.d.ts +1 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/settings/error-message.d.ts +8 -0
- package/dist/settings/error-message.d.ts.map +1 -1
- package/dist/views/NewOrderPage.d.ts.map +1 -1
- package/dist/views/OrderDetailView.d.ts.map +1 -1
- package/dist/views/ProductImagesTab.d.ts.map +1 -1
- package/dist/views/ProductPositionsTab.d.ts.map +1 -1
- package/dist/views/ProductVariantsTab.d.ts.map +1 -1
- package/dist/views/ShipOrderForm.d.ts.map +1 -1
- package/dist/views/ShopPage.d.ts.map +1 -1
- package/dist/views/productEntity.d.ts.map +1 -1
- package/dist/views/stock-agent.d.ts.map +1 -1
- package/package.json +7 -6
- package/dist/ShopPage-6IQ7WKQG.js.map +0 -1
- package/dist/chunk-NFFJVQCY.js.map +0 -1
- package/dist/views/ChannelsPage.d.ts +0 -7
- package/dist/views/ChannelsPage.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { shopCustomerLookup, buildShopCustomerEntity, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-
|
|
1
|
+
import { shopCustomerLookup, buildShopCustomerEntity, resolve, invalidateProductLists, listProductsDetailed, applyQuery, errorMessage, listProductsCached, resolveTerm, candidateMeta, packageMeasureLabel, PACKAGES_REGISTRY, packagesRegistry, SHIPPING_PROFILES_REGISTRY, SHIPPING_ZONES_REGISTRY, buildShopRegistries, locationSummary, LOCATIONS_REGISTRY } from './chunk-RPBKGW24.js';
|
|
2
2
|
import * as React13 from 'react';
|
|
3
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
|
+
import { createConnectionStore, connectionSettings, connectionStatus, settingsStatus, connectionRuns, tl, getSupabaseClientOptional, registerMessageDefaults, connectorRuntimeToken, CONNECTOR_RUNTIME_TOKEN_HEADER, countByTenant, errorMessage as errorMessage$1, getActiveTenantId, emitDataChanged, resolveDataProvider } from '@fayz-ai/core';
|
|
5
5
|
import { getShopTenantId, setShopTenantResolver, buildShopMessageDefaults, registerShippingLabelProvider, getShopProvider, buildShopDomainEvents, axesFromOptions, planVariantMatrix, validateVariantAxes, hasOnlyDefaultVariant, MAX_VARIANT_AXES, leavesNothingSellable, productImageToMediaFile, formatVariantCombination, applyVariantMatrix } from '@fayz-ai/shop';
|
|
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';
|
|
7
|
-
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff,
|
|
8
|
-
import { PageHeaderActions, PageTabs, Card, Button,
|
|
6
|
+
import { fayzConnectorOAuth, describeConnectorAccount, useOrganizationStore, registerEntityPath, registerPersonLookup, createCrudPage, adminNavigateTo, DetailTabHeader, useAgentSurface, askAI, AskAIButton, CrudFormPage, SettingsGroup, SelectRow, ToggleRow, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, MultiSelectRow, AddConnectorButton, AddConnectorPanel, ConnectorDetail, RegistryCrudView, TextRow, ConnectorRow, ConnectorMark, StateChip } from '@fayz-ai/admin';
|
|
7
|
+
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, ImageOff, Plus, Loader2, WandSparkles, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, Upload, AlertTriangle, X, ExternalLink, Box, ChevronDown, Film, FileText, Warehouse, ArrowRight, MapPin, Car, Truck, Info, Check, Sparkles, Ban, ListChecks, Search, Building2 } from 'lucide-react';
|
|
8
|
+
import { PageHeaderActions, PageTabs, Card, Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalFooter, Thumbnail, PageHeader, toast, ConfirmDialog, Hint, ModalBody, Skeleton } from '@fayz-ai/ui';
|
|
9
9
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
10
10
|
import { listStockReasonCodes, resolveStockStore, createSupabaseStockStore } from '@fayz-ai/shop/inventory';
|
|
11
11
|
import { normalizeMovementType } from '@fayz-ai/plugin-inventory';
|
|
12
12
|
import { advanceWindow, createRateLimiter, sliceWindow, paginate, reconcileMirror, serializeCursor } from '@fayz-ai/core/connector-runtime';
|
|
13
13
|
|
|
14
|
-
function buildShopOnboarding() {
|
|
14
|
+
function buildShopOnboarding(siteRoute = "/site") {
|
|
15
15
|
return {
|
|
16
16
|
id: "shop",
|
|
17
17
|
title: "Sua loja no ar",
|
|
@@ -87,22 +87,22 @@ function buildShopOnboarding() {
|
|
|
87
87
|
{
|
|
88
88
|
key: "storefront",
|
|
89
89
|
title: "Configure sua loja online",
|
|
90
|
-
description: "O endere\xE7o que voc\xEA manda para o cliente \u2014 e
|
|
90
|
+
description: "O endere\xE7o que voc\xEA manda para o cliente \u2014 e como a vitrine est\xE1 hoje.",
|
|
91
91
|
icon: "Store",
|
|
92
|
-
route:
|
|
92
|
+
route: siteRoute,
|
|
93
93
|
priority: "high",
|
|
94
94
|
// A plataforma chama de "publique seu site"; numa loja é a vitrine, e
|
|
95
95
|
// quem sabe configurá-la é este módulo.
|
|
96
96
|
replaces: "publish",
|
|
97
|
-
agentHint: "Confira
|
|
98
|
-
howTo: '
|
|
97
|
+
agentHint: "Confira como a vitrine est\xE1 e o endere\xE7o dela. Publicar sem frete configurado gera pedido que ningu\xE9m consegue entregar.",
|
|
98
|
+
howTo: 'O construtor do site (o "Editar" no item "Sua loja", na barra lateral). Veja como a vitrine est\xE1, ajuste o que precisar conversando com o assistente e publique. Esse link \xE9 o que vai para a bio do Instagram e para o WhatsApp.',
|
|
99
99
|
celebrate: "Vitrine pronta \u2728",
|
|
100
100
|
script: [
|
|
101
101
|
{ kind: "say", text: "Agora a vitrine \u2014 o endere\xE7o que voc\xEA manda para o cliente." },
|
|
102
|
-
{ kind: "goto", route:
|
|
102
|
+
{ kind: "goto", route: siteRoute },
|
|
103
103
|
{
|
|
104
104
|
kind: "ask",
|
|
105
|
-
text: "Estou
|
|
105
|
+
text: "Estou vendo o site da loja. Confira comigo como ele est\xE1 e me diga o que falta para publicar."
|
|
106
106
|
}
|
|
107
107
|
]
|
|
108
108
|
},
|
|
@@ -175,7 +175,7 @@ function buildShopOnboarding() {
|
|
|
175
175
|
waitForUser: true
|
|
176
176
|
}
|
|
177
177
|
],
|
|
178
|
-
check: async () => await countByTenant("
|
|
178
|
+
check: async () => await countByTenant("v_shop_orders") > 0,
|
|
179
179
|
tour: {
|
|
180
180
|
id: "shop.orders",
|
|
181
181
|
stops: [
|
|
@@ -194,17 +194,17 @@ function buildShopOnboarding() {
|
|
|
194
194
|
|
|
195
195
|
// src/integrations/melhor-envio/labelProvider.ts
|
|
196
196
|
var MELHOR_ENVIO_CARRIER = "melhor-envio";
|
|
197
|
-
function createMelhorEnvioLabelProvider(
|
|
197
|
+
function createMelhorEnvioLabelProvider(resolve3) {
|
|
198
198
|
const unsupported = () => {
|
|
199
199
|
throw new Error("Este painel n\xE3o sabe comprar etiquetas nesta loja.");
|
|
200
200
|
};
|
|
201
201
|
return {
|
|
202
202
|
id: MELHOR_ENVIO_CARRIER,
|
|
203
203
|
label: "Melhor Envio",
|
|
204
|
-
quote: async (orderId) => await
|
|
205
|
-
buy: async (orderId, serviceId) => await
|
|
206
|
-
print: async (orderId) => await
|
|
207
|
-
track: async (orderId) => await
|
|
204
|
+
quote: async (orderId) => await resolve3().quoteShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
|
|
205
|
+
buy: async (orderId, serviceId) => await resolve3().buyShippingLabel?.(orderId, serviceId, MELHOR_ENVIO_CARRIER) ?? unsupported(),
|
|
206
|
+
print: async (orderId) => await resolve3().printShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null,
|
|
207
|
+
track: async (orderId) => await resolve3().trackShippingLabel?.(orderId, MELHOR_ENVIO_CARRIER) ?? null
|
|
208
208
|
};
|
|
209
209
|
}
|
|
210
210
|
function ProductImagesTab({ item, getProvider }) {
|
|
@@ -256,27 +256,34 @@ function ProductImagesTab({ item, getProvider }) {
|
|
|
256
256
|
}
|
|
257
257
|
const update = getProvider().updateProductImage;
|
|
258
258
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
259
|
-
/* @__PURE__ */
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
multiple: true,
|
|
273
|
-
disabled: busy,
|
|
274
|
-
className: "hidden",
|
|
275
|
-
onChange: (event) => void upload(event.target.files)
|
|
276
|
-
}
|
|
259
|
+
/* @__PURE__ */ jsx(
|
|
260
|
+
DetailTabHeader,
|
|
261
|
+
{
|
|
262
|
+
title: "Fotos do produto",
|
|
263
|
+
description: "A primeira foto \xE9 a capa \u2014 aparece no cat\xE1logo, no carrinho e no pedido. As demais viram o carrossel na p\xE1gina do produto.",
|
|
264
|
+
actions: (
|
|
265
|
+
// The same Button as every other tab. It used to be a <label> hand-
|
|
266
|
+
// painted to look like one, which drifted the moment the real button
|
|
267
|
+
// changed — different height, no icon, no disabled state.
|
|
268
|
+
/* @__PURE__ */ jsxs(Button, { disabled: busy, onClick: () => fileRef.current?.click(), children: [
|
|
269
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
270
|
+
busy ? "Enviando\u2026" : "Adicionar fotos"
|
|
271
|
+
] })
|
|
277
272
|
)
|
|
278
|
-
|
|
279
|
-
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
/* @__PURE__ */ jsx(
|
|
276
|
+
"input",
|
|
277
|
+
{
|
|
278
|
+
ref: fileRef,
|
|
279
|
+
type: "file",
|
|
280
|
+
accept: "image/*",
|
|
281
|
+
multiple: true,
|
|
282
|
+
disabled: busy,
|
|
283
|
+
className: "hidden",
|
|
284
|
+
onChange: (event) => void upload(event.target.files)
|
|
285
|
+
}
|
|
286
|
+
),
|
|
280
287
|
error && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-800", children: error }),
|
|
281
288
|
images.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-10 text-center text-sm text-muted-foreground", children: "Nenhuma foto ainda. Produtos com mais de uma foto vendem melhor \u2014 mostre a embalagem, o produto pronto e uma refer\xEAncia de tamanho." }) : /* @__PURE__ */ jsx("ul", { className: "fz-cards gap-3", children: images.map((image, index) => /* @__PURE__ */ jsxs("li", { className: "overflow-hidden rounded-lg border", children: [
|
|
282
289
|
/* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted", children: [
|
|
@@ -357,6 +364,7 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
357
364
|
const [axes, setAxes] = React13__default.useState([]);
|
|
358
365
|
const [variants, setVariants] = React13__default.useState([]);
|
|
359
366
|
const [busy, setBusy] = React13__default.useState(false);
|
|
367
|
+
const [adding, setAdding] = React13__default.useState(false);
|
|
360
368
|
const [error, setError] = React13__default.useState(null);
|
|
361
369
|
const [selected, setSelected] = React13__default.useState(/* @__PURE__ */ new Set());
|
|
362
370
|
const [dirty, setDirty] = React13__default.useState(false);
|
|
@@ -397,25 +405,40 @@ function ProductVariantsTab({ item, getProvider }) {
|
|
|
397
405
|
}
|
|
398
406
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
399
407
|
/* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
400
|
-
/* @__PURE__ */
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
408
|
+
/* @__PURE__ */ jsx(
|
|
409
|
+
DetailTabHeader,
|
|
410
|
+
{
|
|
411
|
+
title: "Op\xE7\xF5es",
|
|
412
|
+
description: "Tamanho, cor, sabor. Cada combina\xE7\xE3o vira uma variante com pre\xE7o, SKU e estoque pr\xF3prios \u2014 \xE9 assim que P e GG deixam de dividir o mesmo contador.",
|
|
413
|
+
actions: (
|
|
414
|
+
// Same Button as the other two tabs. It used to be a bare <button>
|
|
415
|
+
// at text-xs with its own border, which read as a lesser control
|
|
416
|
+
// than "Dar entrada" one tab over, for no reason anybody chose.
|
|
417
|
+
/* @__PURE__ */ jsxs(
|
|
418
|
+
Button,
|
|
419
|
+
{
|
|
420
|
+
disabled: busy || axes.length >= MAX_VARIANT_AXES,
|
|
421
|
+
onClick: () => setAdding(true),
|
|
422
|
+
children: [
|
|
423
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
424
|
+
" Adicionar op\xE7\xE3o"
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
),
|
|
431
|
+
/* @__PURE__ */ jsx(
|
|
432
|
+
NewAxisModal,
|
|
433
|
+
{
|
|
434
|
+
open: adding,
|
|
435
|
+
onClose: () => setAdding(false),
|
|
436
|
+
onCreate: (axis) => {
|
|
437
|
+
patchAxes([...axes, axis]);
|
|
438
|
+
setAdding(false);
|
|
416
439
|
}
|
|
417
|
-
|
|
418
|
-
|
|
440
|
+
}
|
|
441
|
+
),
|
|
419
442
|
axes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "Este produto \xE9 vendido de um jeito s\xF3. Adicione uma op\xE7\xE3o se ele tem tamanhos, cores ou sabores." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: axes.map((axis, index) => /* @__PURE__ */ jsx(
|
|
420
443
|
AxisEditor,
|
|
421
444
|
{
|
|
@@ -568,14 +591,17 @@ function VariantGrid({ rows, axes, product, busy, dirty, selected, onSelect, onS
|
|
|
568
591
|
onSelect(next);
|
|
569
592
|
}
|
|
570
593
|
return /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
|
|
571
|
-
/* @__PURE__ */
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
594
|
+
/* @__PURE__ */ jsx(
|
|
595
|
+
DetailTabHeader,
|
|
596
|
+
{
|
|
597
|
+
title: "Variantes",
|
|
598
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
599
|
+
"Pre\xE7o vazio herda o pre\xE7o do produto \u2014 o cat\xE1logo em que todo tamanho custa igual n\xE3o precisa repetir o n\xFAmero ",
|
|
600
|
+
rows.length,
|
|
601
|
+
" vezes. Medida vazia herda a do produto, do mesmo jeito."
|
|
602
|
+
] })
|
|
603
|
+
}
|
|
604
|
+
),
|
|
579
605
|
dirty && /* @__PURE__ */ jsx(Notice, { children: "As linhas abaixo ainda refletem o que est\xE1 salvo. Salve as op\xE7\xF5es para gerar as novas combina\xE7\xF5es." }),
|
|
580
606
|
selected.size > 0 && /* @__PURE__ */ jsx(
|
|
581
607
|
BulkBar,
|
|
@@ -904,7 +930,96 @@ function describePlan(plan) {
|
|
|
904
930
|
return `${parts.join(", ")}. Combina\xE7\xF5es removidas s\xE3o arquivadas, nunca exclu\xEDdas \u2014 o estoque delas continua no hist\xF3rico.`;
|
|
905
931
|
}
|
|
906
932
|
function message(error) {
|
|
907
|
-
return
|
|
933
|
+
return errorMessage$1(error);
|
|
934
|
+
}
|
|
935
|
+
function NewAxisModal({ open, onClose, onCreate }) {
|
|
936
|
+
const [name, setName] = React13__default.useState("");
|
|
937
|
+
const [values, setValues] = React13__default.useState([""]);
|
|
938
|
+
React13__default.useEffect(() => {
|
|
939
|
+
if (open) {
|
|
940
|
+
setName("");
|
|
941
|
+
setValues([""]);
|
|
942
|
+
}
|
|
943
|
+
}, [open]);
|
|
944
|
+
function setValue(index, next) {
|
|
945
|
+
setValues((prev) => {
|
|
946
|
+
const out = [...prev];
|
|
947
|
+
out[index] = next;
|
|
948
|
+
if (index === out.length - 1 && next.trim()) out.push("");
|
|
949
|
+
return out;
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
function removeValue(index) {
|
|
953
|
+
setValues((prev) => {
|
|
954
|
+
const out = prev.filter((_, i) => i !== index);
|
|
955
|
+
return out.length === 0 || out[out.length - 1].trim() ? [...out, ""] : out;
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
const filled = values.map((v) => v.trim()).filter(Boolean);
|
|
959
|
+
const unique = [...new Set(filled)];
|
|
960
|
+
const hasDuplicate = unique.length !== filled.length;
|
|
961
|
+
const ready = name.trim().length > 0 && unique.length > 0 && !hasDuplicate;
|
|
962
|
+
return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
|
|
963
|
+
if (!o) onClose();
|
|
964
|
+
}, children: /* @__PURE__ */ jsxs(ModalContent, { className: "sm:max-w-md", children: [
|
|
965
|
+
/* @__PURE__ */ jsxs(ModalHeader, { children: [
|
|
966
|
+
/* @__PURE__ */ jsx(ModalTitle, { children: "Nova op\xE7\xE3o" }),
|
|
967
|
+
/* @__PURE__ */ jsx(ModalDescription, { children: "Cada combina\xE7\xE3o de op\xE7\xF5es vira uma variante com pre\xE7o, SKU e estoque pr\xF3prios." })
|
|
968
|
+
] }),
|
|
969
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 py-2", children: [
|
|
970
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
971
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Nome" }),
|
|
972
|
+
/* @__PURE__ */ jsx(
|
|
973
|
+
"input",
|
|
974
|
+
{
|
|
975
|
+
autoFocus: true,
|
|
976
|
+
value: name,
|
|
977
|
+
onChange: (e) => setName(e.target.value),
|
|
978
|
+
placeholder: "Tamanho",
|
|
979
|
+
className: "mt-1 h-9 w-full rounded-md border bg-background px-2.5 text-sm outline-none focus:border-primary"
|
|
980
|
+
}
|
|
981
|
+
)
|
|
982
|
+
] }),
|
|
983
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
984
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Valores" }),
|
|
985
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-1 space-y-1.5", children: values.map((value, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-1.5", children: [
|
|
986
|
+
/* @__PURE__ */ jsx(
|
|
987
|
+
"input",
|
|
988
|
+
{
|
|
989
|
+
value,
|
|
990
|
+
onChange: (e) => setValue(index, e.target.value),
|
|
991
|
+
onKeyDown: (e) => {
|
|
992
|
+
if (e.key === "Enter") {
|
|
993
|
+
e.preventDefault();
|
|
994
|
+
if (index === values.length - 1 && value.trim()) setValue(index, value);
|
|
995
|
+
const rows = e.currentTarget.closest("ul")?.querySelectorAll("input");
|
|
996
|
+
window.setTimeout(() => rows?.[index + 1]?.focus(), 0);
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
placeholder: index === values.length - 1 ? "Adicionar valor" : "",
|
|
1000
|
+
"aria-label": `Valor ${index + 1}`,
|
|
1001
|
+
className: "h-9 w-full rounded-md border bg-background px-2.5 text-sm outline-none focus:border-primary"
|
|
1002
|
+
}
|
|
1003
|
+
),
|
|
1004
|
+
index < values.length - 1 && /* @__PURE__ */ jsx(
|
|
1005
|
+
"button",
|
|
1006
|
+
{
|
|
1007
|
+
type: "button",
|
|
1008
|
+
onClick: () => removeValue(index),
|
|
1009
|
+
"aria-label": `Remover valor ${index + 1}`,
|
|
1010
|
+
className: "fz-tap shrink-0 rounded-md p-1.5 text-muted-foreground transition hover:bg-muted hover:text-destructive",
|
|
1011
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
1012
|
+
}
|
|
1013
|
+
)
|
|
1014
|
+
] }, index)) }),
|
|
1015
|
+
hasDuplicate && /* @__PURE__ */ jsx("span", { className: "mt-1 block text-[11px] text-destructive", children: "H\xE1 valores repetidos \u2014 cada um vira uma variante, ent\xE3o dois iguais viram uma s\xF3." })
|
|
1016
|
+
] })
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ jsxs(ModalFooter, { children: [
|
|
1019
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: onClose, children: "Cancelar" }),
|
|
1020
|
+
/* @__PURE__ */ jsx(Button, { disabled: !ready, onClick: () => onCreate({ name: name.trim(), values: unique }), children: "Adicionar" })
|
|
1021
|
+
] })
|
|
1022
|
+
] }) });
|
|
908
1023
|
}
|
|
909
1024
|
var KIND_OF = {
|
|
910
1025
|
// `sign: 0` = o razão aceita os dois lados (ajuste corrige para cima e para baixo).
|
|
@@ -1018,8 +1133,14 @@ async function commitMovement(args) {
|
|
|
1018
1133
|
// a Lista, e é a que o usuário estava vendo
|
|
1019
1134
|
"plg_shop_product_variants",
|
|
1020
1135
|
// a matriz de variantes
|
|
1021
|
-
|
|
1136
|
+
// O saldo mudou de livro: quem move o número agora é o razão do
|
|
1137
|
+
// plugin-inventory, e o saldo compartilhado é `public.stock_balances`.
|
|
1138
|
+
// Invalidar a tabela antiga deixava as Posições mostrando o valor anterior
|
|
1139
|
+
// até alguém recarregar — o cache apontando para onde ninguém mais escreve.
|
|
1140
|
+
"stock_balances",
|
|
1022
1141
|
// as Posições
|
|
1142
|
+
"plg_inventory_stock_movements",
|
|
1143
|
+
// o histórico de movimentos
|
|
1023
1144
|
"products"
|
|
1024
1145
|
// o arquétipo, que as telas de Estoque leem
|
|
1025
1146
|
]) {
|
|
@@ -1349,10 +1470,11 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1349
1470
|
return /* @__PURE__ */ jsx("p", { className: missing ? "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground" : "rounded-lg border border-destructive/40 bg-destructive/5 p-4 text-sm text-destructive", children: error });
|
|
1350
1471
|
}
|
|
1351
1472
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1352
|
-
/* @__PURE__ */
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1473
|
+
/* @__PURE__ */ jsx(
|
|
1474
|
+
DetailTabHeader,
|
|
1475
|
+
{
|
|
1476
|
+
title: "Estoque do produto",
|
|
1477
|
+
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1356
1478
|
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.onHand }),
|
|
1357
1479
|
" em m\xE3os \xB7",
|
|
1358
1480
|
" ",
|
|
@@ -1361,31 +1483,31 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1361
1483
|
" ",
|
|
1362
1484
|
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available }),
|
|
1363
1485
|
" dispon\xEDvel. O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio."
|
|
1486
|
+
] }),
|
|
1487
|
+
actions: variantId && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1488
|
+
/* @__PURE__ */ jsxs(
|
|
1489
|
+
Button,
|
|
1490
|
+
{
|
|
1491
|
+
variant: "ghost",
|
|
1492
|
+
size: "sm",
|
|
1493
|
+
onClick: () => askAI({
|
|
1494
|
+
intro: STOCK_INTRO,
|
|
1495
|
+
hint: "O que entrou ou saiu\u2026",
|
|
1496
|
+
draft: `Entrada de unidades de ${product.name}`
|
|
1497
|
+
}),
|
|
1498
|
+
children: [
|
|
1499
|
+
/* @__PURE__ */ jsx(WandSparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1500
|
+
"Com IA"
|
|
1501
|
+
]
|
|
1502
|
+
}
|
|
1503
|
+
),
|
|
1504
|
+
/* @__PURE__ */ jsxs(Button, { onClick: () => open("entrada"), children: [
|
|
1505
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
1506
|
+
"Dar entrada"
|
|
1507
|
+
] })
|
|
1364
1508
|
] })
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
/* @__PURE__ */ jsxs(
|
|
1368
|
-
Button,
|
|
1369
|
-
{
|
|
1370
|
-
variant: "ghost",
|
|
1371
|
-
size: "sm",
|
|
1372
|
-
onClick: () => askAI({
|
|
1373
|
-
intro: STOCK_INTRO,
|
|
1374
|
-
hint: "O que entrou ou saiu\u2026",
|
|
1375
|
-
draft: `Entrada de unidades de ${product.name}`
|
|
1376
|
-
}),
|
|
1377
|
-
children: [
|
|
1378
|
-
/* @__PURE__ */ jsx(WandSparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1379
|
-
"Com IA"
|
|
1380
|
-
]
|
|
1381
|
-
}
|
|
1382
|
-
),
|
|
1383
|
-
/* @__PURE__ */ jsxs(Button, { onClick: () => open("entrada"), children: [
|
|
1384
|
-
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-4 w-4" }),
|
|
1385
|
-
"Dar entrada"
|
|
1386
|
-
] })
|
|
1387
|
-
] })
|
|
1388
|
-
] }),
|
|
1509
|
+
}
|
|
1510
|
+
),
|
|
1389
1511
|
!variantId && // Nunca desabilitar um botão sem dizer por quê. Sem variante o razão não
|
|
1390
1512
|
// tem onde postar, e um "Lançar" apagado sem explicação faz o lojista
|
|
1391
1513
|
// clicar três vezes e concluir que a tela está quebrada.
|
|
@@ -1398,103 +1520,110 @@ function ProductPositionsTab({ item, getProvider }) {
|
|
|
1398
1520
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Variante" }),
|
|
1399
1521
|
/* @__PURE__ */ jsx("select", { value: variantId, onChange: (e) => setVariantId(e.target.value), className: field, children: variants.map((v) => /* @__PURE__ */ jsx("option", { value: v.id, children: Object.values(v.options ?? {}).join(" \xB7 ") || v.sku || v.id.slice(0, 8) }, v.id)) })
|
|
1400
1522
|
] }),
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1523
|
+
/* @__PURE__ */ jsx(Modal, { open: type !== null, onOpenChange: (o) => {
|
|
1524
|
+
if (!o) close();
|
|
1525
|
+
}, children: /* @__PURE__ */ jsxs(ModalContent, { className: "sm:max-w-lg", children: [
|
|
1526
|
+
/* @__PURE__ */ jsxs(ModalHeader, { children: [
|
|
1527
|
+
/* @__PURE__ */ jsx(ModalTitle, { children: "Lan\xE7ar movimento" }),
|
|
1528
|
+
/* @__PURE__ */ jsx(ModalDescription, { children: "O raz\xE3o n\xE3o se edita: um erro se corrige com o movimento contr\xE1rio." })
|
|
1529
|
+
] }),
|
|
1530
|
+
/* @__PURE__ */ jsxs("div", { className: "p-1", children: [
|
|
1531
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: MOVEMENT_TYPES.map((t) => /* @__PURE__ */ jsx(
|
|
1532
|
+
"button",
|
|
1533
|
+
{
|
|
1534
|
+
type: "button",
|
|
1535
|
+
onClick: () => setType(t.value),
|
|
1536
|
+
className: `fz-tap rounded-button px-3 py-1.5 text-xs font-medium transition-colors ${type === t.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
1537
|
+
children: t.label
|
|
1538
|
+
},
|
|
1539
|
+
t.value
|
|
1540
|
+
)) }),
|
|
1541
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-3", children: [
|
|
1542
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1543
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Quantidade" }),
|
|
1544
|
+
/* @__PURE__ */ jsx(
|
|
1545
|
+
"input",
|
|
1546
|
+
{
|
|
1547
|
+
ref: quantityRef,
|
|
1548
|
+
type: "number",
|
|
1549
|
+
inputMode: "numeric",
|
|
1550
|
+
min: 1,
|
|
1551
|
+
value: quantity,
|
|
1552
|
+
onChange: (e) => setQuantity(e.target.value),
|
|
1553
|
+
onKeyDown: (e) => {
|
|
1554
|
+
if (e.key === "Enter") {
|
|
1555
|
+
e.preventDefault();
|
|
1556
|
+
void submit();
|
|
1557
|
+
}
|
|
1558
|
+
if (e.key === "Escape") {
|
|
1559
|
+
e.preventDefault();
|
|
1560
|
+
close();
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
placeholder: "0",
|
|
1564
|
+
className: `${field} font-semibold tabular-nums`
|
|
1565
|
+
}
|
|
1566
|
+
),
|
|
1567
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "mt-1 block text-xs text-muted-foreground", children: nextBalance === null ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1568
|
+
"Fica em ",
|
|
1569
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium tabular-nums text-foreground", children: total.available })
|
|
1570
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1571
|
+
/* @__PURE__ */ jsx("span", { className: "tabular-nums", children: total.available }),
|
|
1572
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1.5", children: "\u2192" }),
|
|
1573
|
+
/* @__PURE__ */ jsx("span", { className: `text-base font-semibold tabular-nums ${nextBalance < 0 ? "text-destructive" : "text-foreground"}`, children: nextBalance }),
|
|
1574
|
+
nextBalance < 0 && /* @__PURE__ */ jsx("span", { className: "ml-2 text-destructive", children: "n\xE3o h\xE1 tudo isso" })
|
|
1575
|
+
] }) })
|
|
1576
|
+
] }),
|
|
1577
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1578
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Motivo" }),
|
|
1579
|
+
/* @__PURE__ */ jsxs("select", { value: reasonCode, onChange: (e) => setReasonCode(e.target.value), className: field, children: [
|
|
1580
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Padr\xE3o" }),
|
|
1581
|
+
reasons.map((r) => /* @__PURE__ */ jsx("option", { value: r.key, children: r.label }, r.key))
|
|
1582
|
+
] })
|
|
1583
|
+
] }),
|
|
1584
|
+
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1585
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: type === "entrada" ? "Chegou em" : "Quando" }),
|
|
1586
|
+
/* @__PURE__ */ jsx(
|
|
1587
|
+
"input",
|
|
1588
|
+
{
|
|
1589
|
+
type: "date",
|
|
1590
|
+
value: occurredOn,
|
|
1591
|
+
max: todayISO(),
|
|
1592
|
+
onChange: (e) => setOccurredOn(e.target.value || todayISO()),
|
|
1593
|
+
className: field
|
|
1594
|
+
}
|
|
1595
|
+
)
|
|
1453
1596
|
] })
|
|
1454
1597
|
] }),
|
|
1455
|
-
/* @__PURE__ */ jsxs("label", { className: "block", children: [
|
|
1456
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children:
|
|
1598
|
+
noteOpen ? /* @__PURE__ */ jsxs("label", { className: "mt-3 block", children: [
|
|
1599
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
|
|
1457
1600
|
/* @__PURE__ */ jsx(
|
|
1458
1601
|
"input",
|
|
1459
1602
|
{
|
|
1460
|
-
|
|
1461
|
-
value:
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1603
|
+
autoFocus: true,
|
|
1604
|
+
value: note,
|
|
1605
|
+
onChange: (e) => setNote(e.target.value),
|
|
1606
|
+
className: field,
|
|
1607
|
+
placeholder: "Nota fiscal, quem entregou\u2026"
|
|
1465
1608
|
}
|
|
1466
1609
|
)
|
|
1467
|
-
] })
|
|
1468
|
-
|
|
1469
|
-
noteOpen ? /* @__PURE__ */ jsxs("label", { className: "mt-3 block", children: [
|
|
1470
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Observa\xE7\xE3o" }),
|
|
1471
|
-
/* @__PURE__ */ jsx(
|
|
1472
|
-
"input",
|
|
1610
|
+
] }) : /* @__PURE__ */ jsx(
|
|
1611
|
+
"button",
|
|
1473
1612
|
{
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
placeholder: "Nota fiscal, quem entregou\u2026"
|
|
1613
|
+
type: "button",
|
|
1614
|
+
onClick: () => setNoteOpen(true),
|
|
1615
|
+
className: "mt-2 text-xs font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
|
|
1616
|
+
children: "+ observa\xE7\xE3o"
|
|
1479
1617
|
}
|
|
1480
1618
|
)
|
|
1481
|
-
] })
|
|
1482
|
-
|
|
1483
|
-
{
|
|
1484
|
-
type: "button",
|
|
1485
|
-
onClick: () => setNoteOpen(true),
|
|
1486
|
-
className: "mt-2 text-xs font-medium text-muted-foreground underline-offset-2 hover:text-foreground hover:underline",
|
|
1487
|
-
children: "+ observa\xE7\xE3o"
|
|
1488
|
-
}
|
|
1489
|
-
),
|
|
1490
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center gap-2", children: [
|
|
1491
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || amount === 0, children: saving ? "Lan\xE7ando\u2026" : "Lan\xE7ar" }),
|
|
1492
|
-
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: close, disabled: saving, children: "Cancelar" }),
|
|
1493
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1619
|
+
] }),
|
|
1620
|
+
/* @__PURE__ */ jsxs(ModalFooter, { children: [
|
|
1494
1621
|
amount === 0 && // O motivo de o botão estar apagado, ao lado dele.
|
|
1495
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Diga quantas unidades" })
|
|
1622
|
+
/* @__PURE__ */ jsx("span", { className: "mr-auto text-xs text-muted-foreground", children: "Diga quantas unidades" }),
|
|
1623
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: close, disabled: saving, children: "Cancelar" }),
|
|
1624
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void submit(), disabled: saving || amount === 0, children: saving ? "Lan\xE7ando\u2026" : "Lan\xE7ar" })
|
|
1496
1625
|
] })
|
|
1497
|
-
] }),
|
|
1626
|
+
] }) }),
|
|
1498
1627
|
variantId && (rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-dashed px-4 py-8 text-center text-sm text-muted-foreground", children: "Nenhuma posi\xE7\xE3o ainda \u2014 a primeira entrada abre o saldo." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: rows.map((r) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 rounded-card border bg-card p-3", children: [
|
|
1499
1628
|
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm font-medium", children: r.location }),
|
|
1500
1629
|
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
@@ -1925,6 +2054,30 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
1925
2054
|
hint: "Menor aparece primeiro.",
|
|
1926
2055
|
showInTable: false
|
|
1927
2056
|
},
|
|
2057
|
+
// — Busca (SEO) —
|
|
2058
|
+
// A coleção já tinha estes dois; o produto — que é a página onde alguém
|
|
2059
|
+
// CAI vindo do Google — não tinha nenhum, então cada página herdava o que
|
|
2060
|
+
// a vitrine inventasse a partir do nome.
|
|
2061
|
+
{
|
|
2062
|
+
key: "seoTitle",
|
|
2063
|
+
label: "T\xEDtulo da p\xE1gina",
|
|
2064
|
+
type: "text",
|
|
2065
|
+
group: "seo",
|
|
2066
|
+
span: 2,
|
|
2067
|
+
placeholder: "T\xEDtulo exibido nos resultados de busca",
|
|
2068
|
+
hint: "Vazio usa o nome do produto.",
|
|
2069
|
+
showInTable: false
|
|
2070
|
+
},
|
|
2071
|
+
{
|
|
2072
|
+
key: "seoDescription",
|
|
2073
|
+
label: "Meta descri\xE7\xE3o",
|
|
2074
|
+
type: "textarea",
|
|
2075
|
+
group: "seo",
|
|
2076
|
+
span: 2,
|
|
2077
|
+
placeholder: "O trecho que aparece embaixo do t\xEDtulo no Google",
|
|
2078
|
+
hint: "Vazio usa a descri\xE7\xE3o do produto.",
|
|
2079
|
+
showInTable: false
|
|
2080
|
+
},
|
|
1928
2081
|
// — Envio —
|
|
1929
2082
|
// O desenho é o do Shopify e a ordem importa: o interruptor primeiro, e o
|
|
1930
2083
|
// resto da seção só existe depois dele. Perguntar altura de um e-book não é
|
|
@@ -2060,6 +2213,7 @@ function buildShopProductEntity(currencyCode = "BRL", getProvider) {
|
|
|
2060
2213
|
{ id: "pricing", label: "Pre\xE7o", description: 'O pre\xE7o comparativo vira o "de" riscado na vitrine.', columns: 3 },
|
|
2061
2214
|
{ id: "inventory", label: "Estoque", columns: 2 },
|
|
2062
2215
|
{ id: "publishing", label: "Publica\xE7\xE3o", description: "Controla se o produto aparece na loja.", columns: 2 },
|
|
2216
|
+
{ id: "seo", label: "Busca (SEO)", description: "Como este produto aparece nos resultados de busca.", columns: 2 },
|
|
2063
2217
|
{
|
|
2064
2218
|
id: "shipping",
|
|
2065
2219
|
label: "Envio",
|
|
@@ -5116,7 +5270,7 @@ var melhorEnvioConnectorDef = {
|
|
|
5116
5270
|
direction: "outbound",
|
|
5117
5271
|
docsUrl: "https://docs.melhorenvio.com.br",
|
|
5118
5272
|
// A quote WRITES NOTHING — `createShippingQuoter` says so in as many words,
|
|
5119
|
-
// and 029_connections.sql is why: one row per quote would make
|
|
5273
|
+
// and 029_connections.sql is why: one row per quote would make sync_runs
|
|
5120
5274
|
// the largest table in the pool within weeks. So `order` is read-only here,
|
|
5121
5275
|
// and the only write is the label, which is an effect with an idempotency key
|
|
5122
5276
|
// (`createLabelBuyer`) because a label bought twice is money spent twice.
|
|
@@ -5187,7 +5341,7 @@ var melhorEnvioConnectorDef = {
|
|
|
5187
5341
|
),
|
|
5188
5342
|
...connectionSettings(store),
|
|
5189
5343
|
// This used to say the platform's redirect route writes `active = true`. It
|
|
5190
|
-
// does not, and says so itself: "It does not touch `
|
|
5344
|
+
// does not, and says so itself: "It does not touch `connections` in the
|
|
5191
5345
|
// tenant's pool. Per FAY-1388 ... the app writes its own." Bling shipped with
|
|
5192
5346
|
// the same belief and looped — authorize, come back, still "not connected",
|
|
5193
5347
|
// click again, forever.
|
|
@@ -5338,7 +5492,7 @@ var appmaxConnectorDef = {
|
|
|
5338
5492
|
}
|
|
5339
5493
|
],
|
|
5340
5494
|
// Read from the row the list already batched, so a screen with N connectors
|
|
5341
|
-
// costs one
|
|
5495
|
+
// costs one connections request rather than N.
|
|
5342
5496
|
...connectionStatus(
|
|
5343
5497
|
APPMAX_CONNECTOR_ID,
|
|
5344
5498
|
(connection) => ({
|
|
@@ -5614,7 +5768,7 @@ var abacatePayConnectorDef = {
|
|
|
5614
5768
|
}
|
|
5615
5769
|
],
|
|
5616
5770
|
// Read from the row the list already batched, so a screen with N connectors
|
|
5617
|
-
// costs one
|
|
5771
|
+
// costs one connections request rather than N.
|
|
5618
5772
|
...connectionStatus(
|
|
5619
5773
|
ABACATEPAY_CONNECTOR_ID,
|
|
5620
5774
|
(connection) => ({
|
|
@@ -5759,7 +5913,7 @@ var stripeConnectConnectorDef = {
|
|
|
5759
5913
|
// Reached with no values when the browser returns marked `connected`. It is
|
|
5760
5914
|
// required even though nothing is collected here: the platform callback stores
|
|
5761
5915
|
// the credential but deliberately never touches the pool, so this is the only
|
|
5762
|
-
// thing that writes the tenant's `
|
|
5916
|
+
// thing that writes the tenant's `connections` row.
|
|
5763
5917
|
saveConnection: async () => {
|
|
5764
5918
|
await store4.save({ active: true });
|
|
5765
5919
|
},
|
|
@@ -6033,7 +6187,7 @@ var vindiConnectorDef = {
|
|
|
6033
6187
|
}
|
|
6034
6188
|
],
|
|
6035
6189
|
// Read from the row the list already batched, so a screen with N connectors
|
|
6036
|
-
// costs one
|
|
6190
|
+
// costs one connections request rather than N.
|
|
6037
6191
|
...connectionStatus(
|
|
6038
6192
|
VINDI_CONNECTOR_ID,
|
|
6039
6193
|
(connection) => ({
|
|
@@ -6065,16 +6219,23 @@ var vindiConnectorDef = {
|
|
|
6065
6219
|
var MERCADOPAGO_CONNECTOR_ID = "mercadopago";
|
|
6066
6220
|
var MERCADOPAGO_LATENCY_BUDGET_MS = 6e3;
|
|
6067
6221
|
var store6 = createConnectionStore(MERCADOPAGO_CONNECTOR_ID);
|
|
6068
|
-
function connectionDetail2() {
|
|
6069
|
-
|
|
6222
|
+
function connectionDetail2(connection) {
|
|
6223
|
+
const account = String(connection?.displayName ?? "").trim();
|
|
6224
|
+
return account ? `${account} \xB7 Pix e cart\xE3o` : "Pix e cart\xE3o";
|
|
6070
6225
|
}
|
|
6071
6226
|
function MercadoPagoExtraPanel() {
|
|
6072
6227
|
const [connected, setConnected] = useState(false);
|
|
6228
|
+
const [account, setAccount] = useState({ name: null, id: null });
|
|
6073
6229
|
const [loaded, setLoaded] = useState(false);
|
|
6074
6230
|
useEffect(() => {
|
|
6075
6231
|
let cancelled = false;
|
|
6076
6232
|
void store6.get().then((c) => {
|
|
6077
|
-
if (
|
|
6233
|
+
if (cancelled) return;
|
|
6234
|
+
setConnected(!!c?.active);
|
|
6235
|
+
setAccount({
|
|
6236
|
+
name: String(c?.displayName ?? "").trim() || null,
|
|
6237
|
+
id: String(c?.providerAccountKey ?? "").trim() || null
|
|
6238
|
+
});
|
|
6078
6239
|
}).catch(() => {
|
|
6079
6240
|
}).finally(() => {
|
|
6080
6241
|
if (!cancelled) setLoaded(true);
|
|
@@ -6085,6 +6246,29 @@ function MercadoPagoExtraPanel() {
|
|
|
6085
6246
|
}, []);
|
|
6086
6247
|
if (!loaded) return null;
|
|
6087
6248
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t pt-4 text-xs text-muted-foreground", children: [
|
|
6249
|
+
connected && (account.name || account.id) && /* Named before anything else is explained. "Conectado" is not an answer
|
|
6250
|
+
to "connected to WHAT" — and a store whose money is going to the
|
|
6251
|
+
wrong Mercado Pago account looks identical to one whose money is not. */
|
|
6252
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/40 p-3", children: [
|
|
6253
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium text-foreground", children: "Conta conectada" }),
|
|
6254
|
+
account.name && /* @__PURE__ */ jsx("p", { className: "mt-1 break-words", children: account.name }),
|
|
6255
|
+
account.id && /* @__PURE__ */ jsxs("p", { className: "mt-0.5", children: [
|
|
6256
|
+
"ID no Mercado Pago: ",
|
|
6257
|
+
/* @__PURE__ */ jsx("code", { className: "rounded bg-muted px-1 py-0.5", children: account.id })
|
|
6258
|
+
] }),
|
|
6259
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1", children: [
|
|
6260
|
+
"N\xE3o \xE9 esta a conta? Clique em ",
|
|
6261
|
+
/* @__PURE__ */ jsx("strong", { children: "Desconectar" }),
|
|
6262
|
+
" e conecte de novo, logado na conta certa do Mercado Pago."
|
|
6263
|
+
] })
|
|
6264
|
+
] }),
|
|
6265
|
+
connected && !account.name && !account.id && /* An older connection, made before the account was recorded. Saying so
|
|
6266
|
+
beats showing nothing and letting it read as "no account". */
|
|
6267
|
+
/* @__PURE__ */ jsxs("p", { className: "rounded-md border border-dashed p-3", children: [
|
|
6268
|
+
"Esta conex\xE3o foi feita antes de a loja passar a guardar qual conta foi autorizada. Para ver o nome da conta aqui, clique em ",
|
|
6269
|
+
/* @__PURE__ */ jsx("strong", { children: "Desconectar" }),
|
|
6270
|
+
" e conecte novamente."
|
|
6271
|
+
] }),
|
|
6088
6272
|
connected ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6089
6273
|
/* @__PURE__ */ jsxs("p", { children: [
|
|
6090
6274
|
"Pix e cart\xE3o acontecem ",
|
|
@@ -6205,7 +6389,7 @@ var mercadoPagoConnectorDef = {
|
|
|
6205
6389
|
}
|
|
6206
6390
|
],
|
|
6207
6391
|
// Read from the row the list already batched, so a screen with N connectors
|
|
6208
|
-
// costs one
|
|
6392
|
+
// costs one connections request rather than N.
|
|
6209
6393
|
//
|
|
6210
6394
|
// No `settingsStatus`: this connector has nothing to configure, so a
|
|
6211
6395
|
// connection either has its credential pair or does not. "Conectado, falta
|
|
@@ -6214,7 +6398,7 @@ var mercadoPagoConnectorDef = {
|
|
|
6214
6398
|
MERCADOPAGO_CONNECTOR_ID,
|
|
6215
6399
|
(connection) => ({
|
|
6216
6400
|
connected: !!connection?.active,
|
|
6217
|
-
detail: connectionDetail2(),
|
|
6401
|
+
detail: connectionDetail2(connection),
|
|
6218
6402
|
plane: "request"
|
|
6219
6403
|
}),
|
|
6220
6404
|
{ store: () => store6 }
|
|
@@ -6229,190 +6413,16 @@ var mercadoPagoConnectorDef = {
|
|
|
6229
6413
|
// key alone cannot open an order. Testing belongs to the edge function that
|
|
6230
6414
|
// holds the credential.
|
|
6231
6415
|
saveConnection: async () => {
|
|
6232
|
-
await
|
|
6416
|
+
const account = await describeConnectorAccount({ connectorId: MERCADOPAGO_CONNECTOR_ID });
|
|
6417
|
+
await store6.save({
|
|
6418
|
+
active: true,
|
|
6419
|
+
...account?.displayName ? { displayName: account.displayName } : {},
|
|
6420
|
+
...account?.providerAccountKey ? { providerAccountKey: account.providerAccountKey } : {}
|
|
6421
|
+
});
|
|
6233
6422
|
},
|
|
6234
6423
|
disconnect: () => store6.disconnect(),
|
|
6235
6424
|
ExtraPanel: MercadoPagoExtraPanel
|
|
6236
6425
|
};
|
|
6237
|
-
var KIND_META = {
|
|
6238
|
-
online_store: { label: "Loja Online", icon: Globe, hint: "Seu storefront fayz \u2014 a vitrine p\xFAblica da loja.", placeholder: "https://minhaloja.com.br/" },
|
|
6239
|
-
whatsapp: { label: "WhatsApp", icon: MessageCircle, hint: "Link direto para o WhatsApp Business.", placeholder: "https://wa.me/5521999999999" },
|
|
6240
|
-
instagram: { label: "Instagram", icon: Camera, hint: "Perfil ou loja no Instagram.", placeholder: "https://instagram.com/sualoja" },
|
|
6241
|
-
tiktok_shop: { label: "TikTok Shop", icon: Music, hint: "Sua vitrine no TikTok Shop.", placeholder: "https://shop.tiktok.com/@sualoja" },
|
|
6242
|
-
marketplace: { label: "Marketplace", icon: ShoppingBag, hint: "Mercado Livre, Amazon, Shopee\u2026", placeholder: "https://\u2026" },
|
|
6243
|
-
physical: { label: "Loja F\xEDsica", icon: MapPin, hint: "Endere\xE7o ou ponto de venda f\xEDsico.", placeholder: "https://maps.google.com/\u2026" },
|
|
6244
|
-
custom: { label: "Outro canal", icon: Link2, hint: "Qualquer outro ponto de venda com um link.", placeholder: "https://\u2026" }
|
|
6245
|
-
};
|
|
6246
|
-
var ADDABLE_KINDS = ["whatsapp", "instagram", "tiktok_shop", "marketplace", "physical", "custom"];
|
|
6247
|
-
function resolve2(resolver) {
|
|
6248
|
-
return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
|
|
6249
|
-
}
|
|
6250
|
-
function notifyChannelsChanged() {
|
|
6251
|
-
if (typeof window !== "undefined") window.dispatchEvent(new Event("fayz:channels-changed"));
|
|
6252
|
-
}
|
|
6253
|
-
function ChannelsPage({ resolveProvider }) {
|
|
6254
|
-
const provider = React13__default.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
|
|
6255
|
-
const supported = typeof provider.listSalesChannels === "function";
|
|
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 () => {
|
|
6260
|
-
if (!provider.listSalesChannels) {
|
|
6261
|
-
setLoading(false);
|
|
6262
|
-
return;
|
|
6263
|
-
}
|
|
6264
|
-
setLoading(true);
|
|
6265
|
-
try {
|
|
6266
|
-
const rows = await provider.listSalesChannels();
|
|
6267
|
-
if (!rows.some((c) => c.kind === "online_store") && provider.createSalesChannel) {
|
|
6268
|
-
await provider.createSalesChannel({ kind: "online_store", label: "Loja Online", enabled: true, position: 0 });
|
|
6269
|
-
setChannels(await provider.listSalesChannels());
|
|
6270
|
-
} else {
|
|
6271
|
-
setChannels(rows);
|
|
6272
|
-
}
|
|
6273
|
-
} finally {
|
|
6274
|
-
setLoading(false);
|
|
6275
|
-
}
|
|
6276
|
-
}, [provider]);
|
|
6277
|
-
React13__default.useEffect(() => {
|
|
6278
|
-
void reload();
|
|
6279
|
-
}, [reload]);
|
|
6280
|
-
const patch = async (id, input) => {
|
|
6281
|
-
if (!provider.updateSalesChannel) return;
|
|
6282
|
-
const updated = await provider.updateSalesChannel(id, input);
|
|
6283
|
-
setChannels((prev) => prev.map((c) => c.id === id ? updated : c));
|
|
6284
|
-
notifyChannelsChanged();
|
|
6285
|
-
};
|
|
6286
|
-
const remove = async (id) => {
|
|
6287
|
-
if (!provider.deleteSalesChannel) return;
|
|
6288
|
-
await provider.deleteSalesChannel(id);
|
|
6289
|
-
setChannels((prev) => prev.filter((c) => c.id !== id));
|
|
6290
|
-
notifyChannelsChanged();
|
|
6291
|
-
};
|
|
6292
|
-
const add = async (kind, label, url) => {
|
|
6293
|
-
if (!provider.createSalesChannel) return;
|
|
6294
|
-
const created = await provider.createSalesChannel({ kind, label: label || KIND_META[kind].label, url: url || null, enabled: true, position: channels.length });
|
|
6295
|
-
setChannels((prev) => [...prev, created]);
|
|
6296
|
-
setAdding(null);
|
|
6297
|
-
notifyChannelsChanged();
|
|
6298
|
-
};
|
|
6299
|
-
if (!supported) {
|
|
6300
|
-
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-4xl p-6", children: [
|
|
6301
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Canais de venda" }),
|
|
6302
|
-
/* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Este provedor de loja ainda n\xE3o exp\xF5e canais de venda." })
|
|
6303
|
-
] });
|
|
6304
|
-
}
|
|
6305
|
-
const online = channels.find((c) => c.kind === "online_store");
|
|
6306
|
-
const others = channels.filter((c) => c.kind !== "online_store");
|
|
6307
|
-
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-4xl p-6", children: [
|
|
6308
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
|
|
6309
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Canais de venda" }),
|
|
6310
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Onde sua loja vende. A Loja Online \xE9 o seu storefront fayz; os demais s\xE3o links que aparecem no menu lateral." })
|
|
6311
|
-
] }),
|
|
6312
|
-
loading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Carregando\u2026" }) : /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
6313
|
-
online && /* @__PURE__ */ jsx(OnlineStoreCard, { channel: online, onSave: (url) => patch(online.id, { url }), onToggle: (enabled) => patch(online.id, { enabled }) }),
|
|
6314
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
6315
|
-
/* @__PURE__ */ jsx("div", { className: "mb-2 flex items-center justify-between", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: "Outros canais" }) }),
|
|
6316
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
6317
|
-
others.map((c) => /* @__PURE__ */ jsx(ChannelRow, { channel: c, onSave: (url) => patch(c.id, { url }), onToggle: (enabled) => patch(c.id, { enabled }), onRemove: () => remove(c.id) }, c.id)),
|
|
6318
|
-
others.length === 0 && /* @__PURE__ */ jsx("p", { className: "rounded-card border border-dashed border-border p-4 text-sm text-muted-foreground", children: "Nenhum canal adicional ainda. Adicione WhatsApp, Instagram, TikTok Shop e outros pontos de venda." })
|
|
6319
|
-
] }),
|
|
6320
|
-
adding ? /* @__PURE__ */ jsx(AddChannelForm, { kind: adding, onCancel: () => setAdding(null), onAdd: (label, url) => add(adding, label, url), onKind: setAdding }) : /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap gap-2", children: ADDABLE_KINDS.map((k) => {
|
|
6321
|
-
const Icon = KIND_META[k].icon;
|
|
6322
|
-
return /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setAdding(k), children: [
|
|
6323
|
-
/* @__PURE__ */ jsx(Icon, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
6324
|
-
" ",
|
|
6325
|
-
KIND_META[k].label
|
|
6326
|
-
] }, k);
|
|
6327
|
-
}) })
|
|
6328
|
-
] })
|
|
6329
|
-
] })
|
|
6330
|
-
] });
|
|
6331
|
-
}
|
|
6332
|
-
function OnlineStoreCard({ channel, onSave, onToggle }) {
|
|
6333
|
-
const [url, setUrl] = React13__default.useState(channel.url ?? "");
|
|
6334
|
-
const dirty = (channel.url ?? "") !== url;
|
|
6335
|
-
const meta = KIND_META.online_store;
|
|
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: [
|
|
6337
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(meta.icon, { className: "h-5 w-5 text-primary" }) }),
|
|
6338
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
6339
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
6340
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Loja Online" }),
|
|
6341
|
-
/* @__PURE__ */ jsx("span", { className: `rounded-full px-2 py-0.5 text-xs font-medium ${channel.enabled ? "bg-emerald-100 text-emerald-800" : "bg-muted text-muted-foreground"}`, children: channel.enabled ? "Ativa" : "Inativa" })
|
|
6342
|
-
] }),
|
|
6343
|
-
/* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: meta.hint }),
|
|
6344
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-col gap-2 sm:flex-row sm:items-center", children: [
|
|
6345
|
-
/* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "flex-1" }),
|
|
6346
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
6347
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", disabled: !dirty, onClick: () => onSave(url.trim() || null), children: "Salvar" }),
|
|
6348
|
-
/* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", disabled: !channel.url, onClick: () => channel.url && window.open(channel.url, "_blank", "noopener,noreferrer"), children: [
|
|
6349
|
-
/* @__PURE__ */ jsx(ExternalLink, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
6350
|
-
" Ver loja"
|
|
6351
|
-
] })
|
|
6352
|
-
] })
|
|
6353
|
-
] }),
|
|
6354
|
-
/* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onToggle(!channel.enabled), className: "text-xs font-medium text-muted-foreground hover:text-foreground", children: channel.enabled ? "Desativar canal" : "Ativar canal" }) })
|
|
6355
|
-
] })
|
|
6356
|
-
] }) });
|
|
6357
|
-
}
|
|
6358
|
-
function ChannelRow({ channel, onSave, onToggle, onRemove }) {
|
|
6359
|
-
const [url, setUrl] = React13__default.useState(channel.url ?? "");
|
|
6360
|
-
const [editing, setEditing] = React13__default.useState(false);
|
|
6361
|
-
const meta = KIND_META[channel.kind] ?? KIND_META.custom;
|
|
6362
|
-
const Icon = meta.icon;
|
|
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: [
|
|
6364
|
-
/* @__PURE__ */ jsx("div", { className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full ${channel.enabled ? "bg-primary/10" : "bg-muted"}`, children: /* @__PURE__ */ jsx(Icon, { className: `h-4 w-4 ${channel.enabled ? "text-primary" : "text-muted-foreground"}` }) }),
|
|
6365
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
6366
|
-
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: channel.label }),
|
|
6367
|
-
editing ? /* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "mt-1 h-8" }) : /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: channel.url || "Sem link configurado" })
|
|
6368
|
-
] }),
|
|
6369
|
-
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-1", children: editing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6370
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
6371
|
-
onSave(url.trim() || null);
|
|
6372
|
-
setEditing(false);
|
|
6373
|
-
}, children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }),
|
|
6374
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
|
|
6375
|
-
setUrl(channel.url ?? "");
|
|
6376
|
-
setEditing(false);
|
|
6377
|
-
}, children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }) })
|
|
6378
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6379
|
-
channel.url && /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => window.open(channel.url, "_blank", "noopener,noreferrer"), children: /* @__PURE__ */ jsx(ExternalLink, { className: "h-4 w-4" }) }),
|
|
6380
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => onToggle(!channel.enabled), title: channel.enabled ? "Desativar" : "Ativar", className: `rounded-full px-2 py-0.5 text-xs font-medium ${channel.enabled ? "bg-emerald-100 text-emerald-800" : "bg-muted text-muted-foreground"}`, children: channel.enabled ? "Ativo" : "Inativo" }),
|
|
6381
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => setEditing(true), children: "Editar" }),
|
|
6382
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onRemove, children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4 text-red-500" }) })
|
|
6383
|
-
] }) })
|
|
6384
|
-
] }) });
|
|
6385
|
-
}
|
|
6386
|
-
function AddChannelForm({ kind, onKind, onCancel, onAdd }) {
|
|
6387
|
-
const meta = KIND_META[kind];
|
|
6388
|
-
const [label, setLabel] = React13__default.useState(meta.label);
|
|
6389
|
-
const [url, setUrl] = React13__default.useState("");
|
|
6390
|
-
React13__default.useEffect(() => {
|
|
6391
|
-
setLabel(KIND_META[kind].label);
|
|
6392
|
-
}, [kind]);
|
|
6393
|
-
return /* @__PURE__ */ jsxs("div", { className: "mt-3 rounded-card border border-border bg-card p-4", children: [
|
|
6394
|
-
/* @__PURE__ */ jsx("div", { className: "mb-3 flex flex-wrap gap-2", children: ADDABLE_KINDS.map((k) => {
|
|
6395
|
-
const Icon = KIND_META[k].icon;
|
|
6396
|
-
return /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => onKind(k), className: `flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium ${k === kind ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"}`, children: [
|
|
6397
|
-
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
6398
|
-
" ",
|
|
6399
|
-
KIND_META[k].label
|
|
6400
|
-
] }, k);
|
|
6401
|
-
}) }),
|
|
6402
|
-
/* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-muted-foreground", children: meta.hint }),
|
|
6403
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
|
|
6404
|
-
/* @__PURE__ */ jsx(Input, { value: label, placeholder: "Nome do canal", onChange: (e) => setLabel(e.target.value), className: "sm:w-48" }),
|
|
6405
|
-
/* @__PURE__ */ jsx(Input, { value: url, placeholder: meta.placeholder, onChange: (e) => setUrl(e.target.value), className: "flex-1" })
|
|
6406
|
-
] }),
|
|
6407
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex gap-2", children: [
|
|
6408
|
-
/* @__PURE__ */ jsxs(Button, { size: "sm", onClick: () => onAdd(label, url), children: [
|
|
6409
|
-
/* @__PURE__ */ jsx(Plus, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
6410
|
-
" Adicionar"
|
|
6411
|
-
] }),
|
|
6412
|
-
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: onCancel, children: "Cancelar" })
|
|
6413
|
-
] })
|
|
6414
|
-
] });
|
|
6415
|
-
}
|
|
6416
6426
|
|
|
6417
6427
|
// src/views/media-links.ts
|
|
6418
6428
|
function recordRoute(reference) {
|
|
@@ -6429,7 +6439,7 @@ var TABS = [
|
|
|
6429
6439
|
{ value: "video", label: "V\xEDdeos" }
|
|
6430
6440
|
];
|
|
6431
6441
|
var LOW_RESOLUTION_PX = 800;
|
|
6432
|
-
function
|
|
6442
|
+
function resolve2(resolver) {
|
|
6433
6443
|
return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
|
|
6434
6444
|
}
|
|
6435
6445
|
function formatSize(bytes) {
|
|
@@ -6439,7 +6449,7 @@ function formatSize(bytes) {
|
|
|
6439
6449
|
return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
6440
6450
|
}
|
|
6441
6451
|
function ContentPage({ resolveProvider }) {
|
|
6442
|
-
const provider = React13__default.useMemo(() =>
|
|
6452
|
+
const provider = React13__default.useMemo(() => resolve2(resolveProvider), [resolveProvider]);
|
|
6443
6453
|
const supported = typeof provider.listMediaFiles === "function";
|
|
6444
6454
|
const [files, setFiles] = React13__default.useState(null);
|
|
6445
6455
|
const [tab, setTab] = React13__default.useState("all");
|
|
@@ -7196,7 +7206,7 @@ var blingConnectorDef = {
|
|
|
7196
7206
|
// where the question is actually asked. It is the only thing in that panel
|
|
7197
7207
|
// that was neither status, nor history, nor an act.
|
|
7198
7208
|
disconnectNote: "Desconectar congela o espelho: nenhum produto \xE9 apagado e o estoque para de ser atualizado.",
|
|
7199
|
-
// No panel of its own. The history is `
|
|
7209
|
+
// No panel of its own. The history is `sync_runs` read through the shared
|
|
7200
7210
|
// store, and the shared surface renders it — including the "first load brings
|
|
7201
7211
|
// a sample" sentence, which it derives from `firstLoad` above rather than
|
|
7202
7212
|
// repeating the number in prose.
|
|
@@ -7767,7 +7777,7 @@ var ORDERS = "plg_shop_rep_orders";
|
|
|
7767
7777
|
var ITEMS = "plg_shop_rep_order_items";
|
|
7768
7778
|
var CUSTOMERS = "plg_shop_rep_customers";
|
|
7769
7779
|
var PRODUCTS = "plg_shop_rep_products";
|
|
7770
|
-
var CANONICAL = "public.
|
|
7780
|
+
var CANONICAL = "public.v_shop_orders";
|
|
7771
7781
|
var CAT = {
|
|
7772
7782
|
sales: tl("Sales", "Vendas"),
|
|
7773
7783
|
orders: tl("Orders", "Pedidos"),
|
|
@@ -8590,7 +8600,7 @@ function buildShopAnalyticsCards() {
|
|
|
8590
8600
|
}
|
|
8591
8601
|
|
|
8592
8602
|
// src/index.ts
|
|
8593
|
-
var ShopPage = React13__default.lazy(() => import('./ShopPage-
|
|
8603
|
+
var ShopPage = React13__default.lazy(() => import('./ShopPage-5IERN4E5.js').then((m) => ({ default: m.ShopPage })));
|
|
8594
8604
|
function createShopPlugin(options) {
|
|
8595
8605
|
const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
|
|
8596
8606
|
setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
|
|
@@ -8805,10 +8815,9 @@ function createShopPlugin(options) {
|
|
|
8805
8815
|
});
|
|
8806
8816
|
const ContentPageWrapped = () => React13__default.createElement(ContentPage, { resolveProvider: options?.provider });
|
|
8807
8817
|
ContentPageWrapped.displayName = "ContentPage";
|
|
8808
|
-
const ChannelsPageWrapped = () => React13__default.createElement(ChannelsPage, { resolveProvider: options?.provider });
|
|
8809
|
-
ChannelsPageWrapped.displayName = "ChannelsPage";
|
|
8810
8818
|
const navSection = options?.navSection ?? "main";
|
|
8811
8819
|
const navPosition = options?.navPosition ?? 6;
|
|
8820
|
+
const siteRoute = options?.siteRoute ?? "/site";
|
|
8812
8821
|
const NAV = [
|
|
8813
8822
|
// Pedidos ▸ Pedidos · Clientes
|
|
8814
8823
|
{
|
|
@@ -8873,7 +8882,6 @@ function createShopPlugin(options) {
|
|
|
8873
8882
|
}
|
|
8874
8883
|
];
|
|
8875
8884
|
const HIDDEN_ROUTES = [
|
|
8876
|
-
{ path: "/store", component: ChannelsPageWrapped, title: label("store", "Canais de venda") },
|
|
8877
8885
|
// Reached from the Clientes tab strip, deliberately not from the menu:
|
|
8878
8886
|
// three sidebar entries for one destination is the duplication the tab
|
|
8879
8887
|
// strip exists to avoid. Nesting under /orders/customers also keeps
|
|
@@ -8916,7 +8924,8 @@ function createShopPlugin(options) {
|
|
|
8916
8924
|
// rota vem do mesmo lugar que a tela usa.
|
|
8917
8925
|
{ path: "/shop/shipping", component: redirectTo(shippingRegistryPath(SHIPPING_ZONES_REGISTRY)) },
|
|
8918
8926
|
{ path: "/store/shipping", component: redirectTo(shippingRegistryPath(SHIPPING_ZONES_REGISTRY)) },
|
|
8919
|
-
{ path: "/
|
|
8927
|
+
{ path: "/store", component: redirectTo(siteRoute) },
|
|
8928
|
+
{ path: "/shop/channels", component: redirectTo(siteRoute) },
|
|
8920
8929
|
{ path: "/customers", component: redirectTo("/orders/customers") },
|
|
8921
8930
|
// A mensageria da loja virou parte de Automações — mesmos eventos, agora ao
|
|
8922
8931
|
// lado dos do CRM e da agenda, e com WhatsApp além do e-mail.
|
|
@@ -8930,7 +8939,7 @@ function createShopPlugin(options) {
|
|
|
8930
8939
|
// O que a loja pede de um tenant novo, na ordem em que faz diferença.
|
|
8931
8940
|
// Vira linha de tarefa, aparece no checklist da trilha e é o roteiro que o
|
|
8932
8941
|
// assistente segue — ver ./lib/onboarding.ts.
|
|
8933
|
-
onboarding: { program: buildShopOnboarding() },
|
|
8942
|
+
onboarding: { program: buildShopOnboarding(siteRoute) },
|
|
8934
8943
|
name: options?.navLabel ?? "Shop",
|
|
8935
8944
|
icon: "ShoppingBag",
|
|
8936
8945
|
version: "1.0.0",
|