medusa-analytics 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +1729 -591
- package/.medusa/server/src/admin/index.mjs +1732 -594
- package/.medusa/server/src/api/admin/analytics/customers-over-time/route.js +14 -7
- package/.medusa/server/src/api/admin/analytics/customers-summary/route.js +14 -7
- package/.medusa/server/src/api/admin/analytics/fetch-orders-for-analytics.js +34 -3
- package/.medusa/server/src/api/admin/analytics/orders-analytics-helpers.js +64 -0
- package/.medusa/server/src/api/admin/analytics/orders-insights/route.js +199 -0
- package/.medusa/server/src/api/admin/analytics/orders-insights/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/orders-over-time/route.js +120 -113
- package/.medusa/server/src/api/admin/analytics/orders-over-time/types.js +1 -1
- package/.medusa/server/src/api/admin/analytics/repeat-customers/route.js +77 -0
- package/.medusa/server/src/api/admin/analytics/repeat-customers/types.js +3 -0
- package/package.json +1 -1
|
@@ -47,33 +47,40 @@ function AnalyticsDashboardHeader({
|
|
|
47
47
|
title,
|
|
48
48
|
description,
|
|
49
49
|
actions,
|
|
50
|
-
variant = "default"
|
|
50
|
+
variant = "default",
|
|
51
|
+
actionsBare = false,
|
|
52
|
+
appearance = "card"
|
|
51
53
|
}) {
|
|
52
54
|
const isPremium = variant === "premium";
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const isInset = appearance === "inset";
|
|
56
|
+
const headerRow = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
57
|
+
"div",
|
|
55
58
|
{
|
|
56
|
-
className: `
|
|
59
|
+
className: `flex flex-col gap-2 px-4 py-2.5 lg:flex-row lg:items-center lg:justify-between ${isPremium ? "border-b border-ui-border-base/80" : "border-b border-ui-border-base"}`.trim(),
|
|
57
60
|
children: [
|
|
58
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
61
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
62
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: isPremium ? "text-lg font-semibold tracking-tight" : "tracking-tight", children: title }),
|
|
63
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted ${isPremium ? "max-w-2xl text-xs leading-snug" : "text-sm"}`.trim(), children: description }) : null
|
|
64
|
+
] }),
|
|
65
|
+
actions ? actionsBare ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-3 self-start lg:self-auto", children: actions }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
59
66
|
"div",
|
|
60
67
|
{
|
|
61
|
-
className: `flex
|
|
62
|
-
children:
|
|
63
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
64
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: isPremium ? "text-lg font-semibold tracking-tight" : "tracking-tight", children: title }),
|
|
65
|
-
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted ${isPremium ? "max-w-2xl text-xs leading-snug" : "text-sm"}`.trim(), children: description }) : null
|
|
66
|
-
] }),
|
|
67
|
-
actions ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
68
|
-
"div",
|
|
69
|
-
{
|
|
70
|
-
className: `flex self-start rounded-xl border px-2 py-1.5 lg:self-auto ${isPremium ? "border-ui-border-strong bg-ui-bg-subtle shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]" : "border-ui-border-base bg-ui-bg-subtle"}`.trim(),
|
|
71
|
-
children: actions
|
|
72
|
-
}
|
|
73
|
-
) : null
|
|
74
|
-
]
|
|
68
|
+
className: `flex self-start rounded-xl border px-2 py-1.5 lg:self-auto ${isPremium ? "border-ui-border-strong bg-ui-bg-subtle shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]" : "border-ui-border-base bg-ui-bg-subtle"}`.trim(),
|
|
69
|
+
children: actions
|
|
75
70
|
}
|
|
76
|
-
)
|
|
71
|
+
) : null
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
if (isInset) {
|
|
76
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-0 bg-transparent p-0 shadow-none", children: headerRow });
|
|
77
|
+
}
|
|
78
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
79
|
+
ui.Container,
|
|
80
|
+
{
|
|
81
|
+
className: `overflow-hidden rounded-2xl border bg-ui-bg-base p-0 ${isPremium ? "border-ui-border-strong shadow-sm" : "border-ui-border-base shadow-sm"}`.trim(),
|
|
82
|
+
children: [
|
|
83
|
+
headerRow,
|
|
77
84
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
78
85
|
"div",
|
|
79
86
|
{
|
|
@@ -144,23 +151,24 @@ function AnalyticsSection({
|
|
|
144
151
|
actions,
|
|
145
152
|
children,
|
|
146
153
|
className,
|
|
147
|
-
variant = "default"
|
|
154
|
+
variant = "default",
|
|
155
|
+
actionsBare = false
|
|
148
156
|
}) {
|
|
149
157
|
const isAtlas = variant === "atlas";
|
|
150
158
|
const isPremium = variant !== "default" && !isAtlas;
|
|
151
159
|
const shellClass = isAtlas ? "overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-base shadow-sm" : `overflow-hidden rounded-2xl border bg-ui-bg-base p-0 ${isPremium ? "border-ui-border-strong shadow-sm" : "border-ui-border-base shadow-sm"}`.trim();
|
|
152
|
-
const headerClass = isAtlas ? "flex flex-col gap-1
|
|
160
|
+
const headerClass = isAtlas ? "flex flex-col gap-1 border-b border-ui-border-base bg-ui-bg-subtle/40 px-3 py-2 lg:flex-row lg:items-center lg:justify-between" : `flex flex-col gap-2 px-4 py-2.5 lg:flex-row lg:items-center lg:justify-between ${isPremium ? "border-b border-ui-border-base/80" : "border-b border-ui-border-base"}`.trim();
|
|
153
161
|
const titleClass = isAtlas ? "text-sm font-semibold tracking-tight text-ui-fg-base" : isPremium ? "text-base font-semibold tracking-tight" : "tracking-tight";
|
|
154
162
|
const descClass = isAtlas ? "max-w-2xl text-[11px] leading-snug text-ui-fg-muted" : isPremium ? "max-w-2xl text-xs leading-snug" : "text-sm";
|
|
155
163
|
const actionsShellClass = isAtlas ? "flex shrink-0 flex-wrap items-center gap-1.5 rounded-md border border-ui-border-base bg-ui-bg-base px-2 py-1" : `flex shrink-0 flex-wrap items-center gap-1.5 rounded-xl border px-2 py-1.5 ${isPremium ? "border-ui-border-strong bg-ui-bg-subtle shadow-[inset_0_1px_0_rgba(255,255,255,0.32)]" : "border-ui-border-base bg-ui-bg-subtle"}`.trim();
|
|
156
|
-
const bodyClass = isAtlas ? "p-
|
|
164
|
+
const bodyClass = isAtlas ? "p-2" : variant === "hero" ? "px-4 py-3" : "px-4 py-3";
|
|
157
165
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: `${shellClass} ${className ?? ""}`.trim(), children: [
|
|
158
166
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: headerClass, children: [
|
|
159
167
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-0.5", children: [
|
|
160
168
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: titleClass, children: title }),
|
|
161
169
|
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted ${descClass}`.trim(), children: description }) : null
|
|
162
170
|
] }),
|
|
163
|
-
actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: actionsShellClass, children: actions }) : null
|
|
171
|
+
actions ? actionsBare ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: actionsShellClass, children: actions }) : null
|
|
164
172
|
] }),
|
|
165
173
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: bodyClass, children })
|
|
166
174
|
] });
|
|
@@ -170,7 +178,7 @@ function AnalyticsChartSurface({
|
|
|
170
178
|
className,
|
|
171
179
|
variant = "default"
|
|
172
180
|
}) {
|
|
173
|
-
const surfaceClassName = variant === "atlas" ? "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 p-2
|
|
181
|
+
const surfaceClassName = variant === "atlas" ? "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 p-2 shadow-sm" : variant === "glass" ? "rounded-xl border border-ui-border-strong bg-[linear-gradient(180deg,rgba(255,255,255,0.12),rgba(255,255,255,0.03))] p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.22)]" : variant === "panel" ? "rounded-xl border border-ui-border-base/90 bg-ui-bg-base p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]" : "rounded-lg border border-ui-border-base bg-ui-bg-subtle p-2 shadow-inner";
|
|
174
182
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
175
183
|
"div",
|
|
176
184
|
{
|
|
@@ -190,7 +198,21 @@ function AnalyticsTableSurface({ children, className }) {
|
|
|
190
198
|
}
|
|
191
199
|
function isDailyOrderRow(value) {
|
|
192
200
|
if (typeof value !== "object" || value === null) return false;
|
|
193
|
-
|
|
201
|
+
const v = value;
|
|
202
|
+
return typeof v.date === "string" && typeof v.orders_count === "number" && typeof v.total_revenue === "number";
|
|
203
|
+
}
|
|
204
|
+
function nonCancelledRevenue(d) {
|
|
205
|
+
const delivered = d.revenue_delivered ?? 0;
|
|
206
|
+
const open = d.revenue_open ?? 0;
|
|
207
|
+
if (delivered > 0 || open > 0 || (d.revenue_cancelled ?? 0) > 0) {
|
|
208
|
+
return delivered + open;
|
|
209
|
+
}
|
|
210
|
+
return Math.max(0, d.total_revenue - (d.revenue_cancelled ?? 0));
|
|
211
|
+
}
|
|
212
|
+
function bucketAov(d) {
|
|
213
|
+
const ordNc = d.orders_count - d.cancelled_count;
|
|
214
|
+
if (ordNc <= 0) return 0;
|
|
215
|
+
return nonCancelledRevenue(d) / ordNc;
|
|
194
216
|
}
|
|
195
217
|
function isOrdersTodayPoint(value) {
|
|
196
218
|
return isDailyOrderRow(value) && "isToday" in value && typeof value.isToday === "boolean";
|
|
@@ -220,6 +242,12 @@ function formatChartLabel(value) {
|
|
|
220
242
|
function formatPercent(value) {
|
|
221
243
|
return `${value.toFixed(1)}%`;
|
|
222
244
|
}
|
|
245
|
+
function ordersCountFromPiePayload(payload) {
|
|
246
|
+
if (typeof payload !== "object" || payload === null) return 0;
|
|
247
|
+
if (!("orders_count" in payload)) return 0;
|
|
248
|
+
const c = payload.orders_count;
|
|
249
|
+
return Math.floor(Number(c) || 0);
|
|
250
|
+
}
|
|
223
251
|
function formatShortNumber(value) {
|
|
224
252
|
return new Intl.NumberFormat("en-IN", {
|
|
225
253
|
notation: "compact",
|
|
@@ -328,33 +356,102 @@ const KPI_ICON_BG = {
|
|
|
328
356
|
purple: "bg-violet-500/20",
|
|
329
357
|
amber: "bg-amber-500/20"
|
|
330
358
|
};
|
|
359
|
+
const KPI_ICONS = {
|
|
360
|
+
green: icons.CurrencyDollar,
|
|
361
|
+
blue: icons.ShoppingCart,
|
|
362
|
+
purple: icons.TruckFast,
|
|
363
|
+
amber: icons.Cash
|
|
364
|
+
};
|
|
365
|
+
const PIE_PALETTE = ["#6366F1", "#94A3B8", "#10B981", "#F59E0B"];
|
|
366
|
+
function TrafficRevenueDonut({
|
|
367
|
+
traffic,
|
|
368
|
+
compact = false
|
|
369
|
+
}) {
|
|
370
|
+
const innerRadius = compact ? 28 : 34;
|
|
371
|
+
const outerRadius = compact ? 44 : 54;
|
|
372
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: compact ? "h-[118px] w-full" : "h-[152px] w-full shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 2, right: 2, left: 2, bottom: 2 }, children: [
|
|
373
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
374
|
+
recharts.Pie,
|
|
375
|
+
{
|
|
376
|
+
data: traffic,
|
|
377
|
+
dataKey: "revenue",
|
|
378
|
+
nameKey: "label",
|
|
379
|
+
cx: "50%",
|
|
380
|
+
cy: "48%",
|
|
381
|
+
innerRadius,
|
|
382
|
+
outerRadius,
|
|
383
|
+
paddingAngle: 2,
|
|
384
|
+
children: traffic.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: PIE_PALETTE[i % PIE_PALETTE.length] }, `traffic-slice-${i}`))
|
|
385
|
+
}
|
|
386
|
+
),
|
|
387
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
388
|
+
recharts.Tooltip,
|
|
389
|
+
{
|
|
390
|
+
content: ({ active, payload }) => {
|
|
391
|
+
var _a, _b, _c;
|
|
392
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(((_a = payload[0]) == null ? void 0 : _a.name) ?? ""), children: [
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
394
|
+
"Revenue:",
|
|
395
|
+
" ",
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(Number((_b = payload[0]) == null ? void 0 : _b.value) || 0) })
|
|
397
|
+
] }),
|
|
398
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
399
|
+
"Orders:",
|
|
400
|
+
" ",
|
|
401
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: ordersCountFromPiePayload((_c = payload[0]) == null ? void 0 : _c.payload).toLocaleString() })
|
|
402
|
+
] })
|
|
403
|
+
] }) : null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
408
|
+
recharts.Legend,
|
|
409
|
+
{
|
|
410
|
+
wrapperStyle: { fontSize: compact ? 8 : 9, paddingTop: 0 },
|
|
411
|
+
verticalAlign: "bottom",
|
|
412
|
+
height: compact ? 18 : 22,
|
|
413
|
+
iconType: "circle"
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
] }) }) });
|
|
417
|
+
}
|
|
418
|
+
function TrafficSourcesOverviewCard({
|
|
419
|
+
traffic,
|
|
420
|
+
loading,
|
|
421
|
+
error,
|
|
422
|
+
totalRevenue,
|
|
423
|
+
windowLabel
|
|
424
|
+
}) {
|
|
425
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-[132px] flex-col rounded-2xl border border-ui-border-base p-3 shadow-sm", children: [
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs font-medium text-ui-fg-base", children: "Traffic sources" }),
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "mb-1 text-[10px] leading-snug text-ui-fg-muted", children: [
|
|
428
|
+
"Revenue share · ",
|
|
429
|
+
windowLabel
|
|
430
|
+
] }),
|
|
431
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center px-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-[10px] text-ui-fg-danger", children: error }) }) : traffic.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center px-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-[10px] leading-snug text-ui-fg-muted", children: "No traffic breakdown for this window." }) }) : totalRevenue <= 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center px-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-[10px] leading-snug text-ui-fg-muted", children: "No recorded revenue in this window." }) }) : /* @__PURE__ */ jsxRuntime.jsx(TrafficRevenueDonut, { traffic, compact: true }) })
|
|
432
|
+
] });
|
|
433
|
+
}
|
|
331
434
|
function AtlasKpiCard({
|
|
332
|
-
index,
|
|
333
435
|
label,
|
|
334
436
|
value,
|
|
335
437
|
helper,
|
|
336
438
|
accent
|
|
337
439
|
}) {
|
|
338
|
-
|
|
440
|
+
const Icon = KPI_ICONS[accent];
|
|
441
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-[112px] min-w-0 flex-col rounded-xl border border-ui-border-base/80 bg-ui-bg-subtle/25 p-4 shadow-sm", children: [
|
|
339
442
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
340
|
-
/* @__PURE__ */ jsxRuntime.
|
|
341
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-ui-fg-base", children: [
|
|
342
|
-
index,
|
|
343
|
-
"."
|
|
344
|
-
] }),
|
|
345
|
-
" ",
|
|
346
|
-
label
|
|
347
|
-
] }),
|
|
443
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "min-w-0 flex-1 text-left text-xs font-medium leading-snug text-ui-fg-base", children: label }),
|
|
348
444
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
349
445
|
"div",
|
|
350
446
|
{
|
|
351
|
-
className: `h-
|
|
352
|
-
"aria-hidden": true
|
|
447
|
+
className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${KPI_ICON_BG[accent]}`,
|
|
448
|
+
"aria-hidden": true,
|
|
449
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-ui-fg-base" })
|
|
353
450
|
}
|
|
354
451
|
)
|
|
355
452
|
] }),
|
|
356
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-
|
|
357
|
-
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-
|
|
453
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 text-xl font-semibold tabular-nums tracking-tight text-ui-fg-base sm:text-2xl", children: value }),
|
|
454
|
+
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-[11px] leading-snug text-ui-fg-muted", children: helper }) : null
|
|
358
455
|
] });
|
|
359
456
|
}
|
|
360
457
|
const SUMMARY_PERIODS$1 = [
|
|
@@ -379,32 +476,33 @@ const STATUS_BAR_COLORS = {
|
|
|
379
476
|
orders: "#38BDF8",
|
|
380
477
|
revenue: "#D946EF"
|
|
381
478
|
};
|
|
382
|
-
const
|
|
479
|
+
const CHART_AXIS_TICK = { fontSize: 10, fill: "#e5e7eb" };
|
|
480
|
+
const CHART_AXIS_TICK_SM = { fontSize: 9, fill: "#e5e7eb" };
|
|
481
|
+
const CHART_AXIS_LINE = "#94a3b8";
|
|
482
|
+
const SELECT_CLASS_NAME = "h-9 min-w-[132px] cursor-pointer appearance-none rounded-full border border-ui-border-base bg-ui-bg-base py-1.5 pl-3 pr-8 text-xs text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive";
|
|
383
483
|
function EmptyAnalyticsPanel({
|
|
384
484
|
title,
|
|
385
485
|
description
|
|
386
486
|
}) {
|
|
387
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
388
|
-
/* @__PURE__ */ jsxRuntime.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
] }),
|
|
392
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base/80 px-2.5 py-2", children: [
|
|
393
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Empty for now" }),
|
|
394
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base mt-0.5 text-[11px] leading-snug", children: "Reserved for live analytics when data is available." })
|
|
395
|
-
] })
|
|
396
|
-
] }) });
|
|
487
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-[88px] flex-col gap-1.5 rounded-lg border border-dashed border-ui-border-base px-3 py-3", children: [
|
|
488
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-xs font-semibold", children: title }),
|
|
489
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[11px] leading-snug", children: description })
|
|
490
|
+
] });
|
|
397
491
|
}
|
|
398
492
|
function OrdersDashboard() {
|
|
399
|
-
var _a, _b;
|
|
493
|
+
var _a, _b, _c, _d;
|
|
400
494
|
const [data, setData] = react.useState(null);
|
|
401
495
|
const [loading, setLoading] = react.useState(true);
|
|
402
496
|
const [error, setError] = react.useState(null);
|
|
403
497
|
const [filter, setFilter] = react.useState("all");
|
|
404
498
|
const [dailyOrders, setDailyOrders] = react.useState([]);
|
|
499
|
+
const [previousPeriodDailyOrders, setPreviousPeriodDailyOrders] = react.useState([]);
|
|
405
500
|
const [overTimePeriod, setOverTimePeriod] = react.useState("one_week");
|
|
406
501
|
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
407
502
|
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
503
|
+
const [ordersInsights, setOrdersInsights] = react.useState(null);
|
|
504
|
+
const [insightsLoading, setInsightsLoading] = react.useState(true);
|
|
505
|
+
const [insightsError, setInsightsError] = react.useState(null);
|
|
408
506
|
const [todayContext, setTodayContext] = react.useState([]);
|
|
409
507
|
const [todayContextLoading, setTodayContextLoading] = react.useState(true);
|
|
410
508
|
const [todayContextError, setTodayContextError] = react.useState(null);
|
|
@@ -436,7 +534,10 @@ function OrdersDashboard() {
|
|
|
436
534
|
if (!res.ok) throw new Error(res.statusText);
|
|
437
535
|
return res.json();
|
|
438
536
|
}).then((body) => {
|
|
439
|
-
if (!cancelled)
|
|
537
|
+
if (!cancelled) {
|
|
538
|
+
setDailyOrders(body.dailyOrders ?? []);
|
|
539
|
+
setPreviousPeriodDailyOrders(body.previousPeriodDailyOrders ?? []);
|
|
540
|
+
}
|
|
440
541
|
}).catch((e) => {
|
|
441
542
|
if (!cancelled) setOverTimeError(e instanceof Error ? e.message : String(e));
|
|
442
543
|
}).finally(() => {
|
|
@@ -446,6 +547,31 @@ function OrdersDashboard() {
|
|
|
446
547
|
cancelled = true;
|
|
447
548
|
};
|
|
448
549
|
}, [overTimePeriod]);
|
|
550
|
+
const insightsWindowDays = react.useMemo(() => {
|
|
551
|
+
if (overTimePeriod === "one_week") return 7;
|
|
552
|
+
if (overTimePeriod === "one_month") return 30;
|
|
553
|
+
return 365;
|
|
554
|
+
}, [overTimePeriod]);
|
|
555
|
+
react.useEffect(() => {
|
|
556
|
+
let cancelled = false;
|
|
557
|
+
setInsightsLoading(true);
|
|
558
|
+
setInsightsError(null);
|
|
559
|
+
fetch(`/admin/analytics/orders-insights?days=${insightsWindowDays}`, {
|
|
560
|
+
credentials: "include"
|
|
561
|
+
}).then((res) => {
|
|
562
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
563
|
+
return res.json();
|
|
564
|
+
}).then((body) => {
|
|
565
|
+
if (!cancelled) setOrdersInsights(body);
|
|
566
|
+
}).catch((e) => {
|
|
567
|
+
if (!cancelled) setInsightsError(e instanceof Error ? e.message : String(e));
|
|
568
|
+
}).finally(() => {
|
|
569
|
+
if (!cancelled) setInsightsLoading(false);
|
|
570
|
+
});
|
|
571
|
+
return () => {
|
|
572
|
+
cancelled = true;
|
|
573
|
+
};
|
|
574
|
+
}, [insightsWindowDays]);
|
|
449
575
|
react.useEffect(() => {
|
|
450
576
|
let cancelled = false;
|
|
451
577
|
setTodayContextLoading(true);
|
|
@@ -544,6 +670,86 @@ function OrdersDashboard() {
|
|
|
544
670
|
const peakRevenuePoint = dailyOrders.length > 0 ? dailyOrders.reduce(
|
|
545
671
|
(peak, point) => point.total_revenue > peak.total_revenue ? point : peak
|
|
546
672
|
) : null;
|
|
673
|
+
const previousPeriodRevenueTotal = react.useMemo(
|
|
674
|
+
() => previousPeriodDailyOrders.reduce((s, d) => s + d.total_revenue, 0),
|
|
675
|
+
[previousPeriodDailyOrders]
|
|
676
|
+
);
|
|
677
|
+
const previousPeriodOrdersTotal = react.useMemo(
|
|
678
|
+
() => previousPeriodDailyOrders.reduce((s, d) => s + d.orders_count, 0),
|
|
679
|
+
[previousPeriodDailyOrders]
|
|
680
|
+
);
|
|
681
|
+
const revenueVsPreviousPercent = previousPeriodRevenueTotal > 0 ? (trendRevenueTotal - previousPeriodRevenueTotal) / previousPeriodRevenueTotal * 100 : null;
|
|
682
|
+
const ordersVsPreviousPercent = previousPeriodOrdersTotal > 0 ? (trendOrdersTotal - previousPeriodOrdersTotal) / previousPeriodOrdersTotal * 100 : null;
|
|
683
|
+
const trendRowsDetailed = react.useMemo(() => {
|
|
684
|
+
return dailyOrders.map((d, i) => {
|
|
685
|
+
const prev = previousPeriodDailyOrders[i];
|
|
686
|
+
return {
|
|
687
|
+
...d,
|
|
688
|
+
aov: bucketAov(d),
|
|
689
|
+
prev_revenue: (prev == null ? void 0 : prev.total_revenue) ?? 0,
|
|
690
|
+
prev_orders: (prev == null ? void 0 : prev.orders_count) ?? 0,
|
|
691
|
+
prev_aov: prev ? bucketAov(prev) : 0
|
|
692
|
+
};
|
|
693
|
+
});
|
|
694
|
+
}, [dailyOrders, previousPeriodDailyOrders]);
|
|
695
|
+
const hourlyChartRows = react.useMemo(() => {
|
|
696
|
+
if (!ordersInsights) return [];
|
|
697
|
+
return ordersInsights.byHour.map((h) => ({
|
|
698
|
+
label: `${String(h.hour).padStart(2, "0")}:00`,
|
|
699
|
+
hour: h.hour,
|
|
700
|
+
orders_count: h.orders_count,
|
|
701
|
+
revenue: h.revenue
|
|
702
|
+
}));
|
|
703
|
+
}, [ordersInsights]);
|
|
704
|
+
const weekdayChartRows = react.useMemo(() => {
|
|
705
|
+
if (!ordersInsights) return [];
|
|
706
|
+
return ordersInsights.byWeekday.map((w) => ({
|
|
707
|
+
label: w.label,
|
|
708
|
+
orders_count: w.orders_count,
|
|
709
|
+
revenue: w.revenue
|
|
710
|
+
}));
|
|
711
|
+
}, [ordersInsights]);
|
|
712
|
+
const ordersVsDraftsPie = react.useMemo(() => {
|
|
713
|
+
var _a2;
|
|
714
|
+
if (!ordersInsights) return [];
|
|
715
|
+
const placed = ((_a2 = ordersInsights.funnel[0]) == null ? void 0 : _a2.count) ?? 0;
|
|
716
|
+
const draftsCount = ordersInsights.drafts.available === true ? ordersInsights.drafts.count : 0;
|
|
717
|
+
return [
|
|
718
|
+
{ name: "Orders placed (window)", value: placed, key: "placed" },
|
|
719
|
+
{
|
|
720
|
+
name: ordersInsights.drafts.available === true ? "Open draft orders (snapshot)" : "Draft orders (unavailable)",
|
|
721
|
+
value: draftsCount,
|
|
722
|
+
key: "drafts"
|
|
723
|
+
}
|
|
724
|
+
];
|
|
725
|
+
}, [ordersInsights]);
|
|
726
|
+
const funnelTimeSeriesRows = react.useMemo(() => {
|
|
727
|
+
return dailyOrders.map((d) => {
|
|
728
|
+
const placed = d.orders_count;
|
|
729
|
+
const notCancelled = Math.max(0, d.orders_count - d.cancelled_count);
|
|
730
|
+
const shipped = d.shipped_count ?? 0;
|
|
731
|
+
const delivered = d.delivered_count;
|
|
732
|
+
const pctNotCancelled = placed > 0 ? notCancelled / placed * 100 : 0;
|
|
733
|
+
const pctShippedOfNc = notCancelled > 0 ? shipped / notCancelled * 100 : 0;
|
|
734
|
+
const pctDeliveredOfShipped = shipped > 0 ? delivered / shipped * 100 : 0;
|
|
735
|
+
return {
|
|
736
|
+
date: d.date,
|
|
737
|
+
label: d.label ?? formatChartLabel(d.date),
|
|
738
|
+
placed,
|
|
739
|
+
not_cancelled: notCancelled,
|
|
740
|
+
shipped,
|
|
741
|
+
delivered,
|
|
742
|
+
pct_not_cancelled: Math.round(pctNotCancelled * 10) / 10,
|
|
743
|
+
pct_shipped_of_active: Math.round(pctShippedOfNc * 10) / 10,
|
|
744
|
+
pct_delivered_of_shipped: Math.round(pctDeliveredOfShipped * 10) / 10
|
|
745
|
+
};
|
|
746
|
+
});
|
|
747
|
+
}, [dailyOrders]);
|
|
748
|
+
const trafficTotalRevenue = react.useMemo(
|
|
749
|
+
() => (ordersInsights == null ? void 0 : ordersInsights.traffic.reduce((sum, t) => sum + (t.revenue || 0), 0)) ?? 0,
|
|
750
|
+
[ordersInsights]
|
|
751
|
+
);
|
|
752
|
+
const insightsRangeLabel = ((_b = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _b.label) ?? "One week";
|
|
547
753
|
const quickPulseMetrics = [
|
|
548
754
|
{
|
|
549
755
|
label: "Range revenue",
|
|
@@ -584,20 +790,22 @@ function OrdersDashboard() {
|
|
|
584
790
|
if (error || !data) {
|
|
585
791
|
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" }) });
|
|
586
792
|
}
|
|
587
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsDashboardShell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-
|
|
793
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsDashboardShell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-hidden rounded-2xl border border-ui-border-base/80 bg-ui-bg-base shadow-sm", children: [
|
|
588
794
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
589
795
|
AnalyticsDashboardHeader,
|
|
590
796
|
{
|
|
591
797
|
variant: "premium",
|
|
798
|
+
appearance: "inset",
|
|
799
|
+
actionsBare: true,
|
|
592
800
|
title: "Orders",
|
|
593
801
|
description: "Revenue, volume, and outcomes in a compact overview sized for a single viewport.",
|
|
594
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
595
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
802
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
803
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
596
804
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
597
805
|
ui.Label,
|
|
598
806
|
{
|
|
599
807
|
htmlFor: "analytics-period",
|
|
600
|
-
className: "text-ui-fg-muted text-xs font-medium",
|
|
808
|
+
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
601
809
|
children: "Period"
|
|
602
810
|
}
|
|
603
811
|
),
|
|
@@ -612,7 +820,7 @@ function OrdersDashboard() {
|
|
|
612
820
|
}
|
|
613
821
|
)
|
|
614
822
|
] }),
|
|
615
|
-
filter !== "all"
|
|
823
|
+
filter !== "all" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
616
824
|
"button",
|
|
617
825
|
{
|
|
618
826
|
type: "button",
|
|
@@ -621,280 +829,1109 @@ function OrdersDashboard() {
|
|
|
621
829
|
"aria-label": "Show all orders (clear filter)",
|
|
622
830
|
children: "Clear period"
|
|
623
831
|
}
|
|
624
|
-
)
|
|
832
|
+
) : null
|
|
625
833
|
] })
|
|
626
834
|
}
|
|
627
835
|
),
|
|
628
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-
|
|
629
|
-
/* @__PURE__ */ jsxRuntime.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Comparison" }),
|
|
682
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-sm font-semibold tracking-tight", children: "Last period" }),
|
|
683
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: "Reserved — API unchanged." })
|
|
684
|
-
] }) })
|
|
836
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 p-3 pt-0 md:p-4 md:pt-0", children: [
|
|
837
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
838
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Order overview" }) }),
|
|
839
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
840
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
841
|
+
TrafficSourcesOverviewCard,
|
|
842
|
+
{
|
|
843
|
+
traffic: (ordersInsights == null ? void 0 : ordersInsights.traffic) ?? [],
|
|
844
|
+
loading: insightsLoading,
|
|
845
|
+
error: insightsError,
|
|
846
|
+
totalRevenue: trafficTotalRevenue,
|
|
847
|
+
windowLabel: insightsRangeLabel
|
|
848
|
+
}
|
|
849
|
+
),
|
|
850
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-3 lg:grid-cols-4", children: primaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
851
|
+
AtlasKpiCard,
|
|
852
|
+
{
|
|
853
|
+
label: stat.label,
|
|
854
|
+
value: stat.value,
|
|
855
|
+
helper: stat.helper,
|
|
856
|
+
accent: stat.accent
|
|
857
|
+
},
|
|
858
|
+
stat.label
|
|
859
|
+
)) })
|
|
860
|
+
] })
|
|
861
|
+
] }),
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-12 gap-2 xl:items-start", children: [
|
|
863
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12 xl:col-span-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
864
|
+
AnalyticsSection,
|
|
865
|
+
{
|
|
866
|
+
variant: "atlas",
|
|
867
|
+
actionsBare: true,
|
|
868
|
+
title: "Revenue & orders",
|
|
869
|
+
description: "Time series for the selected range. Window totals below match this chart.",
|
|
870
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
871
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
872
|
+
ui.Label,
|
|
873
|
+
{
|
|
874
|
+
htmlFor: "over-time-period",
|
|
875
|
+
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
876
|
+
children: "Range"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
880
|
+
"select",
|
|
881
|
+
{
|
|
882
|
+
id: "over-time-period",
|
|
883
|
+
value: overTimePeriod,
|
|
884
|
+
onChange: (e) => setOverTimePeriod(e.target.value),
|
|
885
|
+
className: SELECT_CLASS_NAME,
|
|
886
|
+
children: OVER_TIME_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
887
|
+
}
|
|
888
|
+
)
|
|
685
889
|
] }),
|
|
686
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
687
|
-
/* @__PURE__ */ jsxRuntime.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
/* @__PURE__ */ jsxRuntime.
|
|
691
|
-
|
|
692
|
-
|
|
890
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
891
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
892
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
893
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window revenue" }),
|
|
894
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency(trendRevenueTotal) }),
|
|
895
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
896
|
+
"Avg/day ",
|
|
897
|
+
formatCompactCurrency(trendAverageRevenue)
|
|
898
|
+
] })
|
|
899
|
+
] }) }),
|
|
900
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
901
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window orders" }),
|
|
902
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: trendOrdersTotal.toLocaleString() }),
|
|
903
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
904
|
+
"Avg/day ",
|
|
905
|
+
trendAverageOrders.toFixed(1)
|
|
906
|
+
] })
|
|
907
|
+
] }) }),
|
|
908
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
909
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "vs previous period" }),
|
|
910
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: revenueVsPreviousPercent === null ? "—" : `${revenueVsPreviousPercent >= 0 ? "+" : ""}${revenueVsPreviousPercent.toFixed(1)}% rev` }),
|
|
911
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: [
|
|
912
|
+
"Orders",
|
|
913
|
+
" ",
|
|
914
|
+
ordersVsPreviousPercent === null ? "—" : `${ordersVsPreviousPercent >= 0 ? "+" : ""}${ordersVsPreviousPercent.toFixed(1)}%`
|
|
915
|
+
] })
|
|
916
|
+
] }) })
|
|
917
|
+
] }),
|
|
918
|
+
!overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
919
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 lg:grid-cols-3", children: [
|
|
920
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
921
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue over time" }),
|
|
922
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[118px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
923
|
+
recharts.LineChart,
|
|
924
|
+
{
|
|
925
|
+
data: trendRowsDetailed,
|
|
926
|
+
margin: { top: 4, right: 4, left: 0, bottom: 0 },
|
|
927
|
+
children: [
|
|
928
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
929
|
+
recharts.CartesianGrid,
|
|
930
|
+
{
|
|
931
|
+
strokeDasharray: "3 3",
|
|
932
|
+
vertical: false,
|
|
933
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
937
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
938
|
+
recharts.YAxis,
|
|
939
|
+
{
|
|
940
|
+
width: 36,
|
|
941
|
+
tick: CHART_AXIS_TICK_SM,
|
|
942
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
943
|
+
}
|
|
944
|
+
),
|
|
945
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
946
|
+
recharts.Tooltip,
|
|
947
|
+
{
|
|
948
|
+
content: ({ active, payload, label }) => {
|
|
949
|
+
var _a2, _b2, _c2, _d2;
|
|
950
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
951
|
+
TooltipCard,
|
|
952
|
+
{
|
|
953
|
+
title: String(
|
|
954
|
+
isDailyOrderRow((_a2 = payload[0]) == null ? void 0 : _a2.payload) ? ((_b2 = payload[0]) == null ? void 0 : _b2.payload.label) ?? formatChartLabel(String(label)) : label
|
|
955
|
+
),
|
|
956
|
+
children: [
|
|
957
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
958
|
+
"This:",
|
|
959
|
+
" ",
|
|
960
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCompactCurrency(
|
|
961
|
+
Number(
|
|
962
|
+
(_c2 = payload.find((p) => p.dataKey === "total_revenue")) == null ? void 0 : _c2.value
|
|
963
|
+
) || 0
|
|
964
|
+
) })
|
|
965
|
+
] }),
|
|
966
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
967
|
+
"Previous:",
|
|
968
|
+
" ",
|
|
969
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCompactCurrency(
|
|
970
|
+
Number(
|
|
971
|
+
(_d2 = payload.find((p) => p.dataKey === "prev_revenue")) == null ? void 0 : _d2.value
|
|
972
|
+
) || 0
|
|
973
|
+
) })
|
|
974
|
+
] })
|
|
975
|
+
]
|
|
976
|
+
}
|
|
977
|
+
) : null;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
982
|
+
recharts.Line,
|
|
983
|
+
{
|
|
984
|
+
type: "natural",
|
|
985
|
+
dataKey: "total_revenue",
|
|
986
|
+
name: "This period",
|
|
987
|
+
stroke: "#D946EF",
|
|
988
|
+
strokeWidth: 2,
|
|
989
|
+
dot: false
|
|
990
|
+
}
|
|
991
|
+
),
|
|
992
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
993
|
+
recharts.Line,
|
|
994
|
+
{
|
|
995
|
+
type: "natural",
|
|
996
|
+
dataKey: "prev_revenue",
|
|
997
|
+
name: "Previous",
|
|
998
|
+
stroke: "#94a3b8",
|
|
999
|
+
strokeWidth: 1.5,
|
|
1000
|
+
strokeDasharray: "5 4",
|
|
1001
|
+
dot: false
|
|
1002
|
+
}
|
|
1003
|
+
)
|
|
1004
|
+
]
|
|
1005
|
+
}
|
|
1006
|
+
) }) })
|
|
693
1007
|
] }),
|
|
694
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
695
|
-
/* @__PURE__ */ jsxRuntime.
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1008
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1009
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Orders count" }),
|
|
1010
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[118px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1011
|
+
recharts.LineChart,
|
|
1012
|
+
{
|
|
1013
|
+
data: trendRowsDetailed,
|
|
1014
|
+
margin: { top: 4, right: 4, left: 0, bottom: 0 },
|
|
1015
|
+
children: [
|
|
1016
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1017
|
+
recharts.CartesianGrid,
|
|
1018
|
+
{
|
|
1019
|
+
strokeDasharray: "3 3",
|
|
1020
|
+
vertical: false,
|
|
1021
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1022
|
+
}
|
|
1023
|
+
),
|
|
1024
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
1025
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1026
|
+
recharts.YAxis,
|
|
1027
|
+
{
|
|
1028
|
+
width: 28,
|
|
1029
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1030
|
+
allowDecimals: false
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1034
|
+
recharts.Tooltip,
|
|
1035
|
+
{
|
|
1036
|
+
content: ({ active, payload, label }) => {
|
|
1037
|
+
var _a2, _b2;
|
|
1038
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
1039
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1040
|
+
"This:",
|
|
1041
|
+
" ",
|
|
1042
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
1043
|
+
Number(
|
|
1044
|
+
(_a2 = payload.find((p) => p.dataKey === "orders_count")) == null ? void 0 : _a2.value
|
|
1045
|
+
) || 0
|
|
1046
|
+
).toLocaleString() })
|
|
1047
|
+
] }),
|
|
1048
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1049
|
+
"Previous:",
|
|
1050
|
+
" ",
|
|
1051
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
1052
|
+
Number(
|
|
1053
|
+
(_b2 = payload.find((p) => p.dataKey === "prev_orders")) == null ? void 0 : _b2.value
|
|
1054
|
+
) || 0
|
|
1055
|
+
).toLocaleString() })
|
|
1056
|
+
] })
|
|
1057
|
+
] }) : null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
),
|
|
1061
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1062
|
+
recharts.Line,
|
|
1063
|
+
{
|
|
1064
|
+
type: "natural",
|
|
1065
|
+
dataKey: "orders_count",
|
|
1066
|
+
name: "This period",
|
|
1067
|
+
stroke: "#38BDF8",
|
|
1068
|
+
strokeWidth: 2,
|
|
1069
|
+
dot: false
|
|
1070
|
+
}
|
|
1071
|
+
),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1073
|
+
recharts.Line,
|
|
1074
|
+
{
|
|
1075
|
+
type: "natural",
|
|
1076
|
+
dataKey: "prev_orders",
|
|
1077
|
+
name: "Previous",
|
|
1078
|
+
stroke: "#94a3b8",
|
|
1079
|
+
strokeWidth: 1.5,
|
|
1080
|
+
strokeDasharray: "5 4",
|
|
1081
|
+
dot: false
|
|
1082
|
+
}
|
|
1083
|
+
)
|
|
1084
|
+
]
|
|
1085
|
+
}
|
|
1086
|
+
) }) })
|
|
1087
|
+
] }),
|
|
1088
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "AOV (non-cancelled)" }),
|
|
1090
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[118px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1091
|
+
recharts.LineChart,
|
|
1092
|
+
{
|
|
1093
|
+
data: trendRowsDetailed,
|
|
1094
|
+
margin: { top: 4, right: 4, left: 0, bottom: 0 },
|
|
1095
|
+
children: [
|
|
1096
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1097
|
+
recharts.CartesianGrid,
|
|
1098
|
+
{
|
|
1099
|
+
strokeDasharray: "3 3",
|
|
1100
|
+
vertical: false,
|
|
1101
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1102
|
+
}
|
|
1103
|
+
),
|
|
1104
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
1105
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1106
|
+
recharts.YAxis,
|
|
1107
|
+
{
|
|
1108
|
+
width: 36,
|
|
1109
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1110
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1111
|
+
}
|
|
1112
|
+
),
|
|
1113
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1114
|
+
recharts.Tooltip,
|
|
1115
|
+
{
|
|
1116
|
+
content: ({ active, payload, label }) => {
|
|
1117
|
+
var _a2, _b2;
|
|
1118
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
1119
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1120
|
+
"This:",
|
|
1121
|
+
" ",
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
1123
|
+
Number(
|
|
1124
|
+
(_a2 = payload.find((p) => p.dataKey === "aov")) == null ? void 0 : _a2.value
|
|
1125
|
+
) || 0
|
|
1126
|
+
) })
|
|
1127
|
+
] }),
|
|
1128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1129
|
+
"Previous:",
|
|
1130
|
+
" ",
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
1132
|
+
Number(
|
|
1133
|
+
(_b2 = payload.find((p) => p.dataKey === "prev_aov")) == null ? void 0 : _b2.value
|
|
1134
|
+
) || 0
|
|
1135
|
+
) })
|
|
1136
|
+
] })
|
|
1137
|
+
] }) : null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
),
|
|
1141
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1142
|
+
recharts.Line,
|
|
1143
|
+
{
|
|
1144
|
+
type: "natural",
|
|
1145
|
+
dataKey: "aov",
|
|
1146
|
+
name: "This period",
|
|
1147
|
+
stroke: "#D97706",
|
|
1148
|
+
strokeWidth: 2,
|
|
1149
|
+
dot: false
|
|
1150
|
+
}
|
|
1151
|
+
),
|
|
1152
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1153
|
+
recharts.Line,
|
|
1154
|
+
{
|
|
1155
|
+
type: "natural",
|
|
1156
|
+
dataKey: "prev_aov",
|
|
1157
|
+
name: "Previous",
|
|
1158
|
+
stroke: "#94a3b8",
|
|
1159
|
+
strokeWidth: 1.5,
|
|
1160
|
+
strokeDasharray: "5 4",
|
|
1161
|
+
dot: false
|
|
1162
|
+
}
|
|
1163
|
+
)
|
|
1164
|
+
]
|
|
1165
|
+
}
|
|
1166
|
+
) }) })
|
|
703
1167
|
] })
|
|
704
1168
|
] }),
|
|
705
|
-
/* @__PURE__ */ jsxRuntime.
|
|
706
|
-
"
|
|
707
|
-
{
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1169
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
|
|
1170
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue breakdown (stacked — by order status)" }),
|
|
1171
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(168px,26vh)] min-h-[140px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1172
|
+
recharts.ComposedChart,
|
|
1173
|
+
{
|
|
1174
|
+
data: dailyOrders,
|
|
1175
|
+
margin: { top: 4, right: 8, left: -4, bottom: 0 },
|
|
1176
|
+
children: [
|
|
1177
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1178
|
+
recharts.CartesianGrid,
|
|
1179
|
+
{
|
|
1180
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1181
|
+
strokeDasharray: "3 3",
|
|
1182
|
+
vertical: false
|
|
1183
|
+
}
|
|
1184
|
+
),
|
|
1185
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1186
|
+
recharts.XAxis,
|
|
1187
|
+
{
|
|
1188
|
+
dataKey: "date",
|
|
1189
|
+
tick: CHART_AXIS_TICK,
|
|
1190
|
+
tickLine: false,
|
|
1191
|
+
axisLine: false,
|
|
1192
|
+
tickFormatter: (value, index) => {
|
|
1193
|
+
const row = dailyOrders[index];
|
|
1194
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(value);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
),
|
|
1198
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1199
|
+
recharts.YAxis,
|
|
1200
|
+
{
|
|
1201
|
+
tick: CHART_AXIS_TICK,
|
|
1202
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1203
|
+
}
|
|
1204
|
+
),
|
|
1205
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1206
|
+
recharts.Tooltip,
|
|
1207
|
+
{
|
|
1208
|
+
content: ({ active, payload, label }) => active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(TooltipCard, { title: String(label), children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1209
|
+
p.name,
|
|
1210
|
+
":",
|
|
1211
|
+
" ",
|
|
1212
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(Number(p.value) || 0) })
|
|
1213
|
+
] }, String(p.name))) }) : null
|
|
1214
|
+
}
|
|
1215
|
+
),
|
|
1216
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { wrapperStyle: { fontSize: 10 }, iconType: "circle" }),
|
|
1217
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1218
|
+
recharts.Area,
|
|
1219
|
+
{
|
|
1220
|
+
type: "natural",
|
|
1221
|
+
dataKey: "revenue_delivered",
|
|
1222
|
+
name: "Delivered",
|
|
1223
|
+
stackId: "r",
|
|
1224
|
+
fill: "#10B981",
|
|
1225
|
+
stroke: "#059669",
|
|
1226
|
+
fillOpacity: 0.85
|
|
1227
|
+
}
|
|
1228
|
+
),
|
|
1229
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1230
|
+
recharts.Area,
|
|
1231
|
+
{
|
|
1232
|
+
type: "natural",
|
|
1233
|
+
dataKey: "revenue_open",
|
|
1234
|
+
name: "Open / pending",
|
|
1235
|
+
stackId: "r",
|
|
1236
|
+
fill: "#3B82F6",
|
|
1237
|
+
stroke: "#2563eb",
|
|
1238
|
+
fillOpacity: 0.85
|
|
1239
|
+
}
|
|
1240
|
+
),
|
|
1241
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1242
|
+
recharts.Area,
|
|
1243
|
+
{
|
|
1244
|
+
type: "natural",
|
|
1245
|
+
dataKey: "revenue_cancelled",
|
|
1246
|
+
name: "Cancelled",
|
|
1247
|
+
stackId: "r",
|
|
1248
|
+
fill: "#F87171",
|
|
1249
|
+
stroke: "#EF4444",
|
|
1250
|
+
fillOpacity: 0.75
|
|
1251
|
+
}
|
|
1252
|
+
)
|
|
1253
|
+
]
|
|
1254
|
+
}
|
|
1255
|
+
) }) })
|
|
1256
|
+
] })
|
|
1257
|
+
] }) : null,
|
|
1258
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "relative overflow-hidden", children: [
|
|
1259
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-6 top-0 h-12 rounded-b-full bg-gradient-to-r from-sky-400/10 via-fuchsia-400/10 to-transparent blur-xl" }),
|
|
1260
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative space-y-1", children: [
|
|
1261
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-1.5", children: [
|
|
1262
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0", children: [
|
|
1263
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Trend overview" }),
|
|
1264
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-sm font-semibold", children: ((_c = OVER_TIME_PERIODS.find((period) => period.value === overTimePeriod)) == null ? void 0 : _c.label) ?? "One week" })
|
|
1265
|
+
] }),
|
|
1266
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap justify-end gap-2 text-[10px] text-ui-fg-muted", children: [
|
|
1267
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1268
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-sky-400" }),
|
|
1269
|
+
"Orders"
|
|
736
1270
|
] }),
|
|
737
|
-
/* @__PURE__ */ jsxRuntime.
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1271
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1272
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-fuchsia-400" }),
|
|
1273
|
+
"Revenue"
|
|
1274
|
+
] })
|
|
1275
|
+
] })
|
|
1276
|
+
] }),
|
|
1277
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(200px,30vh)] min-h-[160px] sm:h-[min(216px,32vh)]", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1278
|
+
"div",
|
|
1279
|
+
{
|
|
1280
|
+
className: "flex h-full items-center justify-center",
|
|
1281
|
+
role: "status",
|
|
1282
|
+
"aria-label": "Loading orders over time",
|
|
1283
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" })
|
|
1284
|
+
}
|
|
1285
|
+
) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: overTimeError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1286
|
+
recharts.ComposedChart,
|
|
1287
|
+
{
|
|
1288
|
+
data: dailyOrders,
|
|
1289
|
+
margin: { top: 4, right: 6, left: -6, bottom: 0 },
|
|
1290
|
+
children: [
|
|
1291
|
+
/* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
|
|
1292
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "ordersGradient", x1: "0", y1: "0", x2: "1", y2: "0", children: [
|
|
1293
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#67E8F9" }),
|
|
1294
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3B82F6" })
|
|
1295
|
+
] }),
|
|
1296
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "revenueGradient", x1: "0", y1: "0", x2: "1", y2: "0", children: [
|
|
1297
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#F472B6" }),
|
|
1298
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC" })
|
|
1299
|
+
] }),
|
|
1300
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "ordersAreaFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1301
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#38BDF8", stopOpacity: 0.22 }),
|
|
1302
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#38BDF8", stopOpacity: 0 })
|
|
1303
|
+
] }),
|
|
1304
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "revenueAreaFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1305
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.2 }),
|
|
1306
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#E879F9", stopOpacity: 0 })
|
|
1307
|
+
] })
|
|
1308
|
+
] }),
|
|
1309
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1310
|
+
recharts.CartesianGrid,
|
|
1311
|
+
{
|
|
1312
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1313
|
+
strokeDasharray: "3 3",
|
|
1314
|
+
vertical: false
|
|
755
1315
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
),
|
|
770
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
771
|
-
recharts.YAxis,
|
|
772
|
-
{
|
|
773
|
-
yAxisId: "revenue",
|
|
774
|
-
orientation: "right",
|
|
775
|
-
width: 40,
|
|
776
|
-
tick: { fontSize: 10, fill: "var(--medusa-color-ui-fg-muted)" },
|
|
777
|
-
tickLine: false,
|
|
778
|
-
axisLine: false,
|
|
779
|
-
tickFormatter: (value) => formatCompactCurrency(Number(value) || 0)
|
|
780
|
-
}
|
|
781
|
-
),
|
|
782
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
783
|
-
recharts.Tooltip,
|
|
784
|
-
{
|
|
785
|
-
content: /* @__PURE__ */ jsxRuntime.jsx(OrdersTrendTooltip, {}),
|
|
786
|
-
cursor: {
|
|
787
|
-
stroke: "rgba(248, 250, 252, 0.35)",
|
|
788
|
-
strokeWidth: 1
|
|
1316
|
+
),
|
|
1317
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1318
|
+
recharts.XAxis,
|
|
1319
|
+
{
|
|
1320
|
+
dataKey: "date",
|
|
1321
|
+
tick: CHART_AXIS_TICK,
|
|
1322
|
+
tickLine: false,
|
|
1323
|
+
axisLine: false,
|
|
1324
|
+
tickFormatter: (value, index) => {
|
|
1325
|
+
const row = dailyOrders[index];
|
|
1326
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(value);
|
|
1327
|
+
}
|
|
789
1328
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1329
|
+
),
|
|
1330
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1331
|
+
recharts.YAxis,
|
|
1332
|
+
{
|
|
1333
|
+
yAxisId: "orders",
|
|
1334
|
+
width: 32,
|
|
1335
|
+
tick: CHART_AXIS_TICK,
|
|
1336
|
+
tickLine: false,
|
|
1337
|
+
axisLine: false,
|
|
1338
|
+
allowDecimals: false,
|
|
1339
|
+
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1340
|
+
}
|
|
1341
|
+
),
|
|
1342
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1343
|
+
recharts.YAxis,
|
|
1344
|
+
{
|
|
1345
|
+
yAxisId: "revenue",
|
|
1346
|
+
orientation: "right",
|
|
1347
|
+
width: 40,
|
|
1348
|
+
tick: CHART_AXIS_TICK,
|
|
1349
|
+
tickLine: false,
|
|
1350
|
+
axisLine: false,
|
|
1351
|
+
tickFormatter: (value) => formatCompactCurrency(Number(value) || 0)
|
|
1352
|
+
}
|
|
1353
|
+
),
|
|
1354
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1355
|
+
recharts.Tooltip,
|
|
1356
|
+
{
|
|
1357
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(OrdersTrendTooltip, {}),
|
|
1358
|
+
cursor: {
|
|
1359
|
+
stroke: "rgba(248, 250, 252, 0.35)",
|
|
1360
|
+
strokeWidth: 1
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
),
|
|
1364
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1365
|
+
recharts.Area,
|
|
1366
|
+
{
|
|
1367
|
+
yAxisId: "orders",
|
|
1368
|
+
type: "natural",
|
|
1369
|
+
dataKey: "orders_count",
|
|
1370
|
+
stroke: "none",
|
|
1371
|
+
fill: "url(#ordersAreaFill)",
|
|
1372
|
+
isAnimationActive: false,
|
|
1373
|
+
legendType: "none"
|
|
1374
|
+
}
|
|
1375
|
+
),
|
|
1376
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1377
|
+
recharts.Area,
|
|
1378
|
+
{
|
|
1379
|
+
yAxisId: "revenue",
|
|
1380
|
+
type: "natural",
|
|
1381
|
+
dataKey: "total_revenue",
|
|
1382
|
+
stroke: "none",
|
|
1383
|
+
fill: "url(#revenueAreaFill)",
|
|
1384
|
+
isAnimationActive: false,
|
|
1385
|
+
legendType: "none"
|
|
1386
|
+
}
|
|
1387
|
+
),
|
|
1388
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1389
|
+
recharts.Line,
|
|
1390
|
+
{
|
|
1391
|
+
yAxisId: "orders",
|
|
1392
|
+
type: "natural",
|
|
1393
|
+
dataKey: "orders_count",
|
|
1394
|
+
name: "Orders",
|
|
1395
|
+
stroke: "url(#ordersGradient)",
|
|
1396
|
+
strokeWidth: 2,
|
|
1397
|
+
dot: false,
|
|
1398
|
+
activeDot: { r: 4, fill: STATUS_BAR_COLORS.orders }
|
|
1399
|
+
}
|
|
1400
|
+
),
|
|
1401
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1402
|
+
recharts.Line,
|
|
1403
|
+
{
|
|
1404
|
+
yAxisId: "revenue",
|
|
1405
|
+
type: "natural",
|
|
1406
|
+
dataKey: "total_revenue",
|
|
1407
|
+
name: "Revenue",
|
|
1408
|
+
stroke: "url(#revenueGradient)",
|
|
1409
|
+
strokeWidth: 2,
|
|
1410
|
+
dot: false,
|
|
1411
|
+
activeDot: { r: 4, fill: STATUS_BAR_COLORS.revenue }
|
|
1412
|
+
}
|
|
1413
|
+
)
|
|
1414
|
+
]
|
|
1415
|
+
}
|
|
1416
|
+
) }) })
|
|
1417
|
+
] })
|
|
851
1418
|
] })
|
|
852
1419
|
] })
|
|
853
|
-
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1420
|
+
}
|
|
1421
|
+
) }),
|
|
1422
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12 flex flex-col gap-2 xl:col-span-6", children: [
|
|
1423
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1424
|
+
AnalyticsSection,
|
|
1425
|
+
{
|
|
1426
|
+
variant: "atlas",
|
|
1427
|
+
title: "Pulse & range",
|
|
1428
|
+
description: "Window totals and an orders time series for the selected range.",
|
|
1429
|
+
children: [
|
|
1430
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2", children: quickPulseMetrics.map((metric) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1431
|
+
"div",
|
|
1432
|
+
{
|
|
1433
|
+
className: `rounded-lg border border-ui-border-base bg-ui-bg-base px-2 py-2 shadow-sm ${metric.accentClassName}`.trim(),
|
|
1434
|
+
children: [
|
|
1435
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: metric.label }),
|
|
1436
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base mt-0.5 text-sm font-semibold tracking-tight", children: metric.value }),
|
|
1437
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted mt-0.5 text-[9px] leading-snug", children: metric.helper })
|
|
1438
|
+
]
|
|
1439
|
+
},
|
|
1440
|
+
metric.label
|
|
1441
|
+
)) }),
|
|
1442
|
+
!overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
|
|
1443
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Orders (time series)" }),
|
|
1444
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[76px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1445
|
+
recharts.LineChart,
|
|
1446
|
+
{
|
|
1447
|
+
data: dailyOrders,
|
|
1448
|
+
margin: { top: 2, right: 4, left: -18, bottom: 2 },
|
|
1449
|
+
children: [
|
|
1450
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1451
|
+
recharts.CartesianGrid,
|
|
1452
|
+
{
|
|
1453
|
+
strokeDasharray: "3 3",
|
|
1454
|
+
vertical: false,
|
|
1455
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1458
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
1459
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { hide: true, domain: ["auto", "auto"] }),
|
|
1460
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1461
|
+
recharts.Tooltip,
|
|
1462
|
+
{
|
|
1463
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(OrdersSparklineTooltip, {}),
|
|
1464
|
+
cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 }
|
|
1465
|
+
}
|
|
1466
|
+
),
|
|
1467
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1468
|
+
recharts.Line,
|
|
1469
|
+
{
|
|
1470
|
+
type: "natural",
|
|
1471
|
+
dataKey: "orders_count",
|
|
1472
|
+
stroke: "#3b82f6",
|
|
1473
|
+
strokeWidth: 2,
|
|
1474
|
+
dot: { r: 2, fill: "#3b82f6" },
|
|
1475
|
+
activeDot: { r: 3 }
|
|
1476
|
+
}
|
|
1477
|
+
)
|
|
1478
|
+
]
|
|
1479
|
+
}
|
|
1480
|
+
) }) })
|
|
1481
|
+
] }) : null
|
|
1482
|
+
]
|
|
1483
|
+
}
|
|
1484
|
+
),
|
|
1485
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1486
|
+
AnalyticsSection,
|
|
1487
|
+
{
|
|
1488
|
+
variant: "atlas",
|
|
1489
|
+
title: "Order → fulfillment funnel",
|
|
1490
|
+
description: `Stages by order created date (UTC), same range as Revenue & orders (${((_d = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _d.label) ?? "period"}). Not storefront visitors.`,
|
|
1491
|
+
children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[120px] items-center justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[120px] items-center justify-center px-2 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: overTimeError }) }) }) : funnelTimeSeriesRows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
1492
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1493
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Stage counts (time series)" }),
|
|
1494
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(200px,30vh)] min-h-[168px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1495
|
+
recharts.LineChart,
|
|
1496
|
+
{
|
|
1497
|
+
data: funnelTimeSeriesRows,
|
|
1498
|
+
margin: { top: 4, right: 8, left: 0, bottom: 4 },
|
|
1499
|
+
children: [
|
|
1500
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1501
|
+
recharts.CartesianGrid,
|
|
1502
|
+
{
|
|
1503
|
+
strokeDasharray: "3 3",
|
|
1504
|
+
stroke: "rgba(148,163,184,0.2)"
|
|
1505
|
+
}
|
|
1506
|
+
),
|
|
1507
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1508
|
+
recharts.XAxis,
|
|
1509
|
+
{
|
|
1510
|
+
dataKey: "date",
|
|
1511
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1512
|
+
stroke: CHART_AXIS_LINE,
|
|
1513
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1514
|
+
tickFormatter: (value) => {
|
|
1515
|
+
const row = dailyOrders.find((r) => r.date === value);
|
|
1516
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
),
|
|
1520
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1521
|
+
recharts.YAxis,
|
|
1522
|
+
{
|
|
1523
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1524
|
+
stroke: CHART_AXIS_LINE,
|
|
1525
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1526
|
+
width: 28,
|
|
1527
|
+
allowDecimals: false,
|
|
1528
|
+
tickFormatter: (v) => Math.floor(Number(v) || 0).toLocaleString()
|
|
1529
|
+
}
|
|
1530
|
+
),
|
|
1531
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1532
|
+
recharts.Tooltip,
|
|
1533
|
+
{
|
|
1534
|
+
content: ({ active, payload, label }) => {
|
|
1535
|
+
var _a2;
|
|
1536
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1537
|
+
TooltipCard,
|
|
1538
|
+
{
|
|
1539
|
+
title: typeof label === "string" ? ((_a2 = dailyOrders.find((r) => r.date === label)) == null ? void 0 : _a2.label) ?? formatChartLabel(label) : String(label),
|
|
1540
|
+
children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1541
|
+
String(p.name),
|
|
1542
|
+
":",
|
|
1543
|
+
" ",
|
|
1544
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(p.value) || 0).toLocaleString() })
|
|
1545
|
+
] }, String(p.dataKey)))
|
|
1546
|
+
}
|
|
1547
|
+
) : null;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
),
|
|
1551
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1552
|
+
recharts.Legend,
|
|
1553
|
+
{
|
|
1554
|
+
wrapperStyle: { fontSize: 9, color: "#e5e7eb", paddingTop: 4 },
|
|
1555
|
+
iconType: "circle",
|
|
1556
|
+
iconSize: 6
|
|
1557
|
+
}
|
|
1558
|
+
),
|
|
1559
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1560
|
+
recharts.Line,
|
|
1561
|
+
{
|
|
1562
|
+
type: "natural",
|
|
1563
|
+
dataKey: "placed",
|
|
1564
|
+
name: "Placed",
|
|
1565
|
+
stroke: "#6366F1",
|
|
1566
|
+
strokeWidth: 2,
|
|
1567
|
+
dot: false
|
|
1568
|
+
}
|
|
1569
|
+
),
|
|
1570
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1571
|
+
recharts.Line,
|
|
1572
|
+
{
|
|
1573
|
+
type: "natural",
|
|
1574
|
+
dataKey: "not_cancelled",
|
|
1575
|
+
name: "Not cancelled",
|
|
1576
|
+
stroke: "#22d3ee",
|
|
1577
|
+
strokeWidth: 2,
|
|
1578
|
+
dot: false
|
|
1579
|
+
}
|
|
1580
|
+
),
|
|
1581
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1582
|
+
recharts.Line,
|
|
1583
|
+
{
|
|
1584
|
+
type: "natural",
|
|
1585
|
+
dataKey: "shipped",
|
|
1586
|
+
name: "Shipped",
|
|
1587
|
+
stroke: "#F59E0B",
|
|
1588
|
+
strokeWidth: 2,
|
|
1589
|
+
dot: false
|
|
1590
|
+
}
|
|
1591
|
+
),
|
|
1592
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1593
|
+
recharts.Line,
|
|
1594
|
+
{
|
|
1595
|
+
type: "natural",
|
|
1596
|
+
dataKey: "delivered",
|
|
1597
|
+
name: "Delivered",
|
|
1598
|
+
stroke: "#10B981",
|
|
1599
|
+
strokeWidth: 2,
|
|
1600
|
+
dot: false
|
|
1601
|
+
}
|
|
1602
|
+
)
|
|
1603
|
+
]
|
|
1604
|
+
}
|
|
1605
|
+
) }) })
|
|
1606
|
+
] }),
|
|
1607
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1608
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Stage conversion (% of prior stage, same buckets)" }),
|
|
1609
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(168px,26vh)] min-h-[140px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1610
|
+
recharts.LineChart,
|
|
1611
|
+
{
|
|
1612
|
+
data: funnelTimeSeriesRows,
|
|
1613
|
+
margin: { top: 4, right: 8, left: 0, bottom: 4 },
|
|
1614
|
+
children: [
|
|
1615
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1616
|
+
recharts.CartesianGrid,
|
|
1617
|
+
{
|
|
1618
|
+
strokeDasharray: "3 3",
|
|
1619
|
+
stroke: "rgba(148,163,184,0.2)"
|
|
1620
|
+
}
|
|
1621
|
+
),
|
|
1622
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1623
|
+
recharts.XAxis,
|
|
1624
|
+
{
|
|
1625
|
+
dataKey: "date",
|
|
1626
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1627
|
+
stroke: CHART_AXIS_LINE,
|
|
1628
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1629
|
+
tickFormatter: (value) => {
|
|
1630
|
+
const row = dailyOrders.find((r) => r.date === value);
|
|
1631
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
),
|
|
1635
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1636
|
+
recharts.YAxis,
|
|
1637
|
+
{
|
|
1638
|
+
domain: [0, 100],
|
|
1639
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1640
|
+
stroke: CHART_AXIS_LINE,
|
|
1641
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1642
|
+
width: 36,
|
|
1643
|
+
tickFormatter: (v) => `${Math.round(Number(v) || 0)}%`
|
|
1644
|
+
}
|
|
1645
|
+
),
|
|
1646
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1647
|
+
recharts.Tooltip,
|
|
1648
|
+
{
|
|
1649
|
+
content: ({ active, payload, label }) => {
|
|
1650
|
+
var _a2;
|
|
1651
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1652
|
+
TooltipCard,
|
|
1653
|
+
{
|
|
1654
|
+
title: typeof label === "string" ? ((_a2 = dailyOrders.find((r) => r.date === label)) == null ? void 0 : _a2.label) ?? formatChartLabel(label) : String(label),
|
|
1655
|
+
children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1656
|
+
String(p.name),
|
|
1657
|
+
":",
|
|
1658
|
+
" ",
|
|
1659
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatPercent(Number(p.value) || 0) })
|
|
1660
|
+
] }, String(p.dataKey)))
|
|
1661
|
+
}
|
|
1662
|
+
) : null;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
),
|
|
1666
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1667
|
+
recharts.Legend,
|
|
1668
|
+
{
|
|
1669
|
+
wrapperStyle: { fontSize: 9, color: "#e5e7eb", paddingTop: 4 },
|
|
1670
|
+
iconType: "circle",
|
|
1671
|
+
iconSize: 6
|
|
1672
|
+
}
|
|
1673
|
+
),
|
|
1674
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1675
|
+
recharts.Line,
|
|
1676
|
+
{
|
|
1677
|
+
type: "natural",
|
|
1678
|
+
dataKey: "pct_not_cancelled",
|
|
1679
|
+
name: "Not cancelled / placed",
|
|
1680
|
+
stroke: "#6366F1",
|
|
1681
|
+
strokeWidth: 2,
|
|
1682
|
+
dot: false
|
|
1683
|
+
}
|
|
1684
|
+
),
|
|
1685
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1686
|
+
recharts.Line,
|
|
1687
|
+
{
|
|
1688
|
+
type: "natural",
|
|
1689
|
+
dataKey: "pct_shipped_of_active",
|
|
1690
|
+
name: "Shipped / not cancelled",
|
|
1691
|
+
stroke: "#F59E0B",
|
|
1692
|
+
strokeWidth: 2,
|
|
1693
|
+
dot: false
|
|
1694
|
+
}
|
|
1695
|
+
),
|
|
1696
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1697
|
+
recharts.Line,
|
|
1698
|
+
{
|
|
1699
|
+
type: "natural",
|
|
1700
|
+
dataKey: "pct_delivered_of_shipped",
|
|
1701
|
+
name: "Delivered / shipped",
|
|
1702
|
+
stroke: "#10B981",
|
|
1703
|
+
strokeWidth: 2,
|
|
1704
|
+
dot: false
|
|
1705
|
+
}
|
|
1706
|
+
)
|
|
1707
|
+
]
|
|
1708
|
+
}
|
|
1709
|
+
) }) })
|
|
1710
|
+
] })
|
|
1711
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1712
|
+
EmptyAnalyticsPanel,
|
|
1713
|
+
{
|
|
1714
|
+
title: "No funnel data",
|
|
1715
|
+
description: "No orders in this range to chart."
|
|
1716
|
+
}
|
|
1717
|
+
)
|
|
1718
|
+
}
|
|
1719
|
+
),
|
|
1720
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1721
|
+
AnalyticsSection,
|
|
1722
|
+
{
|
|
1723
|
+
variant: "atlas",
|
|
1724
|
+
title: "Order revenue (attribution)",
|
|
1725
|
+
description: "All revenue is from captured Medusa orders until storefront channel data exists.",
|
|
1726
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) : ordersInsights && ordersInsights.traffic.length > 0 ? trafficTotalRevenue <= 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted px-0.5 text-center text-[11px] leading-snug", children: "No recorded revenue in this window (totals may be zero until orders are paid or finalized)." }) : /* @__PURE__ */ jsxRuntime.jsx(TrafficRevenueDonut, { traffic: ordersInsights.traffic }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1727
|
+
EmptyAnalyticsPanel,
|
|
1728
|
+
{
|
|
1729
|
+
title: "No order revenue",
|
|
1730
|
+
description: "No orders in this window."
|
|
1731
|
+
}
|
|
1732
|
+
)
|
|
1733
|
+
}
|
|
1734
|
+
)
|
|
1735
|
+
] })
|
|
1736
|
+
] }),
|
|
1737
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-1.5", children: [
|
|
1738
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Operational signals" }) }),
|
|
1739
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 lg:grid-cols-3 xl:grid-cols-6", children: secondaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1740
|
+
AnalyticsStatCard,
|
|
1741
|
+
{
|
|
1742
|
+
label: stat.label,
|
|
1743
|
+
value: stat.value,
|
|
1744
|
+
helper: stat.helper,
|
|
1745
|
+
variant: "compact"
|
|
1746
|
+
},
|
|
1747
|
+
stat.label
|
|
1748
|
+
)) })
|
|
1749
|
+
] }),
|
|
1750
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-1 items-start gap-2 xl:grid-cols-12", children: [
|
|
1751
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1752
|
+
AnalyticsSection,
|
|
1753
|
+
{
|
|
1754
|
+
variant: "atlas",
|
|
1755
|
+
className: "xl:col-span-7",
|
|
1756
|
+
title: "Order outcomes",
|
|
1757
|
+
description: "Delivered, cancelled, exchanges, and returns per bucket — same window as Revenue & orders.",
|
|
1758
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[176px] sm:h-[196px]", 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 text-xs", children: "Loading…" }) }) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: overTimeError }) }) : !outcomesTimeSeriesHasPoints ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "No outcome events in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1759
|
+
recharts.LineChart,
|
|
865
1760
|
{
|
|
866
|
-
|
|
1761
|
+
data: dailyOrders,
|
|
1762
|
+
margin: { top: 4, right: 8, left: 0, bottom: 0 },
|
|
867
1763
|
children: [
|
|
868
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
869
|
-
|
|
870
|
-
|
|
1764
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1765
|
+
recharts.CartesianGrid,
|
|
1766
|
+
{
|
|
1767
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1768
|
+
strokeDasharray: "3 3",
|
|
1769
|
+
vertical: false
|
|
1770
|
+
}
|
|
1771
|
+
),
|
|
1772
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1773
|
+
recharts.XAxis,
|
|
1774
|
+
{
|
|
1775
|
+
dataKey: "date",
|
|
1776
|
+
tick: CHART_AXIS_TICK,
|
|
1777
|
+
tickLine: false,
|
|
1778
|
+
axisLine: false,
|
|
1779
|
+
tickFormatter: (value) => {
|
|
1780
|
+
const dateStr = typeof value === "string" ? value : String(value);
|
|
1781
|
+
const row = dailyOrders.find((d) => d.date === dateStr);
|
|
1782
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(dateStr);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
),
|
|
1786
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1787
|
+
recharts.YAxis,
|
|
1788
|
+
{
|
|
1789
|
+
width: 28,
|
|
1790
|
+
tick: CHART_AXIS_TICK,
|
|
1791
|
+
tickLine: false,
|
|
1792
|
+
axisLine: false,
|
|
1793
|
+
allowDecimals: false,
|
|
1794
|
+
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1795
|
+
}
|
|
1796
|
+
),
|
|
1797
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1798
|
+
recharts.Tooltip,
|
|
1799
|
+
{
|
|
1800
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(OutcomesTrendTooltip, {}),
|
|
1801
|
+
cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 }
|
|
1802
|
+
}
|
|
1803
|
+
),
|
|
1804
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1805
|
+
recharts.Legend,
|
|
1806
|
+
{
|
|
1807
|
+
wrapperStyle: { fontSize: 9, paddingTop: 0 },
|
|
1808
|
+
iconType: "circle",
|
|
1809
|
+
iconSize: 6
|
|
1810
|
+
}
|
|
1811
|
+
),
|
|
1812
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1813
|
+
recharts.Line,
|
|
1814
|
+
{
|
|
1815
|
+
type: "natural",
|
|
1816
|
+
dataKey: "delivered_count",
|
|
1817
|
+
name: "Delivered",
|
|
1818
|
+
stroke: STATUS_BAR_COLORS.delivered,
|
|
1819
|
+
strokeWidth: 2,
|
|
1820
|
+
dot: false
|
|
1821
|
+
}
|
|
1822
|
+
),
|
|
1823
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1824
|
+
recharts.Line,
|
|
1825
|
+
{
|
|
1826
|
+
type: "natural",
|
|
1827
|
+
dataKey: "cancelled_count",
|
|
1828
|
+
name: "Cancelled",
|
|
1829
|
+
stroke: STATUS_BAR_COLORS.cancelled,
|
|
1830
|
+
strokeWidth: 2,
|
|
1831
|
+
dot: false
|
|
1832
|
+
}
|
|
1833
|
+
),
|
|
1834
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1835
|
+
recharts.Line,
|
|
1836
|
+
{
|
|
1837
|
+
type: "natural",
|
|
1838
|
+
dataKey: "exchange_count",
|
|
1839
|
+
name: "Exchanges",
|
|
1840
|
+
stroke: STATUS_BAR_COLORS.exchange,
|
|
1841
|
+
strokeWidth: 2,
|
|
1842
|
+
dot: false
|
|
1843
|
+
}
|
|
1844
|
+
),
|
|
1845
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1846
|
+
recharts.Line,
|
|
1847
|
+
{
|
|
1848
|
+
type: "natural",
|
|
1849
|
+
dataKey: "return_count",
|
|
1850
|
+
name: "Returns",
|
|
1851
|
+
stroke: STATUS_BAR_COLORS.return,
|
|
1852
|
+
strokeWidth: 2,
|
|
1853
|
+
dot: false
|
|
1854
|
+
}
|
|
1855
|
+
)
|
|
871
1856
|
]
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1857
|
+
}
|
|
1858
|
+
) }) }) })
|
|
1859
|
+
}
|
|
1860
|
+
),
|
|
1861
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1862
|
+
AnalyticsSection,
|
|
1863
|
+
{
|
|
1864
|
+
variant: "atlas",
|
|
1865
|
+
className: "xl:col-span-5",
|
|
1866
|
+
title: "Recent orders (7 days)",
|
|
1867
|
+
description: "Daily order count vs 7-day average — time series.",
|
|
1868
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1869
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [
|
|
1870
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 px-2 py-2", children: [
|
|
1871
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Today" }),
|
|
1872
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-base font-semibold tracking-tight", children: (todayPoint == null ? void 0 : todayPoint.orders_count.toLocaleString()) ?? "0" })
|
|
1873
|
+
] }),
|
|
1874
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 px-2 py-2", children: [
|
|
1875
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "7-day avg" }),
|
|
1876
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-base font-semibold tracking-tight", children: todayOrdersAverage.toFixed(1) })
|
|
1877
|
+
] })
|
|
1878
|
+
] }),
|
|
1879
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[140px] sm:h-[156px]", children: todayContextLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : todayContextError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: todayContextError }) }) : todayChartData.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 text-xs", children: "No recent activity." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1880
|
+
recharts.ComposedChart,
|
|
879
1881
|
{
|
|
880
|
-
data:
|
|
881
|
-
margin: { top:
|
|
1882
|
+
data: todayChartData,
|
|
1883
|
+
margin: { top: 4, right: 4, left: -4, bottom: 0 },
|
|
882
1884
|
children: [
|
|
1885
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "todayOrdersFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1886
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#3b82f6", stopOpacity: 0.2 }),
|
|
1887
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3b82f6", stopOpacity: 0 })
|
|
1888
|
+
] }) }),
|
|
1889
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1890
|
+
recharts.CartesianGrid,
|
|
1891
|
+
{
|
|
1892
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1893
|
+
strokeDasharray: "3 3",
|
|
1894
|
+
vertical: false
|
|
1895
|
+
}
|
|
1896
|
+
),
|
|
1897
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1898
|
+
recharts.XAxis,
|
|
1899
|
+
{
|
|
1900
|
+
dataKey: "date",
|
|
1901
|
+
tick: CHART_AXIS_TICK,
|
|
1902
|
+
tickLine: false,
|
|
1903
|
+
axisLine: false,
|
|
1904
|
+
tickFormatter: formatChartLabel
|
|
1905
|
+
}
|
|
1906
|
+
),
|
|
1907
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1908
|
+
recharts.YAxis,
|
|
1909
|
+
{
|
|
1910
|
+
width: 28,
|
|
1911
|
+
tick: CHART_AXIS_TICK,
|
|
1912
|
+
tickLine: false,
|
|
1913
|
+
axisLine: false,
|
|
1914
|
+
allowDecimals: false,
|
|
1915
|
+
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1916
|
+
}
|
|
1917
|
+
),
|
|
1918
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersTodayTooltip, {}), cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 } }),
|
|
883
1919
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
884
|
-
recharts.
|
|
1920
|
+
recharts.ReferenceLine,
|
|
885
1921
|
{
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1922
|
+
y: todayOrdersAverage,
|
|
1923
|
+
stroke: "rgba(148,163,184,0.65)",
|
|
1924
|
+
strokeDasharray: "4 4"
|
|
889
1925
|
}
|
|
890
1926
|
),
|
|
891
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
892
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { hide: true, domain: ["auto", "auto"] }),
|
|
893
1927
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
894
|
-
recharts.
|
|
1928
|
+
recharts.Area,
|
|
895
1929
|
{
|
|
896
|
-
|
|
897
|
-
|
|
1930
|
+
type: "natural",
|
|
1931
|
+
dataKey: "orders_count",
|
|
1932
|
+
stroke: "none",
|
|
1933
|
+
fill: "url(#todayOrdersFill)",
|
|
1934
|
+
isAnimationActive: false
|
|
898
1935
|
}
|
|
899
1936
|
),
|
|
900
1937
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -902,31 +1939,111 @@ function OrdersDashboard() {
|
|
|
902
1939
|
{
|
|
903
1940
|
type: "natural",
|
|
904
1941
|
dataKey: "orders_count",
|
|
1942
|
+
name: "Orders",
|
|
905
1943
|
stroke: "#3b82f6",
|
|
906
1944
|
strokeWidth: 2,
|
|
907
|
-
dot:
|
|
908
|
-
|
|
1945
|
+
dot: (props) => {
|
|
1946
|
+
const { cx, cy, payload } = props;
|
|
1947
|
+
if (payload && typeof payload === "object" && "isToday" in payload && payload.isToday) {
|
|
1948
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1949
|
+
"circle",
|
|
1950
|
+
{
|
|
1951
|
+
cx,
|
|
1952
|
+
cy,
|
|
1953
|
+
r: 4,
|
|
1954
|
+
fill: STATUS_BAR_COLORS.today,
|
|
1955
|
+
stroke: "var(--medusa-color-ui-bg-base)",
|
|
1956
|
+
strokeWidth: 1
|
|
1957
|
+
}
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
return /* @__PURE__ */ jsxRuntime.jsx("circle", { cx, cy, r: 0, fill: "transparent" });
|
|
1961
|
+
}
|
|
909
1962
|
}
|
|
910
1963
|
)
|
|
911
1964
|
]
|
|
912
1965
|
}
|
|
913
|
-
) }) })
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1966
|
+
) }) }),
|
|
1967
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/40 px-2 py-1.5 shadow-sm", children: [
|
|
1968
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Note" }),
|
|
1969
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-[11px] leading-snug", children: todayDelta === null ? "Compares the latest day to the 7-day mean." : todayDelta >= 0 ? `Latest day is ${todayDelta.toFixed(1)} above the 7-day average.` : `Latest day is ${Math.abs(todayDelta).toFixed(1)} below the 7-day average.` })
|
|
1970
|
+
] })
|
|
1971
|
+
] }) })
|
|
1972
|
+
}
|
|
1973
|
+
)
|
|
1974
|
+
] }),
|
|
1975
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-1 items-start gap-2 md:grid-cols-3", children: [
|
|
919
1976
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
920
1977
|
AnalyticsSection,
|
|
921
1978
|
{
|
|
922
1979
|
variant: "atlas",
|
|
923
|
-
title: "
|
|
924
|
-
description:
|
|
925
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1980
|
+
title: "Sales by weekday",
|
|
1981
|
+
description: `Order revenue by UTC weekday — last ${insightsWindowDays} days.`,
|
|
1982
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) }) : weekdayChartRows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(176px,28vh)] min-h-[148px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1983
|
+
recharts.BarChart,
|
|
1984
|
+
{
|
|
1985
|
+
data: weekdayChartRows,
|
|
1986
|
+
margin: { top: 2, right: 4, left: -8, bottom: 2 },
|
|
1987
|
+
children: [
|
|
1988
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1989
|
+
recharts.CartesianGrid,
|
|
1990
|
+
{
|
|
1991
|
+
strokeDasharray: "3 3",
|
|
1992
|
+
vertical: false,
|
|
1993
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1994
|
+
}
|
|
1995
|
+
),
|
|
1996
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1997
|
+
recharts.XAxis,
|
|
1998
|
+
{
|
|
1999
|
+
dataKey: "label",
|
|
2000
|
+
tick: CHART_AXIS_TICK
|
|
2001
|
+
}
|
|
2002
|
+
),
|
|
2003
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2004
|
+
recharts.YAxis,
|
|
2005
|
+
{
|
|
2006
|
+
width: 40,
|
|
2007
|
+
tick: CHART_AXIS_TICK_SM,
|
|
2008
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
2009
|
+
}
|
|
2010
|
+
),
|
|
2011
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2012
|
+
recharts.Tooltip,
|
|
2013
|
+
{
|
|
2014
|
+
content: ({ active, payload, label }) => {
|
|
2015
|
+
var _a2, _b2;
|
|
2016
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
2017
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2018
|
+
"Revenue:",
|
|
2019
|
+
" ",
|
|
2020
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
2021
|
+
Number(
|
|
2022
|
+
(_a2 = payload.find((p) => p.dataKey === "revenue")) == null ? void 0 : _a2.value
|
|
2023
|
+
) || 0
|
|
2024
|
+
) })
|
|
2025
|
+
] }),
|
|
2026
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2027
|
+
"Orders:",
|
|
2028
|
+
" ",
|
|
2029
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
2030
|
+
Number(
|
|
2031
|
+
(_b2 = payload.find((p) => p.dataKey === "orders_count")) == null ? void 0 : _b2.value
|
|
2032
|
+
) || 0
|
|
2033
|
+
).toLocaleString() })
|
|
2034
|
+
] })
|
|
2035
|
+
] }) : null;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
),
|
|
2039
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "revenue", name: "Revenue", fill: "#D946EF", radius: [4, 4, 0, 0] })
|
|
2040
|
+
]
|
|
2041
|
+
}
|
|
2042
|
+
) }) }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
926
2043
|
EmptyAnalyticsPanel,
|
|
927
2044
|
{
|
|
928
|
-
title: "
|
|
929
|
-
description: "
|
|
2045
|
+
title: "No weekday data",
|
|
2046
|
+
description: "No orders in this window."
|
|
930
2047
|
}
|
|
931
2048
|
)
|
|
932
2049
|
}
|
|
@@ -935,303 +2052,142 @@ function OrdersDashboard() {
|
|
|
935
2052
|
AnalyticsSection,
|
|
936
2053
|
{
|
|
937
2054
|
variant: "atlas",
|
|
938
|
-
title: "
|
|
939
|
-
description: "
|
|
940
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
941
|
-
|
|
942
|
-
{
|
|
943
|
-
title: "Source mix",
|
|
944
|
-
description: "Channel breakdown (direct, search, social, email) will appear here once connected."
|
|
945
|
-
}
|
|
946
|
-
)
|
|
947
|
-
}
|
|
948
|
-
)
|
|
949
|
-
] })
|
|
950
|
-
] }),
|
|
951
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-1.5", children: [
|
|
952
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Operational signals" }) }),
|
|
953
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 lg:grid-cols-3 xl:grid-cols-6", children: secondaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
954
|
-
AnalyticsStatCard,
|
|
955
|
-
{
|
|
956
|
-
label: stat.label,
|
|
957
|
-
value: stat.value,
|
|
958
|
-
helper: stat.helper,
|
|
959
|
-
variant: "compact"
|
|
960
|
-
},
|
|
961
|
-
stat.label
|
|
962
|
-
)) })
|
|
963
|
-
] }),
|
|
964
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-1 items-start gap-3 xl:grid-cols-12", children: [
|
|
965
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
966
|
-
AnalyticsSection,
|
|
967
|
-
{
|
|
968
|
-
variant: "atlas",
|
|
969
|
-
className: "xl:col-span-7",
|
|
970
|
-
title: "Order outcomes",
|
|
971
|
-
description: "Delivered, cancelled, exchanges, and returns per bucket — same window as Revenue & orders.",
|
|
972
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[200px] sm:h-[220px]", 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 text-xs", children: "Loading…" }) }) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: overTimeError }) }) : !outcomesTimeSeriesHasPoints ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "No outcome events in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
973
|
-
recharts.LineChart,
|
|
974
|
-
{
|
|
975
|
-
data: dailyOrders,
|
|
976
|
-
margin: { top: 4, right: 8, left: 0, bottom: 0 },
|
|
977
|
-
children: [
|
|
978
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
979
|
-
recharts.CartesianGrid,
|
|
980
|
-
{
|
|
981
|
-
stroke: "rgba(148,163,184,0.14)",
|
|
982
|
-
strokeDasharray: "3 3",
|
|
983
|
-
vertical: false
|
|
984
|
-
}
|
|
985
|
-
),
|
|
986
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
987
|
-
recharts.XAxis,
|
|
988
|
-
{
|
|
989
|
-
dataKey: "date",
|
|
990
|
-
tick: { fontSize: 10, fill: "var(--medusa-color-ui-fg-muted)" },
|
|
991
|
-
tickLine: false,
|
|
992
|
-
axisLine: false,
|
|
993
|
-
tickFormatter: (value) => {
|
|
994
|
-
const dateStr = typeof value === "string" ? value : String(value);
|
|
995
|
-
const row = dailyOrders.find((d) => d.date === dateStr);
|
|
996
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel(dateStr);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
),
|
|
2055
|
+
title: "Placed orders vs drafts",
|
|
2056
|
+
description: "Window order count vs current open draft orders (admin). Not storefront cart abandonment.",
|
|
2057
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) }) : ordersVsDraftsPie.some((s) => s.value > 0) ? /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
2058
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(176px,28vh)] min-h-[148px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 2, bottom: 2, left: 2, right: 2 }, children: [
|
|
1000
2059
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1001
|
-
recharts.
|
|
2060
|
+
recharts.Pie,
|
|
1002
2061
|
{
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
2062
|
+
data: ordersVsDraftsPie,
|
|
2063
|
+
dataKey: "value",
|
|
2064
|
+
nameKey: "name",
|
|
2065
|
+
cx: "50%",
|
|
2066
|
+
cy: "46%",
|
|
2067
|
+
innerRadius: 40,
|
|
2068
|
+
outerRadius: 58,
|
|
2069
|
+
paddingAngle: 2,
|
|
2070
|
+
children: ordersVsDraftsPie.map((slice, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2071
|
+
recharts.Cell,
|
|
2072
|
+
{
|
|
2073
|
+
fill: PIE_PALETTE[i % PIE_PALETTE.length]
|
|
2074
|
+
},
|
|
2075
|
+
slice.key
|
|
2076
|
+
))
|
|
1009
2077
|
}
|
|
1010
2078
|
),
|
|
1011
2079
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1012
2080
|
recharts.Tooltip,
|
|
1013
2081
|
{
|
|
1014
|
-
content:
|
|
1015
|
-
|
|
2082
|
+
content: ({ active, payload }) => {
|
|
2083
|
+
var _a2, _b2;
|
|
2084
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(TooltipCard, { title: String(((_a2 = payload[0]) == null ? void 0 : _a2.name) ?? ""), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2085
|
+
"Count:",
|
|
2086
|
+
" ",
|
|
2087
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
2088
|
+
Number((_b2 = payload[0]) == null ? void 0 : _b2.value) || 0
|
|
2089
|
+
).toLocaleString() })
|
|
2090
|
+
] }) }) : null;
|
|
2091
|
+
}
|
|
1016
2092
|
}
|
|
1017
2093
|
),
|
|
1018
2094
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1019
2095
|
recharts.Legend,
|
|
1020
2096
|
{
|
|
1021
2097
|
wrapperStyle: { fontSize: 9, paddingTop: 0 },
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
),
|
|
1026
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1027
|
-
recharts.Line,
|
|
1028
|
-
{
|
|
1029
|
-
type: "natural",
|
|
1030
|
-
dataKey: "delivered_count",
|
|
1031
|
-
name: "Delivered",
|
|
1032
|
-
stroke: STATUS_BAR_COLORS.delivered,
|
|
1033
|
-
strokeWidth: 2,
|
|
1034
|
-
dot: false
|
|
1035
|
-
}
|
|
1036
|
-
),
|
|
1037
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1038
|
-
recharts.Line,
|
|
1039
|
-
{
|
|
1040
|
-
type: "natural",
|
|
1041
|
-
dataKey: "cancelled_count",
|
|
1042
|
-
name: "Cancelled",
|
|
1043
|
-
stroke: STATUS_BAR_COLORS.cancelled,
|
|
1044
|
-
strokeWidth: 2,
|
|
1045
|
-
dot: false
|
|
1046
|
-
}
|
|
1047
|
-
),
|
|
1048
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1049
|
-
recharts.Line,
|
|
1050
|
-
{
|
|
1051
|
-
type: "natural",
|
|
1052
|
-
dataKey: "exchange_count",
|
|
1053
|
-
name: "Exchanges",
|
|
1054
|
-
stroke: STATUS_BAR_COLORS.exchange,
|
|
1055
|
-
strokeWidth: 2,
|
|
1056
|
-
dot: false
|
|
1057
|
-
}
|
|
1058
|
-
),
|
|
1059
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1060
|
-
recharts.Line,
|
|
1061
|
-
{
|
|
1062
|
-
type: "natural",
|
|
1063
|
-
dataKey: "return_count",
|
|
1064
|
-
name: "Returns",
|
|
1065
|
-
stroke: STATUS_BAR_COLORS.return,
|
|
1066
|
-
strokeWidth: 2,
|
|
1067
|
-
dot: false
|
|
2098
|
+
verticalAlign: "bottom",
|
|
2099
|
+
height: 22,
|
|
2100
|
+
iconType: "circle"
|
|
1068
2101
|
}
|
|
1069
2102
|
)
|
|
1070
|
-
]
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "7-day avg" }),
|
|
1090
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-base font-semibold tracking-tight", children: todayOrdersAverage.toFixed(1) })
|
|
1091
|
-
] })
|
|
1092
|
-
] }),
|
|
1093
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[160px] sm:h-[180px]", children: todayContextLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : todayContextError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: todayContextError }) }) : todayChartData.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 text-xs", children: "No recent activity." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1094
|
-
recharts.ComposedChart,
|
|
2103
|
+
] }) }) }),
|
|
2104
|
+
(ordersInsights == null ? void 0 : ordersInsights.drafts.available) === false ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted mt-0.5 px-0.5 text-[10px] leading-snug", children: "Draft totals need draft_order remote query access." }) : null
|
|
2105
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2106
|
+
EmptyAnalyticsPanel,
|
|
2107
|
+
{
|
|
2108
|
+
title: "Nothing to compare",
|
|
2109
|
+
description: "No placed orders and no draft rows in this snapshot."
|
|
2110
|
+
}
|
|
2111
|
+
)
|
|
2112
|
+
}
|
|
2113
|
+
),
|
|
2114
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2115
|
+
AnalyticsSection,
|
|
2116
|
+
{
|
|
2117
|
+
variant: "atlas",
|
|
2118
|
+
title: "Sales by hour (UTC)",
|
|
2119
|
+
description: `Orders created by hour — last ${insightsWindowDays} days.`,
|
|
2120
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) }) : hourlyChartRows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(176px,28vh)] min-h-[148px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2121
|
+
recharts.BarChart,
|
|
1095
2122
|
{
|
|
1096
|
-
data:
|
|
1097
|
-
margin: { top:
|
|
2123
|
+
data: hourlyChartRows,
|
|
2124
|
+
margin: { top: 2, right: 4, left: -8, bottom: 2 },
|
|
1098
2125
|
children: [
|
|
1099
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "todayOrdersFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1100
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#3b82f6", stopOpacity: 0.2 }),
|
|
1101
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3b82f6", stopOpacity: 0 })
|
|
1102
|
-
] }) }),
|
|
1103
2126
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1104
2127
|
recharts.CartesianGrid,
|
|
1105
2128
|
{
|
|
1106
|
-
stroke: "rgba(148,163,184,0.14)",
|
|
1107
2129
|
strokeDasharray: "3 3",
|
|
1108
|
-
vertical: false
|
|
2130
|
+
vertical: false,
|
|
2131
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1109
2132
|
}
|
|
1110
2133
|
),
|
|
1111
2134
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1112
2135
|
recharts.XAxis,
|
|
1113
2136
|
{
|
|
1114
|
-
dataKey: "
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
axisLine: false,
|
|
1118
|
-
tickFormatter: formatChartLabel
|
|
2137
|
+
dataKey: "label",
|
|
2138
|
+
interval: 3,
|
|
2139
|
+
tick: CHART_AXIS_TICK_SM
|
|
1119
2140
|
}
|
|
1120
2141
|
),
|
|
1121
2142
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1122
2143
|
recharts.YAxis,
|
|
1123
2144
|
{
|
|
1124
2145
|
width: 28,
|
|
1125
|
-
tick:
|
|
1126
|
-
|
|
1127
|
-
axisLine: false,
|
|
1128
|
-
allowDecimals: false,
|
|
1129
|
-
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1130
|
-
}
|
|
1131
|
-
),
|
|
1132
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersTodayTooltip, {}), cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 } }),
|
|
1133
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1134
|
-
recharts.ReferenceLine,
|
|
1135
|
-
{
|
|
1136
|
-
y: todayOrdersAverage,
|
|
1137
|
-
stroke: "rgba(148,163,184,0.65)",
|
|
1138
|
-
strokeDasharray: "4 4"
|
|
1139
|
-
}
|
|
1140
|
-
),
|
|
1141
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1142
|
-
recharts.Area,
|
|
1143
|
-
{
|
|
1144
|
-
type: "natural",
|
|
1145
|
-
dataKey: "orders_count",
|
|
1146
|
-
stroke: "none",
|
|
1147
|
-
fill: "url(#todayOrdersFill)",
|
|
1148
|
-
isAnimationActive: false
|
|
2146
|
+
tick: CHART_AXIS_TICK,
|
|
2147
|
+
allowDecimals: false
|
|
1149
2148
|
}
|
|
1150
2149
|
),
|
|
1151
2150
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1152
|
-
recharts.
|
|
2151
|
+
recharts.Tooltip,
|
|
1153
2152
|
{
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
2153
|
+
content: ({ active, payload, label }) => {
|
|
2154
|
+
var _a2, _b2;
|
|
2155
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
2156
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2157
|
+
"Orders:",
|
|
2158
|
+
" ",
|
|
2159
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
2160
|
+
Number(
|
|
2161
|
+
(_a2 = payload.find((p) => p.dataKey === "orders_count")) == null ? void 0 : _a2.value
|
|
2162
|
+
) || 0
|
|
2163
|
+
).toLocaleString() })
|
|
2164
|
+
] }),
|
|
2165
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2166
|
+
"Revenue:",
|
|
2167
|
+
" ",
|
|
2168
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
2169
|
+
Number(
|
|
2170
|
+
(_b2 = payload.find((p) => p.dataKey === "revenue")) == null ? void 0 : _b2.value
|
|
2171
|
+
) || 0
|
|
2172
|
+
) })
|
|
2173
|
+
] })
|
|
2174
|
+
] }) : null;
|
|
1175
2175
|
}
|
|
1176
2176
|
}
|
|
1177
|
-
)
|
|
2177
|
+
),
|
|
2178
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "orders_count", name: "Orders", fill: "#38BDF8", radius: [4, 4, 0, 0] })
|
|
1178
2179
|
]
|
|
1179
2180
|
}
|
|
1180
|
-
) }) })
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1191
|
-
AnalyticsSection,
|
|
1192
|
-
{
|
|
1193
|
-
variant: "atlas",
|
|
1194
|
-
title: "Revenue breakdown",
|
|
1195
|
-
description: "Category or source contribution — time series when connected.",
|
|
1196
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1197
|
-
EmptyAnalyticsPanel,
|
|
1198
|
-
{
|
|
1199
|
-
title: "Stacked breakdown",
|
|
1200
|
-
description: "Connect product or attribution dimensions to show stacked area over time."
|
|
1201
|
-
}
|
|
1202
|
-
)
|
|
1203
|
-
}
|
|
1204
|
-
),
|
|
1205
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1206
|
-
AnalyticsSection,
|
|
1207
|
-
{
|
|
1208
|
-
variant: "atlas",
|
|
1209
|
-
title: "Cart abandonment",
|
|
1210
|
-
description: "Converted vs abandoned — time series when cart analytics exist.",
|
|
1211
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1212
|
-
EmptyAnalyticsPanel,
|
|
1213
|
-
{
|
|
1214
|
-
title: "Checkout leakage",
|
|
1215
|
-
description: "Donut and trend charts will use cart lifecycle data once available."
|
|
1216
|
-
}
|
|
1217
|
-
)
|
|
1218
|
-
}
|
|
1219
|
-
),
|
|
1220
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1221
|
-
AnalyticsSection,
|
|
1222
|
-
{
|
|
1223
|
-
variant: "atlas",
|
|
1224
|
-
title: "Sales by time",
|
|
1225
|
-
description: "Hourly or intraday patterns — bar time series when connected.",
|
|
1226
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1227
|
-
EmptyAnalyticsPanel,
|
|
1228
|
-
{
|
|
1229
|
-
title: "Sales by hour",
|
|
1230
|
-
description: "Order timestamps by hour-of-day will render as a compact bar time series."
|
|
1231
|
-
}
|
|
1232
|
-
)
|
|
1233
|
-
}
|
|
1234
|
-
)
|
|
2181
|
+
) }) }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2182
|
+
EmptyAnalyticsPanel,
|
|
2183
|
+
{
|
|
2184
|
+
title: "No hourly data",
|
|
2185
|
+
description: "No orders in this window."
|
|
2186
|
+
}
|
|
2187
|
+
)
|
|
2188
|
+
}
|
|
2189
|
+
)
|
|
2190
|
+
] })
|
|
1235
2191
|
] })
|
|
1236
2192
|
] }) });
|
|
1237
2193
|
}
|
|
@@ -1273,6 +2229,17 @@ function CustomersOverTimeTooltip({
|
|
|
1273
2229
|
}
|
|
1274
2230
|
);
|
|
1275
2231
|
}
|
|
2232
|
+
function MixPieTooltip({
|
|
2233
|
+
active,
|
|
2234
|
+
payload
|
|
2235
|
+
}) {
|
|
2236
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2237
|
+
const row = payload[0];
|
|
2238
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm shadow-md", children: [
|
|
2239
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium text-ui-fg-base", children: row.name }),
|
|
2240
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: Math.floor(Number(row.value) || 0).toLocaleString() })
|
|
2241
|
+
] });
|
|
2242
|
+
}
|
|
1276
2243
|
const COUNT_DAY_PRESETS = [
|
|
1277
2244
|
{ value: "all", label: "All time" },
|
|
1278
2245
|
{ value: "0", label: "Today" },
|
|
@@ -1296,12 +2263,15 @@ function CustomersDashboard() {
|
|
|
1296
2263
|
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
1297
2264
|
const [graphLoading, setGraphLoading] = react.useState(true);
|
|
1298
2265
|
const [graphError, setGraphError] = react.useState(null);
|
|
2266
|
+
const [repeatStats, setRepeatStats] = react.useState(null);
|
|
2267
|
+
const [repeatLoading, setRepeatLoading] = react.useState(true);
|
|
2268
|
+
const [repeatError, setRepeatError] = react.useState(null);
|
|
1299
2269
|
react.useEffect(() => {
|
|
1300
2270
|
let cancelled = false;
|
|
1301
2271
|
setLoading(true);
|
|
1302
2272
|
setError(null);
|
|
1303
2273
|
const url = `/admin/analytics/customers-summary?days=${countDays}`;
|
|
1304
|
-
fetch(url).then((res) => {
|
|
2274
|
+
fetch(url, { credentials: "include" }).then((res) => {
|
|
1305
2275
|
if (!res.ok) throw new Error(res.statusText);
|
|
1306
2276
|
return res.json();
|
|
1307
2277
|
}).then((body) => {
|
|
@@ -1320,7 +2290,7 @@ function CustomersDashboard() {
|
|
|
1320
2290
|
setGraphLoading(true);
|
|
1321
2291
|
setGraphError(null);
|
|
1322
2292
|
const url = `/admin/analytics/customers-over-time?period=${graphPeriod}`;
|
|
1323
|
-
fetch(url).then((res) => {
|
|
2293
|
+
fetch(url, { credentials: "include" }).then((res) => {
|
|
1324
2294
|
if (!res.ok) throw new Error(res.statusText);
|
|
1325
2295
|
return res.json();
|
|
1326
2296
|
}).then((body) => {
|
|
@@ -1334,6 +2304,34 @@ function CustomersDashboard() {
|
|
|
1334
2304
|
cancelled = true;
|
|
1335
2305
|
};
|
|
1336
2306
|
}, [graphPeriod]);
|
|
2307
|
+
react.useEffect(() => {
|
|
2308
|
+
let cancelled = false;
|
|
2309
|
+
setRepeatLoading(true);
|
|
2310
|
+
setRepeatError(null);
|
|
2311
|
+
fetch(`/admin/analytics/repeat-customers?days=${countDays}`, {
|
|
2312
|
+
credentials: "include"
|
|
2313
|
+
}).then((res) => {
|
|
2314
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
2315
|
+
return res.json();
|
|
2316
|
+
}).then((body) => {
|
|
2317
|
+
if (!cancelled) setRepeatStats(body);
|
|
2318
|
+
}).catch((e) => {
|
|
2319
|
+
if (!cancelled) setRepeatError(e instanceof Error ? e.message : String(e));
|
|
2320
|
+
}).finally(() => {
|
|
2321
|
+
if (!cancelled) setRepeatLoading(false);
|
|
2322
|
+
});
|
|
2323
|
+
return () => {
|
|
2324
|
+
cancelled = true;
|
|
2325
|
+
};
|
|
2326
|
+
}, [countDays]);
|
|
2327
|
+
const guestRegisteredPieData = react.useMemo(() => {
|
|
2328
|
+
const g = Math.max(0, Math.floor((data == null ? void 0 : data.guestCount) ?? 0));
|
|
2329
|
+
const r = Math.max(0, Math.floor((data == null ? void 0 : data.registeredCount) ?? 0));
|
|
2330
|
+
return [
|
|
2331
|
+
{ name: "Guest checkout", value: g, key: "guest" },
|
|
2332
|
+
{ name: "Registered", value: r, key: "registered" }
|
|
2333
|
+
].filter((s) => s.value > 0);
|
|
2334
|
+
}, [data]);
|
|
1337
2335
|
if (loading) {
|
|
1338
2336
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1339
2337
|
"div",
|
|
@@ -1418,6 +2416,70 @@ function CustomersDashboard() {
|
|
|
1418
2416
|
}
|
|
1419
2417
|
)
|
|
1420
2418
|
] }),
|
|
2419
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-2", children: [
|
|
2420
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2421
|
+
AnalyticsSection,
|
|
2422
|
+
{
|
|
2423
|
+
title: "Guest vs registered",
|
|
2424
|
+
description: "Customer mix for the selected count period (same filter as the summary cards).",
|
|
2425
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: guestRegisteredPieData.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 text-sm", children: "No guest or registered customers in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { children: [
|
|
2426
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2427
|
+
recharts.Pie,
|
|
2428
|
+
{
|
|
2429
|
+
data: guestRegisteredPieData,
|
|
2430
|
+
dataKey: "value",
|
|
2431
|
+
nameKey: "name",
|
|
2432
|
+
cx: "50%",
|
|
2433
|
+
cy: "50%",
|
|
2434
|
+
innerRadius: 52,
|
|
2435
|
+
outerRadius: 88,
|
|
2436
|
+
paddingAngle: 2,
|
|
2437
|
+
children: guestRegisteredPieData.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2438
|
+
recharts.Cell,
|
|
2439
|
+
{
|
|
2440
|
+
fill: entry.key === "guest" ? GUEST_COLOR : REGISTERED_COLOR
|
|
2441
|
+
},
|
|
2442
|
+
entry.key
|
|
2443
|
+
))
|
|
2444
|
+
}
|
|
2445
|
+
),
|
|
2446
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(MixPieTooltip, {}) }),
|
|
2447
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { wrapperStyle: { fontSize: 12 }, iconType: "circle" })
|
|
2448
|
+
] }) }) }) })
|
|
2449
|
+
}
|
|
2450
|
+
),
|
|
2451
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2452
|
+
AnalyticsSection,
|
|
2453
|
+
{
|
|
2454
|
+
title: "Repeat purchases",
|
|
2455
|
+
description: "Orders with a customer_id in the same window as the period filter (guest checkouts excluded from rate).",
|
|
2456
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: repeatLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[200px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Loading…" }) }) : repeatError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[200px] items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-sm", children: repeatError }) }) : repeatStats ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [
|
|
2457
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2458
|
+
AnalyticsStatCard,
|
|
2459
|
+
{
|
|
2460
|
+
label: "Repeat rate",
|
|
2461
|
+
value: `${repeatStats.repeat_rate_percent.toFixed(1)}%`,
|
|
2462
|
+
helper: repeatStats.window_days >= 365 ? "Approx. 12-month window" : `Last ${repeatStats.window_days} days`
|
|
2463
|
+
}
|
|
2464
|
+
),
|
|
2465
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2466
|
+
AnalyticsStatCard,
|
|
2467
|
+
{
|
|
2468
|
+
label: "Customers with orders",
|
|
2469
|
+
value: repeatStats.customers_with_orders.toLocaleString()
|
|
2470
|
+
}
|
|
2471
|
+
),
|
|
2472
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2473
|
+
AnalyticsStatCard,
|
|
2474
|
+
{
|
|
2475
|
+
label: "Repeat customers (2+)",
|
|
2476
|
+
value: repeatStats.repeat_customers.toLocaleString()
|
|
2477
|
+
}
|
|
2478
|
+
)
|
|
2479
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[200px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No data." }) }) })
|
|
2480
|
+
}
|
|
2481
|
+
)
|
|
2482
|
+
] }),
|
|
1421
2483
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1422
2484
|
AnalyticsSection,
|
|
1423
2485
|
{
|
|
@@ -1809,6 +2871,10 @@ function ProductsDashboard() {
|
|
|
1809
2871
|
})).reverse(),
|
|
1810
2872
|
[performance]
|
|
1811
2873
|
);
|
|
2874
|
+
const viewsVsUnitsScatterData = react.useMemo(
|
|
2875
|
+
() => ((performance == null ? void 0 : performance.topViewedProducts) ?? []).slice(0, 48).filter((row) => row.total_views > 0 || row.units_sold > 0),
|
|
2876
|
+
[performance]
|
|
2877
|
+
);
|
|
1812
2878
|
if (summaryLoading) {
|
|
1813
2879
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1814
2880
|
"div",
|
|
@@ -2154,7 +3220,66 @@ function ProductsDashboard() {
|
|
|
2154
3220
|
] }) })
|
|
2155
3221
|
}
|
|
2156
3222
|
)
|
|
2157
|
-
] })
|
|
3223
|
+
] }),
|
|
3224
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3225
|
+
AnalyticsSection,
|
|
3226
|
+
{
|
|
3227
|
+
title: "Views vs units sold",
|
|
3228
|
+
description: "Each point is a product (top viewed in this period). Requires product view tracking.",
|
|
3229
|
+
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 scatter…" }) }) : 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." }) }) : viewsVsUnitsScatterData.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 products with views or sales in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.ScatterChart, { margin: { top: 12, right: 16, bottom: 8, left: 8 }, children: [
|
|
3230
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: "rgba(148,163,184,0.2)" }),
|
|
3231
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3232
|
+
recharts.XAxis,
|
|
3233
|
+
{
|
|
3234
|
+
type: "number",
|
|
3235
|
+
dataKey: "total_views",
|
|
3236
|
+
name: "Views",
|
|
3237
|
+
tick: { fontSize: 11 },
|
|
3238
|
+
allowDecimals: false,
|
|
3239
|
+
label: {
|
|
3240
|
+
value: "Views",
|
|
3241
|
+
position: "insideBottom",
|
|
3242
|
+
offset: -4,
|
|
3243
|
+
fontSize: 11,
|
|
3244
|
+
fill: "#6b7280"
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
),
|
|
3248
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3249
|
+
recharts.YAxis,
|
|
3250
|
+
{
|
|
3251
|
+
type: "number",
|
|
3252
|
+
dataKey: "units_sold",
|
|
3253
|
+
name: "Units sold",
|
|
3254
|
+
tick: { fontSize: 11 },
|
|
3255
|
+
allowDecimals: false,
|
|
3256
|
+
label: {
|
|
3257
|
+
value: "Units sold",
|
|
3258
|
+
angle: -90,
|
|
3259
|
+
position: "insideLeft",
|
|
3260
|
+
fontSize: 11,
|
|
3261
|
+
fill: "#6b7280"
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
),
|
|
3265
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3266
|
+
recharts.Tooltip,
|
|
3267
|
+
{
|
|
3268
|
+
cursor: { strokeDasharray: "4 4" },
|
|
3269
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {})
|
|
3270
|
+
}
|
|
3271
|
+
),
|
|
3272
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3273
|
+
recharts.Scatter,
|
|
3274
|
+
{
|
|
3275
|
+
name: "Products",
|
|
3276
|
+
data: viewsVsUnitsScatterData,
|
|
3277
|
+
fill: VIEWS_COLOR
|
|
3278
|
+
}
|
|
3279
|
+
)
|
|
3280
|
+
] }) }) }) })
|
|
3281
|
+
}
|
|
3282
|
+
)
|
|
2158
3283
|
] });
|
|
2159
3284
|
}
|
|
2160
3285
|
const ANALYTICS_MODULES = [
|
|
@@ -2171,16 +3296,29 @@ const AnalyticsPage = () => {
|
|
|
2171
3296
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "tracking-tight", children: "Analytics" }),
|
|
2172
3297
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Review orders, customers, and products in one organized workspace." })
|
|
2173
3298
|
] }),
|
|
2174
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2175
|
-
|
|
3299
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3300
|
+
"div",
|
|
2176
3301
|
{
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
children: m
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
3302
|
+
className: "flex flex-wrap items-center gap-1 rounded-2xl border border-ui-border-base bg-ui-bg-subtle p-1",
|
|
3303
|
+
role: "tablist",
|
|
3304
|
+
"aria-label": "Analytics modules",
|
|
3305
|
+
children: ANALYTICS_MODULES.map((m) => {
|
|
3306
|
+
const active = activeModule === m.id;
|
|
3307
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3308
|
+
"button",
|
|
3309
|
+
{
|
|
3310
|
+
type: "button",
|
|
3311
|
+
role: "tab",
|
|
3312
|
+
"aria-selected": active,
|
|
3313
|
+
onClick: () => setActiveModule(m.id),
|
|
3314
|
+
className: `rounded-xl px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ui-fg-interactive focus-visible:ring-offset-2 focus-visible:ring-offset-ui-bg-base ${active ? "bg-ui-bg-base text-ui-fg-base shadow-sm" : "text-ui-fg-muted hover:bg-ui-bg-base/60 hover:text-ui-fg-base"}`.trim(),
|
|
3315
|
+
children: m.label
|
|
3316
|
+
},
|
|
3317
|
+
m.id
|
|
3318
|
+
);
|
|
3319
|
+
})
|
|
3320
|
+
}
|
|
3321
|
+
)
|
|
2184
3322
|
] }),
|
|
2185
3323
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1 bg-ui-bg-subtle" })
|
|
2186
3324
|
] }),
|