@fayz-ai/plugin-shop 0.8.4 → 0.9.0-next.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.
@@ -1,692 +0,0 @@
1
- 'use strict';
2
-
3
- var react = require('react');
4
- var ui = require('@fayz-ai/ui');
5
- var shop = require('@fayz-ai/shop');
6
- var saas = require('@fayz-ai/saas');
7
- var core = require('@fayz-ai/core');
8
- var jsxRuntime = require('react/jsx-runtime');
9
-
10
- // src/ShopPage.tsx
11
-
12
- // src/views/customerLookup.ts
13
- function shopCustomerLookup(provider) {
14
- return {
15
- async getById(id) {
16
- const c = await provider().getCustomer(id);
17
- if (!c) return null;
18
- const name = [c.firstName, c.lastName].filter(Boolean).join(" ");
19
- return {
20
- id: c.id,
21
- label: name || c.email || "Cliente",
22
- subtitle: c.email ?? void 0,
23
- group: "shop_customer",
24
- // PersonLink reads contact fields off `data`, so the keys must match the
25
- // shape it expects (email/phone), not our camelCase type.
26
- data: {
27
- email: c.email ?? void 0,
28
- phone: c.phone ?? void 0,
29
- kind: "shop_customer",
30
- orders_count: c.ordersCount,
31
- total_spent: c.totalSpent
32
- }
33
- };
34
- }
35
- };
36
- }
37
- var money = (v, currency2 = "BRL") => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currency2 }).format(Number(v) || 0);
38
- var dateTime = (iso) => new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" }).format(new Date(iso));
39
- var dateOnly = (iso) => new Date(iso).toLocaleDateString("pt-BR");
40
- var PAYMENT_LABEL = {
41
- pix: "Pix",
42
- credit_card: "Cart\xE3o de cr\xE9dito",
43
- debit_card: "Cart\xE3o de d\xE9bito",
44
- boleto: "Boleto",
45
- cash: "Dinheiro",
46
- other: "Outro"
47
- };
48
- var LABEL = {
49
- paid: "Pago",
50
- pending: "Aguardando pagamento",
51
- refunded: "Estornado",
52
- partially_refunded: "Estorno parcial",
53
- voided: "Cancelado",
54
- fulfilled: "Enviado",
55
- unfulfilled: "N\xE3o enviado",
56
- partially_fulfilled: "Envio parcial",
57
- open: "Aberto",
58
- cancelled: "Cancelado",
59
- archived: "Arquivado"
60
- };
61
- var RECEIVABLE_LABEL = {
62
- open: "Em aberto",
63
- partial: "Parcialmente recebido",
64
- paid: "Recebido",
65
- overdue: "Vencido",
66
- cancelled: "Cancelado"
67
- };
68
- var SHIPMENT_LABEL = {
69
- pending: "Aguardando coleta",
70
- in_transit: "Em tr\xE2nsito",
71
- delivered: "Entregue",
72
- failed: "Falhou",
73
- returned: "Devolvido",
74
- cancelled: "Cancelado"
75
- };
76
- function toneOf(value) {
77
- switch (value) {
78
- case "paid":
79
- case "fulfilled":
80
- case "delivered":
81
- return "success";
82
- case "pending":
83
- case "partial":
84
- case "partially_fulfilled":
85
- case "open":
86
- case "in_transit":
87
- return "warning";
88
- case "refunded":
89
- case "partially_refunded":
90
- case "voided":
91
- case "cancelled":
92
- case "overdue":
93
- case "failed":
94
- case "returned":
95
- return "destructive";
96
- default:
97
- return "muted";
98
- }
99
- }
100
- function InlineBadge({ value }) {
101
- const tone = toneOf(value);
102
- const cls = {
103
- success: "bg-emerald-100 text-emerald-800",
104
- warning: "bg-amber-100 text-amber-800",
105
- destructive: "bg-red-100 text-red-700",
106
- info: "bg-sky-100 text-sky-800",
107
- muted: "bg-muted text-muted-foreground"
108
- };
109
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls[tone]}`, children: LABEL[value] ?? value });
110
- }
111
- function deliveryFromNotes(notes) {
112
- if (!notes) return null;
113
- const match = notes.match(/entrega:\s*(.+)/i);
114
- return (match?.[1] ?? "").trim() || null;
115
- }
116
- var shopOrderCache = /* @__PURE__ */ new Map();
117
- var shopShipmentsCache = /* @__PURE__ */ new Map();
118
- var shopReceivableCache = /* @__PURE__ */ new Map();
119
- function OrderDetailView({ orderId, provider, onBack }) {
120
- const [order, setOrder] = react.useState(() => shopOrderCache.get(orderId) ?? null);
121
- const [shipments, setShipments] = react.useState(() => shopShipmentsCache.get(orderId) ?? []);
122
- const [loading, setLoading] = react.useState(() => !shopOrderCache.has(orderId));
123
- const [error, setError] = react.useState(null);
124
- const [confirming, setConfirming] = react.useState(false);
125
- const [confirmError, setConfirmError] = react.useState(null);
126
- const [receivable, setReceivable] = react.useState(() => shopReceivableCache.get(orderId) ?? null);
127
- react.useEffect(() => {
128
- let cancelled = false;
129
- const cached = shopOrderCache.get(orderId);
130
- if (cached) {
131
- setOrder(cached);
132
- setLoading(false);
133
- return () => {
134
- cancelled = true;
135
- };
136
- }
137
- setLoading(true);
138
- setError(null);
139
- provider().getOrder(orderId).then((o) => {
140
- if (!cancelled) {
141
- if (o) shopOrderCache.set(orderId, o);
142
- setOrder(o);
143
- }
144
- }).catch((err) => {
145
- if (!cancelled) setError(err instanceof Error ? err.message : String(err));
146
- }).finally(() => {
147
- if (!cancelled) setLoading(false);
148
- });
149
- return () => {
150
- cancelled = true;
151
- };
152
- }, [orderId, provider]);
153
- react.useEffect(() => {
154
- let cancelled = false;
155
- const db = core.getSupabaseClientOptional();
156
- if (!db) return;
157
- if (shopShipmentsCache.has(orderId)) return () => {
158
- cancelled = true;
159
- };
160
- db.from("fulfillments").select("*").eq("order_id", orderId).order("created_at", { ascending: false }).then(({ data: data2 }) => {
161
- if (!cancelled) {
162
- shopShipmentsCache.set(orderId, data2 ?? []);
163
- setShipments(data2 ?? []);
164
- }
165
- });
166
- return () => {
167
- cancelled = true;
168
- };
169
- }, [orderId]);
170
- react.useEffect(() => {
171
- let cancelled = false;
172
- const db = core.getSupabaseClientOptional();
173
- if (!db) return;
174
- if (shopReceivableCache.has(orderId)) {
175
- setReceivable(shopReceivableCache.get(orderId) ?? null);
176
- return () => {
177
- cancelled = true;
178
- };
179
- }
180
- void (async () => {
181
- const [balance, bills] = await Promise.all([
182
- db.from("v_invoice_balances").select("*").eq("invoice_id", orderId).maybeSingle(),
183
- db.from("plg_financial_movements").select("*").eq("invoice_id", orderId).eq("movement_kind", "bill").order("installment_number", { ascending: true })
184
- ]);
185
- if (cancelled || !balance?.data) return;
186
- const rec = { balance: balance.data, bills: bills?.data ?? [] };
187
- shopReceivableCache.set(orderId, rec);
188
- setReceivable(rec);
189
- })();
190
- return () => {
191
- cancelled = true;
192
- };
193
- }, [orderId, confirming]);
194
- if (loading) return /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailSkeleton, {});
195
- if (error) {
196
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800", children: [
197
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: "N\xE3o foi poss\xEDvel carregar o pedido." }),
198
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-red-700", children: error })
199
- ] });
200
- }
201
- if (!order) return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-12 text-center text-sm text-muted-foreground", children: "Pedido n\xE3o encontrado." });
202
- const cur = order.currency;
203
- const delivery = deliveryFromNotes(order.notes);
204
- const addr = order.shippingAddress;
205
- async function registerReceipt() {
206
- if (!order) return;
207
- shopReceivableCache.delete(order.id);
208
- setConfirming(true);
209
- setConfirmError(null);
210
- try {
211
- const fresh = await provider().confirmPayment?.(order.id) ?? await provider().getOrder(order.id);
212
- if (fresh) {
213
- shopOrderCache.set(order.id, fresh);
214
- setOrder(fresh);
215
- }
216
- } catch (err) {
217
- setConfirmError(err instanceof Error ? err.message : String(err));
218
- } finally {
219
- setConfirming(false);
220
- }
221
- }
222
- const installments = receivable && receivable.bills.length > 1 ? receivable.bills.map((b) => ({
223
- number: b.installment_number ?? 0,
224
- amount: `${money(Number(b.paid_amount ?? 0), cur)} / ${money(Number(b.amount), cur)}`,
225
- status: RECEIVABLE_LABEL[b.status] ?? b.status,
226
- statusTone: toneOf(b.status),
227
- dueDate: `vence ${dateOnly(b.due_date)}`
228
- })) : [];
229
- const data = {
230
- title: `Pedido #${order.orderNumber}`,
231
- subtitle: dateTime(order.createdAt),
232
- badges: [
233
- { label: LABEL[order.financialStatus] ?? order.financialStatus, tone: toneOf(order.financialStatus) },
234
- { label: LABEL[order.fulfillmentStatus] ?? order.fulfillmentStatus, tone: toneOf(order.fulfillmentStatus) }
235
- ],
236
- customer: { name: order.customerName, email: order.customerEmail },
237
- customerNode: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 text-sm", children: [
238
- order.customerId ? /* @__PURE__ */ jsxRuntime.jsx(
239
- saas.PersonLink,
240
- {
241
- personId: order.customerId,
242
- name: order.customerName ?? order.customerEmail ?? "Cliente",
243
- lookup: shopCustomerLookup(provider),
244
- profileHref: `#/customers/${order.customerId}`,
245
- className: "font-medium"
246
- }
247
- ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: order.customerName ?? "\u2014" }),
248
- order.customerEmail && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: order.customerEmail }),
249
- !order.customerId && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Compra sem cadastro \u2014 n\xE3o h\xE1 ficha de cliente para abrir." })
250
- ] }),
251
- items: order.items.map((it) => ({
252
- id: it.id,
253
- name: it.name,
254
- description: it.sku,
255
- imageUrl: it.imageUrl,
256
- quantity: it.quantity,
257
- unitPrice: money(it.unitPrice, cur),
258
- total: money(it.total, cur)
259
- })),
260
- totals: [
261
- { label: "Subtotal", value: money(order.subtotal, cur) },
262
- ...order.discountTotal > 0 ? [{ label: `Desconto${order.discountCode ? ` (${order.discountCode})` : ""}`, value: `\u2212 ${money(order.discountTotal, cur)}`, muted: true }] : [],
263
- { label: "Entrega", value: money(order.shippingTotal, cur) },
264
- { label: "Impostos", value: money(order.taxTotal, cur) },
265
- { label: "Total", value: money(order.total, cur), strong: true }
266
- ],
267
- paymentMethod: order.paymentMethodKind ? PAYMENT_LABEL[order.paymentMethodKind] ?? order.paymentMethodKind : void 0,
268
- paidAt: order.paidAt ? dateTime(order.paidAt) : void 0,
269
- receivable: receivable ? {
270
- referenceNumber: receivable.balance.reference_number ?? void 0,
271
- statusBadge: { label: RECEIVABLE_LABEL[receivable.balance.status] ?? receivable.balance.status, tone: toneOf(receivable.balance.status) },
272
- rows: [
273
- { label: "Faturado", value: money(receivable.balance.amount, cur) },
274
- { label: "Recebido", value: money(receivable.balance.paid, cur) },
275
- { label: "Saldo", value: money(receivable.balance.balance, cur), strong: true }
276
- ],
277
- installments,
278
- actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
279
- receivable.balance.balance > 0 && receivable.balance.status !== "cancelled" && /* @__PURE__ */ jsxRuntime.jsx(
280
- "button",
281
- {
282
- type: "button",
283
- onClick: registerReceipt,
284
- disabled: confirming,
285
- "data-testid": "order-register-receipt",
286
- className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-60",
287
- children: confirming ? "Registrando\u2026" : "Registrar recebimento"
288
- }
289
- ),
290
- /* @__PURE__ */ jsxRuntime.jsx(
291
- "button",
292
- {
293
- type: "button",
294
- onClick: () => saas.adminNavigateTo("/financial"),
295
- className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
296
- children: "Abrir no Financeiro"
297
- }
298
- ),
299
- confirmError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 w-full rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-800", children: confirmError })
300
- ] }),
301
- emptyLabel: "Sem conta a receber para este pedido."
302
- } : { rows: [], installments: [], emptyLabel: "Sem conta a receber para este pedido." },
303
- extraSections: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
304
- /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailBlock, { title: "Entrega", hint: "O que a log\xEDstica precisa", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 text-sm", children: [
305
- addr ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
306
- /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
307
- [addr.street, addr.number].filter(Boolean).join(", "),
308
- addr.complement ? ` \u2014 ${addr.complement}` : ""
309
- ] }),
310
- addr.district && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: addr.district }),
311
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground", children: [
312
- [addr.city, addr.state].filter((v) => v && v !== "\u2014").join("/"),
313
- addr.postal_code ? ` \xB7 ${addr.postal_code}` : ""
314
- ] }),
315
- (addr.recipient || addr.phone) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: [addr.recipient, addr.phone].filter(Boolean).join(" \xB7 ") })
316
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: delivery ? "" : "text-muted-foreground", children: delivery ?? "Nenhum endere\xE7o registrado neste pedido." }),
317
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t pt-2", children: [
318
- /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailRow, { label: "Status", value: /* @__PURE__ */ jsxRuntime.jsx(InlineBadge, { value: order.fulfillmentStatus }) }),
319
- shipments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "Nenhum envio registrado." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 space-y-2", children: shipments.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border px-2 py-1.5", children: [
320
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
321
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: [s.carrier, s.service].filter(Boolean).join(" \xB7 ") || "Envio" }),
322
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: SHIPMENT_LABEL[s.status] ?? s.status })
323
- ] }),
324
- s.tracking_code && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 font-mono text-xs", children: s.tracking_url ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: s.tracking_url, target: "_blank", rel: "noreferrer", className: "text-primary hover:underline", children: s.tracking_code }) : s.tracking_code }),
325
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 space-y-0.5 text-[11px] text-muted-foreground", children: [
326
- s.shipped_at && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
327
- "Despachado em ",
328
- dateTime(s.shipped_at)
329
- ] }),
330
- s.estimated_delivery_at && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
331
- "Previs\xE3o: ",
332
- dateTime(s.estimated_delivery_at)
333
- ] }),
334
- s.delivered_at && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
335
- "Entregue em ",
336
- dateTime(s.delivered_at)
337
- ] })
338
- ] })
339
- ] }, s.id)) })
340
- ] })
341
- ] }) }),
342
- /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailBlock, { title: "Hist\xF3rico", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 text-sm", children: [
343
- /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailRow, { label: "Criado", value: dateTime(order.createdAt) }),
344
- /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailRow, { label: "Atualizado", value: dateTime(order.updatedAt) }),
345
- /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailRow, { label: "Situa\xE7\xE3o", value: /* @__PURE__ */ jsxRuntime.jsx(InlineBadge, { value: order.status }) })
346
- ] }) }),
347
- order.notes && /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailBlock, { title: "Observa\xE7\xF5es", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap text-sm text-muted-foreground", children: order.notes }) })
348
- ] }),
349
- onBack,
350
- backLabel: "Pedidos"
351
- };
352
- return /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailView, { ...data });
353
- }
354
- function currency(value, code = "BRL", locale = "pt-BR") {
355
- return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(value);
356
- }
357
- function badge(label, color) {
358
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${color}`, children: label });
359
- }
360
- var statusColors = {
361
- active: "bg-emerald-100 text-emerald-800",
362
- draft: "bg-gray-100 text-gray-700",
363
- archived: "bg-orange-100 text-orange-700",
364
- open: "bg-blue-100 text-blue-800",
365
- cancelled: "bg-red-100 text-red-700",
366
- paid: "bg-emerald-100 text-emerald-800",
367
- pending: "bg-yellow-100 text-yellow-800",
368
- fulfilled: "bg-emerald-100 text-emerald-800",
369
- unfulfilled: "bg-gray-100 text-gray-700",
370
- "partially_fulfilled": "bg-blue-100 text-blue-800"
371
- };
372
- function resolveShopProvider(provider) {
373
- return typeof provider === "function" ? provider() : provider ?? shop.getShopProvider();
374
- }
375
- function useShopList(load, deps) {
376
- const [items, setItems] = react.useState([]);
377
- const [loading, setLoading] = react.useState(true);
378
- const [error, setError] = react.useState(null);
379
- react.useEffect(() => {
380
- let cancelled = false;
381
- setLoading(true);
382
- setError(null);
383
- load().then((data) => {
384
- if (!cancelled) setItems(data);
385
- }).catch((err) => {
386
- if (!cancelled) setError(err instanceof Error ? err.message : String(err));
387
- }).finally(() => {
388
- if (!cancelled) setLoading(false);
389
- });
390
- return () => {
391
- cancelled = true;
392
- };
393
- }, deps);
394
- return { items, loading, error };
395
- }
396
- function ErrorState({ message }) {
397
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800", children: [
398
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: "N\xE3o foi poss\xEDvel carregar estes dados." }),
399
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-red-700", children: message })
400
- ] });
401
- }
402
- function ProductsTab({ provider }) {
403
- const [products, setProducts] = react.useState([]);
404
- const [loading, setLoading] = react.useState(true);
405
- const [search, setSearch] = react.useState("");
406
- const [error, setError] = react.useState(null);
407
- const load = react.useCallback(async (opts) => {
408
- setLoading(true);
409
- setError(null);
410
- try {
411
- const data = await resolveShopProvider(provider).listProducts(opts);
412
- setProducts(data);
413
- } catch (err) {
414
- setError(err instanceof Error ? err.message : String(err));
415
- } finally {
416
- setLoading(false);
417
- }
418
- }, [provider]);
419
- react.useEffect(() => {
420
- load();
421
- }, [load]);
422
- const handleSearch = (e) => {
423
- const q = e.target.value;
424
- setSearch(q);
425
- load(q ? { search: q } : void 0);
426
- };
427
- const columns = react.useMemo(() => [
428
- { accessorKey: "name", header: "Produto", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: getValue() }) },
429
- { accessorKey: "sku", header: "SKU", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: getValue() ?? "\u2014" }) },
430
- {
431
- accessorKey: "price",
432
- header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: "Pre\xE7o" }),
433
- cell: ({ getValue, row }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: currency(getValue(), row.original.currency) })
434
- },
435
- {
436
- accessorKey: "inventoryCount",
437
- header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: "Estoque" }),
438
- cell: ({ getValue }) => {
439
- const count = getValue();
440
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: count === 0 ? "text-red-600 font-medium" : "", children: count }) });
441
- }
442
- },
443
- {
444
- accessorKey: "status",
445
- header: "Status",
446
- cell: ({ getValue }) => {
447
- const status = getValue();
448
- return badge(status, statusColors[status] ?? "bg-gray-100 text-gray-700");
449
- }
450
- }
451
- ], []);
452
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
453
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-4", children: [
454
- /* @__PURE__ */ jsxRuntime.jsx(
455
- "input",
456
- {
457
- type: "search",
458
- value: search,
459
- onChange: handleSearch,
460
- placeholder: "Buscar produtos...",
461
- className: "border rounded-lg px-3 py-2 text-sm w-64 focus:outline-none focus:ring-2 focus:ring-primary/50"
462
- }
463
- ),
464
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground", children: [
465
- products.length,
466
- " produtos"
467
- ] })
468
- ] }),
469
- error ? /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error }) : /* @__PURE__ */ jsxRuntime.jsx(
470
- ui.DataTable,
471
- {
472
- loading,
473
- columns,
474
- data: products,
475
- variant: "card",
476
- emptyMessage: "Nenhum produto encontrado."
477
- }
478
- )
479
- ] });
480
- }
481
- function OrdersTab({ provider }) {
482
- const { items: orders, loading, error } = useShopList(
483
- () => resolveShopProvider(provider).listOrders({ limit: 50 }),
484
- [provider]
485
- );
486
- const columns = react.useMemo(() => [
487
- { accessorKey: "orderNumber", header: "Pedido", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
488
- "#",
489
- getValue()
490
- ] }) },
491
- {
492
- id: "customer",
493
- accessorFn: (o) => o.customerName ?? o.customerEmail ?? "\u2014",
494
- header: "Cliente",
495
- // Links to the customer record. stopPropagation because the row itself
496
- // opens the order — without it the click would do both.
497
- cell: ({ getValue, row }) => {
498
- const label = getValue();
499
- const id = row.original.customerId;
500
- if (!id) return /* @__PURE__ */ jsxRuntime.jsx("span", { children: label });
501
- return /* @__PURE__ */ jsxRuntime.jsx(
502
- saas.PersonLink,
503
- {
504
- personId: id,
505
- name: label,
506
- lookup: shopCustomerLookup(() => resolveShopProvider(provider)),
507
- profileHref: `#/customers/${id}`
508
- }
509
- );
510
- }
511
- },
512
- {
513
- accessorKey: "total",
514
- header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: "Total" }),
515
- cell: ({ getValue, row }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right font-medium", children: currency(getValue(), row.original.currency) })
516
- },
517
- {
518
- accessorKey: "financialStatus",
519
- header: "Pagamento",
520
- cell: ({ getValue }) => {
521
- const status = getValue();
522
- return badge(status, statusColors[status] ?? "bg-gray-100 text-gray-700");
523
- }
524
- },
525
- {
526
- accessorKey: "fulfillmentStatus",
527
- header: "Entrega",
528
- cell: ({ getValue }) => {
529
- const status = getValue();
530
- return badge(status, statusColors[status] ?? "bg-gray-100 text-gray-700");
531
- }
532
- },
533
- {
534
- accessorKey: "createdAt",
535
- header: "Data",
536
- cell: ({ getValue }) => {
537
- const d = new Date(getValue());
538
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "whitespace-nowrap text-muted-foreground", children: [
539
- d.toLocaleDateString("pt-BR"),
540
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1.5 text-xs opacity-70", children: d.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" }) })
541
- ] });
542
- }
543
- }
544
- ], []);
545
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: error ? /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error }) : /* @__PURE__ */ jsxRuntime.jsx(
546
- ui.DataTable,
547
- {
548
- loading,
549
- columns,
550
- data: orders,
551
- variant: "card",
552
- emptyMessage: "Nenhum pedido ainda.",
553
- onRowClick: (row) => saas.adminNavigateTo(`/shop/orders/${row.id}`)
554
- }
555
- ) });
556
- }
557
- function CustomersTab({ provider }) {
558
- const { items: customers, loading, error } = useShopList(
559
- () => resolveShopProvider(provider).listCustomers({ limit: 50 }),
560
- [provider]
561
- );
562
- const columns = react.useMemo(() => [
563
- {
564
- id: "name",
565
- accessorFn: (c) => `${c.firstName} ${c.lastName}`,
566
- header: "Nome",
567
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
568
- row.original.firstName,
569
- " ",
570
- row.original.lastName
571
- ] })
572
- },
573
- { accessorKey: "email", header: "Email", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: getValue() ?? "\u2014" }) },
574
- { accessorKey: "phone", header: "Telefone", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: getValue() ?? "\u2014" }) },
575
- {
576
- accessorKey: "ordersCount",
577
- header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: "Pedidos" }),
578
- cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: getValue() })
579
- },
580
- {
581
- accessorKey: "totalSpent",
582
- header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: "Total Gasto" }),
583
- cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: currency(getValue()) })
584
- }
585
- ], []);
586
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: error ? /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error }) : /* @__PURE__ */ jsxRuntime.jsx(
587
- ui.DataTable,
588
- {
589
- loading,
590
- columns,
591
- data: customers,
592
- variant: "card",
593
- emptyMessage: "Nenhum cliente ainda."
594
- }
595
- ) });
596
- }
597
- function DiscountsTab({ provider }) {
598
- const { items: discounts, loading, error } = useShopList(
599
- () => resolveShopProvider(provider).listDiscounts({ limit: 50 }),
600
- [provider]
601
- );
602
- const columns = react.useMemo(() => [
603
- { accessorKey: "title", header: "T\xEDtulo", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: getValue() }) },
604
- { accessorKey: "code", header: "C\xF3digo", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-sm", children: getValue() ?? "\u2014" }) },
605
- { accessorKey: "type", header: "Tipo", cell: ({ getValue }) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: getValue() }) },
606
- {
607
- accessorKey: "value",
608
- header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: "Valor" }),
609
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: row.original.type === "percentage" ? `${row.original.value}%` : currency(row.original.value) })
610
- },
611
- {
612
- id: "usos",
613
- accessorKey: "timesUsed",
614
- header: () => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-right", children: "Usos" }),
615
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right", children: [
616
- row.original.timesUsed,
617
- row.original.usageLimit ? ` / ${row.original.usageLimit}` : ""
618
- ] })
619
- },
620
- {
621
- accessorKey: "status",
622
- header: "Status",
623
- cell: ({ getValue }) => {
624
- const status = getValue();
625
- return badge(status, statusColors[status] ?? "bg-gray-100 text-gray-700");
626
- }
627
- }
628
- ], []);
629
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: error ? /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { message: error }) : /* @__PURE__ */ jsxRuntime.jsx(
630
- ui.DataTable,
631
- {
632
- loading,
633
- columns,
634
- data: discounts,
635
- variant: "card",
636
- emptyMessage: "Nenhum desconto cadastrado."
637
- }
638
- ) });
639
- }
640
- var TABS = [
641
- { id: "products", label: "Produtos" },
642
- { id: "orders", label: "Pedidos" },
643
- { id: "customers", label: "Clientes" },
644
- { id: "discounts", label: "Descontos" }
645
- ];
646
- var TAB_COPY = {
647
- products: { title: "Produtos", subtitle: "Cat\xE1logo da loja: pre\xE7o, estoque e status." },
648
- orders: { title: "Pedidos", subtitle: "Pedidos recebidos, pagamento e entrega." },
649
- customers: { title: "Clientes", subtitle: "Quem comprou, quanto e com que frequ\xEAncia." },
650
- discounts: { title: "Descontos", subtitle: "Cupons, regras de uso e desempenho." }
651
- };
652
- var ShopPage = ({ provider, section }) => {
653
- const [tab, setTab] = react.useState(section ?? "products");
654
- const active = section ?? tab;
655
- const path = saas.useAdminPath();
656
- const orderId = section === "orders" ? path.match(/^\/shop\/orders\/([^/]+)$/)?.[1] : void 0;
657
- if (orderId) {
658
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 max-w-7xl mx-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
659
- OrderDetailView,
660
- {
661
- orderId,
662
- provider: () => resolveShopProvider(provider),
663
- onBack: () => saas.adminNavigateTo("/shop/orders")
664
- }
665
- ) });
666
- }
667
- const copy = section ? TAB_COPY[section] : { title: "Loja", subtitle: "Gerencie produtos, pedidos, clientes e descontos." };
668
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 max-w-7xl mx-auto", children: [
669
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
670
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold", children: copy.title }),
671
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mt-1", children: copy.subtitle })
672
- ] }),
673
- !section && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b mb-6", children: /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "flex gap-1", children: TABS.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
674
- "button",
675
- {
676
- onClick: () => setTab(t.id),
677
- className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === t.id ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
678
- children: t.label
679
- },
680
- t.id
681
- )) }) }),
682
- active === "products" && /* @__PURE__ */ jsxRuntime.jsx(ProductsTab, { provider }),
683
- active === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersTab, { provider }),
684
- active === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersTab, { provider }),
685
- active === "discounts" && /* @__PURE__ */ jsxRuntime.jsx(DiscountsTab, { provider })
686
- ] });
687
- };
688
- ShopPage.displayName = "ShopPage";
689
-
690
- exports.ShopPage = ShopPage;
691
- //# sourceMappingURL=ShopPage-NK6FSPJ5.cjs.map
692
- //# sourceMappingURL=ShopPage-NK6FSPJ5.cjs.map