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.
@@ -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("div", { className: "space-y-8", children: [
141
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
142
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Orders" }),
143
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
144
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
145
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "analytics-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
146
- /* @__PURE__ */ jsxRuntime.jsx(
147
- "select",
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
- id: "analytics-period",
150
- value: filter,
151
- onChange: (e) => setFilter(e.target.value),
152
- 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",
153
- children: SUMMARY_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
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
- filter !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
158
- "button",
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
- type: "button",
161
- onClick: () => setFilter("all"),
162
- className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
163
- "aria-label": "Show all orders (clear filter)",
164
- children: "Clear filter"
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
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
170
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
171
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Total orders" }),
172
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.totalOrders.toLocaleString() })
173
- ] }),
174
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
175
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Total revenue" }),
176
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency(data.totalRevenue) })
177
- ] }),
178
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
179
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Average order value" }),
180
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency(data.aov) })
181
- ] }),
182
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
183
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Orders today" }),
184
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.ordersToday.toLocaleString() })
185
- ] })
186
- ] }),
187
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-6", children: [
188
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
189
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mb-2", children: "Pending orders" }),
190
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.pendingOrders.toLocaleString() })
191
- ] }),
192
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
193
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mb-2", children: "Avg. fulfillment time (hours)" }),
194
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "" })
195
- ] })
196
- ] }),
197
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
198
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
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(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row flex-wrap gap-6 items-stretch", children: [
216
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", style: { minWidth: "280px" }, children: [
217
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-3", children: "Orders by status" }),
218
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[280px] flex 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: [
219
- /* @__PURE__ */ jsxRuntime.jsx(
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
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
279
- "div",
280
- {
281
- className: "flex items-center justify-center h-full",
282
- role: "status",
283
- "aria-label": "Loading orders over time",
284
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
285
- }
286
- ) : 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(
287
- recharts.LineChart,
288
- {
289
- data: dailyOrders,
290
- margin: { top: 8, right: 8, left: 8, bottom: 8 },
291
- children: [
292
- /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", className: "stroke-ui-border-base" }),
293
- /* @__PURE__ */ jsxRuntime.jsx(
294
- recharts.XAxis,
295
- {
296
- dataKey: "date",
297
- tick: { fontSize: 12 },
298
- tickFormatter: (v, index) => {
299
- const row = dailyOrders[index];
300
- if (row == null ? void 0 : row.label) return row.label;
301
- const d = new Date(v);
302
- return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
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
- /* @__PURE__ */ jsxRuntime.jsx(
307
- recharts.YAxis,
308
- {
309
- tick: { fontSize: 12 },
310
- allowDecimals: false,
311
- domain: [0, "auto"],
312
- ticks: (() => {
313
- const max = Math.max(
314
- 1,
315
- ...dailyOrders.map((d) => d.orders_count ?? 0)
316
- );
317
- const step = max <= 10 ? 1 : Math.ceil(max / 6);
318
- const arr = [];
319
- for (let i = 0; i <= max; i += step) arr.push(i);
320
- if (arr[arr.length - 1] !== max) arr.push(max);
321
- return arr;
322
- })(),
323
- tickFormatter: (v) => String(Math.floor(Number(v) || 0))
324
- }
325
- ),
326
- /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersOverTimeTooltip, {}) }),
327
- /* @__PURE__ */ jsxRuntime.jsx(
328
- recharts.Line,
329
- {
330
- type: "monotone",
331
- dataKey: "orders_count",
332
- name: "Orders",
333
- stroke: "var(--medusa-color-ui-fg-interactive)",
334
- strokeWidth: 2,
335
- dot: { r: 3 }
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("div", { className: "space-y-8", children: [
458
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
459
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Customers" }),
460
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
461
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
462
- /* @__PURE__ */ jsxRuntime.jsx(
463
- ui.Label,
464
- {
465
- htmlFor: "customers-count-period",
466
- className: "text-ui-fg-muted text-sm",
467
- children: "Period (counts)"
468
- }
469
- ),
470
- /* @__PURE__ */ jsxRuntime.jsx(
471
- "select",
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
- id: "customers-count-period",
474
- value: countDays,
475
- onChange: (e) => setCountDays(e.target.value),
476
- 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",
477
- children: COUNT_DAY_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
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
- countDays !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
482
- "button",
483
- {
484
- type: "button",
485
- onClick: () => setCountDays("all"),
486
- className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
487
- "aria-label": "Show all customers (clear filter)",
488
- children: "Clear filter"
489
- }
490
- )
491
- ] })
492
- ] }),
493
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
494
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
495
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Guest customers" }),
496
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.guestCount).toLocaleString() })
497
- ] }),
498
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
499
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Registered customers" }),
500
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.registeredCount).toLocaleString() })
501
- ] }),
502
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
503
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Overall customers" }),
504
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.totalCount).toLocaleString() })
505
- ] }),
506
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
507
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Deleted accounts" }),
508
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: (data.deletedAccountsCount ?? 0).toLocaleString() })
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.jsxs(ui.Container, { className: "p-6", children: [
512
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
513
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Customers over time" }),
514
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
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
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: graphLoading ? /* @__PURE__ */ jsxRuntime.jsx(
536
- "div",
537
- {
538
- className: "flex items-center justify-center h-full",
539
- role: "status",
540
- "aria-label": "Loading customers over time",
541
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
542
- }
543
- ) : 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(
544
- recharts.LineChart,
545
- {
546
- data: series,
547
- margin: { top: 8, right: 8, left: 8, bottom: 8 },
548
- children: [
549
- /* @__PURE__ */ jsxRuntime.jsx(
550
- recharts.CartesianGrid,
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
- strokeDasharray: "3 3",
553
- className: "stroke-ui-border-base"
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
- recharts.XAxis,
1059
+ "select",
558
1060
  {
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()}`;
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
- tick: { fontSize: 12 },
573
- tickFormatter: (v) => String(Math.floor(Number(v))),
574
- allowDecimals: false
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(CustomersOverTimeTooltip, {}) }),
578
- /* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
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.Line,
1294
+ recharts.YAxis,
581
1295
  {
582
- type: "monotone",
583
- dataKey: "registered_count",
584
- name: "Registered",
585
- stroke: REGISTERED_COLOR,
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
- recharts.Line,
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-base", children: /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-6 px-6", children: [
614
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "mb-4", children: "Analytics" }),
615
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 mb-6", children: ANALYTICS_MODULES.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
616
- ui.Button,
617
- {
618
- variant: activeModule === m.id ? "secondary" : "transparent",
619
- size: "small",
620
- onClick: () => setActiveModule(m.id),
621
- children: m.label
622
- },
623
- m.id
624
- )) }),
625
- activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
626
- activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {})
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",