medusa-analytics 0.0.16 → 0.0.18
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 +2227 -299
- package/.medusa/server/src/admin/index.mjs +2230 -302
- package/.medusa/server/src/api/admin/analytics/customers-over-time/route.js +14 -7
- package/.medusa/server/src/api/admin/analytics/customers-summary/route.js +14 -7
- package/.medusa/server/src/api/admin/analytics/fetch-orders-for-analytics.js +117 -0
- package/.medusa/server/src/api/admin/analytics/orders-analytics-helpers.js +64 -0
- package/.medusa/server/src/api/admin/analytics/orders-insights/route.js +199 -0
- package/.medusa/server/src/api/admin/analytics/orders-insights/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/orders-over-time/route.js +181 -99
- package/.medusa/server/src/api/admin/analytics/orders-over-time/types.js +1 -1
- package/.medusa/server/src/api/admin/analytics/orders-summary/route.js +9 -38
- package/.medusa/server/src/api/admin/analytics/repeat-customers/route.js +77 -0
- package/.medusa/server/src/api/admin/analytics/repeat-customers/types.js +3 -0
- package/package.json +1 -1
|
@@ -5,61 +5,184 @@ 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 ACCENT_STYLES = {
|
|
9
|
+
blue: {
|
|
10
|
+
border: "border-sky-400/30",
|
|
11
|
+
glow: "shadow-[0_0_0_1px_rgba(56,189,248,0.12),0_18px_40px_-28px_rgba(59,130,246,0.55)]",
|
|
12
|
+
band: "from-sky-400/70 via-blue-400/30 to-transparent"
|
|
13
|
+
},
|
|
14
|
+
purple: {
|
|
15
|
+
border: "border-fuchsia-400/25",
|
|
16
|
+
glow: "shadow-[0_0_0_1px_rgba(217,70,239,0.10),0_18px_40px_-28px_rgba(168,85,247,0.5)]",
|
|
17
|
+
band: "from-fuchsia-400/65 via-violet-400/25 to-transparent"
|
|
18
|
+
},
|
|
19
|
+
green: {
|
|
20
|
+
border: "border-emerald-400/25",
|
|
21
|
+
glow: "shadow-[0_0_0_1px_rgba(52,211,153,0.10),0_18px_40px_-28px_rgba(16,185,129,0.45)]",
|
|
22
|
+
band: "from-emerald-400/65 via-teal-400/25 to-transparent"
|
|
23
|
+
},
|
|
24
|
+
amber: {
|
|
25
|
+
border: "border-amber-400/25",
|
|
26
|
+
glow: "shadow-[0_0_0_1px_rgba(251,191,36,0.10),0_18px_40px_-28px_rgba(245,158,11,0.45)]",
|
|
27
|
+
band: "from-amber-400/65 via-orange-400/25 to-transparent"
|
|
28
|
+
},
|
|
29
|
+
rose: {
|
|
30
|
+
border: "border-rose-400/25",
|
|
31
|
+
glow: "shadow-[0_0_0_1px_rgba(251,113,133,0.10),0_18px_40px_-28px_rgba(244,63,94,0.45)]",
|
|
32
|
+
band: "from-rose-400/65 via-pink-400/25 to-transparent"
|
|
33
|
+
},
|
|
34
|
+
slate: {
|
|
35
|
+
border: "border-ui-border-strong",
|
|
36
|
+
glow: "shadow-[0_0_0_1px_rgba(15,23,42,0.05),0_18px_40px_-28px_rgba(15,23,42,0.22)]",
|
|
37
|
+
band: "from-ui-border-strong via-ui-border-base to-transparent"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function getAccentStyle(accent) {
|
|
41
|
+
return ACCENT_STYLES[accent];
|
|
42
|
+
}
|
|
8
43
|
function AnalyticsDashboardShell({ children }) {
|
|
9
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-
|
|
44
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2.5 md:space-y-3", children });
|
|
10
45
|
}
|
|
11
46
|
function AnalyticsDashboardHeader({
|
|
12
47
|
title,
|
|
13
48
|
description,
|
|
14
|
-
actions
|
|
49
|
+
actions,
|
|
50
|
+
variant = "default",
|
|
51
|
+
actionsBare = false,
|
|
52
|
+
appearance = "card"
|
|
15
53
|
}) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
const isPremium = variant === "premium";
|
|
55
|
+
const isInset = appearance === "inset";
|
|
56
|
+
const headerRow = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
57
|
+
"div",
|
|
58
|
+
{
|
|
59
|
+
className: `flex flex-col gap-2 px-4 py-2.5 lg:flex-row lg:items-center lg:justify-between ${isPremium ? "border-b border-ui-border-base/80" : "border-b border-ui-border-base"}`.trim(),
|
|
60
|
+
children: [
|
|
61
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
62
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: isPremium ? "text-lg font-semibold tracking-tight" : "tracking-tight", children: title }),
|
|
63
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted ${isPremium ? "max-w-2xl text-xs leading-snug" : "text-sm"}`.trim(), children: description }) : null
|
|
64
|
+
] }),
|
|
65
|
+
actions ? actionsBare ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-3 self-start lg:self-auto", children: actions }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
66
|
+
"div",
|
|
67
|
+
{
|
|
68
|
+
className: `flex self-start rounded-xl border px-2 py-1.5 lg:self-auto ${isPremium ? "border-ui-border-strong bg-ui-bg-subtle shadow-[inset_0_1px_0_rgba(255,255,255,0.35)]" : "border-ui-border-base bg-ui-bg-subtle"}`.trim(),
|
|
69
|
+
children: actions
|
|
70
|
+
}
|
|
71
|
+
) : null
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
if (isInset) {
|
|
76
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-0 bg-transparent p-0 shadow-none", children: headerRow });
|
|
77
|
+
}
|
|
78
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
79
|
+
ui.Container,
|
|
80
|
+
{
|
|
81
|
+
className: `overflow-hidden rounded-2xl border bg-ui-bg-base p-0 ${isPremium ? "border-ui-border-strong shadow-sm" : "border-ui-border-base shadow-sm"}`.trim(),
|
|
82
|
+
children: [
|
|
83
|
+
headerRow,
|
|
84
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
85
|
+
"div",
|
|
86
|
+
{
|
|
87
|
+
className: `h-px w-full ${isPremium ? "bg-gradient-to-r from-transparent via-ui-border-strong to-transparent" : "bg-ui-bg-subtle"}`.trim()
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
);
|
|
33
93
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
94
|
+
function AnalyticsStatCard({
|
|
95
|
+
label,
|
|
96
|
+
value,
|
|
97
|
+
helper,
|
|
98
|
+
className,
|
|
99
|
+
valueClassName,
|
|
100
|
+
variant = "default",
|
|
101
|
+
accent = "slate"
|
|
40
102
|
}) {
|
|
103
|
+
const accentStyle = getAccentStyle(accent);
|
|
104
|
+
const isHero = variant === "hero";
|
|
105
|
+
const isCompact = variant === "compact";
|
|
41
106
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
42
107
|
ui.Container,
|
|
43
108
|
{
|
|
44
|
-
className: `overflow-hidden rounded-2xl border border-ui-border-base bg-ui-bg-base p-
|
|
109
|
+
className: `relative h-full overflow-hidden rounded-2xl border bg-ui-bg-base transition-shadow ${isHero ? `p-3 ${accentStyle.border} ${accentStyle.glow} shadow-sm` : isCompact ? "border-ui-border-base bg-ui-bg-subtle/70 p-2.5 shadow-none" : "border-ui-border-base p-4 shadow-sm"} ${className ?? ""}`.trim(),
|
|
45
110
|
children: [
|
|
46
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
47
|
-
/* @__PURE__ */ jsxRuntime.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
111
|
+
isHero ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
className: `pointer-events-none absolute inset-x-3 top-0 h-10 rounded-b-full bg-gradient-to-r ${accentStyle.band} blur-xl`
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-3 top-0 h-px bg-gradient-to-r from-transparent via-white/40 to-transparent" })
|
|
119
|
+
] }) : null,
|
|
120
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
121
|
+
"div",
|
|
122
|
+
{
|
|
123
|
+
className: `relative flex flex-col justify-between gap-1 ${isCompact ? "min-h-[64px]" : isHero ? "min-h-[72px]" : "min-h-[92px]"}`.trim(),
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
126
|
+
ui.Text,
|
|
127
|
+
{
|
|
128
|
+
className: `text-ui-fg-muted font-medium ${isHero ? "text-[10px] uppercase tracking-[0.16em]" : "text-xs"}`.trim(),
|
|
129
|
+
children: label
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
133
|
+
ui.Heading,
|
|
134
|
+
{
|
|
135
|
+
level: "h2",
|
|
136
|
+
className: `leading-tight tracking-tight ${isHero ? "text-xl sm:text-2xl" : isCompact ? "text-lg" : "text-2xl"} ${valueClassName ?? ""}`.trim(),
|
|
137
|
+
children: value
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted leading-snug ${isHero ? "text-[10px]" : "text-xs"}`.trim(), children: helper }) : null
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
)
|
|
54
144
|
]
|
|
55
145
|
}
|
|
56
146
|
);
|
|
57
147
|
}
|
|
58
|
-
function
|
|
148
|
+
function AnalyticsSection({
|
|
149
|
+
title,
|
|
150
|
+
description,
|
|
151
|
+
actions,
|
|
152
|
+
children,
|
|
153
|
+
className,
|
|
154
|
+
variant = "default",
|
|
155
|
+
actionsBare = false
|
|
156
|
+
}) {
|
|
157
|
+
const isAtlas = variant === "atlas";
|
|
158
|
+
const isPremium = variant !== "default" && !isAtlas;
|
|
159
|
+
const shellClass = isAtlas ? "overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-base shadow-sm" : `overflow-hidden rounded-2xl border bg-ui-bg-base p-0 ${isPremium ? "border-ui-border-strong shadow-sm" : "border-ui-border-base shadow-sm"}`.trim();
|
|
160
|
+
const headerClass = isAtlas ? "flex flex-col gap-1 border-b border-ui-border-base bg-ui-bg-subtle/40 px-3 py-2 lg:flex-row lg:items-center lg:justify-between" : `flex flex-col gap-2 px-4 py-2.5 lg:flex-row lg:items-center lg:justify-between ${isPremium ? "border-b border-ui-border-base/80" : "border-b border-ui-border-base"}`.trim();
|
|
161
|
+
const titleClass = isAtlas ? "text-sm font-semibold tracking-tight text-ui-fg-base" : isPremium ? "text-base font-semibold tracking-tight" : "tracking-tight";
|
|
162
|
+
const descClass = isAtlas ? "max-w-2xl text-[11px] leading-snug text-ui-fg-muted" : isPremium ? "max-w-2xl text-xs leading-snug" : "text-sm";
|
|
163
|
+
const actionsShellClass = isAtlas ? "flex shrink-0 flex-wrap items-center gap-1.5 rounded-md border border-ui-border-base bg-ui-bg-base px-2 py-1" : `flex shrink-0 flex-wrap items-center gap-1.5 rounded-xl border px-2 py-1.5 ${isPremium ? "border-ui-border-strong bg-ui-bg-subtle shadow-[inset_0_1px_0_rgba(255,255,255,0.32)]" : "border-ui-border-base bg-ui-bg-subtle"}`.trim();
|
|
164
|
+
const bodyClass = isAtlas ? "p-2" : variant === "hero" ? "px-4 py-3" : "px-4 py-3";
|
|
165
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: `${shellClass} ${className ?? ""}`.trim(), children: [
|
|
166
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: headerClass, children: [
|
|
167
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-0.5", children: [
|
|
168
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: titleClass, children: title }),
|
|
169
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted ${descClass}`.trim(), children: description }) : null
|
|
170
|
+
] }),
|
|
171
|
+
actions ? actionsBare ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: actionsShellClass, children: actions }) : null
|
|
172
|
+
] }),
|
|
173
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: bodyClass, children })
|
|
174
|
+
] });
|
|
175
|
+
}
|
|
176
|
+
function AnalyticsChartSurface({
|
|
177
|
+
children,
|
|
178
|
+
className,
|
|
179
|
+
variant = "default"
|
|
180
|
+
}) {
|
|
181
|
+
const surfaceClassName = variant === "atlas" ? "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 p-2 shadow-sm" : variant === "glass" ? "rounded-xl border border-ui-border-strong bg-[linear-gradient(180deg,rgba(255,255,255,0.12),rgba(255,255,255,0.03))] p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.22)]" : variant === "panel" ? "rounded-xl border border-ui-border-base/90 bg-ui-bg-base p-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.18)]" : "rounded-lg border border-ui-border-base bg-ui-bg-subtle p-2 shadow-inner";
|
|
59
182
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
60
183
|
"div",
|
|
61
184
|
{
|
|
62
|
-
className:
|
|
185
|
+
className: `${surfaceClassName} ${className ?? ""}`.trim(),
|
|
63
186
|
children
|
|
64
187
|
}
|
|
65
188
|
);
|
|
@@ -73,56 +196,263 @@ function AnalyticsTableSurface({ children, className }) {
|
|
|
73
196
|
}
|
|
74
197
|
);
|
|
75
198
|
}
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
199
|
+
function isDailyOrderRow(value) {
|
|
200
|
+
if (typeof value !== "object" || value === null) return false;
|
|
201
|
+
const v = value;
|
|
202
|
+
return typeof v.date === "string" && typeof v.orders_count === "number" && typeof v.total_revenue === "number";
|
|
203
|
+
}
|
|
204
|
+
function nonCancelledRevenue(d) {
|
|
205
|
+
const delivered = d.revenue_delivered ?? 0;
|
|
206
|
+
const open = d.revenue_open ?? 0;
|
|
207
|
+
if (delivered > 0 || open > 0 || (d.revenue_cancelled ?? 0) > 0) {
|
|
208
|
+
return delivered + open;
|
|
209
|
+
}
|
|
210
|
+
return Math.max(0, d.total_revenue - (d.revenue_cancelled ?? 0));
|
|
211
|
+
}
|
|
212
|
+
function bucketAov(d) {
|
|
213
|
+
const ordNc = d.orders_count - d.cancelled_count;
|
|
214
|
+
if (ordNc <= 0) return 0;
|
|
215
|
+
return nonCancelledRevenue(d) / ordNc;
|
|
216
|
+
}
|
|
217
|
+
function isOrdersTodayPoint(value) {
|
|
218
|
+
return isDailyOrderRow(value) && "isToday" in value && typeof value.isToday === "boolean";
|
|
219
|
+
}
|
|
220
|
+
function formatCurrency$1(value) {
|
|
221
|
+
return new Intl.NumberFormat("en-IN", {
|
|
222
|
+
style: "currency",
|
|
223
|
+
currency: "INR",
|
|
224
|
+
minimumFractionDigits: 0,
|
|
225
|
+
maximumFractionDigits: 0
|
|
226
|
+
}).format(value);
|
|
227
|
+
}
|
|
228
|
+
function formatCompactCurrency(value) {
|
|
229
|
+
return new Intl.NumberFormat("en-IN", {
|
|
230
|
+
style: "currency",
|
|
231
|
+
currency: "INR",
|
|
232
|
+
notation: "compact",
|
|
233
|
+
maximumFractionDigits: 1
|
|
234
|
+
}).format(value);
|
|
235
|
+
}
|
|
236
|
+
function formatChartLabel(value) {
|
|
237
|
+
if (!value) return "";
|
|
238
|
+
const parsed = new Date(value);
|
|
239
|
+
if (Number.isNaN(parsed.getTime())) return value;
|
|
240
|
+
return `${parsed.getUTCMonth() + 1}/${parsed.getUTCDate()}`;
|
|
241
|
+
}
|
|
242
|
+
function formatPercent(value) {
|
|
243
|
+
return `${value.toFixed(1)}%`;
|
|
244
|
+
}
|
|
245
|
+
function ordersCountFromPiePayload(payload) {
|
|
246
|
+
if (typeof payload !== "object" || payload === null) return 0;
|
|
247
|
+
if (!("orders_count" in payload)) return 0;
|
|
248
|
+
const c = payload.orders_count;
|
|
249
|
+
return Math.floor(Number(c) || 0);
|
|
250
|
+
}
|
|
251
|
+
function formatShortNumber(value) {
|
|
252
|
+
return new Intl.NumberFormat("en-IN", {
|
|
253
|
+
notation: "compact",
|
|
254
|
+
maximumFractionDigits: value < 10 ? 1 : 0
|
|
255
|
+
}).format(value);
|
|
256
|
+
}
|
|
257
|
+
function TooltipCard({
|
|
258
|
+
title,
|
|
259
|
+
children
|
|
80
260
|
}) {
|
|
81
|
-
var _a, _b;
|
|
82
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
83
|
-
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
84
|
-
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : label);
|
|
85
|
-
const value = ((_b = payload[0]) == null ? void 0 : _b.value) ?? 0;
|
|
86
261
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
87
262
|
"div",
|
|
88
263
|
{
|
|
89
264
|
style: {
|
|
90
|
-
|
|
265
|
+
background: "linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(248,250,252,0.96) 100%)",
|
|
91
266
|
color: "#111827",
|
|
92
|
-
border: "1px solid
|
|
93
|
-
borderRadius: "
|
|
94
|
-
padding: "12px
|
|
95
|
-
boxShadow: "0
|
|
96
|
-
fontSize: "
|
|
97
|
-
lineHeight: 1.5
|
|
267
|
+
border: "1px solid rgba(148, 163, 184, 0.18)",
|
|
268
|
+
borderRadius: "14px",
|
|
269
|
+
padding: "12px 14px",
|
|
270
|
+
boxShadow: "0 16px 40px rgba(15, 23, 42, 0.12)",
|
|
271
|
+
fontSize: "13px",
|
|
272
|
+
lineHeight: 1.5,
|
|
273
|
+
minWidth: "176px"
|
|
98
274
|
},
|
|
99
275
|
children: [
|
|
100
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px"
|
|
101
|
-
|
|
102
|
-
"Orders: ",
|
|
103
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: "#1f2937" }, children: value })
|
|
104
|
-
] })
|
|
276
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px" }, children: title }),
|
|
277
|
+
children
|
|
105
278
|
]
|
|
106
279
|
}
|
|
107
280
|
);
|
|
108
281
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
282
|
+
function OrdersTrendTooltip({
|
|
283
|
+
active,
|
|
284
|
+
payload,
|
|
285
|
+
label
|
|
286
|
+
}) {
|
|
287
|
+
var _a, _b, _c, _d;
|
|
288
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
289
|
+
const row = isDailyOrderRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
290
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
291
|
+
const orders = ((_c = payload.find((entry) => entry.name === "Orders")) == null ? void 0 : _c.value) ?? 0;
|
|
292
|
+
const revenue = ((_d = payload.find((entry) => entry.name === "Revenue")) == null ? void 0 : _d.value) ?? 0;
|
|
293
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: displayLabel, children: [
|
|
294
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
295
|
+
"Orders: ",
|
|
296
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(orders).toLocaleString() })
|
|
297
|
+
] }),
|
|
298
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
299
|
+
"Revenue: ",
|
|
300
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(Number(revenue)) })
|
|
301
|
+
] })
|
|
302
|
+
] });
|
|
303
|
+
}
|
|
304
|
+
function OrdersTodayTooltip({
|
|
305
|
+
active,
|
|
306
|
+
payload,
|
|
307
|
+
label
|
|
308
|
+
}) {
|
|
309
|
+
var _a, _b, _c;
|
|
310
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
311
|
+
const row = isOrdersTodayPoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
312
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
313
|
+
const orders = ((_c = payload[0]) == null ? void 0 : _c.value) ?? 0;
|
|
314
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: displayLabel, children: [
|
|
315
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
316
|
+
"Orders: ",
|
|
317
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(orders).toLocaleString() })
|
|
318
|
+
] }),
|
|
319
|
+
(row == null ? void 0 : row.isToday) ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#6B7280" }, children: "Current day" }) : null
|
|
320
|
+
] });
|
|
321
|
+
}
|
|
322
|
+
function OrdersSparklineTooltip({
|
|
323
|
+
active,
|
|
324
|
+
payload,
|
|
325
|
+
label
|
|
326
|
+
}) {
|
|
327
|
+
var _a, _b, _c;
|
|
328
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
329
|
+
const row = isDailyOrderRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
330
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
331
|
+
const orders = Number((_c = payload[0]) == null ? void 0 : _c.value) || 0;
|
|
332
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TooltipCard, { title: displayLabel, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
333
|
+
"Orders: ",
|
|
334
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(orders).toLocaleString() })
|
|
335
|
+
] }) });
|
|
336
|
+
}
|
|
337
|
+
function OutcomesTrendTooltip({
|
|
338
|
+
active,
|
|
339
|
+
payload,
|
|
340
|
+
label
|
|
341
|
+
}) {
|
|
342
|
+
var _a, _b;
|
|
343
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
344
|
+
const row = isDailyOrderRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
345
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
346
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TooltipCard, { title: displayLabel, children: payload.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
347
|
+
entry.name,
|
|
348
|
+
":",
|
|
349
|
+
" ",
|
|
350
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(entry.value) || 0).toLocaleString() })
|
|
351
|
+
] }, String(entry.name))) });
|
|
352
|
+
}
|
|
353
|
+
const KPI_ICON_BG = {
|
|
354
|
+
green: "bg-emerald-500/20",
|
|
355
|
+
blue: "bg-sky-500/20",
|
|
356
|
+
purple: "bg-violet-500/20",
|
|
357
|
+
amber: "bg-amber-500/20"
|
|
113
358
|
};
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
359
|
+
const KPI_ICONS = {
|
|
360
|
+
green: icons.CurrencyDollar,
|
|
361
|
+
blue: icons.ShoppingCart,
|
|
362
|
+
purple: icons.TruckFast,
|
|
363
|
+
amber: icons.Cash
|
|
364
|
+
};
|
|
365
|
+
const PIE_PALETTE = ["#6366F1", "#94A3B8", "#10B981", "#F59E0B"];
|
|
366
|
+
function TrafficRevenueDonut({
|
|
367
|
+
traffic,
|
|
368
|
+
compact = false
|
|
369
|
+
}) {
|
|
370
|
+
const innerRadius = compact ? 28 : 34;
|
|
371
|
+
const outerRadius = compact ? 44 : 54;
|
|
372
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: compact ? "h-[118px] w-full" : "h-[152px] w-full shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 2, right: 2, left: 2, bottom: 2 }, children: [
|
|
373
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
374
|
+
recharts.Pie,
|
|
375
|
+
{
|
|
376
|
+
data: traffic,
|
|
377
|
+
dataKey: "revenue",
|
|
378
|
+
nameKey: "label",
|
|
379
|
+
cx: "50%",
|
|
380
|
+
cy: "48%",
|
|
381
|
+
innerRadius,
|
|
382
|
+
outerRadius,
|
|
383
|
+
paddingAngle: 2,
|
|
384
|
+
children: traffic.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: PIE_PALETTE[i % PIE_PALETTE.length] }, `traffic-slice-${i}`))
|
|
385
|
+
}
|
|
386
|
+
),
|
|
387
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
388
|
+
recharts.Tooltip,
|
|
389
|
+
{
|
|
390
|
+
content: ({ active, payload }) => {
|
|
391
|
+
var _a, _b, _c;
|
|
392
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(((_a = payload[0]) == null ? void 0 : _a.name) ?? ""), children: [
|
|
393
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
394
|
+
"Revenue:",
|
|
395
|
+
" ",
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(Number((_b = payload[0]) == null ? void 0 : _b.value) || 0) })
|
|
397
|
+
] }),
|
|
398
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
399
|
+
"Orders:",
|
|
400
|
+
" ",
|
|
401
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: ordersCountFromPiePayload((_c = payload[0]) == null ? void 0 : _c.payload).toLocaleString() })
|
|
402
|
+
] })
|
|
403
|
+
] }) : null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
408
|
+
recharts.Legend,
|
|
409
|
+
{
|
|
410
|
+
wrapperStyle: { fontSize: compact ? 8 : 9, paddingTop: 0 },
|
|
411
|
+
verticalAlign: "bottom",
|
|
412
|
+
height: compact ? 18 : 22,
|
|
413
|
+
iconType: "circle"
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
] }) }) });
|
|
417
|
+
}
|
|
418
|
+
function TrafficSourcesOverviewCard({
|
|
419
|
+
traffic,
|
|
420
|
+
loading,
|
|
421
|
+
error,
|
|
422
|
+
totalRevenue,
|
|
423
|
+
windowLabel
|
|
424
|
+
}) {
|
|
425
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-[132px] flex-col rounded-2xl border border-ui-border-base p-3 shadow-sm", children: [
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs font-medium text-ui-fg-base", children: "Traffic sources" }),
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "mb-1 text-[10px] leading-snug text-ui-fg-muted", children: [
|
|
428
|
+
"Revenue share · ",
|
|
429
|
+
windowLabel
|
|
430
|
+
] }),
|
|
431
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : error ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center px-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-[10px] text-ui-fg-danger", children: error }) }) : traffic.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center px-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-[10px] leading-snug text-ui-fg-muted", children: "No traffic breakdown for this window." }) }) : totalRevenue <= 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[104px] items-center justify-center px-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-[10px] leading-snug text-ui-fg-muted", children: "No recorded revenue in this window." }) }) : /* @__PURE__ */ jsxRuntime.jsx(TrafficRevenueDonut, { traffic, compact: true }) })
|
|
432
|
+
] });
|
|
433
|
+
}
|
|
434
|
+
function AtlasKpiCard({
|
|
435
|
+
label,
|
|
436
|
+
value,
|
|
437
|
+
helper,
|
|
438
|
+
accent
|
|
439
|
+
}) {
|
|
440
|
+
const Icon = KPI_ICONS[accent];
|
|
441
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-[112px] min-w-0 flex-col rounded-xl border border-ui-border-base/80 bg-ui-bg-subtle/25 p-4 shadow-sm", children: [
|
|
442
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
443
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "min-w-0 flex-1 text-left text-xs font-medium leading-snug text-ui-fg-base", children: label }),
|
|
444
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
445
|
+
"div",
|
|
446
|
+
{
|
|
447
|
+
className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${KPI_ICON_BG[accent]}`,
|
|
448
|
+
"aria-hidden": true,
|
|
449
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-ui-fg-base" })
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
] }),
|
|
453
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 text-xl font-semibold tabular-nums tracking-tight text-ui-fg-base sm:text-2xl", children: value }),
|
|
454
|
+
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-[11px] leading-snug text-ui-fg-muted", children: helper }) : null
|
|
455
|
+
] });
|
|
126
456
|
}
|
|
127
457
|
const SUMMARY_PERIODS$1 = [
|
|
128
458
|
{ value: "all", label: "All time" },
|
|
@@ -136,21 +466,52 @@ const OVER_TIME_PERIODS = [
|
|
|
136
466
|
{ value: "one_month", label: "One month" },
|
|
137
467
|
{ value: "one_year", label: "One year" }
|
|
138
468
|
];
|
|
469
|
+
const STATUS_BAR_COLORS = {
|
|
470
|
+
delivered: "#10B981",
|
|
471
|
+
cancelled: "#EF4444",
|
|
472
|
+
exchange: "#F59E0B",
|
|
473
|
+
return: "#8B5CF6",
|
|
474
|
+
today: "var(--medusa-color-ui-fg-interactive)",
|
|
475
|
+
todayMuted: "#BFDBFE",
|
|
476
|
+
orders: "#38BDF8",
|
|
477
|
+
revenue: "#D946EF"
|
|
478
|
+
};
|
|
479
|
+
const CHART_AXIS_TICK = { fontSize: 10, fill: "#e5e7eb" };
|
|
480
|
+
const CHART_AXIS_TICK_SM = { fontSize: 9, fill: "#e5e7eb" };
|
|
481
|
+
const CHART_AXIS_LINE = "#94a3b8";
|
|
482
|
+
const SELECT_CLASS_NAME = "h-9 min-w-[132px] cursor-pointer appearance-none rounded-full border border-ui-border-base bg-ui-bg-base py-1.5 pl-3 pr-8 text-xs text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive";
|
|
483
|
+
function EmptyAnalyticsPanel({
|
|
484
|
+
title,
|
|
485
|
+
description
|
|
486
|
+
}) {
|
|
487
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-[88px] flex-col gap-1.5 rounded-lg border border-dashed border-ui-border-base px-3 py-3", children: [
|
|
488
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-xs font-semibold", children: title }),
|
|
489
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[11px] leading-snug", children: description })
|
|
490
|
+
] });
|
|
491
|
+
}
|
|
139
492
|
function OrdersDashboard() {
|
|
493
|
+
var _a, _b, _c, _d;
|
|
140
494
|
const [data, setData] = react.useState(null);
|
|
141
495
|
const [loading, setLoading] = react.useState(true);
|
|
142
496
|
const [error, setError] = react.useState(null);
|
|
143
497
|
const [filter, setFilter] = react.useState("all");
|
|
144
498
|
const [dailyOrders, setDailyOrders] = react.useState([]);
|
|
499
|
+
const [previousPeriodDailyOrders, setPreviousPeriodDailyOrders] = react.useState([]);
|
|
145
500
|
const [overTimePeriod, setOverTimePeriod] = react.useState("one_week");
|
|
146
501
|
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
147
502
|
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
503
|
+
const [ordersInsights, setOrdersInsights] = react.useState(null);
|
|
504
|
+
const [insightsLoading, setInsightsLoading] = react.useState(true);
|
|
505
|
+
const [insightsError, setInsightsError] = react.useState(null);
|
|
506
|
+
const [todayContext, setTodayContext] = react.useState([]);
|
|
507
|
+
const [todayContextLoading, setTodayContextLoading] = react.useState(true);
|
|
508
|
+
const [todayContextError, setTodayContextError] = react.useState(null);
|
|
148
509
|
react.useEffect(() => {
|
|
149
510
|
let cancelled = false;
|
|
150
511
|
setLoading(true);
|
|
151
512
|
setError(null);
|
|
152
513
|
const url = `/admin/analytics/orders-summary?days=${filter}`;
|
|
153
|
-
fetch(url).then((res) => {
|
|
514
|
+
fetch(url, { credentials: "include" }).then((res) => {
|
|
154
515
|
if (!res.ok) throw new Error(res.statusText);
|
|
155
516
|
return res.json();
|
|
156
517
|
}).then((body) => {
|
|
@@ -169,11 +530,14 @@ function OrdersDashboard() {
|
|
|
169
530
|
setOverTimeLoading(true);
|
|
170
531
|
setOverTimeError(null);
|
|
171
532
|
const url = `/admin/analytics/orders-over-time?period=${overTimePeriod}`;
|
|
172
|
-
fetch(url).then((res) => {
|
|
533
|
+
fetch(url, { credentials: "include" }).then((res) => {
|
|
173
534
|
if (!res.ok) throw new Error(res.statusText);
|
|
174
535
|
return res.json();
|
|
175
536
|
}).then((body) => {
|
|
176
|
-
if (!cancelled)
|
|
537
|
+
if (!cancelled) {
|
|
538
|
+
setDailyOrders(body.dailyOrders ?? []);
|
|
539
|
+
setPreviousPeriodDailyOrders(body.previousPeriodDailyOrders ?? []);
|
|
540
|
+
}
|
|
177
541
|
}).catch((e) => {
|
|
178
542
|
if (!cancelled) setOverTimeError(e instanceof Error ? e.message : String(e));
|
|
179
543
|
}).finally(() => {
|
|
@@ -183,11 +547,240 @@ function OrdersDashboard() {
|
|
|
183
547
|
cancelled = true;
|
|
184
548
|
};
|
|
185
549
|
}, [overTimePeriod]);
|
|
550
|
+
const insightsWindowDays = react.useMemo(() => {
|
|
551
|
+
if (overTimePeriod === "one_week") return 7;
|
|
552
|
+
if (overTimePeriod === "one_month") return 30;
|
|
553
|
+
return 365;
|
|
554
|
+
}, [overTimePeriod]);
|
|
555
|
+
react.useEffect(() => {
|
|
556
|
+
let cancelled = false;
|
|
557
|
+
setInsightsLoading(true);
|
|
558
|
+
setInsightsError(null);
|
|
559
|
+
fetch(`/admin/analytics/orders-insights?days=${insightsWindowDays}`, {
|
|
560
|
+
credentials: "include"
|
|
561
|
+
}).then((res) => {
|
|
562
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
563
|
+
return res.json();
|
|
564
|
+
}).then((body) => {
|
|
565
|
+
if (!cancelled) setOrdersInsights(body);
|
|
566
|
+
}).catch((e) => {
|
|
567
|
+
if (!cancelled) setInsightsError(e instanceof Error ? e.message : String(e));
|
|
568
|
+
}).finally(() => {
|
|
569
|
+
if (!cancelled) setInsightsLoading(false);
|
|
570
|
+
});
|
|
571
|
+
return () => {
|
|
572
|
+
cancelled = true;
|
|
573
|
+
};
|
|
574
|
+
}, [insightsWindowDays]);
|
|
575
|
+
react.useEffect(() => {
|
|
576
|
+
let cancelled = false;
|
|
577
|
+
setTodayContextLoading(true);
|
|
578
|
+
setTodayContextError(null);
|
|
579
|
+
fetch("/admin/analytics/orders-over-time?period=one_week", {
|
|
580
|
+
credentials: "include"
|
|
581
|
+
}).then((res) => {
|
|
582
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
583
|
+
return res.json();
|
|
584
|
+
}).then((body) => {
|
|
585
|
+
if (!cancelled) setTodayContext(body.dailyOrders ?? []);
|
|
586
|
+
}).catch((e) => {
|
|
587
|
+
if (!cancelled) {
|
|
588
|
+
setTodayContextError(e instanceof Error ? e.message : String(e));
|
|
589
|
+
}
|
|
590
|
+
}).finally(() => {
|
|
591
|
+
if (!cancelled) setTodayContextLoading(false);
|
|
592
|
+
});
|
|
593
|
+
return () => {
|
|
594
|
+
cancelled = true;
|
|
595
|
+
};
|
|
596
|
+
}, []);
|
|
597
|
+
const selectedSummaryPeriod = ((_a = SUMMARY_PERIODS$1.find((period) => period.value === filter)) == null ? void 0 : _a.label) ?? "All time";
|
|
598
|
+
const operationsBreakdown = (data == null ? void 0 : data.operationalCounts) ?? {
|
|
599
|
+
cancelled: (data == null ? void 0 : data.cancelledOrders) ?? 0,
|
|
600
|
+
delivered: (data == null ? void 0 : data.deliveredOrders) ?? 0,
|
|
601
|
+
exchange: (data == null ? void 0 : data.exchangeOrders) ?? 0,
|
|
602
|
+
return: (data == null ? void 0 : data.returnOrders) ?? 0
|
|
603
|
+
};
|
|
604
|
+
const outcomesTimeSeriesHasPoints = react.useMemo(
|
|
605
|
+
() => dailyOrders.some(
|
|
606
|
+
(d) => d.delivered_count > 0 || d.cancelled_count > 0 || d.exchange_count > 0 || d.return_count > 0
|
|
607
|
+
),
|
|
608
|
+
[dailyOrders]
|
|
609
|
+
);
|
|
610
|
+
const totalResolvedOrders = operationsBreakdown.delivered + operationsBreakdown.cancelled;
|
|
611
|
+
const deliveryRate = totalResolvedOrders > 0 ? operationsBreakdown.delivered / totalResolvedOrders * 100 : 0;
|
|
612
|
+
const primaryStats = data ? [
|
|
613
|
+
{
|
|
614
|
+
label: "Total revenue",
|
|
615
|
+
value: formatCurrency$1(data.totalRevenue),
|
|
616
|
+
helper: "Non-cancelled orders",
|
|
617
|
+
accent: "green"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
label: "Total orders",
|
|
621
|
+
value: data.totalOrders.toLocaleString(),
|
|
622
|
+
helper: selectedSummaryPeriod,
|
|
623
|
+
accent: "blue"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
label: "Delivery rate",
|
|
627
|
+
value: formatPercent(deliveryRate),
|
|
628
|
+
helper: "Delivered vs cancelled",
|
|
629
|
+
accent: "purple"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
label: "Average order value",
|
|
633
|
+
value: formatCurrency$1(data.aov),
|
|
634
|
+
helper: "Per non-cancelled order",
|
|
635
|
+
accent: "amber"
|
|
636
|
+
}
|
|
637
|
+
] : [];
|
|
638
|
+
const secondaryStats = data ? [
|
|
639
|
+
{ label: "Orders today", value: data.ordersToday.toLocaleString() },
|
|
640
|
+
{ label: "Pending orders", value: data.pendingOrders.toLocaleString() },
|
|
641
|
+
{ label: "Delivered orders", value: data.deliveredOrders.toLocaleString() },
|
|
642
|
+
{ label: "Cancelled orders", value: data.cancelledOrders.toLocaleString() },
|
|
643
|
+
{ label: "Exchange orders", value: data.exchangeOrders.toLocaleString() },
|
|
644
|
+
{
|
|
645
|
+
label: "Avg. fulfillment time",
|
|
646
|
+
value: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "—",
|
|
647
|
+
helper: "Measured in hours"
|
|
648
|
+
}
|
|
649
|
+
] : [];
|
|
650
|
+
const todayChartData = react.useMemo(
|
|
651
|
+
() => todayContext.map((point, index) => ({
|
|
652
|
+
...point,
|
|
653
|
+
isToday: index === todayContext.length - 1
|
|
654
|
+
})),
|
|
655
|
+
[todayContext]
|
|
656
|
+
);
|
|
657
|
+
const todayOrdersAverage = todayChartData.length > 0 ? todayChartData.reduce((sum, point) => sum + point.orders_count, 0) / todayChartData.length : 0;
|
|
658
|
+
const todayPoint = todayChartData[todayChartData.length - 1] ?? null;
|
|
659
|
+
const todayDelta = todayPoint && todayOrdersAverage > 0 ? todayPoint.orders_count - todayOrdersAverage : null;
|
|
660
|
+
const trendRevenueTotal = dailyOrders.reduce(
|
|
661
|
+
(sum, point) => sum + point.total_revenue,
|
|
662
|
+
0
|
|
663
|
+
);
|
|
664
|
+
const trendOrdersTotal = dailyOrders.reduce(
|
|
665
|
+
(sum, point) => sum + point.orders_count,
|
|
666
|
+
0
|
|
667
|
+
);
|
|
668
|
+
const trendAverageRevenue = dailyOrders.length > 0 ? trendRevenueTotal / dailyOrders.length : 0;
|
|
669
|
+
const trendAverageOrders = dailyOrders.length > 0 ? trendOrdersTotal / dailyOrders.length : 0;
|
|
670
|
+
const peakRevenuePoint = dailyOrders.length > 0 ? dailyOrders.reduce(
|
|
671
|
+
(peak, point) => point.total_revenue > peak.total_revenue ? point : peak
|
|
672
|
+
) : null;
|
|
673
|
+
const previousPeriodRevenueTotal = react.useMemo(
|
|
674
|
+
() => previousPeriodDailyOrders.reduce((s, d) => s + d.total_revenue, 0),
|
|
675
|
+
[previousPeriodDailyOrders]
|
|
676
|
+
);
|
|
677
|
+
const previousPeriodOrdersTotal = react.useMemo(
|
|
678
|
+
() => previousPeriodDailyOrders.reduce((s, d) => s + d.orders_count, 0),
|
|
679
|
+
[previousPeriodDailyOrders]
|
|
680
|
+
);
|
|
681
|
+
const revenueVsPreviousPercent = previousPeriodRevenueTotal > 0 ? (trendRevenueTotal - previousPeriodRevenueTotal) / previousPeriodRevenueTotal * 100 : null;
|
|
682
|
+
const ordersVsPreviousPercent = previousPeriodOrdersTotal > 0 ? (trendOrdersTotal - previousPeriodOrdersTotal) / previousPeriodOrdersTotal * 100 : null;
|
|
683
|
+
const trendRowsDetailed = react.useMemo(() => {
|
|
684
|
+
return dailyOrders.map((d, i) => {
|
|
685
|
+
const prev = previousPeriodDailyOrders[i];
|
|
686
|
+
return {
|
|
687
|
+
...d,
|
|
688
|
+
aov: bucketAov(d),
|
|
689
|
+
prev_revenue: (prev == null ? void 0 : prev.total_revenue) ?? 0,
|
|
690
|
+
prev_orders: (prev == null ? void 0 : prev.orders_count) ?? 0,
|
|
691
|
+
prev_aov: prev ? bucketAov(prev) : 0
|
|
692
|
+
};
|
|
693
|
+
});
|
|
694
|
+
}, [dailyOrders, previousPeriodDailyOrders]);
|
|
695
|
+
const hourlyChartRows = react.useMemo(() => {
|
|
696
|
+
if (!ordersInsights) return [];
|
|
697
|
+
return ordersInsights.byHour.map((h) => ({
|
|
698
|
+
label: `${String(h.hour).padStart(2, "0")}:00`,
|
|
699
|
+
hour: h.hour,
|
|
700
|
+
orders_count: h.orders_count,
|
|
701
|
+
revenue: h.revenue
|
|
702
|
+
}));
|
|
703
|
+
}, [ordersInsights]);
|
|
704
|
+
const weekdayChartRows = react.useMemo(() => {
|
|
705
|
+
if (!ordersInsights) return [];
|
|
706
|
+
return ordersInsights.byWeekday.map((w) => ({
|
|
707
|
+
label: w.label,
|
|
708
|
+
orders_count: w.orders_count,
|
|
709
|
+
revenue: w.revenue
|
|
710
|
+
}));
|
|
711
|
+
}, [ordersInsights]);
|
|
712
|
+
const ordersVsDraftsPie = react.useMemo(() => {
|
|
713
|
+
var _a2;
|
|
714
|
+
if (!ordersInsights) return [];
|
|
715
|
+
const placed = ((_a2 = ordersInsights.funnel[0]) == null ? void 0 : _a2.count) ?? 0;
|
|
716
|
+
const draftsCount = ordersInsights.drafts.available === true ? ordersInsights.drafts.count : 0;
|
|
717
|
+
return [
|
|
718
|
+
{ name: "Orders placed (window)", value: placed, key: "placed" },
|
|
719
|
+
{
|
|
720
|
+
name: ordersInsights.drafts.available === true ? "Open draft orders (snapshot)" : "Draft orders (unavailable)",
|
|
721
|
+
value: draftsCount,
|
|
722
|
+
key: "drafts"
|
|
723
|
+
}
|
|
724
|
+
];
|
|
725
|
+
}, [ordersInsights]);
|
|
726
|
+
const funnelTimeSeriesRows = react.useMemo(() => {
|
|
727
|
+
return dailyOrders.map((d) => {
|
|
728
|
+
const placed = d.orders_count;
|
|
729
|
+
const notCancelled = Math.max(0, d.orders_count - d.cancelled_count);
|
|
730
|
+
const shipped = d.shipped_count ?? 0;
|
|
731
|
+
const delivered = d.delivered_count;
|
|
732
|
+
const pctNotCancelled = placed > 0 ? notCancelled / placed * 100 : 0;
|
|
733
|
+
const pctShippedOfNc = notCancelled > 0 ? shipped / notCancelled * 100 : 0;
|
|
734
|
+
const pctDeliveredOfShipped = shipped > 0 ? delivered / shipped * 100 : 0;
|
|
735
|
+
return {
|
|
736
|
+
date: d.date,
|
|
737
|
+
label: d.label ?? formatChartLabel(d.date),
|
|
738
|
+
placed,
|
|
739
|
+
not_cancelled: notCancelled,
|
|
740
|
+
shipped,
|
|
741
|
+
delivered,
|
|
742
|
+
pct_not_cancelled: Math.round(pctNotCancelled * 10) / 10,
|
|
743
|
+
pct_shipped_of_active: Math.round(pctShippedOfNc * 10) / 10,
|
|
744
|
+
pct_delivered_of_shipped: Math.round(pctDeliveredOfShipped * 10) / 10
|
|
745
|
+
};
|
|
746
|
+
});
|
|
747
|
+
}, [dailyOrders]);
|
|
748
|
+
const trafficTotalRevenue = react.useMemo(
|
|
749
|
+
() => (ordersInsights == null ? void 0 : ordersInsights.traffic.reduce((sum, t) => sum + (t.revenue || 0), 0)) ?? 0,
|
|
750
|
+
[ordersInsights]
|
|
751
|
+
);
|
|
752
|
+
const insightsRangeLabel = ((_b = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _b.label) ?? "One week";
|
|
753
|
+
const quickPulseMetrics = [
|
|
754
|
+
{
|
|
755
|
+
label: "Range revenue",
|
|
756
|
+
value: formatCompactCurrency(trendRevenueTotal),
|
|
757
|
+
helper: selectedSummaryPeriod,
|
|
758
|
+
accentClassName: "border-emerald-400/25 bg-emerald-500/5"
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
label: "Range orders",
|
|
762
|
+
value: formatShortNumber(trendOrdersTotal),
|
|
763
|
+
helper: "Current trend window",
|
|
764
|
+
accentClassName: "border-sky-400/25 bg-sky-500/5"
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
label: "Avg/day orders",
|
|
768
|
+
value: trendAverageOrders.toFixed(1),
|
|
769
|
+
helper: "Across selected chart range",
|
|
770
|
+
accentClassName: "border-violet-400/25 bg-violet-500/5"
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
label: "Peak revenue day",
|
|
774
|
+
value: peakRevenuePoint ? formatCompactCurrency(peakRevenuePoint.total_revenue) : "—",
|
|
775
|
+
helper: (peakRevenuePoint == null ? void 0 : peakRevenuePoint.label) ?? formatChartLabel(peakRevenuePoint == null ? void 0 : peakRevenuePoint.date),
|
|
776
|
+
accentClassName: "border-amber-400/25 bg-amber-500/5"
|
|
777
|
+
}
|
|
778
|
+
];
|
|
186
779
|
if (loading) {
|
|
187
780
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
188
781
|
"div",
|
|
189
782
|
{
|
|
190
|
-
className: "flex items-center justify-center
|
|
783
|
+
className: "flex min-h-[200px] items-center justify-center",
|
|
191
784
|
role: "status",
|
|
192
785
|
"aria-label": "Loading analytics",
|
|
193
786
|
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
@@ -197,253 +790,1406 @@ function OrdersDashboard() {
|
|
|
197
790
|
if (error || !data) {
|
|
198
791
|
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" }) });
|
|
199
792
|
}
|
|
200
|
-
|
|
201
|
-
const value = key === "delivered" ? data.statusCounts.delivered ?? data.deliveredOrders ?? 0 : key === "cancelled" ? data.statusCounts.cancelled ?? data.cancelledOrders ?? 0 : data.statusCounts.pending ?? data.pendingOrders ?? 0;
|
|
202
|
-
return { name: label, value, color };
|
|
203
|
-
}).filter((d) => d.value > 0);
|
|
204
|
-
const returnsExchangesData = [
|
|
205
|
-
{ name: "Return orders", value: data.returnOrders, color: "#EF4444" },
|
|
206
|
-
{ name: "Exchange orders", value: data.exchangeOrders, color: "#F59E0B" }
|
|
207
|
-
].filter((d) => d.value > 0);
|
|
208
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsDashboardShell, { children: [
|
|
793
|
+
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: [
|
|
209
794
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
210
795
|
AnalyticsDashboardHeader,
|
|
211
796
|
{
|
|
797
|
+
variant: "premium",
|
|
798
|
+
appearance: "inset",
|
|
799
|
+
actionsBare: true,
|
|
212
800
|
title: "Orders",
|
|
213
|
-
description: "
|
|
801
|
+
description: "Revenue, volume, and outcomes in a compact overview sized for a single viewport.",
|
|
214
802
|
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
215
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
216
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
803
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
804
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
805
|
+
ui.Label,
|
|
806
|
+
{
|
|
807
|
+
htmlFor: "analytics-period",
|
|
808
|
+
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
809
|
+
children: "Period"
|
|
810
|
+
}
|
|
811
|
+
),
|
|
217
812
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
218
813
|
"select",
|
|
219
814
|
{
|
|
220
815
|
id: "analytics-period",
|
|
221
816
|
value: filter,
|
|
222
817
|
onChange: (e) => setFilter(e.target.value),
|
|
223
|
-
className:
|
|
818
|
+
className: SELECT_CLASS_NAME,
|
|
224
819
|
children: SUMMARY_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
225
820
|
}
|
|
226
821
|
)
|
|
227
822
|
] }),
|
|
228
|
-
filter !== "all"
|
|
823
|
+
filter !== "all" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
229
824
|
"button",
|
|
230
825
|
{
|
|
231
|
-
type: "button",
|
|
232
|
-
onClick: () => setFilter("all"),
|
|
233
|
-
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base
|
|
234
|
-
"aria-label": "Show all orders (clear filter)",
|
|
235
|
-
children: "Clear
|
|
826
|
+
type: "button",
|
|
827
|
+
onClick: () => setFilter("all"),
|
|
828
|
+
className: "text-xs text-ui-fg-muted transition-colors hover:text-ui-fg-base",
|
|
829
|
+
"aria-label": "Show all orders (clear filter)",
|
|
830
|
+
children: "Clear period"
|
|
831
|
+
}
|
|
832
|
+
) : null
|
|
833
|
+
] })
|
|
834
|
+
}
|
|
835
|
+
),
|
|
836
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 p-3 pt-0 md:p-4 md:pt-0", children: [
|
|
837
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
838
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Order overview" }) }),
|
|
839
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
840
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
841
|
+
TrafficSourcesOverviewCard,
|
|
842
|
+
{
|
|
843
|
+
traffic: (ordersInsights == null ? void 0 : ordersInsights.traffic) ?? [],
|
|
844
|
+
loading: insightsLoading,
|
|
845
|
+
error: insightsError,
|
|
846
|
+
totalRevenue: trafficTotalRevenue,
|
|
847
|
+
windowLabel: insightsRangeLabel
|
|
848
|
+
}
|
|
849
|
+
),
|
|
850
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-3 lg:grid-cols-4", children: primaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
851
|
+
AtlasKpiCard,
|
|
852
|
+
{
|
|
853
|
+
label: stat.label,
|
|
854
|
+
value: stat.value,
|
|
855
|
+
helper: stat.helper,
|
|
856
|
+
accent: stat.accent
|
|
857
|
+
},
|
|
858
|
+
stat.label
|
|
859
|
+
)) })
|
|
860
|
+
] })
|
|
861
|
+
] }),
|
|
862
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-12 gap-2 xl:items-start", children: [
|
|
863
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12 xl:col-span-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
864
|
+
AnalyticsSection,
|
|
865
|
+
{
|
|
866
|
+
variant: "atlas",
|
|
867
|
+
actionsBare: true,
|
|
868
|
+
title: "Revenue & orders",
|
|
869
|
+
description: "Time series for the selected range. Window totals below match this chart.",
|
|
870
|
+
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
871
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
872
|
+
ui.Label,
|
|
873
|
+
{
|
|
874
|
+
htmlFor: "over-time-period",
|
|
875
|
+
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
876
|
+
children: "Range"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
880
|
+
"select",
|
|
881
|
+
{
|
|
882
|
+
id: "over-time-period",
|
|
883
|
+
value: overTimePeriod,
|
|
884
|
+
onChange: (e) => setOverTimePeriod(e.target.value),
|
|
885
|
+
className: SELECT_CLASS_NAME,
|
|
886
|
+
children: OVER_TIME_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
887
|
+
}
|
|
888
|
+
)
|
|
889
|
+
] }),
|
|
890
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
891
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
892
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
893
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window revenue" }),
|
|
894
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency(trendRevenueTotal) }),
|
|
895
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
896
|
+
"Avg/day ",
|
|
897
|
+
formatCompactCurrency(trendAverageRevenue)
|
|
898
|
+
] })
|
|
899
|
+
] }) }),
|
|
900
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
901
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window orders" }),
|
|
902
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: trendOrdersTotal.toLocaleString() }),
|
|
903
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
904
|
+
"Avg/day ",
|
|
905
|
+
trendAverageOrders.toFixed(1)
|
|
906
|
+
] })
|
|
907
|
+
] }) }),
|
|
908
|
+
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
909
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "vs previous period" }),
|
|
910
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: revenueVsPreviousPercent === null ? "—" : `${revenueVsPreviousPercent >= 0 ? "+" : ""}${revenueVsPreviousPercent.toFixed(1)}% rev` }),
|
|
911
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: [
|
|
912
|
+
"Orders",
|
|
913
|
+
" ",
|
|
914
|
+
ordersVsPreviousPercent === null ? "—" : `${ordersVsPreviousPercent >= 0 ? "+" : ""}${ordersVsPreviousPercent.toFixed(1)}%`
|
|
915
|
+
] })
|
|
916
|
+
] }) })
|
|
917
|
+
] }),
|
|
918
|
+
!overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
919
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 lg:grid-cols-3", children: [
|
|
920
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
921
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue over time" }),
|
|
922
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[118px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
923
|
+
recharts.LineChart,
|
|
924
|
+
{
|
|
925
|
+
data: trendRowsDetailed,
|
|
926
|
+
margin: { top: 4, right: 4, left: 0, bottom: 0 },
|
|
927
|
+
children: [
|
|
928
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
929
|
+
recharts.CartesianGrid,
|
|
930
|
+
{
|
|
931
|
+
strokeDasharray: "3 3",
|
|
932
|
+
vertical: false,
|
|
933
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
934
|
+
}
|
|
935
|
+
),
|
|
936
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
937
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
938
|
+
recharts.YAxis,
|
|
939
|
+
{
|
|
940
|
+
width: 36,
|
|
941
|
+
tick: CHART_AXIS_TICK_SM,
|
|
942
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
943
|
+
}
|
|
944
|
+
),
|
|
945
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
946
|
+
recharts.Tooltip,
|
|
947
|
+
{
|
|
948
|
+
content: ({ active, payload, label }) => {
|
|
949
|
+
var _a2, _b2, _c2, _d2;
|
|
950
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
951
|
+
TooltipCard,
|
|
952
|
+
{
|
|
953
|
+
title: String(
|
|
954
|
+
isDailyOrderRow((_a2 = payload[0]) == null ? void 0 : _a2.payload) ? ((_b2 = payload[0]) == null ? void 0 : _b2.payload.label) ?? formatChartLabel(String(label)) : label
|
|
955
|
+
),
|
|
956
|
+
children: [
|
|
957
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
958
|
+
"This:",
|
|
959
|
+
" ",
|
|
960
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCompactCurrency(
|
|
961
|
+
Number(
|
|
962
|
+
(_c2 = payload.find((p) => p.dataKey === "total_revenue")) == null ? void 0 : _c2.value
|
|
963
|
+
) || 0
|
|
964
|
+
) })
|
|
965
|
+
] }),
|
|
966
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
967
|
+
"Previous:",
|
|
968
|
+
" ",
|
|
969
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCompactCurrency(
|
|
970
|
+
Number(
|
|
971
|
+
(_d2 = payload.find((p) => p.dataKey === "prev_revenue")) == null ? void 0 : _d2.value
|
|
972
|
+
) || 0
|
|
973
|
+
) })
|
|
974
|
+
] })
|
|
975
|
+
]
|
|
976
|
+
}
|
|
977
|
+
) : null;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
982
|
+
recharts.Line,
|
|
983
|
+
{
|
|
984
|
+
type: "natural",
|
|
985
|
+
dataKey: "total_revenue",
|
|
986
|
+
name: "This period",
|
|
987
|
+
stroke: "#D946EF",
|
|
988
|
+
strokeWidth: 2,
|
|
989
|
+
dot: false
|
|
990
|
+
}
|
|
991
|
+
),
|
|
992
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
993
|
+
recharts.Line,
|
|
994
|
+
{
|
|
995
|
+
type: "natural",
|
|
996
|
+
dataKey: "prev_revenue",
|
|
997
|
+
name: "Previous",
|
|
998
|
+
stroke: "#94a3b8",
|
|
999
|
+
strokeWidth: 1.5,
|
|
1000
|
+
strokeDasharray: "5 4",
|
|
1001
|
+
dot: false
|
|
1002
|
+
}
|
|
1003
|
+
)
|
|
1004
|
+
]
|
|
1005
|
+
}
|
|
1006
|
+
) }) })
|
|
1007
|
+
] }),
|
|
1008
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1009
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Orders count" }),
|
|
1010
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[118px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1011
|
+
recharts.LineChart,
|
|
1012
|
+
{
|
|
1013
|
+
data: trendRowsDetailed,
|
|
1014
|
+
margin: { top: 4, right: 4, left: 0, bottom: 0 },
|
|
1015
|
+
children: [
|
|
1016
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1017
|
+
recharts.CartesianGrid,
|
|
1018
|
+
{
|
|
1019
|
+
strokeDasharray: "3 3",
|
|
1020
|
+
vertical: false,
|
|
1021
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1022
|
+
}
|
|
1023
|
+
),
|
|
1024
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
1025
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1026
|
+
recharts.YAxis,
|
|
1027
|
+
{
|
|
1028
|
+
width: 28,
|
|
1029
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1030
|
+
allowDecimals: false
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1034
|
+
recharts.Tooltip,
|
|
1035
|
+
{
|
|
1036
|
+
content: ({ active, payload, label }) => {
|
|
1037
|
+
var _a2, _b2;
|
|
1038
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
1039
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1040
|
+
"This:",
|
|
1041
|
+
" ",
|
|
1042
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
1043
|
+
Number(
|
|
1044
|
+
(_a2 = payload.find((p) => p.dataKey === "orders_count")) == null ? void 0 : _a2.value
|
|
1045
|
+
) || 0
|
|
1046
|
+
).toLocaleString() })
|
|
1047
|
+
] }),
|
|
1048
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1049
|
+
"Previous:",
|
|
1050
|
+
" ",
|
|
1051
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
1052
|
+
Number(
|
|
1053
|
+
(_b2 = payload.find((p) => p.dataKey === "prev_orders")) == null ? void 0 : _b2.value
|
|
1054
|
+
) || 0
|
|
1055
|
+
).toLocaleString() })
|
|
1056
|
+
] })
|
|
1057
|
+
] }) : null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
),
|
|
1061
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1062
|
+
recharts.Line,
|
|
1063
|
+
{
|
|
1064
|
+
type: "natural",
|
|
1065
|
+
dataKey: "orders_count",
|
|
1066
|
+
name: "This period",
|
|
1067
|
+
stroke: "#38BDF8",
|
|
1068
|
+
strokeWidth: 2,
|
|
1069
|
+
dot: false
|
|
1070
|
+
}
|
|
1071
|
+
),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1073
|
+
recharts.Line,
|
|
1074
|
+
{
|
|
1075
|
+
type: "natural",
|
|
1076
|
+
dataKey: "prev_orders",
|
|
1077
|
+
name: "Previous",
|
|
1078
|
+
stroke: "#94a3b8",
|
|
1079
|
+
strokeWidth: 1.5,
|
|
1080
|
+
strokeDasharray: "5 4",
|
|
1081
|
+
dot: false
|
|
1082
|
+
}
|
|
1083
|
+
)
|
|
1084
|
+
]
|
|
1085
|
+
}
|
|
1086
|
+
) }) })
|
|
1087
|
+
] }),
|
|
1088
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "AOV (non-cancelled)" }),
|
|
1090
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[118px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1091
|
+
recharts.LineChart,
|
|
1092
|
+
{
|
|
1093
|
+
data: trendRowsDetailed,
|
|
1094
|
+
margin: { top: 4, right: 4, left: 0, bottom: 0 },
|
|
1095
|
+
children: [
|
|
1096
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1097
|
+
recharts.CartesianGrid,
|
|
1098
|
+
{
|
|
1099
|
+
strokeDasharray: "3 3",
|
|
1100
|
+
vertical: false,
|
|
1101
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1102
|
+
}
|
|
1103
|
+
),
|
|
1104
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
1105
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1106
|
+
recharts.YAxis,
|
|
1107
|
+
{
|
|
1108
|
+
width: 36,
|
|
1109
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1110
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1111
|
+
}
|
|
1112
|
+
),
|
|
1113
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1114
|
+
recharts.Tooltip,
|
|
1115
|
+
{
|
|
1116
|
+
content: ({ active, payload, label }) => {
|
|
1117
|
+
var _a2, _b2;
|
|
1118
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
1119
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1120
|
+
"This:",
|
|
1121
|
+
" ",
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
1123
|
+
Number(
|
|
1124
|
+
(_a2 = payload.find((p) => p.dataKey === "aov")) == null ? void 0 : _a2.value
|
|
1125
|
+
) || 0
|
|
1126
|
+
) })
|
|
1127
|
+
] }),
|
|
1128
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1129
|
+
"Previous:",
|
|
1130
|
+
" ",
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
1132
|
+
Number(
|
|
1133
|
+
(_b2 = payload.find((p) => p.dataKey === "prev_aov")) == null ? void 0 : _b2.value
|
|
1134
|
+
) || 0
|
|
1135
|
+
) })
|
|
1136
|
+
] })
|
|
1137
|
+
] }) : null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
),
|
|
1141
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1142
|
+
recharts.Line,
|
|
1143
|
+
{
|
|
1144
|
+
type: "natural",
|
|
1145
|
+
dataKey: "aov",
|
|
1146
|
+
name: "This period",
|
|
1147
|
+
stroke: "#D97706",
|
|
1148
|
+
strokeWidth: 2,
|
|
1149
|
+
dot: false
|
|
1150
|
+
}
|
|
1151
|
+
),
|
|
1152
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1153
|
+
recharts.Line,
|
|
1154
|
+
{
|
|
1155
|
+
type: "natural",
|
|
1156
|
+
dataKey: "prev_aov",
|
|
1157
|
+
name: "Previous",
|
|
1158
|
+
stroke: "#94a3b8",
|
|
1159
|
+
strokeWidth: 1.5,
|
|
1160
|
+
strokeDasharray: "5 4",
|
|
1161
|
+
dot: false
|
|
1162
|
+
}
|
|
1163
|
+
)
|
|
1164
|
+
]
|
|
1165
|
+
}
|
|
1166
|
+
) }) })
|
|
1167
|
+
] })
|
|
1168
|
+
] }),
|
|
1169
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
|
|
1170
|
+
/* @__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)" }),
|
|
1171
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(168px,26vh)] min-h-[140px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1172
|
+
recharts.ComposedChart,
|
|
1173
|
+
{
|
|
1174
|
+
data: dailyOrders,
|
|
1175
|
+
margin: { top: 4, right: 8, left: -4, bottom: 0 },
|
|
1176
|
+
children: [
|
|
1177
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1178
|
+
recharts.CartesianGrid,
|
|
1179
|
+
{
|
|
1180
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1181
|
+
strokeDasharray: "3 3",
|
|
1182
|
+
vertical: false
|
|
1183
|
+
}
|
|
1184
|
+
),
|
|
1185
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1186
|
+
recharts.XAxis,
|
|
1187
|
+
{
|
|
1188
|
+
dataKey: "date",
|
|
1189
|
+
tick: CHART_AXIS_TICK,
|
|
1190
|
+
tickLine: false,
|
|
1191
|
+
axisLine: false,
|
|
1192
|
+
tickFormatter: (value, index) => {
|
|
1193
|
+
const row = dailyOrders[index];
|
|
1194
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(value);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
),
|
|
1198
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1199
|
+
recharts.YAxis,
|
|
1200
|
+
{
|
|
1201
|
+
tick: CHART_AXIS_TICK,
|
|
1202
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1203
|
+
}
|
|
1204
|
+
),
|
|
1205
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1206
|
+
recharts.Tooltip,
|
|
1207
|
+
{
|
|
1208
|
+
content: ({ active, payload, label }) => active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(TooltipCard, { title: String(label), children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1209
|
+
p.name,
|
|
1210
|
+
":",
|
|
1211
|
+
" ",
|
|
1212
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(Number(p.value) || 0) })
|
|
1213
|
+
] }, String(p.name))) }) : null
|
|
1214
|
+
}
|
|
1215
|
+
),
|
|
1216
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { wrapperStyle: { fontSize: 10 }, iconType: "circle" }),
|
|
1217
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1218
|
+
recharts.Area,
|
|
1219
|
+
{
|
|
1220
|
+
type: "natural",
|
|
1221
|
+
dataKey: "revenue_delivered",
|
|
1222
|
+
name: "Delivered",
|
|
1223
|
+
stackId: "r",
|
|
1224
|
+
fill: "#10B981",
|
|
1225
|
+
stroke: "#059669",
|
|
1226
|
+
fillOpacity: 0.85
|
|
1227
|
+
}
|
|
1228
|
+
),
|
|
1229
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1230
|
+
recharts.Area,
|
|
1231
|
+
{
|
|
1232
|
+
type: "natural",
|
|
1233
|
+
dataKey: "revenue_open",
|
|
1234
|
+
name: "Open / pending",
|
|
1235
|
+
stackId: "r",
|
|
1236
|
+
fill: "#3B82F6",
|
|
1237
|
+
stroke: "#2563eb",
|
|
1238
|
+
fillOpacity: 0.85
|
|
1239
|
+
}
|
|
1240
|
+
),
|
|
1241
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1242
|
+
recharts.Area,
|
|
1243
|
+
{
|
|
1244
|
+
type: "natural",
|
|
1245
|
+
dataKey: "revenue_cancelled",
|
|
1246
|
+
name: "Cancelled",
|
|
1247
|
+
stackId: "r",
|
|
1248
|
+
fill: "#F87171",
|
|
1249
|
+
stroke: "#EF4444",
|
|
1250
|
+
fillOpacity: 0.75
|
|
1251
|
+
}
|
|
1252
|
+
)
|
|
1253
|
+
]
|
|
1254
|
+
}
|
|
1255
|
+
) }) })
|
|
1256
|
+
] })
|
|
1257
|
+
] }) : null,
|
|
1258
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "relative overflow-hidden", children: [
|
|
1259
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-6 top-0 h-12 rounded-b-full bg-gradient-to-r from-sky-400/10 via-fuchsia-400/10 to-transparent blur-xl" }),
|
|
1260
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative space-y-1", children: [
|
|
1261
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-1.5", children: [
|
|
1262
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0", children: [
|
|
1263
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Trend overview" }),
|
|
1264
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-sm font-semibold", children: ((_c = OVER_TIME_PERIODS.find((period) => period.value === overTimePeriod)) == null ? void 0 : _c.label) ?? "One week" })
|
|
1265
|
+
] }),
|
|
1266
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap justify-end gap-2 text-[10px] text-ui-fg-muted", children: [
|
|
1267
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1268
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-sky-400" }),
|
|
1269
|
+
"Orders"
|
|
1270
|
+
] }),
|
|
1271
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
1272
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-fuchsia-400" }),
|
|
1273
|
+
"Revenue"
|
|
1274
|
+
] })
|
|
1275
|
+
] })
|
|
1276
|
+
] }),
|
|
1277
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(200px,30vh)] min-h-[160px] sm:h-[min(216px,32vh)]", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1278
|
+
"div",
|
|
1279
|
+
{
|
|
1280
|
+
className: "flex h-full items-center justify-center",
|
|
1281
|
+
role: "status",
|
|
1282
|
+
"aria-label": "Loading orders over time",
|
|
1283
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" })
|
|
1284
|
+
}
|
|
1285
|
+
) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: overTimeError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1286
|
+
recharts.ComposedChart,
|
|
1287
|
+
{
|
|
1288
|
+
data: dailyOrders,
|
|
1289
|
+
margin: { top: 4, right: 6, left: -6, bottom: 0 },
|
|
1290
|
+
children: [
|
|
1291
|
+
/* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
|
|
1292
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "ordersGradient", x1: "0", y1: "0", x2: "1", y2: "0", children: [
|
|
1293
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#67E8F9" }),
|
|
1294
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3B82F6" })
|
|
1295
|
+
] }),
|
|
1296
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "revenueGradient", x1: "0", y1: "0", x2: "1", y2: "0", children: [
|
|
1297
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#F472B6" }),
|
|
1298
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC" })
|
|
1299
|
+
] }),
|
|
1300
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "ordersAreaFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1301
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#38BDF8", stopOpacity: 0.22 }),
|
|
1302
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#38BDF8", stopOpacity: 0 })
|
|
1303
|
+
] }),
|
|
1304
|
+
/* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "revenueAreaFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1305
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.2 }),
|
|
1306
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#E879F9", stopOpacity: 0 })
|
|
1307
|
+
] })
|
|
1308
|
+
] }),
|
|
1309
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1310
|
+
recharts.CartesianGrid,
|
|
1311
|
+
{
|
|
1312
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1313
|
+
strokeDasharray: "3 3",
|
|
1314
|
+
vertical: false
|
|
1315
|
+
}
|
|
1316
|
+
),
|
|
1317
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1318
|
+
recharts.XAxis,
|
|
1319
|
+
{
|
|
1320
|
+
dataKey: "date",
|
|
1321
|
+
tick: CHART_AXIS_TICK,
|
|
1322
|
+
tickLine: false,
|
|
1323
|
+
axisLine: false,
|
|
1324
|
+
tickFormatter: (value, index) => {
|
|
1325
|
+
const row = dailyOrders[index];
|
|
1326
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(value);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
),
|
|
1330
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1331
|
+
recharts.YAxis,
|
|
1332
|
+
{
|
|
1333
|
+
yAxisId: "orders",
|
|
1334
|
+
width: 32,
|
|
1335
|
+
tick: CHART_AXIS_TICK,
|
|
1336
|
+
tickLine: false,
|
|
1337
|
+
axisLine: false,
|
|
1338
|
+
allowDecimals: false,
|
|
1339
|
+
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1340
|
+
}
|
|
1341
|
+
),
|
|
1342
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1343
|
+
recharts.YAxis,
|
|
1344
|
+
{
|
|
1345
|
+
yAxisId: "revenue",
|
|
1346
|
+
orientation: "right",
|
|
1347
|
+
width: 40,
|
|
1348
|
+
tick: CHART_AXIS_TICK,
|
|
1349
|
+
tickLine: false,
|
|
1350
|
+
axisLine: false,
|
|
1351
|
+
tickFormatter: (value) => formatCompactCurrency(Number(value) || 0)
|
|
1352
|
+
}
|
|
1353
|
+
),
|
|
1354
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1355
|
+
recharts.Tooltip,
|
|
1356
|
+
{
|
|
1357
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(OrdersTrendTooltip, {}),
|
|
1358
|
+
cursor: {
|
|
1359
|
+
stroke: "rgba(248, 250, 252, 0.35)",
|
|
1360
|
+
strokeWidth: 1
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
),
|
|
1364
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1365
|
+
recharts.Area,
|
|
1366
|
+
{
|
|
1367
|
+
yAxisId: "orders",
|
|
1368
|
+
type: "natural",
|
|
1369
|
+
dataKey: "orders_count",
|
|
1370
|
+
stroke: "none",
|
|
1371
|
+
fill: "url(#ordersAreaFill)",
|
|
1372
|
+
isAnimationActive: false,
|
|
1373
|
+
legendType: "none"
|
|
1374
|
+
}
|
|
1375
|
+
),
|
|
1376
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1377
|
+
recharts.Area,
|
|
1378
|
+
{
|
|
1379
|
+
yAxisId: "revenue",
|
|
1380
|
+
type: "natural",
|
|
1381
|
+
dataKey: "total_revenue",
|
|
1382
|
+
stroke: "none",
|
|
1383
|
+
fill: "url(#revenueAreaFill)",
|
|
1384
|
+
isAnimationActive: false,
|
|
1385
|
+
legendType: "none"
|
|
1386
|
+
}
|
|
1387
|
+
),
|
|
1388
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1389
|
+
recharts.Line,
|
|
1390
|
+
{
|
|
1391
|
+
yAxisId: "orders",
|
|
1392
|
+
type: "natural",
|
|
1393
|
+
dataKey: "orders_count",
|
|
1394
|
+
name: "Orders",
|
|
1395
|
+
stroke: "url(#ordersGradient)",
|
|
1396
|
+
strokeWidth: 2,
|
|
1397
|
+
dot: false,
|
|
1398
|
+
activeDot: { r: 4, fill: STATUS_BAR_COLORS.orders }
|
|
1399
|
+
}
|
|
1400
|
+
),
|
|
1401
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1402
|
+
recharts.Line,
|
|
1403
|
+
{
|
|
1404
|
+
yAxisId: "revenue",
|
|
1405
|
+
type: "natural",
|
|
1406
|
+
dataKey: "total_revenue",
|
|
1407
|
+
name: "Revenue",
|
|
1408
|
+
stroke: "url(#revenueGradient)",
|
|
1409
|
+
strokeWidth: 2,
|
|
1410
|
+
dot: false,
|
|
1411
|
+
activeDot: { r: 4, fill: STATUS_BAR_COLORS.revenue }
|
|
1412
|
+
}
|
|
1413
|
+
)
|
|
1414
|
+
]
|
|
1415
|
+
}
|
|
1416
|
+
) }) })
|
|
1417
|
+
] })
|
|
1418
|
+
] })
|
|
1419
|
+
] })
|
|
1420
|
+
}
|
|
1421
|
+
) }),
|
|
1422
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12 flex flex-col gap-2 xl:col-span-6", children: [
|
|
1423
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1424
|
+
AnalyticsSection,
|
|
1425
|
+
{
|
|
1426
|
+
variant: "atlas",
|
|
1427
|
+
title: "Pulse & range",
|
|
1428
|
+
description: "Window totals and an orders time series for the selected range.",
|
|
1429
|
+
children: [
|
|
1430
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2", children: quickPulseMetrics.map((metric) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1431
|
+
"div",
|
|
1432
|
+
{
|
|
1433
|
+
className: `rounded-lg border border-ui-border-base bg-ui-bg-base px-2 py-2 shadow-sm ${metric.accentClassName}`.trim(),
|
|
1434
|
+
children: [
|
|
1435
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: metric.label }),
|
|
1436
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base mt-0.5 text-sm font-semibold tracking-tight", children: metric.value }),
|
|
1437
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted mt-0.5 text-[9px] leading-snug", children: metric.helper })
|
|
1438
|
+
]
|
|
1439
|
+
},
|
|
1440
|
+
metric.label
|
|
1441
|
+
)) }),
|
|
1442
|
+
!overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
|
|
1443
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Orders (time series)" }),
|
|
1444
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[76px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1445
|
+
recharts.LineChart,
|
|
1446
|
+
{
|
|
1447
|
+
data: dailyOrders,
|
|
1448
|
+
margin: { top: 2, right: 4, left: -18, bottom: 2 },
|
|
1449
|
+
children: [
|
|
1450
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1451
|
+
recharts.CartesianGrid,
|
|
1452
|
+
{
|
|
1453
|
+
strokeDasharray: "3 3",
|
|
1454
|
+
vertical: false,
|
|
1455
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1458
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", hide: true }),
|
|
1459
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { hide: true, domain: ["auto", "auto"] }),
|
|
1460
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1461
|
+
recharts.Tooltip,
|
|
1462
|
+
{
|
|
1463
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(OrdersSparklineTooltip, {}),
|
|
1464
|
+
cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 }
|
|
1465
|
+
}
|
|
1466
|
+
),
|
|
1467
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1468
|
+
recharts.Line,
|
|
1469
|
+
{
|
|
1470
|
+
type: "natural",
|
|
1471
|
+
dataKey: "orders_count",
|
|
1472
|
+
stroke: "#3b82f6",
|
|
1473
|
+
strokeWidth: 2,
|
|
1474
|
+
dot: { r: 2, fill: "#3b82f6" },
|
|
1475
|
+
activeDot: { r: 3 }
|
|
1476
|
+
}
|
|
1477
|
+
)
|
|
1478
|
+
]
|
|
1479
|
+
}
|
|
1480
|
+
) }) })
|
|
1481
|
+
] }) : null
|
|
1482
|
+
]
|
|
1483
|
+
}
|
|
1484
|
+
),
|
|
1485
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1486
|
+
AnalyticsSection,
|
|
1487
|
+
{
|
|
1488
|
+
variant: "atlas",
|
|
1489
|
+
title: "Order → fulfillment funnel",
|
|
1490
|
+
description: `Stages by order created date (UTC), same range as Revenue & orders (${((_d = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _d.label) ?? "period"}). Not storefront visitors.`,
|
|
1491
|
+
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: [
|
|
1492
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1493
|
+
/* @__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)" }),
|
|
1494
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(200px,30vh)] min-h-[168px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1495
|
+
recharts.LineChart,
|
|
1496
|
+
{
|
|
1497
|
+
data: funnelTimeSeriesRows,
|
|
1498
|
+
margin: { top: 4, right: 8, left: 0, bottom: 4 },
|
|
1499
|
+
children: [
|
|
1500
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1501
|
+
recharts.CartesianGrid,
|
|
1502
|
+
{
|
|
1503
|
+
strokeDasharray: "3 3",
|
|
1504
|
+
stroke: "rgba(148,163,184,0.2)"
|
|
1505
|
+
}
|
|
1506
|
+
),
|
|
1507
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1508
|
+
recharts.XAxis,
|
|
1509
|
+
{
|
|
1510
|
+
dataKey: "date",
|
|
1511
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1512
|
+
stroke: CHART_AXIS_LINE,
|
|
1513
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1514
|
+
tickFormatter: (value) => {
|
|
1515
|
+
const row = dailyOrders.find((r) => r.date === value);
|
|
1516
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
),
|
|
1520
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1521
|
+
recharts.YAxis,
|
|
1522
|
+
{
|
|
1523
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1524
|
+
stroke: CHART_AXIS_LINE,
|
|
1525
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1526
|
+
width: 28,
|
|
1527
|
+
allowDecimals: false,
|
|
1528
|
+
tickFormatter: (v) => Math.floor(Number(v) || 0).toLocaleString()
|
|
1529
|
+
}
|
|
1530
|
+
),
|
|
1531
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1532
|
+
recharts.Tooltip,
|
|
1533
|
+
{
|
|
1534
|
+
content: ({ active, payload, label }) => {
|
|
1535
|
+
var _a2;
|
|
1536
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1537
|
+
TooltipCard,
|
|
1538
|
+
{
|
|
1539
|
+
title: typeof label === "string" ? ((_a2 = dailyOrders.find((r) => r.date === label)) == null ? void 0 : _a2.label) ?? formatChartLabel(label) : String(label),
|
|
1540
|
+
children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1541
|
+
String(p.name),
|
|
1542
|
+
":",
|
|
1543
|
+
" ",
|
|
1544
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(p.value) || 0).toLocaleString() })
|
|
1545
|
+
] }, String(p.dataKey)))
|
|
1546
|
+
}
|
|
1547
|
+
) : null;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
),
|
|
1551
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1552
|
+
recharts.Legend,
|
|
1553
|
+
{
|
|
1554
|
+
wrapperStyle: { fontSize: 9, color: "#e5e7eb", paddingTop: 4 },
|
|
1555
|
+
iconType: "circle",
|
|
1556
|
+
iconSize: 6
|
|
1557
|
+
}
|
|
1558
|
+
),
|
|
1559
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1560
|
+
recharts.Line,
|
|
1561
|
+
{
|
|
1562
|
+
type: "natural",
|
|
1563
|
+
dataKey: "placed",
|
|
1564
|
+
name: "Placed",
|
|
1565
|
+
stroke: "#6366F1",
|
|
1566
|
+
strokeWidth: 2,
|
|
1567
|
+
dot: false
|
|
1568
|
+
}
|
|
1569
|
+
),
|
|
1570
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1571
|
+
recharts.Line,
|
|
1572
|
+
{
|
|
1573
|
+
type: "natural",
|
|
1574
|
+
dataKey: "not_cancelled",
|
|
1575
|
+
name: "Not cancelled",
|
|
1576
|
+
stroke: "#22d3ee",
|
|
1577
|
+
strokeWidth: 2,
|
|
1578
|
+
dot: false
|
|
1579
|
+
}
|
|
1580
|
+
),
|
|
1581
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1582
|
+
recharts.Line,
|
|
1583
|
+
{
|
|
1584
|
+
type: "natural",
|
|
1585
|
+
dataKey: "shipped",
|
|
1586
|
+
name: "Shipped",
|
|
1587
|
+
stroke: "#F59E0B",
|
|
1588
|
+
strokeWidth: 2,
|
|
1589
|
+
dot: false
|
|
1590
|
+
}
|
|
1591
|
+
),
|
|
1592
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1593
|
+
recharts.Line,
|
|
1594
|
+
{
|
|
1595
|
+
type: "natural",
|
|
1596
|
+
dataKey: "delivered",
|
|
1597
|
+
name: "Delivered",
|
|
1598
|
+
stroke: "#10B981",
|
|
1599
|
+
strokeWidth: 2,
|
|
1600
|
+
dot: false
|
|
1601
|
+
}
|
|
1602
|
+
)
|
|
1603
|
+
]
|
|
1604
|
+
}
|
|
1605
|
+
) }) })
|
|
1606
|
+
] }),
|
|
1607
|
+
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1608
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Stage conversion (% of prior stage, same buckets)" }),
|
|
1609
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(168px,26vh)] min-h-[140px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1610
|
+
recharts.LineChart,
|
|
1611
|
+
{
|
|
1612
|
+
data: funnelTimeSeriesRows,
|
|
1613
|
+
margin: { top: 4, right: 8, left: 0, bottom: 4 },
|
|
1614
|
+
children: [
|
|
1615
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1616
|
+
recharts.CartesianGrid,
|
|
1617
|
+
{
|
|
1618
|
+
strokeDasharray: "3 3",
|
|
1619
|
+
stroke: "rgba(148,163,184,0.2)"
|
|
1620
|
+
}
|
|
1621
|
+
),
|
|
1622
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1623
|
+
recharts.XAxis,
|
|
1624
|
+
{
|
|
1625
|
+
dataKey: "date",
|
|
1626
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1627
|
+
stroke: CHART_AXIS_LINE,
|
|
1628
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1629
|
+
tickFormatter: (value) => {
|
|
1630
|
+
const row = dailyOrders.find((r) => r.date === value);
|
|
1631
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
),
|
|
1635
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1636
|
+
recharts.YAxis,
|
|
1637
|
+
{
|
|
1638
|
+
domain: [0, 100],
|
|
1639
|
+
tick: CHART_AXIS_TICK_SM,
|
|
1640
|
+
stroke: CHART_AXIS_LINE,
|
|
1641
|
+
tickLine: { stroke: CHART_AXIS_LINE },
|
|
1642
|
+
width: 36,
|
|
1643
|
+
tickFormatter: (v) => `${Math.round(Number(v) || 0)}%`
|
|
1644
|
+
}
|
|
1645
|
+
),
|
|
1646
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1647
|
+
recharts.Tooltip,
|
|
1648
|
+
{
|
|
1649
|
+
content: ({ active, payload, label }) => {
|
|
1650
|
+
var _a2;
|
|
1651
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1652
|
+
TooltipCard,
|
|
1653
|
+
{
|
|
1654
|
+
title: typeof label === "string" ? ((_a2 = dailyOrders.find((r) => r.date === label)) == null ? void 0 : _a2.label) ?? formatChartLabel(label) : String(label),
|
|
1655
|
+
children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1656
|
+
String(p.name),
|
|
1657
|
+
":",
|
|
1658
|
+
" ",
|
|
1659
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatPercent(Number(p.value) || 0) })
|
|
1660
|
+
] }, String(p.dataKey)))
|
|
1661
|
+
}
|
|
1662
|
+
) : null;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
),
|
|
1666
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1667
|
+
recharts.Legend,
|
|
1668
|
+
{
|
|
1669
|
+
wrapperStyle: { fontSize: 9, color: "#e5e7eb", paddingTop: 4 },
|
|
1670
|
+
iconType: "circle",
|
|
1671
|
+
iconSize: 6
|
|
1672
|
+
}
|
|
1673
|
+
),
|
|
1674
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1675
|
+
recharts.Line,
|
|
1676
|
+
{
|
|
1677
|
+
type: "natural",
|
|
1678
|
+
dataKey: "pct_not_cancelled",
|
|
1679
|
+
name: "Not cancelled / placed",
|
|
1680
|
+
stroke: "#6366F1",
|
|
1681
|
+
strokeWidth: 2,
|
|
1682
|
+
dot: false
|
|
1683
|
+
}
|
|
1684
|
+
),
|
|
1685
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1686
|
+
recharts.Line,
|
|
1687
|
+
{
|
|
1688
|
+
type: "natural",
|
|
1689
|
+
dataKey: "pct_shipped_of_active",
|
|
1690
|
+
name: "Shipped / not cancelled",
|
|
1691
|
+
stroke: "#F59E0B",
|
|
1692
|
+
strokeWidth: 2,
|
|
1693
|
+
dot: false
|
|
1694
|
+
}
|
|
1695
|
+
),
|
|
1696
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1697
|
+
recharts.Line,
|
|
1698
|
+
{
|
|
1699
|
+
type: "natural",
|
|
1700
|
+
dataKey: "pct_delivered_of_shipped",
|
|
1701
|
+
name: "Delivered / shipped",
|
|
1702
|
+
stroke: "#10B981",
|
|
1703
|
+
strokeWidth: 2,
|
|
1704
|
+
dot: false
|
|
1705
|
+
}
|
|
1706
|
+
)
|
|
1707
|
+
]
|
|
1708
|
+
}
|
|
1709
|
+
) }) })
|
|
1710
|
+
] })
|
|
1711
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1712
|
+
EmptyAnalyticsPanel,
|
|
1713
|
+
{
|
|
1714
|
+
title: "No funnel data",
|
|
1715
|
+
description: "No orders in this range to chart."
|
|
1716
|
+
}
|
|
1717
|
+
)
|
|
1718
|
+
}
|
|
1719
|
+
),
|
|
1720
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1721
|
+
AnalyticsSection,
|
|
1722
|
+
{
|
|
1723
|
+
variant: "atlas",
|
|
1724
|
+
title: "Order revenue (attribution)",
|
|
1725
|
+
description: "All revenue is from captured Medusa orders until storefront channel data exists.",
|
|
1726
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) : ordersInsights && ordersInsights.traffic.length > 0 ? trafficTotalRevenue <= 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted px-0.5 text-center text-[11px] leading-snug", children: "No recorded revenue in this window (totals may be zero until orders are paid or finalized)." }) : /* @__PURE__ */ jsxRuntime.jsx(TrafficRevenueDonut, { traffic: ordersInsights.traffic }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1727
|
+
EmptyAnalyticsPanel,
|
|
1728
|
+
{
|
|
1729
|
+
title: "No order revenue",
|
|
1730
|
+
description: "No orders in this window."
|
|
1731
|
+
}
|
|
1732
|
+
)
|
|
236
1733
|
}
|
|
237
1734
|
)
|
|
238
1735
|
] })
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
),
|
|
256
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
257
|
-
AnalyticsStatCard,
|
|
258
|
-
{
|
|
259
|
-
label: "Average order value",
|
|
260
|
-
value: formatCurrency$1(data.aov)
|
|
261
|
-
}
|
|
262
|
-
),
|
|
263
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
264
|
-
AnalyticsStatCard,
|
|
265
|
-
{
|
|
266
|
-
label: "Orders today",
|
|
267
|
-
value: data.ordersToday.toLocaleString()
|
|
268
|
-
}
|
|
269
|
-
),
|
|
270
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
271
|
-
AnalyticsStatCard,
|
|
272
|
-
{
|
|
273
|
-
label: "Pending orders",
|
|
274
|
-
value: data.pendingOrders.toLocaleString()
|
|
275
|
-
}
|
|
276
|
-
),
|
|
277
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
278
|
-
AnalyticsStatCard,
|
|
279
|
-
{
|
|
280
|
-
label: "Avg. fulfillment time",
|
|
281
|
-
value: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "—",
|
|
282
|
-
helper: "Measured in hours"
|
|
283
|
-
}
|
|
284
|
-
),
|
|
285
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
286
|
-
AnalyticsStatCard,
|
|
287
|
-
{
|
|
288
|
-
label: "Cancelled orders",
|
|
289
|
-
value: data.cancelledOrders.toLocaleString()
|
|
290
|
-
}
|
|
291
|
-
),
|
|
292
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
293
|
-
AnalyticsStatCard,
|
|
294
|
-
{
|
|
295
|
-
label: "Delivered orders",
|
|
296
|
-
value: data.deliveredOrders.toLocaleString()
|
|
297
|
-
}
|
|
298
|
-
),
|
|
299
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
300
|
-
AnalyticsStatCard,
|
|
301
|
-
{
|
|
302
|
-
label: "Exchange orders",
|
|
303
|
-
value: data.exchangeOrders.toLocaleString()
|
|
304
|
-
}
|
|
305
|
-
),
|
|
306
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
307
|
-
AnalyticsStatCard,
|
|
308
|
-
{
|
|
309
|
-
label: "Return orders",
|
|
310
|
-
value: data.returnOrders.toLocaleString()
|
|
311
|
-
}
|
|
312
|
-
)
|
|
313
|
-
] }),
|
|
314
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 xl:grid-cols-2", children: [
|
|
315
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsSection, { title: "Orders by status", children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[300px] items-center justify-center", children: pieData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: 280, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 8, right: 8, bottom: 8, left: 8 }, children: [
|
|
1736
|
+
] }),
|
|
1737
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-1.5", children: [
|
|
1738
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Operational signals" }) }),
|
|
1739
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 lg:grid-cols-3 xl:grid-cols-6", children: secondaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1740
|
+
AnalyticsStatCard,
|
|
1741
|
+
{
|
|
1742
|
+
label: stat.label,
|
|
1743
|
+
value: stat.value,
|
|
1744
|
+
helper: stat.helper,
|
|
1745
|
+
variant: "compact"
|
|
1746
|
+
},
|
|
1747
|
+
stat.label
|
|
1748
|
+
)) })
|
|
1749
|
+
] }),
|
|
1750
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-1 items-start gap-2 xl:grid-cols-12", children: [
|
|
316
1751
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
317
|
-
|
|
1752
|
+
AnalyticsSection,
|
|
318
1753
|
{
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
1754
|
+
variant: "atlas",
|
|
1755
|
+
className: "xl:col-span-7",
|
|
1756
|
+
title: "Order outcomes",
|
|
1757
|
+
description: "Delivered, cancelled, exchanges, and returns per bucket — same window as Revenue & orders.",
|
|
1758
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[176px] sm:h-[196px]", children: overTimeLoading ? /* @__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…" }) }) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: overTimeError }) }) : !outcomesTimeSeriesHasPoints ? /* @__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 outcome events in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1759
|
+
recharts.LineChart,
|
|
1760
|
+
{
|
|
1761
|
+
data: dailyOrders,
|
|
1762
|
+
margin: { top: 4, right: 8, left: 0, bottom: 0 },
|
|
1763
|
+
children: [
|
|
1764
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1765
|
+
recharts.CartesianGrid,
|
|
1766
|
+
{
|
|
1767
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1768
|
+
strokeDasharray: "3 3",
|
|
1769
|
+
vertical: false
|
|
1770
|
+
}
|
|
1771
|
+
),
|
|
1772
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1773
|
+
recharts.XAxis,
|
|
1774
|
+
{
|
|
1775
|
+
dataKey: "date",
|
|
1776
|
+
tick: CHART_AXIS_TICK,
|
|
1777
|
+
tickLine: false,
|
|
1778
|
+
axisLine: false,
|
|
1779
|
+
tickFormatter: (value) => {
|
|
1780
|
+
const dateStr = typeof value === "string" ? value : String(value);
|
|
1781
|
+
const row = dailyOrders.find((d) => d.date === dateStr);
|
|
1782
|
+
return (row == null ? void 0 : row.label) ?? formatChartLabel(dateStr);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
),
|
|
1786
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1787
|
+
recharts.YAxis,
|
|
1788
|
+
{
|
|
1789
|
+
width: 28,
|
|
1790
|
+
tick: CHART_AXIS_TICK,
|
|
1791
|
+
tickLine: false,
|
|
1792
|
+
axisLine: false,
|
|
1793
|
+
allowDecimals: false,
|
|
1794
|
+
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1795
|
+
}
|
|
1796
|
+
),
|
|
1797
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1798
|
+
recharts.Tooltip,
|
|
1799
|
+
{
|
|
1800
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(OutcomesTrendTooltip, {}),
|
|
1801
|
+
cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 }
|
|
1802
|
+
}
|
|
1803
|
+
),
|
|
1804
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1805
|
+
recharts.Legend,
|
|
1806
|
+
{
|
|
1807
|
+
wrapperStyle: { fontSize: 9, paddingTop: 0 },
|
|
1808
|
+
iconType: "circle",
|
|
1809
|
+
iconSize: 6
|
|
1810
|
+
}
|
|
1811
|
+
),
|
|
1812
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1813
|
+
recharts.Line,
|
|
1814
|
+
{
|
|
1815
|
+
type: "natural",
|
|
1816
|
+
dataKey: "delivered_count",
|
|
1817
|
+
name: "Delivered",
|
|
1818
|
+
stroke: STATUS_BAR_COLORS.delivered,
|
|
1819
|
+
strokeWidth: 2,
|
|
1820
|
+
dot: false
|
|
1821
|
+
}
|
|
1822
|
+
),
|
|
1823
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1824
|
+
recharts.Line,
|
|
1825
|
+
{
|
|
1826
|
+
type: "natural",
|
|
1827
|
+
dataKey: "cancelled_count",
|
|
1828
|
+
name: "Cancelled",
|
|
1829
|
+
stroke: STATUS_BAR_COLORS.cancelled,
|
|
1830
|
+
strokeWidth: 2,
|
|
1831
|
+
dot: false
|
|
1832
|
+
}
|
|
1833
|
+
),
|
|
1834
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1835
|
+
recharts.Line,
|
|
1836
|
+
{
|
|
1837
|
+
type: "natural",
|
|
1838
|
+
dataKey: "exchange_count",
|
|
1839
|
+
name: "Exchanges",
|
|
1840
|
+
stroke: STATUS_BAR_COLORS.exchange,
|
|
1841
|
+
strokeWidth: 2,
|
|
1842
|
+
dot: false
|
|
1843
|
+
}
|
|
1844
|
+
),
|
|
1845
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1846
|
+
recharts.Line,
|
|
1847
|
+
{
|
|
1848
|
+
type: "natural",
|
|
1849
|
+
dataKey: "return_count",
|
|
1850
|
+
name: "Returns",
|
|
1851
|
+
stroke: STATUS_BAR_COLORS.return,
|
|
1852
|
+
strokeWidth: 2,
|
|
1853
|
+
dot: false
|
|
1854
|
+
}
|
|
1855
|
+
)
|
|
1856
|
+
]
|
|
1857
|
+
}
|
|
1858
|
+
) }) }) })
|
|
329
1859
|
}
|
|
330
1860
|
),
|
|
331
1861
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
332
|
-
|
|
1862
|
+
AnalyticsSection,
|
|
333
1863
|
{
|
|
334
|
-
|
|
1864
|
+
variant: "atlas",
|
|
1865
|
+
className: "xl:col-span-5",
|
|
1866
|
+
title: "Recent orders (7 days)",
|
|
1867
|
+
description: "Daily order count vs 7-day average — time series.",
|
|
1868
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1869
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [
|
|
1870
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 px-2 py-2", children: [
|
|
1871
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Today" }),
|
|
1872
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-base font-semibold tracking-tight", children: (todayPoint == null ? void 0 : todayPoint.orders_count.toLocaleString()) ?? "0" })
|
|
1873
|
+
] }),
|
|
1874
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 px-2 py-2", children: [
|
|
1875
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "7-day avg" }),
|
|
1876
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-base font-semibold tracking-tight", children: todayOrdersAverage.toFixed(1) })
|
|
1877
|
+
] })
|
|
1878
|
+
] }),
|
|
1879
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[140px] sm:h-[156px]", children: todayContextLoading ? /* @__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…" }) }) : todayContextError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: todayContextError }) }) : todayChartData.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 recent activity." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1880
|
+
recharts.ComposedChart,
|
|
1881
|
+
{
|
|
1882
|
+
data: todayChartData,
|
|
1883
|
+
margin: { top: 4, right: 4, left: -4, bottom: 0 },
|
|
1884
|
+
children: [
|
|
1885
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "todayOrdersFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1886
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#3b82f6", stopOpacity: 0.2 }),
|
|
1887
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3b82f6", stopOpacity: 0 })
|
|
1888
|
+
] }) }),
|
|
1889
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1890
|
+
recharts.CartesianGrid,
|
|
1891
|
+
{
|
|
1892
|
+
stroke: "rgba(148,163,184,0.14)",
|
|
1893
|
+
strokeDasharray: "3 3",
|
|
1894
|
+
vertical: false
|
|
1895
|
+
}
|
|
1896
|
+
),
|
|
1897
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1898
|
+
recharts.XAxis,
|
|
1899
|
+
{
|
|
1900
|
+
dataKey: "date",
|
|
1901
|
+
tick: CHART_AXIS_TICK,
|
|
1902
|
+
tickLine: false,
|
|
1903
|
+
axisLine: false,
|
|
1904
|
+
tickFormatter: formatChartLabel
|
|
1905
|
+
}
|
|
1906
|
+
),
|
|
1907
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1908
|
+
recharts.YAxis,
|
|
1909
|
+
{
|
|
1910
|
+
width: 28,
|
|
1911
|
+
tick: CHART_AXIS_TICK,
|
|
1912
|
+
tickLine: false,
|
|
1913
|
+
axisLine: false,
|
|
1914
|
+
allowDecimals: false,
|
|
1915
|
+
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1916
|
+
}
|
|
1917
|
+
),
|
|
1918
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersTodayTooltip, {}), cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 } }),
|
|
1919
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1920
|
+
recharts.ReferenceLine,
|
|
1921
|
+
{
|
|
1922
|
+
y: todayOrdersAverage,
|
|
1923
|
+
stroke: "rgba(148,163,184,0.65)",
|
|
1924
|
+
strokeDasharray: "4 4"
|
|
1925
|
+
}
|
|
1926
|
+
),
|
|
1927
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1928
|
+
recharts.Area,
|
|
1929
|
+
{
|
|
1930
|
+
type: "natural",
|
|
1931
|
+
dataKey: "orders_count",
|
|
1932
|
+
stroke: "none",
|
|
1933
|
+
fill: "url(#todayOrdersFill)",
|
|
1934
|
+
isAnimationActive: false
|
|
1935
|
+
}
|
|
1936
|
+
),
|
|
1937
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1938
|
+
recharts.Line,
|
|
1939
|
+
{
|
|
1940
|
+
type: "natural",
|
|
1941
|
+
dataKey: "orders_count",
|
|
1942
|
+
name: "Orders",
|
|
1943
|
+
stroke: "#3b82f6",
|
|
1944
|
+
strokeWidth: 2,
|
|
1945
|
+
dot: (props) => {
|
|
1946
|
+
const { cx, cy, payload } = props;
|
|
1947
|
+
if (payload && typeof payload === "object" && "isToday" in payload && payload.isToday) {
|
|
1948
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1949
|
+
"circle",
|
|
1950
|
+
{
|
|
1951
|
+
cx,
|
|
1952
|
+
cy,
|
|
1953
|
+
r: 4,
|
|
1954
|
+
fill: STATUS_BAR_COLORS.today,
|
|
1955
|
+
stroke: "var(--medusa-color-ui-bg-base)",
|
|
1956
|
+
strokeWidth: 1
|
|
1957
|
+
}
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
return /* @__PURE__ */ jsxRuntime.jsx("circle", { cx, cy, r: 0, fill: "transparent" });
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
)
|
|
1964
|
+
]
|
|
1965
|
+
}
|
|
1966
|
+
) }) }),
|
|
1967
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/40 px-2 py-1.5 shadow-sm", children: [
|
|
1968
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Note" }),
|
|
1969
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-[11px] leading-snug", children: todayDelta === null ? "Compares the latest day to the 7-day mean." : todayDelta >= 0 ? `Latest day is ${todayDelta.toFixed(1)} above the 7-day average.` : `Latest day is ${Math.abs(todayDelta).toFixed(1)} below the 7-day average.` })
|
|
1970
|
+
] })
|
|
1971
|
+
] }) })
|
|
335
1972
|
}
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsSection, { title: "Returns & exchanges", children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[300px] items-center justify-center", children: returnsExchangesData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: 280, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 8, right: 8, bottom: 8, left: 8 }, children: [
|
|
1973
|
+
)
|
|
1974
|
+
] }),
|
|
1975
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-1 items-start gap-2 md:grid-cols-3", children: [
|
|
340
1976
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
341
|
-
|
|
1977
|
+
AnalyticsSection,
|
|
342
1978
|
{
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
1979
|
+
variant: "atlas",
|
|
1980
|
+
title: "Sales by weekday",
|
|
1981
|
+
description: `Order revenue by UTC weekday — last ${insightsWindowDays} days.`,
|
|
1982
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) }) : weekdayChartRows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(176px,28vh)] min-h-[148px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1983
|
+
recharts.BarChart,
|
|
1984
|
+
{
|
|
1985
|
+
data: weekdayChartRows,
|
|
1986
|
+
margin: { top: 2, right: 4, left: -8, bottom: 2 },
|
|
1987
|
+
children: [
|
|
1988
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1989
|
+
recharts.CartesianGrid,
|
|
1990
|
+
{
|
|
1991
|
+
strokeDasharray: "3 3",
|
|
1992
|
+
vertical: false,
|
|
1993
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
1994
|
+
}
|
|
1995
|
+
),
|
|
1996
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1997
|
+
recharts.XAxis,
|
|
1998
|
+
{
|
|
1999
|
+
dataKey: "label",
|
|
2000
|
+
tick: CHART_AXIS_TICK
|
|
2001
|
+
}
|
|
2002
|
+
),
|
|
2003
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2004
|
+
recharts.YAxis,
|
|
2005
|
+
{
|
|
2006
|
+
width: 40,
|
|
2007
|
+
tick: CHART_AXIS_TICK_SM,
|
|
2008
|
+
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
2009
|
+
}
|
|
2010
|
+
),
|
|
2011
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2012
|
+
recharts.Tooltip,
|
|
2013
|
+
{
|
|
2014
|
+
content: ({ active, payload, label }) => {
|
|
2015
|
+
var _a2, _b2;
|
|
2016
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
2017
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2018
|
+
"Revenue:",
|
|
2019
|
+
" ",
|
|
2020
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
2021
|
+
Number(
|
|
2022
|
+
(_a2 = payload.find((p) => p.dataKey === "revenue")) == null ? void 0 : _a2.value
|
|
2023
|
+
) || 0
|
|
2024
|
+
) })
|
|
2025
|
+
] }),
|
|
2026
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2027
|
+
"Orders:",
|
|
2028
|
+
" ",
|
|
2029
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
2030
|
+
Number(
|
|
2031
|
+
(_b2 = payload.find((p) => p.dataKey === "orders_count")) == null ? void 0 : _b2.value
|
|
2032
|
+
) || 0
|
|
2033
|
+
).toLocaleString() })
|
|
2034
|
+
] })
|
|
2035
|
+
] }) : null;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
),
|
|
2039
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "revenue", name: "Revenue", fill: "#D946EF", radius: [4, 4, 0, 0] })
|
|
2040
|
+
]
|
|
2041
|
+
}
|
|
2042
|
+
) }) }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2043
|
+
EmptyAnalyticsPanel,
|
|
2044
|
+
{
|
|
2045
|
+
title: "No weekday data",
|
|
2046
|
+
description: "No orders in this window."
|
|
2047
|
+
}
|
|
2048
|
+
)
|
|
353
2049
|
}
|
|
354
2050
|
),
|
|
355
2051
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
356
|
-
|
|
2052
|
+
AnalyticsSection,
|
|
357
2053
|
{
|
|
358
|
-
|
|
2054
|
+
variant: "atlas",
|
|
2055
|
+
title: "Placed orders vs drafts",
|
|
2056
|
+
description: "Window order count vs current open draft orders (admin). Not storefront cart abandonment.",
|
|
2057
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) }) : ordersVsDraftsPie.some((s) => s.value > 0) ? /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
2058
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(176px,28vh)] min-h-[148px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { margin: { top: 2, bottom: 2, left: 2, right: 2 }, children: [
|
|
2059
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2060
|
+
recharts.Pie,
|
|
2061
|
+
{
|
|
2062
|
+
data: ordersVsDraftsPie,
|
|
2063
|
+
dataKey: "value",
|
|
2064
|
+
nameKey: "name",
|
|
2065
|
+
cx: "50%",
|
|
2066
|
+
cy: "46%",
|
|
2067
|
+
innerRadius: 40,
|
|
2068
|
+
outerRadius: 58,
|
|
2069
|
+
paddingAngle: 2,
|
|
2070
|
+
children: ordersVsDraftsPie.map((slice, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2071
|
+
recharts.Cell,
|
|
2072
|
+
{
|
|
2073
|
+
fill: PIE_PALETTE[i % PIE_PALETTE.length]
|
|
2074
|
+
},
|
|
2075
|
+
slice.key
|
|
2076
|
+
))
|
|
2077
|
+
}
|
|
2078
|
+
),
|
|
2079
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2080
|
+
recharts.Tooltip,
|
|
2081
|
+
{
|
|
2082
|
+
content: ({ active, payload }) => {
|
|
2083
|
+
var _a2, _b2;
|
|
2084
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(TooltipCard, { title: String(((_a2 = payload[0]) == null ? void 0 : _a2.name) ?? ""), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2085
|
+
"Count:",
|
|
2086
|
+
" ",
|
|
2087
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
2088
|
+
Number((_b2 = payload[0]) == null ? void 0 : _b2.value) || 0
|
|
2089
|
+
).toLocaleString() })
|
|
2090
|
+
] }) }) : null;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
),
|
|
2094
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2095
|
+
recharts.Legend,
|
|
2096
|
+
{
|
|
2097
|
+
wrapperStyle: { fontSize: 9, paddingTop: 0 },
|
|
2098
|
+
verticalAlign: "bottom",
|
|
2099
|
+
height: 22,
|
|
2100
|
+
iconType: "circle"
|
|
2101
|
+
}
|
|
2102
|
+
)
|
|
2103
|
+
] }) }) }),
|
|
2104
|
+
(ordersInsights == null ? void 0 : ordersInsights.drafts.available) === false ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted mt-0.5 px-0.5 text-[10px] leading-snug", children: "Draft totals need draft_order remote query access." }) : null
|
|
2105
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2106
|
+
EmptyAnalyticsPanel,
|
|
2107
|
+
{
|
|
2108
|
+
title: "Nothing to compare",
|
|
2109
|
+
description: "No placed orders and no draft rows in this snapshot."
|
|
2110
|
+
}
|
|
2111
|
+
)
|
|
359
2112
|
}
|
|
360
2113
|
),
|
|
361
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
362
|
-
|
|
363
|
-
] }),
|
|
364
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
365
|
-
AnalyticsSection,
|
|
366
|
-
{
|
|
367
|
-
title: "Orders over time",
|
|
368
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
369
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "over-time-period", className: "text-ui-fg-muted text-sm", children: "Range" }),
|
|
370
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
371
|
-
"select",
|
|
372
|
-
{
|
|
373
|
-
id: "over-time-period",
|
|
374
|
-
value: overTimePeriod,
|
|
375
|
-
onChange: (e) => setOverTimePeriod(e.target.value),
|
|
376
|
-
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
377
|
-
children: OVER_TIME_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
378
|
-
}
|
|
379
|
-
)
|
|
380
|
-
] }),
|
|
381
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
382
|
-
"div",
|
|
383
|
-
{
|
|
384
|
-
className: "flex items-center justify-center h-full",
|
|
385
|
-
role: "status",
|
|
386
|
-
"aria-label": "Loading orders over time",
|
|
387
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
|
|
388
|
-
}
|
|
389
|
-
) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: overTimeError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
390
|
-
recharts.LineChart,
|
|
2114
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2115
|
+
AnalyticsSection,
|
|
391
2116
|
{
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
2117
|
+
variant: "atlas",
|
|
2118
|
+
title: "Sales by hour (UTC)",
|
|
2119
|
+
description: `Orders created by hour — last ${insightsWindowDays} days.`,
|
|
2120
|
+
children: insightsLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : insightsError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[100px] items-center justify-center px-2 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: insightsError }) }) }) : hourlyChartRows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(176px,28vh)] min-h-[148px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2121
|
+
recharts.BarChart,
|
|
2122
|
+
{
|
|
2123
|
+
data: hourlyChartRows,
|
|
2124
|
+
margin: { top: 2, right: 4, left: -8, bottom: 2 },
|
|
2125
|
+
children: [
|
|
2126
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2127
|
+
recharts.CartesianGrid,
|
|
2128
|
+
{
|
|
2129
|
+
strokeDasharray: "3 3",
|
|
2130
|
+
vertical: false,
|
|
2131
|
+
stroke: "rgba(148,163,184,0.12)"
|
|
2132
|
+
}
|
|
2133
|
+
),
|
|
2134
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2135
|
+
recharts.XAxis,
|
|
2136
|
+
{
|
|
2137
|
+
dataKey: "label",
|
|
2138
|
+
interval: 3,
|
|
2139
|
+
tick: CHART_AXIS_TICK_SM
|
|
2140
|
+
}
|
|
2141
|
+
),
|
|
2142
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2143
|
+
recharts.YAxis,
|
|
2144
|
+
{
|
|
2145
|
+
width: 28,
|
|
2146
|
+
tick: CHART_AXIS_TICK,
|
|
2147
|
+
allowDecimals: false
|
|
2148
|
+
}
|
|
2149
|
+
),
|
|
2150
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2151
|
+
recharts.Tooltip,
|
|
2152
|
+
{
|
|
2153
|
+
content: ({ active, payload, label }) => {
|
|
2154
|
+
var _a2, _b2;
|
|
2155
|
+
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsxs(TooltipCard, { title: String(label), children: [
|
|
2156
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2157
|
+
"Orders:",
|
|
2158
|
+
" ",
|
|
2159
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
2160
|
+
Number(
|
|
2161
|
+
(_a2 = payload.find((p) => p.dataKey === "orders_count")) == null ? void 0 : _a2.value
|
|
2162
|
+
) || 0
|
|
2163
|
+
).toLocaleString() })
|
|
2164
|
+
] }),
|
|
2165
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2166
|
+
"Revenue:",
|
|
2167
|
+
" ",
|
|
2168
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency$1(
|
|
2169
|
+
Number(
|
|
2170
|
+
(_b2 = payload.find((p) => p.dataKey === "revenue")) == null ? void 0 : _b2.value
|
|
2171
|
+
) || 0
|
|
2172
|
+
) })
|
|
2173
|
+
] })
|
|
2174
|
+
] }) : null;
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
),
|
|
2178
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "orders_count", name: "Orders", fill: "#38BDF8", radius: [4, 4, 0, 0] })
|
|
2179
|
+
]
|
|
2180
|
+
}
|
|
2181
|
+
) }) }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2182
|
+
EmptyAnalyticsPanel,
|
|
2183
|
+
{
|
|
2184
|
+
title: "No hourly data",
|
|
2185
|
+
description: "No orders in this window."
|
|
2186
|
+
}
|
|
2187
|
+
)
|
|
442
2188
|
}
|
|
443
|
-
)
|
|
444
|
-
}
|
|
445
|
-
)
|
|
446
|
-
] });
|
|
2189
|
+
)
|
|
2190
|
+
] })
|
|
2191
|
+
] })
|
|
2192
|
+
] }) });
|
|
447
2193
|
}
|
|
448
2194
|
function CustomersOverTimeTooltip({
|
|
449
2195
|
active,
|
|
@@ -483,6 +2229,17 @@ function CustomersOverTimeTooltip({
|
|
|
483
2229
|
}
|
|
484
2230
|
);
|
|
485
2231
|
}
|
|
2232
|
+
function MixPieTooltip({
|
|
2233
|
+
active,
|
|
2234
|
+
payload
|
|
2235
|
+
}) {
|
|
2236
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2237
|
+
const row = payload[0];
|
|
2238
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-base px-3 py-2 text-sm shadow-md", children: [
|
|
2239
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium text-ui-fg-base", children: row.name }),
|
|
2240
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: Math.floor(Number(row.value) || 0).toLocaleString() })
|
|
2241
|
+
] });
|
|
2242
|
+
}
|
|
486
2243
|
const COUNT_DAY_PRESETS = [
|
|
487
2244
|
{ value: "all", label: "All time" },
|
|
488
2245
|
{ value: "0", label: "Today" },
|
|
@@ -506,12 +2263,15 @@ function CustomersDashboard() {
|
|
|
506
2263
|
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
507
2264
|
const [graphLoading, setGraphLoading] = react.useState(true);
|
|
508
2265
|
const [graphError, setGraphError] = react.useState(null);
|
|
2266
|
+
const [repeatStats, setRepeatStats] = react.useState(null);
|
|
2267
|
+
const [repeatLoading, setRepeatLoading] = react.useState(true);
|
|
2268
|
+
const [repeatError, setRepeatError] = react.useState(null);
|
|
509
2269
|
react.useEffect(() => {
|
|
510
2270
|
let cancelled = false;
|
|
511
2271
|
setLoading(true);
|
|
512
2272
|
setError(null);
|
|
513
2273
|
const url = `/admin/analytics/customers-summary?days=${countDays}`;
|
|
514
|
-
fetch(url).then((res) => {
|
|
2274
|
+
fetch(url, { credentials: "include" }).then((res) => {
|
|
515
2275
|
if (!res.ok) throw new Error(res.statusText);
|
|
516
2276
|
return res.json();
|
|
517
2277
|
}).then((body) => {
|
|
@@ -530,7 +2290,7 @@ function CustomersDashboard() {
|
|
|
530
2290
|
setGraphLoading(true);
|
|
531
2291
|
setGraphError(null);
|
|
532
2292
|
const url = `/admin/analytics/customers-over-time?period=${graphPeriod}`;
|
|
533
|
-
fetch(url).then((res) => {
|
|
2293
|
+
fetch(url, { credentials: "include" }).then((res) => {
|
|
534
2294
|
if (!res.ok) throw new Error(res.statusText);
|
|
535
2295
|
return res.json();
|
|
536
2296
|
}).then((body) => {
|
|
@@ -544,6 +2304,34 @@ function CustomersDashboard() {
|
|
|
544
2304
|
cancelled = true;
|
|
545
2305
|
};
|
|
546
2306
|
}, [graphPeriod]);
|
|
2307
|
+
react.useEffect(() => {
|
|
2308
|
+
let cancelled = false;
|
|
2309
|
+
setRepeatLoading(true);
|
|
2310
|
+
setRepeatError(null);
|
|
2311
|
+
fetch(`/admin/analytics/repeat-customers?days=${countDays}`, {
|
|
2312
|
+
credentials: "include"
|
|
2313
|
+
}).then((res) => {
|
|
2314
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
2315
|
+
return res.json();
|
|
2316
|
+
}).then((body) => {
|
|
2317
|
+
if (!cancelled) setRepeatStats(body);
|
|
2318
|
+
}).catch((e) => {
|
|
2319
|
+
if (!cancelled) setRepeatError(e instanceof Error ? e.message : String(e));
|
|
2320
|
+
}).finally(() => {
|
|
2321
|
+
if (!cancelled) setRepeatLoading(false);
|
|
2322
|
+
});
|
|
2323
|
+
return () => {
|
|
2324
|
+
cancelled = true;
|
|
2325
|
+
};
|
|
2326
|
+
}, [countDays]);
|
|
2327
|
+
const guestRegisteredPieData = react.useMemo(() => {
|
|
2328
|
+
const g = Math.max(0, Math.floor((data == null ? void 0 : data.guestCount) ?? 0));
|
|
2329
|
+
const r = Math.max(0, Math.floor((data == null ? void 0 : data.registeredCount) ?? 0));
|
|
2330
|
+
return [
|
|
2331
|
+
{ name: "Guest checkout", value: g, key: "guest" },
|
|
2332
|
+
{ name: "Registered", value: r, key: "registered" }
|
|
2333
|
+
].filter((s) => s.value > 0);
|
|
2334
|
+
}, [data]);
|
|
547
2335
|
if (loading) {
|
|
548
2336
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
549
2337
|
"div",
|
|
@@ -628,6 +2416,70 @@ function CustomersDashboard() {
|
|
|
628
2416
|
}
|
|
629
2417
|
)
|
|
630
2418
|
] }),
|
|
2419
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-2", children: [
|
|
2420
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2421
|
+
AnalyticsSection,
|
|
2422
|
+
{
|
|
2423
|
+
title: "Guest vs registered",
|
|
2424
|
+
description: "Customer mix for the selected count period (same filter as the summary cards).",
|
|
2425
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: guestRegisteredPieData.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-sm", children: "No guest or registered customers in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { children: [
|
|
2426
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2427
|
+
recharts.Pie,
|
|
2428
|
+
{
|
|
2429
|
+
data: guestRegisteredPieData,
|
|
2430
|
+
dataKey: "value",
|
|
2431
|
+
nameKey: "name",
|
|
2432
|
+
cx: "50%",
|
|
2433
|
+
cy: "50%",
|
|
2434
|
+
innerRadius: 52,
|
|
2435
|
+
outerRadius: 88,
|
|
2436
|
+
paddingAngle: 2,
|
|
2437
|
+
children: guestRegisteredPieData.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2438
|
+
recharts.Cell,
|
|
2439
|
+
{
|
|
2440
|
+
fill: entry.key === "guest" ? GUEST_COLOR : REGISTERED_COLOR
|
|
2441
|
+
},
|
|
2442
|
+
entry.key
|
|
2443
|
+
))
|
|
2444
|
+
}
|
|
2445
|
+
),
|
|
2446
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(MixPieTooltip, {}) }),
|
|
2447
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { wrapperStyle: { fontSize: 12 }, iconType: "circle" })
|
|
2448
|
+
] }) }) }) })
|
|
2449
|
+
}
|
|
2450
|
+
),
|
|
2451
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2452
|
+
AnalyticsSection,
|
|
2453
|
+
{
|
|
2454
|
+
title: "Repeat purchases",
|
|
2455
|
+
description: "Orders with a customer_id in the same window as the period filter (guest checkouts excluded from rate).",
|
|
2456
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: repeatLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[200px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Loading…" }) }) : repeatError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[200px] items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-sm", children: repeatError }) }) : repeatStats ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [
|
|
2457
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2458
|
+
AnalyticsStatCard,
|
|
2459
|
+
{
|
|
2460
|
+
label: "Repeat rate",
|
|
2461
|
+
value: `${repeatStats.repeat_rate_percent.toFixed(1)}%`,
|
|
2462
|
+
helper: repeatStats.window_days >= 365 ? "Approx. 12-month window" : `Last ${repeatStats.window_days} days`
|
|
2463
|
+
}
|
|
2464
|
+
),
|
|
2465
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2466
|
+
AnalyticsStatCard,
|
|
2467
|
+
{
|
|
2468
|
+
label: "Customers with orders",
|
|
2469
|
+
value: repeatStats.customers_with_orders.toLocaleString()
|
|
2470
|
+
}
|
|
2471
|
+
),
|
|
2472
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2473
|
+
AnalyticsStatCard,
|
|
2474
|
+
{
|
|
2475
|
+
label: "Repeat customers (2+)",
|
|
2476
|
+
value: repeatStats.repeat_customers.toLocaleString()
|
|
2477
|
+
}
|
|
2478
|
+
)
|
|
2479
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[200px] items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No data." }) }) })
|
|
2480
|
+
}
|
|
2481
|
+
)
|
|
2482
|
+
] }),
|
|
631
2483
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
632
2484
|
AnalyticsSection,
|
|
633
2485
|
{
|
|
@@ -1019,6 +2871,10 @@ function ProductsDashboard() {
|
|
|
1019
2871
|
})).reverse(),
|
|
1020
2872
|
[performance]
|
|
1021
2873
|
);
|
|
2874
|
+
const viewsVsUnitsScatterData = react.useMemo(
|
|
2875
|
+
() => ((performance == null ? void 0 : performance.topViewedProducts) ?? []).slice(0, 48).filter((row) => row.total_views > 0 || row.units_sold > 0),
|
|
2876
|
+
[performance]
|
|
2877
|
+
);
|
|
1022
2878
|
if (summaryLoading) {
|
|
1023
2879
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1024
2880
|
"div",
|
|
@@ -1364,7 +3220,66 @@ function ProductsDashboard() {
|
|
|
1364
3220
|
] }) })
|
|
1365
3221
|
}
|
|
1366
3222
|
)
|
|
1367
|
-
] })
|
|
3223
|
+
] }),
|
|
3224
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3225
|
+
AnalyticsSection,
|
|
3226
|
+
{
|
|
3227
|
+
title: "Views vs units sold",
|
|
3228
|
+
description: "Each point is a product (top viewed in this period). Requires product view tracking.",
|
|
3229
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-80", 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", children: "Loading scatter…" }) }) : performanceError || !performance ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", 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", 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", 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: [
|
|
3230
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: "rgba(148,163,184,0.2)" }),
|
|
3231
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3232
|
+
recharts.XAxis,
|
|
3233
|
+
{
|
|
3234
|
+
type: "number",
|
|
3235
|
+
dataKey: "total_views",
|
|
3236
|
+
name: "Views",
|
|
3237
|
+
tick: { fontSize: 11 },
|
|
3238
|
+
allowDecimals: false,
|
|
3239
|
+
label: {
|
|
3240
|
+
value: "Views",
|
|
3241
|
+
position: "insideBottom",
|
|
3242
|
+
offset: -4,
|
|
3243
|
+
fontSize: 11,
|
|
3244
|
+
fill: "#6b7280"
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
),
|
|
3248
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3249
|
+
recharts.YAxis,
|
|
3250
|
+
{
|
|
3251
|
+
type: "number",
|
|
3252
|
+
dataKey: "units_sold",
|
|
3253
|
+
name: "Units sold",
|
|
3254
|
+
tick: { fontSize: 11 },
|
|
3255
|
+
allowDecimals: false,
|
|
3256
|
+
label: {
|
|
3257
|
+
value: "Units sold",
|
|
3258
|
+
angle: -90,
|
|
3259
|
+
position: "insideLeft",
|
|
3260
|
+
fontSize: 11,
|
|
3261
|
+
fill: "#6b7280"
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
),
|
|
3265
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3266
|
+
recharts.Tooltip,
|
|
3267
|
+
{
|
|
3268
|
+
cursor: { strokeDasharray: "4 4" },
|
|
3269
|
+
content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {})
|
|
3270
|
+
}
|
|
3271
|
+
),
|
|
3272
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3273
|
+
recharts.Scatter,
|
|
3274
|
+
{
|
|
3275
|
+
name: "Products",
|
|
3276
|
+
data: viewsVsUnitsScatterData,
|
|
3277
|
+
fill: VIEWS_COLOR
|
|
3278
|
+
}
|
|
3279
|
+
)
|
|
3280
|
+
] }) }) }) })
|
|
3281
|
+
}
|
|
3282
|
+
)
|
|
1368
3283
|
] });
|
|
1369
3284
|
}
|
|
1370
3285
|
const ANALYTICS_MODULES = [
|
|
@@ -1381,16 +3296,29 @@ const AnalyticsPage = () => {
|
|
|
1381
3296
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "tracking-tight", children: "Analytics" }),
|
|
1382
3297
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Review orders, customers, and products in one organized workspace." })
|
|
1383
3298
|
] }),
|
|
1384
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1385
|
-
|
|
3299
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3300
|
+
"div",
|
|
1386
3301
|
{
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
children: m
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
3302
|
+
className: "flex flex-wrap items-center gap-1 rounded-2xl border border-ui-border-base bg-ui-bg-subtle p-1",
|
|
3303
|
+
role: "tablist",
|
|
3304
|
+
"aria-label": "Analytics modules",
|
|
3305
|
+
children: ANALYTICS_MODULES.map((m) => {
|
|
3306
|
+
const active = activeModule === m.id;
|
|
3307
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3308
|
+
"button",
|
|
3309
|
+
{
|
|
3310
|
+
type: "button",
|
|
3311
|
+
role: "tab",
|
|
3312
|
+
"aria-selected": active,
|
|
3313
|
+
onClick: () => setActiveModule(m.id),
|
|
3314
|
+
className: `rounded-xl px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ui-fg-interactive focus-visible:ring-offset-2 focus-visible:ring-offset-ui-bg-base ${active ? "bg-ui-bg-base text-ui-fg-base shadow-sm" : "text-ui-fg-muted hover:bg-ui-bg-base/60 hover:text-ui-fg-base"}`.trim(),
|
|
3315
|
+
children: m.label
|
|
3316
|
+
},
|
|
3317
|
+
m.id
|
|
3318
|
+
);
|
|
3319
|
+
})
|
|
3320
|
+
}
|
|
3321
|
+
)
|
|
1394
3322
|
] }),
|
|
1395
3323
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1 bg-ui-bg-subtle" })
|
|
1396
3324
|
] }),
|