medusa-analytics 0.0.13 → 0.0.15
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/.medusa/server/src/admin/index.js +1057 -313
- package/.medusa/server/src/admin/index.mjs +1061 -317
- package/.medusa/server/src/api/admin/analytics/products/shared.js +624 -0
- package/.medusa/server/src/api/admin/analytics/products-filters/route.js +21 -0
- package/.medusa/server/src/api/admin/analytics/products-filters/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-over-time/route.js +24 -0
- package/.medusa/server/src/api/admin/analytics/products-over-time/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-performance/route.js +51 -0
- package/.medusa/server/src/api/admin/analytics/products-performance/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-summary/route.js +36 -0
- package/.medusa/server/src/api/admin/analytics/products-summary/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-top-sellers/route.js +48 -0
- package/.medusa/server/src/api/admin/analytics/products-top-sellers/types.js +3 -0
- package/README.md +45 -0
- package/package.json +1 -1
|
@@ -5,6 +5,74 @@ const adminSdk = require("@medusajs/admin-sdk");
|
|
|
5
5
|
const icons = require("@medusajs/icons");
|
|
6
6
|
const ui = require("@medusajs/ui");
|
|
7
7
|
const recharts = require("recharts");
|
|
8
|
+
function AnalyticsDashboardShell({ children }) {
|
|
9
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children });
|
|
10
|
+
}
|
|
11
|
+
function AnalyticsDashboardHeader({
|
|
12
|
+
title,
|
|
13
|
+
description,
|
|
14
|
+
actions
|
|
15
|
+
}) {
|
|
16
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "overflow-hidden rounded-2xl border border-ui-border-base bg-ui-bg-base p-0 shadow-sm", children: [
|
|
17
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 border-b border-ui-border-base px-5 py-4 xl:flex-row xl:items-start xl:justify-between", children: [
|
|
18
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
19
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "tracking-tight", children: title }),
|
|
20
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: description }) : null
|
|
21
|
+
] }),
|
|
22
|
+
actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2 rounded-xl border border-ui-border-base bg-ui-bg-subtle p-2", children: actions }) : null
|
|
23
|
+
] }),
|
|
24
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1 bg-ui-bg-subtle" })
|
|
25
|
+
] });
|
|
26
|
+
}
|
|
27
|
+
function AnalyticsStatCard({ label, value, helper }) {
|
|
28
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "h-full rounded-2xl border border-ui-border-base bg-ui-bg-base p-4 shadow-sm transition-shadow", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-[104px] flex-col justify-between gap-3", children: [
|
|
29
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: label }),
|
|
30
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "tracking-tight", children: value }),
|
|
31
|
+
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: helper }) : null
|
|
32
|
+
] }) });
|
|
33
|
+
}
|
|
34
|
+
function AnalyticsSection({
|
|
35
|
+
title,
|
|
36
|
+
description,
|
|
37
|
+
actions,
|
|
38
|
+
children,
|
|
39
|
+
className
|
|
40
|
+
}) {
|
|
41
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
42
|
+
ui.Container,
|
|
43
|
+
{
|
|
44
|
+
className: `overflow-hidden rounded-2xl border border-ui-border-base bg-ui-bg-base p-0 shadow-sm ${className ?? ""}`.trim(),
|
|
45
|
+
children: [
|
|
46
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 border-b border-ui-border-base px-5 py-4 lg:flex-row lg:items-start lg:justify-between", children: [
|
|
47
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
48
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "tracking-tight", children: title }),
|
|
49
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: description }) : null
|
|
50
|
+
] }),
|
|
51
|
+
actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2 rounded-xl border border-ui-border-base bg-ui-bg-subtle p-2", children: actions }) : null
|
|
52
|
+
] }),
|
|
53
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-4", children })
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
function AnalyticsChartSurface({ children, className }) {
|
|
59
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
60
|
+
"div",
|
|
61
|
+
{
|
|
62
|
+
className: `rounded-xl border border-ui-border-base bg-ui-bg-subtle p-3 shadow-inner ${className ?? ""}`.trim(),
|
|
63
|
+
children
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function AnalyticsTableSurface({ children, className }) {
|
|
68
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
className: `overflow-hidden rounded-xl border border-ui-border-base bg-ui-bg-base ${className ?? ""}`.trim(),
|
|
72
|
+
children
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
8
76
|
function OrdersOverTimeTooltip({
|
|
9
77
|
active,
|
|
10
78
|
payload,
|
|
@@ -48,7 +116,7 @@ const ORDER_CHART_STATUSES = [
|
|
|
48
116
|
{ key: "cancelled", label: "Cancelled", color: STATUS_COLORS.cancelled },
|
|
49
117
|
{ key: "delivered", label: "Delivered", color: STATUS_COLORS.delivered }
|
|
50
118
|
];
|
|
51
|
-
function formatCurrency(value) {
|
|
119
|
+
function formatCurrency$1(value) {
|
|
52
120
|
return new Intl.NumberFormat("en-IN", {
|
|
53
121
|
style: "currency",
|
|
54
122
|
currency: "INR",
|
|
@@ -56,7 +124,7 @@ function formatCurrency(value) {
|
|
|
56
124
|
maximumFractionDigits: 0
|
|
57
125
|
}).format(value);
|
|
58
126
|
}
|
|
59
|
-
const SUMMARY_PERIODS = [
|
|
127
|
+
const SUMMARY_PERIODS$1 = [
|
|
60
128
|
{ value: "all", label: "All time" },
|
|
61
129
|
{ value: "0", label: "Today's orders" },
|
|
62
130
|
{ value: "7", label: "Last 7 days" },
|
|
@@ -137,131 +205,167 @@ function OrdersDashboard() {
|
|
|
137
205
|
{ name: "Return orders", value: data.returnOrders, color: "#EF4444" },
|
|
138
206
|
{ name: "Exchange orders", value: data.exchangeOrders, color: "#F59E0B" }
|
|
139
207
|
].filter((d) => d.value > 0);
|
|
140
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
141
|
-
/* @__PURE__ */ jsxRuntime.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
208
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsDashboardShell, { children: [
|
|
209
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
210
|
+
AnalyticsDashboardHeader,
|
|
211
|
+
{
|
|
212
|
+
title: "Orders",
|
|
213
|
+
description: "Monitor revenue, operational health, and status distribution without leaving the analytics workspace.",
|
|
214
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
215
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
216
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "analytics-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
|
|
217
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
218
|
+
"select",
|
|
219
|
+
{
|
|
220
|
+
id: "analytics-period",
|
|
221
|
+
value: filter,
|
|
222
|
+
onChange: (e) => setFilter(e.target.value),
|
|
223
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
224
|
+
children: SUMMARY_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
] }),
|
|
228
|
+
filter !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
229
|
+
"button",
|
|
148
230
|
{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
children:
|
|
231
|
+
type: "button",
|
|
232
|
+
onClick: () => setFilter("all"),
|
|
233
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
234
|
+
"aria-label": "Show all orders (clear filter)",
|
|
235
|
+
children: "Clear filter"
|
|
154
236
|
}
|
|
155
237
|
)
|
|
156
|
-
] })
|
|
157
|
-
|
|
158
|
-
|
|
238
|
+
] })
|
|
239
|
+
}
|
|
240
|
+
),
|
|
241
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4", children: [
|
|
242
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
243
|
+
AnalyticsStatCard,
|
|
244
|
+
{
|
|
245
|
+
label: "Total orders",
|
|
246
|
+
value: data.totalOrders.toLocaleString()
|
|
247
|
+
}
|
|
248
|
+
),
|
|
249
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
250
|
+
AnalyticsStatCard,
|
|
251
|
+
{
|
|
252
|
+
label: "Total revenue",
|
|
253
|
+
value: formatCurrency$1(data.totalRevenue)
|
|
254
|
+
}
|
|
255
|
+
),
|
|
256
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
257
|
+
AnalyticsStatCard,
|
|
258
|
+
{
|
|
259
|
+
label: "Average order value",
|
|
260
|
+
value: formatCurrency$1(data.aov)
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
264
|
+
AnalyticsStatCard,
|
|
265
|
+
{
|
|
266
|
+
label: "Orders today",
|
|
267
|
+
value: data.ordersToday.toLocaleString()
|
|
268
|
+
}
|
|
269
|
+
),
|
|
270
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
271
|
+
AnalyticsStatCard,
|
|
272
|
+
{
|
|
273
|
+
label: "Pending orders",
|
|
274
|
+
value: data.pendingOrders.toLocaleString()
|
|
275
|
+
}
|
|
276
|
+
),
|
|
277
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
278
|
+
AnalyticsStatCard,
|
|
279
|
+
{
|
|
280
|
+
label: "Avg. fulfillment time",
|
|
281
|
+
value: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "—",
|
|
282
|
+
helper: "Measured in hours"
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
286
|
+
AnalyticsStatCard,
|
|
287
|
+
{
|
|
288
|
+
label: "Cancelled orders",
|
|
289
|
+
value: data.cancelledOrders.toLocaleString()
|
|
290
|
+
}
|
|
291
|
+
),
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
293
|
+
AnalyticsStatCard,
|
|
294
|
+
{
|
|
295
|
+
label: "Delivered orders",
|
|
296
|
+
value: data.deliveredOrders.toLocaleString()
|
|
297
|
+
}
|
|
298
|
+
),
|
|
299
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
300
|
+
AnalyticsStatCard,
|
|
301
|
+
{
|
|
302
|
+
label: "Exchange orders",
|
|
303
|
+
value: data.exchangeOrders.toLocaleString()
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
307
|
+
AnalyticsStatCard,
|
|
308
|
+
{
|
|
309
|
+
label: "Return orders",
|
|
310
|
+
value: data.returnOrders.toLocaleString()
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
] }),
|
|
314
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [
|
|
315
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsSection, { title: "Orders by status", children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[300px] items-center justify-center", children: pieData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: 280, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 8, right: 8, bottom: 8, left: 8 }, children: [
|
|
316
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
317
|
+
recharts.Pie,
|
|
159
318
|
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
319
|
+
data: pieData,
|
|
320
|
+
cx: "50%",
|
|
321
|
+
cy: "50%",
|
|
322
|
+
innerRadius: 52,
|
|
323
|
+
outerRadius: 78,
|
|
324
|
+
paddingAngle: 2,
|
|
325
|
+
dataKey: "value",
|
|
326
|
+
nameKey: "name",
|
|
327
|
+
isAnimationActive: true,
|
|
328
|
+
children: pieData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-${index}`))
|
|
165
329
|
}
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
/* @__PURE__ */ jsxRuntime.
|
|
175
|
-
|
|
176
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
/* @__PURE__ */ jsxRuntime.
|
|
199
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Cancelled orders" }),
|
|
200
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.cancelledOrders.toLocaleString() })
|
|
201
|
-
] }),
|
|
202
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
203
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Delivered orders" }),
|
|
204
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.deliveredOrders.toLocaleString() })
|
|
205
|
-
] }),
|
|
206
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
207
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Exchange orders" }),
|
|
208
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.exchangeOrders.toLocaleString() })
|
|
209
|
-
] }),
|
|
210
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
211
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Return orders" }),
|
|
212
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.returnOrders.toLocaleString() })
|
|
213
|
-
] })
|
|
330
|
+
),
|
|
331
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
332
|
+
recharts.Tooltip,
|
|
333
|
+
{
|
|
334
|
+
formatter: (value) => [value ?? 0, "Orders"]
|
|
335
|
+
}
|
|
336
|
+
),
|
|
337
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { verticalAlign: "bottom", height: 36 })
|
|
338
|
+
] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No orders in this period" }) }) }) }),
|
|
339
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsSection, { title: "Returns & exchanges", children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[300px] items-center justify-center", children: returnsExchangesData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: 280, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 8, right: 8, bottom: 8, left: 8 }, children: [
|
|
340
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
341
|
+
recharts.Pie,
|
|
342
|
+
{
|
|
343
|
+
data: returnsExchangesData,
|
|
344
|
+
cx: "50%",
|
|
345
|
+
cy: "50%",
|
|
346
|
+
innerRadius: 52,
|
|
347
|
+
outerRadius: 78,
|
|
348
|
+
paddingAngle: 2,
|
|
349
|
+
dataKey: "value",
|
|
350
|
+
nameKey: "name",
|
|
351
|
+
isAnimationActive: true,
|
|
352
|
+
children: returnsExchangesData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-re-${index}`))
|
|
353
|
+
}
|
|
354
|
+
),
|
|
355
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
356
|
+
recharts.Tooltip,
|
|
357
|
+
{
|
|
358
|
+
formatter: (value) => [value ?? 0, "Count"]
|
|
359
|
+
}
|
|
360
|
+
),
|
|
361
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { verticalAlign: "bottom", height: 36 })
|
|
362
|
+
] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No returns or exchanges in this period" }) }) }) })
|
|
214
363
|
] }),
|
|
215
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
recharts.Pie,
|
|
221
|
-
{
|
|
222
|
-
data: pieData,
|
|
223
|
-
cx: "50%",
|
|
224
|
-
cy: "50%",
|
|
225
|
-
innerRadius: 52,
|
|
226
|
-
outerRadius: 78,
|
|
227
|
-
paddingAngle: 2,
|
|
228
|
-
dataKey: "value",
|
|
229
|
-
nameKey: "name",
|
|
230
|
-
isAnimationActive: true,
|
|
231
|
-
children: pieData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-${index}`))
|
|
232
|
-
}
|
|
233
|
-
),
|
|
234
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (value) => [value ?? 0, "Orders"] }),
|
|
235
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { verticalAlign: "bottom", height: 36 })
|
|
236
|
-
] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No orders in this period" }) })
|
|
237
|
-
] }),
|
|
238
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", style: { minWidth: "280px" }, children: [
|
|
239
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-3", children: "Returns & exchanges" }),
|
|
240
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[280px] flex items-center justify-center", children: returnsExchangesData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: 280, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 8, right: 8, bottom: 8, left: 8 }, children: [
|
|
241
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
242
|
-
recharts.Pie,
|
|
243
|
-
{
|
|
244
|
-
data: returnsExchangesData,
|
|
245
|
-
cx: "50%",
|
|
246
|
-
cy: "50%",
|
|
247
|
-
innerRadius: 52,
|
|
248
|
-
outerRadius: 78,
|
|
249
|
-
paddingAngle: 2,
|
|
250
|
-
dataKey: "value",
|
|
251
|
-
nameKey: "name",
|
|
252
|
-
isAnimationActive: true,
|
|
253
|
-
children: returnsExchangesData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-re-${index}`))
|
|
254
|
-
}
|
|
255
|
-
),
|
|
256
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (value) => [value ?? 0, "Count"] }),
|
|
257
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { verticalAlign: "bottom", height: 36 })
|
|
258
|
-
] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No returns or exchanges in this period" }) })
|
|
259
|
-
] })
|
|
260
|
-
] }) }),
|
|
261
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
262
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
263
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Orders over time" }),
|
|
264
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
364
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
365
|
+
AnalyticsSection,
|
|
366
|
+
{
|
|
367
|
+
title: "Orders over time",
|
|
368
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
265
369
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "over-time-period", className: "text-ui-fg-muted text-sm", children: "Range" }),
|
|
266
370
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
267
371
|
"select",
|
|
@@ -273,72 +377,72 @@ function OrdersDashboard() {
|
|
|
273
377
|
children: OVER_TIME_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
274
378
|
}
|
|
275
379
|
)
|
|
276
|
-
] })
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
380
|
+
] }),
|
|
381
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
382
|
+
"div",
|
|
383
|
+
{
|
|
384
|
+
className: "flex items-center justify-center h-full",
|
|
385
|
+
role: "status",
|
|
386
|
+
"aria-label": "Loading orders over time",
|
|
387
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
|
|
388
|
+
}
|
|
389
|
+
) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: overTimeError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
390
|
+
recharts.LineChart,
|
|
391
|
+
{
|
|
392
|
+
data: dailyOrders,
|
|
393
|
+
margin: { top: 8, right: 8, left: 8, bottom: 8 },
|
|
394
|
+
children: [
|
|
395
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", className: "stroke-ui-border-base" }),
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
397
|
+
recharts.XAxis,
|
|
398
|
+
{
|
|
399
|
+
dataKey: "date",
|
|
400
|
+
tick: { fontSize: 12 },
|
|
401
|
+
tickFormatter: (v, index) => {
|
|
402
|
+
const row = dailyOrders[index];
|
|
403
|
+
if (row == null ? void 0 : row.label) return row.label;
|
|
404
|
+
const d = new Date(v);
|
|
405
|
+
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
406
|
+
}
|
|
303
407
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
408
|
+
),
|
|
409
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
410
|
+
recharts.YAxis,
|
|
411
|
+
{
|
|
412
|
+
tick: { fontSize: 12 },
|
|
413
|
+
allowDecimals: false,
|
|
414
|
+
domain: [0, "auto"],
|
|
415
|
+
ticks: (() => {
|
|
416
|
+
const max = Math.max(
|
|
417
|
+
1,
|
|
418
|
+
...dailyOrders.map((d) => d.orders_count ?? 0)
|
|
419
|
+
);
|
|
420
|
+
const step = max <= 10 ? 1 : Math.ceil(max / 6);
|
|
421
|
+
const arr = [];
|
|
422
|
+
for (let i = 0; i <= max; i += step) arr.push(i);
|
|
423
|
+
if (arr[arr.length - 1] !== max) arr.push(max);
|
|
424
|
+
return arr;
|
|
425
|
+
})(),
|
|
426
|
+
tickFormatter: (v) => String(Math.floor(Number(v) || 0))
|
|
427
|
+
}
|
|
428
|
+
),
|
|
429
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersOverTimeTooltip, {}) }),
|
|
430
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
431
|
+
recharts.Line,
|
|
432
|
+
{
|
|
433
|
+
type: "monotone",
|
|
434
|
+
dataKey: "orders_count",
|
|
435
|
+
name: "Orders",
|
|
436
|
+
stroke: "var(--medusa-color-ui-fg-interactive)",
|
|
437
|
+
strokeWidth: 2,
|
|
438
|
+
dot: { r: 3 }
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
) }) }) })
|
|
444
|
+
}
|
|
445
|
+
)
|
|
342
446
|
] });
|
|
343
447
|
}
|
|
344
448
|
function CustomersOverTimeTooltip({
|
|
@@ -386,7 +490,7 @@ const COUNT_DAY_PRESETS = [
|
|
|
386
490
|
{ value: "30", label: "Last 30 days" },
|
|
387
491
|
{ value: "90", label: "Last 90 days" }
|
|
388
492
|
];
|
|
389
|
-
const GRAPH_PERIODS = [
|
|
493
|
+
const GRAPH_PERIODS$1 = [
|
|
390
494
|
{ value: "one_week", label: "One week" },
|
|
391
495
|
{ value: "one_month", label: "One month" },
|
|
392
496
|
{ value: "one_year", label: "One year" }
|
|
@@ -454,64 +558,81 @@ function CustomersDashboard() {
|
|
|
454
558
|
if (error || !data) {
|
|
455
559
|
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: error ?? "Failed to load analytics" }) });
|
|
456
560
|
}
|
|
457
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
458
|
-
/* @__PURE__ */ jsxRuntime.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
561
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsDashboardShell, { children: [
|
|
562
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
563
|
+
AnalyticsDashboardHeader,
|
|
564
|
+
{
|
|
565
|
+
title: "Customers",
|
|
566
|
+
description: "Keep customer acquisition and account mix compact, readable, and aligned with the rest of analytics.",
|
|
567
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
568
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
569
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
570
|
+
ui.Label,
|
|
571
|
+
{
|
|
572
|
+
htmlFor: "customers-count-period",
|
|
573
|
+
className: "text-ui-fg-muted text-sm",
|
|
574
|
+
children: "Period"
|
|
575
|
+
}
|
|
576
|
+
),
|
|
577
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
578
|
+
"select",
|
|
579
|
+
{
|
|
580
|
+
id: "customers-count-period",
|
|
581
|
+
value: countDays,
|
|
582
|
+
onChange: (e) => setCountDays(e.target.value),
|
|
583
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
584
|
+
children: COUNT_DAY_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
585
|
+
}
|
|
586
|
+
)
|
|
587
|
+
] }),
|
|
588
|
+
countDays !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
589
|
+
"button",
|
|
472
590
|
{
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
children:
|
|
591
|
+
type: "button",
|
|
592
|
+
onClick: () => setCountDays("all"),
|
|
593
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
594
|
+
"aria-label": "Show all customers (clear filter)",
|
|
595
|
+
children: "Clear filter"
|
|
478
596
|
}
|
|
479
597
|
)
|
|
480
|
-
] })
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
/* @__PURE__ */ jsxRuntime.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
598
|
+
] })
|
|
599
|
+
}
|
|
600
|
+
),
|
|
601
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4", children: [
|
|
602
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
603
|
+
AnalyticsStatCard,
|
|
604
|
+
{
|
|
605
|
+
label: "Guest customers",
|
|
606
|
+
value: Math.floor(data.guestCount).toLocaleString()
|
|
607
|
+
}
|
|
608
|
+
),
|
|
609
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
610
|
+
AnalyticsStatCard,
|
|
611
|
+
{
|
|
612
|
+
label: "Registered customers",
|
|
613
|
+
value: Math.floor(data.registeredCount).toLocaleString()
|
|
614
|
+
}
|
|
615
|
+
),
|
|
616
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
617
|
+
AnalyticsStatCard,
|
|
618
|
+
{
|
|
619
|
+
label: "Overall customers",
|
|
620
|
+
value: Math.floor(data.totalCount).toLocaleString()
|
|
621
|
+
}
|
|
622
|
+
),
|
|
623
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
624
|
+
AnalyticsStatCard,
|
|
625
|
+
{
|
|
626
|
+
label: "Deleted accounts",
|
|
627
|
+
value: (data.deletedAccountsCount ?? 0).toLocaleString()
|
|
628
|
+
}
|
|
629
|
+
)
|
|
510
630
|
] }),
|
|
511
|
-
/* @__PURE__ */ jsxRuntime.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
631
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
632
|
+
AnalyticsSection,
|
|
633
|
+
{
|
|
634
|
+
title: "Customers over time",
|
|
635
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
515
636
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
516
637
|
ui.Label,
|
|
517
638
|
{
|
|
@@ -527,104 +648,727 @@ function CustomersDashboard() {
|
|
|
527
648
|
value: graphPeriod,
|
|
528
649
|
onChange: (e) => setGraphPeriod(e.target.value),
|
|
529
650
|
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
530
|
-
children: GRAPH_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
651
|
+
children: GRAPH_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
531
652
|
}
|
|
532
653
|
)
|
|
654
|
+
] }),
|
|
655
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: graphLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
656
|
+
"div",
|
|
657
|
+
{
|
|
658
|
+
className: "flex items-center justify-center h-full",
|
|
659
|
+
role: "status",
|
|
660
|
+
"aria-label": "Loading customers over time",
|
|
661
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
|
|
662
|
+
}
|
|
663
|
+
) : graphError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: graphError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
664
|
+
recharts.LineChart,
|
|
665
|
+
{
|
|
666
|
+
data: series,
|
|
667
|
+
margin: { top: 8, right: 8, left: 8, bottom: 8 },
|
|
668
|
+
children: [
|
|
669
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
670
|
+
recharts.CartesianGrid,
|
|
671
|
+
{
|
|
672
|
+
strokeDasharray: "3 3",
|
|
673
|
+
className: "stroke-ui-border-base"
|
|
674
|
+
}
|
|
675
|
+
),
|
|
676
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
677
|
+
recharts.XAxis,
|
|
678
|
+
{
|
|
679
|
+
dataKey: "date",
|
|
680
|
+
tick: { fontSize: 12 },
|
|
681
|
+
tickFormatter: (v, index) => {
|
|
682
|
+
const row = series[index];
|
|
683
|
+
if (row == null ? void 0 : row.label) return row.label;
|
|
684
|
+
const d = new Date(v);
|
|
685
|
+
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
),
|
|
689
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
690
|
+
recharts.YAxis,
|
|
691
|
+
{
|
|
692
|
+
tick: { fontSize: 12 },
|
|
693
|
+
tickFormatter: (v) => String(Math.floor(Number(v))),
|
|
694
|
+
allowDecimals: false
|
|
695
|
+
}
|
|
696
|
+
),
|
|
697
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(CustomersOverTimeTooltip, {}) }),
|
|
698
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
699
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
700
|
+
recharts.Line,
|
|
701
|
+
{
|
|
702
|
+
type: "monotone",
|
|
703
|
+
dataKey: "registered_count",
|
|
704
|
+
name: "Registered",
|
|
705
|
+
stroke: REGISTERED_COLOR,
|
|
706
|
+
strokeWidth: 2,
|
|
707
|
+
dot: { r: 3 }
|
|
708
|
+
}
|
|
709
|
+
),
|
|
710
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
711
|
+
recharts.Line,
|
|
712
|
+
{
|
|
713
|
+
type: "monotone",
|
|
714
|
+
dataKey: "guest_count",
|
|
715
|
+
name: "Guest",
|
|
716
|
+
stroke: GUEST_COLOR,
|
|
717
|
+
strokeWidth: 2,
|
|
718
|
+
dot: { r: 3 }
|
|
719
|
+
}
|
|
720
|
+
)
|
|
721
|
+
]
|
|
722
|
+
}
|
|
723
|
+
) }) }) })
|
|
724
|
+
}
|
|
725
|
+
)
|
|
726
|
+
] });
|
|
727
|
+
}
|
|
728
|
+
const SUMMARY_PERIODS = [
|
|
729
|
+
{ value: "all", label: "All time" },
|
|
730
|
+
{ value: "0", label: "Today" },
|
|
731
|
+
{ value: "7", label: "Last 7 days" },
|
|
732
|
+
{ value: "30", label: "Last 30 days" },
|
|
733
|
+
{ value: "90", label: "Last 90 days" }
|
|
734
|
+
];
|
|
735
|
+
const GRAPH_PERIODS = [
|
|
736
|
+
{ value: "one_week", label: "One week" },
|
|
737
|
+
{ value: "one_month", label: "One month" },
|
|
738
|
+
{ value: "one_year", label: "One year" }
|
|
739
|
+
];
|
|
740
|
+
const TOP_SELLER_PERIODS = [
|
|
741
|
+
{ value: "week", label: "Week" },
|
|
742
|
+
{ value: "month", label: "Month" },
|
|
743
|
+
{ value: "year", label: "Year" },
|
|
744
|
+
{ value: "all", label: "All time" }
|
|
745
|
+
];
|
|
746
|
+
const SALES_COLOR = "var(--medusa-color-ui-fg-interactive)";
|
|
747
|
+
const VIEWS_COLOR = "#8B5CF6";
|
|
748
|
+
const REVENUE_COLOR = "#F59E0B";
|
|
749
|
+
function formatCurrency(value) {
|
|
750
|
+
return new Intl.NumberFormat("en-IN", {
|
|
751
|
+
style: "currency",
|
|
752
|
+
currency: "INR",
|
|
753
|
+
minimumFractionDigits: 0,
|
|
754
|
+
maximumFractionDigits: 0
|
|
755
|
+
}).format(value);
|
|
756
|
+
}
|
|
757
|
+
function formatRatio(value) {
|
|
758
|
+
if (value === null || Number.isNaN(value)) return "N/A";
|
|
759
|
+
return `${value.toFixed(2)}x`;
|
|
760
|
+
}
|
|
761
|
+
function truncateLabel(value, maxLength = 22) {
|
|
762
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
|
|
763
|
+
}
|
|
764
|
+
function buildAnalyticsUrl(path, params) {
|
|
765
|
+
const search = new URLSearchParams();
|
|
766
|
+
for (const [key, value] of Object.entries(params)) {
|
|
767
|
+
if (value) {
|
|
768
|
+
search.set(key, value);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const query = search.toString();
|
|
772
|
+
return query ? `${path}?${query}` : path;
|
|
773
|
+
}
|
|
774
|
+
function TrendTooltip({
|
|
775
|
+
active,
|
|
776
|
+
payload,
|
|
777
|
+
label
|
|
778
|
+
}) {
|
|
779
|
+
var _a, _b, _c, _d;
|
|
780
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
781
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
782
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
783
|
+
const unitsSold = ((_b = payload.find((entry) => entry.name === "Units sold")) == null ? void 0 : _b.value) ?? 0;
|
|
784
|
+
const views = ((_c = payload.find((entry) => entry.name === "Views")) == null ? void 0 : _c.value) ?? 0;
|
|
785
|
+
const revenue = ((_d = payload.find((entry) => entry.name === "Revenue")) == null ? void 0 : _d.value) ?? 0;
|
|
786
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
787
|
+
"div",
|
|
788
|
+
{
|
|
789
|
+
style: {
|
|
790
|
+
backgroundColor: "rgb(255, 255, 255)",
|
|
791
|
+
color: "#111827",
|
|
792
|
+
border: "1px solid rgb(229, 231, 235)",
|
|
793
|
+
borderRadius: "8px",
|
|
794
|
+
padding: "12px 16px",
|
|
795
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.08)",
|
|
796
|
+
fontSize: "14px",
|
|
797
|
+
lineHeight: 1.5
|
|
798
|
+
},
|
|
799
|
+
children: [
|
|
800
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px" }, children: displayLabel }),
|
|
801
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
802
|
+
"Units sold: ",
|
|
803
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(unitsSold).toLocaleString() })
|
|
804
|
+
] }),
|
|
805
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
806
|
+
"Views: ",
|
|
807
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(views).toLocaleString() })
|
|
808
|
+
] }),
|
|
809
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
810
|
+
"Revenue: ",
|
|
811
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
533
812
|
] })
|
|
534
|
-
]
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
813
|
+
]
|
|
814
|
+
}
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
function ProductBarTooltip({
|
|
818
|
+
active,
|
|
819
|
+
payload
|
|
820
|
+
}) {
|
|
821
|
+
var _a;
|
|
822
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
823
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
824
|
+
if (!row) return null;
|
|
825
|
+
const unitsSold = "units_sold" in row ? row.units_sold : 0;
|
|
826
|
+
const totalViews = "total_views" in row ? row.total_views : 0;
|
|
827
|
+
const revenue = "revenue" in row ? row.revenue : 0;
|
|
828
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
829
|
+
"div",
|
|
830
|
+
{
|
|
831
|
+
style: {
|
|
832
|
+
backgroundColor: "rgb(255, 255, 255)",
|
|
833
|
+
color: "#111827",
|
|
834
|
+
border: "1px solid rgb(229, 231, 235)",
|
|
835
|
+
borderRadius: "8px",
|
|
836
|
+
padding: "12px 16px",
|
|
837
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.08)",
|
|
838
|
+
fontSize: "14px",
|
|
839
|
+
lineHeight: 1.5
|
|
840
|
+
},
|
|
841
|
+
children: [
|
|
842
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px" }, children: row.product_title }),
|
|
843
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
844
|
+
"Units sold: ",
|
|
845
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(unitsSold).toLocaleString() })
|
|
846
|
+
] }),
|
|
847
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
848
|
+
"Views: ",
|
|
849
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(totalViews).toLocaleString() })
|
|
850
|
+
] }),
|
|
851
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
852
|
+
"Revenue: ",
|
|
853
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
854
|
+
] })
|
|
855
|
+
]
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
function ProductsDashboard() {
|
|
860
|
+
var _a, _b, _c;
|
|
861
|
+
const [summaryDays, setSummaryDays] = react.useState("all");
|
|
862
|
+
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
863
|
+
const [topSellerPeriod, setTopSellerPeriod] = react.useState("week");
|
|
864
|
+
const [salesChannelId, setSalesChannelId] = react.useState("all");
|
|
865
|
+
const [salesChannels, setSalesChannels] = react.useState([]);
|
|
866
|
+
const [filtersLoading, setFiltersLoading] = react.useState(true);
|
|
867
|
+
const [filtersError, setFiltersError] = react.useState(null);
|
|
868
|
+
const [summary, setSummary] = react.useState(null);
|
|
869
|
+
const [summaryLoading, setSummaryLoading] = react.useState(true);
|
|
870
|
+
const [summaryError, setSummaryError] = react.useState(null);
|
|
871
|
+
const [overTime, setOverTime] = react.useState(null);
|
|
872
|
+
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
873
|
+
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
874
|
+
const [topSellers, setTopSellers] = react.useState(null);
|
|
875
|
+
const [topSellersLoading, setTopSellersLoading] = react.useState(true);
|
|
876
|
+
const [topSellersError, setTopSellersError] = react.useState(null);
|
|
877
|
+
const [performance, setPerformance] = react.useState(null);
|
|
878
|
+
const [performanceLoading, setPerformanceLoading] = react.useState(true);
|
|
879
|
+
const [performanceError, setPerformanceError] = react.useState(null);
|
|
880
|
+
react.useEffect(() => {
|
|
881
|
+
let cancelled = false;
|
|
882
|
+
setFiltersLoading(true);
|
|
883
|
+
setFiltersError(null);
|
|
884
|
+
fetch("/admin/analytics/products-filters").then((res) => {
|
|
885
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
886
|
+
return res.json();
|
|
887
|
+
}).then((body) => {
|
|
888
|
+
if (!cancelled) {
|
|
889
|
+
setSalesChannels(body.salesChannels ?? []);
|
|
890
|
+
}
|
|
891
|
+
}).catch((error) => {
|
|
892
|
+
if (!cancelled) {
|
|
893
|
+
setFiltersError(error instanceof Error ? error.message : String(error));
|
|
894
|
+
}
|
|
895
|
+
}).finally(() => {
|
|
896
|
+
if (!cancelled) setFiltersLoading(false);
|
|
897
|
+
});
|
|
898
|
+
return () => {
|
|
899
|
+
cancelled = true;
|
|
900
|
+
};
|
|
901
|
+
}, []);
|
|
902
|
+
react.useEffect(() => {
|
|
903
|
+
let cancelled = false;
|
|
904
|
+
setSummaryLoading(true);
|
|
905
|
+
setSummaryError(null);
|
|
906
|
+
fetch(
|
|
907
|
+
buildAnalyticsUrl("/admin/analytics/products-summary", {
|
|
908
|
+
days: summaryDays,
|
|
909
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
910
|
+
})
|
|
911
|
+
).then((res) => {
|
|
912
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
913
|
+
return res.json();
|
|
914
|
+
}).then((body) => {
|
|
915
|
+
if (!cancelled) setSummary(body);
|
|
916
|
+
}).catch((error) => {
|
|
917
|
+
if (!cancelled) {
|
|
918
|
+
setSummaryError(error instanceof Error ? error.message : String(error));
|
|
919
|
+
}
|
|
920
|
+
}).finally(() => {
|
|
921
|
+
if (!cancelled) setSummaryLoading(false);
|
|
922
|
+
});
|
|
923
|
+
return () => {
|
|
924
|
+
cancelled = true;
|
|
925
|
+
};
|
|
926
|
+
}, [salesChannelId, summaryDays]);
|
|
927
|
+
react.useEffect(() => {
|
|
928
|
+
let cancelled = false;
|
|
929
|
+
setOverTimeLoading(true);
|
|
930
|
+
setOverTimeError(null);
|
|
931
|
+
fetch(
|
|
932
|
+
buildAnalyticsUrl("/admin/analytics/products-over-time", {
|
|
933
|
+
period: graphPeriod,
|
|
934
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
935
|
+
})
|
|
936
|
+
).then((res) => {
|
|
937
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
938
|
+
return res.json();
|
|
939
|
+
}).then((body) => {
|
|
940
|
+
if (!cancelled) setOverTime(body);
|
|
941
|
+
}).catch((error) => {
|
|
942
|
+
if (!cancelled) {
|
|
943
|
+
setOverTimeError(error instanceof Error ? error.message : String(error));
|
|
944
|
+
}
|
|
945
|
+
}).finally(() => {
|
|
946
|
+
if (!cancelled) setOverTimeLoading(false);
|
|
947
|
+
});
|
|
948
|
+
return () => {
|
|
949
|
+
cancelled = true;
|
|
950
|
+
};
|
|
951
|
+
}, [graphPeriod, salesChannelId]);
|
|
952
|
+
react.useEffect(() => {
|
|
953
|
+
let cancelled = false;
|
|
954
|
+
setTopSellersLoading(true);
|
|
955
|
+
setTopSellersError(null);
|
|
956
|
+
fetch(
|
|
957
|
+
buildAnalyticsUrl("/admin/analytics/products-top-sellers", {
|
|
958
|
+
period: topSellerPeriod,
|
|
959
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
960
|
+
})
|
|
961
|
+
).then((res) => {
|
|
962
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
963
|
+
return res.json();
|
|
964
|
+
}).then((body) => {
|
|
965
|
+
if (!cancelled) setTopSellers(body);
|
|
966
|
+
}).catch((error) => {
|
|
967
|
+
if (!cancelled) {
|
|
968
|
+
setTopSellersError(error instanceof Error ? error.message : String(error));
|
|
969
|
+
}
|
|
970
|
+
}).finally(() => {
|
|
971
|
+
if (!cancelled) setTopSellersLoading(false);
|
|
972
|
+
});
|
|
973
|
+
return () => {
|
|
974
|
+
cancelled = true;
|
|
975
|
+
};
|
|
976
|
+
}, [salesChannelId, topSellerPeriod]);
|
|
977
|
+
react.useEffect(() => {
|
|
978
|
+
let cancelled = false;
|
|
979
|
+
setPerformanceLoading(true);
|
|
980
|
+
setPerformanceError(null);
|
|
981
|
+
fetch(
|
|
982
|
+
buildAnalyticsUrl("/admin/analytics/products-performance", {
|
|
983
|
+
days: summaryDays,
|
|
984
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
985
|
+
})
|
|
986
|
+
).then((res) => {
|
|
987
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
988
|
+
return res.json();
|
|
989
|
+
}).then((body) => {
|
|
990
|
+
if (!cancelled) setPerformance(body);
|
|
991
|
+
}).catch((error) => {
|
|
992
|
+
if (!cancelled) {
|
|
993
|
+
setPerformanceError(error instanceof Error ? error.message : String(error));
|
|
994
|
+
}
|
|
995
|
+
}).finally(() => {
|
|
996
|
+
if (!cancelled) setPerformanceLoading(false);
|
|
997
|
+
});
|
|
998
|
+
return () => {
|
|
999
|
+
cancelled = true;
|
|
1000
|
+
};
|
|
1001
|
+
}, [salesChannelId, summaryDays]);
|
|
1002
|
+
const topSellerChartData = react.useMemo(
|
|
1003
|
+
() => ((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 7).map((row) => ({
|
|
1004
|
+
...row,
|
|
1005
|
+
product_title: truncateLabel(row.product_title, 24)
|
|
1006
|
+
})).reverse(),
|
|
1007
|
+
[topSellers]
|
|
1008
|
+
);
|
|
1009
|
+
const topViewedChartData = react.useMemo(
|
|
1010
|
+
() => ((performance == null ? void 0 : performance.topViewedProducts) ?? []).slice(0, 7).map((row) => ({
|
|
1011
|
+
...row,
|
|
1012
|
+
product_title: truncateLabel(row.product_title, 24)
|
|
1013
|
+
})).reverse(),
|
|
1014
|
+
[performance]
|
|
1015
|
+
);
|
|
1016
|
+
if (summaryLoading) {
|
|
1017
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1018
|
+
"div",
|
|
1019
|
+
{
|
|
1020
|
+
className: "flex items-center justify-center min-h-[320px]",
|
|
1021
|
+
role: "status",
|
|
1022
|
+
"aria-label": "Loading product analytics",
|
|
1023
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
1024
|
+
}
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
if (summaryError || !summary) {
|
|
1028
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: summaryError ?? "Failed to load product analytics" }) });
|
|
1029
|
+
}
|
|
1030
|
+
const viewsConnected = summary.productViewsConnected || (overTime == null ? void 0 : overTime.productViewsConnected) === true || (topSellers == null ? void 0 : topSellers.productViewsConnected) === true || (performance == null ? void 0 : performance.productViewsConnected) === true;
|
|
1031
|
+
const selectedChannelLabel = salesChannelId === "all" ? "All channels" : ((_a = salesChannels.find((channel) => channel.id === salesChannelId)) == null ? void 0 : _a.name) ?? "Selected channel";
|
|
1032
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsDashboardShell, { children: [
|
|
1033
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1034
|
+
AnalyticsDashboardHeader,
|
|
1035
|
+
{
|
|
1036
|
+
title: "Products",
|
|
1037
|
+
description: "Track units sold, revenue, best sellers, and product visit behavior in a denser, easier-to-scan layout.",
|
|
1038
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1039
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1040
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-channel-filter", className: "text-ui-fg-muted text-sm", children: "Channel" }),
|
|
1041
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1042
|
+
"select",
|
|
551
1043
|
{
|
|
552
|
-
|
|
553
|
-
|
|
1044
|
+
id: "products-channel-filter",
|
|
1045
|
+
value: salesChannelId,
|
|
1046
|
+
onChange: (event) => setSalesChannelId(event.target.value),
|
|
1047
|
+
disabled: filtersLoading,
|
|
1048
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
1049
|
+
children: [
|
|
1050
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All channels" }),
|
|
1051
|
+
salesChannels.map((channel) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: channel.id, children: channel.name }, channel.id))
|
|
1052
|
+
]
|
|
554
1053
|
}
|
|
555
|
-
)
|
|
1054
|
+
)
|
|
1055
|
+
] }),
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1057
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-summary-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
|
|
556
1058
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
-
|
|
1059
|
+
"select",
|
|
558
1060
|
{
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const d = new Date(v);
|
|
565
|
-
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
566
|
-
}
|
|
1061
|
+
id: "products-summary-period",
|
|
1062
|
+
value: summaryDays,
|
|
1063
|
+
onChange: (event) => setSummaryDays(event.target.value),
|
|
1064
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
1065
|
+
children: SUMMARY_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
567
1066
|
}
|
|
568
|
-
)
|
|
1067
|
+
)
|
|
1068
|
+
] }),
|
|
1069
|
+
summaryDays !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1070
|
+
"button",
|
|
1071
|
+
{
|
|
1072
|
+
type: "button",
|
|
1073
|
+
onClick: () => setSummaryDays("all"),
|
|
1074
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
1075
|
+
"aria-label": "Show all products analytics",
|
|
1076
|
+
children: "Clear filter"
|
|
1077
|
+
}
|
|
1078
|
+
),
|
|
1079
|
+
salesChannelId !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1080
|
+
"button",
|
|
1081
|
+
{
|
|
1082
|
+
type: "button",
|
|
1083
|
+
onClick: () => setSalesChannelId("all"),
|
|
1084
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
1085
|
+
"aria-label": "Show all sales channels",
|
|
1086
|
+
children: "Clear channel"
|
|
1087
|
+
}
|
|
1088
|
+
)
|
|
1089
|
+
] })
|
|
1090
|
+
}
|
|
1091
|
+
),
|
|
1092
|
+
filtersError && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "rounded-2xl border border-ui-border-base bg-ui-bg-base p-4 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-sm", children: filtersError }) }),
|
|
1093
|
+
!viewsConnected && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "rounded-2xl border border-ui-border-base bg-ui-bg-base p-4 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Product views are unavailable until the `medusa-product-helper` tracking module is registered and storefront page views are being recorded." }) }),
|
|
1094
|
+
salesChannelId !== "all" && viewsConnected && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "rounded-2xl border border-ui-border-base bg-ui-bg-base p-4 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-sm", children: [
|
|
1095
|
+
"Showing product analytics for ",
|
|
1096
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: selectedChannelLabel }),
|
|
1097
|
+
". Product views are scoped to products assigned to this sales channel."
|
|
1098
|
+
] }) }),
|
|
1099
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3", children: [
|
|
1100
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1101
|
+
AnalyticsStatCard,
|
|
1102
|
+
{
|
|
1103
|
+
label: "Units sold",
|
|
1104
|
+
value: summary.unitsSold.toLocaleString()
|
|
1105
|
+
}
|
|
1106
|
+
),
|
|
1107
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1108
|
+
AnalyticsStatCard,
|
|
1109
|
+
{
|
|
1110
|
+
label: "Product revenue",
|
|
1111
|
+
value: formatCurrency(summary.productRevenue)
|
|
1112
|
+
}
|
|
1113
|
+
),
|
|
1114
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1115
|
+
AnalyticsStatCard,
|
|
1116
|
+
{
|
|
1117
|
+
label: "Orders with product sales",
|
|
1118
|
+
value: summary.ordersWithProducts.toLocaleString()
|
|
1119
|
+
}
|
|
1120
|
+
),
|
|
1121
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1122
|
+
AnalyticsStatCard,
|
|
1123
|
+
{
|
|
1124
|
+
label: "Active products sold",
|
|
1125
|
+
value: summary.activeProductsSold.toLocaleString()
|
|
1126
|
+
}
|
|
1127
|
+
),
|
|
1128
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1129
|
+
AnalyticsStatCard,
|
|
1130
|
+
{
|
|
1131
|
+
label: "Product views",
|
|
1132
|
+
value: summary.totalProductViews.toLocaleString()
|
|
1133
|
+
}
|
|
1134
|
+
),
|
|
1135
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1136
|
+
AnalyticsStatCard,
|
|
1137
|
+
{
|
|
1138
|
+
label: "View / order ratio",
|
|
1139
|
+
value: formatRatio(summary.viewToOrderRatio)
|
|
1140
|
+
}
|
|
1141
|
+
)
|
|
1142
|
+
] }),
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1144
|
+
AnalyticsSection,
|
|
1145
|
+
{
|
|
1146
|
+
title: "Sales and views over time",
|
|
1147
|
+
description: "Units sold and revenue are always shown. Product views appear when tracking data is available.",
|
|
1148
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1149
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-graph-period", className: "text-ui-fg-muted text-sm", children: "Range" }),
|
|
1150
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1151
|
+
"select",
|
|
1152
|
+
{
|
|
1153
|
+
id: "products-graph-period",
|
|
1154
|
+
value: graphPeriod,
|
|
1155
|
+
onChange: (event) => setGraphPeriod(event.target.value),
|
|
1156
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
1157
|
+
children: GRAPH_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
1158
|
+
}
|
|
1159
|
+
)
|
|
1160
|
+
] }),
|
|
1161
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-80", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" }) }) : overTimeError || !overTime ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: overTimeError ?? "Failed to load product trend" }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data: overTime.series, children: [
|
|
1162
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
|
|
1163
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "label" }),
|
|
1164
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1165
|
+
recharts.YAxis,
|
|
1166
|
+
{
|
|
1167
|
+
yAxisId: "counts",
|
|
1168
|
+
allowDecimals: false,
|
|
1169
|
+
tickFormatter: (value) => Math.floor(Number(value)).toLocaleString()
|
|
1170
|
+
}
|
|
1171
|
+
),
|
|
1172
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1173
|
+
recharts.YAxis,
|
|
1174
|
+
{
|
|
1175
|
+
yAxisId: "revenue",
|
|
1176
|
+
orientation: "right",
|
|
1177
|
+
tickFormatter: (value) => formatCurrency(Number(value))
|
|
1178
|
+
}
|
|
1179
|
+
),
|
|
1180
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(TrendTooltip, {}) }),
|
|
1181
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
1182
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1183
|
+
recharts.Line,
|
|
1184
|
+
{
|
|
1185
|
+
yAxisId: "counts",
|
|
1186
|
+
type: "monotone",
|
|
1187
|
+
dataKey: "units_sold",
|
|
1188
|
+
name: "Units sold",
|
|
1189
|
+
stroke: SALES_COLOR,
|
|
1190
|
+
strokeWidth: 2,
|
|
1191
|
+
dot: false
|
|
1192
|
+
}
|
|
1193
|
+
),
|
|
1194
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1195
|
+
recharts.Line,
|
|
1196
|
+
{
|
|
1197
|
+
yAxisId: "counts",
|
|
1198
|
+
type: "monotone",
|
|
1199
|
+
dataKey: "views",
|
|
1200
|
+
name: "Views",
|
|
1201
|
+
stroke: VIEWS_COLOR,
|
|
1202
|
+
strokeWidth: 2,
|
|
1203
|
+
dot: false
|
|
1204
|
+
}
|
|
1205
|
+
),
|
|
1206
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1207
|
+
recharts.Line,
|
|
1208
|
+
{
|
|
1209
|
+
yAxisId: "revenue",
|
|
1210
|
+
type: "monotone",
|
|
1211
|
+
dataKey: "revenue",
|
|
1212
|
+
name: "Revenue",
|
|
1213
|
+
stroke: REVENUE_COLOR,
|
|
1214
|
+
strokeWidth: 2,
|
|
1215
|
+
dot: false
|
|
1216
|
+
}
|
|
1217
|
+
)
|
|
1218
|
+
] }) }) }) })
|
|
1219
|
+
}
|
|
1220
|
+
),
|
|
1221
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [
|
|
1222
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1223
|
+
AnalyticsSection,
|
|
1224
|
+
{
|
|
1225
|
+
title: "Best sellers",
|
|
1226
|
+
description: "Ranked by units sold.",
|
|
1227
|
+
actions: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2", children: TOP_SELLER_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1228
|
+
ui.Button,
|
|
1229
|
+
{
|
|
1230
|
+
variant: topSellerPeriod === period.value ? "secondary" : "transparent",
|
|
1231
|
+
size: "small",
|
|
1232
|
+
onClick: () => setTopSellerPeriod(period.value),
|
|
1233
|
+
children: period.label
|
|
1234
|
+
},
|
|
1235
|
+
period.value
|
|
1236
|
+
)) }),
|
|
1237
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-80", children: topSellersLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading best sellers…" }) }) : topSellersError || !topSellers ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: topSellersError ?? "Failed to load best sellers" }) }) : topSellerChartData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "No product sales found for this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data: topSellerChartData, layout: "vertical", margin: { left: 8, right: 8 }, children: [
|
|
1238
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", horizontal: false }),
|
|
1239
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", allowDecimals: false }),
|
|
569
1240
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
570
1241
|
recharts.YAxis,
|
|
571
1242
|
{
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
1243
|
+
type: "category",
|
|
1244
|
+
dataKey: "product_title",
|
|
1245
|
+
width: 160,
|
|
1246
|
+
tickLine: false
|
|
575
1247
|
}
|
|
576
1248
|
),
|
|
577
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(
|
|
578
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.
|
|
1249
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {}) }),
|
|
1250
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "units_sold", fill: SALES_COLOR, radius: [0, 6, 6, 0] })
|
|
1251
|
+
] }) }) }) })
|
|
1252
|
+
}
|
|
1253
|
+
),
|
|
1254
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1255
|
+
AnalyticsSection,
|
|
1256
|
+
{
|
|
1257
|
+
title: "Top seller breakdown",
|
|
1258
|
+
description: "Compact leaderboard for the leading products in the selected period.",
|
|
1259
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTableSurface, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1260
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1261
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
1262
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Units" }),
|
|
1263
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Orders" }),
|
|
1264
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Revenue" })
|
|
1265
|
+
] }) }),
|
|
1266
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
1267
|
+
((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 8).map((product) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1268
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
1269
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
1270
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.order_count.toLocaleString() }),
|
|
1271
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatCurrency(product.revenue) })
|
|
1272
|
+
] }, product.product_id)),
|
|
1273
|
+
!topSellersLoading && (((_b = topSellers == null ? void 0 : topSellers.products) == null ? void 0 : _b.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1274
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: "No best seller data yet." }),
|
|
1275
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1276
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1277
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
1278
|
+
] })
|
|
1279
|
+
] })
|
|
1280
|
+
] }) })
|
|
1281
|
+
}
|
|
1282
|
+
)
|
|
1283
|
+
] }),
|
|
1284
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [
|
|
1285
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1286
|
+
AnalyticsSection,
|
|
1287
|
+
{
|
|
1288
|
+
title: "Most viewed products",
|
|
1289
|
+
description: "View activity is sourced from the existing product-helper tracking module.",
|
|
1290
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-80", children: performanceLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading product views…" }) }) : performanceError || !performance ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: performanceError ?? "Failed to load product performance" }) }) : !performance.productViewsConnected ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Product view tracking is not available in this environment." }) }) : topViewedChartData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "No product views found for this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data: topViewedChartData, layout: "vertical", margin: { left: 8, right: 8 }, children: [
|
|
1291
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", horizontal: false }),
|
|
1292
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", allowDecimals: false }),
|
|
579
1293
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
580
|
-
recharts.
|
|
1294
|
+
recharts.YAxis,
|
|
581
1295
|
{
|
|
582
|
-
type: "
|
|
583
|
-
dataKey: "
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
strokeWidth: 2,
|
|
587
|
-
dot: { r: 3 }
|
|
1296
|
+
type: "category",
|
|
1297
|
+
dataKey: "product_title",
|
|
1298
|
+
width: 160,
|
|
1299
|
+
tickLine: false
|
|
588
1300
|
}
|
|
589
1301
|
),
|
|
590
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
type: "monotone",
|
|
594
|
-
dataKey: "guest_count",
|
|
595
|
-
name: "Guest",
|
|
596
|
-
stroke: GUEST_COLOR,
|
|
597
|
-
strokeWidth: 2,
|
|
598
|
-
dot: { r: 3 }
|
|
599
|
-
}
|
|
600
|
-
)
|
|
601
|
-
]
|
|
1302
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {}) }),
|
|
1303
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "total_views", fill: VIEWS_COLOR, radius: [0, 6, 6, 0] })
|
|
1304
|
+
] }) }) }) })
|
|
602
1305
|
}
|
|
603
|
-
)
|
|
1306
|
+
),
|
|
1307
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1308
|
+
AnalyticsSection,
|
|
1309
|
+
{
|
|
1310
|
+
title: "View-to-sales opportunities",
|
|
1311
|
+
description: "Highlight products that attract attention but convert less efficiently.",
|
|
1312
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTableSurface, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1313
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1314
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
1315
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Views" }),
|
|
1316
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Units" }),
|
|
1317
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Views / unit" })
|
|
1318
|
+
] }) }),
|
|
1319
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
1320
|
+
((performance == null ? void 0 : performance.viewOpportunities) ?? []).slice(0, 8).map((product) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1321
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
1322
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.total_views.toLocaleString() }),
|
|
1323
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
1324
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatRatio(product.views_per_unit) })
|
|
1325
|
+
] }, product.product_id)),
|
|
1326
|
+
!performanceLoading && !performanceError && (((_c = performance == null ? void 0 : performance.viewOpportunities) == null ? void 0 : _c.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1327
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: "No product view opportunities yet." }),
|
|
1328
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1329
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1330
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
1331
|
+
] })
|
|
1332
|
+
] })
|
|
1333
|
+
] }) })
|
|
1334
|
+
}
|
|
1335
|
+
)
|
|
604
1336
|
] })
|
|
605
1337
|
] });
|
|
606
1338
|
}
|
|
607
1339
|
const ANALYTICS_MODULES = [
|
|
608
1340
|
{ id: "orders", label: "Orders" },
|
|
609
|
-
{ id: "customers", label: "Customers" }
|
|
1341
|
+
{ id: "customers", label: "Customers" },
|
|
1342
|
+
{ id: "products", label: "Products" }
|
|
610
1343
|
];
|
|
611
1344
|
const AnalyticsPage = () => {
|
|
612
1345
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
613
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-
|
|
614
|
-
/* @__PURE__ */ jsxRuntime.
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
1346
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex w-full max-w-[1600px] flex-col gap-4 p-4", children: [
|
|
1347
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "overflow-hidden rounded-2xl border border-ui-border-base bg-ui-bg-base p-0 shadow-sm", children: [
|
|
1348
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-5 px-5 py-5 xl:flex-row xl:items-end xl:justify-between", children: [
|
|
1349
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1350
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "tracking-tight", children: "Analytics" }),
|
|
1351
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Review orders, customers, and products in one organized workspace." })
|
|
1352
|
+
] }),
|
|
1353
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2 rounded-2xl border border-ui-border-base bg-ui-bg-subtle p-1.5", children: ANALYTICS_MODULES.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1354
|
+
ui.Button,
|
|
1355
|
+
{
|
|
1356
|
+
variant: activeModule === m.id ? "secondary" : "transparent",
|
|
1357
|
+
size: "small",
|
|
1358
|
+
onClick: () => setActiveModule(m.id),
|
|
1359
|
+
children: m.label
|
|
1360
|
+
},
|
|
1361
|
+
m.id
|
|
1362
|
+
)) })
|
|
1363
|
+
] }),
|
|
1364
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1 bg-ui-bg-subtle" })
|
|
1365
|
+
] }),
|
|
1366
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1367
|
+
activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
|
|
1368
|
+
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {}),
|
|
1369
|
+
activeModule === "products" && /* @__PURE__ */ jsxRuntime.jsx(ProductsDashboard, {})
|
|
1370
|
+
] })
|
|
1371
|
+
] }) }) });
|
|
628
1372
|
};
|
|
629
1373
|
const config = adminSdk.defineRouteConfig({
|
|
630
1374
|
label: "Analytics",
|