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
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
|
735
|
-
key: `
|
|
736
|
-
label:
|
|
737
|
-
orders_count:
|
|
738
|
-
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|
|
1110
|
+
return salesBreakdownDaily.map((d) => ({
|
|
758
1111
|
key: d.date,
|
|
759
|
-
label:
|
|
1112
|
+
label: formatDayBreakdownDm(d.date),
|
|
760
1113
|
orders_count: d.orders_count,
|
|
761
1114
|
revenue: d.total_revenue
|
|
762
1115
|
}));
|
|
763
|
-
}, [
|
|
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
|
|
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
|
|
1128
|
+
return "Last 7 weeks (UTC, weeks start Sunday); axis shows each week’s start as day/month.";
|
|
775
1129
|
case "month":
|
|
776
|
-
return
|
|
1130
|
+
return "Last 12 calendar months through the current month (UTC) — Jan, Feb, Mar, …";
|
|
777
1131
|
default:
|
|
778
1132
|
return "";
|
|
779
1133
|
}
|
|
780
|
-
}, [salesGranularity
|
|
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)
|
|
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
|
|
827
|
-
helper:
|
|
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
|
|
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
|
|
864
|
-
const
|
|
1220
|
+
const salesBreakdownWaiting = salesGranularity === "hour" ? salesBreakdownHourlyLoading : salesBreakdownOtLoading;
|
|
1221
|
+
const salesBreakdownFetchError = salesGranularity === "hour" ? salesBreakdownHourlyError : salesBreakdownOtError;
|
|
865
1222
|
const salesBreakdownBody = (() => {
|
|
866
|
-
if (
|
|
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 (
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
1057
|
-
/* @__PURE__ */
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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__ */
|
|
1087
|
-
|
|
1429
|
+
/* @__PURE__ */ jsxs(
|
|
1430
|
+
"div",
|
|
1088
1431
|
{
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
|
1262
|
-
const row = dailyOrders
|
|
1263
|
-
|
|
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
|
|
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
|
|
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 (${((
|
|
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__ */
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
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: ((
|
|
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: (
|
|
2361
|
-
|
|
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
|
-
|
|
2365
|
-
|
|
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(
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
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
|
|
3263
|
-
const row = series
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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: "
|
|
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) =>
|
|
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:
|
|
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: "
|
|
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) =>
|
|
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:
|
|
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: "
|
|
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:
|
|
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",
|