@fayz-ai/plugin-shop 0.8.0 → 0.8.1

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