medusa-analytics 0.0.23 → 0.0.24
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 +530 -338
- package/.medusa/server/src/admin/index.mjs +532 -340
- package/.medusa/server/src/api/admin/analytics/fetch-orders-for-analytics.js +21 -9
- package/.medusa/server/src/api/admin/analytics/orders-analytics-meta.js +18 -0
- package/.medusa/server/src/api/admin/analytics/orders-insights/route.js +3 -2
- package/.medusa/server/src/api/admin/analytics/orders-over-time/route.js +3 -2
- package/.medusa/server/src/api/admin/analytics/orders-summary/route.js +3 -2
- package/.medusa/server/src/api/admin/analytics/repeat-customers/route.js +3 -2
- package/.medusa/server/src/api/admin/analytics/store-context/resolve-default-currency-code.js +86 -0
- package/.medusa/server/src/api/admin/analytics/store-context/route.js +19 -0
- package/.medusa/server/src/api/admin/analytics/store-context/types.js +3 -0
- package/package.json +4 -2
|
@@ -5,6 +5,64 @@ const adminSdk = require("@medusajs/admin-sdk");
|
|
|
5
5
|
const icons = require("@medusajs/icons");
|
|
6
6
|
const ui = require("@medusajs/ui");
|
|
7
7
|
const recharts = require("recharts");
|
|
8
|
+
const AnalyticsCurrencyContext = react.createContext(
|
|
9
|
+
null
|
|
10
|
+
);
|
|
11
|
+
function formatMoney(value, currencyCode, compact) {
|
|
12
|
+
return new Intl.NumberFormat(void 0, {
|
|
13
|
+
style: "currency",
|
|
14
|
+
currency: currencyCode,
|
|
15
|
+
minimumFractionDigits: 0,
|
|
16
|
+
maximumFractionDigits: compact ? 1 : 0,
|
|
17
|
+
...compact ? { notation: "compact" } : {}
|
|
18
|
+
}).format(value);
|
|
19
|
+
}
|
|
20
|
+
function AnalyticsCurrencyProvider({ children }) {
|
|
21
|
+
const [currencyCode, setCurrencyCode] = react.useState("USD");
|
|
22
|
+
react.useEffect(() => {
|
|
23
|
+
let cancelled = false;
|
|
24
|
+
fetch("/admin/analytics/store-context", { credentials: "include" }).then((res) => {
|
|
25
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
26
|
+
return res.json();
|
|
27
|
+
}).then((body) => {
|
|
28
|
+
if (cancelled) return;
|
|
29
|
+
const code = typeof body.default_currency_code === "string" ? body.default_currency_code.trim().toUpperCase() : "";
|
|
30
|
+
if (code && /^[A-Z]{3}$/.test(code)) {
|
|
31
|
+
setCurrencyCode(code);
|
|
32
|
+
}
|
|
33
|
+
}).catch(() => {
|
|
34
|
+
if (!cancelled) setCurrencyCode("USD");
|
|
35
|
+
});
|
|
36
|
+
return () => {
|
|
37
|
+
cancelled = true;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
const formatCurrency = react.useCallback(
|
|
41
|
+
(value2) => formatMoney(value2, currencyCode, false),
|
|
42
|
+
[currencyCode]
|
|
43
|
+
);
|
|
44
|
+
const formatCompactCurrency = react.useCallback(
|
|
45
|
+
(value2) => formatMoney(value2, currencyCode, true),
|
|
46
|
+
[currencyCode]
|
|
47
|
+
);
|
|
48
|
+
const value = react.useMemo(
|
|
49
|
+
() => ({
|
|
50
|
+
currencyCode,
|
|
51
|
+
formatCurrency,
|
|
52
|
+
formatCompactCurrency
|
|
53
|
+
}),
|
|
54
|
+
[currencyCode, formatCurrency, formatCompactCurrency]
|
|
55
|
+
);
|
|
56
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsCurrencyContext.Provider, { value, children });
|
|
57
|
+
}
|
|
58
|
+
function useAnalyticsCurrency() {
|
|
59
|
+
const ctx = react.useContext(AnalyticsCurrencyContext);
|
|
60
|
+
if (!ctx) {
|
|
61
|
+
throw new Error("useAnalyticsCurrency must be used within AnalyticsCurrencyProvider");
|
|
62
|
+
}
|
|
63
|
+
return ctx;
|
|
64
|
+
}
|
|
65
|
+
const ORDERS_ANALYTICS_MAX_FETCH = 5e4;
|
|
8
66
|
const ACCENT_STYLES = {
|
|
9
67
|
blue: {
|
|
10
68
|
border: "border-sky-400/30",
|
|
@@ -263,30 +321,9 @@ function nonCancelledRevenue(d) {
|
|
|
263
321
|
}
|
|
264
322
|
return Math.max(0, d.total_revenue - (d.revenue_cancelled ?? 0));
|
|
265
323
|
}
|
|
266
|
-
function bucketAov(d) {
|
|
267
|
-
const ordNc = d.orders_count - d.cancelled_count;
|
|
268
|
-
if (ordNc <= 0) return 0;
|
|
269
|
-
return nonCancelledRevenue(d) / ordNc;
|
|
270
|
-
}
|
|
271
324
|
function isOrdersTodayPoint(value) {
|
|
272
325
|
return isDailyOrderRow(value) && "isToday" in value && typeof value.isToday === "boolean";
|
|
273
326
|
}
|
|
274
|
-
function formatCurrency$1(value) {
|
|
275
|
-
return new Intl.NumberFormat("en-IN", {
|
|
276
|
-
style: "currency",
|
|
277
|
-
currency: "INR",
|
|
278
|
-
minimumFractionDigits: 0,
|
|
279
|
-
maximumFractionDigits: 0
|
|
280
|
-
}).format(value);
|
|
281
|
-
}
|
|
282
|
-
function formatCompactCurrency$1(value) {
|
|
283
|
-
return new Intl.NumberFormat("en-IN", {
|
|
284
|
-
style: "currency",
|
|
285
|
-
currency: "INR",
|
|
286
|
-
notation: "compact",
|
|
287
|
-
maximumFractionDigits: 1
|
|
288
|
-
}).format(value);
|
|
289
|
-
}
|
|
290
327
|
function formatChartLabel$2(value) {
|
|
291
328
|
if (!value) return "";
|
|
292
329
|
const parsed = new Date(value);
|
|
@@ -383,13 +420,14 @@ function SalesBreakdownTooltip({
|
|
|
383
420
|
payload
|
|
384
421
|
}) {
|
|
385
422
|
var _a, _b;
|
|
423
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
386
424
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
387
425
|
const row = isSalesBreakdownBarRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
388
426
|
if (!row) return null;
|
|
389
427
|
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { variant: "compact", title: row.label, children: [
|
|
390
428
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
391
429
|
"Revenue: ",
|
|
392
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency
|
|
430
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(row.revenue) })
|
|
393
431
|
] }),
|
|
394
432
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
395
433
|
"Orders:",
|
|
@@ -432,52 +470,223 @@ function OutcomesTrendTooltip({
|
|
|
432
470
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(entry.value) || 0).toLocaleString() })
|
|
433
471
|
] }, String(entry.name))) });
|
|
434
472
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
473
|
+
const TREND_MIX_PIE_COLORS = {
|
|
474
|
+
revenue: "#c084fc",
|
|
475
|
+
orders: "#7dd3fc",
|
|
476
|
+
aov: "#fdba74"
|
|
477
|
+
};
|
|
478
|
+
function buildTrendMixPieSlices(dailyOrders, trendRevenueTotal, trendOrdersTotal, formatCompactCurrencyFn, formatCurrencyFn) {
|
|
479
|
+
const ncRev = dailyOrders.reduce((s, d) => s + nonCancelledRevenue(d), 0);
|
|
480
|
+
const ncOrd = dailyOrders.reduce(
|
|
481
|
+
(s, d) => s + Math.max(0, d.orders_count - d.cancelled_count),
|
|
482
|
+
0
|
|
483
|
+
);
|
|
484
|
+
const windowAov = ncOrd > 0 ? ncRev / ncOrd : 0;
|
|
485
|
+
const lr = Math.log1p(Math.max(trendRevenueTotal, 0));
|
|
486
|
+
const lo = Math.log1p(Math.max(trendOrdersTotal, 0));
|
|
487
|
+
const la = Math.log1p(Math.max(windowAov, 0));
|
|
488
|
+
const sumW = lr + lo + la;
|
|
489
|
+
const share = (w) => sumW > 0 ? w / sumW * 100 : 100 / 3;
|
|
490
|
+
const sR = share(lr);
|
|
491
|
+
const sO = share(lo);
|
|
492
|
+
const sA = share(la);
|
|
493
|
+
return [
|
|
494
|
+
{
|
|
495
|
+
key: "revenue",
|
|
496
|
+
name: "Revenue",
|
|
497
|
+
value: sR,
|
|
498
|
+
fill: TREND_MIX_PIE_COLORS.revenue,
|
|
499
|
+
sharePercent: sR,
|
|
500
|
+
formattedPrimary: formatCompactCurrencyFn(trendRevenueTotal),
|
|
501
|
+
formattedDetail: "Total revenue for this range, all days summed."
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
key: "orders",
|
|
505
|
+
name: "Orders",
|
|
506
|
+
value: sO,
|
|
507
|
+
fill: TREND_MIX_PIE_COLORS.orders,
|
|
508
|
+
sharePercent: sO,
|
|
509
|
+
formattedPrimary: Math.floor(trendOrdersTotal).toLocaleString(),
|
|
510
|
+
formattedDetail: "Order count in this range from daily totals."
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
key: "aov",
|
|
514
|
+
name: "AOV",
|
|
515
|
+
value: sA,
|
|
516
|
+
fill: TREND_MIX_PIE_COLORS.aov,
|
|
517
|
+
sharePercent: sA,
|
|
518
|
+
formattedPrimary: formatCurrencyFn(windowAov),
|
|
519
|
+
formattedDetail: "Average per non-cancelled order for the window."
|
|
520
|
+
}
|
|
521
|
+
];
|
|
439
522
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
523
|
+
const TREND_MIX_LEADER_STROKE = "#b8956a";
|
|
524
|
+
function isTrendMixPayload(value) {
|
|
525
|
+
if (typeof value !== "object" || value === null) return false;
|
|
526
|
+
const k = value.key;
|
|
527
|
+
return k === "revenue" || k === "orders" || k === "aov";
|
|
528
|
+
}
|
|
529
|
+
function splitDetailTwoLines(s, firstLineMax) {
|
|
530
|
+
const t = s.trim();
|
|
531
|
+
if (t.length <= firstLineMax) return [t, ""];
|
|
532
|
+
const sliceEnd = t.lastIndexOf(" ", firstLineMax);
|
|
533
|
+
const cut = sliceEnd > firstLineMax * 0.45 ? sliceEnd : firstLineMax;
|
|
534
|
+
const a = t.slice(0, cut).trim();
|
|
535
|
+
const b = t.slice(cut).trim();
|
|
536
|
+
return b ? [a, b] : [a, ""];
|
|
537
|
+
}
|
|
538
|
+
function TrendMixExplodedSector(props) {
|
|
539
|
+
const {
|
|
540
|
+
cx,
|
|
541
|
+
cy,
|
|
542
|
+
innerRadius,
|
|
543
|
+
outerRadius,
|
|
544
|
+
startAngle,
|
|
545
|
+
endAngle,
|
|
546
|
+
fill,
|
|
547
|
+
stroke,
|
|
548
|
+
strokeWidth,
|
|
549
|
+
cornerRadius
|
|
550
|
+
} = props;
|
|
551
|
+
const midDeg = (Number(startAngle) + Number(endAngle)) / 2;
|
|
552
|
+
const rad = -midDeg * Math.PI / 180;
|
|
553
|
+
const offset = 19;
|
|
554
|
+
const ncx = (Number(cx) || 0) + offset * Math.cos(rad);
|
|
555
|
+
const ncy = (Number(cy) || 0) + offset * Math.sin(rad);
|
|
556
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
+
recharts.Sector,
|
|
558
|
+
{
|
|
559
|
+
cx: ncx,
|
|
560
|
+
cy: ncy,
|
|
561
|
+
innerRadius,
|
|
562
|
+
outerRadius,
|
|
563
|
+
startAngle,
|
|
564
|
+
endAngle,
|
|
565
|
+
fill,
|
|
566
|
+
stroke,
|
|
567
|
+
strokeWidth,
|
|
568
|
+
cornerRadius,
|
|
569
|
+
style: {
|
|
570
|
+
filter: "drop-shadow(0 5px 6px rgba(0,0,0,0.5)) drop-shadow(0 2px 2px rgba(0,0,0,0.35))"
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
function createTrendMixInfographicLabel(slices) {
|
|
576
|
+
return function TrendMixInfographicLabel(props) {
|
|
577
|
+
const { cx, cy, midAngle, outerRadius, percent, payload, index } = props;
|
|
578
|
+
const slice = typeof index === "number" && slices[index] !== void 0 ? slices[index] : isTrendMixPayload(payload) ? payload : null;
|
|
579
|
+
if (slice === null || cx === void 0 || cy === void 0 || midAngle === void 0) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
const p = percent ?? 0;
|
|
583
|
+
const ang = midAngle;
|
|
584
|
+
const RAD = Math.PI / 180;
|
|
585
|
+
const cos = Math.cos(-RAD * ang);
|
|
586
|
+
const sin = Math.sin(-RAD * ang);
|
|
587
|
+
const or = Number(outerRadius);
|
|
588
|
+
if (!Number.isFinite(or)) return null;
|
|
589
|
+
const rimPad = 24;
|
|
590
|
+
const sx = Number(cx) + (or + rimPad) * cos;
|
|
591
|
+
const sy = Number(cy) + (or + rimPad) * sin;
|
|
592
|
+
const elbow = 30;
|
|
593
|
+
const mx = sx + elbow * cos;
|
|
594
|
+
const my = sy;
|
|
595
|
+
const extend = 58;
|
|
596
|
+
const rightSide = cos >= 0;
|
|
597
|
+
const hx = mx + (rightSide ? extend : -58);
|
|
598
|
+
const anchor = rightSide ? "start" : "end";
|
|
599
|
+
const tx = hx + (rightSide ? 4 : -4);
|
|
600
|
+
const yRule = my + 14;
|
|
601
|
+
const pctStr = `${(p * 100).toFixed(0)}%`;
|
|
602
|
+
const ruleHalfW = 52;
|
|
603
|
+
const xRuleA = rightSide ? tx - 2 : tx - ruleHalfW;
|
|
604
|
+
const xRuleB = rightSide ? tx + ruleHalfW : tx + 2;
|
|
605
|
+
const [detail1, detail2] = splitDetailTwoLines(slice.formattedDetail, 46);
|
|
606
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("g", { "aria-hidden": true, children: [
|
|
607
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
608
|
+
"polyline",
|
|
609
|
+
{
|
|
610
|
+
points: `${sx},${sy} ${mx},${my} ${hx},${yRule - 18}`,
|
|
611
|
+
fill: "none",
|
|
612
|
+
stroke: TREND_MIX_LEADER_STROKE,
|
|
613
|
+
strokeWidth: 1.15,
|
|
614
|
+
strokeLinecap: "round",
|
|
615
|
+
strokeLinejoin: "round"
|
|
616
|
+
}
|
|
617
|
+
),
|
|
618
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
619
|
+
"circle",
|
|
620
|
+
{
|
|
621
|
+
cx: sx,
|
|
622
|
+
cy: sy,
|
|
623
|
+
r: 3.5,
|
|
624
|
+
fill: "none",
|
|
625
|
+
stroke: TREND_MIX_LEADER_STROKE,
|
|
626
|
+
strokeWidth: 1.15
|
|
627
|
+
}
|
|
628
|
+
),
|
|
629
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
630
|
+
"line",
|
|
631
|
+
{
|
|
632
|
+
x1: xRuleA,
|
|
633
|
+
y1: yRule,
|
|
634
|
+
x2: xRuleB,
|
|
635
|
+
y2: yRule,
|
|
636
|
+
stroke: TREND_MIX_LEADER_STROKE,
|
|
637
|
+
strokeWidth: 1.15,
|
|
638
|
+
strokeLinecap: "round"
|
|
639
|
+
}
|
|
640
|
+
),
|
|
641
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
642
|
+
"text",
|
|
643
|
+
{
|
|
644
|
+
x: tx,
|
|
645
|
+
y: yRule - 8,
|
|
646
|
+
textAnchor: anchor,
|
|
647
|
+
fill: "#f8fafc",
|
|
648
|
+
className: "text-[13px] font-bold tabular-nums",
|
|
649
|
+
children: pctStr
|
|
650
|
+
}
|
|
651
|
+
),
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
653
|
+
"text",
|
|
654
|
+
{
|
|
655
|
+
x: tx,
|
|
656
|
+
y: yRule + 16,
|
|
657
|
+
textAnchor: anchor,
|
|
658
|
+
fill: "#f1f5f9",
|
|
659
|
+
className: "text-[9px] font-semibold uppercase tracking-[0.16em]",
|
|
660
|
+
children: slice.name
|
|
661
|
+
}
|
|
662
|
+
),
|
|
663
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
664
|
+
"text",
|
|
665
|
+
{
|
|
666
|
+
x: tx,
|
|
667
|
+
y: yRule + 32,
|
|
668
|
+
textAnchor: anchor,
|
|
669
|
+
fill: "#f8fafc",
|
|
670
|
+
className: "text-[11px] font-bold tabular-nums",
|
|
671
|
+
children: slice.formattedPrimary
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
675
|
+
"text",
|
|
676
|
+
{
|
|
677
|
+
x: tx,
|
|
678
|
+
y: yRule + 46,
|
|
679
|
+
textAnchor: anchor,
|
|
680
|
+
fill: "#94a3b8",
|
|
681
|
+
className: "text-[8px] leading-snug",
|
|
682
|
+
children: [
|
|
683
|
+
/* @__PURE__ */ jsxRuntime.jsx("tspan", { x: tx, dy: 0, children: detail1 }),
|
|
684
|
+
detail2 ? /* @__PURE__ */ jsxRuntime.jsx("tspan", { x: tx, dy: 11, children: detail2 }) : null
|
|
685
|
+
]
|
|
686
|
+
}
|
|
687
|
+
)
|
|
688
|
+
] });
|
|
689
|
+
};
|
|
481
690
|
}
|
|
482
691
|
const KPI_ICON_BG$2 = {
|
|
483
692
|
green: "bg-emerald-500/20",
|
|
@@ -551,7 +760,8 @@ function EmptyAnalyticsPanel$1({
|
|
|
551
760
|
] });
|
|
552
761
|
}
|
|
553
762
|
function OrdersDashboard() {
|
|
554
|
-
var _a, _b;
|
|
763
|
+
var _a, _b, _c, _d, _e;
|
|
764
|
+
const { formatCurrency, formatCompactCurrency } = useAnalyticsCurrency();
|
|
555
765
|
const [data, setData] = react.useState(null);
|
|
556
766
|
const [loading, setLoading] = react.useState(true);
|
|
557
767
|
const [error, setError] = react.useState(null);
|
|
@@ -561,6 +771,7 @@ function OrdersDashboard() {
|
|
|
561
771
|
const [overTimePeriod, setOverTimePeriod] = react.useState("one_week");
|
|
562
772
|
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
563
773
|
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
774
|
+
const [overTimeOrdersMeta, setOverTimeOrdersMeta] = react.useState(null);
|
|
564
775
|
const [ordersInsights, setOrdersInsights] = react.useState(null);
|
|
565
776
|
const [insightsLoading, setInsightsLoading] = react.useState(true);
|
|
566
777
|
const [insightsError, setInsightsError] = react.useState(null);
|
|
@@ -674,9 +885,13 @@ function OrdersDashboard() {
|
|
|
674
885
|
if (!cancelled) {
|
|
675
886
|
setDailyOrders(body.dailyOrders ?? []);
|
|
676
887
|
setPreviousPeriodDailyOrders(body.previousPeriodDailyOrders ?? []);
|
|
888
|
+
setOverTimeOrdersMeta(body.ordersAnalyticsMeta ?? null);
|
|
677
889
|
}
|
|
678
890
|
}).catch((e) => {
|
|
679
|
-
if (!cancelled)
|
|
891
|
+
if (!cancelled) {
|
|
892
|
+
setOverTimeError(e instanceof Error ? e.message : String(e));
|
|
893
|
+
setOverTimeOrdersMeta(null);
|
|
894
|
+
}
|
|
680
895
|
}).finally(() => {
|
|
681
896
|
if (!cancelled) setOverTimeLoading(false);
|
|
682
897
|
});
|
|
@@ -749,7 +964,7 @@ function OrdersDashboard() {
|
|
|
749
964
|
const primaryStats = data ? [
|
|
750
965
|
{
|
|
751
966
|
label: "Total revenue",
|
|
752
|
-
value: formatCurrency
|
|
967
|
+
value: formatCurrency(data.totalRevenue),
|
|
753
968
|
helper: "Non-cancelled orders",
|
|
754
969
|
accent: "green"
|
|
755
970
|
},
|
|
@@ -767,7 +982,7 @@ function OrdersDashboard() {
|
|
|
767
982
|
},
|
|
768
983
|
{
|
|
769
984
|
label: "Average order value",
|
|
770
|
-
value: formatCurrency
|
|
985
|
+
value: formatCurrency(data.aov),
|
|
771
986
|
helper: "Per non-cancelled order",
|
|
772
987
|
accent: "amber"
|
|
773
988
|
}
|
|
@@ -817,18 +1032,45 @@ function OrdersDashboard() {
|
|
|
817
1032
|
);
|
|
818
1033
|
const revenueVsPreviousPercent = previousPeriodRevenueTotal > 0 ? (trendRevenueTotal - previousPeriodRevenueTotal) / previousPeriodRevenueTotal * 100 : null;
|
|
819
1034
|
const ordersVsPreviousPercent = previousPeriodOrdersTotal > 0 ? (trendOrdersTotal - previousPeriodOrdersTotal) / previousPeriodOrdersTotal * 100 : null;
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1035
|
+
const trendMixPieSlices = react.useMemo(
|
|
1036
|
+
() => buildTrendMixPieSlices(
|
|
1037
|
+
dailyOrders,
|
|
1038
|
+
trendRevenueTotal,
|
|
1039
|
+
trendOrdersTotal,
|
|
1040
|
+
formatCompactCurrency,
|
|
1041
|
+
formatCurrency
|
|
1042
|
+
),
|
|
1043
|
+
[
|
|
1044
|
+
dailyOrders,
|
|
1045
|
+
trendRevenueTotal,
|
|
1046
|
+
trendOrdersTotal,
|
|
1047
|
+
formatCompactCurrency,
|
|
1048
|
+
formatCurrency
|
|
1049
|
+
]
|
|
1050
|
+
);
|
|
1051
|
+
const trendMixPieLabelRenderer = react.useMemo(
|
|
1052
|
+
() => createTrendMixInfographicLabel(trendMixPieSlices),
|
|
1053
|
+
[trendMixPieSlices]
|
|
1054
|
+
);
|
|
1055
|
+
const trendMixRangeFootnote = react.useMemo(() => {
|
|
1056
|
+
const ncRev = dailyOrders.reduce((s, d) => s + nonCancelledRevenue(d), 0);
|
|
1057
|
+
const ncOrd = dailyOrders.reduce(
|
|
1058
|
+
(s, d) => s + Math.max(0, d.orders_count - d.cancelled_count),
|
|
1059
|
+
0
|
|
1060
|
+
);
|
|
1061
|
+
const windowAov = ncOrd > 0 ? ncRev / ncOrd : 0;
|
|
1062
|
+
return {
|
|
1063
|
+
revenue: formatCompactCurrency(trendRevenueTotal),
|
|
1064
|
+
orders: Math.floor(trendOrdersTotal).toLocaleString(),
|
|
1065
|
+
aov: formatCurrency(windowAov)
|
|
1066
|
+
};
|
|
1067
|
+
}, [
|
|
1068
|
+
dailyOrders,
|
|
1069
|
+
trendRevenueTotal,
|
|
1070
|
+
trendOrdersTotal,
|
|
1071
|
+
formatCompactCurrency,
|
|
1072
|
+
formatCurrency
|
|
1073
|
+
]);
|
|
832
1074
|
const salesBreakdownChartRows = react.useMemo(() => {
|
|
833
1075
|
if (salesGranularity === "hour") {
|
|
834
1076
|
return salesBreakdownHourlyRolling.map((b, i) => ({
|
|
@@ -934,11 +1176,13 @@ function OrdersDashboard() {
|
|
|
934
1176
|
};
|
|
935
1177
|
});
|
|
936
1178
|
}, [dailyOrders]);
|
|
1179
|
+
const selectedTrendPeriodLabel = ((_b = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _b.label) ?? "Selected range";
|
|
1180
|
+
const orderSampleLikelyTruncated = (((_c = data == null ? void 0 : data.ordersAnalyticsMeta) == null ? void 0 : _c.likely_truncated) ?? false) || ((overTimeOrdersMeta == null ? void 0 : overTimeOrdersMeta.likely_truncated) ?? false) || (((_d = ordersInsights == null ? void 0 : ordersInsights.ordersAnalyticsMeta) == null ? void 0 : _d.likely_truncated) ?? false);
|
|
937
1181
|
const quickPulseMetrics = [
|
|
938
1182
|
{
|
|
939
1183
|
label: "Range revenue",
|
|
940
|
-
value: formatCompactCurrency
|
|
941
|
-
helper:
|
|
1184
|
+
value: formatCompactCurrency(trendRevenueTotal),
|
|
1185
|
+
helper: selectedTrendPeriodLabel,
|
|
942
1186
|
accentClassName: "border-emerald-400/25 bg-emerald-500/5"
|
|
943
1187
|
},
|
|
944
1188
|
{
|
|
@@ -955,7 +1199,7 @@ function OrdersDashboard() {
|
|
|
955
1199
|
},
|
|
956
1200
|
{
|
|
957
1201
|
label: "Peak revenue day",
|
|
958
|
-
value: peakRevenuePoint ? formatCompactCurrency
|
|
1202
|
+
value: peakRevenuePoint ? formatCompactCurrency(peakRevenuePoint.total_revenue) : "—",
|
|
959
1203
|
helper: (peakRevenuePoint == null ? void 0 : peakRevenuePoint.label) ?? formatChartLabel$2(peakRevenuePoint == null ? void 0 : peakRevenuePoint.date),
|
|
960
1204
|
accentClassName: "border-amber-400/25 bg-amber-500/5"
|
|
961
1205
|
}
|
|
@@ -1023,7 +1267,7 @@ function OrdersDashboard() {
|
|
|
1023
1267
|
tick: CHART_AXIS_TICK_SM$1,
|
|
1024
1268
|
tickLine: false,
|
|
1025
1269
|
axisLine: false,
|
|
1026
|
-
tickFormatter: (v) => formatCompactCurrency
|
|
1270
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1027
1271
|
}
|
|
1028
1272
|
),
|
|
1029
1273
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1047,6 +1291,19 @@ function OrdersDashboard() {
|
|
|
1047
1291
|
) }) }) });
|
|
1048
1292
|
})();
|
|
1049
1293
|
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: [
|
|
1294
|
+
orderSampleLikelyTruncated ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1295
|
+
"div",
|
|
1296
|
+
{
|
|
1297
|
+
className: "border-b border-amber-400/30 bg-amber-500/10 px-4 py-2.5 sm:px-5",
|
|
1298
|
+
role: "status",
|
|
1299
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-base text-xs leading-snug", children: [
|
|
1300
|
+
"Order analytics loaded up to ",
|
|
1301
|
+
ORDERS_ANALYTICS_MAX_FETCH.toLocaleString(),
|
|
1302
|
+
" ",
|
|
1303
|
+
"orders per request. If you have more orders, KPIs and charts may under-count. Plan server-side aggregation or pagination for exact totals at very high volume."
|
|
1304
|
+
] })
|
|
1305
|
+
}
|
|
1306
|
+
) : null,
|
|
1050
1307
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1051
1308
|
AnalyticsDashboardHeader,
|
|
1052
1309
|
{
|
|
@@ -1135,10 +1392,10 @@ function OrdersDashboard() {
|
|
|
1135
1392
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
1136
1393
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
1137
1394
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window revenue" }),
|
|
1138
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency
|
|
1395
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency(trendRevenueTotal) }),
|
|
1139
1396
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
1140
1397
|
"Avg/day ",
|
|
1141
|
-
formatCompactCurrency
|
|
1398
|
+
formatCompactCurrency(trendAverageRevenue)
|
|
1142
1399
|
] })
|
|
1143
1400
|
] }) }),
|
|
1144
1401
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
@@ -1161,187 +1418,63 @@ function OrdersDashboard() {
|
|
|
1161
1418
|
] }),
|
|
1162
1419
|
!overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1163
1420
|
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1164
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-
|
|
1165
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-fuchsia-500" }),
|
|
1172
|
-
"Revenue"
|
|
1173
|
-
] }),
|
|
1174
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1175
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-sky-400" }),
|
|
1176
|
-
"Orders"
|
|
1177
|
-
] }),
|
|
1178
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1179
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-amber-500" }),
|
|
1180
|
-
"AOV"
|
|
1181
|
-
] }),
|
|
1182
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1183
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1184
|
-
"span",
|
|
1185
|
-
{
|
|
1186
|
-
className: "h-0 w-4 shrink-0 border-t border-dashed border-slate-400",
|
|
1187
|
-
"aria-hidden": true
|
|
1188
|
-
}
|
|
1189
|
-
),
|
|
1190
|
-
"Previous"
|
|
1191
|
-
] })
|
|
1421
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 min-w-0", children: [
|
|
1422
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue, orders & AOV" }),
|
|
1423
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[9px] leading-snug", children: [
|
|
1424
|
+
"Floating wedges with open center · leader labels only (no hover). Window:",
|
|
1425
|
+
" ",
|
|
1426
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-fg-subtle", children: selectedTrendPeriodLabel }),
|
|
1427
|
+
"."
|
|
1192
1428
|
] })
|
|
1193
1429
|
] }),
|
|
1194
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1195
|
-
|
|
1430
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1431
|
+
"div",
|
|
1196
1432
|
{
|
|
1197
|
-
|
|
1198
|
-
|
|
1433
|
+
className: "w-full rounded-2xl bg-[radial-gradient(ellipse_at_50%_42%,rgba(148,163,184,0.14)_0%,transparent_58%)] py-1",
|
|
1434
|
+
role: "img",
|
|
1435
|
+
"aria-label": `Revenue, orders, and AOV for ${selectedTrendPeriodLabel}. Values are shown on leader labels.`,
|
|
1199
1436
|
children: [
|
|
1200
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1201
|
-
recharts.
|
|
1202
|
-
{
|
|
1203
|
-
strokeDasharray: "3 3",
|
|
1204
|
-
vertical: false,
|
|
1205
|
-
stroke: "rgba(148,163,184,0.12)"
|
|
1206
|
-
}
|
|
1207
|
-
),
|
|
1208
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1209
|
-
recharts.XAxis,
|
|
1437
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(340px,48vh)] min-h-[288px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1438
|
+
recharts.PieChart,
|
|
1210
1439
|
{
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
{
|
|
1236
|
-
yAxisId: "revenue",
|
|
1237
|
-
orientation: "right",
|
|
1238
|
-
width: 42,
|
|
1239
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1240
|
-
tickLine: false,
|
|
1241
|
-
axisLine: false,
|
|
1242
|
-
tickFormatter: (v) => formatCompactCurrency$1(Number(v))
|
|
1243
|
-
}
|
|
1244
|
-
),
|
|
1245
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1246
|
-
recharts.YAxis,
|
|
1247
|
-
{
|
|
1248
|
-
yAxisId: "aov",
|
|
1249
|
-
orientation: "right",
|
|
1250
|
-
width: 40,
|
|
1251
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1252
|
-
tickLine: false,
|
|
1253
|
-
axisLine: false,
|
|
1254
|
-
tickFormatter: (v) => formatCompactCurrency$1(Number(v))
|
|
1255
|
-
}
|
|
1256
|
-
),
|
|
1257
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1258
|
-
recharts.Tooltip,
|
|
1259
|
-
{
|
|
1260
|
-
content: /* @__PURE__ */ jsxRuntime.jsx(CombinedMetricsTooltip, {}),
|
|
1261
|
-
cursor: {
|
|
1262
|
-
stroke: "rgba(248, 250, 252, 0.35)",
|
|
1263
|
-
strokeWidth: 1
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
),
|
|
1267
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1268
|
-
recharts.Line,
|
|
1269
|
-
{
|
|
1270
|
-
yAxisId: "orders",
|
|
1271
|
-
type: "natural",
|
|
1272
|
-
dataKey: "orders_count",
|
|
1273
|
-
name: "Orders",
|
|
1274
|
-
stroke: "#38BDF8",
|
|
1275
|
-
strokeWidth: 2,
|
|
1276
|
-
dot: false
|
|
1277
|
-
}
|
|
1278
|
-
),
|
|
1279
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1280
|
-
recharts.Line,
|
|
1281
|
-
{
|
|
1282
|
-
yAxisId: "orders",
|
|
1283
|
-
type: "natural",
|
|
1284
|
-
dataKey: "prev_orders",
|
|
1285
|
-
name: "Orders (prev)",
|
|
1286
|
-
stroke: "#64748b",
|
|
1287
|
-
strokeWidth: 1.5,
|
|
1288
|
-
strokeDasharray: "5 4",
|
|
1289
|
-
dot: false
|
|
1290
|
-
}
|
|
1291
|
-
),
|
|
1292
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1293
|
-
recharts.Line,
|
|
1294
|
-
{
|
|
1295
|
-
yAxisId: "revenue",
|
|
1296
|
-
type: "natural",
|
|
1297
|
-
dataKey: "total_revenue",
|
|
1298
|
-
name: "Revenue",
|
|
1299
|
-
stroke: "#D946EF",
|
|
1300
|
-
strokeWidth: 2,
|
|
1301
|
-
dot: false
|
|
1302
|
-
}
|
|
1303
|
-
),
|
|
1304
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1305
|
-
recharts.Line,
|
|
1306
|
-
{
|
|
1307
|
-
yAxisId: "revenue",
|
|
1308
|
-
type: "natural",
|
|
1309
|
-
dataKey: "prev_revenue",
|
|
1310
|
-
name: "Revenue (prev)",
|
|
1311
|
-
stroke: "#64748b",
|
|
1312
|
-
strokeWidth: 1.5,
|
|
1313
|
-
strokeDasharray: "5 4",
|
|
1314
|
-
dot: false
|
|
1315
|
-
}
|
|
1316
|
-
),
|
|
1317
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1318
|
-
recharts.Line,
|
|
1319
|
-
{
|
|
1320
|
-
yAxisId: "aov",
|
|
1321
|
-
type: "natural",
|
|
1322
|
-
dataKey: "aov",
|
|
1323
|
-
name: "AOV",
|
|
1324
|
-
stroke: "#D97706",
|
|
1325
|
-
strokeWidth: 2,
|
|
1326
|
-
dot: false
|
|
1327
|
-
}
|
|
1328
|
-
),
|
|
1329
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1330
|
-
recharts.Line,
|
|
1331
|
-
{
|
|
1332
|
-
yAxisId: "aov",
|
|
1333
|
-
type: "natural",
|
|
1334
|
-
dataKey: "prev_aov",
|
|
1335
|
-
name: "AOV (prev)",
|
|
1336
|
-
stroke: "#64748b",
|
|
1337
|
-
strokeWidth: 1.5,
|
|
1338
|
-
strokeDasharray: "5 4",
|
|
1339
|
-
dot: false
|
|
1440
|
+
margin: { top: 20, right: 132, bottom: 28, left: 132 },
|
|
1441
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1442
|
+
recharts.Pie,
|
|
1443
|
+
{
|
|
1444
|
+
data: trendMixPieSlices,
|
|
1445
|
+
dataKey: "value",
|
|
1446
|
+
nameKey: "name",
|
|
1447
|
+
cx: "50%",
|
|
1448
|
+
cy: "50%",
|
|
1449
|
+
startAngle: 90,
|
|
1450
|
+
endAngle: -270,
|
|
1451
|
+
innerRadius: "26%",
|
|
1452
|
+
outerRadius: "46%",
|
|
1453
|
+
paddingAngle: 9,
|
|
1454
|
+
cornerRadius: 8,
|
|
1455
|
+
stroke: "rgba(15,23,42,0.55)",
|
|
1456
|
+
strokeWidth: 2,
|
|
1457
|
+
shape: TrendMixExplodedSector,
|
|
1458
|
+
label: trendMixPieLabelRenderer,
|
|
1459
|
+
labelLine: false,
|
|
1460
|
+
isAnimationActive: false,
|
|
1461
|
+
children: trendMixPieSlices.map((slice) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: slice.fill }, slice.key))
|
|
1462
|
+
}
|
|
1463
|
+
)
|
|
1340
1464
|
}
|
|
1341
|
-
)
|
|
1465
|
+
) }) }),
|
|
1466
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "mt-2 px-1 text-center text-[9px] leading-snug text-ui-fg-muted", children: [
|
|
1467
|
+
"Range snapshot — Revenue ",
|
|
1468
|
+
trendMixRangeFootnote.revenue,
|
|
1469
|
+
" · Orders",
|
|
1470
|
+
" ",
|
|
1471
|
+
trendMixRangeFootnote.orders,
|
|
1472
|
+
" · AOV ",
|
|
1473
|
+
trendMixRangeFootnote.aov
|
|
1474
|
+
] })
|
|
1342
1475
|
]
|
|
1343
1476
|
}
|
|
1344
|
-
)
|
|
1477
|
+
)
|
|
1345
1478
|
] }),
|
|
1346
1479
|
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
|
|
1347
1480
|
/* @__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)" }),
|
|
@@ -1366,9 +1499,11 @@ function OrdersDashboard() {
|
|
|
1366
1499
|
tick: CHART_AXIS_TICK$2,
|
|
1367
1500
|
tickLine: false,
|
|
1368
1501
|
axisLine: false,
|
|
1369
|
-
tickFormatter: (value
|
|
1370
|
-
const row = dailyOrders
|
|
1371
|
-
|
|
1502
|
+
tickFormatter: (value) => {
|
|
1503
|
+
const row = dailyOrders.find(
|
|
1504
|
+
(d) => d.date === String(value)
|
|
1505
|
+
);
|
|
1506
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel$2(String(value));
|
|
1372
1507
|
}
|
|
1373
1508
|
}
|
|
1374
1509
|
),
|
|
@@ -1376,7 +1511,7 @@ function OrdersDashboard() {
|
|
|
1376
1511
|
recharts.YAxis,
|
|
1377
1512
|
{
|
|
1378
1513
|
tick: CHART_AXIS_TICK$2,
|
|
1379
|
-
tickFormatter: (v) => formatCompactCurrency
|
|
1514
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1380
1515
|
}
|
|
1381
1516
|
),
|
|
1382
1517
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1386,7 +1521,7 @@ function OrdersDashboard() {
|
|
|
1386
1521
|
p.name,
|
|
1387
1522
|
":",
|
|
1388
1523
|
" ",
|
|
1389
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency
|
|
1524
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(p.value) || 0) })
|
|
1390
1525
|
] }, String(p.name))) }) : null
|
|
1391
1526
|
}
|
|
1392
1527
|
),
|
|
@@ -1461,7 +1596,7 @@ function OrdersDashboard() {
|
|
|
1461
1596
|
{
|
|
1462
1597
|
variant: "atlas",
|
|
1463
1598
|
title: "Order → fulfillment funnel",
|
|
1464
|
-
description: `Stages by order created date (UTC), same range as Revenue & orders (${((
|
|
1599
|
+
description: `Stages by order created date (UTC), same range as Revenue & orders (${((_e = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _e.label) ?? "period"}). Not storefront visitors.`,
|
|
1465
1600
|
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: [
|
|
1466
1601
|
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1467
1602
|
/* @__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)" }),
|
|
@@ -2131,7 +2266,7 @@ function formatChartLabel$1(value) {
|
|
|
2131
2266
|
return `${parsed.getUTCMonth() + 1}/${parsed.getUTCDate()}`;
|
|
2132
2267
|
}
|
|
2133
2268
|
function CustomersDashboard() {
|
|
2134
|
-
var _a, _b;
|
|
2269
|
+
var _a, _b, _c;
|
|
2135
2270
|
const [data, setData] = react.useState(null);
|
|
2136
2271
|
const [loading, setLoading] = react.useState(true);
|
|
2137
2272
|
const [error, setError] = react.useState(null);
|
|
@@ -2370,6 +2505,13 @@ function CustomersDashboard() {
|
|
|
2370
2505
|
title: "Repeat purchases",
|
|
2371
2506
|
description: "Orders with a customer_id in the same window (guest checkouts excluded from rate).",
|
|
2372
2507
|
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-1.5", children: repeatLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "flex flex-1 items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : repeatError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "flex flex-1 items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-danger text-xs", children: repeatError }) }) : repeatStats ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
2508
|
+
((_b = repeatStats.ordersAnalyticsMeta) == null ? void 0 : _b.likely_truncated) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-full rounded-md border border-amber-400/25 bg-amber-500/10 px-2 py-1.5", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-base text-[10px] leading-snug", children: [
|
|
2509
|
+
"Repeat stats are computed from a sample of up to",
|
|
2510
|
+
" ",
|
|
2511
|
+
repeatStats.ordersAnalyticsMeta.max_fetch.toLocaleString(),
|
|
2512
|
+
" ",
|
|
2513
|
+
"orders; your true repeat rate may differ if you exceed that sample."
|
|
2514
|
+
] }) }) : null,
|
|
2373
2515
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2374
2516
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Repeat rate" }),
|
|
2375
2517
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: `${repeatStats.repeat_rate_percent.toFixed(1)}%` }),
|
|
@@ -2434,7 +2576,7 @@ function CustomersDashboard() {
|
|
|
2434
2576
|
] }) }),
|
|
2435
2577
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2436
2578
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Chart period" }),
|
|
2437
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: ((
|
|
2579
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: ((_c = GRAPH_PERIODS$1.find((p) => p.value === graphPeriod)) == null ? void 0 : _c.label) ?? graphPeriod }),
|
|
2438
2580
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: "Independent of summary period filter" })
|
|
2439
2581
|
] }) })
|
|
2440
2582
|
] }),
|
|
@@ -2468,12 +2610,12 @@ function CustomersDashboard() {
|
|
|
2468
2610
|
stroke: CHART_AXIS_LINE$1,
|
|
2469
2611
|
tickLine: false,
|
|
2470
2612
|
axisLine: { stroke: CHART_AXIS_LINE$1 },
|
|
2471
|
-
tickFormatter: (
|
|
2472
|
-
|
|
2473
|
-
const row = series[index];
|
|
2613
|
+
tickFormatter: (value) => {
|
|
2614
|
+
const row = series.find((p) => p.date === String(value));
|
|
2474
2615
|
if (row == null ? void 0 : row.label) return row.label;
|
|
2475
|
-
|
|
2476
|
-
|
|
2616
|
+
return formatChartLabel$1(
|
|
2617
|
+
(row == null ? void 0 : row.date) ?? (typeof value === "string" ? value : String(value))
|
|
2618
|
+
);
|
|
2477
2619
|
}
|
|
2478
2620
|
}
|
|
2479
2621
|
),
|
|
@@ -2528,6 +2670,16 @@ function CustomersDashboard() {
|
|
|
2528
2670
|
] })
|
|
2529
2671
|
] }) });
|
|
2530
2672
|
}
|
|
2673
|
+
function buildAnalyticsUrl(path, params) {
|
|
2674
|
+
const search = new URLSearchParams();
|
|
2675
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2676
|
+
if (value !== void 0 && value !== null) {
|
|
2677
|
+
search.set(key, String(value));
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
const query = search.toString();
|
|
2681
|
+
return query ? `${path}?${query}` : path;
|
|
2682
|
+
}
|
|
2531
2683
|
const SUMMARY_PERIODS = [
|
|
2532
2684
|
{ value: "all", label: "All time" },
|
|
2533
2685
|
{ value: "0", label: "Today" },
|
|
@@ -2572,22 +2724,6 @@ const SELECT_CLASS_NAME = "h-9 w-full min-w-[8rem] max-w-full cursor-pointer app
|
|
|
2572
2724
|
const LEADERBOARD_TABLE_CLASS = "min-w-[32rem] sm:min-w-[36rem] max-w-none";
|
|
2573
2725
|
const OPPORTUNITIES_TABLE_CLASS = "min-w-[30rem] sm:min-w-[34rem] max-w-none";
|
|
2574
2726
|
const TABLE_HEAD_CELL_NOWRAP = "whitespace-nowrap";
|
|
2575
|
-
function formatCurrency(value) {
|
|
2576
|
-
return new Intl.NumberFormat("en-IN", {
|
|
2577
|
-
style: "currency",
|
|
2578
|
-
currency: "INR",
|
|
2579
|
-
minimumFractionDigits: 0,
|
|
2580
|
-
maximumFractionDigits: 0
|
|
2581
|
-
}).format(value);
|
|
2582
|
-
}
|
|
2583
|
-
function formatCompactCurrency(value) {
|
|
2584
|
-
return new Intl.NumberFormat("en-IN", {
|
|
2585
|
-
style: "currency",
|
|
2586
|
-
currency: "INR",
|
|
2587
|
-
notation: "compact",
|
|
2588
|
-
maximumFractionDigits: 1
|
|
2589
|
-
}).format(value);
|
|
2590
|
-
}
|
|
2591
2727
|
function formatChartLabel(value) {
|
|
2592
2728
|
if (!value) return "";
|
|
2593
2729
|
const parsed = new Date(value);
|
|
@@ -2614,16 +2750,6 @@ function summaryDaysToGraphPeriod(days) {
|
|
|
2614
2750
|
if (days === "all" || days === "90") return "one_year";
|
|
2615
2751
|
return "one_week";
|
|
2616
2752
|
}
|
|
2617
|
-
function buildAnalyticsUrl(path, params) {
|
|
2618
|
-
const search = new URLSearchParams();
|
|
2619
|
-
for (const [key, value] of Object.entries(params)) {
|
|
2620
|
-
if (value) {
|
|
2621
|
-
search.set(key, value);
|
|
2622
|
-
}
|
|
2623
|
-
}
|
|
2624
|
-
const query = search.toString();
|
|
2625
|
-
return query ? `${path}?${query}` : path;
|
|
2626
|
-
}
|
|
2627
2753
|
function isProductOverTimePoint(value) {
|
|
2628
2754
|
if (typeof value !== "object" || value === null) return false;
|
|
2629
2755
|
const v = value;
|
|
@@ -2635,6 +2761,7 @@ function TrendTooltip({
|
|
|
2635
2761
|
label
|
|
2636
2762
|
}) {
|
|
2637
2763
|
var _a, _b, _c, _d, _e;
|
|
2764
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
2638
2765
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2639
2766
|
const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
2640
2767
|
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
@@ -2666,6 +2793,7 @@ function MiniTrendTooltip({
|
|
|
2666
2793
|
hideRevenueRow
|
|
2667
2794
|
}) {
|
|
2668
2795
|
var _a, _b, _c, _d, _e;
|
|
2796
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
2669
2797
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2670
2798
|
const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
2671
2799
|
const labelKey = label !== void 0 && label !== null ? String(label) : "";
|
|
@@ -2696,6 +2824,7 @@ function ProductBarTooltip({
|
|
|
2696
2824
|
showViews = true
|
|
2697
2825
|
}) {
|
|
2698
2826
|
var _a;
|
|
2827
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
2699
2828
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2700
2829
|
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
2701
2830
|
if (!row) return null;
|
|
@@ -2748,6 +2877,9 @@ function EmptyAnalyticsPanel({ title, description }) {
|
|
|
2748
2877
|
}
|
|
2749
2878
|
function ProductsDashboard() {
|
|
2750
2879
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2880
|
+
const { formatCurrency, formatCompactCurrency } = useAnalyticsCurrency();
|
|
2881
|
+
const chartGradientPrefix = react.useId().replace(/:/g, "");
|
|
2882
|
+
const gid = (suffix) => `${chartGradientPrefix}-${suffix}`;
|
|
2751
2883
|
const [summaryDays, setSummaryDays] = react.useState("all");
|
|
2752
2884
|
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
2753
2885
|
const [topSellerPeriod, setTopSellerPeriod] = react.useState("week");
|
|
@@ -3334,26 +3466,76 @@ function ProductsDashboard() {
|
|
|
3334
3466
|
margin: { top: 4, right: 6, left: -6, bottom: 0 },
|
|
3335
3467
|
children: [
|
|
3336
3468
|
/* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
|
|
3337
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3469
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3470
|
+
"linearGradient",
|
|
3471
|
+
{
|
|
3472
|
+
id: gid("trend-units-line"),
|
|
3473
|
+
x1: "0",
|
|
3474
|
+
y1: "0",
|
|
3475
|
+
x2: "1",
|
|
3476
|
+
y2: "0",
|
|
3477
|
+
children: [
|
|
3478
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#67E8F9" }),
|
|
3479
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3B82F6" })
|
|
3480
|
+
]
|
|
3481
|
+
}
|
|
3482
|
+
),
|
|
3483
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3484
|
+
"linearGradient",
|
|
3485
|
+
{
|
|
3486
|
+
id: gid("trend-revenue-line"),
|
|
3487
|
+
x1: "0",
|
|
3488
|
+
y1: "0",
|
|
3489
|
+
x2: "1",
|
|
3490
|
+
y2: "0",
|
|
3491
|
+
children: [
|
|
3492
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#F472B6" }),
|
|
3493
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC" })
|
|
3494
|
+
]
|
|
3495
|
+
}
|
|
3496
|
+
),
|
|
3497
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3498
|
+
"linearGradient",
|
|
3499
|
+
{
|
|
3500
|
+
id: gid("trend-units-area"),
|
|
3501
|
+
x1: "0",
|
|
3502
|
+
y1: "0",
|
|
3503
|
+
x2: "0",
|
|
3504
|
+
y2: "1",
|
|
3505
|
+
children: [
|
|
3506
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#38BDF8", stopOpacity: 0.22 }),
|
|
3507
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#38BDF8", stopOpacity: 0 })
|
|
3508
|
+
]
|
|
3509
|
+
}
|
|
3510
|
+
),
|
|
3511
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3512
|
+
"linearGradient",
|
|
3513
|
+
{
|
|
3514
|
+
id: gid("trend-revenue-area"),
|
|
3515
|
+
x1: "0",
|
|
3516
|
+
y1: "0",
|
|
3517
|
+
x2: "0",
|
|
3518
|
+
y2: "1",
|
|
3519
|
+
children: [
|
|
3520
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.2 }),
|
|
3521
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#E879F9", stopOpacity: 0 })
|
|
3522
|
+
]
|
|
3523
|
+
}
|
|
3524
|
+
),
|
|
3525
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3526
|
+
"linearGradient",
|
|
3527
|
+
{
|
|
3528
|
+
id: gid("trend-views-area"),
|
|
3529
|
+
x1: "0",
|
|
3530
|
+
y1: "0",
|
|
3531
|
+
x2: "0",
|
|
3532
|
+
y2: "1",
|
|
3533
|
+
children: [
|
|
3534
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#C084FC", stopOpacity: 0.18 }),
|
|
3535
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC", stopOpacity: 0 })
|
|
3536
|
+
]
|
|
3537
|
+
}
|
|
3538
|
+
)
|
|
3357
3539
|
] }),
|
|
3358
3540
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3359
3541
|
recharts.CartesianGrid,
|
|
@@ -3370,8 +3552,8 @@ function ProductsDashboard() {
|
|
|
3370
3552
|
tick: CHART_AXIS_TICK,
|
|
3371
3553
|
tickLine: false,
|
|
3372
3554
|
axisLine: false,
|
|
3373
|
-
tickFormatter: (value
|
|
3374
|
-
const row = series
|
|
3555
|
+
tickFormatter: (value) => {
|
|
3556
|
+
const row = series.find((p) => p.date === String(value));
|
|
3375
3557
|
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3376
3558
|
}
|
|
3377
3559
|
}
|
|
@@ -3417,7 +3599,7 @@ function ProductsDashboard() {
|
|
|
3417
3599
|
type: "natural",
|
|
3418
3600
|
dataKey: "units_sold",
|
|
3419
3601
|
stroke: "none",
|
|
3420
|
-
fill:
|
|
3602
|
+
fill: `url(#${gid("trend-units-area")})`,
|
|
3421
3603
|
isAnimationActive: false,
|
|
3422
3604
|
legendType: "none"
|
|
3423
3605
|
}
|
|
@@ -3429,7 +3611,7 @@ function ProductsDashboard() {
|
|
|
3429
3611
|
type: "natural",
|
|
3430
3612
|
dataKey: "views",
|
|
3431
3613
|
stroke: "none",
|
|
3432
|
-
fill:
|
|
3614
|
+
fill: `url(#${gid("trend-views-area")})`,
|
|
3433
3615
|
isAnimationActive: false,
|
|
3434
3616
|
legendType: "none"
|
|
3435
3617
|
}
|
|
@@ -3441,7 +3623,7 @@ function ProductsDashboard() {
|
|
|
3441
3623
|
type: "natural",
|
|
3442
3624
|
dataKey: "revenue",
|
|
3443
3625
|
stroke: "none",
|
|
3444
|
-
fill:
|
|
3626
|
+
fill: `url(#${gid("trend-revenue-area")})`,
|
|
3445
3627
|
isAnimationActive: false,
|
|
3446
3628
|
legendType: "none"
|
|
3447
3629
|
}
|
|
@@ -3453,7 +3635,7 @@ function ProductsDashboard() {
|
|
|
3453
3635
|
type: "natural",
|
|
3454
3636
|
dataKey: "units_sold",
|
|
3455
3637
|
name: "Units sold",
|
|
3456
|
-
stroke:
|
|
3638
|
+
stroke: `url(#${gid("trend-units-line")})`,
|
|
3457
3639
|
strokeWidth: 2,
|
|
3458
3640
|
dot: false,
|
|
3459
3641
|
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.units }
|
|
@@ -3480,7 +3662,7 @@ function ProductsDashboard() {
|
|
|
3480
3662
|
type: "natural",
|
|
3481
3663
|
dataKey: "revenue",
|
|
3482
3664
|
name: "Revenue",
|
|
3483
|
-
stroke:
|
|
3665
|
+
stroke: `url(#${gid("trend-revenue-line")})`,
|
|
3484
3666
|
strokeWidth: 2,
|
|
3485
3667
|
dot: false,
|
|
3486
3668
|
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.revenue }
|
|
@@ -3551,7 +3733,7 @@ function ProductsDashboard() {
|
|
|
3551
3733
|
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3552
3734
|
"linearGradient",
|
|
3553
3735
|
{
|
|
3554
|
-
id: "
|
|
3736
|
+
id: gid("best-revenue-area"),
|
|
3555
3737
|
x1: "0",
|
|
3556
3738
|
y1: "0",
|
|
3557
3739
|
x2: "0",
|
|
@@ -3589,7 +3771,12 @@ function ProductsDashboard() {
|
|
|
3589
3771
|
{
|
|
3590
3772
|
dataKey: "date",
|
|
3591
3773
|
tick: CHART_AXIS_TICK_SM,
|
|
3592
|
-
tickFormatter: (value) =>
|
|
3774
|
+
tickFormatter: (value) => {
|
|
3775
|
+
const row = bestSellersTrendSeries.find(
|
|
3776
|
+
(p) => p.date === String(value)
|
|
3777
|
+
);
|
|
3778
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3779
|
+
},
|
|
3593
3780
|
tickLine: false,
|
|
3594
3781
|
axisLine: { stroke: CHART_AXIS_LINE }
|
|
3595
3782
|
}
|
|
@@ -3639,7 +3826,7 @@ function ProductsDashboard() {
|
|
|
3639
3826
|
dataKey: "revenue",
|
|
3640
3827
|
name: "Revenue",
|
|
3641
3828
|
stroke: "transparent",
|
|
3642
|
-
fill:
|
|
3829
|
+
fill: `url(#${gid("best-revenue-area")})`,
|
|
3643
3830
|
isAnimationActive: false
|
|
3644
3831
|
}
|
|
3645
3832
|
),
|
|
@@ -3754,7 +3941,7 @@ function ProductsDashboard() {
|
|
|
3754
3941
|
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3755
3942
|
"linearGradient",
|
|
3756
3943
|
{
|
|
3757
|
-
id: "
|
|
3944
|
+
id: gid("viewed-views-area"),
|
|
3758
3945
|
x1: "0",
|
|
3759
3946
|
y1: "0",
|
|
3760
3947
|
x2: "0",
|
|
@@ -3792,7 +3979,12 @@ function ProductsDashboard() {
|
|
|
3792
3979
|
{
|
|
3793
3980
|
dataKey: "date",
|
|
3794
3981
|
tick: CHART_AXIS_TICK_SM,
|
|
3795
|
-
tickFormatter: (value) =>
|
|
3982
|
+
tickFormatter: (value) => {
|
|
3983
|
+
const row = mostViewedTrendSeries.find(
|
|
3984
|
+
(p) => p.date === String(value)
|
|
3985
|
+
);
|
|
3986
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3987
|
+
},
|
|
3796
3988
|
tickLine: false,
|
|
3797
3989
|
axisLine: { stroke: CHART_AXIS_LINE }
|
|
3798
3990
|
}
|
|
@@ -3829,7 +4021,7 @@ function ProductsDashboard() {
|
|
|
3829
4021
|
dataKey: "views",
|
|
3830
4022
|
name: "Views",
|
|
3831
4023
|
stroke: "transparent",
|
|
3832
|
-
fill:
|
|
4024
|
+
fill: `url(#${gid("viewed-views-area")})`,
|
|
3833
4025
|
isAnimationActive: false
|
|
3834
4026
|
}
|
|
3835
4027
|
),
|
|
@@ -3891,7 +4083,7 @@ function ProductsDashboard() {
|
|
|
3891
4083
|
title: "Views vs units sold",
|
|
3892
4084
|
description: "Each point is a product (top viewed in this period). Requires product view tracking.",
|
|
3893
4085
|
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[220px] w-full sm:h-[248px]", 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 text-xs", children: "Loading scatter…" }) }) : performanceError || !performance ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", 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 text-xs", 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 text-xs", 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: [
|
|
3894
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("radialGradient", { id: "
|
|
4086
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("radialGradient", { id: gid("scatter-glow"), cx: "50%", cy: "50%", r: "50%", children: [
|
|
3895
4087
|
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.95 }),
|
|
3896
4088
|
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#6366F1", stopOpacity: 0.65 })
|
|
3897
4089
|
] }) }),
|
|
@@ -3952,7 +4144,7 @@ function ProductsDashboard() {
|
|
|
3952
4144
|
{
|
|
3953
4145
|
name: "Products",
|
|
3954
4146
|
data: viewsVsUnitsScatterData,
|
|
3955
|
-
fill:
|
|
4147
|
+
fill: `url(#${gid("scatter-glow")})`
|
|
3956
4148
|
}
|
|
3957
4149
|
)
|
|
3958
4150
|
] }) }) }) })
|
|
@@ -3968,7 +4160,7 @@ const ANALYTICS_MODULES = [
|
|
|
3968
4160
|
];
|
|
3969
4161
|
const AnalyticsPage = () => {
|
|
3970
4162
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
3971
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4163
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsCurrencyProvider, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3972
4164
|
"div",
|
|
3973
4165
|
{
|
|
3974
4166
|
className: `mx-auto flex w-full min-w-0 flex-col ${activeModule === "products" ? "max-w-full gap-2 px-3 py-3 sm:px-4 sm:py-3" : "max-w-[1600px] gap-4 p-4"}`.trim(),
|
|
@@ -4009,7 +4201,7 @@ const AnalyticsPage = () => {
|
|
|
4009
4201
|
] })
|
|
4010
4202
|
]
|
|
4011
4203
|
}
|
|
4012
|
-
) }) });
|
|
4204
|
+
) }) }) });
|
|
4013
4205
|
};
|
|
4014
4206
|
const config = adminSdk.defineRouteConfig({
|
|
4015
4207
|
label: "Analytics",
|