medusa-analytics 0.0.14 → 0.0.16
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.
|
@@ -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 lg:flex-row lg:items-end lg: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 self-start rounded-xl border border-ui-border-base bg-ui-bg-subtle p-2 lg:self-auto", 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-3 shadow-sm transition-shadow", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-[76px] flex-col justify-between gap-1.5", children: [
|
|
29
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs font-medium", children: label }),
|
|
30
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "text-2xl leading-none tracking-tight", children: value }),
|
|
31
|
+
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs leading-4", 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 px-3 py-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,
|
|
@@ -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({
|
|
@@ -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
|
{
|
|
@@ -530,78 +651,78 @@ function CustomersDashboard() {
|
|
|
530
651
|
children: GRAPH_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
531
652
|
}
|
|
532
653
|
)
|
|
533
|
-
] })
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
className: "stroke-ui-border-base"
|
|
554
|
-
}
|
|
555
|
-
),
|
|
556
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
-
recharts.XAxis,
|
|
558
|
-
{
|
|
559
|
-
dataKey: "date",
|
|
560
|
-
tick: { fontSize: 12 },
|
|
561
|
-
tickFormatter: (v, index) => {
|
|
562
|
-
const row = series[index];
|
|
563
|
-
if (row == null ? void 0 : row.label) return row.label;
|
|
564
|
-
const d = new Date(v);
|
|
565
|
-
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
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"
|
|
566
674
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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
|
+
)
|
|
605
726
|
] });
|
|
606
727
|
}
|
|
607
728
|
const SUMMARY_PERIODS = [
|
|
@@ -625,6 +746,7 @@ const TOP_SELLER_PERIODS = [
|
|
|
625
746
|
const SALES_COLOR = "var(--medusa-color-ui-fg-interactive)";
|
|
626
747
|
const VIEWS_COLOR = "#8B5CF6";
|
|
627
748
|
const REVENUE_COLOR = "#F59E0B";
|
|
749
|
+
const REVENUE_MUTED_COLOR = "#FCD34D";
|
|
628
750
|
function formatCurrency(value) {
|
|
629
751
|
return new Intl.NumberFormat("en-IN", {
|
|
630
752
|
style: "currency",
|
|
@@ -695,7 +817,8 @@ function TrendTooltip({
|
|
|
695
817
|
}
|
|
696
818
|
function ProductBarTooltip({
|
|
697
819
|
active,
|
|
698
|
-
payload
|
|
820
|
+
payload,
|
|
821
|
+
showViews = true
|
|
699
822
|
}) {
|
|
700
823
|
var _a;
|
|
701
824
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
@@ -723,10 +846,10 @@ function ProductBarTooltip({
|
|
|
723
846
|
"Units sold: ",
|
|
724
847
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(unitsSold).toLocaleString() })
|
|
725
848
|
] }),
|
|
726
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
849
|
+
showViews ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
727
850
|
"Views: ",
|
|
728
851
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(totalViews).toLocaleString() })
|
|
729
|
-
] }),
|
|
852
|
+
] }) : null,
|
|
730
853
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
731
854
|
"Revenue: ",
|
|
732
855
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
@@ -879,10 +1002,14 @@ function ProductsDashboard() {
|
|
|
879
1002
|
};
|
|
880
1003
|
}, [salesChannelId, summaryDays]);
|
|
881
1004
|
const topSellerChartData = react.useMemo(
|
|
882
|
-
() => ((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 7).map((row) =>
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1005
|
+
() => ((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 7).map((row) => {
|
|
1006
|
+
var _a2, _b2;
|
|
1007
|
+
return {
|
|
1008
|
+
...row,
|
|
1009
|
+
isBestSeller: row.product_id === (((_b2 = (_a2 = topSellers == null ? void 0 : topSellers.products) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.product_id) ?? null),
|
|
1010
|
+
product_title: truncateLabel(row.product_title, 24)
|
|
1011
|
+
};
|
|
1012
|
+
}).reverse(),
|
|
886
1013
|
[topSellers]
|
|
887
1014
|
);
|
|
888
1015
|
const topViewedChartData = react.useMemo(
|
|
@@ -908,105 +1035,125 @@ function ProductsDashboard() {
|
|
|
908
1035
|
}
|
|
909
1036
|
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;
|
|
910
1037
|
const selectedChannelLabel = salesChannelId === "all" ? "All channels" : ((_a = salesChannels.find((channel) => channel.id === salesChannelId)) == null ? void 0 : _a.name) ?? "Selected channel";
|
|
911
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
912
|
-
/* @__PURE__ */ jsxRuntime.
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1038
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsDashboardShell, { children: [
|
|
1039
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1040
|
+
AnalyticsDashboardHeader,
|
|
1041
|
+
{
|
|
1042
|
+
title: "Products",
|
|
1043
|
+
description: "Track units sold, revenue, best sellers, and product visit behavior in a denser, easier-to-scan layout.",
|
|
1044
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-end gap-2.5", children: [
|
|
1045
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
1046
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-channel-filter", className: "text-ui-fg-muted text-xs font-medium", children: "Channel" }),
|
|
1047
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1048
|
+
"select",
|
|
1049
|
+
{
|
|
1050
|
+
id: "products-channel-filter",
|
|
1051
|
+
value: salesChannelId,
|
|
1052
|
+
onChange: (event) => setSalesChannelId(event.target.value),
|
|
1053
|
+
disabled: filtersLoading,
|
|
1054
|
+
className: "h-8 min-w-[150px] rounded-md border border-ui-border-base bg-ui-bg-base px-2.5 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
1055
|
+
children: [
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All channels" }),
|
|
1057
|
+
salesChannels.map((channel) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: channel.id, children: channel.name }, channel.id))
|
|
1058
|
+
]
|
|
1059
|
+
}
|
|
1060
|
+
)
|
|
1061
|
+
] }),
|
|
1062
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
1063
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-summary-period", className: "text-ui-fg-muted text-xs font-medium", children: "Period" }),
|
|
1064
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1065
|
+
"select",
|
|
1066
|
+
{
|
|
1067
|
+
id: "products-summary-period",
|
|
1068
|
+
value: summaryDays,
|
|
1069
|
+
onChange: (event) => setSummaryDays(event.target.value),
|
|
1070
|
+
className: "h-8 min-w-[132px] rounded-md border border-ui-border-base bg-ui-bg-base px-2.5 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
1071
|
+
children: SUMMARY_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
1072
|
+
}
|
|
1073
|
+
)
|
|
1074
|
+
] }),
|
|
1075
|
+
(summaryDays !== "all" || salesChannelId !== "all") && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 self-end pb-1", children: [
|
|
1076
|
+
summaryDays !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1077
|
+
"button",
|
|
1078
|
+
{
|
|
1079
|
+
type: "button",
|
|
1080
|
+
onClick: () => setSummaryDays("all"),
|
|
1081
|
+
className: "text-xs text-ui-fg-muted transition-colors hover:text-ui-fg-base",
|
|
1082
|
+
"aria-label": "Show all products analytics",
|
|
1083
|
+
children: "Clear period"
|
|
1084
|
+
}
|
|
1085
|
+
),
|
|
1086
|
+
salesChannelId !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1087
|
+
"button",
|
|
1088
|
+
{
|
|
1089
|
+
type: "button",
|
|
1090
|
+
onClick: () => setSalesChannelId("all"),
|
|
1091
|
+
className: "text-xs text-ui-fg-muted transition-colors hover:text-ui-fg-base",
|
|
1092
|
+
"aria-label": "Show all sales channels",
|
|
1093
|
+
children: "Clear channel"
|
|
1094
|
+
}
|
|
1095
|
+
)
|
|
1096
|
+
] })
|
|
1097
|
+
] })
|
|
1098
|
+
}
|
|
1099
|
+
),
|
|
1100
|
+
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 }) }),
|
|
1101
|
+
!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." }) }),
|
|
1102
|
+
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: [
|
|
973
1103
|
"Showing product analytics for ",
|
|
974
1104
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: selectedChannelLabel }),
|
|
975
1105
|
". Product views are scoped to products assigned to this sales channel."
|
|
976
1106
|
] }) }),
|
|
977
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3
|
|
978
|
-
/* @__PURE__ */ jsxRuntime.
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1107
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3", children: [
|
|
1108
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1109
|
+
AnalyticsStatCard,
|
|
1110
|
+
{
|
|
1111
|
+
label: "Units sold",
|
|
1112
|
+
value: summary.unitsSold.toLocaleString()
|
|
1113
|
+
}
|
|
1114
|
+
),
|
|
1115
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1116
|
+
AnalyticsStatCard,
|
|
1117
|
+
{
|
|
1118
|
+
label: "Product revenue",
|
|
1119
|
+
value: formatCurrency(summary.productRevenue)
|
|
1120
|
+
}
|
|
1121
|
+
),
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1123
|
+
AnalyticsStatCard,
|
|
1124
|
+
{
|
|
1125
|
+
label: "Orders with product sales",
|
|
1126
|
+
value: summary.ordersWithProducts.toLocaleString()
|
|
1127
|
+
}
|
|
1128
|
+
),
|
|
1129
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1130
|
+
AnalyticsStatCard,
|
|
1131
|
+
{
|
|
1132
|
+
label: "Active products sold",
|
|
1133
|
+
value: summary.activeProductsSold.toLocaleString()
|
|
1134
|
+
}
|
|
1135
|
+
),
|
|
1136
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1137
|
+
AnalyticsStatCard,
|
|
1138
|
+
{
|
|
1139
|
+
label: "Product views",
|
|
1140
|
+
value: summary.totalProductViews.toLocaleString()
|
|
1141
|
+
}
|
|
1142
|
+
),
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1144
|
+
AnalyticsStatCard,
|
|
1145
|
+
{
|
|
1146
|
+
label: "View / order ratio",
|
|
1147
|
+
value: formatRatio(summary.viewToOrderRatio)
|
|
1148
|
+
}
|
|
1149
|
+
)
|
|
1002
1150
|
] }),
|
|
1003
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1151
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1152
|
+
AnalyticsSection,
|
|
1153
|
+
{
|
|
1154
|
+
title: "Sales and views over time",
|
|
1155
|
+
description: "Units sold and revenue are always shown. Product views appear when tracking data is available.",
|
|
1156
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1010
1157
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-graph-period", className: "text-ui-fg-muted text-sm", children: "Range" }),
|
|
1011
1158
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1012
1159
|
"select",
|
|
@@ -1018,172 +1165,205 @@ function ProductsDashboard() {
|
|
|
1018
1165
|
children: GRAPH_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
1019
1166
|
}
|
|
1020
1167
|
)
|
|
1021
|
-
] })
|
|
1022
|
-
] }),
|
|
1023
|
-
/* @__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: [
|
|
1024
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
|
|
1025
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "label" }),
|
|
1026
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1027
|
-
recharts.YAxis,
|
|
1028
|
-
{
|
|
1029
|
-
yAxisId: "counts",
|
|
1030
|
-
allowDecimals: false,
|
|
1031
|
-
tickFormatter: (value) => Math.floor(Number(value)).toLocaleString()
|
|
1032
|
-
}
|
|
1033
|
-
),
|
|
1034
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1035
|
-
recharts.YAxis,
|
|
1036
|
-
{
|
|
1037
|
-
yAxisId: "revenue",
|
|
1038
|
-
orientation: "right",
|
|
1039
|
-
tickFormatter: (value) => formatCurrency(Number(value))
|
|
1040
|
-
}
|
|
1041
|
-
),
|
|
1042
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(TrendTooltip, {}) }),
|
|
1043
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
1044
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1045
|
-
recharts.Line,
|
|
1046
|
-
{
|
|
1047
|
-
yAxisId: "counts",
|
|
1048
|
-
type: "monotone",
|
|
1049
|
-
dataKey: "units_sold",
|
|
1050
|
-
name: "Units sold",
|
|
1051
|
-
stroke: SALES_COLOR,
|
|
1052
|
-
strokeWidth: 2,
|
|
1053
|
-
dot: false
|
|
1054
|
-
}
|
|
1055
|
-
),
|
|
1056
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1057
|
-
recharts.Line,
|
|
1058
|
-
{
|
|
1059
|
-
yAxisId: "counts",
|
|
1060
|
-
type: "monotone",
|
|
1061
|
-
dataKey: "views",
|
|
1062
|
-
name: "Views",
|
|
1063
|
-
stroke: VIEWS_COLOR,
|
|
1064
|
-
strokeWidth: 2,
|
|
1065
|
-
dot: false
|
|
1066
|
-
}
|
|
1067
|
-
),
|
|
1068
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1069
|
-
recharts.Line,
|
|
1070
|
-
{
|
|
1071
|
-
yAxisId: "revenue",
|
|
1072
|
-
type: "monotone",
|
|
1073
|
-
dataKey: "revenue",
|
|
1074
|
-
name: "Revenue",
|
|
1075
|
-
stroke: REVENUE_COLOR,
|
|
1076
|
-
strokeWidth: 2,
|
|
1077
|
-
dot: false
|
|
1078
|
-
}
|
|
1079
|
-
)
|
|
1080
|
-
] }) }) })
|
|
1081
|
-
] }),
|
|
1082
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-2 gap-4", children: [
|
|
1083
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1084
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
1085
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1086
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Best sellers" }),
|
|
1087
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mt-1", children: "Ranked by units sold." })
|
|
1088
|
-
] }),
|
|
1089
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2", children: TOP_SELLER_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1090
|
-
ui.Button,
|
|
1091
|
-
{
|
|
1092
|
-
variant: topSellerPeriod === period.value ? "secondary" : "transparent",
|
|
1093
|
-
size: "small",
|
|
1094
|
-
onClick: () => setTopSellerPeriod(period.value),
|
|
1095
|
-
children: period.label
|
|
1096
|
-
},
|
|
1097
|
-
period.value
|
|
1098
|
-
)) })
|
|
1099
1168
|
] }),
|
|
1100
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1101
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3",
|
|
1102
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, {
|
|
1169
|
+
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: [
|
|
1170
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
|
|
1171
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "label" }),
|
|
1103
1172
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1104
1173
|
recharts.YAxis,
|
|
1105
1174
|
{
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
tickLine: false
|
|
1175
|
+
yAxisId: "counts",
|
|
1176
|
+
allowDecimals: false,
|
|
1177
|
+
tickFormatter: (value) => Math.floor(Number(value)).toLocaleString()
|
|
1110
1178
|
}
|
|
1111
1179
|
),
|
|
1112
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {}) }),
|
|
1113
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "units_sold", fill: SALES_COLOR, radius: [0, 6, 6, 0] })
|
|
1114
|
-
] }) }) })
|
|
1115
|
-
] }),
|
|
1116
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1117
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-4", children: "Top seller breakdown" }),
|
|
1118
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1119
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1120
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
1121
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Units" }),
|
|
1122
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Orders" }),
|
|
1123
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Revenue" })
|
|
1124
|
-
] }) }),
|
|
1125
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
1126
|
-
((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 8).map((product) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1127
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
1128
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
1129
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.order_count.toLocaleString() }),
|
|
1130
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatCurrency(product.revenue) })
|
|
1131
|
-
] }, product.product_id)),
|
|
1132
|
-
!topSellersLoading && (((_b = topSellers == null ? void 0 : topSellers.products) == null ? void 0 : _b.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1133
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: "No best seller data yet." }),
|
|
1134
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1135
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1136
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
1137
|
-
] })
|
|
1138
|
-
] })
|
|
1139
|
-
] })
|
|
1140
|
-
] })
|
|
1141
|
-
] }),
|
|
1142
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-2 gap-4", children: [
|
|
1143
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1144
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-1", children: "Most viewed products" }),
|
|
1145
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mb-4", children: "View activity is sourced from the existing product-helper tracking module." }),
|
|
1146
|
-
/* @__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: [
|
|
1147
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", horizontal: false }),
|
|
1148
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", allowDecimals: false }),
|
|
1149
1180
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1150
1181
|
recharts.YAxis,
|
|
1151
1182
|
{
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
tickLine: false
|
|
1183
|
+
yAxisId: "revenue",
|
|
1184
|
+
orientation: "right",
|
|
1185
|
+
tickFormatter: (value) => formatCurrency(Number(value))
|
|
1156
1186
|
}
|
|
1157
1187
|
),
|
|
1158
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1159
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1188
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(TrendTooltip, {}) }),
|
|
1189
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
1190
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1191
|
+
recharts.Line,
|
|
1192
|
+
{
|
|
1193
|
+
yAxisId: "counts",
|
|
1194
|
+
type: "monotone",
|
|
1195
|
+
dataKey: "units_sold",
|
|
1196
|
+
name: "Units sold",
|
|
1197
|
+
stroke: SALES_COLOR,
|
|
1198
|
+
strokeWidth: 2,
|
|
1199
|
+
dot: false
|
|
1200
|
+
}
|
|
1201
|
+
),
|
|
1202
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1203
|
+
recharts.Line,
|
|
1204
|
+
{
|
|
1205
|
+
yAxisId: "counts",
|
|
1206
|
+
type: "monotone",
|
|
1207
|
+
dataKey: "views",
|
|
1208
|
+
name: "Views",
|
|
1209
|
+
stroke: VIEWS_COLOR,
|
|
1210
|
+
strokeWidth: 2,
|
|
1211
|
+
dot: false
|
|
1212
|
+
}
|
|
1213
|
+
),
|
|
1214
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1215
|
+
recharts.Line,
|
|
1216
|
+
{
|
|
1217
|
+
yAxisId: "revenue",
|
|
1218
|
+
type: "monotone",
|
|
1219
|
+
dataKey: "revenue",
|
|
1220
|
+
name: "Revenue",
|
|
1221
|
+
stroke: REVENUE_COLOR,
|
|
1222
|
+
strokeWidth: 2,
|
|
1223
|
+
dot: false
|
|
1224
|
+
}
|
|
1225
|
+
)
|
|
1226
|
+
] }) }) }) })
|
|
1227
|
+
}
|
|
1228
|
+
),
|
|
1229
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [
|
|
1230
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1231
|
+
AnalyticsSection,
|
|
1232
|
+
{
|
|
1233
|
+
title: "Best sellers",
|
|
1234
|
+
description: "Top products by revenue.",
|
|
1235
|
+
actions: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2", children: TOP_SELLER_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1236
|
+
ui.Button,
|
|
1237
|
+
{
|
|
1238
|
+
variant: topSellerPeriod === period.value ? "secondary" : "transparent",
|
|
1239
|
+
size: "small",
|
|
1240
|
+
onClick: () => setTopSellerPeriod(period.value),
|
|
1241
|
+
children: period.label
|
|
1242
|
+
},
|
|
1243
|
+
period.value
|
|
1244
|
+
)) }),
|
|
1245
|
+
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: [
|
|
1246
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", horizontal: false }),
|
|
1247
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1248
|
+
recharts.XAxis,
|
|
1249
|
+
{
|
|
1250
|
+
type: "number",
|
|
1251
|
+
allowDecimals: false,
|
|
1252
|
+
tickFormatter: (value) => formatCurrency(Number(value))
|
|
1253
|
+
}
|
|
1254
|
+
),
|
|
1255
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1256
|
+
recharts.YAxis,
|
|
1257
|
+
{
|
|
1258
|
+
type: "category",
|
|
1259
|
+
dataKey: "product_title",
|
|
1260
|
+
width: 160,
|
|
1261
|
+
tickLine: false
|
|
1262
|
+
}
|
|
1263
|
+
),
|
|
1264
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, { showViews: false }) }),
|
|
1265
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "revenue", radius: [0, 6, 6, 0], children: topSellerChartData.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1266
|
+
recharts.Cell,
|
|
1267
|
+
{
|
|
1268
|
+
fill: entry.isBestSeller ? REVENUE_COLOR : REVENUE_MUTED_COLOR
|
|
1269
|
+
},
|
|
1270
|
+
entry.product_id
|
|
1271
|
+
)) })
|
|
1272
|
+
] }) }) }) })
|
|
1273
|
+
}
|
|
1274
|
+
),
|
|
1275
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1276
|
+
AnalyticsSection,
|
|
1277
|
+
{
|
|
1278
|
+
title: "Top seller breakdown",
|
|
1279
|
+
description: "Products ranked by revenue.",
|
|
1280
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTableSurface, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1281
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1282
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
1283
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Units" }),
|
|
1284
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Orders" }),
|
|
1285
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Revenue" })
|
|
1286
|
+
] }) }),
|
|
1287
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
1288
|
+
((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 8).map((product) => {
|
|
1289
|
+
var _a2, _b2;
|
|
1290
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1291
|
+
ui.Table.Row,
|
|
1292
|
+
{
|
|
1293
|
+
className: product.product_id === ((_b2 = (_a2 = topSellers == null ? void 0 : topSellers.products) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.product_id) ? "bg-ui-bg-subtle" : void 0,
|
|
1294
|
+
children: [
|
|
1295
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
1296
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
1297
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.order_count.toLocaleString() }),
|
|
1298
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatCurrency(product.revenue) })
|
|
1299
|
+
]
|
|
1300
|
+
},
|
|
1301
|
+
product.product_id
|
|
1302
|
+
);
|
|
1303
|
+
}),
|
|
1304
|
+
!topSellersLoading && (((_b = topSellers == null ? void 0 : topSellers.products) == null ? void 0 : _b.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1305
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: "No best seller data yet." }),
|
|
1306
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1307
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1308
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
1309
|
+
] })
|
|
1183
1310
|
] })
|
|
1184
|
-
] })
|
|
1185
|
-
|
|
1186
|
-
|
|
1311
|
+
] }) })
|
|
1312
|
+
}
|
|
1313
|
+
)
|
|
1314
|
+
] }),
|
|
1315
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [
|
|
1316
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1317
|
+
AnalyticsSection,
|
|
1318
|
+
{
|
|
1319
|
+
title: "Most viewed products",
|
|
1320
|
+
description: "View activity is sourced from the existing product-helper tracking module.",
|
|
1321
|
+
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: [
|
|
1322
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", horizontal: false }),
|
|
1323
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", allowDecimals: false }),
|
|
1324
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1325
|
+
recharts.YAxis,
|
|
1326
|
+
{
|
|
1327
|
+
type: "category",
|
|
1328
|
+
dataKey: "product_title",
|
|
1329
|
+
width: 160,
|
|
1330
|
+
tickLine: false
|
|
1331
|
+
}
|
|
1332
|
+
),
|
|
1333
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {}) }),
|
|
1334
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "total_views", fill: VIEWS_COLOR, radius: [0, 6, 6, 0] })
|
|
1335
|
+
] }) }) }) })
|
|
1336
|
+
}
|
|
1337
|
+
),
|
|
1338
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1339
|
+
AnalyticsSection,
|
|
1340
|
+
{
|
|
1341
|
+
title: "View-to-sales opportunities",
|
|
1342
|
+
description: "Highlight products that attract attention but convert less efficiently.",
|
|
1343
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTableSurface, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1344
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1345
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
1346
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Views" }),
|
|
1347
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Units" }),
|
|
1348
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Views / unit" })
|
|
1349
|
+
] }) }),
|
|
1350
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
1351
|
+
((performance == null ? void 0 : performance.viewOpportunities) ?? []).slice(0, 8).map((product) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1352
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
1353
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.total_views.toLocaleString() }),
|
|
1354
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
1355
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatRatio(product.views_per_unit) })
|
|
1356
|
+
] }, product.product_id)),
|
|
1357
|
+
!performanceLoading && !performanceError && (((_c = performance == null ? void 0 : performance.viewOpportunities) == null ? void 0 : _c.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1358
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: "No product view opportunities yet." }),
|
|
1359
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1360
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1361
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
1362
|
+
] })
|
|
1363
|
+
] })
|
|
1364
|
+
] }) })
|
|
1365
|
+
}
|
|
1366
|
+
)
|
|
1187
1367
|
] })
|
|
1188
1368
|
] });
|
|
1189
1369
|
}
|
|
@@ -1194,22 +1374,32 @@ const ANALYTICS_MODULES = [
|
|
|
1194
1374
|
];
|
|
1195
1375
|
const AnalyticsPage = () => {
|
|
1196
1376
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
1197
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-
|
|
1198
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1377
|
+
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: [
|
|
1378
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "overflow-hidden rounded-2xl border border-ui-border-base bg-ui-bg-base p-0 shadow-sm", children: [
|
|
1379
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-5 px-5 py-5 xl:flex-row xl:items-end xl:justify-between", children: [
|
|
1380
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1381
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "tracking-tight", children: "Analytics" }),
|
|
1382
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Review orders, customers, and products in one organized workspace." })
|
|
1383
|
+
] }),
|
|
1384
|
+
/* @__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(
|
|
1385
|
+
ui.Button,
|
|
1386
|
+
{
|
|
1387
|
+
variant: activeModule === m.id ? "secondary" : "transparent",
|
|
1388
|
+
size: "small",
|
|
1389
|
+
onClick: () => setActiveModule(m.id),
|
|
1390
|
+
children: m.label
|
|
1391
|
+
},
|
|
1392
|
+
m.id
|
|
1393
|
+
)) })
|
|
1394
|
+
] }),
|
|
1395
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1 bg-ui-bg-subtle" })
|
|
1396
|
+
] }),
|
|
1397
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1398
|
+
activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
|
|
1399
|
+
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {}),
|
|
1400
|
+
activeModule === "products" && /* @__PURE__ */ jsxRuntime.jsx(ProductsDashboard, {})
|
|
1401
|
+
] })
|
|
1402
|
+
] }) }) });
|
|
1213
1403
|
};
|
|
1214
1404
|
const config = adminSdk.defineRouteConfig({
|
|
1215
1405
|
label: "Analytics",
|