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.
- package/.medusa/server/src/admin/index.js +732 -430
- package/.medusa/server/src/admin/index.mjs +734 -432
- package/.medusa/server/src/api/admin/analytics/fetch-orders-for-analytics.js +21 -9
- package/.medusa/server/src/api/admin/analytics/orders-analytics-meta.js +18 -0
- package/.medusa/server/src/api/admin/analytics/orders-insights/route.js +73 -10
- package/.medusa/server/src/api/admin/analytics/orders-over-time/route.js +16 -5
- package/.medusa/server/src/api/admin/analytics/orders-summary/route.js +3 -2
- package/.medusa/server/src/api/admin/analytics/repeat-customers/route.js +3 -2
- package/.medusa/server/src/api/admin/analytics/store-context/resolve-default-currency-code.js +86 -0
- package/.medusa/server/src/api/admin/analytics/store-context/route.js +19 -0
- package/.medusa/server/src/api/admin/analytics/store-context/types.js +3 -0
- package/package.json +4 -2
|
@@ -5,6 +5,64 @@ const adminSdk = require("@medusajs/admin-sdk");
|
|
|
5
5
|
const icons = require("@medusajs/icons");
|
|
6
6
|
const ui = require("@medusajs/ui");
|
|
7
7
|
const recharts = require("recharts");
|
|
8
|
+
const AnalyticsCurrencyContext = react.createContext(
|
|
9
|
+
null
|
|
10
|
+
);
|
|
11
|
+
function formatMoney(value, currencyCode, compact) {
|
|
12
|
+
return new Intl.NumberFormat(void 0, {
|
|
13
|
+
style: "currency",
|
|
14
|
+
currency: currencyCode,
|
|
15
|
+
minimumFractionDigits: 0,
|
|
16
|
+
maximumFractionDigits: compact ? 1 : 0,
|
|
17
|
+
...compact ? { notation: "compact" } : {}
|
|
18
|
+
}).format(value);
|
|
19
|
+
}
|
|
20
|
+
function AnalyticsCurrencyProvider({ children }) {
|
|
21
|
+
const [currencyCode, setCurrencyCode] = react.useState("USD");
|
|
22
|
+
react.useEffect(() => {
|
|
23
|
+
let cancelled = false;
|
|
24
|
+
fetch("/admin/analytics/store-context", { credentials: "include" }).then((res) => {
|
|
25
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
26
|
+
return res.json();
|
|
27
|
+
}).then((body) => {
|
|
28
|
+
if (cancelled) return;
|
|
29
|
+
const code = typeof body.default_currency_code === "string" ? body.default_currency_code.trim().toUpperCase() : "";
|
|
30
|
+
if (code && /^[A-Z]{3}$/.test(code)) {
|
|
31
|
+
setCurrencyCode(code);
|
|
32
|
+
}
|
|
33
|
+
}).catch(() => {
|
|
34
|
+
if (!cancelled) setCurrencyCode("USD");
|
|
35
|
+
});
|
|
36
|
+
return () => {
|
|
37
|
+
cancelled = true;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
const formatCurrency = react.useCallback(
|
|
41
|
+
(value2) => formatMoney(value2, currencyCode, false),
|
|
42
|
+
[currencyCode]
|
|
43
|
+
);
|
|
44
|
+
const formatCompactCurrency = react.useCallback(
|
|
45
|
+
(value2) => formatMoney(value2, currencyCode, true),
|
|
46
|
+
[currencyCode]
|
|
47
|
+
);
|
|
48
|
+
const value = react.useMemo(
|
|
49
|
+
() => ({
|
|
50
|
+
currencyCode,
|
|
51
|
+
formatCurrency,
|
|
52
|
+
formatCompactCurrency
|
|
53
|
+
}),
|
|
54
|
+
[currencyCode, formatCurrency, formatCompactCurrency]
|
|
55
|
+
);
|
|
56
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsCurrencyContext.Provider, { value, children });
|
|
57
|
+
}
|
|
58
|
+
function useAnalyticsCurrency() {
|
|
59
|
+
const ctx = react.useContext(AnalyticsCurrencyContext);
|
|
60
|
+
if (!ctx) {
|
|
61
|
+
throw new Error("useAnalyticsCurrency must be used within AnalyticsCurrencyProvider");
|
|
62
|
+
}
|
|
63
|
+
return ctx;
|
|
64
|
+
}
|
|
65
|
+
const ORDERS_ANALYTICS_MAX_FETCH = 5e4;
|
|
8
66
|
const ACCENT_STYLES = {
|
|
9
67
|
blue: {
|
|
10
68
|
border: "border-sky-400/30",
|
|
@@ -263,30 +321,9 @@ function nonCancelledRevenue(d) {
|
|
|
263
321
|
}
|
|
264
322
|
return Math.max(0, d.total_revenue - (d.revenue_cancelled ?? 0));
|
|
265
323
|
}
|
|
266
|
-
function bucketAov(d) {
|
|
267
|
-
const ordNc = d.orders_count - d.cancelled_count;
|
|
268
|
-
if (ordNc <= 0) return 0;
|
|
269
|
-
return nonCancelledRevenue(d) / ordNc;
|
|
270
|
-
}
|
|
271
324
|
function isOrdersTodayPoint(value) {
|
|
272
325
|
return isDailyOrderRow(value) && "isToday" in value && typeof value.isToday === "boolean";
|
|
273
326
|
}
|
|
274
|
-
function formatCurrency$1(value) {
|
|
275
|
-
return new Intl.NumberFormat("en-IN", {
|
|
276
|
-
style: "currency",
|
|
277
|
-
currency: "INR",
|
|
278
|
-
minimumFractionDigits: 0,
|
|
279
|
-
maximumFractionDigits: 0
|
|
280
|
-
}).format(value);
|
|
281
|
-
}
|
|
282
|
-
function formatCompactCurrency$1(value) {
|
|
283
|
-
return new Intl.NumberFormat("en-IN", {
|
|
284
|
-
style: "currency",
|
|
285
|
-
currency: "INR",
|
|
286
|
-
notation: "compact",
|
|
287
|
-
maximumFractionDigits: 1
|
|
288
|
-
}).format(value);
|
|
289
|
-
}
|
|
290
327
|
function formatChartLabel$2(value) {
|
|
291
328
|
if (!value) return "";
|
|
292
329
|
const parsed = new Date(value);
|
|
@@ -303,15 +340,66 @@ function formatShortNumber$1(value) {
|
|
|
303
340
|
}).format(value);
|
|
304
341
|
}
|
|
305
342
|
const SALES_GRANULARITY_TABS = [
|
|
306
|
-
{ value: "day", label: "Day" },
|
|
307
343
|
{ value: "hour", label: "Hour" },
|
|
344
|
+
{ value: "day", label: "Day" },
|
|
308
345
|
{ value: "week", label: "Week" },
|
|
309
346
|
{ value: "month", label: "Month" }
|
|
310
347
|
];
|
|
348
|
+
function getSalesBreakdownOtPeriod(g) {
|
|
349
|
+
if (g === "day") return "one_week";
|
|
350
|
+
if (g === "week") return "two_months";
|
|
351
|
+
if (g === "month") return "one_year";
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
311
354
|
function monthKeyFromDateStr(dateStr) {
|
|
312
355
|
if (dateStr.length >= 7) return dateStr.slice(0, 7);
|
|
313
356
|
return dateStr;
|
|
314
357
|
}
|
|
358
|
+
function utcSundayWeekStartKeyFromDateStr(dateStr) {
|
|
359
|
+
const parsed = new Date(dateStr);
|
|
360
|
+
if (Number.isNaN(parsed.getTime())) return dateStr;
|
|
361
|
+
const y = parsed.getUTCFullYear();
|
|
362
|
+
const m = parsed.getUTCMonth();
|
|
363
|
+
const day = parsed.getUTCDate();
|
|
364
|
+
const dow = parsed.getUTCDay();
|
|
365
|
+
const start = new Date(Date.UTC(y, m, day - dow));
|
|
366
|
+
const ys = start.getUTCFullYear();
|
|
367
|
+
const ms = String(start.getUTCMonth() + 1).padStart(2, "0");
|
|
368
|
+
const ds = String(start.getUTCDate()).padStart(2, "0");
|
|
369
|
+
return `${ys}-${ms}-${ds}`;
|
|
370
|
+
}
|
|
371
|
+
function formatWeekStartDm(weekStartKey) {
|
|
372
|
+
const parsed = /* @__PURE__ */ new Date(`${weekStartKey}T00:00:00.000Z`);
|
|
373
|
+
if (Number.isNaN(parsed.getTime())) return weekStartKey;
|
|
374
|
+
return `${parsed.getUTCDate()}/${parsed.getUTCMonth() + 1}`;
|
|
375
|
+
}
|
|
376
|
+
function formatDayBreakdownDm(dateStr) {
|
|
377
|
+
const parsed = new Date(dateStr);
|
|
378
|
+
if (Number.isNaN(parsed.getTime())) return dateStr;
|
|
379
|
+
return `${parsed.getUTCDate()}/${parsed.getUTCMonth() + 1}`;
|
|
380
|
+
}
|
|
381
|
+
function toDateKeyUtcFromDate(d) {
|
|
382
|
+
const y = d.getUTCFullYear();
|
|
383
|
+
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
384
|
+
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
385
|
+
return `${y}-${m}-${day}`;
|
|
386
|
+
}
|
|
387
|
+
function lastNWeekStartKeysUtc(n) {
|
|
388
|
+
const now = /* @__PURE__ */ new Date();
|
|
389
|
+
const todayUtc = new Date(
|
|
390
|
+
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
|
391
|
+
);
|
|
392
|
+
const dow = todayUtc.getUTCDay();
|
|
393
|
+
const currentWeekStart = new Date(todayUtc);
|
|
394
|
+
currentWeekStart.setUTCDate(currentWeekStart.getUTCDate() - dow);
|
|
395
|
+
const keys = [];
|
|
396
|
+
for (let i = n - 1; i >= 0; i -= 1) {
|
|
397
|
+
const d = new Date(currentWeekStart);
|
|
398
|
+
d.setUTCDate(d.getUTCDate() - 7 * i);
|
|
399
|
+
keys.push(toDateKeyUtcFromDate(d));
|
|
400
|
+
}
|
|
401
|
+
return keys;
|
|
402
|
+
}
|
|
315
403
|
function formatMonthBucketLabel(ymKey) {
|
|
316
404
|
const [ys, ms] = ymKey.split("-");
|
|
317
405
|
const y = Number(ys);
|
|
@@ -319,27 +407,9 @@ function formatMonthBucketLabel(ymKey) {
|
|
|
319
407
|
if (!Number.isFinite(y) || !Number.isFinite(m)) return ymKey;
|
|
320
408
|
return new Intl.DateTimeFormat("en-IN", {
|
|
321
409
|
month: "short",
|
|
322
|
-
year: "numeric",
|
|
323
410
|
timeZone: "UTC"
|
|
324
411
|
}).format(new Date(Date.UTC(y, m - 1, 1)));
|
|
325
412
|
}
|
|
326
|
-
function aggregateDailyOrdersForBreakdown(rows, keyFn, labelFn) {
|
|
327
|
-
const map = /* @__PURE__ */ new Map();
|
|
328
|
-
for (const d of rows) {
|
|
329
|
-
const key = keyFn(d);
|
|
330
|
-
const cur = map.get(key) ?? { orders: 0, revenue: 0, bucketRows: [] };
|
|
331
|
-
cur.orders += d.orders_count;
|
|
332
|
-
cur.revenue += d.total_revenue;
|
|
333
|
-
cur.bucketRows.push(d);
|
|
334
|
-
map.set(key, cur);
|
|
335
|
-
}
|
|
336
|
-
return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([key, v]) => ({
|
|
337
|
-
key,
|
|
338
|
-
label: labelFn(key, v.bucketRows),
|
|
339
|
-
orders_count: v.orders,
|
|
340
|
-
revenue: v.revenue
|
|
341
|
-
}));
|
|
342
|
-
}
|
|
343
413
|
function isSalesBreakdownBarRow(value) {
|
|
344
414
|
if (typeof value !== "object" || value === null) return false;
|
|
345
415
|
const v = value;
|
|
@@ -350,13 +420,14 @@ function SalesBreakdownTooltip({
|
|
|
350
420
|
payload
|
|
351
421
|
}) {
|
|
352
422
|
var _a, _b;
|
|
423
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
353
424
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
354
425
|
const row = isSalesBreakdownBarRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
355
426
|
if (!row) return null;
|
|
356
427
|
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { variant: "compact", title: row.label, children: [
|
|
357
428
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
358
429
|
"Revenue: ",
|
|
359
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency
|
|
430
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(row.revenue) })
|
|
360
431
|
] }),
|
|
361
432
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
362
433
|
"Orders:",
|
|
@@ -399,52 +470,223 @@ function OutcomesTrendTooltip({
|
|
|
399
470
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(entry.value) || 0).toLocaleString() })
|
|
400
471
|
] }, String(entry.name))) });
|
|
401
472
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
473
|
+
const TREND_MIX_PIE_COLORS = {
|
|
474
|
+
revenue: "#c084fc",
|
|
475
|
+
orders: "#7dd3fc",
|
|
476
|
+
aov: "#fdba74"
|
|
477
|
+
};
|
|
478
|
+
function buildTrendMixPieSlices(dailyOrders, trendRevenueTotal, trendOrdersTotal, formatCompactCurrencyFn, formatCurrencyFn) {
|
|
479
|
+
const ncRev = dailyOrders.reduce((s, d) => s + nonCancelledRevenue(d), 0);
|
|
480
|
+
const ncOrd = dailyOrders.reduce(
|
|
481
|
+
(s, d) => s + Math.max(0, d.orders_count - d.cancelled_count),
|
|
482
|
+
0
|
|
483
|
+
);
|
|
484
|
+
const windowAov = ncOrd > 0 ? ncRev / ncOrd : 0;
|
|
485
|
+
const lr = Math.log1p(Math.max(trendRevenueTotal, 0));
|
|
486
|
+
const lo = Math.log1p(Math.max(trendOrdersTotal, 0));
|
|
487
|
+
const la = Math.log1p(Math.max(windowAov, 0));
|
|
488
|
+
const sumW = lr + lo + la;
|
|
489
|
+
const share = (w) => sumW > 0 ? w / sumW * 100 : 100 / 3;
|
|
490
|
+
const sR = share(lr);
|
|
491
|
+
const sO = share(lo);
|
|
492
|
+
const sA = share(la);
|
|
493
|
+
return [
|
|
494
|
+
{
|
|
495
|
+
key: "revenue",
|
|
496
|
+
name: "Revenue",
|
|
497
|
+
value: sR,
|
|
498
|
+
fill: TREND_MIX_PIE_COLORS.revenue,
|
|
499
|
+
sharePercent: sR,
|
|
500
|
+
formattedPrimary: formatCompactCurrencyFn(trendRevenueTotal),
|
|
501
|
+
formattedDetail: "Total revenue for this range, all days summed."
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
key: "orders",
|
|
505
|
+
name: "Orders",
|
|
506
|
+
value: sO,
|
|
507
|
+
fill: TREND_MIX_PIE_COLORS.orders,
|
|
508
|
+
sharePercent: sO,
|
|
509
|
+
formattedPrimary: Math.floor(trendOrdersTotal).toLocaleString(),
|
|
510
|
+
formattedDetail: "Order count in this range from daily totals."
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
key: "aov",
|
|
514
|
+
name: "AOV",
|
|
515
|
+
value: sA,
|
|
516
|
+
fill: TREND_MIX_PIE_COLORS.aov,
|
|
517
|
+
sharePercent: sA,
|
|
518
|
+
formattedPrimary: formatCurrencyFn(windowAov),
|
|
519
|
+
formattedDetail: "Average per non-cancelled order for the window."
|
|
520
|
+
}
|
|
521
|
+
];
|
|
406
522
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
523
|
+
const TREND_MIX_LEADER_STROKE = "#b8956a";
|
|
524
|
+
function isTrendMixPayload(value) {
|
|
525
|
+
if (typeof value !== "object" || value === null) return false;
|
|
526
|
+
const k = value.key;
|
|
527
|
+
return k === "revenue" || k === "orders" || k === "aov";
|
|
528
|
+
}
|
|
529
|
+
function splitDetailTwoLines(s, firstLineMax) {
|
|
530
|
+
const t = s.trim();
|
|
531
|
+
if (t.length <= firstLineMax) return [t, ""];
|
|
532
|
+
const sliceEnd = t.lastIndexOf(" ", firstLineMax);
|
|
533
|
+
const cut = sliceEnd > firstLineMax * 0.45 ? sliceEnd : firstLineMax;
|
|
534
|
+
const a = t.slice(0, cut).trim();
|
|
535
|
+
const b = t.slice(cut).trim();
|
|
536
|
+
return b ? [a, b] : [a, ""];
|
|
537
|
+
}
|
|
538
|
+
function TrendMixExplodedSector(props) {
|
|
539
|
+
const {
|
|
540
|
+
cx,
|
|
541
|
+
cy,
|
|
542
|
+
innerRadius,
|
|
543
|
+
outerRadius,
|
|
544
|
+
startAngle,
|
|
545
|
+
endAngle,
|
|
546
|
+
fill,
|
|
547
|
+
stroke,
|
|
548
|
+
strokeWidth,
|
|
549
|
+
cornerRadius
|
|
550
|
+
} = props;
|
|
551
|
+
const midDeg = (Number(startAngle) + Number(endAngle)) / 2;
|
|
552
|
+
const rad = -midDeg * Math.PI / 180;
|
|
553
|
+
const offset = 19;
|
|
554
|
+
const ncx = (Number(cx) || 0) + offset * Math.cos(rad);
|
|
555
|
+
const ncy = (Number(cy) || 0) + offset * Math.sin(rad);
|
|
556
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
+
recharts.Sector,
|
|
558
|
+
{
|
|
559
|
+
cx: ncx,
|
|
560
|
+
cy: ncy,
|
|
561
|
+
innerRadius,
|
|
562
|
+
outerRadius,
|
|
563
|
+
startAngle,
|
|
564
|
+
endAngle,
|
|
565
|
+
fill,
|
|
566
|
+
stroke,
|
|
567
|
+
strokeWidth,
|
|
568
|
+
cornerRadius,
|
|
569
|
+
style: {
|
|
570
|
+
filter: "drop-shadow(0 5px 6px rgba(0,0,0,0.5)) drop-shadow(0 2px 2px rgba(0,0,0,0.35))"
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
function createTrendMixInfographicLabel(slices) {
|
|
576
|
+
return function TrendMixInfographicLabel(props) {
|
|
577
|
+
const { cx, cy, midAngle, outerRadius, percent, payload, index } = props;
|
|
578
|
+
const slice = typeof index === "number" && slices[index] !== void 0 ? slices[index] : isTrendMixPayload(payload) ? payload : null;
|
|
579
|
+
if (slice === null || cx === void 0 || cy === void 0 || midAngle === void 0) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
const p = percent ?? 0;
|
|
583
|
+
const ang = midAngle;
|
|
584
|
+
const RAD = Math.PI / 180;
|
|
585
|
+
const cos = Math.cos(-RAD * ang);
|
|
586
|
+
const sin = Math.sin(-RAD * ang);
|
|
587
|
+
const or = Number(outerRadius);
|
|
588
|
+
if (!Number.isFinite(or)) return null;
|
|
589
|
+
const rimPad = 24;
|
|
590
|
+
const sx = Number(cx) + (or + rimPad) * cos;
|
|
591
|
+
const sy = Number(cy) + (or + rimPad) * sin;
|
|
592
|
+
const elbow = 30;
|
|
593
|
+
const mx = sx + elbow * cos;
|
|
594
|
+
const my = sy;
|
|
595
|
+
const extend = 58;
|
|
596
|
+
const rightSide = cos >= 0;
|
|
597
|
+
const hx = mx + (rightSide ? extend : -58);
|
|
598
|
+
const anchor = rightSide ? "start" : "end";
|
|
599
|
+
const tx = hx + (rightSide ? 4 : -4);
|
|
600
|
+
const yRule = my + 14;
|
|
601
|
+
const pctStr = `${(p * 100).toFixed(0)}%`;
|
|
602
|
+
const ruleHalfW = 52;
|
|
603
|
+
const xRuleA = rightSide ? tx - 2 : tx - ruleHalfW;
|
|
604
|
+
const xRuleB = rightSide ? tx + ruleHalfW : tx + 2;
|
|
605
|
+
const [detail1, detail2] = splitDetailTwoLines(slice.formattedDetail, 46);
|
|
606
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("g", { "aria-hidden": true, children: [
|
|
607
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
608
|
+
"polyline",
|
|
609
|
+
{
|
|
610
|
+
points: `${sx},${sy} ${mx},${my} ${hx},${yRule - 18}`,
|
|
611
|
+
fill: "none",
|
|
612
|
+
stroke: TREND_MIX_LEADER_STROKE,
|
|
613
|
+
strokeWidth: 1.15,
|
|
614
|
+
strokeLinecap: "round",
|
|
615
|
+
strokeLinejoin: "round"
|
|
616
|
+
}
|
|
617
|
+
),
|
|
618
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
619
|
+
"circle",
|
|
620
|
+
{
|
|
621
|
+
cx: sx,
|
|
622
|
+
cy: sy,
|
|
623
|
+
r: 3.5,
|
|
624
|
+
fill: "none",
|
|
625
|
+
stroke: TREND_MIX_LEADER_STROKE,
|
|
626
|
+
strokeWidth: 1.15
|
|
627
|
+
}
|
|
628
|
+
),
|
|
629
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
630
|
+
"line",
|
|
631
|
+
{
|
|
632
|
+
x1: xRuleA,
|
|
633
|
+
y1: yRule,
|
|
634
|
+
x2: xRuleB,
|
|
635
|
+
y2: yRule,
|
|
636
|
+
stroke: TREND_MIX_LEADER_STROKE,
|
|
637
|
+
strokeWidth: 1.15,
|
|
638
|
+
strokeLinecap: "round"
|
|
639
|
+
}
|
|
640
|
+
),
|
|
641
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
642
|
+
"text",
|
|
643
|
+
{
|
|
644
|
+
x: tx,
|
|
645
|
+
y: yRule - 8,
|
|
646
|
+
textAnchor: anchor,
|
|
647
|
+
fill: "#f8fafc",
|
|
648
|
+
className: "text-[13px] font-bold tabular-nums",
|
|
649
|
+
children: pctStr
|
|
650
|
+
}
|
|
651
|
+
),
|
|
652
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
653
|
+
"text",
|
|
654
|
+
{
|
|
655
|
+
x: tx,
|
|
656
|
+
y: yRule + 16,
|
|
657
|
+
textAnchor: anchor,
|
|
658
|
+
fill: "#f1f5f9",
|
|
659
|
+
className: "text-[9px] font-semibold uppercase tracking-[0.16em]",
|
|
660
|
+
children: slice.name
|
|
661
|
+
}
|
|
662
|
+
),
|
|
663
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
664
|
+
"text",
|
|
665
|
+
{
|
|
666
|
+
x: tx,
|
|
667
|
+
y: yRule + 32,
|
|
668
|
+
textAnchor: anchor,
|
|
669
|
+
fill: "#f8fafc",
|
|
670
|
+
className: "text-[11px] font-bold tabular-nums",
|
|
671
|
+
children: slice.formattedPrimary
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
675
|
+
"text",
|
|
676
|
+
{
|
|
677
|
+
x: tx,
|
|
678
|
+
y: yRule + 46,
|
|
679
|
+
textAnchor: anchor,
|
|
680
|
+
fill: "#94a3b8",
|
|
681
|
+
className: "text-[8px] leading-snug",
|
|
682
|
+
children: [
|
|
683
|
+
/* @__PURE__ */ jsxRuntime.jsx("tspan", { x: tx, dy: 0, children: detail1 }),
|
|
684
|
+
detail2 ? /* @__PURE__ */ jsxRuntime.jsx("tspan", { x: tx, dy: 11, children: detail2 }) : null
|
|
685
|
+
]
|
|
686
|
+
}
|
|
687
|
+
)
|
|
688
|
+
] });
|
|
689
|
+
};
|
|
448
690
|
}
|
|
449
691
|
const KPI_ICON_BG$2 = {
|
|
450
692
|
green: "bg-emerald-500/20",
|
|
@@ -518,7 +760,8 @@ function EmptyAnalyticsPanel$1({
|
|
|
518
760
|
] });
|
|
519
761
|
}
|
|
520
762
|
function OrdersDashboard() {
|
|
521
|
-
var _a, _b;
|
|
763
|
+
var _a, _b, _c, _d, _e;
|
|
764
|
+
const { formatCurrency, formatCompactCurrency } = useAnalyticsCurrency();
|
|
522
765
|
const [data, setData] = react.useState(null);
|
|
523
766
|
const [loading, setLoading] = react.useState(true);
|
|
524
767
|
const [error, setError] = react.useState(null);
|
|
@@ -528,6 +771,7 @@ function OrdersDashboard() {
|
|
|
528
771
|
const [overTimePeriod, setOverTimePeriod] = react.useState("one_week");
|
|
529
772
|
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
530
773
|
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
774
|
+
const [overTimeOrdersMeta, setOverTimeOrdersMeta] = react.useState(null);
|
|
531
775
|
const [ordersInsights, setOrdersInsights] = react.useState(null);
|
|
532
776
|
const [insightsLoading, setInsightsLoading] = react.useState(true);
|
|
533
777
|
const [insightsError, setInsightsError] = react.useState(null);
|
|
@@ -535,6 +779,81 @@ function OrdersDashboard() {
|
|
|
535
779
|
const [todayContextLoading, setTodayContextLoading] = react.useState(true);
|
|
536
780
|
const [todayContextError, setTodayContextError] = react.useState(null);
|
|
537
781
|
const [salesGranularity, setSalesGranularity] = react.useState("day");
|
|
782
|
+
const [salesBreakdownDaily, setSalesBreakdownDaily] = react.useState(
|
|
783
|
+
[]
|
|
784
|
+
);
|
|
785
|
+
const [salesBreakdownOtLoading, setSalesBreakdownOtLoading] = react.useState(true);
|
|
786
|
+
const [salesBreakdownOtError, setSalesBreakdownOtError] = react.useState(null);
|
|
787
|
+
const [salesBreakdownHourlyRolling, setSalesBreakdownHourlyRolling] = react.useState([]);
|
|
788
|
+
const [salesBreakdownHourlyLoading, setSalesBreakdownHourlyLoading] = react.useState(false);
|
|
789
|
+
const [salesBreakdownHourlyError, setSalesBreakdownHourlyError] = react.useState(null);
|
|
790
|
+
const salesBreakdownOtPeriod = react.useMemo(
|
|
791
|
+
() => getSalesBreakdownOtPeriod(salesGranularity),
|
|
792
|
+
[salesGranularity]
|
|
793
|
+
);
|
|
794
|
+
react.useEffect(() => {
|
|
795
|
+
if (salesBreakdownOtPeriod === null) {
|
|
796
|
+
setSalesBreakdownDaily([]);
|
|
797
|
+
setSalesBreakdownOtLoading(false);
|
|
798
|
+
setSalesBreakdownOtError(null);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
let cancelled = false;
|
|
802
|
+
setSalesBreakdownOtLoading(true);
|
|
803
|
+
setSalesBreakdownOtError(null);
|
|
804
|
+
fetch(
|
|
805
|
+
`/admin/analytics/orders-over-time?period=${salesBreakdownOtPeriod}`,
|
|
806
|
+
{ credentials: "include" }
|
|
807
|
+
).then((res) => {
|
|
808
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
809
|
+
return res.json();
|
|
810
|
+
}).then((body) => {
|
|
811
|
+
if (!cancelled) {
|
|
812
|
+
setSalesBreakdownDaily(body.dailyOrders ?? []);
|
|
813
|
+
}
|
|
814
|
+
}).catch((e) => {
|
|
815
|
+
if (!cancelled) {
|
|
816
|
+
setSalesBreakdownOtError(e instanceof Error ? e.message : String(e));
|
|
817
|
+
}
|
|
818
|
+
}).finally(() => {
|
|
819
|
+
if (!cancelled) setSalesBreakdownOtLoading(false);
|
|
820
|
+
});
|
|
821
|
+
return () => {
|
|
822
|
+
cancelled = true;
|
|
823
|
+
};
|
|
824
|
+
}, [salesBreakdownOtPeriod]);
|
|
825
|
+
react.useEffect(() => {
|
|
826
|
+
if (salesGranularity !== "hour") {
|
|
827
|
+
setSalesBreakdownHourlyRolling([]);
|
|
828
|
+
setSalesBreakdownHourlyLoading(false);
|
|
829
|
+
setSalesBreakdownHourlyError(null);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
let cancelled = false;
|
|
833
|
+
setSalesBreakdownHourlyLoading(true);
|
|
834
|
+
setSalesBreakdownHourlyError(null);
|
|
835
|
+
fetch("/admin/analytics/orders-insights?last_hours=24", {
|
|
836
|
+
credentials: "include"
|
|
837
|
+
}).then((res) => {
|
|
838
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
839
|
+
return res.json();
|
|
840
|
+
}).then((body) => {
|
|
841
|
+
if (!cancelled) {
|
|
842
|
+
setSalesBreakdownHourlyRolling(body.hourly_rolling ?? []);
|
|
843
|
+
}
|
|
844
|
+
}).catch((e) => {
|
|
845
|
+
if (!cancelled) {
|
|
846
|
+
setSalesBreakdownHourlyError(
|
|
847
|
+
e instanceof Error ? e.message : String(e)
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
}).finally(() => {
|
|
851
|
+
if (!cancelled) setSalesBreakdownHourlyLoading(false);
|
|
852
|
+
});
|
|
853
|
+
return () => {
|
|
854
|
+
cancelled = true;
|
|
855
|
+
};
|
|
856
|
+
}, [salesGranularity]);
|
|
538
857
|
react.useEffect(() => {
|
|
539
858
|
let cancelled = false;
|
|
540
859
|
setLoading(true);
|
|
@@ -566,9 +885,13 @@ function OrdersDashboard() {
|
|
|
566
885
|
if (!cancelled) {
|
|
567
886
|
setDailyOrders(body.dailyOrders ?? []);
|
|
568
887
|
setPreviousPeriodDailyOrders(body.previousPeriodDailyOrders ?? []);
|
|
888
|
+
setOverTimeOrdersMeta(body.ordersAnalyticsMeta ?? null);
|
|
569
889
|
}
|
|
570
890
|
}).catch((e) => {
|
|
571
|
-
if (!cancelled)
|
|
891
|
+
if (!cancelled) {
|
|
892
|
+
setOverTimeError(e instanceof Error ? e.message : String(e));
|
|
893
|
+
setOverTimeOrdersMeta(null);
|
|
894
|
+
}
|
|
572
895
|
}).finally(() => {
|
|
573
896
|
if (!cancelled) setOverTimeLoading(false);
|
|
574
897
|
});
|
|
@@ -641,7 +964,7 @@ function OrdersDashboard() {
|
|
|
641
964
|
const primaryStats = data ? [
|
|
642
965
|
{
|
|
643
966
|
label: "Total revenue",
|
|
644
|
-
value: formatCurrency
|
|
967
|
+
value: formatCurrency(data.totalRevenue),
|
|
645
968
|
helper: "Non-cancelled orders",
|
|
646
969
|
accent: "green"
|
|
647
970
|
},
|
|
@@ -659,7 +982,7 @@ function OrdersDashboard() {
|
|
|
659
982
|
},
|
|
660
983
|
{
|
|
661
984
|
label: "Average order value",
|
|
662
|
-
value: formatCurrency
|
|
985
|
+
value: formatCurrency(data.aov),
|
|
663
986
|
helper: "Per non-cancelled order",
|
|
664
987
|
accent: "amber"
|
|
665
988
|
}
|
|
@@ -709,80 +1032,112 @@ function OrdersDashboard() {
|
|
|
709
1032
|
);
|
|
710
1033
|
const revenueVsPreviousPercent = previousPeriodRevenueTotal > 0 ? (trendRevenueTotal - previousPeriodRevenueTotal) / previousPeriodRevenueTotal * 100 : null;
|
|
711
1034
|
const ordersVsPreviousPercent = previousPeriodOrdersTotal > 0 ? (trendOrdersTotal - previousPeriodOrdersTotal) / previousPeriodOrdersTotal * 100 : null;
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1035
|
+
const trendMixPieSlices = react.useMemo(
|
|
1036
|
+
() => buildTrendMixPieSlices(
|
|
1037
|
+
dailyOrders,
|
|
1038
|
+
trendRevenueTotal,
|
|
1039
|
+
trendOrdersTotal,
|
|
1040
|
+
formatCompactCurrency,
|
|
1041
|
+
formatCurrency
|
|
1042
|
+
),
|
|
1043
|
+
[
|
|
1044
|
+
dailyOrders,
|
|
1045
|
+
trendRevenueTotal,
|
|
1046
|
+
trendOrdersTotal,
|
|
1047
|
+
formatCompactCurrency,
|
|
1048
|
+
formatCurrency
|
|
1049
|
+
]
|
|
1050
|
+
);
|
|
1051
|
+
const trendMixPieLabelRenderer = react.useMemo(
|
|
1052
|
+
() => createTrendMixInfographicLabel(trendMixPieSlices),
|
|
1053
|
+
[trendMixPieSlices]
|
|
1054
|
+
);
|
|
1055
|
+
const trendMixRangeFootnote = react.useMemo(() => {
|
|
1056
|
+
const ncRev = dailyOrders.reduce((s, d) => s + nonCancelledRevenue(d), 0);
|
|
1057
|
+
const ncOrd = dailyOrders.reduce(
|
|
1058
|
+
(s, d) => s + Math.max(0, d.orders_count - d.cancelled_count),
|
|
1059
|
+
0
|
|
1060
|
+
);
|
|
1061
|
+
const windowAov = ncOrd > 0 ? ncRev / ncOrd : 0;
|
|
1062
|
+
return {
|
|
1063
|
+
revenue: formatCompactCurrency(trendRevenueTotal),
|
|
1064
|
+
orders: Math.floor(trendOrdersTotal).toLocaleString(),
|
|
1065
|
+
aov: formatCurrency(windowAov)
|
|
1066
|
+
};
|
|
1067
|
+
}, [
|
|
1068
|
+
dailyOrders,
|
|
1069
|
+
trendRevenueTotal,
|
|
1070
|
+
trendOrdersTotal,
|
|
1071
|
+
formatCompactCurrency,
|
|
1072
|
+
formatCurrency
|
|
1073
|
+
]);
|
|
733
1074
|
const salesBreakdownChartRows = react.useMemo(() => {
|
|
734
1075
|
if (salesGranularity === "hour") {
|
|
735
|
-
return
|
|
736
|
-
key: `
|
|
737
|
-
label:
|
|
738
|
-
orders_count:
|
|
739
|
-
revenue:
|
|
1076
|
+
return salesBreakdownHourlyRolling.map((b, i) => ({
|
|
1077
|
+
key: `rolling-${i}`,
|
|
1078
|
+
label: b.label,
|
|
1079
|
+
orders_count: b.orders_count,
|
|
1080
|
+
revenue: b.revenue
|
|
740
1081
|
}));
|
|
741
1082
|
}
|
|
742
1083
|
if (salesGranularity === "week") {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1084
|
+
const targetKeys = lastNWeekStartKeysUtc(7);
|
|
1085
|
+
const agg = /* @__PURE__ */ new Map();
|
|
1086
|
+
for (const d of salesBreakdownDaily) {
|
|
1087
|
+
const wk = utcSundayWeekStartKeyFromDateStr(d.date);
|
|
1088
|
+
const cur = agg.get(wk) ?? { orders: 0, revenue: 0 };
|
|
1089
|
+
cur.orders += d.orders_count;
|
|
1090
|
+
cur.revenue += d.total_revenue;
|
|
1091
|
+
agg.set(wk, cur);
|
|
1092
|
+
}
|
|
1093
|
+
return targetKeys.map((key) => {
|
|
1094
|
+
var _a2, _b2;
|
|
1095
|
+
return {
|
|
1096
|
+
key,
|
|
1097
|
+
label: formatWeekStartDm(key),
|
|
1098
|
+
orders_count: ((_a2 = agg.get(key)) == null ? void 0 : _a2.orders) ?? 0,
|
|
1099
|
+
revenue: ((_b2 = agg.get(key)) == null ? void 0 : _b2.revenue) ?? 0
|
|
1100
|
+
};
|
|
1101
|
+
});
|
|
750
1102
|
}
|
|
751
1103
|
if (salesGranularity === "month") {
|
|
752
|
-
return
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1104
|
+
return salesBreakdownDaily.map((d) => ({
|
|
1105
|
+
key: d.date,
|
|
1106
|
+
label: d.label ?? formatMonthBucketLabel(monthKeyFromDateStr(d.date)),
|
|
1107
|
+
orders_count: d.orders_count,
|
|
1108
|
+
revenue: d.total_revenue
|
|
1109
|
+
}));
|
|
757
1110
|
}
|
|
758
|
-
return
|
|
1111
|
+
return salesBreakdownDaily.map((d) => ({
|
|
759
1112
|
key: d.date,
|
|
760
|
-
label:
|
|
1113
|
+
label: formatDayBreakdownDm(d.date),
|
|
761
1114
|
orders_count: d.orders_count,
|
|
762
1115
|
revenue: d.total_revenue
|
|
763
1116
|
}));
|
|
764
|
-
}, [
|
|
1117
|
+
}, [
|
|
1118
|
+
salesGranularity,
|
|
1119
|
+
salesBreakdownDaily,
|
|
1120
|
+
salesBreakdownHourlyRolling
|
|
1121
|
+
]);
|
|
765
1122
|
const salesBreakdownDescription = react.useMemo(() => {
|
|
766
|
-
var _a2;
|
|
767
|
-
const rangeLabel = ((_a2 = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _a2.label) ?? "selected range";
|
|
768
|
-
const insightsHint = `Order-time breakdown uses last ${insightsWindowDays} days (UTC).`;
|
|
769
1123
|
switch (salesGranularity) {
|
|
770
|
-
case "day":
|
|
771
|
-
return `Revenue by calendar day for ${rangeLabel} (aligned with Revenue & orders).`;
|
|
772
1124
|
case "hour":
|
|
773
|
-
return
|
|
1125
|
+
return "Last 24 hours from now (UTC): one bar per clock hour in the rolling window.";
|
|
1126
|
+
case "day":
|
|
1127
|
+
return "Last 7 calendar days (UTC), one bar per day — axis day/month.";
|
|
774
1128
|
case "week":
|
|
775
|
-
return
|
|
1129
|
+
return "Last 7 weeks (UTC, weeks start Sunday); axis shows each week’s start as day/month.";
|
|
776
1130
|
case "month":
|
|
777
|
-
return
|
|
1131
|
+
return "Last 12 calendar months through the current month (UTC) — Jan, Feb, Mar, …";
|
|
778
1132
|
default:
|
|
779
1133
|
return "";
|
|
780
1134
|
}
|
|
781
|
-
}, [salesGranularity
|
|
1135
|
+
}, [salesGranularity]);
|
|
782
1136
|
const salesBreakdownXAxisInterval = react.useMemo(() => {
|
|
783
1137
|
const n = salesBreakdownChartRows.length;
|
|
784
1138
|
if (salesGranularity === "hour") return 3;
|
|
785
|
-
if (salesGranularity === "day" && n > 24)
|
|
1139
|
+
if (salesGranularity === "day" && n > 24)
|
|
1140
|
+
return Math.max(0, Math.ceil(n / 10) - 1);
|
|
786
1141
|
return 0;
|
|
787
1142
|
}, [salesGranularity, salesBreakdownChartRows.length]);
|
|
788
1143
|
const ordersVsDraftsPie = react.useMemo(() => {
|
|
@@ -821,11 +1176,13 @@ function OrdersDashboard() {
|
|
|
821
1176
|
};
|
|
822
1177
|
});
|
|
823
1178
|
}, [dailyOrders]);
|
|
1179
|
+
const selectedTrendPeriodLabel = ((_b = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _b.label) ?? "Selected range";
|
|
1180
|
+
const orderSampleLikelyTruncated = (((_c = data == null ? void 0 : data.ordersAnalyticsMeta) == null ? void 0 : _c.likely_truncated) ?? false) || ((overTimeOrdersMeta == null ? void 0 : overTimeOrdersMeta.likely_truncated) ?? false) || (((_d = ordersInsights == null ? void 0 : ordersInsights.ordersAnalyticsMeta) == null ? void 0 : _d.likely_truncated) ?? false);
|
|
824
1181
|
const quickPulseMetrics = [
|
|
825
1182
|
{
|
|
826
1183
|
label: "Range revenue",
|
|
827
|
-
value: formatCompactCurrency
|
|
828
|
-
helper:
|
|
1184
|
+
value: formatCompactCurrency(trendRevenueTotal),
|
|
1185
|
+
helper: selectedTrendPeriodLabel,
|
|
829
1186
|
accentClassName: "border-emerald-400/25 bg-emerald-500/5"
|
|
830
1187
|
},
|
|
831
1188
|
{
|
|
@@ -842,7 +1199,7 @@ function OrdersDashboard() {
|
|
|
842
1199
|
},
|
|
843
1200
|
{
|
|
844
1201
|
label: "Peak revenue day",
|
|
845
|
-
value: peakRevenuePoint ? formatCompactCurrency
|
|
1202
|
+
value: peakRevenuePoint ? formatCompactCurrency(peakRevenuePoint.total_revenue) : "—",
|
|
846
1203
|
helper: (peakRevenuePoint == null ? void 0 : peakRevenuePoint.label) ?? formatChartLabel$2(peakRevenuePoint == null ? void 0 : peakRevenuePoint.date),
|
|
847
1204
|
accentClassName: "border-amber-400/25 bg-amber-500/5"
|
|
848
1205
|
}
|
|
@@ -861,20 +1218,14 @@ function OrdersDashboard() {
|
|
|
861
1218
|
if (error || !data) {
|
|
862
1219
|
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: error ?? "Failed to load analytics" }) });
|
|
863
1220
|
}
|
|
864
|
-
const
|
|
865
|
-
const
|
|
1221
|
+
const salesBreakdownWaiting = salesGranularity === "hour" ? salesBreakdownHourlyLoading : salesBreakdownOtLoading;
|
|
1222
|
+
const salesBreakdownFetchError = salesGranularity === "hour" ? salesBreakdownHourlyError : salesBreakdownOtError;
|
|
866
1223
|
const salesBreakdownBody = (() => {
|
|
867
|
-
if (
|
|
868
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" }) }) });
|
|
869
|
-
}
|
|
870
|
-
if (needsSalesOverTime && overTimeError) {
|
|
871
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: overTimeError }) }) });
|
|
872
|
-
}
|
|
873
|
-
if (needsSalesInsights && insightsLoading) {
|
|
1224
|
+
if (salesBreakdownWaiting) {
|
|
874
1225
|
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" }) }) });
|
|
875
1226
|
}
|
|
876
|
-
if (
|
|
877
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children:
|
|
1227
|
+
if (salesBreakdownFetchError) {
|
|
1228
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: salesBreakdownFetchError }) }) });
|
|
878
1229
|
}
|
|
879
1230
|
if (salesBreakdownChartRows.length === 0) {
|
|
880
1231
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -916,7 +1267,7 @@ function OrdersDashboard() {
|
|
|
916
1267
|
tick: CHART_AXIS_TICK_SM$1,
|
|
917
1268
|
tickLine: false,
|
|
918
1269
|
axisLine: false,
|
|
919
|
-
tickFormatter: (v) => formatCompactCurrency
|
|
1270
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
920
1271
|
}
|
|
921
1272
|
),
|
|
922
1273
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -940,6 +1291,19 @@ function OrdersDashboard() {
|
|
|
940
1291
|
) }) }) });
|
|
941
1292
|
})();
|
|
942
1293
|
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsDashboardShell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-hidden rounded-2xl border border-ui-border-base/80 bg-ui-bg-base shadow-sm", children: [
|
|
1294
|
+
orderSampleLikelyTruncated ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1295
|
+
"div",
|
|
1296
|
+
{
|
|
1297
|
+
className: "border-b border-amber-400/30 bg-amber-500/10 px-4 py-2.5 sm:px-5",
|
|
1298
|
+
role: "status",
|
|
1299
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-base text-xs leading-snug", children: [
|
|
1300
|
+
"Order analytics loaded up to ",
|
|
1301
|
+
ORDERS_ANALYTICS_MAX_FETCH.toLocaleString(),
|
|
1302
|
+
" ",
|
|
1303
|
+
"orders per request. If you have more orders, KPIs and charts may under-count. Plan server-side aggregation or pagination for exact totals at very high volume."
|
|
1304
|
+
] })
|
|
1305
|
+
}
|
|
1306
|
+
) : null,
|
|
943
1307
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
944
1308
|
AnalyticsDashboardHeader,
|
|
945
1309
|
{
|
|
@@ -1028,10 +1392,10 @@ function OrdersDashboard() {
|
|
|
1028
1392
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
1029
1393
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
1030
1394
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window revenue" }),
|
|
1031
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency
|
|
1395
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency(trendRevenueTotal) }),
|
|
1032
1396
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
1033
1397
|
"Avg/day ",
|
|
1034
|
-
formatCompactCurrency
|
|
1398
|
+
formatCompactCurrency(trendAverageRevenue)
|
|
1035
1399
|
] })
|
|
1036
1400
|
] }) }),
|
|
1037
1401
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
@@ -1054,187 +1418,63 @@ function OrdersDashboard() {
|
|
|
1054
1418
|
] }),
|
|
1055
1419
|
!overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1056
1420
|
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1057
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-
|
|
1058
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-fuchsia-500" }),
|
|
1065
|
-
"Revenue"
|
|
1066
|
-
] }),
|
|
1067
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1068
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-sky-400" }),
|
|
1069
|
-
"Orders"
|
|
1070
|
-
] }),
|
|
1071
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1072
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-amber-500" }),
|
|
1073
|
-
"AOV"
|
|
1074
|
-
] }),
|
|
1075
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1076
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1077
|
-
"span",
|
|
1078
|
-
{
|
|
1079
|
-
className: "h-0 w-4 shrink-0 border-t border-dashed border-slate-400",
|
|
1080
|
-
"aria-hidden": true
|
|
1081
|
-
}
|
|
1082
|
-
),
|
|
1083
|
-
"Previous"
|
|
1084
|
-
] })
|
|
1421
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 min-w-0", children: [
|
|
1422
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue, orders & AOV" }),
|
|
1423
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[9px] leading-snug", children: [
|
|
1424
|
+
"Floating wedges with open center · leader labels only (no hover). Window:",
|
|
1425
|
+
" ",
|
|
1426
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-fg-subtle", children: selectedTrendPeriodLabel }),
|
|
1427
|
+
"."
|
|
1085
1428
|
] })
|
|
1086
1429
|
] }),
|
|
1087
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1088
|
-
|
|
1430
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1431
|
+
"div",
|
|
1089
1432
|
{
|
|
1090
|
-
|
|
1091
|
-
|
|
1433
|
+
className: "w-full rounded-2xl bg-[radial-gradient(ellipse_at_50%_42%,rgba(148,163,184,0.14)_0%,transparent_58%)] py-1",
|
|
1434
|
+
role: "img",
|
|
1435
|
+
"aria-label": `Revenue, orders, and AOV for ${selectedTrendPeriodLabel}. Values are shown on leader labels.`,
|
|
1092
1436
|
children: [
|
|
1093
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1094
|
-
recharts.
|
|
1095
|
-
{
|
|
1096
|
-
strokeDasharray: "3 3",
|
|
1097
|
-
vertical: false,
|
|
1098
|
-
stroke: "rgba(148,163,184,0.12)"
|
|
1099
|
-
}
|
|
1100
|
-
),
|
|
1101
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1102
|
-
recharts.XAxis,
|
|
1103
|
-
{
|
|
1104
|
-
dataKey: "date",
|
|
1105
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1106
|
-
tickLine: false,
|
|
1107
|
-
axisLine: false,
|
|
1108
|
-
tickFormatter: (value, index) => {
|
|
1109
|
-
const row = trendRowsDetailed[index];
|
|
1110
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel$2(String(value));
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
),
|
|
1114
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1115
|
-
recharts.YAxis,
|
|
1116
|
-
{
|
|
1117
|
-
yAxisId: "orders",
|
|
1118
|
-
width: 30,
|
|
1119
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1120
|
-
tickLine: false,
|
|
1121
|
-
axisLine: false,
|
|
1122
|
-
allowDecimals: false,
|
|
1123
|
-
tickFormatter: (v) => Math.floor(Number(v) || 0).toLocaleString()
|
|
1124
|
-
}
|
|
1125
|
-
),
|
|
1126
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1127
|
-
recharts.YAxis,
|
|
1128
|
-
{
|
|
1129
|
-
yAxisId: "revenue",
|
|
1130
|
-
orientation: "right",
|
|
1131
|
-
width: 42,
|
|
1132
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1133
|
-
tickLine: false,
|
|
1134
|
-
axisLine: false,
|
|
1135
|
-
tickFormatter: (v) => formatCompactCurrency$1(Number(v))
|
|
1136
|
-
}
|
|
1137
|
-
),
|
|
1138
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1139
|
-
recharts.YAxis,
|
|
1437
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(340px,48vh)] min-h-[288px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1438
|
+
recharts.PieChart,
|
|
1140
1439
|
{
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
dataKey: "orders_count",
|
|
1166
|
-
name: "Orders",
|
|
1167
|
-
stroke: "#38BDF8",
|
|
1168
|
-
strokeWidth: 2,
|
|
1169
|
-
dot: false
|
|
1170
|
-
}
|
|
1171
|
-
),
|
|
1172
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1173
|
-
recharts.Line,
|
|
1174
|
-
{
|
|
1175
|
-
yAxisId: "orders",
|
|
1176
|
-
type: "natural",
|
|
1177
|
-
dataKey: "prev_orders",
|
|
1178
|
-
name: "Orders (prev)",
|
|
1179
|
-
stroke: "#64748b",
|
|
1180
|
-
strokeWidth: 1.5,
|
|
1181
|
-
strokeDasharray: "5 4",
|
|
1182
|
-
dot: false
|
|
1183
|
-
}
|
|
1184
|
-
),
|
|
1185
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1186
|
-
recharts.Line,
|
|
1187
|
-
{
|
|
1188
|
-
yAxisId: "revenue",
|
|
1189
|
-
type: "natural",
|
|
1190
|
-
dataKey: "total_revenue",
|
|
1191
|
-
name: "Revenue",
|
|
1192
|
-
stroke: "#D946EF",
|
|
1193
|
-
strokeWidth: 2,
|
|
1194
|
-
dot: false
|
|
1195
|
-
}
|
|
1196
|
-
),
|
|
1197
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1198
|
-
recharts.Line,
|
|
1199
|
-
{
|
|
1200
|
-
yAxisId: "revenue",
|
|
1201
|
-
type: "natural",
|
|
1202
|
-
dataKey: "prev_revenue",
|
|
1203
|
-
name: "Revenue (prev)",
|
|
1204
|
-
stroke: "#64748b",
|
|
1205
|
-
strokeWidth: 1.5,
|
|
1206
|
-
strokeDasharray: "5 4",
|
|
1207
|
-
dot: false
|
|
1208
|
-
}
|
|
1209
|
-
),
|
|
1210
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1211
|
-
recharts.Line,
|
|
1212
|
-
{
|
|
1213
|
-
yAxisId: "aov",
|
|
1214
|
-
type: "natural",
|
|
1215
|
-
dataKey: "aov",
|
|
1216
|
-
name: "AOV",
|
|
1217
|
-
stroke: "#D97706",
|
|
1218
|
-
strokeWidth: 2,
|
|
1219
|
-
dot: false
|
|
1220
|
-
}
|
|
1221
|
-
),
|
|
1222
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1223
|
-
recharts.Line,
|
|
1224
|
-
{
|
|
1225
|
-
yAxisId: "aov",
|
|
1226
|
-
type: "natural",
|
|
1227
|
-
dataKey: "prev_aov",
|
|
1228
|
-
name: "AOV (prev)",
|
|
1229
|
-
stroke: "#64748b",
|
|
1230
|
-
strokeWidth: 1.5,
|
|
1231
|
-
strokeDasharray: "5 4",
|
|
1232
|
-
dot: false
|
|
1440
|
+
margin: { top: 20, right: 132, bottom: 28, left: 132 },
|
|
1441
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1442
|
+
recharts.Pie,
|
|
1443
|
+
{
|
|
1444
|
+
data: trendMixPieSlices,
|
|
1445
|
+
dataKey: "value",
|
|
1446
|
+
nameKey: "name",
|
|
1447
|
+
cx: "50%",
|
|
1448
|
+
cy: "50%",
|
|
1449
|
+
startAngle: 90,
|
|
1450
|
+
endAngle: -270,
|
|
1451
|
+
innerRadius: "26%",
|
|
1452
|
+
outerRadius: "46%",
|
|
1453
|
+
paddingAngle: 9,
|
|
1454
|
+
cornerRadius: 8,
|
|
1455
|
+
stroke: "rgba(15,23,42,0.55)",
|
|
1456
|
+
strokeWidth: 2,
|
|
1457
|
+
shape: TrendMixExplodedSector,
|
|
1458
|
+
label: trendMixPieLabelRenderer,
|
|
1459
|
+
labelLine: false,
|
|
1460
|
+
isAnimationActive: false,
|
|
1461
|
+
children: trendMixPieSlices.map((slice) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: slice.fill }, slice.key))
|
|
1462
|
+
}
|
|
1463
|
+
)
|
|
1233
1464
|
}
|
|
1234
|
-
)
|
|
1465
|
+
) }) }),
|
|
1466
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "mt-2 px-1 text-center text-[9px] leading-snug text-ui-fg-muted", children: [
|
|
1467
|
+
"Range snapshot — Revenue ",
|
|
1468
|
+
trendMixRangeFootnote.revenue,
|
|
1469
|
+
" · Orders",
|
|
1470
|
+
" ",
|
|
1471
|
+
trendMixRangeFootnote.orders,
|
|
1472
|
+
" · AOV ",
|
|
1473
|
+
trendMixRangeFootnote.aov
|
|
1474
|
+
] })
|
|
1235
1475
|
]
|
|
1236
1476
|
}
|
|
1237
|
-
)
|
|
1477
|
+
)
|
|
1238
1478
|
] }),
|
|
1239
1479
|
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
|
|
1240
1480
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue breakdown (stacked — by order status)" }),
|
|
@@ -1259,9 +1499,11 @@ function OrdersDashboard() {
|
|
|
1259
1499
|
tick: CHART_AXIS_TICK$2,
|
|
1260
1500
|
tickLine: false,
|
|
1261
1501
|
axisLine: false,
|
|
1262
|
-
tickFormatter: (value
|
|
1263
|
-
const row = dailyOrders
|
|
1264
|
-
|
|
1502
|
+
tickFormatter: (value) => {
|
|
1503
|
+
const row = dailyOrders.find(
|
|
1504
|
+
(d) => d.date === String(value)
|
|
1505
|
+
);
|
|
1506
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel$2(String(value));
|
|
1265
1507
|
}
|
|
1266
1508
|
}
|
|
1267
1509
|
),
|
|
@@ -1269,7 +1511,7 @@ function OrdersDashboard() {
|
|
|
1269
1511
|
recharts.YAxis,
|
|
1270
1512
|
{
|
|
1271
1513
|
tick: CHART_AXIS_TICK$2,
|
|
1272
|
-
tickFormatter: (v) => formatCompactCurrency
|
|
1514
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1273
1515
|
}
|
|
1274
1516
|
),
|
|
1275
1517
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1279,7 +1521,7 @@ function OrdersDashboard() {
|
|
|
1279
1521
|
p.name,
|
|
1280
1522
|
":",
|
|
1281
1523
|
" ",
|
|
1282
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency
|
|
1524
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(p.value) || 0) })
|
|
1283
1525
|
] }, String(p.name))) }) : null
|
|
1284
1526
|
}
|
|
1285
1527
|
),
|
|
@@ -1354,7 +1596,7 @@ function OrdersDashboard() {
|
|
|
1354
1596
|
{
|
|
1355
1597
|
variant: "atlas",
|
|
1356
1598
|
title: "Order → fulfillment funnel",
|
|
1357
|
-
description: `Stages by order created date (UTC), same range as Revenue & orders (${((
|
|
1599
|
+
description: `Stages by order created date (UTC), same range as Revenue & orders (${((_e = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _e.label) ?? "period"}). Not storefront visitors.`,
|
|
1358
1600
|
children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[120px] items-center justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[120px] items-center justify-center px-2 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: overTimeError }) }) }) : funnelTimeSeriesRows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
1359
1601
|
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1360
1602
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Stage counts (time series)" }),
|
|
@@ -1835,29 +2077,32 @@ function OrdersDashboard() {
|
|
|
1835
2077
|
title: "Sales breakdown",
|
|
1836
2078
|
description: salesBreakdownDescription,
|
|
1837
2079
|
actionsBare: true,
|
|
1838
|
-
actions: /* @__PURE__ */ jsxRuntime.
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2080
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-stretch gap-1 sm:flex-row sm:items-center sm:gap-2", children: [
|
|
2081
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2082
|
+
ui.Label,
|
|
2083
|
+
{
|
|
2084
|
+
htmlFor: "sales-breakdown-granularity",
|
|
2085
|
+
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
2086
|
+
children: "Breakdown"
|
|
2087
|
+
}
|
|
2088
|
+
),
|
|
2089
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2090
|
+
"select",
|
|
2091
|
+
{
|
|
2092
|
+
id: "sales-breakdown-granularity",
|
|
2093
|
+
value: salesGranularity,
|
|
2094
|
+
onChange: (e) => {
|
|
2095
|
+
const v = e.target.value;
|
|
2096
|
+
if (v === "hour" || v === "day" || v === "week" || v === "month") {
|
|
2097
|
+
setSalesGranularity(v);
|
|
2098
|
+
}
|
|
2099
|
+
},
|
|
2100
|
+
className: SELECT_CLASS_NAME$2,
|
|
2101
|
+
"aria-label": "Sales breakdown time bucket",
|
|
2102
|
+
children: SALES_GRANULARITY_TABS.map((tab) => /* @__PURE__ */ jsxRuntime.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))
|
|
2103
|
+
}
|
|
2104
|
+
)
|
|
2105
|
+
] }),
|
|
1861
2106
|
children: salesBreakdownBody
|
|
1862
2107
|
}
|
|
1863
2108
|
),
|
|
@@ -2021,7 +2266,7 @@ function formatChartLabel$1(value) {
|
|
|
2021
2266
|
return `${parsed.getUTCMonth() + 1}/${parsed.getUTCDate()}`;
|
|
2022
2267
|
}
|
|
2023
2268
|
function CustomersDashboard() {
|
|
2024
|
-
var _a, _b;
|
|
2269
|
+
var _a, _b, _c;
|
|
2025
2270
|
const [data, setData] = react.useState(null);
|
|
2026
2271
|
const [loading, setLoading] = react.useState(true);
|
|
2027
2272
|
const [error, setError] = react.useState(null);
|
|
@@ -2260,6 +2505,13 @@ function CustomersDashboard() {
|
|
|
2260
2505
|
title: "Repeat purchases",
|
|
2261
2506
|
description: "Orders with a customer_id in the same window (guest checkouts excluded from rate).",
|
|
2262
2507
|
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-1.5", children: repeatLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "flex flex-1 items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : repeatError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "flex flex-1 items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-danger text-xs", children: repeatError }) }) : repeatStats ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
2508
|
+
((_b = repeatStats.ordersAnalyticsMeta) == null ? void 0 : _b.likely_truncated) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-full rounded-md border border-amber-400/25 bg-amber-500/10 px-2 py-1.5", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-base text-[10px] leading-snug", children: [
|
|
2509
|
+
"Repeat stats are computed from a sample of up to",
|
|
2510
|
+
" ",
|
|
2511
|
+
repeatStats.ordersAnalyticsMeta.max_fetch.toLocaleString(),
|
|
2512
|
+
" ",
|
|
2513
|
+
"orders; your true repeat rate may differ if you exceed that sample."
|
|
2514
|
+
] }) }) : null,
|
|
2263
2515
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2264
2516
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Repeat rate" }),
|
|
2265
2517
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: `${repeatStats.repeat_rate_percent.toFixed(1)}%` }),
|
|
@@ -2324,7 +2576,7 @@ function CustomersDashboard() {
|
|
|
2324
2576
|
] }) }),
|
|
2325
2577
|
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2326
2578
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Chart period" }),
|
|
2327
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: ((
|
|
2579
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: ((_c = GRAPH_PERIODS$1.find((p) => p.value === graphPeriod)) == null ? void 0 : _c.label) ?? graphPeriod }),
|
|
2328
2580
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: "Independent of summary period filter" })
|
|
2329
2581
|
] }) })
|
|
2330
2582
|
] }),
|
|
@@ -2358,12 +2610,12 @@ function CustomersDashboard() {
|
|
|
2358
2610
|
stroke: CHART_AXIS_LINE$1,
|
|
2359
2611
|
tickLine: false,
|
|
2360
2612
|
axisLine: { stroke: CHART_AXIS_LINE$1 },
|
|
2361
|
-
tickFormatter: (
|
|
2362
|
-
|
|
2363
|
-
const row = series[index];
|
|
2613
|
+
tickFormatter: (value) => {
|
|
2614
|
+
const row = series.find((p) => p.date === String(value));
|
|
2364
2615
|
if (row == null ? void 0 : row.label) return row.label;
|
|
2365
|
-
|
|
2366
|
-
|
|
2616
|
+
return formatChartLabel$1(
|
|
2617
|
+
(row == null ? void 0 : row.date) ?? (typeof value === "string" ? value : String(value))
|
|
2618
|
+
);
|
|
2367
2619
|
}
|
|
2368
2620
|
}
|
|
2369
2621
|
),
|
|
@@ -2418,6 +2670,16 @@ function CustomersDashboard() {
|
|
|
2418
2670
|
] })
|
|
2419
2671
|
] }) });
|
|
2420
2672
|
}
|
|
2673
|
+
function buildAnalyticsUrl(path, params) {
|
|
2674
|
+
const search = new URLSearchParams();
|
|
2675
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2676
|
+
if (value !== void 0 && value !== null) {
|
|
2677
|
+
search.set(key, String(value));
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
const query = search.toString();
|
|
2681
|
+
return query ? `${path}?${query}` : path;
|
|
2682
|
+
}
|
|
2421
2683
|
const SUMMARY_PERIODS = [
|
|
2422
2684
|
{ value: "all", label: "All time" },
|
|
2423
2685
|
{ value: "0", label: "Today" },
|
|
@@ -2462,22 +2724,6 @@ const SELECT_CLASS_NAME = "h-9 w-full min-w-[8rem] max-w-full cursor-pointer app
|
|
|
2462
2724
|
const LEADERBOARD_TABLE_CLASS = "min-w-[32rem] sm:min-w-[36rem] max-w-none";
|
|
2463
2725
|
const OPPORTUNITIES_TABLE_CLASS = "min-w-[30rem] sm:min-w-[34rem] max-w-none";
|
|
2464
2726
|
const TABLE_HEAD_CELL_NOWRAP = "whitespace-nowrap";
|
|
2465
|
-
function formatCurrency(value) {
|
|
2466
|
-
return new Intl.NumberFormat("en-IN", {
|
|
2467
|
-
style: "currency",
|
|
2468
|
-
currency: "INR",
|
|
2469
|
-
minimumFractionDigits: 0,
|
|
2470
|
-
maximumFractionDigits: 0
|
|
2471
|
-
}).format(value);
|
|
2472
|
-
}
|
|
2473
|
-
function formatCompactCurrency(value) {
|
|
2474
|
-
return new Intl.NumberFormat("en-IN", {
|
|
2475
|
-
style: "currency",
|
|
2476
|
-
currency: "INR",
|
|
2477
|
-
notation: "compact",
|
|
2478
|
-
maximumFractionDigits: 1
|
|
2479
|
-
}).format(value);
|
|
2480
|
-
}
|
|
2481
2727
|
function formatChartLabel(value) {
|
|
2482
2728
|
if (!value) return "";
|
|
2483
2729
|
const parsed = new Date(value);
|
|
@@ -2504,16 +2750,6 @@ function summaryDaysToGraphPeriod(days) {
|
|
|
2504
2750
|
if (days === "all" || days === "90") return "one_year";
|
|
2505
2751
|
return "one_week";
|
|
2506
2752
|
}
|
|
2507
|
-
function buildAnalyticsUrl(path, params) {
|
|
2508
|
-
const search = new URLSearchParams();
|
|
2509
|
-
for (const [key, value] of Object.entries(params)) {
|
|
2510
|
-
if (value) {
|
|
2511
|
-
search.set(key, value);
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
const query = search.toString();
|
|
2515
|
-
return query ? `${path}?${query}` : path;
|
|
2516
|
-
}
|
|
2517
2753
|
function isProductOverTimePoint(value) {
|
|
2518
2754
|
if (typeof value !== "object" || value === null) return false;
|
|
2519
2755
|
const v = value;
|
|
@@ -2525,6 +2761,7 @@ function TrendTooltip({
|
|
|
2525
2761
|
label
|
|
2526
2762
|
}) {
|
|
2527
2763
|
var _a, _b, _c, _d, _e;
|
|
2764
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
2528
2765
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2529
2766
|
const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
2530
2767
|
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
@@ -2556,6 +2793,7 @@ function MiniTrendTooltip({
|
|
|
2556
2793
|
hideRevenueRow
|
|
2557
2794
|
}) {
|
|
2558
2795
|
var _a, _b, _c, _d, _e;
|
|
2796
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
2559
2797
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2560
2798
|
const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
2561
2799
|
const labelKey = label !== void 0 && label !== null ? String(label) : "";
|
|
@@ -2586,6 +2824,7 @@ function ProductBarTooltip({
|
|
|
2586
2824
|
showViews = true
|
|
2587
2825
|
}) {
|
|
2588
2826
|
var _a;
|
|
2827
|
+
const { formatCurrency } = useAnalyticsCurrency();
|
|
2589
2828
|
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2590
2829
|
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
2591
2830
|
if (!row) return null;
|
|
@@ -2638,6 +2877,9 @@ function EmptyAnalyticsPanel({ title, description }) {
|
|
|
2638
2877
|
}
|
|
2639
2878
|
function ProductsDashboard() {
|
|
2640
2879
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2880
|
+
const { formatCurrency, formatCompactCurrency } = useAnalyticsCurrency();
|
|
2881
|
+
const chartGradientPrefix = react.useId().replace(/:/g, "");
|
|
2882
|
+
const gid = (suffix) => `${chartGradientPrefix}-${suffix}`;
|
|
2641
2883
|
const [summaryDays, setSummaryDays] = react.useState("all");
|
|
2642
2884
|
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
2643
2885
|
const [topSellerPeriod, setTopSellerPeriod] = react.useState("week");
|
|
@@ -3224,26 +3466,76 @@ function ProductsDashboard() {
|
|
|
3224
3466
|
margin: { top: 4, right: 6, left: -6, bottom: 0 },
|
|
3225
3467
|
children: [
|
|
3226
3468
|
/* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
|
|
3227
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3469
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3470
|
+
"linearGradient",
|
|
3471
|
+
{
|
|
3472
|
+
id: gid("trend-units-line"),
|
|
3473
|
+
x1: "0",
|
|
3474
|
+
y1: "0",
|
|
3475
|
+
x2: "1",
|
|
3476
|
+
y2: "0",
|
|
3477
|
+
children: [
|
|
3478
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#67E8F9" }),
|
|
3479
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3B82F6" })
|
|
3480
|
+
]
|
|
3481
|
+
}
|
|
3482
|
+
),
|
|
3483
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3484
|
+
"linearGradient",
|
|
3485
|
+
{
|
|
3486
|
+
id: gid("trend-revenue-line"),
|
|
3487
|
+
x1: "0",
|
|
3488
|
+
y1: "0",
|
|
3489
|
+
x2: "1",
|
|
3490
|
+
y2: "0",
|
|
3491
|
+
children: [
|
|
3492
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#F472B6" }),
|
|
3493
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC" })
|
|
3494
|
+
]
|
|
3495
|
+
}
|
|
3496
|
+
),
|
|
3497
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3498
|
+
"linearGradient",
|
|
3499
|
+
{
|
|
3500
|
+
id: gid("trend-units-area"),
|
|
3501
|
+
x1: "0",
|
|
3502
|
+
y1: "0",
|
|
3503
|
+
x2: "0",
|
|
3504
|
+
y2: "1",
|
|
3505
|
+
children: [
|
|
3506
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#38BDF8", stopOpacity: 0.22 }),
|
|
3507
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#38BDF8", stopOpacity: 0 })
|
|
3508
|
+
]
|
|
3509
|
+
}
|
|
3510
|
+
),
|
|
3511
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3512
|
+
"linearGradient",
|
|
3513
|
+
{
|
|
3514
|
+
id: gid("trend-revenue-area"),
|
|
3515
|
+
x1: "0",
|
|
3516
|
+
y1: "0",
|
|
3517
|
+
x2: "0",
|
|
3518
|
+
y2: "1",
|
|
3519
|
+
children: [
|
|
3520
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.2 }),
|
|
3521
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#E879F9", stopOpacity: 0 })
|
|
3522
|
+
]
|
|
3523
|
+
}
|
|
3524
|
+
),
|
|
3525
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3526
|
+
"linearGradient",
|
|
3527
|
+
{
|
|
3528
|
+
id: gid("trend-views-area"),
|
|
3529
|
+
x1: "0",
|
|
3530
|
+
y1: "0",
|
|
3531
|
+
x2: "0",
|
|
3532
|
+
y2: "1",
|
|
3533
|
+
children: [
|
|
3534
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#C084FC", stopOpacity: 0.18 }),
|
|
3535
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC", stopOpacity: 0 })
|
|
3536
|
+
]
|
|
3537
|
+
}
|
|
3538
|
+
)
|
|
3247
3539
|
] }),
|
|
3248
3540
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3249
3541
|
recharts.CartesianGrid,
|
|
@@ -3260,8 +3552,8 @@ function ProductsDashboard() {
|
|
|
3260
3552
|
tick: CHART_AXIS_TICK,
|
|
3261
3553
|
tickLine: false,
|
|
3262
3554
|
axisLine: false,
|
|
3263
|
-
tickFormatter: (value
|
|
3264
|
-
const row = series
|
|
3555
|
+
tickFormatter: (value) => {
|
|
3556
|
+
const row = series.find((p) => p.date === String(value));
|
|
3265
3557
|
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3266
3558
|
}
|
|
3267
3559
|
}
|
|
@@ -3307,7 +3599,7 @@ function ProductsDashboard() {
|
|
|
3307
3599
|
type: "natural",
|
|
3308
3600
|
dataKey: "units_sold",
|
|
3309
3601
|
stroke: "none",
|
|
3310
|
-
fill:
|
|
3602
|
+
fill: `url(#${gid("trend-units-area")})`,
|
|
3311
3603
|
isAnimationActive: false,
|
|
3312
3604
|
legendType: "none"
|
|
3313
3605
|
}
|
|
@@ -3319,7 +3611,7 @@ function ProductsDashboard() {
|
|
|
3319
3611
|
type: "natural",
|
|
3320
3612
|
dataKey: "views",
|
|
3321
3613
|
stroke: "none",
|
|
3322
|
-
fill:
|
|
3614
|
+
fill: `url(#${gid("trend-views-area")})`,
|
|
3323
3615
|
isAnimationActive: false,
|
|
3324
3616
|
legendType: "none"
|
|
3325
3617
|
}
|
|
@@ -3331,7 +3623,7 @@ function ProductsDashboard() {
|
|
|
3331
3623
|
type: "natural",
|
|
3332
3624
|
dataKey: "revenue",
|
|
3333
3625
|
stroke: "none",
|
|
3334
|
-
fill:
|
|
3626
|
+
fill: `url(#${gid("trend-revenue-area")})`,
|
|
3335
3627
|
isAnimationActive: false,
|
|
3336
3628
|
legendType: "none"
|
|
3337
3629
|
}
|
|
@@ -3343,7 +3635,7 @@ function ProductsDashboard() {
|
|
|
3343
3635
|
type: "natural",
|
|
3344
3636
|
dataKey: "units_sold",
|
|
3345
3637
|
name: "Units sold",
|
|
3346
|
-
stroke:
|
|
3638
|
+
stroke: `url(#${gid("trend-units-line")})`,
|
|
3347
3639
|
strokeWidth: 2,
|
|
3348
3640
|
dot: false,
|
|
3349
3641
|
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.units }
|
|
@@ -3370,7 +3662,7 @@ function ProductsDashboard() {
|
|
|
3370
3662
|
type: "natural",
|
|
3371
3663
|
dataKey: "revenue",
|
|
3372
3664
|
name: "Revenue",
|
|
3373
|
-
stroke:
|
|
3665
|
+
stroke: `url(#${gid("trend-revenue-line")})`,
|
|
3374
3666
|
strokeWidth: 2,
|
|
3375
3667
|
dot: false,
|
|
3376
3668
|
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.revenue }
|
|
@@ -3441,7 +3733,7 @@ function ProductsDashboard() {
|
|
|
3441
3733
|
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3442
3734
|
"linearGradient",
|
|
3443
3735
|
{
|
|
3444
|
-
id: "
|
|
3736
|
+
id: gid("best-revenue-area"),
|
|
3445
3737
|
x1: "0",
|
|
3446
3738
|
y1: "0",
|
|
3447
3739
|
x2: "0",
|
|
@@ -3479,7 +3771,12 @@ function ProductsDashboard() {
|
|
|
3479
3771
|
{
|
|
3480
3772
|
dataKey: "date",
|
|
3481
3773
|
tick: CHART_AXIS_TICK_SM,
|
|
3482
|
-
tickFormatter: (value) =>
|
|
3774
|
+
tickFormatter: (value) => {
|
|
3775
|
+
const row = bestSellersTrendSeries.find(
|
|
3776
|
+
(p) => p.date === String(value)
|
|
3777
|
+
);
|
|
3778
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3779
|
+
},
|
|
3483
3780
|
tickLine: false,
|
|
3484
3781
|
axisLine: { stroke: CHART_AXIS_LINE }
|
|
3485
3782
|
}
|
|
@@ -3529,7 +3826,7 @@ function ProductsDashboard() {
|
|
|
3529
3826
|
dataKey: "revenue",
|
|
3530
3827
|
name: "Revenue",
|
|
3531
3828
|
stroke: "transparent",
|
|
3532
|
-
fill:
|
|
3829
|
+
fill: `url(#${gid("best-revenue-area")})`,
|
|
3533
3830
|
isAnimationActive: false
|
|
3534
3831
|
}
|
|
3535
3832
|
),
|
|
@@ -3644,7 +3941,7 @@ function ProductsDashboard() {
|
|
|
3644
3941
|
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3645
3942
|
"linearGradient",
|
|
3646
3943
|
{
|
|
3647
|
-
id: "
|
|
3944
|
+
id: gid("viewed-views-area"),
|
|
3648
3945
|
x1: "0",
|
|
3649
3946
|
y1: "0",
|
|
3650
3947
|
x2: "0",
|
|
@@ -3682,7 +3979,12 @@ function ProductsDashboard() {
|
|
|
3682
3979
|
{
|
|
3683
3980
|
dataKey: "date",
|
|
3684
3981
|
tick: CHART_AXIS_TICK_SM,
|
|
3685
|
-
tickFormatter: (value) =>
|
|
3982
|
+
tickFormatter: (value) => {
|
|
3983
|
+
const row = mostViewedTrendSeries.find(
|
|
3984
|
+
(p) => p.date === String(value)
|
|
3985
|
+
);
|
|
3986
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3987
|
+
},
|
|
3686
3988
|
tickLine: false,
|
|
3687
3989
|
axisLine: { stroke: CHART_AXIS_LINE }
|
|
3688
3990
|
}
|
|
@@ -3719,7 +4021,7 @@ function ProductsDashboard() {
|
|
|
3719
4021
|
dataKey: "views",
|
|
3720
4022
|
name: "Views",
|
|
3721
4023
|
stroke: "transparent",
|
|
3722
|
-
fill:
|
|
4024
|
+
fill: `url(#${gid("viewed-views-area")})`,
|
|
3723
4025
|
isAnimationActive: false
|
|
3724
4026
|
}
|
|
3725
4027
|
),
|
|
@@ -3781,7 +4083,7 @@ function ProductsDashboard() {
|
|
|
3781
4083
|
title: "Views vs units sold",
|
|
3782
4084
|
description: "Each point is a product (top viewed in this period). Requires product view tracking.",
|
|
3783
4085
|
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[220px] w-full sm:h-[248px]", children: performanceLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading scatter…" }) }) : performanceError || !performance ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: performanceError ?? "Failed to load product performance" }) }) : !performance.productViewsConnected ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Product view tracking is not available in this environment." }) }) : viewsVsUnitsScatterData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "No products with views or sales in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.ScatterChart, { margin: { top: 12, right: 16, bottom: 8, left: 8 }, children: [
|
|
3784
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("radialGradient", { id: "
|
|
4086
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("radialGradient", { id: gid("scatter-glow"), cx: "50%", cy: "50%", r: "50%", children: [
|
|
3785
4087
|
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.95 }),
|
|
3786
4088
|
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#6366F1", stopOpacity: 0.65 })
|
|
3787
4089
|
] }) }),
|
|
@@ -3842,7 +4144,7 @@ function ProductsDashboard() {
|
|
|
3842
4144
|
{
|
|
3843
4145
|
name: "Products",
|
|
3844
4146
|
data: viewsVsUnitsScatterData,
|
|
3845
|
-
fill:
|
|
4147
|
+
fill: `url(#${gid("scatter-glow")})`
|
|
3846
4148
|
}
|
|
3847
4149
|
)
|
|
3848
4150
|
] }) }) }) })
|
|
@@ -3858,7 +4160,7 @@ const ANALYTICS_MODULES = [
|
|
|
3858
4160
|
];
|
|
3859
4161
|
const AnalyticsPage = () => {
|
|
3860
4162
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
3861
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4163
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsCurrencyProvider, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3862
4164
|
"div",
|
|
3863
4165
|
{
|
|
3864
4166
|
className: `mx-auto flex w-full min-w-0 flex-col ${activeModule === "products" ? "max-w-full gap-2 px-3 py-3 sm:px-4 sm:py-3" : "max-w-[1600px] gap-4 p-4"}`.trim(),
|
|
@@ -3899,7 +4201,7 @@ const AnalyticsPage = () => {
|
|
|
3899
4201
|
] })
|
|
3900
4202
|
]
|
|
3901
4203
|
}
|
|
3902
|
-
) }) });
|
|
4204
|
+
) }) }) });
|
|
3903
4205
|
};
|
|
3904
4206
|
const config = adminSdk.defineRouteConfig({
|
|
3905
4207
|
label: "Analytics",
|