medusa-analytics 0.0.22 → 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, useEffect, useMemo } 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);
@@ -302,15 +339,66 @@ function formatShortNumber$1(value) {
302
339
  }).format(value);
303
340
  }
304
341
  const SALES_GRANULARITY_TABS = [
305
- { value: "day", label: "Day" },
306
342
  { value: "hour", label: "Hour" },
343
+ { value: "day", label: "Day" },
307
344
  { value: "week", label: "Week" },
308
345
  { value: "month", label: "Month" }
309
346
  ];
347
+ function getSalesBreakdownOtPeriod(g) {
348
+ if (g === "day") return "one_week";
349
+ if (g === "week") return "two_months";
350
+ if (g === "month") return "one_year";
351
+ return null;
352
+ }
310
353
  function monthKeyFromDateStr(dateStr) {
311
354
  if (dateStr.length >= 7) return dateStr.slice(0, 7);
312
355
  return dateStr;
313
356
  }
357
+ function utcSundayWeekStartKeyFromDateStr(dateStr) {
358
+ const parsed = new Date(dateStr);
359
+ if (Number.isNaN(parsed.getTime())) return dateStr;
360
+ const y = parsed.getUTCFullYear();
361
+ const m = parsed.getUTCMonth();
362
+ const day = parsed.getUTCDate();
363
+ const dow = parsed.getUTCDay();
364
+ const start = new Date(Date.UTC(y, m, day - dow));
365
+ const ys = start.getUTCFullYear();
366
+ const ms = String(start.getUTCMonth() + 1).padStart(2, "0");
367
+ const ds = String(start.getUTCDate()).padStart(2, "0");
368
+ return `${ys}-${ms}-${ds}`;
369
+ }
370
+ function formatWeekStartDm(weekStartKey) {
371
+ const parsed = /* @__PURE__ */ new Date(`${weekStartKey}T00:00:00.000Z`);
372
+ if (Number.isNaN(parsed.getTime())) return weekStartKey;
373
+ return `${parsed.getUTCDate()}/${parsed.getUTCMonth() + 1}`;
374
+ }
375
+ function formatDayBreakdownDm(dateStr) {
376
+ const parsed = new Date(dateStr);
377
+ if (Number.isNaN(parsed.getTime())) return dateStr;
378
+ return `${parsed.getUTCDate()}/${parsed.getUTCMonth() + 1}`;
379
+ }
380
+ function toDateKeyUtcFromDate(d) {
381
+ const y = d.getUTCFullYear();
382
+ const m = String(d.getUTCMonth() + 1).padStart(2, "0");
383
+ const day = String(d.getUTCDate()).padStart(2, "0");
384
+ return `${y}-${m}-${day}`;
385
+ }
386
+ function lastNWeekStartKeysUtc(n) {
387
+ const now = /* @__PURE__ */ new Date();
388
+ const todayUtc = new Date(
389
+ Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
390
+ );
391
+ const dow = todayUtc.getUTCDay();
392
+ const currentWeekStart = new Date(todayUtc);
393
+ currentWeekStart.setUTCDate(currentWeekStart.getUTCDate() - dow);
394
+ const keys = [];
395
+ for (let i = n - 1; i >= 0; i -= 1) {
396
+ const d = new Date(currentWeekStart);
397
+ d.setUTCDate(d.getUTCDate() - 7 * i);
398
+ keys.push(toDateKeyUtcFromDate(d));
399
+ }
400
+ return keys;
401
+ }
314
402
  function formatMonthBucketLabel(ymKey) {
315
403
  const [ys, ms] = ymKey.split("-");
316
404
  const y = Number(ys);
@@ -318,27 +406,9 @@ function formatMonthBucketLabel(ymKey) {
318
406
  if (!Number.isFinite(y) || !Number.isFinite(m)) return ymKey;
319
407
  return new Intl.DateTimeFormat("en-IN", {
320
408
  month: "short",
321
- year: "numeric",
322
409
  timeZone: "UTC"
323
410
  }).format(new Date(Date.UTC(y, m - 1, 1)));
324
411
  }
325
- function aggregateDailyOrdersForBreakdown(rows, keyFn, labelFn) {
326
- const map = /* @__PURE__ */ new Map();
327
- for (const d of rows) {
328
- const key = keyFn(d);
329
- const cur = map.get(key) ?? { orders: 0, revenue: 0, bucketRows: [] };
330
- cur.orders += d.orders_count;
331
- cur.revenue += d.total_revenue;
332
- cur.bucketRows.push(d);
333
- map.set(key, cur);
334
- }
335
- return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([key, v]) => ({
336
- key,
337
- label: labelFn(key, v.bucketRows),
338
- orders_count: v.orders,
339
- revenue: v.revenue
340
- }));
341
- }
342
412
  function isSalesBreakdownBarRow(value) {
343
413
  if (typeof value !== "object" || value === null) return false;
344
414
  const v = value;
@@ -349,13 +419,14 @@ function SalesBreakdownTooltip({
349
419
  payload
350
420
  }) {
351
421
  var _a, _b;
422
+ const { formatCurrency } = useAnalyticsCurrency();
352
423
  if (!active || !(payload == null ? void 0 : payload.length)) return null;
353
424
  const row = isSalesBreakdownBarRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
354
425
  if (!row) return null;
355
426
  return /* @__PURE__ */ jsxs(AnalyticsTooltipCard, { variant: "compact", title: row.label, children: [
356
427
  /* @__PURE__ */ jsxs("div", { children: [
357
428
  "Revenue: ",
358
- /* @__PURE__ */ jsx("strong", { children: formatCurrency$1(row.revenue) })
429
+ /* @__PURE__ */ jsx("strong", { children: formatCurrency(row.revenue) })
359
430
  ] }),
360
431
  /* @__PURE__ */ jsxs("div", { children: [
361
432
  "Orders:",
@@ -398,52 +469,223 @@ function OutcomesTrendTooltip({
398
469
  /* @__PURE__ */ jsx("strong", { children: Math.floor(Number(entry.value) || 0).toLocaleString() })
399
470
  ] }, String(entry.name))) });
400
471
  }
401
- function isTrendRowDetailed(value) {
402
- if (!isDailyOrderRow(value)) return false;
403
- const v = value;
404
- 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
+ ];
405
521
  }
406
- function CombinedMetricsTooltip({
407
- active,
408
- payload
409
- }) {
410
- var _a, _b;
411
- if (!active || !(payload == null ? void 0 : payload.length)) return null;
412
- const row = isTrendRowDetailed((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
413
- if (!row) return null;
414
- const title = row.label ?? formatChartLabel$2(row.date);
415
- return /* @__PURE__ */ jsxs(AnalyticsTooltipCard, { variant: "compact", title, children: [
416
- /* @__PURE__ */ jsxs("div", { children: [
417
- "Revenue:",
418
- " ",
419
- /* @__PURE__ */ jsx("strong", { children: formatCompactCurrency$1(row.total_revenue) }),
420
- /* @__PURE__ */ jsxs("span", { className: "text-ui-fg-muted", children: [
421
- " ",
422
- prev ",
423
- formatCompactCurrency$1(row.prev_revenue)
424
- ] })
425
- ] }),
426
- /* @__PURE__ */ jsxs("div", { children: [
427
- "Orders:",
428
- " ",
429
- /* @__PURE__ */ jsx("strong", { children: Math.floor(row.orders_count).toLocaleString() }),
430
- /* @__PURE__ */ jsxs("span", { className: "text-ui-fg-muted", children: [
431
- " ",
432
- "· prev ",
433
- Math.floor(row.prev_orders).toLocaleString()
434
- ] })
435
- ] }),
436
- /* @__PURE__ */ jsxs("div", { children: [
437
- "AOV (non-canc.):",
438
- " ",
439
- /* @__PURE__ */ jsx("strong", { children: formatCurrency$1(row.aov) }),
440
- /* @__PURE__ */ jsxs("span", { className: "text-ui-fg-muted", children: [
441
- " ",
442
- prev ",
443
- formatCurrency$1(row.prev_aov)
444
- ] })
445
- ] })
446
- ] });
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
+ };
447
689
  }
448
690
  const KPI_ICON_BG$2 = {
449
691
  green: "bg-emerald-500/20",
@@ -517,7 +759,8 @@ function EmptyAnalyticsPanel$1({
517
759
  ] });
518
760
  }
519
761
  function OrdersDashboard() {
520
- var _a, _b;
762
+ var _a, _b, _c, _d, _e;
763
+ const { formatCurrency, formatCompactCurrency } = useAnalyticsCurrency();
521
764
  const [data, setData] = useState(null);
522
765
  const [loading, setLoading] = useState(true);
523
766
  const [error, setError] = useState(null);
@@ -527,6 +770,7 @@ function OrdersDashboard() {
527
770
  const [overTimePeriod, setOverTimePeriod] = useState("one_week");
528
771
  const [overTimeLoading, setOverTimeLoading] = useState(true);
529
772
  const [overTimeError, setOverTimeError] = useState(null);
773
+ const [overTimeOrdersMeta, setOverTimeOrdersMeta] = useState(null);
530
774
  const [ordersInsights, setOrdersInsights] = useState(null);
531
775
  const [insightsLoading, setInsightsLoading] = useState(true);
532
776
  const [insightsError, setInsightsError] = useState(null);
@@ -534,6 +778,81 @@ function OrdersDashboard() {
534
778
  const [todayContextLoading, setTodayContextLoading] = useState(true);
535
779
  const [todayContextError, setTodayContextError] = useState(null);
536
780
  const [salesGranularity, setSalesGranularity] = useState("day");
781
+ const [salesBreakdownDaily, setSalesBreakdownDaily] = useState(
782
+ []
783
+ );
784
+ const [salesBreakdownOtLoading, setSalesBreakdownOtLoading] = useState(true);
785
+ const [salesBreakdownOtError, setSalesBreakdownOtError] = useState(null);
786
+ const [salesBreakdownHourlyRolling, setSalesBreakdownHourlyRolling] = useState([]);
787
+ const [salesBreakdownHourlyLoading, setSalesBreakdownHourlyLoading] = useState(false);
788
+ const [salesBreakdownHourlyError, setSalesBreakdownHourlyError] = useState(null);
789
+ const salesBreakdownOtPeriod = useMemo(
790
+ () => getSalesBreakdownOtPeriod(salesGranularity),
791
+ [salesGranularity]
792
+ );
793
+ useEffect(() => {
794
+ if (salesBreakdownOtPeriod === null) {
795
+ setSalesBreakdownDaily([]);
796
+ setSalesBreakdownOtLoading(false);
797
+ setSalesBreakdownOtError(null);
798
+ return;
799
+ }
800
+ let cancelled = false;
801
+ setSalesBreakdownOtLoading(true);
802
+ setSalesBreakdownOtError(null);
803
+ fetch(
804
+ `/admin/analytics/orders-over-time?period=${salesBreakdownOtPeriod}`,
805
+ { credentials: "include" }
806
+ ).then((res) => {
807
+ if (!res.ok) throw new Error(res.statusText);
808
+ return res.json();
809
+ }).then((body) => {
810
+ if (!cancelled) {
811
+ setSalesBreakdownDaily(body.dailyOrders ?? []);
812
+ }
813
+ }).catch((e) => {
814
+ if (!cancelled) {
815
+ setSalesBreakdownOtError(e instanceof Error ? e.message : String(e));
816
+ }
817
+ }).finally(() => {
818
+ if (!cancelled) setSalesBreakdownOtLoading(false);
819
+ });
820
+ return () => {
821
+ cancelled = true;
822
+ };
823
+ }, [salesBreakdownOtPeriod]);
824
+ useEffect(() => {
825
+ if (salesGranularity !== "hour") {
826
+ setSalesBreakdownHourlyRolling([]);
827
+ setSalesBreakdownHourlyLoading(false);
828
+ setSalesBreakdownHourlyError(null);
829
+ return;
830
+ }
831
+ let cancelled = false;
832
+ setSalesBreakdownHourlyLoading(true);
833
+ setSalesBreakdownHourlyError(null);
834
+ fetch("/admin/analytics/orders-insights?last_hours=24", {
835
+ credentials: "include"
836
+ }).then((res) => {
837
+ if (!res.ok) throw new Error(res.statusText);
838
+ return res.json();
839
+ }).then((body) => {
840
+ if (!cancelled) {
841
+ setSalesBreakdownHourlyRolling(body.hourly_rolling ?? []);
842
+ }
843
+ }).catch((e) => {
844
+ if (!cancelled) {
845
+ setSalesBreakdownHourlyError(
846
+ e instanceof Error ? e.message : String(e)
847
+ );
848
+ }
849
+ }).finally(() => {
850
+ if (!cancelled) setSalesBreakdownHourlyLoading(false);
851
+ });
852
+ return () => {
853
+ cancelled = true;
854
+ };
855
+ }, [salesGranularity]);
537
856
  useEffect(() => {
538
857
  let cancelled = false;
539
858
  setLoading(true);
@@ -565,9 +884,13 @@ function OrdersDashboard() {
565
884
  if (!cancelled) {
566
885
  setDailyOrders(body.dailyOrders ?? []);
567
886
  setPreviousPeriodDailyOrders(body.previousPeriodDailyOrders ?? []);
887
+ setOverTimeOrdersMeta(body.ordersAnalyticsMeta ?? null);
568
888
  }
569
889
  }).catch((e) => {
570
- 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
+ }
571
894
  }).finally(() => {
572
895
  if (!cancelled) setOverTimeLoading(false);
573
896
  });
@@ -640,7 +963,7 @@ function OrdersDashboard() {
640
963
  const primaryStats = data ? [
641
964
  {
642
965
  label: "Total revenue",
643
- value: formatCurrency$1(data.totalRevenue),
966
+ value: formatCurrency(data.totalRevenue),
644
967
  helper: "Non-cancelled orders",
645
968
  accent: "green"
646
969
  },
@@ -658,7 +981,7 @@ function OrdersDashboard() {
658
981
  },
659
982
  {
660
983
  label: "Average order value",
661
- value: formatCurrency$1(data.aov),
984
+ value: formatCurrency(data.aov),
662
985
  helper: "Per non-cancelled order",
663
986
  accent: "amber"
664
987
  }
@@ -708,80 +1031,112 @@ function OrdersDashboard() {
708
1031
  );
709
1032
  const revenueVsPreviousPercent = previousPeriodRevenueTotal > 0 ? (trendRevenueTotal - previousPeriodRevenueTotal) / previousPeriodRevenueTotal * 100 : null;
710
1033
  const ordersVsPreviousPercent = previousPeriodOrdersTotal > 0 ? (trendOrdersTotal - previousPeriodOrdersTotal) / previousPeriodOrdersTotal * 100 : null;
711
- const trendRowsDetailed = useMemo(() => {
712
- return dailyOrders.map((d, i) => {
713
- const prev = previousPeriodDailyOrders[i];
714
- return {
715
- ...d,
716
- aov: bucketAov(d),
717
- prev_revenue: (prev == null ? void 0 : prev.total_revenue) ?? 0,
718
- prev_orders: (prev == null ? void 0 : prev.orders_count) ?? 0,
719
- prev_aov: prev ? bucketAov(prev) : 0
720
- };
721
- });
722
- }, [dailyOrders, previousPeriodDailyOrders]);
723
- const hourlyChartRows = useMemo(() => {
724
- if (!ordersInsights) return [];
725
- return ordersInsights.byHour.map((h) => ({
726
- label: `${String(h.hour).padStart(2, "0")}:00`,
727
- hour: h.hour,
728
- orders_count: h.orders_count,
729
- revenue: h.revenue
730
- }));
731
- }, [ordersInsights]);
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
+ ]);
732
1073
  const salesBreakdownChartRows = useMemo(() => {
733
1074
  if (salesGranularity === "hour") {
734
- return hourlyChartRows.map((h) => ({
735
- key: `hour-${h.hour}`,
736
- label: h.label,
737
- orders_count: h.orders_count,
738
- revenue: h.revenue
1075
+ return salesBreakdownHourlyRolling.map((b, i) => ({
1076
+ key: `rolling-${i}`,
1077
+ label: b.label,
1078
+ orders_count: b.orders_count,
1079
+ revenue: b.revenue
739
1080
  }));
740
1081
  }
741
1082
  if (salesGranularity === "week") {
742
- if (!ordersInsights) return [];
743
- return ordersInsights.byWeekday.map((w) => ({
744
- key: `weekday-${w.weekday}`,
745
- label: w.label,
746
- orders_count: w.orders_count,
747
- revenue: w.revenue
748
- }));
1083
+ const targetKeys = lastNWeekStartKeysUtc(7);
1084
+ const agg = /* @__PURE__ */ new Map();
1085
+ for (const d of salesBreakdownDaily) {
1086
+ const wk = utcSundayWeekStartKeyFromDateStr(d.date);
1087
+ const cur = agg.get(wk) ?? { orders: 0, revenue: 0 };
1088
+ cur.orders += d.orders_count;
1089
+ cur.revenue += d.total_revenue;
1090
+ agg.set(wk, cur);
1091
+ }
1092
+ return targetKeys.map((key) => {
1093
+ var _a2, _b2;
1094
+ return {
1095
+ key,
1096
+ label: formatWeekStartDm(key),
1097
+ orders_count: ((_a2 = agg.get(key)) == null ? void 0 : _a2.orders) ?? 0,
1098
+ revenue: ((_b2 = agg.get(key)) == null ? void 0 : _b2.revenue) ?? 0
1099
+ };
1100
+ });
749
1101
  }
750
1102
  if (salesGranularity === "month") {
751
- return aggregateDailyOrdersForBreakdown(
752
- dailyOrders,
753
- (d) => monthKeyFromDateStr(d.date),
754
- (ymKey) => formatMonthBucketLabel(ymKey)
755
- );
1103
+ return salesBreakdownDaily.map((d) => ({
1104
+ key: d.date,
1105
+ label: d.label ?? formatMonthBucketLabel(monthKeyFromDateStr(d.date)),
1106
+ orders_count: d.orders_count,
1107
+ revenue: d.total_revenue
1108
+ }));
756
1109
  }
757
- return dailyOrders.map((d) => ({
1110
+ return salesBreakdownDaily.map((d) => ({
758
1111
  key: d.date,
759
- label: d.label ?? formatChartLabel$2(d.date),
1112
+ label: formatDayBreakdownDm(d.date),
760
1113
  orders_count: d.orders_count,
761
1114
  revenue: d.total_revenue
762
1115
  }));
763
- }, [salesGranularity, dailyOrders, hourlyChartRows, ordersInsights]);
1116
+ }, [
1117
+ salesGranularity,
1118
+ salesBreakdownDaily,
1119
+ salesBreakdownHourlyRolling
1120
+ ]);
764
1121
  const salesBreakdownDescription = useMemo(() => {
765
- var _a2;
766
- const rangeLabel = ((_a2 = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _a2.label) ?? "selected range";
767
- const insightsHint = `Order-time breakdown uses last ${insightsWindowDays} days (UTC).`;
768
1122
  switch (salesGranularity) {
769
- case "day":
770
- return `Revenue by calendar day for ${rangeLabel} (aligned with Revenue & orders).`;
771
1123
  case "hour":
772
- return `${insightsHint} Bars show revenue by hour of day.`;
1124
+ return "Last 24 hours from now (UTC): one bar per clock hour in the rolling window.";
1125
+ case "day":
1126
+ return "Last 7 calendar days (UTC), one bar per day — axis day/month.";
773
1127
  case "week":
774
- return `Revenue by UTC weekday last ${insightsWindowDays} days.`;
1128
+ return "Last 7 weeks (UTC, weeks start Sunday); axis shows each week’s start as day/month.";
775
1129
  case "month":
776
- return `Revenue summed by calendar month (UTC) across ${rangeLabel}.`;
1130
+ return "Last 12 calendar months through the current month (UTC) Jan, Feb, Mar, …";
777
1131
  default:
778
1132
  return "";
779
1133
  }
780
- }, [salesGranularity, overTimePeriod, insightsWindowDays]);
1134
+ }, [salesGranularity]);
781
1135
  const salesBreakdownXAxisInterval = useMemo(() => {
782
1136
  const n = salesBreakdownChartRows.length;
783
1137
  if (salesGranularity === "hour") return 3;
784
- if (salesGranularity === "day" && n > 24) return Math.max(0, Math.ceil(n / 10) - 1);
1138
+ if (salesGranularity === "day" && n > 24)
1139
+ return Math.max(0, Math.ceil(n / 10) - 1);
785
1140
  return 0;
786
1141
  }, [salesGranularity, salesBreakdownChartRows.length]);
787
1142
  const ordersVsDraftsPie = useMemo(() => {
@@ -820,11 +1175,13 @@ function OrdersDashboard() {
820
1175
  };
821
1176
  });
822
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);
823
1180
  const quickPulseMetrics = [
824
1181
  {
825
1182
  label: "Range revenue",
826
- value: formatCompactCurrency$1(trendRevenueTotal),
827
- helper: selectedSummaryPeriod,
1183
+ value: formatCompactCurrency(trendRevenueTotal),
1184
+ helper: selectedTrendPeriodLabel,
828
1185
  accentClassName: "border-emerald-400/25 bg-emerald-500/5"
829
1186
  },
830
1187
  {
@@ -841,7 +1198,7 @@ function OrdersDashboard() {
841
1198
  },
842
1199
  {
843
1200
  label: "Peak revenue day",
844
- value: peakRevenuePoint ? formatCompactCurrency$1(peakRevenuePoint.total_revenue) : "—",
1201
+ value: peakRevenuePoint ? formatCompactCurrency(peakRevenuePoint.total_revenue) : "—",
845
1202
  helper: (peakRevenuePoint == null ? void 0 : peakRevenuePoint.label) ?? formatChartLabel$2(peakRevenuePoint == null ? void 0 : peakRevenuePoint.date),
846
1203
  accentClassName: "border-amber-400/25 bg-amber-500/5"
847
1204
  }
@@ -860,20 +1217,14 @@ function OrdersDashboard() {
860
1217
  if (error || !data) {
861
1218
  return /* @__PURE__ */ jsx(Container, { className: "p-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-danger", children: error ?? "Failed to load analytics" }) });
862
1219
  }
863
- const needsSalesOverTime = salesGranularity === "day" || salesGranularity === "month";
864
- const needsSalesInsights = salesGranularity === "hour" || salesGranularity === "week";
1220
+ const salesBreakdownWaiting = salesGranularity === "hour" ? salesBreakdownHourlyLoading : salesBreakdownOtLoading;
1221
+ const salesBreakdownFetchError = salesGranularity === "hour" ? salesBreakdownHourlyError : salesBreakdownOtError;
865
1222
  const salesBreakdownBody = (() => {
866
- if (needsSalesOverTime && overTimeLoading) {
867
- return /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center py-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" }) }) });
868
- }
869
- if (needsSalesOverTime && overTimeError) {
870
- return /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-danger text-center text-xs", children: overTimeError }) }) });
871
- }
872
- if (needsSalesInsights && insightsLoading) {
1223
+ if (salesBreakdownWaiting) {
873
1224
  return /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center py-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" }) }) });
874
1225
  }
875
- if (needsSalesInsights && insightsError) {
876
- return /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) });
1226
+ if (salesBreakdownFetchError) {
1227
+ return /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-danger text-center text-xs", children: salesBreakdownFetchError }) }) });
877
1228
  }
878
1229
  if (salesBreakdownChartRows.length === 0) {
879
1230
  return /* @__PURE__ */ jsx(
@@ -915,7 +1266,7 @@ function OrdersDashboard() {
915
1266
  tick: CHART_AXIS_TICK_SM$1,
916
1267
  tickLine: false,
917
1268
  axisLine: false,
918
- tickFormatter: (v) => formatCompactCurrency$1(Number(v))
1269
+ tickFormatter: (v) => formatCompactCurrency(Number(v))
919
1270
  }
920
1271
  ),
921
1272
  /* @__PURE__ */ jsx(
@@ -939,6 +1290,19 @@ function OrdersDashboard() {
939
1290
  ) }) }) });
940
1291
  })();
941
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,
942
1306
  /* @__PURE__ */ jsx(
943
1307
  AnalyticsDashboardHeader,
944
1308
  {
@@ -1027,10 +1391,10 @@ function OrdersDashboard() {
1027
1391
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
1028
1392
  /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
1029
1393
  /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window revenue" }),
1030
- /* @__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) }),
1031
1395
  /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-muted text-[10px]", children: [
1032
1396
  "Avg/day ",
1033
- formatCompactCurrency$1(trendAverageRevenue)
1397
+ formatCompactCurrency(trendAverageRevenue)
1034
1398
  ] })
1035
1399
  ] }) }),
1036
1400
  /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
@@ -1053,187 +1417,63 @@ function OrdersDashboard() {
1053
1417
  ] }),
1054
1418
  !overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1055
1419
  /* @__PURE__ */ jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
1056
- /* @__PURE__ */ jsxs("div", { className: "mb-1 flex flex-col gap-0.5 sm:flex-row sm:items-start sm:justify-between", children: [
1057
- /* @__PURE__ */ jsxs("div", { children: [
1058
- /* @__PURE__ */ jsx(Text, { className: "text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue, orders & AOV" }),
1059
- /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-[9px] leading-snug", children: "One timeline · solid = this range, dashed = previous period (same length)" })
1060
- ] }),
1061
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-x-3 gap-y-0.5 text-[9px] text-ui-fg-muted sm:justify-end", children: [
1062
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1063
- /* @__PURE__ */ jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-fuchsia-500" }),
1064
- "Revenue"
1065
- ] }),
1066
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1067
- /* @__PURE__ */ jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-sky-400" }),
1068
- "Orders"
1069
- ] }),
1070
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1071
- /* @__PURE__ */ jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-amber-500" }),
1072
- "AOV"
1073
- ] }),
1074
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1075
- /* @__PURE__ */ jsx(
1076
- "span",
1077
- {
1078
- className: "h-0 w-4 shrink-0 border-t border-dashed border-slate-400",
1079
- "aria-hidden": true
1080
- }
1081
- ),
1082
- "Previous"
1083
- ] })
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
+ "."
1084
1427
  ] })
1085
1428
  ] }),
1086
- /* @__PURE__ */ jsx("div", { className: "h-[min(220px,32vh)] min-h-[156px] w-full", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(
1087
- ComposedChart,
1429
+ /* @__PURE__ */ jsxs(
1430
+ "div",
1088
1431
  {
1089
- data: trendRowsDetailed,
1090
- 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.`,
1091
1435
  children: [
1092
- /* @__PURE__ */ jsx(
1093
- CartesianGrid,
1094
- {
1095
- strokeDasharray: "3 3",
1096
- vertical: false,
1097
- stroke: "rgba(148,163,184,0.12)"
1098
- }
1099
- ),
1100
- /* @__PURE__ */ jsx(
1101
- XAxis,
1102
- {
1103
- dataKey: "date",
1104
- tick: CHART_AXIS_TICK_SM$1,
1105
- tickLine: false,
1106
- axisLine: false,
1107
- tickFormatter: (value, index) => {
1108
- const row = trendRowsDetailed[index];
1109
- return (row == null ? void 0 : row.label) ?? formatChartLabel$2(String(value));
1110
- }
1111
- }
1112
- ),
1113
- /* @__PURE__ */ jsx(
1114
- YAxis,
1115
- {
1116
- yAxisId: "orders",
1117
- width: 30,
1118
- tick: CHART_AXIS_TICK_SM$1,
1119
- tickLine: false,
1120
- axisLine: false,
1121
- allowDecimals: false,
1122
- tickFormatter: (v) => Math.floor(Number(v) || 0).toLocaleString()
1123
- }
1124
- ),
1125
- /* @__PURE__ */ jsx(
1126
- YAxis,
1127
- {
1128
- yAxisId: "revenue",
1129
- orientation: "right",
1130
- width: 42,
1131
- tick: CHART_AXIS_TICK_SM$1,
1132
- tickLine: false,
1133
- axisLine: false,
1134
- tickFormatter: (v) => formatCompactCurrency$1(Number(v))
1135
- }
1136
- ),
1137
- /* @__PURE__ */ jsx(
1138
- YAxis,
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,
1139
1438
  {
1140
- yAxisId: "aov",
1141
- orientation: "right",
1142
- width: 40,
1143
- tick: CHART_AXIS_TICK_SM$1,
1144
- tickLine: false,
1145
- axisLine: false,
1146
- tickFormatter: (v) => formatCompactCurrency$1(Number(v))
1147
- }
1148
- ),
1149
- /* @__PURE__ */ jsx(
1150
- Tooltip,
1151
- {
1152
- content: /* @__PURE__ */ jsx(CombinedMetricsTooltip, {}),
1153
- cursor: {
1154
- stroke: "rgba(248, 250, 252, 0.35)",
1155
- strokeWidth: 1
1156
- }
1157
- }
1158
- ),
1159
- /* @__PURE__ */ jsx(
1160
- Line,
1161
- {
1162
- yAxisId: "orders",
1163
- type: "natural",
1164
- dataKey: "orders_count",
1165
- name: "Orders",
1166
- stroke: "#38BDF8",
1167
- strokeWidth: 2,
1168
- dot: false
1169
- }
1170
- ),
1171
- /* @__PURE__ */ jsx(
1172
- Line,
1173
- {
1174
- yAxisId: "orders",
1175
- type: "natural",
1176
- dataKey: "prev_orders",
1177
- name: "Orders (prev)",
1178
- stroke: "#64748b",
1179
- strokeWidth: 1.5,
1180
- strokeDasharray: "5 4",
1181
- dot: false
1182
- }
1183
- ),
1184
- /* @__PURE__ */ jsx(
1185
- Line,
1186
- {
1187
- yAxisId: "revenue",
1188
- type: "natural",
1189
- dataKey: "total_revenue",
1190
- name: "Revenue",
1191
- stroke: "#D946EF",
1192
- strokeWidth: 2,
1193
- dot: false
1194
- }
1195
- ),
1196
- /* @__PURE__ */ jsx(
1197
- Line,
1198
- {
1199
- yAxisId: "revenue",
1200
- type: "natural",
1201
- dataKey: "prev_revenue",
1202
- name: "Revenue (prev)",
1203
- stroke: "#64748b",
1204
- strokeWidth: 1.5,
1205
- strokeDasharray: "5 4",
1206
- dot: false
1207
- }
1208
- ),
1209
- /* @__PURE__ */ jsx(
1210
- Line,
1211
- {
1212
- yAxisId: "aov",
1213
- type: "natural",
1214
- dataKey: "aov",
1215
- name: "AOV",
1216
- stroke: "#D97706",
1217
- strokeWidth: 2,
1218
- dot: false
1219
- }
1220
- ),
1221
- /* @__PURE__ */ jsx(
1222
- Line,
1223
- {
1224
- yAxisId: "aov",
1225
- type: "natural",
1226
- dataKey: "prev_aov",
1227
- name: "AOV (prev)",
1228
- stroke: "#64748b",
1229
- strokeWidth: 1.5,
1230
- strokeDasharray: "5 4",
1231
- 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
+ )
1232
1463
  }
1233
- )
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
+ ] })
1234
1474
  ]
1235
1475
  }
1236
- ) }) })
1476
+ )
1237
1477
  ] }),
1238
1478
  /* @__PURE__ */ jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
1239
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)" }),
@@ -1258,9 +1498,11 @@ function OrdersDashboard() {
1258
1498
  tick: CHART_AXIS_TICK$2,
1259
1499
  tickLine: false,
1260
1500
  axisLine: false,
1261
- tickFormatter: (value, index) => {
1262
- const row = dailyOrders[index];
1263
- 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));
1264
1506
  }
1265
1507
  }
1266
1508
  ),
@@ -1268,7 +1510,7 @@ function OrdersDashboard() {
1268
1510
  YAxis,
1269
1511
  {
1270
1512
  tick: CHART_AXIS_TICK$2,
1271
- tickFormatter: (v) => formatCompactCurrency$1(Number(v))
1513
+ tickFormatter: (v) => formatCompactCurrency(Number(v))
1272
1514
  }
1273
1515
  ),
1274
1516
  /* @__PURE__ */ jsx(
@@ -1278,7 +1520,7 @@ function OrdersDashboard() {
1278
1520
  p.name,
1279
1521
  ":",
1280
1522
  " ",
1281
- /* @__PURE__ */ jsx("strong", { children: formatCurrency$1(Number(p.value) || 0) })
1523
+ /* @__PURE__ */ jsx("strong", { children: formatCurrency(Number(p.value) || 0) })
1282
1524
  ] }, String(p.name))) }) : null
1283
1525
  }
1284
1526
  ),
@@ -1353,7 +1595,7 @@ function OrdersDashboard() {
1353
1595
  {
1354
1596
  variant: "atlas",
1355
1597
  title: "Order → fulfillment funnel",
1356
- 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.`,
1357
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: [
1358
1600
  /* @__PURE__ */ jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
1359
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)" }),
@@ -1834,29 +2076,32 @@ function OrdersDashboard() {
1834
2076
  title: "Sales breakdown",
1835
2077
  description: salesBreakdownDescription,
1836
2078
  actionsBare: true,
1837
- actions: /* @__PURE__ */ jsx(
1838
- "div",
1839
- {
1840
- className: "flex flex-wrap items-center gap-1 rounded-lg border border-ui-border-base bg-ui-bg-subtle/40 p-0.5",
1841
- role: "tablist",
1842
- "aria-label": "Sales breakdown granularity",
1843
- children: SALES_GRANULARITY_TABS.map((tab) => {
1844
- const selected = salesGranularity === tab.value;
1845
- return /* @__PURE__ */ jsx(
1846
- "button",
1847
- {
1848
- type: "button",
1849
- role: "tab",
1850
- "aria-selected": selected,
1851
- onClick: () => setSalesGranularity(tab.value),
1852
- className: `rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ui-fg-interactive ${selected ? "bg-ui-bg-base text-ui-fg-base shadow-sm" : "text-ui-fg-muted hover:text-ui-fg-base"}`,
1853
- children: tab.label
1854
- },
1855
- tab.value
1856
- );
1857
- })
1858
- }
1859
- ),
2079
+ actions: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-stretch gap-1 sm:flex-row sm:items-center sm:gap-2", children: [
2080
+ /* @__PURE__ */ jsx(
2081
+ Label,
2082
+ {
2083
+ htmlFor: "sales-breakdown-granularity",
2084
+ className: "shrink-0 text-ui-fg-muted text-xs font-medium",
2085
+ children: "Breakdown"
2086
+ }
2087
+ ),
2088
+ /* @__PURE__ */ jsx(
2089
+ "select",
2090
+ {
2091
+ id: "sales-breakdown-granularity",
2092
+ value: salesGranularity,
2093
+ onChange: (e) => {
2094
+ const v = e.target.value;
2095
+ if (v === "hour" || v === "day" || v === "week" || v === "month") {
2096
+ setSalesGranularity(v);
2097
+ }
2098
+ },
2099
+ className: SELECT_CLASS_NAME$2,
2100
+ "aria-label": "Sales breakdown time bucket",
2101
+ children: SALES_GRANULARITY_TABS.map((tab) => /* @__PURE__ */ jsx("option", { value: tab.value, children: tab.value === "hour" ? "Hour (last 24h)" : tab.value === "day" ? "Day (last 7 days)" : tab.value === "week" ? "Week (last 7 weeks)" : "Month (last 12 months)" }, tab.value))
2102
+ }
2103
+ )
2104
+ ] }),
1860
2105
  children: salesBreakdownBody
1861
2106
  }
1862
2107
  ),
@@ -2020,7 +2265,7 @@ function formatChartLabel$1(value) {
2020
2265
  return `${parsed.getUTCMonth() + 1}/${parsed.getUTCDate()}`;
2021
2266
  }
2022
2267
  function CustomersDashboard() {
2023
- var _a, _b;
2268
+ var _a, _b, _c;
2024
2269
  const [data, setData] = useState(null);
2025
2270
  const [loading, setLoading] = useState(true);
2026
2271
  const [error, setError] = useState(null);
@@ -2259,6 +2504,13 @@ function CustomersDashboard() {
2259
2504
  title: "Repeat purchases",
2260
2505
  description: "Orders with a customer_id in the same window (guest checkouts excluded from rate).",
2261
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,
2262
2514
  /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
2263
2515
  /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Repeat rate" }),
2264
2516
  /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: `${repeatStats.repeat_rate_percent.toFixed(1)}%` }),
@@ -2323,7 +2575,7 @@ function CustomersDashboard() {
2323
2575
  ] }) }),
2324
2576
  /* @__PURE__ */ jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
2325
2577
  /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Chart period" }),
2326
- /* @__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 }),
2327
2579
  /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: "Independent of summary period filter" })
2328
2580
  ] }) })
2329
2581
  ] }),
@@ -2357,12 +2609,12 @@ function CustomersDashboard() {
2357
2609
  stroke: CHART_AXIS_LINE$1,
2358
2610
  tickLine: false,
2359
2611
  axisLine: { stroke: CHART_AXIS_LINE$1 },
2360
- tickFormatter: (_v, index) => {
2361
- var _a2;
2362
- const row = series[index];
2612
+ tickFormatter: (value) => {
2613
+ const row = series.find((p) => p.date === String(value));
2363
2614
  if (row == null ? void 0 : row.label) return row.label;
2364
- const d = (_a2 = series[index]) == null ? void 0 : _a2.date;
2365
- return formatChartLabel$1(d);
2615
+ return formatChartLabel$1(
2616
+ (row == null ? void 0 : row.date) ?? (typeof value === "string" ? value : String(value))
2617
+ );
2366
2618
  }
2367
2619
  }
2368
2620
  ),
@@ -2417,6 +2669,16 @@ function CustomersDashboard() {
2417
2669
  ] })
2418
2670
  ] }) });
2419
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
+ }
2420
2682
  const SUMMARY_PERIODS = [
2421
2683
  { value: "all", label: "All time" },
2422
2684
  { value: "0", label: "Today" },
@@ -2461,22 +2723,6 @@ const SELECT_CLASS_NAME = "h-9 w-full min-w-[8rem] max-w-full cursor-pointer app
2461
2723
  const LEADERBOARD_TABLE_CLASS = "min-w-[32rem] sm:min-w-[36rem] max-w-none";
2462
2724
  const OPPORTUNITIES_TABLE_CLASS = "min-w-[30rem] sm:min-w-[34rem] max-w-none";
2463
2725
  const TABLE_HEAD_CELL_NOWRAP = "whitespace-nowrap";
2464
- function formatCurrency(value) {
2465
- return new Intl.NumberFormat("en-IN", {
2466
- style: "currency",
2467
- currency: "INR",
2468
- minimumFractionDigits: 0,
2469
- maximumFractionDigits: 0
2470
- }).format(value);
2471
- }
2472
- function formatCompactCurrency(value) {
2473
- return new Intl.NumberFormat("en-IN", {
2474
- style: "currency",
2475
- currency: "INR",
2476
- notation: "compact",
2477
- maximumFractionDigits: 1
2478
- }).format(value);
2479
- }
2480
2726
  function formatChartLabel(value) {
2481
2727
  if (!value) return "";
2482
2728
  const parsed = new Date(value);
@@ -2503,16 +2749,6 @@ function summaryDaysToGraphPeriod(days) {
2503
2749
  if (days === "all" || days === "90") return "one_year";
2504
2750
  return "one_week";
2505
2751
  }
2506
- function buildAnalyticsUrl(path, params) {
2507
- const search = new URLSearchParams();
2508
- for (const [key, value] of Object.entries(params)) {
2509
- if (value) {
2510
- search.set(key, value);
2511
- }
2512
- }
2513
- const query = search.toString();
2514
- return query ? `${path}?${query}` : path;
2515
- }
2516
2752
  function isProductOverTimePoint(value) {
2517
2753
  if (typeof value !== "object" || value === null) return false;
2518
2754
  const v = value;
@@ -2524,6 +2760,7 @@ function TrendTooltip({
2524
2760
  label
2525
2761
  }) {
2526
2762
  var _a, _b, _c, _d, _e;
2763
+ const { formatCurrency } = useAnalyticsCurrency();
2527
2764
  if (!active || !(payload == null ? void 0 : payload.length)) return null;
2528
2765
  const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
2529
2766
  const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
@@ -2555,6 +2792,7 @@ function MiniTrendTooltip({
2555
2792
  hideRevenueRow
2556
2793
  }) {
2557
2794
  var _a, _b, _c, _d, _e;
2795
+ const { formatCurrency } = useAnalyticsCurrency();
2558
2796
  if (!active || !(payload == null ? void 0 : payload.length)) return null;
2559
2797
  const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
2560
2798
  const labelKey = label !== void 0 && label !== null ? String(label) : "";
@@ -2585,6 +2823,7 @@ function ProductBarTooltip({
2585
2823
  showViews = true
2586
2824
  }) {
2587
2825
  var _a;
2826
+ const { formatCurrency } = useAnalyticsCurrency();
2588
2827
  if (!active || !(payload == null ? void 0 : payload.length)) return null;
2589
2828
  const row = (_a = payload[0]) == null ? void 0 : _a.payload;
2590
2829
  if (!row) return null;
@@ -2637,6 +2876,9 @@ function EmptyAnalyticsPanel({ title, description }) {
2637
2876
  }
2638
2877
  function ProductsDashboard() {
2639
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}`;
2640
2882
  const [summaryDays, setSummaryDays] = useState("all");
2641
2883
  const [graphPeriod, setGraphPeriod] = useState("one_week");
2642
2884
  const [topSellerPeriod, setTopSellerPeriod] = useState("week");
@@ -3223,26 +3465,76 @@ function ProductsDashboard() {
3223
3465
  margin: { top: 4, right: 6, left: -6, bottom: 0 },
3224
3466
  children: [
3225
3467
  /* @__PURE__ */ jsxs("defs", { children: [
3226
- /* @__PURE__ */ jsxs("linearGradient", { id: "productUnitsGradient", x1: "0", y1: "0", x2: "1", y2: "0", children: [
3227
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#67E8F9" }),
3228
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#3B82F6" })
3229
- ] }),
3230
- /* @__PURE__ */ jsxs("linearGradient", { id: "productRevenueGradient", x1: "0", y1: "0", x2: "1", y2: "0", children: [
3231
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#F472B6" }),
3232
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#C084FC" })
3233
- ] }),
3234
- /* @__PURE__ */ jsxs("linearGradient", { id: "productUnitsAreaFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
3235
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#38BDF8", stopOpacity: 0.22 }),
3236
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#38BDF8", stopOpacity: 0 })
3237
- ] }),
3238
- /* @__PURE__ */ jsxs("linearGradient", { id: "productRevenueAreaFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
3239
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.2 }),
3240
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#E879F9", stopOpacity: 0 })
3241
- ] }),
3242
- /* @__PURE__ */ jsxs("linearGradient", { id: "productViewsAreaFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
3243
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#C084FC", stopOpacity: 0.18 }),
3244
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#C084FC", stopOpacity: 0 })
3245
- ] })
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
+ )
3246
3538
  ] }),
3247
3539
  /* @__PURE__ */ jsx(
3248
3540
  CartesianGrid,
@@ -3259,8 +3551,8 @@ function ProductsDashboard() {
3259
3551
  tick: CHART_AXIS_TICK,
3260
3552
  tickLine: false,
3261
3553
  axisLine: false,
3262
- tickFormatter: (value, index) => {
3263
- const row = series[index];
3554
+ tickFormatter: (value) => {
3555
+ const row = series.find((p) => p.date === String(value));
3264
3556
  return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
3265
3557
  }
3266
3558
  }
@@ -3306,7 +3598,7 @@ function ProductsDashboard() {
3306
3598
  type: "natural",
3307
3599
  dataKey: "units_sold",
3308
3600
  stroke: "none",
3309
- fill: "url(#productUnitsAreaFill)",
3601
+ fill: `url(#${gid("trend-units-area")})`,
3310
3602
  isAnimationActive: false,
3311
3603
  legendType: "none"
3312
3604
  }
@@ -3318,7 +3610,7 @@ function ProductsDashboard() {
3318
3610
  type: "natural",
3319
3611
  dataKey: "views",
3320
3612
  stroke: "none",
3321
- fill: "url(#productViewsAreaFill)",
3613
+ fill: `url(#${gid("trend-views-area")})`,
3322
3614
  isAnimationActive: false,
3323
3615
  legendType: "none"
3324
3616
  }
@@ -3330,7 +3622,7 @@ function ProductsDashboard() {
3330
3622
  type: "natural",
3331
3623
  dataKey: "revenue",
3332
3624
  stroke: "none",
3333
- fill: "url(#productRevenueAreaFill)",
3625
+ fill: `url(#${gid("trend-revenue-area")})`,
3334
3626
  isAnimationActive: false,
3335
3627
  legendType: "none"
3336
3628
  }
@@ -3342,7 +3634,7 @@ function ProductsDashboard() {
3342
3634
  type: "natural",
3343
3635
  dataKey: "units_sold",
3344
3636
  name: "Units sold",
3345
- stroke: "url(#productUnitsGradient)",
3637
+ stroke: `url(#${gid("trend-units-line")})`,
3346
3638
  strokeWidth: 2,
3347
3639
  dot: false,
3348
3640
  activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.units }
@@ -3369,7 +3661,7 @@ function ProductsDashboard() {
3369
3661
  type: "natural",
3370
3662
  dataKey: "revenue",
3371
3663
  name: "Revenue",
3372
- stroke: "url(#productRevenueGradient)",
3664
+ stroke: `url(#${gid("trend-revenue-line")})`,
3373
3665
  strokeWidth: 2,
3374
3666
  dot: false,
3375
3667
  activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.revenue }
@@ -3440,7 +3732,7 @@ function ProductsDashboard() {
3440
3732
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
3441
3733
  "linearGradient",
3442
3734
  {
3443
- id: "bestSellersRevenueArea",
3735
+ id: gid("best-revenue-area"),
3444
3736
  x1: "0",
3445
3737
  y1: "0",
3446
3738
  x2: "0",
@@ -3478,7 +3770,12 @@ function ProductsDashboard() {
3478
3770
  {
3479
3771
  dataKey: "date",
3480
3772
  tick: CHART_AXIS_TICK_SM,
3481
- 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
+ },
3482
3779
  tickLine: false,
3483
3780
  axisLine: { stroke: CHART_AXIS_LINE }
3484
3781
  }
@@ -3528,7 +3825,7 @@ function ProductsDashboard() {
3528
3825
  dataKey: "revenue",
3529
3826
  name: "Revenue",
3530
3827
  stroke: "transparent",
3531
- fill: "url(#bestSellersRevenueArea)",
3828
+ fill: `url(#${gid("best-revenue-area")})`,
3532
3829
  isAnimationActive: false
3533
3830
  }
3534
3831
  ),
@@ -3643,7 +3940,7 @@ function ProductsDashboard() {
3643
3940
  /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
3644
3941
  "linearGradient",
3645
3942
  {
3646
- id: "mostViewedViewsArea",
3943
+ id: gid("viewed-views-area"),
3647
3944
  x1: "0",
3648
3945
  y1: "0",
3649
3946
  x2: "0",
@@ -3681,7 +3978,12 @@ function ProductsDashboard() {
3681
3978
  {
3682
3979
  dataKey: "date",
3683
3980
  tick: CHART_AXIS_TICK_SM,
3684
- 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
+ },
3685
3987
  tickLine: false,
3686
3988
  axisLine: { stroke: CHART_AXIS_LINE }
3687
3989
  }
@@ -3718,7 +4020,7 @@ function ProductsDashboard() {
3718
4020
  dataKey: "views",
3719
4021
  name: "Views",
3720
4022
  stroke: "transparent",
3721
- fill: "url(#mostViewedViewsArea)",
4023
+ fill: `url(#${gid("viewed-views-area")})`,
3722
4024
  isAnimationActive: false
3723
4025
  }
3724
4026
  ),
@@ -3780,7 +4082,7 @@ function ProductsDashboard() {
3780
4082
  title: "Views vs units sold",
3781
4083
  description: "Each point is a product (top viewed in this period). Requires product view tracking.",
3782
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: [
3783
- /* @__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: [
3784
4086
  /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.95 }),
3785
4087
  /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "#6366F1", stopOpacity: 0.65 })
3786
4088
  ] }) }),
@@ -3841,7 +4143,7 @@ function ProductsDashboard() {
3841
4143
  {
3842
4144
  name: "Products",
3843
4145
  data: viewsVsUnitsScatterData,
3844
- fill: "url(#productScatterGlow)"
4146
+ fill: `url(#${gid("scatter-glow")})`
3845
4147
  }
3846
4148
  )
3847
4149
  ] }) }) }) })
@@ -3857,7 +4159,7 @@ const ANALYTICS_MODULES = [
3857
4159
  ];
3858
4160
  const AnalyticsPage = () => {
3859
4161
  const [activeModule, setActiveModule] = useState("orders");
3860
- 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(
3861
4163
  "div",
3862
4164
  {
3863
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(),
@@ -3898,7 +4200,7 @@ const AnalyticsPage = () => {
3898
4200
  ] })
3899
4201
  ]
3900
4202
  }
3901
- ) }) });
4203
+ ) }) }) });
3902
4204
  };
3903
4205
  const config = defineRouteConfig({
3904
4206
  label: "Analytics",