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