medusa-analytics 0.0.24 → 0.0.25
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 +499 -4076
- package/.medusa/server/src/admin/index.mjs +504 -4081
- package/.medusa/server/src/api/admin/analytics/customers-over-time/route.js +7 -14
- package/.medusa/server/src/api/admin/analytics/customers-summary/route.js +7 -14
- package/.medusa/server/src/api/admin/analytics/orders-over-time/route.js +100 -193
- 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 +38 -10
- package/README.md +255 -131
- package/package.json +2 -4
- package/.medusa/server/src/api/admin/analytics/fetch-orders-for-analytics.js +0 -129
- package/.medusa/server/src/api/admin/analytics/orders-analytics-helpers.js +0 -64
- package/.medusa/server/src/api/admin/analytics/orders-analytics-meta.js +0 -18
- package/.medusa/server/src/api/admin/analytics/orders-insights/route.js +0 -262
- package/.medusa/server/src/api/admin/analytics/orders-insights/types.js +0 -3
- package/.medusa/server/src/api/admin/analytics/products/shared.js +0 -719
- package/.medusa/server/src/api/admin/analytics/products-filters/route.js +0 -21
- package/.medusa/server/src/api/admin/analytics/products-filters/types.js +0 -3
- package/.medusa/server/src/api/admin/analytics/products-over-time/route.js +0 -28
- package/.medusa/server/src/api/admin/analytics/products-over-time/types.js +0 -3
- package/.medusa/server/src/api/admin/analytics/products-performance/route.js +0 -51
- package/.medusa/server/src/api/admin/analytics/products-performance/types.js +0 -3
- package/.medusa/server/src/api/admin/analytics/products-summary/route.js +0 -36
- package/.medusa/server/src/api/admin/analytics/products-summary/types.js +0 -3
- package/.medusa/server/src/api/admin/analytics/products-top-sellers/route.js +0 -48
- package/.medusa/server/src/api/admin/analytics/products-top-sellers/types.js +0 -3
- package/.medusa/server/src/api/admin/analytics/repeat-customers/route.js +0 -78
- package/.medusa/server/src/api/admin/analytics/repeat-customers/types.js +0 -3
- package/.medusa/server/src/api/admin/analytics/store-context/resolve-default-currency-code.js +0 -86
- package/.medusa/server/src/api/admin/analytics/store-context/route.js +0 -19
- package/.medusa/server/src/api/admin/analytics/store-context/types.js +0 -3
- package/index.js +0 -5
|
@@ -5,726 +5,58 @@ 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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return new Intl.NumberFormat(void 0, {
|
|
13
|
-
style: "currency",
|
|
14
|
-
currency: currencyCode,
|
|
15
|
-
minimumFractionDigits: 0,
|
|
16
|
-
maximumFractionDigits: compact ? 1 : 0,
|
|
17
|
-
...compact ? { notation: "compact" } : {}
|
|
18
|
-
}).format(value);
|
|
19
|
-
}
|
|
20
|
-
function AnalyticsCurrencyProvider({ children }) {
|
|
21
|
-
const [currencyCode, setCurrencyCode] = react.useState("USD");
|
|
22
|
-
react.useEffect(() => {
|
|
23
|
-
let cancelled = false;
|
|
24
|
-
fetch("/admin/analytics/store-context", { credentials: "include" }).then((res) => {
|
|
25
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
26
|
-
return res.json();
|
|
27
|
-
}).then((body) => {
|
|
28
|
-
if (cancelled) return;
|
|
29
|
-
const code = typeof body.default_currency_code === "string" ? body.default_currency_code.trim().toUpperCase() : "";
|
|
30
|
-
if (code && /^[A-Z]{3}$/.test(code)) {
|
|
31
|
-
setCurrencyCode(code);
|
|
32
|
-
}
|
|
33
|
-
}).catch(() => {
|
|
34
|
-
if (!cancelled) setCurrencyCode("USD");
|
|
35
|
-
});
|
|
36
|
-
return () => {
|
|
37
|
-
cancelled = true;
|
|
38
|
-
};
|
|
39
|
-
}, []);
|
|
40
|
-
const formatCurrency = react.useCallback(
|
|
41
|
-
(value2) => formatMoney(value2, currencyCode, false),
|
|
42
|
-
[currencyCode]
|
|
43
|
-
);
|
|
44
|
-
const formatCompactCurrency = react.useCallback(
|
|
45
|
-
(value2) => formatMoney(value2, currencyCode, true),
|
|
46
|
-
[currencyCode]
|
|
47
|
-
);
|
|
48
|
-
const value = react.useMemo(
|
|
49
|
-
() => ({
|
|
50
|
-
currencyCode,
|
|
51
|
-
formatCurrency,
|
|
52
|
-
formatCompactCurrency
|
|
53
|
-
}),
|
|
54
|
-
[currencyCode, formatCurrency, formatCompactCurrency]
|
|
55
|
-
);
|
|
56
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsCurrencyContext.Provider, { value, children });
|
|
57
|
-
}
|
|
58
|
-
function useAnalyticsCurrency() {
|
|
59
|
-
const ctx = react.useContext(AnalyticsCurrencyContext);
|
|
60
|
-
if (!ctx) {
|
|
61
|
-
throw new Error("useAnalyticsCurrency must be used within AnalyticsCurrencyProvider");
|
|
62
|
-
}
|
|
63
|
-
return ctx;
|
|
64
|
-
}
|
|
65
|
-
const ORDERS_ANALYTICS_MAX_FETCH = 5e4;
|
|
66
|
-
const ACCENT_STYLES = {
|
|
67
|
-
blue: {
|
|
68
|
-
border: "border-sky-400/30",
|
|
69
|
-
glow: "shadow-[0_0_0_1px_rgba(56,189,248,0.12),0_18px_40px_-28px_rgba(59,130,246,0.55)]",
|
|
70
|
-
band: "from-sky-400/70 via-blue-400/30 to-transparent"
|
|
71
|
-
},
|
|
72
|
-
purple: {
|
|
73
|
-
border: "border-fuchsia-400/25",
|
|
74
|
-
glow: "shadow-[0_0_0_1px_rgba(217,70,239,0.10),0_18px_40px_-28px_rgba(168,85,247,0.5)]",
|
|
75
|
-
band: "from-fuchsia-400/65 via-violet-400/25 to-transparent"
|
|
76
|
-
},
|
|
77
|
-
green: {
|
|
78
|
-
border: "border-emerald-400/25",
|
|
79
|
-
glow: "shadow-[0_0_0_1px_rgba(52,211,153,0.10),0_18px_40px_-28px_rgba(16,185,129,0.45)]",
|
|
80
|
-
band: "from-emerald-400/65 via-teal-400/25 to-transparent"
|
|
81
|
-
},
|
|
82
|
-
amber: {
|
|
83
|
-
border: "border-amber-400/25",
|
|
84
|
-
glow: "shadow-[0_0_0_1px_rgba(251,191,36,0.10),0_18px_40px_-28px_rgba(245,158,11,0.45)]",
|
|
85
|
-
band: "from-amber-400/65 via-orange-400/25 to-transparent"
|
|
86
|
-
},
|
|
87
|
-
rose: {
|
|
88
|
-
border: "border-rose-400/25",
|
|
89
|
-
glow: "shadow-[0_0_0_1px_rgba(251,113,133,0.10),0_18px_40px_-28px_rgba(244,63,94,0.45)]",
|
|
90
|
-
band: "from-rose-400/65 via-pink-400/25 to-transparent"
|
|
91
|
-
},
|
|
92
|
-
slate: {
|
|
93
|
-
border: "border-ui-border-strong",
|
|
94
|
-
glow: "shadow-[0_0_0_1px_rgba(15,23,42,0.05),0_18px_40px_-28px_rgba(15,23,42,0.22)]",
|
|
95
|
-
band: "from-ui-border-strong via-ui-border-base to-transparent"
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
function getAccentStyle(accent) {
|
|
99
|
-
return ACCENT_STYLES[accent];
|
|
100
|
-
}
|
|
101
|
-
function AnalyticsDashboardShell({ children, className }) {
|
|
102
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ["space-y-2.5 md:space-y-3", className].filter(Boolean).join(" "), children });
|
|
103
|
-
}
|
|
104
|
-
function AnalyticsDashboardHeader({
|
|
105
|
-
title,
|
|
106
|
-
description,
|
|
107
|
-
actions,
|
|
108
|
-
variant = "default",
|
|
109
|
-
actionsBare = false,
|
|
110
|
-
appearance = "card",
|
|
111
|
-
actionsClassName
|
|
112
|
-
}) {
|
|
113
|
-
const isPremium = variant === "premium";
|
|
114
|
-
const isInset = appearance === "inset";
|
|
115
|
-
const headerRow = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
116
|
-
"div",
|
|
117
|
-
{
|
|
118
|
-
className: `flex flex-col gap-2 px-4 py-2.5 lg:flex-row lg:items-center lg:justify-between lg:gap-4 ${isPremium ? "border-b border-ui-border-base/80" : "border-b border-ui-border-base"}`.trim(),
|
|
119
|
-
children: [
|
|
120
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-0.5", children: [
|
|
121
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: isPremium ? "text-lg font-semibold tracking-tight" : "tracking-tight", children: title }),
|
|
122
|
-
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
|
|
123
|
-
] }),
|
|
124
|
-
actions ? actionsBare ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
125
|
-
"div",
|
|
126
|
-
{
|
|
127
|
-
className: [
|
|
128
|
-
"flex flex-wrap items-center gap-3 self-start lg:shrink-0 lg:self-auto",
|
|
129
|
-
actionsClassName ?? ""
|
|
130
|
-
].join(" ").trim(),
|
|
131
|
-
children: actions
|
|
132
|
-
}
|
|
133
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
134
|
-
"div",
|
|
135
|
-
{
|
|
136
|
-
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(),
|
|
137
|
-
children: actions
|
|
138
|
-
}
|
|
139
|
-
) : null
|
|
140
|
-
]
|
|
141
|
-
}
|
|
142
|
-
);
|
|
143
|
-
if (isInset) {
|
|
144
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-0 bg-transparent p-0 shadow-none", children: headerRow });
|
|
145
|
-
}
|
|
146
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
147
|
-
ui.Container,
|
|
148
|
-
{
|
|
149
|
-
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(),
|
|
150
|
-
children: [
|
|
151
|
-
headerRow,
|
|
152
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
153
|
-
"div",
|
|
154
|
-
{
|
|
155
|
-
className: `h-px w-full ${isPremium ? "bg-gradient-to-r from-transparent via-ui-border-strong to-transparent" : "bg-ui-bg-subtle"}`.trim()
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
]
|
|
159
|
-
}
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
function AnalyticsStatCard({
|
|
163
|
-
label,
|
|
164
|
-
value,
|
|
165
|
-
helper,
|
|
166
|
-
className,
|
|
167
|
-
valueClassName,
|
|
168
|
-
variant = "default",
|
|
169
|
-
accent = "slate"
|
|
170
|
-
}) {
|
|
171
|
-
const accentStyle = getAccentStyle(accent);
|
|
172
|
-
const isHero = variant === "hero";
|
|
173
|
-
const isCompact = variant === "compact";
|
|
174
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
175
|
-
ui.Container,
|
|
176
|
-
{
|
|
177
|
-
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(),
|
|
178
|
-
children: [
|
|
179
|
-
isHero ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
180
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
181
|
-
"div",
|
|
182
|
-
{
|
|
183
|
-
className: `pointer-events-none absolute inset-x-3 top-0 h-10 rounded-b-full bg-gradient-to-r ${accentStyle.band} blur-xl`
|
|
184
|
-
}
|
|
185
|
-
),
|
|
186
|
-
/* @__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" })
|
|
187
|
-
] }) : null,
|
|
188
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
189
|
-
"div",
|
|
190
|
-
{
|
|
191
|
-
className: `relative flex flex-col justify-between gap-1 ${isCompact ? "min-h-[64px]" : isHero ? "min-h-[72px]" : "min-h-[92px]"}`.trim(),
|
|
192
|
-
children: [
|
|
193
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
194
|
-
ui.Text,
|
|
195
|
-
{
|
|
196
|
-
className: `text-ui-fg-muted font-medium ${isHero ? "text-[10px] uppercase tracking-[0.16em]" : "text-xs"}`.trim(),
|
|
197
|
-
children: label
|
|
198
|
-
}
|
|
199
|
-
),
|
|
200
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
201
|
-
ui.Heading,
|
|
202
|
-
{
|
|
203
|
-
level: "h2",
|
|
204
|
-
className: `leading-tight tracking-tight ${isHero ? "text-xl sm:text-2xl" : isCompact ? "text-lg" : "text-2xl"} ${valueClassName ?? ""}`.trim(),
|
|
205
|
-
children: value
|
|
206
|
-
}
|
|
207
|
-
),
|
|
208
|
-
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted leading-snug ${isHero ? "text-[10px]" : "text-xs"}`.trim(), children: helper }) : null
|
|
209
|
-
]
|
|
210
|
-
}
|
|
211
|
-
)
|
|
212
|
-
]
|
|
213
|
-
}
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
function AnalyticsSection({
|
|
217
|
-
title,
|
|
218
|
-
description,
|
|
219
|
-
actions,
|
|
220
|
-
children,
|
|
221
|
-
className,
|
|
222
|
-
variant = "default",
|
|
223
|
-
actionsBare = false
|
|
224
|
-
}) {
|
|
225
|
-
const isAtlas = variant === "atlas";
|
|
226
|
-
const isPremium = variant !== "default" && !isAtlas;
|
|
227
|
-
const shellClass = isAtlas ? "min-w-0 overflow-x-clip overflow-y-visible 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();
|
|
228
|
-
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();
|
|
229
|
-
const titleClass = isAtlas ? "text-sm font-semibold tracking-tight text-ui-fg-base" : isPremium ? "text-base font-semibold tracking-tight" : "tracking-tight";
|
|
230
|
-
const descClass = isAtlas ? "max-w-2xl text-[11px] leading-snug text-ui-fg-muted" : isPremium ? "max-w-2xl text-xs leading-snug" : "text-sm";
|
|
231
|
-
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();
|
|
232
|
-
const bodyClass = isAtlas ? "p-2" : variant === "hero" ? "px-4 py-3" : "px-4 py-3";
|
|
233
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: `${shellClass} ${className ?? ""}`.trim(), children: [
|
|
234
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: headerClass, children: [
|
|
235
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 space-y-0.5", children: [
|
|
236
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: titleClass, children: title }),
|
|
237
|
-
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: `text-ui-fg-muted ${descClass}`.trim(), children: description }) : null
|
|
238
|
-
] }),
|
|
239
|
-
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
|
|
240
|
-
] }),
|
|
241
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: bodyClass, children })
|
|
242
|
-
] });
|
|
243
|
-
}
|
|
244
|
-
function AnalyticsChartSurface({
|
|
245
|
-
children,
|
|
246
|
-
className,
|
|
247
|
-
variant = "default"
|
|
248
|
-
}) {
|
|
249
|
-
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";
|
|
250
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
251
|
-
"div",
|
|
252
|
-
{
|
|
253
|
-
className: `${surfaceClassName} ${className ?? ""}`.trim(),
|
|
254
|
-
children
|
|
255
|
-
}
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
function AnalyticsTableSurface({ children, className }) {
|
|
259
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
260
|
-
"div",
|
|
261
|
-
{
|
|
262
|
-
className: [
|
|
263
|
-
"min-w-0 overflow-x-auto overflow-y-visible rounded-xl border border-ui-border-base bg-ui-bg-base",
|
|
264
|
-
"[scrollbar-width:thin]",
|
|
265
|
-
className ?? ""
|
|
266
|
-
].join(" ").trim(),
|
|
267
|
-
children
|
|
268
|
-
}
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
function AnalyticsTooltipCard({
|
|
272
|
-
title,
|
|
273
|
-
children,
|
|
274
|
-
variant = "default"
|
|
8
|
+
function OrdersOverTimeTooltip({
|
|
9
|
+
active,
|
|
10
|
+
payload,
|
|
11
|
+
label
|
|
275
12
|
}) {
|
|
276
|
-
|
|
13
|
+
var _a, _b;
|
|
14
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
15
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
16
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : label);
|
|
17
|
+
const value = ((_b = payload[0]) == null ? void 0 : _b.value) ?? 0;
|
|
277
18
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
278
19
|
"div",
|
|
279
20
|
{
|
|
280
21
|
style: {
|
|
281
|
-
|
|
22
|
+
backgroundColor: "rgb(250, 234, 234)",
|
|
282
23
|
color: "#111827",
|
|
283
|
-
border: "1px solid
|
|
284
|
-
borderRadius:
|
|
285
|
-
padding:
|
|
286
|
-
boxShadow:
|
|
287
|
-
fontSize:
|
|
288
|
-
lineHeight:
|
|
289
|
-
minWidth: compact ? 0 : 176,
|
|
290
|
-
maxWidth: compact ? "min(92vw, 200px)" : "min(96vw, 380px)"
|
|
24
|
+
border: "1px solid rgb(229, 231, 235)",
|
|
25
|
+
borderRadius: "8px",
|
|
26
|
+
padding: "12px 16px",
|
|
27
|
+
boxShadow: "0 4px 14px rgba(102, 102, 102, 0.99)",
|
|
28
|
+
fontSize: "14px",
|
|
29
|
+
lineHeight: 1.5
|
|
291
30
|
},
|
|
292
31
|
children: [
|
|
293
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
marginBottom: compact ? "2px" : "6px",
|
|
299
|
-
fontSize: compact ? "10px" : "inherit",
|
|
300
|
-
lineHeight: compact ? 1.25 : 1.35,
|
|
301
|
-
wordBreak: "break-word"
|
|
302
|
-
},
|
|
303
|
-
children: title
|
|
304
|
-
}
|
|
305
|
-
),
|
|
306
|
-
children
|
|
32
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px", color: "#111827" }, children: displayLabel }),
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#374151" }, children: [
|
|
34
|
+
"Orders: ",
|
|
35
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: "#1f2937" }, children: value })
|
|
36
|
+
] })
|
|
307
37
|
]
|
|
308
38
|
}
|
|
309
39
|
);
|
|
310
40
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return Math.max(0, d.total_revenue - (d.revenue_cancelled ?? 0));
|
|
323
|
-
}
|
|
324
|
-
function isOrdersTodayPoint(value) {
|
|
325
|
-
return isDailyOrderRow(value) && "isToday" in value && typeof value.isToday === "boolean";
|
|
326
|
-
}
|
|
327
|
-
function formatChartLabel$2(value) {
|
|
328
|
-
if (!value) return "";
|
|
329
|
-
const parsed = new Date(value);
|
|
330
|
-
if (Number.isNaN(parsed.getTime())) return value;
|
|
331
|
-
return `${parsed.getUTCMonth() + 1}/${parsed.getUTCDate()}`;
|
|
332
|
-
}
|
|
333
|
-
function formatPercent(value) {
|
|
334
|
-
return `${value.toFixed(1)}%`;
|
|
335
|
-
}
|
|
336
|
-
function formatShortNumber$1(value) {
|
|
41
|
+
const STATUS_COLORS = {
|
|
42
|
+
pending: "#F59E0B",
|
|
43
|
+
cancelled: "#EF4444",
|
|
44
|
+
delivered: "#10B981"
|
|
45
|
+
};
|
|
46
|
+
const ORDER_CHART_STATUSES = [
|
|
47
|
+
{ key: "pending", label: "Pending", color: STATUS_COLORS.pending },
|
|
48
|
+
{ key: "cancelled", label: "Cancelled", color: STATUS_COLORS.cancelled },
|
|
49
|
+
{ key: "delivered", label: "Delivered", color: STATUS_COLORS.delivered }
|
|
50
|
+
];
|
|
51
|
+
function formatCurrency(value) {
|
|
337
52
|
return new Intl.NumberFormat("en-IN", {
|
|
338
|
-
|
|
339
|
-
|
|
53
|
+
style: "currency",
|
|
54
|
+
currency: "INR",
|
|
55
|
+
minimumFractionDigits: 0,
|
|
56
|
+
maximumFractionDigits: 0
|
|
340
57
|
}).format(value);
|
|
341
58
|
}
|
|
342
|
-
const
|
|
343
|
-
{ value: "hour", label: "Hour" },
|
|
344
|
-
{ value: "day", label: "Day" },
|
|
345
|
-
{ value: "week", label: "Week" },
|
|
346
|
-
{ value: "month", label: "Month" }
|
|
347
|
-
];
|
|
348
|
-
function getSalesBreakdownOtPeriod(g) {
|
|
349
|
-
if (g === "day") return "one_week";
|
|
350
|
-
if (g === "week") return "two_months";
|
|
351
|
-
if (g === "month") return "one_year";
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
function monthKeyFromDateStr(dateStr) {
|
|
355
|
-
if (dateStr.length >= 7) return dateStr.slice(0, 7);
|
|
356
|
-
return dateStr;
|
|
357
|
-
}
|
|
358
|
-
function utcSundayWeekStartKeyFromDateStr(dateStr) {
|
|
359
|
-
const parsed = new Date(dateStr);
|
|
360
|
-
if (Number.isNaN(parsed.getTime())) return dateStr;
|
|
361
|
-
const y = parsed.getUTCFullYear();
|
|
362
|
-
const m = parsed.getUTCMonth();
|
|
363
|
-
const day = parsed.getUTCDate();
|
|
364
|
-
const dow = parsed.getUTCDay();
|
|
365
|
-
const start = new Date(Date.UTC(y, m, day - dow));
|
|
366
|
-
const ys = start.getUTCFullYear();
|
|
367
|
-
const ms = String(start.getUTCMonth() + 1).padStart(2, "0");
|
|
368
|
-
const ds = String(start.getUTCDate()).padStart(2, "0");
|
|
369
|
-
return `${ys}-${ms}-${ds}`;
|
|
370
|
-
}
|
|
371
|
-
function formatWeekStartDm(weekStartKey) {
|
|
372
|
-
const parsed = /* @__PURE__ */ new Date(`${weekStartKey}T00:00:00.000Z`);
|
|
373
|
-
if (Number.isNaN(parsed.getTime())) return weekStartKey;
|
|
374
|
-
return `${parsed.getUTCDate()}/${parsed.getUTCMonth() + 1}`;
|
|
375
|
-
}
|
|
376
|
-
function formatDayBreakdownDm(dateStr) {
|
|
377
|
-
const parsed = new Date(dateStr);
|
|
378
|
-
if (Number.isNaN(parsed.getTime())) return dateStr;
|
|
379
|
-
return `${parsed.getUTCDate()}/${parsed.getUTCMonth() + 1}`;
|
|
380
|
-
}
|
|
381
|
-
function toDateKeyUtcFromDate(d) {
|
|
382
|
-
const y = d.getUTCFullYear();
|
|
383
|
-
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
384
|
-
const day = String(d.getUTCDate()).padStart(2, "0");
|
|
385
|
-
return `${y}-${m}-${day}`;
|
|
386
|
-
}
|
|
387
|
-
function lastNWeekStartKeysUtc(n) {
|
|
388
|
-
const now = /* @__PURE__ */ new Date();
|
|
389
|
-
const todayUtc = new Date(
|
|
390
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
|
|
391
|
-
);
|
|
392
|
-
const dow = todayUtc.getUTCDay();
|
|
393
|
-
const currentWeekStart = new Date(todayUtc);
|
|
394
|
-
currentWeekStart.setUTCDate(currentWeekStart.getUTCDate() - dow);
|
|
395
|
-
const keys = [];
|
|
396
|
-
for (let i = n - 1; i >= 0; i -= 1) {
|
|
397
|
-
const d = new Date(currentWeekStart);
|
|
398
|
-
d.setUTCDate(d.getUTCDate() - 7 * i);
|
|
399
|
-
keys.push(toDateKeyUtcFromDate(d));
|
|
400
|
-
}
|
|
401
|
-
return keys;
|
|
402
|
-
}
|
|
403
|
-
function formatMonthBucketLabel(ymKey) {
|
|
404
|
-
const [ys, ms] = ymKey.split("-");
|
|
405
|
-
const y = Number(ys);
|
|
406
|
-
const m = Number(ms);
|
|
407
|
-
if (!Number.isFinite(y) || !Number.isFinite(m)) return ymKey;
|
|
408
|
-
return new Intl.DateTimeFormat("en-IN", {
|
|
409
|
-
month: "short",
|
|
410
|
-
timeZone: "UTC"
|
|
411
|
-
}).format(new Date(Date.UTC(y, m - 1, 1)));
|
|
412
|
-
}
|
|
413
|
-
function isSalesBreakdownBarRow(value) {
|
|
414
|
-
if (typeof value !== "object" || value === null) return false;
|
|
415
|
-
const v = value;
|
|
416
|
-
return typeof v.key === "string" && typeof v.label === "string" && typeof v.orders_count === "number" && typeof v.revenue === "number";
|
|
417
|
-
}
|
|
418
|
-
function SalesBreakdownTooltip({
|
|
419
|
-
active,
|
|
420
|
-
payload
|
|
421
|
-
}) {
|
|
422
|
-
var _a, _b;
|
|
423
|
-
const { formatCurrency } = useAnalyticsCurrency();
|
|
424
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
425
|
-
const row = isSalesBreakdownBarRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
426
|
-
if (!row) return null;
|
|
427
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { variant: "compact", title: row.label, children: [
|
|
428
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
429
|
-
"Revenue: ",
|
|
430
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(row.revenue) })
|
|
431
|
-
] }),
|
|
432
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
433
|
-
"Orders:",
|
|
434
|
-
" ",
|
|
435
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(row.orders_count).toLocaleString() })
|
|
436
|
-
] })
|
|
437
|
-
] });
|
|
438
|
-
}
|
|
439
|
-
function OrdersTodayTooltip({
|
|
440
|
-
active,
|
|
441
|
-
payload,
|
|
442
|
-
label
|
|
443
|
-
}) {
|
|
444
|
-
var _a, _b, _c;
|
|
445
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
446
|
-
const row = isOrdersTodayPoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
447
|
-
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
448
|
-
const orders = ((_c = payload[0]) == null ? void 0 : _c.value) ?? 0;
|
|
449
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { variant: "compact", title: displayLabel, children: [
|
|
450
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
451
|
-
"Orders: ",
|
|
452
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(orders).toLocaleString() })
|
|
453
|
-
] }),
|
|
454
|
-
(row == null ? void 0 : row.isToday) ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#6B7280" }, children: "Current day" }) : null
|
|
455
|
-
] });
|
|
456
|
-
}
|
|
457
|
-
function OutcomesTrendTooltip({
|
|
458
|
-
active,
|
|
459
|
-
payload,
|
|
460
|
-
label
|
|
461
|
-
}) {
|
|
462
|
-
var _a, _b;
|
|
463
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
464
|
-
const row = isDailyOrderRow((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
465
|
-
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
466
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTooltipCard, { variant: "compact", title: displayLabel, children: payload.map((entry) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
467
|
-
entry.name,
|
|
468
|
-
":",
|
|
469
|
-
" ",
|
|
470
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(entry.value) || 0).toLocaleString() })
|
|
471
|
-
] }, String(entry.name))) });
|
|
472
|
-
}
|
|
473
|
-
const TREND_MIX_PIE_COLORS = {
|
|
474
|
-
revenue: "#c084fc",
|
|
475
|
-
orders: "#7dd3fc",
|
|
476
|
-
aov: "#fdba74"
|
|
477
|
-
};
|
|
478
|
-
function buildTrendMixPieSlices(dailyOrders, trendRevenueTotal, trendOrdersTotal, formatCompactCurrencyFn, formatCurrencyFn) {
|
|
479
|
-
const ncRev = dailyOrders.reduce((s, d) => s + nonCancelledRevenue(d), 0);
|
|
480
|
-
const ncOrd = dailyOrders.reduce(
|
|
481
|
-
(s, d) => s + Math.max(0, d.orders_count - d.cancelled_count),
|
|
482
|
-
0
|
|
483
|
-
);
|
|
484
|
-
const windowAov = ncOrd > 0 ? ncRev / ncOrd : 0;
|
|
485
|
-
const lr = Math.log1p(Math.max(trendRevenueTotal, 0));
|
|
486
|
-
const lo = Math.log1p(Math.max(trendOrdersTotal, 0));
|
|
487
|
-
const la = Math.log1p(Math.max(windowAov, 0));
|
|
488
|
-
const sumW = lr + lo + la;
|
|
489
|
-
const share = (w) => sumW > 0 ? w / sumW * 100 : 100 / 3;
|
|
490
|
-
const sR = share(lr);
|
|
491
|
-
const sO = share(lo);
|
|
492
|
-
const sA = share(la);
|
|
493
|
-
return [
|
|
494
|
-
{
|
|
495
|
-
key: "revenue",
|
|
496
|
-
name: "Revenue",
|
|
497
|
-
value: sR,
|
|
498
|
-
fill: TREND_MIX_PIE_COLORS.revenue,
|
|
499
|
-
sharePercent: sR,
|
|
500
|
-
formattedPrimary: formatCompactCurrencyFn(trendRevenueTotal),
|
|
501
|
-
formattedDetail: "Total revenue for this range, all days summed."
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
key: "orders",
|
|
505
|
-
name: "Orders",
|
|
506
|
-
value: sO,
|
|
507
|
-
fill: TREND_MIX_PIE_COLORS.orders,
|
|
508
|
-
sharePercent: sO,
|
|
509
|
-
formattedPrimary: Math.floor(trendOrdersTotal).toLocaleString(),
|
|
510
|
-
formattedDetail: "Order count in this range from daily totals."
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
key: "aov",
|
|
514
|
-
name: "AOV",
|
|
515
|
-
value: sA,
|
|
516
|
-
fill: TREND_MIX_PIE_COLORS.aov,
|
|
517
|
-
sharePercent: sA,
|
|
518
|
-
formattedPrimary: formatCurrencyFn(windowAov),
|
|
519
|
-
formattedDetail: "Average per non-cancelled order for the window."
|
|
520
|
-
}
|
|
521
|
-
];
|
|
522
|
-
}
|
|
523
|
-
const TREND_MIX_LEADER_STROKE = "#b8956a";
|
|
524
|
-
function isTrendMixPayload(value) {
|
|
525
|
-
if (typeof value !== "object" || value === null) return false;
|
|
526
|
-
const k = value.key;
|
|
527
|
-
return k === "revenue" || k === "orders" || k === "aov";
|
|
528
|
-
}
|
|
529
|
-
function splitDetailTwoLines(s, firstLineMax) {
|
|
530
|
-
const t = s.trim();
|
|
531
|
-
if (t.length <= firstLineMax) return [t, ""];
|
|
532
|
-
const sliceEnd = t.lastIndexOf(" ", firstLineMax);
|
|
533
|
-
const cut = sliceEnd > firstLineMax * 0.45 ? sliceEnd : firstLineMax;
|
|
534
|
-
const a = t.slice(0, cut).trim();
|
|
535
|
-
const b = t.slice(cut).trim();
|
|
536
|
-
return b ? [a, b] : [a, ""];
|
|
537
|
-
}
|
|
538
|
-
function TrendMixExplodedSector(props) {
|
|
539
|
-
const {
|
|
540
|
-
cx,
|
|
541
|
-
cy,
|
|
542
|
-
innerRadius,
|
|
543
|
-
outerRadius,
|
|
544
|
-
startAngle,
|
|
545
|
-
endAngle,
|
|
546
|
-
fill,
|
|
547
|
-
stroke,
|
|
548
|
-
strokeWidth,
|
|
549
|
-
cornerRadius
|
|
550
|
-
} = props;
|
|
551
|
-
const midDeg = (Number(startAngle) + Number(endAngle)) / 2;
|
|
552
|
-
const rad = -midDeg * Math.PI / 180;
|
|
553
|
-
const offset = 19;
|
|
554
|
-
const ncx = (Number(cx) || 0) + offset * Math.cos(rad);
|
|
555
|
-
const ncy = (Number(cy) || 0) + offset * Math.sin(rad);
|
|
556
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
557
|
-
recharts.Sector,
|
|
558
|
-
{
|
|
559
|
-
cx: ncx,
|
|
560
|
-
cy: ncy,
|
|
561
|
-
innerRadius,
|
|
562
|
-
outerRadius,
|
|
563
|
-
startAngle,
|
|
564
|
-
endAngle,
|
|
565
|
-
fill,
|
|
566
|
-
stroke,
|
|
567
|
-
strokeWidth,
|
|
568
|
-
cornerRadius,
|
|
569
|
-
style: {
|
|
570
|
-
filter: "drop-shadow(0 5px 6px rgba(0,0,0,0.5)) drop-shadow(0 2px 2px rgba(0,0,0,0.35))"
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
function createTrendMixInfographicLabel(slices) {
|
|
576
|
-
return function TrendMixInfographicLabel(props) {
|
|
577
|
-
const { cx, cy, midAngle, outerRadius, percent, payload, index } = props;
|
|
578
|
-
const slice = typeof index === "number" && slices[index] !== void 0 ? slices[index] : isTrendMixPayload(payload) ? payload : null;
|
|
579
|
-
if (slice === null || cx === void 0 || cy === void 0 || midAngle === void 0) {
|
|
580
|
-
return null;
|
|
581
|
-
}
|
|
582
|
-
const p = percent ?? 0;
|
|
583
|
-
const ang = midAngle;
|
|
584
|
-
const RAD = Math.PI / 180;
|
|
585
|
-
const cos = Math.cos(-RAD * ang);
|
|
586
|
-
const sin = Math.sin(-RAD * ang);
|
|
587
|
-
const or = Number(outerRadius);
|
|
588
|
-
if (!Number.isFinite(or)) return null;
|
|
589
|
-
const rimPad = 24;
|
|
590
|
-
const sx = Number(cx) + (or + rimPad) * cos;
|
|
591
|
-
const sy = Number(cy) + (or + rimPad) * sin;
|
|
592
|
-
const elbow = 30;
|
|
593
|
-
const mx = sx + elbow * cos;
|
|
594
|
-
const my = sy;
|
|
595
|
-
const extend = 58;
|
|
596
|
-
const rightSide = cos >= 0;
|
|
597
|
-
const hx = mx + (rightSide ? extend : -58);
|
|
598
|
-
const anchor = rightSide ? "start" : "end";
|
|
599
|
-
const tx = hx + (rightSide ? 4 : -4);
|
|
600
|
-
const yRule = my + 14;
|
|
601
|
-
const pctStr = `${(p * 100).toFixed(0)}%`;
|
|
602
|
-
const ruleHalfW = 52;
|
|
603
|
-
const xRuleA = rightSide ? tx - 2 : tx - ruleHalfW;
|
|
604
|
-
const xRuleB = rightSide ? tx + ruleHalfW : tx + 2;
|
|
605
|
-
const [detail1, detail2] = splitDetailTwoLines(slice.formattedDetail, 46);
|
|
606
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("g", { "aria-hidden": true, children: [
|
|
607
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
608
|
-
"polyline",
|
|
609
|
-
{
|
|
610
|
-
points: `${sx},${sy} ${mx},${my} ${hx},${yRule - 18}`,
|
|
611
|
-
fill: "none",
|
|
612
|
-
stroke: TREND_MIX_LEADER_STROKE,
|
|
613
|
-
strokeWidth: 1.15,
|
|
614
|
-
strokeLinecap: "round",
|
|
615
|
-
strokeLinejoin: "round"
|
|
616
|
-
}
|
|
617
|
-
),
|
|
618
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
619
|
-
"circle",
|
|
620
|
-
{
|
|
621
|
-
cx: sx,
|
|
622
|
-
cy: sy,
|
|
623
|
-
r: 3.5,
|
|
624
|
-
fill: "none",
|
|
625
|
-
stroke: TREND_MIX_LEADER_STROKE,
|
|
626
|
-
strokeWidth: 1.15
|
|
627
|
-
}
|
|
628
|
-
),
|
|
629
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
630
|
-
"line",
|
|
631
|
-
{
|
|
632
|
-
x1: xRuleA,
|
|
633
|
-
y1: yRule,
|
|
634
|
-
x2: xRuleB,
|
|
635
|
-
y2: yRule,
|
|
636
|
-
stroke: TREND_MIX_LEADER_STROKE,
|
|
637
|
-
strokeWidth: 1.15,
|
|
638
|
-
strokeLinecap: "round"
|
|
639
|
-
}
|
|
640
|
-
),
|
|
641
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
642
|
-
"text",
|
|
643
|
-
{
|
|
644
|
-
x: tx,
|
|
645
|
-
y: yRule - 8,
|
|
646
|
-
textAnchor: anchor,
|
|
647
|
-
fill: "#f8fafc",
|
|
648
|
-
className: "text-[13px] font-bold tabular-nums",
|
|
649
|
-
children: pctStr
|
|
650
|
-
}
|
|
651
|
-
),
|
|
652
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
653
|
-
"text",
|
|
654
|
-
{
|
|
655
|
-
x: tx,
|
|
656
|
-
y: yRule + 16,
|
|
657
|
-
textAnchor: anchor,
|
|
658
|
-
fill: "#f1f5f9",
|
|
659
|
-
className: "text-[9px] font-semibold uppercase tracking-[0.16em]",
|
|
660
|
-
children: slice.name
|
|
661
|
-
}
|
|
662
|
-
),
|
|
663
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
664
|
-
"text",
|
|
665
|
-
{
|
|
666
|
-
x: tx,
|
|
667
|
-
y: yRule + 32,
|
|
668
|
-
textAnchor: anchor,
|
|
669
|
-
fill: "#f8fafc",
|
|
670
|
-
className: "text-[11px] font-bold tabular-nums",
|
|
671
|
-
children: slice.formattedPrimary
|
|
672
|
-
}
|
|
673
|
-
),
|
|
674
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
675
|
-
"text",
|
|
676
|
-
{
|
|
677
|
-
x: tx,
|
|
678
|
-
y: yRule + 46,
|
|
679
|
-
textAnchor: anchor,
|
|
680
|
-
fill: "#94a3b8",
|
|
681
|
-
className: "text-[8px] leading-snug",
|
|
682
|
-
children: [
|
|
683
|
-
/* @__PURE__ */ jsxRuntime.jsx("tspan", { x: tx, dy: 0, children: detail1 }),
|
|
684
|
-
detail2 ? /* @__PURE__ */ jsxRuntime.jsx("tspan", { x: tx, dy: 11, children: detail2 }) : null
|
|
685
|
-
]
|
|
686
|
-
}
|
|
687
|
-
)
|
|
688
|
-
] });
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
const KPI_ICON_BG$2 = {
|
|
692
|
-
green: "bg-emerald-500/20",
|
|
693
|
-
blue: "bg-sky-500/20",
|
|
694
|
-
purple: "bg-violet-500/20",
|
|
695
|
-
amber: "bg-amber-500/20"
|
|
696
|
-
};
|
|
697
|
-
const KPI_ICONS$2 = {
|
|
698
|
-
green: icons.CurrencyDollar,
|
|
699
|
-
blue: icons.ShoppingCart,
|
|
700
|
-
purple: icons.TruckFast,
|
|
701
|
-
amber: icons.Cash
|
|
702
|
-
};
|
|
703
|
-
const PIE_PALETTE = ["#6366F1", "#94A3B8", "#10B981", "#F59E0B"];
|
|
704
|
-
function AtlasKpiCard$2({
|
|
705
|
-
label,
|
|
706
|
-
value,
|
|
707
|
-
helper,
|
|
708
|
-
accent
|
|
709
|
-
}) {
|
|
710
|
-
const Icon = KPI_ICONS$2[accent];
|
|
711
|
-
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: [
|
|
712
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
713
|
-
/* @__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 }),
|
|
714
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
715
|
-
"div",
|
|
716
|
-
{
|
|
717
|
-
className: `flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ${KPI_ICON_BG$2[accent]}`,
|
|
718
|
-
"aria-hidden": true,
|
|
719
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 text-ui-fg-base" })
|
|
720
|
-
}
|
|
721
|
-
)
|
|
722
|
-
] }),
|
|
723
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 text-xl font-semibold tabular-nums tracking-tight text-ui-fg-base sm:text-2xl", children: value }),
|
|
724
|
-
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-[11px] leading-snug text-ui-fg-muted", children: helper }) : null
|
|
725
|
-
] });
|
|
726
|
-
}
|
|
727
|
-
const SUMMARY_PERIODS$1 = [
|
|
59
|
+
const SUMMARY_PERIODS = [
|
|
728
60
|
{ value: "all", label: "All time" },
|
|
729
61
|
{ value: "0", label: "Today's orders" },
|
|
730
62
|
{ value: "7", label: "Last 7 days" },
|
|
@@ -736,3472 +68,563 @@ const OVER_TIME_PERIODS = [
|
|
|
736
68
|
{ value: "one_month", label: "One month" },
|
|
737
69
|
{ value: "one_year", label: "One year" }
|
|
738
70
|
];
|
|
739
|
-
const STATUS_BAR_COLORS = {
|
|
740
|
-
delivered: "#10B981",
|
|
741
|
-
cancelled: "#EF4444",
|
|
742
|
-
exchange: "#F59E0B",
|
|
743
|
-
return: "#8B5CF6",
|
|
744
|
-
today: "var(--medusa-color-ui-fg-interactive)",
|
|
745
|
-
todayMuted: "#BFDBFE",
|
|
746
|
-
orders: "#38BDF8",
|
|
747
|
-
revenue: "#D946EF"
|
|
748
|
-
};
|
|
749
|
-
const CHART_AXIS_TICK$2 = { fontSize: 10, fill: "#e5e7eb" };
|
|
750
|
-
const CHART_AXIS_TICK_SM$1 = { fontSize: 9, fill: "#e5e7eb" };
|
|
751
|
-
const CHART_AXIS_LINE$2 = "#94a3b8";
|
|
752
|
-
const SELECT_CLASS_NAME$2 = "h-9 min-w-[132px] cursor-pointer appearance-none rounded 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";
|
|
753
|
-
function EmptyAnalyticsPanel$1({
|
|
754
|
-
title,
|
|
755
|
-
description
|
|
756
|
-
}) {
|
|
757
|
-
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: [
|
|
758
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-xs font-semibold", children: title }),
|
|
759
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[11px] leading-snug", children: description })
|
|
760
|
-
] });
|
|
761
|
-
}
|
|
762
71
|
function OrdersDashboard() {
|
|
763
|
-
var _a, _b, _c, _d, _e;
|
|
764
|
-
const { formatCurrency, formatCompactCurrency } = useAnalyticsCurrency();
|
|
765
72
|
const [data, setData] = react.useState(null);
|
|
766
73
|
const [loading, setLoading] = react.useState(true);
|
|
767
74
|
const [error, setError] = react.useState(null);
|
|
768
75
|
const [filter, setFilter] = react.useState("all");
|
|
769
76
|
const [dailyOrders, setDailyOrders] = react.useState([]);
|
|
770
|
-
const [
|
|
771
|
-
const [
|
|
772
|
-
const [
|
|
773
|
-
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
774
|
-
const [overTimeOrdersMeta, setOverTimeOrdersMeta] = react.useState(null);
|
|
775
|
-
const [ordersInsights, setOrdersInsights] = react.useState(null);
|
|
776
|
-
const [insightsLoading, setInsightsLoading] = react.useState(true);
|
|
777
|
-
const [insightsError, setInsightsError] = react.useState(null);
|
|
778
|
-
const [todayContext, setTodayContext] = react.useState([]);
|
|
779
|
-
const [todayContextLoading, setTodayContextLoading] = react.useState(true);
|
|
780
|
-
const [todayContextError, setTodayContextError] = react.useState(null);
|
|
781
|
-
const [salesGranularity, setSalesGranularity] = react.useState("day");
|
|
782
|
-
const [salesBreakdownDaily, setSalesBreakdownDaily] = react.useState(
|
|
783
|
-
[]
|
|
784
|
-
);
|
|
785
|
-
const [salesBreakdownOtLoading, setSalesBreakdownOtLoading] = react.useState(true);
|
|
786
|
-
const [salesBreakdownOtError, setSalesBreakdownOtError] = react.useState(null);
|
|
787
|
-
const [salesBreakdownHourlyRolling, setSalesBreakdownHourlyRolling] = react.useState([]);
|
|
788
|
-
const [salesBreakdownHourlyLoading, setSalesBreakdownHourlyLoading] = react.useState(false);
|
|
789
|
-
const [salesBreakdownHourlyError, setSalesBreakdownHourlyError] = react.useState(null);
|
|
790
|
-
const salesBreakdownOtPeriod = react.useMemo(
|
|
791
|
-
() => getSalesBreakdownOtPeriod(salesGranularity),
|
|
792
|
-
[salesGranularity]
|
|
793
|
-
);
|
|
794
|
-
react.useEffect(() => {
|
|
795
|
-
if (salesBreakdownOtPeriod === null) {
|
|
796
|
-
setSalesBreakdownDaily([]);
|
|
797
|
-
setSalesBreakdownOtLoading(false);
|
|
798
|
-
setSalesBreakdownOtError(null);
|
|
799
|
-
return;
|
|
800
|
-
}
|
|
801
|
-
let cancelled = false;
|
|
802
|
-
setSalesBreakdownOtLoading(true);
|
|
803
|
-
setSalesBreakdownOtError(null);
|
|
804
|
-
fetch(
|
|
805
|
-
`/admin/analytics/orders-over-time?period=${salesBreakdownOtPeriod}`,
|
|
806
|
-
{ credentials: "include" }
|
|
807
|
-
).then((res) => {
|
|
808
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
809
|
-
return res.json();
|
|
810
|
-
}).then((body) => {
|
|
811
|
-
if (!cancelled) {
|
|
812
|
-
setSalesBreakdownDaily(body.dailyOrders ?? []);
|
|
813
|
-
}
|
|
814
|
-
}).catch((e) => {
|
|
815
|
-
if (!cancelled) {
|
|
816
|
-
setSalesBreakdownOtError(e instanceof Error ? e.message : String(e));
|
|
817
|
-
}
|
|
818
|
-
}).finally(() => {
|
|
819
|
-
if (!cancelled) setSalesBreakdownOtLoading(false);
|
|
820
|
-
});
|
|
821
|
-
return () => {
|
|
822
|
-
cancelled = true;
|
|
823
|
-
};
|
|
824
|
-
}, [salesBreakdownOtPeriod]);
|
|
825
|
-
react.useEffect(() => {
|
|
826
|
-
if (salesGranularity !== "hour") {
|
|
827
|
-
setSalesBreakdownHourlyRolling([]);
|
|
828
|
-
setSalesBreakdownHourlyLoading(false);
|
|
829
|
-
setSalesBreakdownHourlyError(null);
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
let cancelled = false;
|
|
833
|
-
setSalesBreakdownHourlyLoading(true);
|
|
834
|
-
setSalesBreakdownHourlyError(null);
|
|
835
|
-
fetch("/admin/analytics/orders-insights?last_hours=24", {
|
|
836
|
-
credentials: "include"
|
|
837
|
-
}).then((res) => {
|
|
838
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
839
|
-
return res.json();
|
|
840
|
-
}).then((body) => {
|
|
841
|
-
if (!cancelled) {
|
|
842
|
-
setSalesBreakdownHourlyRolling(body.hourly_rolling ?? []);
|
|
843
|
-
}
|
|
844
|
-
}).catch((e) => {
|
|
845
|
-
if (!cancelled) {
|
|
846
|
-
setSalesBreakdownHourlyError(
|
|
847
|
-
e instanceof Error ? e.message : String(e)
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
}).finally(() => {
|
|
851
|
-
if (!cancelled) setSalesBreakdownHourlyLoading(false);
|
|
852
|
-
});
|
|
853
|
-
return () => {
|
|
854
|
-
cancelled = true;
|
|
855
|
-
};
|
|
856
|
-
}, [salesGranularity]);
|
|
857
|
-
react.useEffect(() => {
|
|
858
|
-
let cancelled = false;
|
|
859
|
-
setLoading(true);
|
|
860
|
-
setError(null);
|
|
861
|
-
const url = `/admin/analytics/orders-summary?days=${filter}`;
|
|
862
|
-
fetch(url, { credentials: "include" }).then((res) => {
|
|
863
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
864
|
-
return res.json();
|
|
865
|
-
}).then((body) => {
|
|
866
|
-
if (!cancelled) setData(body);
|
|
867
|
-
}).catch((e) => {
|
|
868
|
-
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
869
|
-
}).finally(() => {
|
|
870
|
-
if (!cancelled) setLoading(false);
|
|
871
|
-
});
|
|
872
|
-
return () => {
|
|
873
|
-
cancelled = true;
|
|
874
|
-
};
|
|
875
|
-
}, [filter]);
|
|
876
|
-
react.useEffect(() => {
|
|
877
|
-
let cancelled = false;
|
|
878
|
-
setOverTimeLoading(true);
|
|
879
|
-
setOverTimeError(null);
|
|
880
|
-
const url = `/admin/analytics/orders-over-time?period=${overTimePeriod}`;
|
|
881
|
-
fetch(url, { credentials: "include" }).then((res) => {
|
|
882
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
883
|
-
return res.json();
|
|
884
|
-
}).then((body) => {
|
|
885
|
-
if (!cancelled) {
|
|
886
|
-
setDailyOrders(body.dailyOrders ?? []);
|
|
887
|
-
setPreviousPeriodDailyOrders(body.previousPeriodDailyOrders ?? []);
|
|
888
|
-
setOverTimeOrdersMeta(body.ordersAnalyticsMeta ?? null);
|
|
889
|
-
}
|
|
890
|
-
}).catch((e) => {
|
|
891
|
-
if (!cancelled) {
|
|
892
|
-
setOverTimeError(e instanceof Error ? e.message : String(e));
|
|
893
|
-
setOverTimeOrdersMeta(null);
|
|
894
|
-
}
|
|
895
|
-
}).finally(() => {
|
|
896
|
-
if (!cancelled) setOverTimeLoading(false);
|
|
897
|
-
});
|
|
898
|
-
return () => {
|
|
899
|
-
cancelled = true;
|
|
900
|
-
};
|
|
901
|
-
}, [overTimePeriod]);
|
|
902
|
-
const insightsWindowDays = react.useMemo(() => {
|
|
903
|
-
if (overTimePeriod === "one_week") return 7;
|
|
904
|
-
if (overTimePeriod === "one_month") return 30;
|
|
905
|
-
return 365;
|
|
906
|
-
}, [overTimePeriod]);
|
|
907
|
-
react.useEffect(() => {
|
|
908
|
-
let cancelled = false;
|
|
909
|
-
setInsightsLoading(true);
|
|
910
|
-
setInsightsError(null);
|
|
911
|
-
fetch(`/admin/analytics/orders-insights?days=${insightsWindowDays}`, {
|
|
912
|
-
credentials: "include"
|
|
913
|
-
}).then((res) => {
|
|
914
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
915
|
-
return res.json();
|
|
916
|
-
}).then((body) => {
|
|
917
|
-
if (!cancelled) setOrdersInsights(body);
|
|
918
|
-
}).catch((e) => {
|
|
919
|
-
if (!cancelled) setInsightsError(e instanceof Error ? e.message : String(e));
|
|
920
|
-
}).finally(() => {
|
|
921
|
-
if (!cancelled) setInsightsLoading(false);
|
|
922
|
-
});
|
|
923
|
-
return () => {
|
|
924
|
-
cancelled = true;
|
|
925
|
-
};
|
|
926
|
-
}, [insightsWindowDays]);
|
|
927
|
-
react.useEffect(() => {
|
|
928
|
-
let cancelled = false;
|
|
929
|
-
setTodayContextLoading(true);
|
|
930
|
-
setTodayContextError(null);
|
|
931
|
-
fetch("/admin/analytics/orders-over-time?period=one_week", {
|
|
932
|
-
credentials: "include"
|
|
933
|
-
}).then((res) => {
|
|
934
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
935
|
-
return res.json();
|
|
936
|
-
}).then((body) => {
|
|
937
|
-
if (!cancelled) setTodayContext(body.dailyOrders ?? []);
|
|
938
|
-
}).catch((e) => {
|
|
939
|
-
if (!cancelled) {
|
|
940
|
-
setTodayContextError(e instanceof Error ? e.message : String(e));
|
|
941
|
-
}
|
|
942
|
-
}).finally(() => {
|
|
943
|
-
if (!cancelled) setTodayContextLoading(false);
|
|
944
|
-
});
|
|
945
|
-
return () => {
|
|
946
|
-
cancelled = true;
|
|
947
|
-
};
|
|
948
|
-
}, []);
|
|
949
|
-
const selectedSummaryPeriod = ((_a = SUMMARY_PERIODS$1.find((period) => period.value === filter)) == null ? void 0 : _a.label) ?? "All time";
|
|
950
|
-
const operationsBreakdown = (data == null ? void 0 : data.operationalCounts) ?? {
|
|
951
|
-
cancelled: (data == null ? void 0 : data.cancelledOrders) ?? 0,
|
|
952
|
-
delivered: (data == null ? void 0 : data.deliveredOrders) ?? 0,
|
|
953
|
-
exchange: (data == null ? void 0 : data.exchangeOrders) ?? 0,
|
|
954
|
-
return: (data == null ? void 0 : data.returnOrders) ?? 0
|
|
955
|
-
};
|
|
956
|
-
const outcomesTimeSeriesHasPoints = react.useMemo(
|
|
957
|
-
() => dailyOrders.some(
|
|
958
|
-
(d) => d.delivered_count > 0 || d.cancelled_count > 0 || d.exchange_count > 0 || d.return_count > 0
|
|
959
|
-
),
|
|
960
|
-
[dailyOrders]
|
|
961
|
-
);
|
|
962
|
-
const totalResolvedOrders = operationsBreakdown.delivered + operationsBreakdown.cancelled;
|
|
963
|
-
const deliveryRate = totalResolvedOrders > 0 ? operationsBreakdown.delivered / totalResolvedOrders * 100 : 0;
|
|
964
|
-
const primaryStats = data ? [
|
|
965
|
-
{
|
|
966
|
-
label: "Total revenue",
|
|
967
|
-
value: formatCurrency(data.totalRevenue),
|
|
968
|
-
helper: "Non-cancelled orders",
|
|
969
|
-
accent: "green"
|
|
970
|
-
},
|
|
971
|
-
{
|
|
972
|
-
label: "Total orders",
|
|
973
|
-
value: data.totalOrders.toLocaleString(),
|
|
974
|
-
helper: selectedSummaryPeriod,
|
|
975
|
-
accent: "blue"
|
|
976
|
-
},
|
|
977
|
-
{
|
|
978
|
-
label: "Delivery rate",
|
|
979
|
-
value: formatPercent(deliveryRate),
|
|
980
|
-
helper: "Delivered vs cancelled",
|
|
981
|
-
accent: "purple"
|
|
982
|
-
},
|
|
983
|
-
{
|
|
984
|
-
label: "Average order value",
|
|
985
|
-
value: formatCurrency(data.aov),
|
|
986
|
-
helper: "Per non-cancelled order",
|
|
987
|
-
accent: "amber"
|
|
988
|
-
}
|
|
989
|
-
] : [];
|
|
990
|
-
const secondaryStats = data ? [
|
|
991
|
-
{ label: "Orders today", value: data.ordersToday.toLocaleString() },
|
|
992
|
-
{ label: "Pending orders", value: data.pendingOrders.toLocaleString() },
|
|
993
|
-
{ label: "Delivered orders", value: data.deliveredOrders.toLocaleString() },
|
|
994
|
-
{ label: "Cancelled orders", value: data.cancelledOrders.toLocaleString() },
|
|
995
|
-
{ label: "Exchange orders", value: data.exchangeOrders.toLocaleString() },
|
|
996
|
-
{
|
|
997
|
-
label: "Avg. fulfillment time",
|
|
998
|
-
value: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "—",
|
|
999
|
-
helper: "Measured in hours"
|
|
1000
|
-
}
|
|
1001
|
-
] : [];
|
|
1002
|
-
const todayChartData = react.useMemo(
|
|
1003
|
-
() => todayContext.map((point, index) => ({
|
|
1004
|
-
...point,
|
|
1005
|
-
isToday: index === todayContext.length - 1
|
|
1006
|
-
})),
|
|
1007
|
-
[todayContext]
|
|
1008
|
-
);
|
|
1009
|
-
const todayOrdersAverage = todayChartData.length > 0 ? todayChartData.reduce((sum, point) => sum + point.orders_count, 0) / todayChartData.length : 0;
|
|
1010
|
-
const todayPoint = todayChartData[todayChartData.length - 1] ?? null;
|
|
1011
|
-
const todayDelta = todayPoint && todayOrdersAverage > 0 ? todayPoint.orders_count - todayOrdersAverage : null;
|
|
1012
|
-
const trendRevenueTotal = dailyOrders.reduce(
|
|
1013
|
-
(sum, point) => sum + point.total_revenue,
|
|
1014
|
-
0
|
|
1015
|
-
);
|
|
1016
|
-
const trendOrdersTotal = dailyOrders.reduce(
|
|
1017
|
-
(sum, point) => sum + point.orders_count,
|
|
1018
|
-
0
|
|
1019
|
-
);
|
|
1020
|
-
const trendAverageRevenue = dailyOrders.length > 0 ? trendRevenueTotal / dailyOrders.length : 0;
|
|
1021
|
-
const trendAverageOrders = dailyOrders.length > 0 ? trendOrdersTotal / dailyOrders.length : 0;
|
|
1022
|
-
const peakRevenuePoint = dailyOrders.length > 0 ? dailyOrders.reduce(
|
|
1023
|
-
(peak, point) => point.total_revenue > peak.total_revenue ? point : peak
|
|
1024
|
-
) : null;
|
|
1025
|
-
const previousPeriodRevenueTotal = react.useMemo(
|
|
1026
|
-
() => previousPeriodDailyOrders.reduce((s, d) => s + d.total_revenue, 0),
|
|
1027
|
-
[previousPeriodDailyOrders]
|
|
1028
|
-
);
|
|
1029
|
-
const previousPeriodOrdersTotal = react.useMemo(
|
|
1030
|
-
() => previousPeriodDailyOrders.reduce((s, d) => s + d.orders_count, 0),
|
|
1031
|
-
[previousPeriodDailyOrders]
|
|
1032
|
-
);
|
|
1033
|
-
const revenueVsPreviousPercent = previousPeriodRevenueTotal > 0 ? (trendRevenueTotal - previousPeriodRevenueTotal) / previousPeriodRevenueTotal * 100 : null;
|
|
1034
|
-
const ordersVsPreviousPercent = previousPeriodOrdersTotal > 0 ? (trendOrdersTotal - previousPeriodOrdersTotal) / previousPeriodOrdersTotal * 100 : null;
|
|
1035
|
-
const trendMixPieSlices = react.useMemo(
|
|
1036
|
-
() => buildTrendMixPieSlices(
|
|
1037
|
-
dailyOrders,
|
|
1038
|
-
trendRevenueTotal,
|
|
1039
|
-
trendOrdersTotal,
|
|
1040
|
-
formatCompactCurrency,
|
|
1041
|
-
formatCurrency
|
|
1042
|
-
),
|
|
1043
|
-
[
|
|
1044
|
-
dailyOrders,
|
|
1045
|
-
trendRevenueTotal,
|
|
1046
|
-
trendOrdersTotal,
|
|
1047
|
-
formatCompactCurrency,
|
|
1048
|
-
formatCurrency
|
|
1049
|
-
]
|
|
1050
|
-
);
|
|
1051
|
-
const trendMixPieLabelRenderer = react.useMemo(
|
|
1052
|
-
() => createTrendMixInfographicLabel(trendMixPieSlices),
|
|
1053
|
-
[trendMixPieSlices]
|
|
1054
|
-
);
|
|
1055
|
-
const trendMixRangeFootnote = react.useMemo(() => {
|
|
1056
|
-
const ncRev = dailyOrders.reduce((s, d) => s + nonCancelledRevenue(d), 0);
|
|
1057
|
-
const ncOrd = dailyOrders.reduce(
|
|
1058
|
-
(s, d) => s + Math.max(0, d.orders_count - d.cancelled_count),
|
|
1059
|
-
0
|
|
1060
|
-
);
|
|
1061
|
-
const windowAov = ncOrd > 0 ? ncRev / ncOrd : 0;
|
|
1062
|
-
return {
|
|
1063
|
-
revenue: formatCompactCurrency(trendRevenueTotal),
|
|
1064
|
-
orders: Math.floor(trendOrdersTotal).toLocaleString(),
|
|
1065
|
-
aov: formatCurrency(windowAov)
|
|
1066
|
-
};
|
|
1067
|
-
}, [
|
|
1068
|
-
dailyOrders,
|
|
1069
|
-
trendRevenueTotal,
|
|
1070
|
-
trendOrdersTotal,
|
|
1071
|
-
formatCompactCurrency,
|
|
1072
|
-
formatCurrency
|
|
1073
|
-
]);
|
|
1074
|
-
const salesBreakdownChartRows = react.useMemo(() => {
|
|
1075
|
-
if (salesGranularity === "hour") {
|
|
1076
|
-
return salesBreakdownHourlyRolling.map((b, i) => ({
|
|
1077
|
-
key: `rolling-${i}`,
|
|
1078
|
-
label: b.label,
|
|
1079
|
-
orders_count: b.orders_count,
|
|
1080
|
-
revenue: b.revenue
|
|
1081
|
-
}));
|
|
1082
|
-
}
|
|
1083
|
-
if (salesGranularity === "week") {
|
|
1084
|
-
const targetKeys = lastNWeekStartKeysUtc(7);
|
|
1085
|
-
const agg = /* @__PURE__ */ new Map();
|
|
1086
|
-
for (const d of salesBreakdownDaily) {
|
|
1087
|
-
const wk = utcSundayWeekStartKeyFromDateStr(d.date);
|
|
1088
|
-
const cur = agg.get(wk) ?? { orders: 0, revenue: 0 };
|
|
1089
|
-
cur.orders += d.orders_count;
|
|
1090
|
-
cur.revenue += d.total_revenue;
|
|
1091
|
-
agg.set(wk, cur);
|
|
1092
|
-
}
|
|
1093
|
-
return targetKeys.map((key) => {
|
|
1094
|
-
var _a2, _b2;
|
|
1095
|
-
return {
|
|
1096
|
-
key,
|
|
1097
|
-
label: formatWeekStartDm(key),
|
|
1098
|
-
orders_count: ((_a2 = agg.get(key)) == null ? void 0 : _a2.orders) ?? 0,
|
|
1099
|
-
revenue: ((_b2 = agg.get(key)) == null ? void 0 : _b2.revenue) ?? 0
|
|
1100
|
-
};
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
if (salesGranularity === "month") {
|
|
1104
|
-
return salesBreakdownDaily.map((d) => ({
|
|
1105
|
-
key: d.date,
|
|
1106
|
-
label: d.label ?? formatMonthBucketLabel(monthKeyFromDateStr(d.date)),
|
|
1107
|
-
orders_count: d.orders_count,
|
|
1108
|
-
revenue: d.total_revenue
|
|
1109
|
-
}));
|
|
1110
|
-
}
|
|
1111
|
-
return salesBreakdownDaily.map((d) => ({
|
|
1112
|
-
key: d.date,
|
|
1113
|
-
label: formatDayBreakdownDm(d.date),
|
|
1114
|
-
orders_count: d.orders_count,
|
|
1115
|
-
revenue: d.total_revenue
|
|
1116
|
-
}));
|
|
1117
|
-
}, [
|
|
1118
|
-
salesGranularity,
|
|
1119
|
-
salesBreakdownDaily,
|
|
1120
|
-
salesBreakdownHourlyRolling
|
|
1121
|
-
]);
|
|
1122
|
-
const salesBreakdownDescription = react.useMemo(() => {
|
|
1123
|
-
switch (salesGranularity) {
|
|
1124
|
-
case "hour":
|
|
1125
|
-
return "Last 24 hours from now (UTC): one bar per clock hour in the rolling window.";
|
|
1126
|
-
case "day":
|
|
1127
|
-
return "Last 7 calendar days (UTC), one bar per day — axis day/month.";
|
|
1128
|
-
case "week":
|
|
1129
|
-
return "Last 7 weeks (UTC, weeks start Sunday); axis shows each week’s start as day/month.";
|
|
1130
|
-
case "month":
|
|
1131
|
-
return "Last 12 calendar months through the current month (UTC) — Jan, Feb, Mar, …";
|
|
1132
|
-
default:
|
|
1133
|
-
return "";
|
|
1134
|
-
}
|
|
1135
|
-
}, [salesGranularity]);
|
|
1136
|
-
const salesBreakdownXAxisInterval = react.useMemo(() => {
|
|
1137
|
-
const n = salesBreakdownChartRows.length;
|
|
1138
|
-
if (salesGranularity === "hour") return 3;
|
|
1139
|
-
if (salesGranularity === "day" && n > 24)
|
|
1140
|
-
return Math.max(0, Math.ceil(n / 10) - 1);
|
|
1141
|
-
return 0;
|
|
1142
|
-
}, [salesGranularity, salesBreakdownChartRows.length]);
|
|
1143
|
-
const ordersVsDraftsPie = react.useMemo(() => {
|
|
1144
|
-
var _a2;
|
|
1145
|
-
if (!ordersInsights) return [];
|
|
1146
|
-
const placed = ((_a2 = ordersInsights.funnel[0]) == null ? void 0 : _a2.count) ?? 0;
|
|
1147
|
-
const draftsCount = ordersInsights.drafts.available === true ? ordersInsights.drafts.count : 0;
|
|
1148
|
-
return [
|
|
1149
|
-
{ name: "Orders placed (window)", value: placed, key: "placed" },
|
|
1150
|
-
{
|
|
1151
|
-
name: ordersInsights.drafts.available === true ? "Open draft orders (snapshot)" : "Draft orders (unavailable)",
|
|
1152
|
-
value: draftsCount,
|
|
1153
|
-
key: "drafts"
|
|
1154
|
-
}
|
|
1155
|
-
];
|
|
1156
|
-
}, [ordersInsights]);
|
|
1157
|
-
const funnelTimeSeriesRows = react.useMemo(() => {
|
|
1158
|
-
return dailyOrders.map((d) => {
|
|
1159
|
-
const placed = d.orders_count;
|
|
1160
|
-
const notCancelled = Math.max(0, d.orders_count - d.cancelled_count);
|
|
1161
|
-
const shipped = d.shipped_count ?? 0;
|
|
1162
|
-
const delivered = d.delivered_count;
|
|
1163
|
-
const pctNotCancelled = placed > 0 ? notCancelled / placed * 100 : 0;
|
|
1164
|
-
const pctShippedOfNc = notCancelled > 0 ? shipped / notCancelled * 100 : 0;
|
|
1165
|
-
const pctDeliveredOfShipped = shipped > 0 ? delivered / shipped * 100 : 0;
|
|
1166
|
-
return {
|
|
1167
|
-
date: d.date,
|
|
1168
|
-
label: d.label ?? formatChartLabel$2(d.date),
|
|
1169
|
-
placed,
|
|
1170
|
-
not_cancelled: notCancelled,
|
|
1171
|
-
shipped,
|
|
1172
|
-
delivered,
|
|
1173
|
-
pct_not_cancelled: Math.round(pctNotCancelled * 10) / 10,
|
|
1174
|
-
pct_shipped_of_active: Math.round(pctShippedOfNc * 10) / 10,
|
|
1175
|
-
pct_delivered_of_shipped: Math.round(pctDeliveredOfShipped * 10) / 10
|
|
1176
|
-
};
|
|
1177
|
-
});
|
|
1178
|
-
}, [dailyOrders]);
|
|
1179
|
-
const selectedTrendPeriodLabel = ((_b = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _b.label) ?? "Selected range";
|
|
1180
|
-
const orderSampleLikelyTruncated = (((_c = data == null ? void 0 : data.ordersAnalyticsMeta) == null ? void 0 : _c.likely_truncated) ?? false) || ((overTimeOrdersMeta == null ? void 0 : overTimeOrdersMeta.likely_truncated) ?? false) || (((_d = ordersInsights == null ? void 0 : ordersInsights.ordersAnalyticsMeta) == null ? void 0 : _d.likely_truncated) ?? false);
|
|
1181
|
-
const quickPulseMetrics = [
|
|
1182
|
-
{
|
|
1183
|
-
label: "Range revenue",
|
|
1184
|
-
value: formatCompactCurrency(trendRevenueTotal),
|
|
1185
|
-
helper: selectedTrendPeriodLabel,
|
|
1186
|
-
accentClassName: "border-emerald-400/25 bg-emerald-500/5"
|
|
1187
|
-
},
|
|
1188
|
-
{
|
|
1189
|
-
label: "Range orders",
|
|
1190
|
-
value: formatShortNumber$1(trendOrdersTotal),
|
|
1191
|
-
helper: "Current trend window",
|
|
1192
|
-
accentClassName: "border-sky-400/25 bg-sky-500/5"
|
|
1193
|
-
},
|
|
1194
|
-
{
|
|
1195
|
-
label: "Avg/day orders",
|
|
1196
|
-
value: trendAverageOrders.toFixed(1),
|
|
1197
|
-
helper: "Across selected chart range",
|
|
1198
|
-
accentClassName: "border-violet-400/25 bg-violet-500/5"
|
|
1199
|
-
},
|
|
1200
|
-
{
|
|
1201
|
-
label: "Peak revenue day",
|
|
1202
|
-
value: peakRevenuePoint ? formatCompactCurrency(peakRevenuePoint.total_revenue) : "—",
|
|
1203
|
-
helper: (peakRevenuePoint == null ? void 0 : peakRevenuePoint.label) ?? formatChartLabel$2(peakRevenuePoint == null ? void 0 : peakRevenuePoint.date),
|
|
1204
|
-
accentClassName: "border-amber-400/25 bg-amber-500/5"
|
|
1205
|
-
}
|
|
1206
|
-
];
|
|
1207
|
-
if (loading) {
|
|
1208
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1209
|
-
"div",
|
|
1210
|
-
{
|
|
1211
|
-
className: "flex min-h-[200px] items-center justify-center",
|
|
1212
|
-
role: "status",
|
|
1213
|
-
"aria-label": "Loading analytics",
|
|
1214
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
1215
|
-
}
|
|
1216
|
-
);
|
|
1217
|
-
}
|
|
1218
|
-
if (error || !data) {
|
|
1219
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: error ?? "Failed to load analytics" }) });
|
|
1220
|
-
}
|
|
1221
|
-
const salesBreakdownWaiting = salesGranularity === "hour" ? salesBreakdownHourlyLoading : salesBreakdownOtLoading;
|
|
1222
|
-
const salesBreakdownFetchError = salesGranularity === "hour" ? salesBreakdownHourlyError : salesBreakdownOtError;
|
|
1223
|
-
const salesBreakdownBody = (() => {
|
|
1224
|
-
if (salesBreakdownWaiting) {
|
|
1225
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" }) }) });
|
|
1226
|
-
}
|
|
1227
|
-
if (salesBreakdownFetchError) {
|
|
1228
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[min(176px,28vh)] items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: salesBreakdownFetchError }) }) });
|
|
1229
|
-
}
|
|
1230
|
-
if (salesBreakdownChartRows.length === 0) {
|
|
1231
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1232
|
-
EmptyAnalyticsPanel$1,
|
|
1233
|
-
{
|
|
1234
|
-
title: "No sales data",
|
|
1235
|
-
description: "Nothing to show for this view and range."
|
|
1236
|
-
}
|
|
1237
|
-
);
|
|
1238
|
-
}
|
|
1239
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(200px,30vh)] min-h-[160px] w-full sm:h-[min(220px,32vh)]", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1240
|
-
recharts.BarChart,
|
|
1241
|
-
{
|
|
1242
|
-
data: salesBreakdownChartRows,
|
|
1243
|
-
margin: { top: 4, right: 8, left: 4, bottom: 4 },
|
|
1244
|
-
children: [
|
|
1245
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1246
|
-
recharts.CartesianGrid,
|
|
1247
|
-
{
|
|
1248
|
-
strokeDasharray: "3 3",
|
|
1249
|
-
vertical: false,
|
|
1250
|
-
stroke: "rgba(148,163,184,0.12)"
|
|
1251
|
-
}
|
|
1252
|
-
),
|
|
1253
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1254
|
-
recharts.XAxis,
|
|
1255
|
-
{
|
|
1256
|
-
dataKey: "label",
|
|
1257
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1258
|
-
tickLine: false,
|
|
1259
|
-
axisLine: false,
|
|
1260
|
-
interval: salesBreakdownXAxisInterval
|
|
1261
|
-
}
|
|
1262
|
-
),
|
|
1263
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1264
|
-
recharts.YAxis,
|
|
1265
|
-
{
|
|
1266
|
-
width: 44,
|
|
1267
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1268
|
-
tickLine: false,
|
|
1269
|
-
axisLine: false,
|
|
1270
|
-
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1271
|
-
}
|
|
1272
|
-
),
|
|
1273
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1274
|
-
recharts.Tooltip,
|
|
1275
|
-
{
|
|
1276
|
-
content: /* @__PURE__ */ jsxRuntime.jsx(SalesBreakdownTooltip, {}),
|
|
1277
|
-
cursor: { fill: "rgba(148,163,184,0.08)" }
|
|
1278
|
-
}
|
|
1279
|
-
),
|
|
1280
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1281
|
-
recharts.Bar,
|
|
1282
|
-
{
|
|
1283
|
-
dataKey: "revenue",
|
|
1284
|
-
name: "Revenue",
|
|
1285
|
-
fill: "#D946EF",
|
|
1286
|
-
radius: [6, 6, 0, 0]
|
|
1287
|
-
}
|
|
1288
|
-
)
|
|
1289
|
-
]
|
|
1290
|
-
}
|
|
1291
|
-
) }) }) });
|
|
1292
|
-
})();
|
|
1293
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsDashboardShell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-hidden rounded-2xl border border-ui-border-base/80 bg-ui-bg-base shadow-sm", children: [
|
|
1294
|
-
orderSampleLikelyTruncated ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1295
|
-
"div",
|
|
1296
|
-
{
|
|
1297
|
-
className: "border-b border-amber-400/30 bg-amber-500/10 px-4 py-2.5 sm:px-5",
|
|
1298
|
-
role: "status",
|
|
1299
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-base text-xs leading-snug", children: [
|
|
1300
|
-
"Order analytics loaded up to ",
|
|
1301
|
-
ORDERS_ANALYTICS_MAX_FETCH.toLocaleString(),
|
|
1302
|
-
" ",
|
|
1303
|
-
"orders per request. If you have more orders, KPIs and charts may under-count. Plan server-side aggregation or pagination for exact totals at very high volume."
|
|
1304
|
-
] })
|
|
1305
|
-
}
|
|
1306
|
-
) : null,
|
|
1307
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1308
|
-
AnalyticsDashboardHeader,
|
|
1309
|
-
{
|
|
1310
|
-
variant: "premium",
|
|
1311
|
-
appearance: "inset",
|
|
1312
|
-
actionsBare: true,
|
|
1313
|
-
title: "Orders",
|
|
1314
|
-
description: "Revenue, volume, and outcomes in a compact overview sized for a single viewport.",
|
|
1315
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1316
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1317
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1318
|
-
ui.Label,
|
|
1319
|
-
{
|
|
1320
|
-
htmlFor: "analytics-period",
|
|
1321
|
-
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
1322
|
-
children: "Period"
|
|
1323
|
-
}
|
|
1324
|
-
),
|
|
1325
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1326
|
-
"select",
|
|
1327
|
-
{
|
|
1328
|
-
id: "analytics-period",
|
|
1329
|
-
value: filter,
|
|
1330
|
-
onChange: (e) => setFilter(e.target.value),
|
|
1331
|
-
className: SELECT_CLASS_NAME$2,
|
|
1332
|
-
children: SUMMARY_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
1333
|
-
}
|
|
1334
|
-
)
|
|
1335
|
-
] }),
|
|
1336
|
-
filter !== "all" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1337
|
-
"button",
|
|
1338
|
-
{
|
|
1339
|
-
type: "button",
|
|
1340
|
-
onClick: () => setFilter("all"),
|
|
1341
|
-
className: "text-xs text-ui-fg-muted transition-colors hover:text-ui-fg-base",
|
|
1342
|
-
"aria-label": "Show all orders (clear filter)",
|
|
1343
|
-
children: "Clear period"
|
|
1344
|
-
}
|
|
1345
|
-
) : null
|
|
1346
|
-
] })
|
|
1347
|
-
}
|
|
1348
|
-
),
|
|
1349
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 p-3 pt-0 md:p-4 md:pt-0", children: [
|
|
1350
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1351
|
-
/* @__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" }) }),
|
|
1352
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-3 lg:grid-cols-4", children: primaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1353
|
-
AtlasKpiCard$2,
|
|
1354
|
-
{
|
|
1355
|
-
label: stat.label,
|
|
1356
|
-
value: stat.value,
|
|
1357
|
-
helper: stat.helper,
|
|
1358
|
-
accent: stat.accent
|
|
1359
|
-
},
|
|
1360
|
-
stat.label
|
|
1361
|
-
)) })
|
|
1362
|
-
] }),
|
|
1363
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-12 gap-2 xl:items-start", children: [
|
|
1364
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12 xl:col-span-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1365
|
-
AnalyticsSection,
|
|
1366
|
-
{
|
|
1367
|
-
variant: "atlas",
|
|
1368
|
-
actionsBare: true,
|
|
1369
|
-
title: "Revenue & orders",
|
|
1370
|
-
description: "Time series for the selected range. Window totals below match this chart.",
|
|
1371
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1372
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1373
|
-
ui.Label,
|
|
1374
|
-
{
|
|
1375
|
-
htmlFor: "over-time-period",
|
|
1376
|
-
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
1377
|
-
children: "Range"
|
|
1378
|
-
}
|
|
1379
|
-
),
|
|
1380
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1381
|
-
"select",
|
|
1382
|
-
{
|
|
1383
|
-
id: "over-time-period",
|
|
1384
|
-
value: overTimePeriod,
|
|
1385
|
-
onChange: (e) => setOverTimePeriod(e.target.value),
|
|
1386
|
-
className: SELECT_CLASS_NAME$2,
|
|
1387
|
-
children: OVER_TIME_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
1388
|
-
}
|
|
1389
|
-
)
|
|
1390
|
-
] }),
|
|
1391
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1392
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
1393
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
1394
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window revenue" }),
|
|
1395
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency(trendRevenueTotal) }),
|
|
1396
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
1397
|
-
"Avg/day ",
|
|
1398
|
-
formatCompactCurrency(trendAverageRevenue)
|
|
1399
|
-
] })
|
|
1400
|
-
] }) }),
|
|
1401
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
1402
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window orders" }),
|
|
1403
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: trendOrdersTotal.toLocaleString() }),
|
|
1404
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
1405
|
-
"Avg/day ",
|
|
1406
|
-
trendAverageOrders.toFixed(1)
|
|
1407
|
-
] })
|
|
1408
|
-
] }) }),
|
|
1409
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
1410
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "vs previous period" }),
|
|
1411
|
-
/* @__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` }),
|
|
1412
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: [
|
|
1413
|
-
"Orders",
|
|
1414
|
-
" ",
|
|
1415
|
-
ordersVsPreviousPercent === null ? "—" : `${ordersVsPreviousPercent >= 0 ? "+" : ""}${ordersVsPreviousPercent.toFixed(1)}%`
|
|
1416
|
-
] })
|
|
1417
|
-
] }) })
|
|
1418
|
-
] }),
|
|
1419
|
-
!overTimeLoading && !overTimeError && dailyOrders.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1420
|
-
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1421
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 min-w-0", children: [
|
|
1422
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue, orders & AOV" }),
|
|
1423
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[9px] leading-snug", children: [
|
|
1424
|
-
"Floating wedges with open center · leader labels only (no hover). Window:",
|
|
1425
|
-
" ",
|
|
1426
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-fg-subtle", children: selectedTrendPeriodLabel }),
|
|
1427
|
-
"."
|
|
1428
|
-
] })
|
|
1429
|
-
] }),
|
|
1430
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1431
|
-
"div",
|
|
1432
|
-
{
|
|
1433
|
-
className: "w-full rounded-2xl bg-[radial-gradient(ellipse_at_50%_42%,rgba(148,163,184,0.14)_0%,transparent_58%)] py-1",
|
|
1434
|
-
role: "img",
|
|
1435
|
-
"aria-label": `Revenue, orders, and AOV for ${selectedTrendPeriodLabel}. Values are shown on leader labels.`,
|
|
1436
|
-
children: [
|
|
1437
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(340px,48vh)] min-h-[288px] w-full", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1438
|
-
recharts.PieChart,
|
|
1439
|
-
{
|
|
1440
|
-
margin: { top: 20, right: 132, bottom: 28, left: 132 },
|
|
1441
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1442
|
-
recharts.Pie,
|
|
1443
|
-
{
|
|
1444
|
-
data: trendMixPieSlices,
|
|
1445
|
-
dataKey: "value",
|
|
1446
|
-
nameKey: "name",
|
|
1447
|
-
cx: "50%",
|
|
1448
|
-
cy: "50%",
|
|
1449
|
-
startAngle: 90,
|
|
1450
|
-
endAngle: -270,
|
|
1451
|
-
innerRadius: "26%",
|
|
1452
|
-
outerRadius: "46%",
|
|
1453
|
-
paddingAngle: 9,
|
|
1454
|
-
cornerRadius: 8,
|
|
1455
|
-
stroke: "rgba(15,23,42,0.55)",
|
|
1456
|
-
strokeWidth: 2,
|
|
1457
|
-
shape: TrendMixExplodedSector,
|
|
1458
|
-
label: trendMixPieLabelRenderer,
|
|
1459
|
-
labelLine: false,
|
|
1460
|
-
isAnimationActive: false,
|
|
1461
|
-
children: trendMixPieSlices.map((slice) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: slice.fill }, slice.key))
|
|
1462
|
-
}
|
|
1463
|
-
)
|
|
1464
|
-
}
|
|
1465
|
-
) }) }),
|
|
1466
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "mt-2 px-1 text-center text-[9px] leading-snug text-ui-fg-muted", children: [
|
|
1467
|
-
"Range snapshot — Revenue ",
|
|
1468
|
-
trendMixRangeFootnote.revenue,
|
|
1469
|
-
" · Orders",
|
|
1470
|
-
" ",
|
|
1471
|
-
trendMixRangeFootnote.orders,
|
|
1472
|
-
" · AOV ",
|
|
1473
|
-
trendMixRangeFootnote.aov
|
|
1474
|
-
] })
|
|
1475
|
-
]
|
|
1476
|
-
}
|
|
1477
|
-
)
|
|
1478
|
-
] }),
|
|
1479
|
-
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "mt-1.5", children: [
|
|
1480
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Revenue breakdown (stacked — by order status)" }),
|
|
1481
|
-
/* @__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(
|
|
1482
|
-
recharts.ComposedChart,
|
|
1483
|
-
{
|
|
1484
|
-
data: dailyOrders,
|
|
1485
|
-
margin: { top: 4, right: 8, left: -4, bottom: 0 },
|
|
1486
|
-
children: [
|
|
1487
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1488
|
-
recharts.CartesianGrid,
|
|
1489
|
-
{
|
|
1490
|
-
stroke: "rgba(148,163,184,0.14)",
|
|
1491
|
-
strokeDasharray: "3 3",
|
|
1492
|
-
vertical: false
|
|
1493
|
-
}
|
|
1494
|
-
),
|
|
1495
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1496
|
-
recharts.XAxis,
|
|
1497
|
-
{
|
|
1498
|
-
dataKey: "date",
|
|
1499
|
-
tick: CHART_AXIS_TICK$2,
|
|
1500
|
-
tickLine: false,
|
|
1501
|
-
axisLine: false,
|
|
1502
|
-
tickFormatter: (value) => {
|
|
1503
|
-
const row = dailyOrders.find(
|
|
1504
|
-
(d) => d.date === String(value)
|
|
1505
|
-
);
|
|
1506
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel$2(String(value));
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
),
|
|
1510
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1511
|
-
recharts.YAxis,
|
|
1512
|
-
{
|
|
1513
|
-
tick: CHART_AXIS_TICK$2,
|
|
1514
|
-
tickFormatter: (v) => formatCompactCurrency(Number(v))
|
|
1515
|
-
}
|
|
1516
|
-
),
|
|
1517
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1518
|
-
recharts.Tooltip,
|
|
1519
|
-
{
|
|
1520
|
-
content: ({ active, payload, label }) => active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTooltipCard, { variant: "compact", title: String(label), children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1521
|
-
p.name,
|
|
1522
|
-
":",
|
|
1523
|
-
" ",
|
|
1524
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(p.value) || 0) })
|
|
1525
|
-
] }, String(p.name))) }) : null
|
|
1526
|
-
}
|
|
1527
|
-
),
|
|
1528
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { wrapperStyle: { fontSize: 10 }, iconType: "circle" }),
|
|
1529
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1530
|
-
recharts.Area,
|
|
1531
|
-
{
|
|
1532
|
-
type: "natural",
|
|
1533
|
-
dataKey: "revenue_delivered",
|
|
1534
|
-
name: "Delivered",
|
|
1535
|
-
stackId: "r",
|
|
1536
|
-
fill: "#10B981",
|
|
1537
|
-
stroke: "#059669",
|
|
1538
|
-
fillOpacity: 0.85
|
|
1539
|
-
}
|
|
1540
|
-
),
|
|
1541
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1542
|
-
recharts.Area,
|
|
1543
|
-
{
|
|
1544
|
-
type: "natural",
|
|
1545
|
-
dataKey: "revenue_open",
|
|
1546
|
-
name: "Open / pending",
|
|
1547
|
-
stackId: "r",
|
|
1548
|
-
fill: "#3B82F6",
|
|
1549
|
-
stroke: "#2563eb",
|
|
1550
|
-
fillOpacity: 0.85
|
|
1551
|
-
}
|
|
1552
|
-
),
|
|
1553
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1554
|
-
recharts.Area,
|
|
1555
|
-
{
|
|
1556
|
-
type: "natural",
|
|
1557
|
-
dataKey: "revenue_cancelled",
|
|
1558
|
-
name: "Cancelled",
|
|
1559
|
-
stackId: "r",
|
|
1560
|
-
fill: "#F87171",
|
|
1561
|
-
stroke: "#EF4444",
|
|
1562
|
-
fillOpacity: 0.75
|
|
1563
|
-
}
|
|
1564
|
-
)
|
|
1565
|
-
]
|
|
1566
|
-
}
|
|
1567
|
-
) }) })
|
|
1568
|
-
] })
|
|
1569
|
-
] }) : null
|
|
1570
|
-
] })
|
|
1571
|
-
}
|
|
1572
|
-
) }),
|
|
1573
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12 flex flex-col gap-2 xl:col-span-6", children: [
|
|
1574
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1575
|
-
AnalyticsSection,
|
|
1576
|
-
{
|
|
1577
|
-
variant: "atlas",
|
|
1578
|
-
title: "Pulse & range",
|
|
1579
|
-
description: "Window totals for the selected range.",
|
|
1580
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2", children: quickPulseMetrics.map((metric) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1581
|
-
"div",
|
|
1582
|
-
{
|
|
1583
|
-
className: `rounded-lg border border-ui-border-base bg-ui-bg-base px-2 py-2 shadow-sm ${metric.accentClassName}`.trim(),
|
|
1584
|
-
children: [
|
|
1585
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: metric.label }),
|
|
1586
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base mt-0.5 text-sm font-semibold tracking-tight", children: metric.value }),
|
|
1587
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted mt-0.5 text-[9px] leading-snug", children: metric.helper })
|
|
1588
|
-
]
|
|
1589
|
-
},
|
|
1590
|
-
metric.label
|
|
1591
|
-
)) })
|
|
1592
|
-
}
|
|
1593
|
-
),
|
|
1594
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1595
|
-
AnalyticsSection,
|
|
1596
|
-
{
|
|
1597
|
-
variant: "atlas",
|
|
1598
|
-
title: "Order → fulfillment funnel",
|
|
1599
|
-
description: `Stages by order created date (UTC), same range as Revenue & orders (${((_e = OVER_TIME_PERIODS.find((p) => p.value === overTimePeriod)) == null ? void 0 : _e.label) ?? "period"}). Not storefront visitors.`,
|
|
1600
|
-
children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[120px] items-center justify-center py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-[120px] items-center justify-center px-2 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: overTimeError }) }) }) : funnelTimeSeriesRows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
1601
|
-
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1602
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-0.5 text-[10px] font-medium uppercase tracking-[0.14em] text-ui-fg-muted", children: "Stage counts (time series)" }),
|
|
1603
|
-
/* @__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(
|
|
1604
|
-
recharts.LineChart,
|
|
1605
|
-
{
|
|
1606
|
-
data: funnelTimeSeriesRows,
|
|
1607
|
-
margin: { top: 4, right: 8, left: 0, bottom: 4 },
|
|
1608
|
-
children: [
|
|
1609
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1610
|
-
recharts.CartesianGrid,
|
|
1611
|
-
{
|
|
1612
|
-
strokeDasharray: "3 3",
|
|
1613
|
-
stroke: "rgba(148,163,184,0.2)"
|
|
1614
|
-
}
|
|
1615
|
-
),
|
|
1616
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1617
|
-
recharts.XAxis,
|
|
1618
|
-
{
|
|
1619
|
-
dataKey: "date",
|
|
1620
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1621
|
-
stroke: CHART_AXIS_LINE$2,
|
|
1622
|
-
tickLine: { stroke: CHART_AXIS_LINE$2 },
|
|
1623
|
-
tickFormatter: (value) => {
|
|
1624
|
-
const row = dailyOrders.find((r) => r.date === value);
|
|
1625
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel$2(String(value));
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
),
|
|
1629
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1630
|
-
recharts.YAxis,
|
|
1631
|
-
{
|
|
1632
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1633
|
-
stroke: CHART_AXIS_LINE$2,
|
|
1634
|
-
tickLine: { stroke: CHART_AXIS_LINE$2 },
|
|
1635
|
-
width: 28,
|
|
1636
|
-
allowDecimals: false,
|
|
1637
|
-
tickFormatter: (v) => Math.floor(Number(v) || 0).toLocaleString()
|
|
1638
|
-
}
|
|
1639
|
-
),
|
|
1640
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1641
|
-
recharts.Tooltip,
|
|
1642
|
-
{
|
|
1643
|
-
content: ({ active, payload, label }) => {
|
|
1644
|
-
var _a2;
|
|
1645
|
-
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1646
|
-
AnalyticsTooltipCard,
|
|
1647
|
-
{
|
|
1648
|
-
variant: "compact",
|
|
1649
|
-
title: typeof label === "string" ? ((_a2 = dailyOrders.find((r) => r.date === label)) == null ? void 0 : _a2.label) ?? formatChartLabel$2(label) : String(label),
|
|
1650
|
-
children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1651
|
-
String(p.name),
|
|
1652
|
-
":",
|
|
1653
|
-
" ",
|
|
1654
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(p.value) || 0).toLocaleString() })
|
|
1655
|
-
] }, String(p.dataKey)))
|
|
1656
|
-
}
|
|
1657
|
-
) : null;
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
),
|
|
1661
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1662
|
-
recharts.Legend,
|
|
1663
|
-
{
|
|
1664
|
-
wrapperStyle: { fontSize: 9, color: "#e5e7eb", paddingTop: 4 },
|
|
1665
|
-
iconType: "circle",
|
|
1666
|
-
iconSize: 6
|
|
1667
|
-
}
|
|
1668
|
-
),
|
|
1669
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1670
|
-
recharts.Line,
|
|
1671
|
-
{
|
|
1672
|
-
type: "natural",
|
|
1673
|
-
dataKey: "placed",
|
|
1674
|
-
name: "Placed",
|
|
1675
|
-
stroke: "#6366F1",
|
|
1676
|
-
strokeWidth: 2,
|
|
1677
|
-
dot: false
|
|
1678
|
-
}
|
|
1679
|
-
),
|
|
1680
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1681
|
-
recharts.Line,
|
|
1682
|
-
{
|
|
1683
|
-
type: "natural",
|
|
1684
|
-
dataKey: "not_cancelled",
|
|
1685
|
-
name: "Not cancelled",
|
|
1686
|
-
stroke: "#22d3ee",
|
|
1687
|
-
strokeWidth: 2,
|
|
1688
|
-
dot: false
|
|
1689
|
-
}
|
|
1690
|
-
),
|
|
1691
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1692
|
-
recharts.Line,
|
|
1693
|
-
{
|
|
1694
|
-
type: "natural",
|
|
1695
|
-
dataKey: "shipped",
|
|
1696
|
-
name: "Shipped",
|
|
1697
|
-
stroke: "#F59E0B",
|
|
1698
|
-
strokeWidth: 2,
|
|
1699
|
-
dot: false
|
|
1700
|
-
}
|
|
1701
|
-
),
|
|
1702
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1703
|
-
recharts.Line,
|
|
1704
|
-
{
|
|
1705
|
-
type: "natural",
|
|
1706
|
-
dataKey: "delivered",
|
|
1707
|
-
name: "Delivered",
|
|
1708
|
-
stroke: "#10B981",
|
|
1709
|
-
strokeWidth: 2,
|
|
1710
|
-
dot: false
|
|
1711
|
-
}
|
|
1712
|
-
)
|
|
1713
|
-
]
|
|
1714
|
-
}
|
|
1715
|
-
) }) })
|
|
1716
|
-
] }),
|
|
1717
|
-
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", children: [
|
|
1718
|
-
/* @__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)" }),
|
|
1719
|
-
/* @__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(
|
|
1720
|
-
recharts.LineChart,
|
|
1721
|
-
{
|
|
1722
|
-
data: funnelTimeSeriesRows,
|
|
1723
|
-
margin: { top: 4, right: 8, left: 0, bottom: 4 },
|
|
1724
|
-
children: [
|
|
1725
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1726
|
-
recharts.CartesianGrid,
|
|
1727
|
-
{
|
|
1728
|
-
strokeDasharray: "3 3",
|
|
1729
|
-
stroke: "rgba(148,163,184,0.2)"
|
|
1730
|
-
}
|
|
1731
|
-
),
|
|
1732
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1733
|
-
recharts.XAxis,
|
|
1734
|
-
{
|
|
1735
|
-
dataKey: "date",
|
|
1736
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1737
|
-
stroke: CHART_AXIS_LINE$2,
|
|
1738
|
-
tickLine: { stroke: CHART_AXIS_LINE$2 },
|
|
1739
|
-
tickFormatter: (value) => {
|
|
1740
|
-
const row = dailyOrders.find((r) => r.date === value);
|
|
1741
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel$2(String(value));
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
),
|
|
1745
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1746
|
-
recharts.YAxis,
|
|
1747
|
-
{
|
|
1748
|
-
domain: [0, 100],
|
|
1749
|
-
tick: CHART_AXIS_TICK_SM$1,
|
|
1750
|
-
stroke: CHART_AXIS_LINE$2,
|
|
1751
|
-
tickLine: { stroke: CHART_AXIS_LINE$2 },
|
|
1752
|
-
width: 36,
|
|
1753
|
-
tickFormatter: (v) => `${Math.round(Number(v) || 0)}%`
|
|
1754
|
-
}
|
|
1755
|
-
),
|
|
1756
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1757
|
-
recharts.Tooltip,
|
|
1758
|
-
{
|
|
1759
|
-
content: ({ active, payload, label }) => {
|
|
1760
|
-
var _a2;
|
|
1761
|
-
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1762
|
-
AnalyticsTooltipCard,
|
|
1763
|
-
{
|
|
1764
|
-
variant: "compact",
|
|
1765
|
-
title: typeof label === "string" ? ((_a2 = dailyOrders.find((r) => r.date === label)) == null ? void 0 : _a2.label) ?? formatChartLabel$2(label) : String(label),
|
|
1766
|
-
children: payload.map((p) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1767
|
-
String(p.name),
|
|
1768
|
-
":",
|
|
1769
|
-
" ",
|
|
1770
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatPercent(Number(p.value) || 0) })
|
|
1771
|
-
] }, String(p.dataKey)))
|
|
1772
|
-
}
|
|
1773
|
-
) : null;
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
),
|
|
1777
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1778
|
-
recharts.Legend,
|
|
1779
|
-
{
|
|
1780
|
-
wrapperStyle: { fontSize: 9, color: "#e5e7eb", paddingTop: 4 },
|
|
1781
|
-
iconType: "circle",
|
|
1782
|
-
iconSize: 6
|
|
1783
|
-
}
|
|
1784
|
-
),
|
|
1785
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1786
|
-
recharts.Line,
|
|
1787
|
-
{
|
|
1788
|
-
type: "natural",
|
|
1789
|
-
dataKey: "pct_not_cancelled",
|
|
1790
|
-
name: "Not cancelled / placed",
|
|
1791
|
-
stroke: "#6366F1",
|
|
1792
|
-
strokeWidth: 2,
|
|
1793
|
-
dot: false
|
|
1794
|
-
}
|
|
1795
|
-
),
|
|
1796
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1797
|
-
recharts.Line,
|
|
1798
|
-
{
|
|
1799
|
-
type: "natural",
|
|
1800
|
-
dataKey: "pct_shipped_of_active",
|
|
1801
|
-
name: "Shipped / not cancelled",
|
|
1802
|
-
stroke: "#F59E0B",
|
|
1803
|
-
strokeWidth: 2,
|
|
1804
|
-
dot: false
|
|
1805
|
-
}
|
|
1806
|
-
),
|
|
1807
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1808
|
-
recharts.Line,
|
|
1809
|
-
{
|
|
1810
|
-
type: "natural",
|
|
1811
|
-
dataKey: "pct_delivered_of_shipped",
|
|
1812
|
-
name: "Delivered / shipped",
|
|
1813
|
-
stroke: "#10B981",
|
|
1814
|
-
strokeWidth: 2,
|
|
1815
|
-
dot: false
|
|
1816
|
-
}
|
|
1817
|
-
)
|
|
1818
|
-
]
|
|
1819
|
-
}
|
|
1820
|
-
) }) })
|
|
1821
|
-
] })
|
|
1822
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1823
|
-
EmptyAnalyticsPanel$1,
|
|
1824
|
-
{
|
|
1825
|
-
title: "No funnel data",
|
|
1826
|
-
description: "No orders in this range to chart."
|
|
1827
|
-
}
|
|
1828
|
-
)
|
|
1829
|
-
}
|
|
1830
|
-
)
|
|
1831
|
-
] })
|
|
1832
|
-
] }),
|
|
1833
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 space-y-1.5", children: [
|
|
1834
|
-
/* @__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" }) }),
|
|
1835
|
-
/* @__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(
|
|
1836
|
-
AnalyticsStatCard,
|
|
1837
|
-
{
|
|
1838
|
-
label: stat.label,
|
|
1839
|
-
value: stat.value,
|
|
1840
|
-
helper: stat.helper,
|
|
1841
|
-
variant: "compact"
|
|
1842
|
-
},
|
|
1843
|
-
stat.label
|
|
1844
|
-
)) })
|
|
1845
|
-
] }),
|
|
1846
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-1 items-start gap-2 xl:grid-cols-12", children: [
|
|
1847
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1848
|
-
AnalyticsSection,
|
|
1849
|
-
{
|
|
1850
|
-
variant: "atlas",
|
|
1851
|
-
className: "xl:col-span-7",
|
|
1852
|
-
title: "Order outcomes",
|
|
1853
|
-
description: "Delivered, cancelled, exchanges, and returns per bucket — same window as Revenue & orders.",
|
|
1854
|
-
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(
|
|
1855
|
-
recharts.LineChart,
|
|
1856
|
-
{
|
|
1857
|
-
data: dailyOrders,
|
|
1858
|
-
margin: { top: 4, right: 8, left: 0, bottom: 0 },
|
|
1859
|
-
children: [
|
|
1860
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1861
|
-
recharts.CartesianGrid,
|
|
1862
|
-
{
|
|
1863
|
-
stroke: "rgba(148,163,184,0.14)",
|
|
1864
|
-
strokeDasharray: "3 3",
|
|
1865
|
-
vertical: false
|
|
1866
|
-
}
|
|
1867
|
-
),
|
|
1868
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1869
|
-
recharts.XAxis,
|
|
1870
|
-
{
|
|
1871
|
-
dataKey: "date",
|
|
1872
|
-
tick: CHART_AXIS_TICK$2,
|
|
1873
|
-
tickLine: false,
|
|
1874
|
-
axisLine: false,
|
|
1875
|
-
tickFormatter: (value) => {
|
|
1876
|
-
const dateStr = typeof value === "string" ? value : String(value);
|
|
1877
|
-
const row = dailyOrders.find((d) => d.date === dateStr);
|
|
1878
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel$2(dateStr);
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
),
|
|
1882
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1883
|
-
recharts.YAxis,
|
|
1884
|
-
{
|
|
1885
|
-
width: 28,
|
|
1886
|
-
tick: CHART_AXIS_TICK$2,
|
|
1887
|
-
tickLine: false,
|
|
1888
|
-
axisLine: false,
|
|
1889
|
-
allowDecimals: false,
|
|
1890
|
-
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
1891
|
-
}
|
|
1892
|
-
),
|
|
1893
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1894
|
-
recharts.Tooltip,
|
|
1895
|
-
{
|
|
1896
|
-
content: /* @__PURE__ */ jsxRuntime.jsx(OutcomesTrendTooltip, {}),
|
|
1897
|
-
cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 }
|
|
1898
|
-
}
|
|
1899
|
-
),
|
|
1900
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1901
|
-
recharts.Legend,
|
|
1902
|
-
{
|
|
1903
|
-
wrapperStyle: { fontSize: 9, paddingTop: 0 },
|
|
1904
|
-
iconType: "circle",
|
|
1905
|
-
iconSize: 6
|
|
1906
|
-
}
|
|
1907
|
-
),
|
|
1908
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1909
|
-
recharts.Line,
|
|
1910
|
-
{
|
|
1911
|
-
type: "natural",
|
|
1912
|
-
dataKey: "delivered_count",
|
|
1913
|
-
name: "Delivered",
|
|
1914
|
-
stroke: STATUS_BAR_COLORS.delivered,
|
|
1915
|
-
strokeWidth: 2,
|
|
1916
|
-
dot: false
|
|
1917
|
-
}
|
|
1918
|
-
),
|
|
1919
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1920
|
-
recharts.Line,
|
|
1921
|
-
{
|
|
1922
|
-
type: "natural",
|
|
1923
|
-
dataKey: "cancelled_count",
|
|
1924
|
-
name: "Cancelled",
|
|
1925
|
-
stroke: STATUS_BAR_COLORS.cancelled,
|
|
1926
|
-
strokeWidth: 2,
|
|
1927
|
-
dot: false
|
|
1928
|
-
}
|
|
1929
|
-
),
|
|
1930
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1931
|
-
recharts.Line,
|
|
1932
|
-
{
|
|
1933
|
-
type: "natural",
|
|
1934
|
-
dataKey: "exchange_count",
|
|
1935
|
-
name: "Exchanges",
|
|
1936
|
-
stroke: STATUS_BAR_COLORS.exchange,
|
|
1937
|
-
strokeWidth: 2,
|
|
1938
|
-
dot: false
|
|
1939
|
-
}
|
|
1940
|
-
),
|
|
1941
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1942
|
-
recharts.Line,
|
|
1943
|
-
{
|
|
1944
|
-
type: "natural",
|
|
1945
|
-
dataKey: "return_count",
|
|
1946
|
-
name: "Returns",
|
|
1947
|
-
stroke: STATUS_BAR_COLORS.return,
|
|
1948
|
-
strokeWidth: 2,
|
|
1949
|
-
dot: false
|
|
1950
|
-
}
|
|
1951
|
-
)
|
|
1952
|
-
]
|
|
1953
|
-
}
|
|
1954
|
-
) }) }) })
|
|
1955
|
-
}
|
|
1956
|
-
),
|
|
1957
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1958
|
-
AnalyticsSection,
|
|
1959
|
-
{
|
|
1960
|
-
variant: "atlas",
|
|
1961
|
-
className: "xl:col-span-5",
|
|
1962
|
-
title: "Recent orders (7 days)",
|
|
1963
|
-
description: "Daily order count vs 7-day average — time series.",
|
|
1964
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
1965
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-1.5", children: [
|
|
1966
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 px-2 py-2", children: [
|
|
1967
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Today" }),
|
|
1968
|
-
/* @__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" })
|
|
1969
|
-
] }),
|
|
1970
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-ui-border-base bg-ui-bg-subtle/50 px-2 py-2", children: [
|
|
1971
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "7-day avg" }),
|
|
1972
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-base font-semibold tracking-tight", children: todayOrdersAverage.toFixed(1) })
|
|
1973
|
-
] })
|
|
1974
|
-
] }),
|
|
1975
|
-
/* @__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(
|
|
1976
|
-
recharts.ComposedChart,
|
|
1977
|
-
{
|
|
1978
|
-
data: todayChartData,
|
|
1979
|
-
margin: { top: 4, right: 4, left: -4, bottom: 0 },
|
|
1980
|
-
children: [
|
|
1981
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "todayOrdersFill", x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
1982
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#3b82f6", stopOpacity: 0.2 }),
|
|
1983
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3b82f6", stopOpacity: 0 })
|
|
1984
|
-
] }) }),
|
|
1985
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1986
|
-
recharts.CartesianGrid,
|
|
1987
|
-
{
|
|
1988
|
-
stroke: "rgba(148,163,184,0.14)",
|
|
1989
|
-
strokeDasharray: "3 3",
|
|
1990
|
-
vertical: false
|
|
1991
|
-
}
|
|
1992
|
-
),
|
|
1993
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1994
|
-
recharts.XAxis,
|
|
1995
|
-
{
|
|
1996
|
-
dataKey: "date",
|
|
1997
|
-
tick: CHART_AXIS_TICK$2,
|
|
1998
|
-
tickLine: false,
|
|
1999
|
-
axisLine: false,
|
|
2000
|
-
tickFormatter: formatChartLabel$2
|
|
2001
|
-
}
|
|
2002
|
-
),
|
|
2003
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2004
|
-
recharts.YAxis,
|
|
2005
|
-
{
|
|
2006
|
-
width: 28,
|
|
2007
|
-
tick: CHART_AXIS_TICK$2,
|
|
2008
|
-
tickLine: false,
|
|
2009
|
-
axisLine: false,
|
|
2010
|
-
allowDecimals: false,
|
|
2011
|
-
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
2012
|
-
}
|
|
2013
|
-
),
|
|
2014
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersTodayTooltip, {}), cursor: { stroke: "rgba(148,163,184,0.35)", strokeWidth: 1 } }),
|
|
2015
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2016
|
-
recharts.ReferenceLine,
|
|
2017
|
-
{
|
|
2018
|
-
y: todayOrdersAverage,
|
|
2019
|
-
stroke: "rgba(148,163,184,0.65)",
|
|
2020
|
-
strokeDasharray: "4 4"
|
|
2021
|
-
}
|
|
2022
|
-
),
|
|
2023
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2024
|
-
recharts.Area,
|
|
2025
|
-
{
|
|
2026
|
-
type: "natural",
|
|
2027
|
-
dataKey: "orders_count",
|
|
2028
|
-
stroke: "none",
|
|
2029
|
-
fill: "url(#todayOrdersFill)",
|
|
2030
|
-
isAnimationActive: false
|
|
2031
|
-
}
|
|
2032
|
-
),
|
|
2033
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2034
|
-
recharts.Line,
|
|
2035
|
-
{
|
|
2036
|
-
type: "natural",
|
|
2037
|
-
dataKey: "orders_count",
|
|
2038
|
-
name: "Orders",
|
|
2039
|
-
stroke: "#3b82f6",
|
|
2040
|
-
strokeWidth: 2,
|
|
2041
|
-
dot: (props) => {
|
|
2042
|
-
const { cx, cy, payload } = props;
|
|
2043
|
-
if (payload && typeof payload === "object" && "isToday" in payload && payload.isToday) {
|
|
2044
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2045
|
-
"circle",
|
|
2046
|
-
{
|
|
2047
|
-
cx,
|
|
2048
|
-
cy,
|
|
2049
|
-
r: 4,
|
|
2050
|
-
fill: STATUS_BAR_COLORS.today,
|
|
2051
|
-
stroke: "var(--medusa-color-ui-bg-base)",
|
|
2052
|
-
strokeWidth: 1
|
|
2053
|
-
}
|
|
2054
|
-
);
|
|
2055
|
-
}
|
|
2056
|
-
return /* @__PURE__ */ jsxRuntime.jsx("circle", { cx, cy, r: 0, fill: "transparent" });
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
)
|
|
2060
|
-
]
|
|
2061
|
-
}
|
|
2062
|
-
) }) }),
|
|
2063
|
-
/* @__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: [
|
|
2064
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Note" }),
|
|
2065
|
-
/* @__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.` })
|
|
2066
|
-
] })
|
|
2067
|
-
] }) })
|
|
2068
|
-
}
|
|
2069
|
-
)
|
|
2070
|
-
] }),
|
|
2071
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 grid grid-cols-1 items-start gap-3 xl:grid-cols-12", children: [
|
|
2072
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2073
|
-
AnalyticsSection,
|
|
2074
|
-
{
|
|
2075
|
-
variant: "atlas",
|
|
2076
|
-
className: "xl:col-span-8",
|
|
2077
|
-
title: "Sales breakdown",
|
|
2078
|
-
description: salesBreakdownDescription,
|
|
2079
|
-
actionsBare: true,
|
|
2080
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-stretch gap-1 sm:flex-row sm:items-center sm:gap-2", children: [
|
|
2081
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2082
|
-
ui.Label,
|
|
2083
|
-
{
|
|
2084
|
-
htmlFor: "sales-breakdown-granularity",
|
|
2085
|
-
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
2086
|
-
children: "Breakdown"
|
|
2087
|
-
}
|
|
2088
|
-
),
|
|
2089
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2090
|
-
"select",
|
|
2091
|
-
{
|
|
2092
|
-
id: "sales-breakdown-granularity",
|
|
2093
|
-
value: salesGranularity,
|
|
2094
|
-
onChange: (e) => {
|
|
2095
|
-
const v = e.target.value;
|
|
2096
|
-
if (v === "hour" || v === "day" || v === "week" || v === "month") {
|
|
2097
|
-
setSalesGranularity(v);
|
|
2098
|
-
}
|
|
2099
|
-
},
|
|
2100
|
-
className: SELECT_CLASS_NAME$2,
|
|
2101
|
-
"aria-label": "Sales breakdown time bucket",
|
|
2102
|
-
children: SALES_GRANULARITY_TABS.map((tab) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: tab.value, children: tab.value === "hour" ? "Hour (last 24h)" : tab.value === "day" ? "Day (last 7 days)" : tab.value === "week" ? "Week (last 7 weeks)" : "Month (last 12 months)" }, tab.value))
|
|
2103
|
-
}
|
|
2104
|
-
)
|
|
2105
|
-
] }),
|
|
2106
|
-
children: salesBreakdownBody
|
|
2107
|
-
}
|
|
2108
|
-
),
|
|
2109
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2110
|
-
AnalyticsSection,
|
|
2111
|
-
{
|
|
2112
|
-
variant: "atlas",
|
|
2113
|
-
className: "xl:col-span-4",
|
|
2114
|
-
title: "Placed orders vs drafts",
|
|
2115
|
-
description: "Window order count vs current open draft orders (admin). Not storefront cart abandonment.",
|
|
2116
|
-
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: [
|
|
2117
|
-
/* @__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: [
|
|
2118
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2119
|
-
recharts.Pie,
|
|
2120
|
-
{
|
|
2121
|
-
data: ordersVsDraftsPie,
|
|
2122
|
-
dataKey: "value",
|
|
2123
|
-
nameKey: "name",
|
|
2124
|
-
cx: "50%",
|
|
2125
|
-
cy: "46%",
|
|
2126
|
-
innerRadius: 40,
|
|
2127
|
-
outerRadius: 58,
|
|
2128
|
-
paddingAngle: 2,
|
|
2129
|
-
children: ordersVsDraftsPie.map((slice, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2130
|
-
recharts.Cell,
|
|
2131
|
-
{
|
|
2132
|
-
fill: PIE_PALETTE[i % PIE_PALETTE.length]
|
|
2133
|
-
},
|
|
2134
|
-
slice.key
|
|
2135
|
-
))
|
|
2136
|
-
}
|
|
2137
|
-
),
|
|
2138
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2139
|
-
recharts.Tooltip,
|
|
2140
|
-
{
|
|
2141
|
-
content: ({ active, payload }) => {
|
|
2142
|
-
var _a2, _b2;
|
|
2143
|
-
return active && (payload == null ? void 0 : payload.length) ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTooltipCard, { variant: "compact", title: String(((_a2 = payload[0]) == null ? void 0 : _a2.name) ?? ""), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2144
|
-
"Count:",
|
|
2145
|
-
" ",
|
|
2146
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(
|
|
2147
|
-
Number((_b2 = payload[0]) == null ? void 0 : _b2.value) || 0
|
|
2148
|
-
).toLocaleString() })
|
|
2149
|
-
] }) }) : null;
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
),
|
|
2153
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2154
|
-
recharts.Legend,
|
|
2155
|
-
{
|
|
2156
|
-
wrapperStyle: { fontSize: 9, paddingTop: 0 },
|
|
2157
|
-
verticalAlign: "bottom",
|
|
2158
|
-
height: 22,
|
|
2159
|
-
iconType: "circle"
|
|
2160
|
-
}
|
|
2161
|
-
)
|
|
2162
|
-
] }) }) }),
|
|
2163
|
-
(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
|
|
2164
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2165
|
-
EmptyAnalyticsPanel$1,
|
|
2166
|
-
{
|
|
2167
|
-
title: "Nothing to compare",
|
|
2168
|
-
description: "No placed orders and no draft rows in this snapshot."
|
|
2169
|
-
}
|
|
2170
|
-
)
|
|
2171
|
-
}
|
|
2172
|
-
)
|
|
2173
|
-
] })
|
|
2174
|
-
] })
|
|
2175
|
-
] }) });
|
|
2176
|
-
}
|
|
2177
|
-
const KPI_ICON_BG$1 = {
|
|
2178
|
-
green: "bg-emerald-500/20",
|
|
2179
|
-
blue: "bg-sky-500/20",
|
|
2180
|
-
purple: "bg-violet-500/20",
|
|
2181
|
-
amber: "bg-amber-500/20"
|
|
2182
|
-
};
|
|
2183
|
-
const KPI_ICONS$1 = {
|
|
2184
|
-
green: icons.UsersSolid,
|
|
2185
|
-
blue: icons.User,
|
|
2186
|
-
purple: icons.UserGroup,
|
|
2187
|
-
amber: icons.Trash
|
|
2188
|
-
};
|
|
2189
|
-
function AtlasKpiCard$1({
|
|
2190
|
-
label,
|
|
2191
|
-
value,
|
|
2192
|
-
helper,
|
|
2193
|
-
accent
|
|
2194
|
-
}) {
|
|
2195
|
-
const Icon = KPI_ICONS$1[accent];
|
|
2196
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-[88px] min-w-0 flex-col rounded-xl border border-ui-border-base/80 bg-ui-bg-subtle/25 p-3 shadow-sm", children: [
|
|
2197
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
2198
|
-
/* @__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 }),
|
|
2199
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2200
|
-
"div",
|
|
2201
|
-
{
|
|
2202
|
-
className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-lg ${KPI_ICON_BG$1[accent]}`,
|
|
2203
|
-
"aria-hidden": true,
|
|
2204
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5 text-ui-fg-base" })
|
|
2205
|
-
}
|
|
2206
|
-
)
|
|
2207
|
-
] }),
|
|
2208
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 text-lg font-semibold tabular-nums tracking-tight text-ui-fg-base sm:text-xl", children: value }),
|
|
2209
|
-
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-[11px] leading-snug text-ui-fg-muted", children: helper }) : null
|
|
2210
|
-
] });
|
|
2211
|
-
}
|
|
2212
|
-
function CustomersOverTimeTooltip({
|
|
2213
|
-
active,
|
|
2214
|
-
payload,
|
|
2215
|
-
label
|
|
2216
|
-
}) {
|
|
2217
|
-
var _a, _b, _c;
|
|
2218
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2219
|
-
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
2220
|
-
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : label);
|
|
2221
|
-
const registered = Math.floor(((_b = payload.find((p) => p.name === "Registered")) == null ? void 0 : _b.value) ?? 0);
|
|
2222
|
-
const guest = Math.floor(((_c = payload.find((p) => p.name === "Guest")) == null ? void 0 : _c.value) ?? 0);
|
|
2223
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { title: String(displayLabel ?? ""), children: [
|
|
2224
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2225
|
-
"Registered: ",
|
|
2226
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: registered.toLocaleString() })
|
|
2227
|
-
] }),
|
|
2228
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2229
|
-
"Guest: ",
|
|
2230
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: guest.toLocaleString() })
|
|
2231
|
-
] })
|
|
2232
|
-
] });
|
|
2233
|
-
}
|
|
2234
|
-
function MixPieTooltip({
|
|
2235
|
-
active,
|
|
2236
|
-
payload
|
|
2237
|
-
}) {
|
|
2238
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2239
|
-
const row = payload[0];
|
|
2240
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTooltipCard, { variant: "compact", title: String(row.name ?? ""), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2241
|
-
"Count: ",
|
|
2242
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(row.value) || 0).toLocaleString() })
|
|
2243
|
-
] }) });
|
|
2244
|
-
}
|
|
2245
|
-
const COUNT_DAY_PRESETS = [
|
|
2246
|
-
{ value: "all", label: "All time" },
|
|
2247
|
-
{ value: "0", label: "Today" },
|
|
2248
|
-
{ value: "7", label: "Last 7 days" },
|
|
2249
|
-
{ value: "30", label: "Last 30 days" },
|
|
2250
|
-
{ value: "90", label: "Last 90 days" }
|
|
2251
|
-
];
|
|
2252
|
-
const GRAPH_PERIODS$1 = [
|
|
2253
|
-
{ value: "one_week", label: "One week" },
|
|
2254
|
-
{ value: "one_month", label: "One month" },
|
|
2255
|
-
{ value: "one_year", label: "One year" }
|
|
2256
|
-
];
|
|
2257
|
-
const REGISTERED_COLOR = "var(--medusa-color-ui-fg-interactive)";
|
|
2258
|
-
const GUEST_COLOR = "#6B7280";
|
|
2259
|
-
const CHART_AXIS_TICK$1 = { fontSize: 10, fill: "#e5e7eb" };
|
|
2260
|
-
const CHART_AXIS_LINE$1 = "#94a3b8";
|
|
2261
|
-
const SELECT_CLASS_NAME$1 = "h-9 w-full min-w-[8rem] max-w-full cursor-pointer appearance-none rounded 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 sm:w-auto sm:min-w-[132px] sm:max-w-[220px]";
|
|
2262
|
-
function formatChartLabel$1(value) {
|
|
2263
|
-
if (!value) return "";
|
|
2264
|
-
const parsed = new Date(value);
|
|
2265
|
-
if (Number.isNaN(parsed.getTime())) return value;
|
|
2266
|
-
return `${parsed.getUTCMonth() + 1}/${parsed.getUTCDate()}`;
|
|
2267
|
-
}
|
|
2268
|
-
function CustomersDashboard() {
|
|
2269
|
-
var _a, _b, _c;
|
|
2270
|
-
const [data, setData] = react.useState(null);
|
|
2271
|
-
const [loading, setLoading] = react.useState(true);
|
|
2272
|
-
const [error, setError] = react.useState(null);
|
|
2273
|
-
const [countDays, setCountDays] = react.useState("all");
|
|
2274
|
-
const [series, setSeries] = react.useState([]);
|
|
2275
|
-
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
2276
|
-
const [graphLoading, setGraphLoading] = react.useState(true);
|
|
2277
|
-
const [graphError, setGraphError] = react.useState(null);
|
|
2278
|
-
const [repeatStats, setRepeatStats] = react.useState(null);
|
|
2279
|
-
const [repeatLoading, setRepeatLoading] = react.useState(true);
|
|
2280
|
-
const [repeatError, setRepeatError] = react.useState(null);
|
|
2281
|
-
react.useEffect(() => {
|
|
2282
|
-
let cancelled = false;
|
|
2283
|
-
setLoading(true);
|
|
2284
|
-
setError(null);
|
|
2285
|
-
const url = `/admin/analytics/customers-summary?days=${countDays}`;
|
|
2286
|
-
fetch(url, { credentials: "include" }).then((res) => {
|
|
2287
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
2288
|
-
return res.json();
|
|
2289
|
-
}).then((body) => {
|
|
2290
|
-
if (!cancelled) setData(body);
|
|
2291
|
-
}).catch((e) => {
|
|
2292
|
-
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
2293
|
-
}).finally(() => {
|
|
2294
|
-
if (!cancelled) setLoading(false);
|
|
2295
|
-
});
|
|
2296
|
-
return () => {
|
|
2297
|
-
cancelled = true;
|
|
2298
|
-
};
|
|
2299
|
-
}, [countDays]);
|
|
2300
|
-
react.useEffect(() => {
|
|
2301
|
-
let cancelled = false;
|
|
2302
|
-
setGraphLoading(true);
|
|
2303
|
-
setGraphError(null);
|
|
2304
|
-
const url = `/admin/analytics/customers-over-time?period=${graphPeriod}`;
|
|
2305
|
-
fetch(url, { credentials: "include" }).then((res) => {
|
|
2306
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
2307
|
-
return res.json();
|
|
2308
|
-
}).then((body) => {
|
|
2309
|
-
if (!cancelled) setSeries(body.series ?? []);
|
|
2310
|
-
}).catch((e) => {
|
|
2311
|
-
if (!cancelled) setGraphError(e instanceof Error ? e.message : String(e));
|
|
2312
|
-
}).finally(() => {
|
|
2313
|
-
if (!cancelled) setGraphLoading(false);
|
|
2314
|
-
});
|
|
2315
|
-
return () => {
|
|
2316
|
-
cancelled = true;
|
|
2317
|
-
};
|
|
2318
|
-
}, [graphPeriod]);
|
|
2319
|
-
react.useEffect(() => {
|
|
2320
|
-
let cancelled = false;
|
|
2321
|
-
setRepeatLoading(true);
|
|
2322
|
-
setRepeatError(null);
|
|
2323
|
-
fetch(`/admin/analytics/repeat-customers?days=${countDays}`, {
|
|
2324
|
-
credentials: "include"
|
|
2325
|
-
}).then((res) => {
|
|
2326
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
2327
|
-
return res.json();
|
|
2328
|
-
}).then((body) => {
|
|
2329
|
-
if (!cancelled) setRepeatStats(body);
|
|
2330
|
-
}).catch((e) => {
|
|
2331
|
-
if (!cancelled) setRepeatError(e instanceof Error ? e.message : String(e));
|
|
2332
|
-
}).finally(() => {
|
|
2333
|
-
if (!cancelled) setRepeatLoading(false);
|
|
2334
|
-
});
|
|
2335
|
-
return () => {
|
|
2336
|
-
cancelled = true;
|
|
2337
|
-
};
|
|
2338
|
-
}, [countDays]);
|
|
2339
|
-
const selectedCountPeriodLabel = ((_a = COUNT_DAY_PRESETS.find((p) => p.value === countDays)) == null ? void 0 : _a.label) ?? "All time";
|
|
2340
|
-
const guestRegisteredPieData = react.useMemo(() => {
|
|
2341
|
-
const g = Math.max(0, Math.floor((data == null ? void 0 : data.guestCount) ?? 0));
|
|
2342
|
-
const r = Math.max(0, Math.floor((data == null ? void 0 : data.registeredCount) ?? 0));
|
|
2343
|
-
return [
|
|
2344
|
-
{ name: "Guest checkout", value: g, key: "guest" },
|
|
2345
|
-
{ name: "Registered", value: r, key: "registered" }
|
|
2346
|
-
].filter((s) => s.value > 0);
|
|
2347
|
-
}, [data]);
|
|
2348
|
-
const seriesWindowTotals = react.useMemo(() => {
|
|
2349
|
-
let registered = 0;
|
|
2350
|
-
let guest = 0;
|
|
2351
|
-
for (const row of series) {
|
|
2352
|
-
registered += Math.max(0, Math.floor(row.registered_count));
|
|
2353
|
-
guest += Math.max(0, Math.floor(row.guest_count));
|
|
2354
|
-
}
|
|
2355
|
-
return {
|
|
2356
|
-
registered,
|
|
2357
|
-
guest,
|
|
2358
|
-
points: series.length
|
|
2359
|
-
};
|
|
2360
|
-
}, [series]);
|
|
2361
|
-
const primaryStats = data ? [
|
|
2362
|
-
{
|
|
2363
|
-
label: "Guest customers",
|
|
2364
|
-
value: Math.floor(data.guestCount).toLocaleString(),
|
|
2365
|
-
helper: selectedCountPeriodLabel,
|
|
2366
|
-
accent: "blue"
|
|
2367
|
-
},
|
|
2368
|
-
{
|
|
2369
|
-
label: "Registered customers",
|
|
2370
|
-
value: Math.floor(data.registeredCount).toLocaleString(),
|
|
2371
|
-
helper: selectedCountPeriodLabel,
|
|
2372
|
-
accent: "purple"
|
|
2373
|
-
},
|
|
2374
|
-
{
|
|
2375
|
-
label: "Overall customers",
|
|
2376
|
-
value: Math.floor(data.totalCount).toLocaleString(),
|
|
2377
|
-
helper: "Guest + registered",
|
|
2378
|
-
accent: "green"
|
|
2379
|
-
},
|
|
2380
|
-
{
|
|
2381
|
-
label: "Deleted accounts",
|
|
2382
|
-
value: (data.deletedAccountsCount ?? 0).toLocaleString(),
|
|
2383
|
-
helper: "Removed profiles",
|
|
2384
|
-
accent: "amber"
|
|
2385
|
-
}
|
|
2386
|
-
] : [];
|
|
2387
|
-
if (loading) {
|
|
2388
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2389
|
-
"div",
|
|
2390
|
-
{
|
|
2391
|
-
className: "flex min-h-[200px] items-center justify-center",
|
|
2392
|
-
role: "status",
|
|
2393
|
-
"aria-label": "Loading analytics",
|
|
2394
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
2395
|
-
}
|
|
2396
|
-
);
|
|
2397
|
-
}
|
|
2398
|
-
if (error || !data) {
|
|
2399
|
-
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" }) });
|
|
2400
|
-
}
|
|
2401
|
-
return /* @__PURE__ */ jsxRuntime.jsx(AnalyticsDashboardShell, { className: "w-full min-w-0 max-w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full min-w-0 max-w-full overflow-hidden rounded-2xl border border-ui-border-base/80 bg-ui-bg-base shadow-sm", children: [
|
|
2402
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2403
|
-
AnalyticsDashboardHeader,
|
|
2404
|
-
{
|
|
2405
|
-
variant: "premium",
|
|
2406
|
-
appearance: "inset",
|
|
2407
|
-
actionsBare: true,
|
|
2408
|
-
title: "Customers",
|
|
2409
|
-
description: "Acquisition mix, repeat behavior, and signups over time — aligned with Orders and Products analytics.",
|
|
2410
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2411
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2412
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2413
|
-
ui.Label,
|
|
2414
|
-
{
|
|
2415
|
-
htmlFor: "customers-count-period",
|
|
2416
|
-
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
2417
|
-
children: "Period"
|
|
2418
|
-
}
|
|
2419
|
-
),
|
|
2420
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2421
|
-
"select",
|
|
2422
|
-
{
|
|
2423
|
-
id: "customers-count-period",
|
|
2424
|
-
value: countDays,
|
|
2425
|
-
onChange: (e) => setCountDays(e.target.value),
|
|
2426
|
-
className: SELECT_CLASS_NAME$1,
|
|
2427
|
-
children: COUNT_DAY_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
2428
|
-
}
|
|
2429
|
-
)
|
|
2430
|
-
] }),
|
|
2431
|
-
countDays !== "all" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2432
|
-
"button",
|
|
2433
|
-
{
|
|
2434
|
-
type: "button",
|
|
2435
|
-
onClick: () => setCountDays("all"),
|
|
2436
|
-
className: "text-xs text-ui-fg-muted transition-colors hover:text-ui-fg-base",
|
|
2437
|
-
"aria-label": "Show all customers (clear filter)",
|
|
2438
|
-
children: "Clear period"
|
|
2439
|
-
}
|
|
2440
|
-
) : null
|
|
2441
|
-
] })
|
|
2442
|
-
}
|
|
2443
|
-
),
|
|
2444
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 p-2 pt-0 sm:p-3 sm:pt-0 md:p-4 md:pt-0", children: [
|
|
2445
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
2446
|
-
/* @__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: "Customer overview" }) }),
|
|
2447
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 lg:grid-cols-4", children: primaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2448
|
-
AtlasKpiCard$1,
|
|
2449
|
-
{
|
|
2450
|
-
label: stat.label,
|
|
2451
|
-
value: stat.value,
|
|
2452
|
-
helper: stat.helper,
|
|
2453
|
-
accent: stat.accent
|
|
2454
|
-
},
|
|
2455
|
-
stat.label
|
|
2456
|
-
)) })
|
|
2457
|
-
] }),
|
|
2458
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 grid grid-cols-12 gap-2 lg:items-stretch", children: [
|
|
2459
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12 flex min-h-0 min-w-0 lg:col-span-5", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2460
|
-
AnalyticsSection,
|
|
2461
|
-
{
|
|
2462
|
-
variant: "atlas",
|
|
2463
|
-
className: "flex min-h-0 w-full flex-col",
|
|
2464
|
-
title: "Guest vs registered",
|
|
2465
|
-
description: "Mix for the selected period (same filter as overview).",
|
|
2466
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(168px,22vh)] min-h-[140px] w-full sm:h-[min(176px,24vh)]", children: guestRegisteredPieData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-muted text-[11px] leading-snug", children: "No guest or registered customers in this range." }) }) : /* @__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: [
|
|
2467
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2468
|
-
recharts.Pie,
|
|
2469
|
-
{
|
|
2470
|
-
data: guestRegisteredPieData,
|
|
2471
|
-
dataKey: "value",
|
|
2472
|
-
nameKey: "name",
|
|
2473
|
-
cx: "50%",
|
|
2474
|
-
cy: "48%",
|
|
2475
|
-
innerRadius: 34,
|
|
2476
|
-
outerRadius: 54,
|
|
2477
|
-
paddingAngle: 2,
|
|
2478
|
-
children: guestRegisteredPieData.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2479
|
-
recharts.Cell,
|
|
2480
|
-
{
|
|
2481
|
-
fill: entry.key === "guest" ? GUEST_COLOR : REGISTERED_COLOR
|
|
2482
|
-
},
|
|
2483
|
-
entry.key
|
|
2484
|
-
))
|
|
2485
|
-
}
|
|
2486
|
-
),
|
|
2487
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(MixPieTooltip, {}) }),
|
|
2488
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2489
|
-
recharts.Legend,
|
|
2490
|
-
{
|
|
2491
|
-
wrapperStyle: { fontSize: 9, paddingTop: 0 },
|
|
2492
|
-
verticalAlign: "bottom",
|
|
2493
|
-
height: 22,
|
|
2494
|
-
iconType: "circle"
|
|
2495
|
-
}
|
|
2496
|
-
)
|
|
2497
|
-
] }) }) }) })
|
|
2498
|
-
}
|
|
2499
|
-
) }),
|
|
2500
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12 flex min-h-0 min-w-0 lg:col-span-7", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2501
|
-
AnalyticsSection,
|
|
2502
|
-
{
|
|
2503
|
-
variant: "atlas",
|
|
2504
|
-
className: "flex min-h-0 w-full flex-col",
|
|
2505
|
-
title: "Repeat purchases",
|
|
2506
|
-
description: "Orders with a customer_id in the same window (guest checkouts excluded from rate).",
|
|
2507
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-1.5", children: repeatLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "flex flex-1 items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) : repeatError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "flex flex-1 items-center justify-center px-2 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-danger text-xs", children: repeatError }) }) : repeatStats ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
2508
|
-
((_b = repeatStats.ordersAnalyticsMeta) == null ? void 0 : _b.likely_truncated) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-full rounded-md border border-amber-400/25 bg-amber-500/10 px-2 py-1.5", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-base text-[10px] leading-snug", children: [
|
|
2509
|
-
"Repeat stats are computed from a sample of up to",
|
|
2510
|
-
" ",
|
|
2511
|
-
repeatStats.ordersAnalyticsMeta.max_fetch.toLocaleString(),
|
|
2512
|
-
" ",
|
|
2513
|
-
"orders; your true repeat rate may differ if you exceed that sample."
|
|
2514
|
-
] }) }) : null,
|
|
2515
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2516
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Repeat rate" }),
|
|
2517
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: `${repeatStats.repeat_rate_percent.toFixed(1)}%` }),
|
|
2518
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: repeatStats.window_days >= 365 ? "Approx. 12-month window" : `Last ${repeatStats.window_days} days` })
|
|
2519
|
-
] }) }),
|
|
2520
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2521
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "With orders" }),
|
|
2522
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: repeatStats.customers_with_orders.toLocaleString() }),
|
|
2523
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: "Customers in window" })
|
|
2524
|
-
] }) }),
|
|
2525
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2526
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Repeat (2+ orders)" }),
|
|
2527
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: repeatStats.repeat_customers.toLocaleString() }),
|
|
2528
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: "Multi-order customers" })
|
|
2529
|
-
] }) })
|
|
2530
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "flex flex-1 items-center justify-center py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "No data." }) }) })
|
|
2531
|
-
}
|
|
2532
|
-
) })
|
|
2533
|
-
] }),
|
|
2534
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2535
|
-
AnalyticsSection,
|
|
2536
|
-
{
|
|
2537
|
-
variant: "atlas",
|
|
2538
|
-
actionsBare: true,
|
|
2539
|
-
title: "Customers over time",
|
|
2540
|
-
description: "New registered vs guest activity for the chart range below.",
|
|
2541
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2542
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2543
|
-
ui.Label,
|
|
2544
|
-
{
|
|
2545
|
-
htmlFor: "customers-graph-period",
|
|
2546
|
-
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
2547
|
-
children: "Range"
|
|
2548
|
-
}
|
|
2549
|
-
),
|
|
2550
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2551
|
-
"select",
|
|
2552
|
-
{
|
|
2553
|
-
id: "customers-graph-period",
|
|
2554
|
-
value: graphPeriod,
|
|
2555
|
-
onChange: (e) => setGraphPeriod(e.target.value),
|
|
2556
|
-
className: SELECT_CLASS_NAME$1,
|
|
2557
|
-
children: GRAPH_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
2558
|
-
}
|
|
2559
|
-
)
|
|
2560
|
-
] }),
|
|
2561
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
2562
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-1.5 sm:grid-cols-3", children: [
|
|
2563
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2564
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window registered (sum)" }),
|
|
2565
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: seriesWindowTotals.registered.toLocaleString() }),
|
|
2566
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
2567
|
-
"Across ",
|
|
2568
|
-
seriesWindowTotals.points,
|
|
2569
|
-
" buckets"
|
|
2570
|
-
] })
|
|
2571
|
-
] }) }),
|
|
2572
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2573
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window guest (sum)" }),
|
|
2574
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: seriesWindowTotals.guest.toLocaleString() }),
|
|
2575
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: "Same range as chart" })
|
|
2576
|
-
] }) }),
|
|
2577
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
2578
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Chart period" }),
|
|
2579
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: ((_c = GRAPH_PERIODS$1.find((p) => p.value === graphPeriod)) == null ? void 0 : _c.label) ?? graphPeriod }),
|
|
2580
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] leading-snug", children: "Independent of summary period filter" })
|
|
2581
|
-
] }) })
|
|
2582
|
-
] }),
|
|
2583
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[212px] w-full sm:h-[228px]", children: graphLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2584
|
-
"div",
|
|
2585
|
-
{
|
|
2586
|
-
className: "flex h-full items-center justify-center",
|
|
2587
|
-
role: "status",
|
|
2588
|
-
"aria-label": "Loading customers over time",
|
|
2589
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" })
|
|
2590
|
-
}
|
|
2591
|
-
) : graphError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-center text-ui-fg-danger text-xs", children: graphError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2592
|
-
recharts.LineChart,
|
|
2593
|
-
{
|
|
2594
|
-
data: series,
|
|
2595
|
-
margin: { top: 6, right: 8, left: 0, bottom: 4 },
|
|
2596
|
-
children: [
|
|
2597
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2598
|
-
recharts.CartesianGrid,
|
|
2599
|
-
{
|
|
2600
|
-
strokeDasharray: "3 3",
|
|
2601
|
-
vertical: false,
|
|
2602
|
-
stroke: "rgba(148,163,184,0.12)"
|
|
2603
|
-
}
|
|
2604
|
-
),
|
|
2605
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2606
|
-
recharts.XAxis,
|
|
2607
|
-
{
|
|
2608
|
-
dataKey: "date",
|
|
2609
|
-
tick: CHART_AXIS_TICK$1,
|
|
2610
|
-
stroke: CHART_AXIS_LINE$1,
|
|
2611
|
-
tickLine: false,
|
|
2612
|
-
axisLine: { stroke: CHART_AXIS_LINE$1 },
|
|
2613
|
-
tickFormatter: (value) => {
|
|
2614
|
-
const row = series.find((p) => p.date === String(value));
|
|
2615
|
-
if (row == null ? void 0 : row.label) return row.label;
|
|
2616
|
-
return formatChartLabel$1(
|
|
2617
|
-
(row == null ? void 0 : row.date) ?? (typeof value === "string" ? value : String(value))
|
|
2618
|
-
);
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
),
|
|
2622
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2623
|
-
recharts.YAxis,
|
|
2624
|
-
{
|
|
2625
|
-
width: 36,
|
|
2626
|
-
tick: CHART_AXIS_TICK$1,
|
|
2627
|
-
stroke: CHART_AXIS_LINE$1,
|
|
2628
|
-
tickLine: false,
|
|
2629
|
-
axisLine: { stroke: CHART_AXIS_LINE$1 },
|
|
2630
|
-
tickFormatter: (v) => String(Math.floor(Number(v))),
|
|
2631
|
-
allowDecimals: false
|
|
2632
|
-
}
|
|
2633
|
-
),
|
|
2634
|
-
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(CustomersOverTimeTooltip, {}) }),
|
|
2635
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2636
|
-
recharts.Legend,
|
|
2637
|
-
{
|
|
2638
|
-
wrapperStyle: { fontSize: 10, paddingTop: 4 },
|
|
2639
|
-
iconType: "circle"
|
|
2640
|
-
}
|
|
2641
|
-
),
|
|
2642
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2643
|
-
recharts.Line,
|
|
2644
|
-
{
|
|
2645
|
-
type: "monotone",
|
|
2646
|
-
dataKey: "registered_count",
|
|
2647
|
-
name: "Registered",
|
|
2648
|
-
stroke: REGISTERED_COLOR,
|
|
2649
|
-
strokeWidth: 2,
|
|
2650
|
-
dot: { r: 2 }
|
|
2651
|
-
}
|
|
2652
|
-
),
|
|
2653
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2654
|
-
recharts.Line,
|
|
2655
|
-
{
|
|
2656
|
-
type: "monotone",
|
|
2657
|
-
dataKey: "guest_count",
|
|
2658
|
-
name: "Guest",
|
|
2659
|
-
stroke: GUEST_COLOR,
|
|
2660
|
-
strokeWidth: 2,
|
|
2661
|
-
dot: { r: 2 }
|
|
2662
|
-
}
|
|
2663
|
-
)
|
|
2664
|
-
]
|
|
2665
|
-
}
|
|
2666
|
-
) }) }) })
|
|
2667
|
-
] })
|
|
2668
|
-
}
|
|
2669
|
-
)
|
|
2670
|
-
] })
|
|
2671
|
-
] }) });
|
|
2672
|
-
}
|
|
2673
|
-
function buildAnalyticsUrl(path, params) {
|
|
2674
|
-
const search = new URLSearchParams();
|
|
2675
|
-
for (const [key, value] of Object.entries(params)) {
|
|
2676
|
-
if (value !== void 0 && value !== null) {
|
|
2677
|
-
search.set(key, String(value));
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
const query = search.toString();
|
|
2681
|
-
return query ? `${path}?${query}` : path;
|
|
2682
|
-
}
|
|
2683
|
-
const SUMMARY_PERIODS = [
|
|
2684
|
-
{ value: "all", label: "All time" },
|
|
2685
|
-
{ value: "0", label: "Today" },
|
|
2686
|
-
{ value: "7", label: "Last 7 days" },
|
|
2687
|
-
{ value: "30", label: "Last 30 days" },
|
|
2688
|
-
{ value: "90", label: "Last 90 days" }
|
|
2689
|
-
];
|
|
2690
|
-
const GRAPH_PERIODS = [
|
|
2691
|
-
{ value: "one_week", label: "One week" },
|
|
2692
|
-
{ value: "one_month", label: "One month" },
|
|
2693
|
-
{ value: "one_year", label: "One year" }
|
|
2694
|
-
];
|
|
2695
|
-
const TOP_SELLER_PERIODS = [
|
|
2696
|
-
{ value: "week", label: "Week" },
|
|
2697
|
-
{ value: "month", label: "Month" },
|
|
2698
|
-
{ value: "year", label: "Year" },
|
|
2699
|
-
{ value: "all", label: "All time" }
|
|
2700
|
-
];
|
|
2701
|
-
const PRODUCT_CHART_COLORS = {
|
|
2702
|
-
units: "#38BDF8",
|
|
2703
|
-
views: "#C084FC",
|
|
2704
|
-
revenue: "#D946EF",
|
|
2705
|
-
revenueMuted: "#94A3B8",
|
|
2706
|
-
scatter: "#A855F7"
|
|
2707
|
-
};
|
|
2708
|
-
const KPI_ICON_BG = {
|
|
2709
|
-
green: "bg-emerald-500/20",
|
|
2710
|
-
blue: "bg-sky-500/20",
|
|
2711
|
-
purple: "bg-violet-500/20",
|
|
2712
|
-
amber: "bg-amber-500/20"
|
|
2713
|
-
};
|
|
2714
|
-
const KPI_ICONS = {
|
|
2715
|
-
green: icons.CurrencyDollar,
|
|
2716
|
-
blue: icons.ShoppingCart,
|
|
2717
|
-
purple: icons.Tag,
|
|
2718
|
-
amber: icons.CubeSolid
|
|
2719
|
-
};
|
|
2720
|
-
const CHART_AXIS_TICK = { fontSize: 10, fill: "#e5e7eb" };
|
|
2721
|
-
const CHART_AXIS_TICK_SM = { fontSize: 9, fill: "#e5e7eb" };
|
|
2722
|
-
const CHART_AXIS_LINE = "#94a3b8";
|
|
2723
|
-
const SELECT_CLASS_NAME = "h-9 w-full min-w-[8rem] max-w-full cursor-pointer appearance-none rounded 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 sm:w-auto sm:min-w-[132px] sm:max-w-[220px]";
|
|
2724
|
-
const LEADERBOARD_TABLE_CLASS = "min-w-[32rem] sm:min-w-[36rem] max-w-none";
|
|
2725
|
-
const OPPORTUNITIES_TABLE_CLASS = "min-w-[30rem] sm:min-w-[34rem] max-w-none";
|
|
2726
|
-
const TABLE_HEAD_CELL_NOWRAP = "whitespace-nowrap";
|
|
2727
|
-
function formatChartLabel(value) {
|
|
2728
|
-
if (!value) return "";
|
|
2729
|
-
const parsed = new Date(value);
|
|
2730
|
-
if (Number.isNaN(parsed.getTime())) return value;
|
|
2731
|
-
return `${parsed.getUTCMonth() + 1}/${parsed.getUTCDate()}`;
|
|
2732
|
-
}
|
|
2733
|
-
function formatRatio(value) {
|
|
2734
|
-
if (value === null || Number.isNaN(value)) return "N/A";
|
|
2735
|
-
return `${value.toFixed(2)}x`;
|
|
2736
|
-
}
|
|
2737
|
-
function formatShortNumber(value) {
|
|
2738
|
-
return new Intl.NumberFormat("en-IN", {
|
|
2739
|
-
notation: "compact",
|
|
2740
|
-
maximumFractionDigits: value < 10 ? 1 : 0
|
|
2741
|
-
}).format(value);
|
|
2742
|
-
}
|
|
2743
|
-
function topSellerPeriodToGraphPeriod(period) {
|
|
2744
|
-
if (period === "month") return "one_month";
|
|
2745
|
-
if (period === "year" || period === "all") return "one_year";
|
|
2746
|
-
return "one_week";
|
|
2747
|
-
}
|
|
2748
|
-
function summaryDaysToGraphPeriod(days) {
|
|
2749
|
-
if (days === "30") return "one_month";
|
|
2750
|
-
if (days === "all" || days === "90") return "one_year";
|
|
2751
|
-
return "one_week";
|
|
2752
|
-
}
|
|
2753
|
-
function isProductOverTimePoint(value) {
|
|
2754
|
-
if (typeof value !== "object" || value === null) return false;
|
|
2755
|
-
const v = value;
|
|
2756
|
-
return typeof v.date === "string" && typeof v.units_sold === "number" && typeof v.revenue === "number" && typeof v.views === "number";
|
|
2757
|
-
}
|
|
2758
|
-
function TrendTooltip({
|
|
2759
|
-
active,
|
|
2760
|
-
payload,
|
|
2761
|
-
label
|
|
2762
|
-
}) {
|
|
2763
|
-
var _a, _b, _c, _d, _e;
|
|
2764
|
-
const { formatCurrency } = useAnalyticsCurrency();
|
|
2765
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2766
|
-
const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
2767
|
-
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
2768
|
-
const unitsSold = ((_c = payload.find((entry) => entry.name === "Units sold")) == null ? void 0 : _c.value) ?? 0;
|
|
2769
|
-
const views = ((_d = payload.find((entry) => entry.name === "Views")) == null ? void 0 : _d.value) ?? 0;
|
|
2770
|
-
const revenue = ((_e = payload.find((entry) => entry.name === "Revenue")) == null ? void 0 : _e.value) ?? 0;
|
|
2771
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { title: displayLabel, children: [
|
|
2772
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2773
|
-
"Units sold: ",
|
|
2774
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(unitsSold)).toLocaleString() })
|
|
2775
|
-
] }),
|
|
2776
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2777
|
-
"Views: ",
|
|
2778
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(views)).toLocaleString() })
|
|
2779
|
-
] }),
|
|
2780
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2781
|
-
"Revenue: ",
|
|
2782
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
2783
|
-
] })
|
|
2784
|
-
] });
|
|
2785
|
-
}
|
|
2786
|
-
function MiniTrendTooltip({
|
|
2787
|
-
active,
|
|
2788
|
-
payload,
|
|
2789
|
-
label,
|
|
2790
|
-
productHeadline,
|
|
2791
|
-
hideViewsRow,
|
|
2792
|
-
hideUnitsRow,
|
|
2793
|
-
hideRevenueRow
|
|
2794
|
-
}) {
|
|
2795
|
-
var _a, _b, _c, _d, _e;
|
|
2796
|
-
const { formatCurrency } = useAnalyticsCurrency();
|
|
2797
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2798
|
-
const row = isProductOverTimePoint((_a = payload[0]) == null ? void 0 : _a.payload) ? (_b = payload[0]) == null ? void 0 : _b.payload : void 0;
|
|
2799
|
-
const labelKey = label !== void 0 && label !== null ? String(label) : "";
|
|
2800
|
-
const displayLabel = (row == null ? void 0 : row.label) ?? (labelKey ? new Date(labelKey).toLocaleDateString() : "");
|
|
2801
|
-
const unitsSold = ((_c = payload.find((entry) => entry.name === "Units sold")) == null ? void 0 : _c.value) ?? 0;
|
|
2802
|
-
const views = ((_d = payload.find((entry) => entry.name === "Views")) == null ? void 0 : _d.value) ?? 0;
|
|
2803
|
-
const revenue = ((_e = payload.find((entry) => entry.name === "Revenue")) == null ? void 0 : _e.value) ?? 0;
|
|
2804
|
-
const title = productHeadline ?? displayLabel;
|
|
2805
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { variant: "compact", title, children: [
|
|
2806
|
-
productHeadline ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 text-[10px] leading-tight opacity-80", children: displayLabel }) : null,
|
|
2807
|
-
!hideUnitsRow ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2808
|
-
"Units sold: ",
|
|
2809
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(unitsSold)).toLocaleString() })
|
|
2810
|
-
] }) : null,
|
|
2811
|
-
!hideViewsRow ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2812
|
-
"Views: ",
|
|
2813
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(Number(views)).toLocaleString() })
|
|
2814
|
-
] }) : null,
|
|
2815
|
-
!hideRevenueRow ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2816
|
-
"Revenue: ",
|
|
2817
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
2818
|
-
] }) : null
|
|
2819
|
-
] });
|
|
2820
|
-
}
|
|
2821
|
-
function ProductBarTooltip({
|
|
2822
|
-
active,
|
|
2823
|
-
payload,
|
|
2824
|
-
showViews = true
|
|
2825
|
-
}) {
|
|
2826
|
-
var _a;
|
|
2827
|
-
const { formatCurrency } = useAnalyticsCurrency();
|
|
2828
|
-
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
2829
|
-
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
2830
|
-
if (!row) return null;
|
|
2831
|
-
const unitsSold = "units_sold" in row ? row.units_sold : 0;
|
|
2832
|
-
const totalViews = "total_views" in row ? row.total_views : 0;
|
|
2833
|
-
const revenue = "revenue" in row ? row.revenue : 0;
|
|
2834
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(AnalyticsTooltipCard, { variant: "compact", title: row.product_title, children: [
|
|
2835
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2836
|
-
"Units sold: ",
|
|
2837
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(unitsSold).toLocaleString() })
|
|
2838
|
-
] }),
|
|
2839
|
-
showViews ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2840
|
-
"Views: ",
|
|
2841
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(totalViews).toLocaleString() })
|
|
2842
|
-
] }) : null,
|
|
2843
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2844
|
-
"Revenue: ",
|
|
2845
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
2846
|
-
] })
|
|
2847
|
-
] });
|
|
2848
|
-
}
|
|
2849
|
-
function AtlasKpiCard({
|
|
2850
|
-
label,
|
|
2851
|
-
value,
|
|
2852
|
-
helper,
|
|
2853
|
-
accent
|
|
2854
|
-
}) {
|
|
2855
|
-
const Icon = KPI_ICONS[accent];
|
|
2856
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-[88px] min-w-0 flex-col rounded-xl border border-ui-border-base/80 bg-ui-bg-subtle/25 p-3 shadow-sm", children: [
|
|
2857
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
2858
|
-
/* @__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 }),
|
|
2859
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2860
|
-
"div",
|
|
2861
|
-
{
|
|
2862
|
-
className: `flex h-7 w-7 shrink-0 items-center justify-center rounded-lg ${KPI_ICON_BG[accent]}`,
|
|
2863
|
-
"aria-hidden": true,
|
|
2864
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5 text-ui-fg-base" })
|
|
2865
|
-
}
|
|
2866
|
-
)
|
|
2867
|
-
] }),
|
|
2868
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 text-lg font-semibold tabular-nums tracking-tight text-ui-fg-base sm:text-xl", children: value }),
|
|
2869
|
-
helper ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-[11px] leading-snug text-ui-fg-muted", children: helper }) : null
|
|
2870
|
-
] });
|
|
2871
|
-
}
|
|
2872
|
-
function EmptyAnalyticsPanel({ title, description }) {
|
|
2873
|
-
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: [
|
|
2874
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-xs font-semibold", children: title }),
|
|
2875
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[11px] leading-snug", children: description })
|
|
2876
|
-
] });
|
|
2877
|
-
}
|
|
2878
|
-
function ProductsDashboard() {
|
|
2879
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
2880
|
-
const { formatCurrency, formatCompactCurrency } = useAnalyticsCurrency();
|
|
2881
|
-
const chartGradientPrefix = react.useId().replace(/:/g, "");
|
|
2882
|
-
const gid = (suffix) => `${chartGradientPrefix}-${suffix}`;
|
|
2883
|
-
const [summaryDays, setSummaryDays] = react.useState("all");
|
|
2884
|
-
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
2885
|
-
const [topSellerPeriod, setTopSellerPeriod] = react.useState("week");
|
|
2886
|
-
const [salesChannelId, setSalesChannelId] = react.useState("all");
|
|
2887
|
-
const [salesChannels, setSalesChannels] = react.useState([]);
|
|
2888
|
-
const [filtersLoading, setFiltersLoading] = react.useState(true);
|
|
2889
|
-
const [filtersError, setFiltersError] = react.useState(null);
|
|
2890
|
-
const [summary, setSummary] = react.useState(null);
|
|
2891
|
-
const [summaryLoading, setSummaryLoading] = react.useState(true);
|
|
2892
|
-
const [summaryError, setSummaryError] = react.useState(null);
|
|
2893
|
-
const [overTime, setOverTime] = react.useState(null);
|
|
2894
|
-
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
2895
|
-
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
2896
|
-
const [topSellers, setTopSellers] = react.useState(null);
|
|
2897
|
-
const [topSellersLoading, setTopSellersLoading] = react.useState(true);
|
|
2898
|
-
const [topSellersError, setTopSellersError] = react.useState(null);
|
|
2899
|
-
const [performance, setPerformance] = react.useState(null);
|
|
2900
|
-
const [performanceLoading, setPerformanceLoading] = react.useState(true);
|
|
2901
|
-
const [performanceError, setPerformanceError] = react.useState(null);
|
|
2902
|
-
const [bestSellersTrend, setBestSellersTrend] = react.useState(null);
|
|
2903
|
-
const [bestSellersTrendLoading, setBestSellersTrendLoading] = react.useState(true);
|
|
2904
|
-
const [bestSellersTrendError, setBestSellersTrendError] = react.useState(null);
|
|
2905
|
-
const [mostViewedTrend, setMostViewedTrend] = react.useState(null);
|
|
2906
|
-
const [mostViewedTrendLoading, setMostViewedTrendLoading] = react.useState(true);
|
|
2907
|
-
const [mostViewedTrendError, setMostViewedTrendError] = react.useState(null);
|
|
2908
|
-
react.useEffect(() => {
|
|
2909
|
-
let cancelled = false;
|
|
2910
|
-
setFiltersLoading(true);
|
|
2911
|
-
setFiltersError(null);
|
|
2912
|
-
fetch("/admin/analytics/products-filters", { credentials: "include" }).then((res) => {
|
|
2913
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
2914
|
-
return res.json();
|
|
2915
|
-
}).then((body) => {
|
|
2916
|
-
if (!cancelled) {
|
|
2917
|
-
setSalesChannels(body.salesChannels ?? []);
|
|
2918
|
-
}
|
|
2919
|
-
}).catch((error) => {
|
|
2920
|
-
if (!cancelled) {
|
|
2921
|
-
setFiltersError(error instanceof Error ? error.message : String(error));
|
|
2922
|
-
}
|
|
2923
|
-
}).finally(() => {
|
|
2924
|
-
if (!cancelled) setFiltersLoading(false);
|
|
2925
|
-
});
|
|
2926
|
-
return () => {
|
|
2927
|
-
cancelled = true;
|
|
2928
|
-
};
|
|
2929
|
-
}, []);
|
|
2930
|
-
react.useEffect(() => {
|
|
2931
|
-
let cancelled = false;
|
|
2932
|
-
setSummaryLoading(true);
|
|
2933
|
-
setSummaryError(null);
|
|
2934
|
-
fetch(
|
|
2935
|
-
buildAnalyticsUrl("/admin/analytics/products-summary", {
|
|
2936
|
-
days: summaryDays,
|
|
2937
|
-
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
2938
|
-
}),
|
|
2939
|
-
{ credentials: "include" }
|
|
2940
|
-
).then((res) => {
|
|
2941
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
2942
|
-
return res.json();
|
|
2943
|
-
}).then((body) => {
|
|
2944
|
-
if (!cancelled) setSummary(body);
|
|
2945
|
-
}).catch((error) => {
|
|
2946
|
-
if (!cancelled) {
|
|
2947
|
-
setSummaryError(error instanceof Error ? error.message : String(error));
|
|
2948
|
-
}
|
|
2949
|
-
}).finally(() => {
|
|
2950
|
-
if (!cancelled) setSummaryLoading(false);
|
|
2951
|
-
});
|
|
2952
|
-
return () => {
|
|
2953
|
-
cancelled = true;
|
|
2954
|
-
};
|
|
2955
|
-
}, [salesChannelId, summaryDays]);
|
|
2956
|
-
react.useEffect(() => {
|
|
2957
|
-
let cancelled = false;
|
|
2958
|
-
setOverTimeLoading(true);
|
|
2959
|
-
setOverTimeError(null);
|
|
2960
|
-
fetch(
|
|
2961
|
-
buildAnalyticsUrl("/admin/analytics/products-over-time", {
|
|
2962
|
-
period: graphPeriod,
|
|
2963
|
-
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
2964
|
-
}),
|
|
2965
|
-
{ credentials: "include" }
|
|
2966
|
-
).then((res) => {
|
|
2967
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
2968
|
-
return res.json();
|
|
2969
|
-
}).then((body) => {
|
|
2970
|
-
if (!cancelled) setOverTime(body);
|
|
2971
|
-
}).catch((error) => {
|
|
2972
|
-
if (!cancelled) {
|
|
2973
|
-
setOverTimeError(error instanceof Error ? error.message : String(error));
|
|
2974
|
-
}
|
|
2975
|
-
}).finally(() => {
|
|
2976
|
-
if (!cancelled) setOverTimeLoading(false);
|
|
2977
|
-
});
|
|
2978
|
-
return () => {
|
|
2979
|
-
cancelled = true;
|
|
2980
|
-
};
|
|
2981
|
-
}, [graphPeriod, salesChannelId]);
|
|
2982
|
-
react.useEffect(() => {
|
|
2983
|
-
let cancelled = false;
|
|
2984
|
-
setTopSellersLoading(true);
|
|
2985
|
-
setTopSellersError(null);
|
|
2986
|
-
fetch(
|
|
2987
|
-
buildAnalyticsUrl("/admin/analytics/products-top-sellers", {
|
|
2988
|
-
period: topSellerPeriod,
|
|
2989
|
-
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
2990
|
-
}),
|
|
2991
|
-
{ credentials: "include" }
|
|
2992
|
-
).then((res) => {
|
|
2993
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
2994
|
-
return res.json();
|
|
2995
|
-
}).then((body) => {
|
|
2996
|
-
if (!cancelled) setTopSellers(body);
|
|
2997
|
-
}).catch((error) => {
|
|
2998
|
-
if (!cancelled) {
|
|
2999
|
-
setTopSellersError(error instanceof Error ? error.message : String(error));
|
|
3000
|
-
}
|
|
3001
|
-
}).finally(() => {
|
|
3002
|
-
if (!cancelled) setTopSellersLoading(false);
|
|
3003
|
-
});
|
|
3004
|
-
return () => {
|
|
3005
|
-
cancelled = true;
|
|
3006
|
-
};
|
|
3007
|
-
}, [salesChannelId, topSellerPeriod]);
|
|
3008
|
-
react.useEffect(() => {
|
|
3009
|
-
var _a2;
|
|
3010
|
-
let cancelled = false;
|
|
3011
|
-
if (topSellersLoading) {
|
|
3012
|
-
setBestSellersTrendLoading(true);
|
|
3013
|
-
return () => {
|
|
3014
|
-
cancelled = true;
|
|
3015
|
-
};
|
|
3016
|
-
}
|
|
3017
|
-
if (topSellersError) {
|
|
3018
|
-
setBestSellersTrendLoading(false);
|
|
3019
|
-
setBestSellersTrendError(topSellersError);
|
|
3020
|
-
setBestSellersTrend(null);
|
|
3021
|
-
return () => {
|
|
3022
|
-
cancelled = true;
|
|
3023
|
-
};
|
|
3024
|
-
}
|
|
3025
|
-
const topSellersByUnits2 = [...(topSellers == null ? void 0 : topSellers.products) ?? []].sort((a, b) => {
|
|
3026
|
-
if (b.units_sold !== a.units_sold) return b.units_sold - a.units_sold;
|
|
3027
|
-
if (b.order_count !== a.order_count) return b.order_count - a.order_count;
|
|
3028
|
-
return b.revenue - a.revenue;
|
|
3029
|
-
});
|
|
3030
|
-
const topProductId = (_a2 = topSellersByUnits2[0]) == null ? void 0 : _a2.product_id;
|
|
3031
|
-
if (!topProductId) {
|
|
3032
|
-
setBestSellersTrendLoading(false);
|
|
3033
|
-
setBestSellersTrendError(null);
|
|
3034
|
-
setBestSellersTrend({
|
|
3035
|
-
series: [],
|
|
3036
|
-
productViewsConnected: (topSellers == null ? void 0 : topSellers.productViewsConnected) ?? false,
|
|
3037
|
-
product: null
|
|
3038
|
-
});
|
|
3039
|
-
return () => {
|
|
3040
|
-
cancelled = true;
|
|
3041
|
-
};
|
|
3042
|
-
}
|
|
3043
|
-
setBestSellersTrendLoading(true);
|
|
3044
|
-
setBestSellersTrendError(null);
|
|
3045
|
-
const period = topSellerPeriodToGraphPeriod(topSellerPeriod);
|
|
3046
|
-
fetch(
|
|
3047
|
-
buildAnalyticsUrl("/admin/analytics/products-over-time", {
|
|
3048
|
-
period,
|
|
3049
|
-
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null,
|
|
3050
|
-
product_id: topProductId
|
|
3051
|
-
}),
|
|
3052
|
-
{ credentials: "include" }
|
|
3053
|
-
).then((res) => {
|
|
3054
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
3055
|
-
return res.json();
|
|
3056
|
-
}).then((body) => {
|
|
3057
|
-
if (!cancelled) setBestSellersTrend(body);
|
|
3058
|
-
}).catch((error) => {
|
|
3059
|
-
if (!cancelled) {
|
|
3060
|
-
setBestSellersTrendError(error instanceof Error ? error.message : String(error));
|
|
3061
|
-
}
|
|
3062
|
-
}).finally(() => {
|
|
3063
|
-
if (!cancelled) setBestSellersTrendLoading(false);
|
|
3064
|
-
});
|
|
3065
|
-
return () => {
|
|
3066
|
-
cancelled = true;
|
|
3067
|
-
};
|
|
3068
|
-
}, [salesChannelId, topSellerPeriod, topSellersLoading, topSellersError, topSellers]);
|
|
77
|
+
const [overTimePeriod, setOverTimePeriod] = react.useState("one_week");
|
|
78
|
+
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
79
|
+
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
3069
80
|
react.useEffect(() => {
|
|
3070
|
-
var _a2, _b2;
|
|
3071
81
|
let cancelled = false;
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
};
|
|
3077
|
-
}
|
|
3078
|
-
if (performanceError) {
|
|
3079
|
-
setMostViewedTrendLoading(false);
|
|
3080
|
-
setMostViewedTrendError(performanceError);
|
|
3081
|
-
setMostViewedTrend(null);
|
|
3082
|
-
return () => {
|
|
3083
|
-
cancelled = true;
|
|
3084
|
-
};
|
|
3085
|
-
}
|
|
3086
|
-
if (!(performance == null ? void 0 : performance.productViewsConnected)) {
|
|
3087
|
-
setMostViewedTrendLoading(false);
|
|
3088
|
-
setMostViewedTrendError(null);
|
|
3089
|
-
setMostViewedTrend(null);
|
|
3090
|
-
return () => {
|
|
3091
|
-
cancelled = true;
|
|
3092
|
-
};
|
|
3093
|
-
}
|
|
3094
|
-
const topViewedId = (_b2 = (_a2 = performance.topViewedProducts) == null ? void 0 : _a2[0]) == null ? void 0 : _b2.product_id;
|
|
3095
|
-
if (!topViewedId) {
|
|
3096
|
-
setMostViewedTrendLoading(false);
|
|
3097
|
-
setMostViewedTrendError(null);
|
|
3098
|
-
setMostViewedTrend({
|
|
3099
|
-
series: [],
|
|
3100
|
-
productViewsConnected: true,
|
|
3101
|
-
product: null
|
|
3102
|
-
});
|
|
3103
|
-
return () => {
|
|
3104
|
-
cancelled = true;
|
|
3105
|
-
};
|
|
3106
|
-
}
|
|
3107
|
-
setMostViewedTrendLoading(true);
|
|
3108
|
-
setMostViewedTrendError(null);
|
|
3109
|
-
const period = summaryDaysToGraphPeriod(summaryDays);
|
|
3110
|
-
fetch(
|
|
3111
|
-
buildAnalyticsUrl("/admin/analytics/products-over-time", {
|
|
3112
|
-
period,
|
|
3113
|
-
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null,
|
|
3114
|
-
product_id: topViewedId
|
|
3115
|
-
}),
|
|
3116
|
-
{ credentials: "include" }
|
|
3117
|
-
).then((res) => {
|
|
82
|
+
setLoading(true);
|
|
83
|
+
setError(null);
|
|
84
|
+
const url = `/admin/analytics/orders-summary?days=${filter}`;
|
|
85
|
+
fetch(url).then((res) => {
|
|
3118
86
|
if (!res.ok) throw new Error(res.statusText);
|
|
3119
87
|
return res.json();
|
|
3120
88
|
}).then((body) => {
|
|
3121
|
-
if (!cancelled)
|
|
3122
|
-
}).catch((
|
|
3123
|
-
if (!cancelled)
|
|
3124
|
-
setMostViewedTrendError(error instanceof Error ? error.message : String(error));
|
|
3125
|
-
}
|
|
89
|
+
if (!cancelled) setData(body);
|
|
90
|
+
}).catch((e) => {
|
|
91
|
+
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
3126
92
|
}).finally(() => {
|
|
3127
|
-
if (!cancelled)
|
|
93
|
+
if (!cancelled) setLoading(false);
|
|
3128
94
|
});
|
|
3129
95
|
return () => {
|
|
3130
96
|
cancelled = true;
|
|
3131
97
|
};
|
|
3132
|
-
}, [
|
|
98
|
+
}, [filter]);
|
|
3133
99
|
react.useEffect(() => {
|
|
3134
100
|
let cancelled = false;
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
days: summaryDays,
|
|
3140
|
-
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
3141
|
-
}),
|
|
3142
|
-
{ credentials: "include" }
|
|
3143
|
-
).then((res) => {
|
|
101
|
+
setOverTimeLoading(true);
|
|
102
|
+
setOverTimeError(null);
|
|
103
|
+
const url = `/admin/analytics/orders-over-time?period=${overTimePeriod}`;
|
|
104
|
+
fetch(url).then((res) => {
|
|
3144
105
|
if (!res.ok) throw new Error(res.statusText);
|
|
3145
106
|
return res.json();
|
|
3146
107
|
}).then((body) => {
|
|
3147
|
-
if (!cancelled)
|
|
3148
|
-
}).catch((
|
|
3149
|
-
if (!cancelled)
|
|
3150
|
-
setPerformanceError(error instanceof Error ? error.message : String(error));
|
|
3151
|
-
}
|
|
108
|
+
if (!cancelled) setDailyOrders(body.dailyOrders ?? []);
|
|
109
|
+
}).catch((e) => {
|
|
110
|
+
if (!cancelled) setOverTimeError(e instanceof Error ? e.message : String(e));
|
|
3152
111
|
}).finally(() => {
|
|
3153
|
-
if (!cancelled)
|
|
112
|
+
if (!cancelled) setOverTimeLoading(false);
|
|
3154
113
|
});
|
|
3155
114
|
return () => {
|
|
3156
115
|
cancelled = true;
|
|
3157
116
|
};
|
|
3158
|
-
}, [
|
|
3159
|
-
|
|
3160
|
-
const trendTotals = react.useMemo(() => {
|
|
3161
|
-
const totalUnits = series.reduce((s, p) => s + p.units_sold, 0);
|
|
3162
|
-
const totalRevenue = series.reduce((s, p) => s + p.revenue, 0);
|
|
3163
|
-
const totalViews = series.reduce((s, p) => s + p.views, 0);
|
|
3164
|
-
const n = series.length || 1;
|
|
3165
|
-
return {
|
|
3166
|
-
totalUnits,
|
|
3167
|
-
totalRevenue,
|
|
3168
|
-
totalViews,
|
|
3169
|
-
avgUnitsPerDay: totalUnits / n,
|
|
3170
|
-
avgRevenuePerDay: totalRevenue / n,
|
|
3171
|
-
avgViewsPerDay: totalViews / n
|
|
3172
|
-
};
|
|
3173
|
-
}, [series]);
|
|
3174
|
-
const peakRevenuePoint = series.length > 0 ? series.reduce((peak, point) => point.revenue > peak.revenue ? point : peak) : null;
|
|
3175
|
-
const viewsConnectedForPulse = ((summary == null ? void 0 : summary.productViewsConnected) ?? false) || (overTime == null ? void 0 : overTime.productViewsConnected) === true || (topSellers == null ? void 0 : topSellers.productViewsConnected) === true || (performance == null ? void 0 : performance.productViewsConnected) === true;
|
|
3176
|
-
const topSellersByUnits = react.useMemo(
|
|
3177
|
-
() => [...(topSellers == null ? void 0 : topSellers.products) ?? []].sort((a, b) => {
|
|
3178
|
-
if (b.units_sold !== a.units_sold) return b.units_sold - a.units_sold;
|
|
3179
|
-
if (b.order_count !== a.order_count) return b.order_count - a.order_count;
|
|
3180
|
-
return b.revenue - a.revenue;
|
|
3181
|
-
}),
|
|
3182
|
-
[topSellers]
|
|
3183
|
-
);
|
|
3184
|
-
const bestSellersTrendSeries = (bestSellersTrend == null ? void 0 : bestSellersTrend.series) ?? [];
|
|
3185
|
-
const mostViewedTrendSeries = (mostViewedTrend == null ? void 0 : mostViewedTrend.series) ?? [];
|
|
3186
|
-
const bestSellerTrendProductTitle = ((_a = bestSellersTrend == null ? void 0 : bestSellersTrend.product) == null ? void 0 : _a.product_title) ?? ((_b = topSellersByUnits[0]) == null ? void 0 : _b.product_title) ?? null;
|
|
3187
|
-
const mostViewedTrendProductTitle = ((_c = mostViewedTrend == null ? void 0 : mostViewedTrend.product) == null ? void 0 : _c.product_title) ?? ((_e = (_d = performance == null ? void 0 : performance.topViewedProducts) == null ? void 0 : _d[0]) == null ? void 0 : _e.product_title) ?? null;
|
|
3188
|
-
const viewsVsUnitsScatterData = react.useMemo(
|
|
3189
|
-
() => ((performance == null ? void 0 : performance.topViewedProducts) ?? []).slice(0, 48).filter((row) => row.total_views > 0 || row.units_sold > 0),
|
|
3190
|
-
[performance]
|
|
3191
|
-
);
|
|
3192
|
-
const selectedSummaryPeriod = ((_f = SUMMARY_PERIODS.find((p) => p.value === summaryDays)) == null ? void 0 : _f.label) ?? "All time";
|
|
3193
|
-
const selectedGraphPeriodLabel = ((_g = GRAPH_PERIODS.find((p) => p.value === graphPeriod)) == null ? void 0 : _g.label) ?? "One week";
|
|
3194
|
-
const quickPulseMetrics = [
|
|
3195
|
-
{
|
|
3196
|
-
label: "Range units",
|
|
3197
|
-
value: formatShortNumber(trendTotals.totalUnits),
|
|
3198
|
-
helper: selectedGraphPeriodLabel,
|
|
3199
|
-
accentClassName: "border-sky-400/25 bg-sky-500/5"
|
|
3200
|
-
},
|
|
3201
|
-
{
|
|
3202
|
-
label: "Range revenue",
|
|
3203
|
-
value: formatCompactCurrency(trendTotals.totalRevenue),
|
|
3204
|
-
helper: "Current chart window",
|
|
3205
|
-
accentClassName: "border-fuchsia-400/25 bg-fuchsia-500/5"
|
|
3206
|
-
},
|
|
3207
|
-
{
|
|
3208
|
-
label: "Avg/day views",
|
|
3209
|
-
value: trendTotals.avgViewsPerDay.toFixed(0),
|
|
3210
|
-
helper: viewsConnectedForPulse ? "Across chart range" : "Views need tracking",
|
|
3211
|
-
accentClassName: "border-violet-400/25 bg-violet-500/5"
|
|
3212
|
-
},
|
|
3213
|
-
{
|
|
3214
|
-
label: "Peak revenue day",
|
|
3215
|
-
value: peakRevenuePoint ? formatCompactCurrency(peakRevenuePoint.revenue) : "—",
|
|
3216
|
-
helper: (peakRevenuePoint == null ? void 0 : peakRevenuePoint.label) ?? formatChartLabel(peakRevenuePoint == null ? void 0 : peakRevenuePoint.date),
|
|
3217
|
-
accentClassName: "border-amber-400/25 bg-amber-500/5"
|
|
3218
|
-
}
|
|
3219
|
-
];
|
|
3220
|
-
if (summaryLoading) {
|
|
117
|
+
}, [overTimePeriod]);
|
|
118
|
+
if (loading) {
|
|
3221
119
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3222
120
|
"div",
|
|
3223
121
|
{
|
|
3224
|
-
className: "flex
|
|
122
|
+
className: "flex items-center justify-center min-h-[320px]",
|
|
3225
123
|
role: "status",
|
|
3226
|
-
"aria-label": "Loading
|
|
124
|
+
"aria-label": "Loading analytics",
|
|
3227
125
|
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
3228
126
|
}
|
|
3229
127
|
);
|
|
3230
128
|
}
|
|
3231
|
-
if (
|
|
3232
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children:
|
|
129
|
+
if (error || !data) {
|
|
130
|
+
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" }) });
|
|
3233
131
|
}
|
|
3234
|
-
const
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
{
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
132
|
+
const pieData = ORDER_CHART_STATUSES.map(({ key, label, color }) => {
|
|
133
|
+
const value = key === "delivered" ? data.statusCounts.delivered ?? data.deliveredOrders ?? 0 : key === "cancelled" ? data.statusCounts.cancelled ?? data.cancelledOrders ?? 0 : data.statusCounts.pending ?? data.pendingOrders ?? 0;
|
|
134
|
+
return { name: label, value, color };
|
|
135
|
+
}).filter((d) => d.value > 0);
|
|
136
|
+
const returnsExchangesData = [
|
|
137
|
+
{ name: "Return orders", value: data.returnOrders, color: "#EF4444" },
|
|
138
|
+
{ name: "Exchange orders", value: data.exchangeOrders, color: "#F59E0B" }
|
|
139
|
+
].filter((d) => d.value > 0);
|
|
140
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
|
|
141
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
142
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Orders" }),
|
|
143
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
144
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
145
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "analytics-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
|
|
146
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
+
"select",
|
|
148
|
+
{
|
|
149
|
+
id: "analytics-period",
|
|
150
|
+
value: filter,
|
|
151
|
+
onChange: (e) => setFilter(e.target.value),
|
|
152
|
+
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",
|
|
153
|
+
children: SUMMARY_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
] }),
|
|
157
|
+
filter !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
158
|
+
"button",
|
|
159
|
+
{
|
|
160
|
+
type: "button",
|
|
161
|
+
onClick: () => setFilter("all"),
|
|
162
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
163
|
+
"aria-label": "Show all orders (clear filter)",
|
|
164
|
+
children: "Clear filter"
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
] })
|
|
168
|
+
] }),
|
|
169
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
|
|
170
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
171
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Total orders" }),
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.totalOrders.toLocaleString() })
|
|
173
|
+
] }),
|
|
174
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Total revenue" }),
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency(data.totalRevenue) })
|
|
177
|
+
] }),
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Average order value" }),
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency(data.aov) })
|
|
181
|
+
] }),
|
|
182
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
183
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Orders today" }),
|
|
184
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.ordersToday.toLocaleString() })
|
|
185
|
+
] })
|
|
186
|
+
] }),
|
|
187
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-6", children: [
|
|
188
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mb-2", children: "Pending orders" }),
|
|
190
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.pendingOrders.toLocaleString() })
|
|
191
|
+
] }),
|
|
192
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
193
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mb-2", children: "Avg. fulfillment time (hours)" }),
|
|
194
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "—" })
|
|
195
|
+
] })
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Cancelled orders" }),
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.cancelledOrders.toLocaleString() })
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Delivered orders" }),
|
|
204
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.deliveredOrders.toLocaleString() })
|
|
205
|
+
] }),
|
|
206
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Exchange orders" }),
|
|
208
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.exchangeOrders.toLocaleString() })
|
|
209
|
+
] }),
|
|
210
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
211
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Return orders" }),
|
|
212
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.returnOrders.toLocaleString() })
|
|
213
|
+
] })
|
|
214
|
+
] }),
|
|
215
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row flex-wrap gap-6 items-stretch", children: [
|
|
216
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", style: { minWidth: "280px" }, children: [
|
|
217
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-3", children: "Orders by status" }),
|
|
218
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[280px] flex 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: [
|
|
219
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
220
|
+
recharts.Pie,
|
|
221
|
+
{
|
|
222
|
+
data: pieData,
|
|
223
|
+
cx: "50%",
|
|
224
|
+
cy: "50%",
|
|
225
|
+
innerRadius: 52,
|
|
226
|
+
outerRadius: 78,
|
|
227
|
+
paddingAngle: 2,
|
|
228
|
+
dataKey: "value",
|
|
229
|
+
nameKey: "name",
|
|
230
|
+
isAnimationActive: true,
|
|
231
|
+
children: pieData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-${index}`))
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (value) => [value ?? 0, "Orders"] }),
|
|
235
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { verticalAlign: "bottom", height: 36 })
|
|
236
|
+
] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No orders in this period" }) })
|
|
237
|
+
] }),
|
|
238
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex flex-col", style: { minWidth: "280px" }, children: [
|
|
239
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-3", children: "Returns & exchanges" }),
|
|
240
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-[280px] flex 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: [
|
|
241
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
242
|
+
recharts.Pie,
|
|
243
|
+
{
|
|
244
|
+
data: returnsExchangesData,
|
|
245
|
+
cx: "50%",
|
|
246
|
+
cy: "50%",
|
|
247
|
+
innerRadius: 52,
|
|
248
|
+
outerRadius: 78,
|
|
249
|
+
paddingAngle: 2,
|
|
250
|
+
dataKey: "value",
|
|
251
|
+
nameKey: "name",
|
|
252
|
+
isAnimationActive: true,
|
|
253
|
+
children: returnsExchangesData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-re-${index}`))
|
|
254
|
+
}
|
|
255
|
+
),
|
|
256
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (value) => [value ?? 0, "Count"] }),
|
|
257
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { verticalAlign: "bottom", height: 36 })
|
|
258
|
+
] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "No returns or exchanges in this period" }) })
|
|
259
|
+
] })
|
|
260
|
+
] }) }),
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
262
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
263
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Orders over time" }),
|
|
264
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "over-time-period", className: "text-ui-fg-muted text-sm", children: "Range" }),
|
|
266
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
267
|
+
"select",
|
|
268
|
+
{
|
|
269
|
+
id: "over-time-period",
|
|
270
|
+
value: overTimePeriod,
|
|
271
|
+
onChange: (e) => setOverTimePeriod(e.target.value),
|
|
272
|
+
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",
|
|
273
|
+
children: OVER_TIME_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
] })
|
|
277
|
+
] }),
|
|
278
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
279
|
+
"div",
|
|
280
|
+
{
|
|
281
|
+
className: "flex items-center justify-center h-full",
|
|
282
|
+
role: "status",
|
|
283
|
+
"aria-label": "Loading orders over time",
|
|
284
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
|
|
285
|
+
}
|
|
286
|
+
) : 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(
|
|
287
|
+
recharts.LineChart,
|
|
288
|
+
{
|
|
289
|
+
data: dailyOrders,
|
|
290
|
+
margin: { top: 8, right: 8, left: 8, bottom: 8 },
|
|
291
|
+
children: [
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", className: "stroke-ui-border-base" }),
|
|
3286
293
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3287
|
-
|
|
294
|
+
recharts.XAxis,
|
|
3288
295
|
{
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
296
|
+
dataKey: "date",
|
|
297
|
+
tick: { fontSize: 12 },
|
|
298
|
+
tickFormatter: (v, index) => {
|
|
299
|
+
const row = dailyOrders[index];
|
|
300
|
+
if (row == null ? void 0 : row.label) return row.label;
|
|
301
|
+
const d = new Date(v);
|
|
302
|
+
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
303
|
+
}
|
|
3292
304
|
}
|
|
3293
305
|
),
|
|
3294
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3295
|
-
"select",
|
|
3296
|
-
{
|
|
3297
|
-
id: "products-channel-filter",
|
|
3298
|
-
value: salesChannelId,
|
|
3299
|
-
onChange: (event) => setSalesChannelId(event.target.value),
|
|
3300
|
-
disabled: filtersLoading,
|
|
3301
|
-
className: SELECT_CLASS_NAME,
|
|
3302
|
-
"aria-busy": filtersLoading,
|
|
3303
|
-
children: [
|
|
3304
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All channels" }),
|
|
3305
|
-
salesChannels.map((channel) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: channel.id, children: channel.name }, channel.id))
|
|
3306
|
-
]
|
|
3307
|
-
}
|
|
3308
|
-
)
|
|
3309
|
-
] }),
|
|
3310
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 sm:flex-initial sm:flex-none", children: [
|
|
3311
306
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3312
|
-
|
|
307
|
+
recharts.YAxis,
|
|
3313
308
|
{
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
309
|
+
tick: { fontSize: 12 },
|
|
310
|
+
allowDecimals: false,
|
|
311
|
+
domain: [0, "auto"],
|
|
312
|
+
ticks: (() => {
|
|
313
|
+
const max = Math.max(
|
|
314
|
+
1,
|
|
315
|
+
...dailyOrders.map((d) => d.orders_count ?? 0)
|
|
316
|
+
);
|
|
317
|
+
const step = max <= 10 ? 1 : Math.ceil(max / 6);
|
|
318
|
+
const arr = [];
|
|
319
|
+
for (let i = 0; i <= max; i += step) arr.push(i);
|
|
320
|
+
if (arr[arr.length - 1] !== max) arr.push(max);
|
|
321
|
+
return arr;
|
|
322
|
+
})(),
|
|
323
|
+
tickFormatter: (v) => String(Math.floor(Number(v) || 0))
|
|
3317
324
|
}
|
|
3318
325
|
),
|
|
326
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersOverTimeTooltip, {}) }),
|
|
3319
327
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3320
|
-
|
|
328
|
+
recharts.Line,
|
|
3321
329
|
{
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
330
|
+
type: "monotone",
|
|
331
|
+
dataKey: "orders_count",
|
|
332
|
+
name: "Orders",
|
|
333
|
+
stroke: "var(--medusa-color-ui-fg-interactive)",
|
|
334
|
+
strokeWidth: 2,
|
|
335
|
+
dot: { r: 3 }
|
|
3327
336
|
}
|
|
3328
337
|
)
|
|
3329
|
-
]
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
) }) })
|
|
341
|
+
] })
|
|
342
|
+
] });
|
|
343
|
+
}
|
|
344
|
+
function CustomersOverTimeTooltip({
|
|
345
|
+
active,
|
|
346
|
+
payload,
|
|
347
|
+
label
|
|
348
|
+
}) {
|
|
349
|
+
var _a, _b, _c;
|
|
350
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
351
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
352
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : label);
|
|
353
|
+
const registered = Math.floor(((_b = payload.find((p) => p.name === "Registered")) == null ? void 0 : _b.value) ?? 0);
|
|
354
|
+
const guest = Math.floor(((_c = payload.find((p) => p.name === "Guest")) == null ? void 0 : _c.value) ?? 0);
|
|
355
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
356
|
+
"div",
|
|
357
|
+
{
|
|
358
|
+
style: {
|
|
359
|
+
backgroundColor: "rgb(254, 254, 254)",
|
|
360
|
+
color: "#111827",
|
|
361
|
+
border: "1px solid rgb(229, 231, 235)",
|
|
362
|
+
borderRadius: "8px",
|
|
363
|
+
padding: "12px 16px",
|
|
364
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.08)",
|
|
365
|
+
fontSize: "14px",
|
|
366
|
+
lineHeight: 1.5
|
|
367
|
+
},
|
|
368
|
+
children: [
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px", color: "#111827" }, children: displayLabel }),
|
|
370
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#374151" }, children: [
|
|
371
|
+
"Registered: ",
|
|
372
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: "#1f2937" }, children: registered })
|
|
373
|
+
] }),
|
|
374
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#374151" }, children: [
|
|
375
|
+
"Guest: ",
|
|
376
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: "#1f2937" }, children: guest })
|
|
3352
377
|
] })
|
|
378
|
+
]
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
const COUNT_DAY_PRESETS = [
|
|
383
|
+
{ value: "all", label: "All time" },
|
|
384
|
+
{ value: "0", label: "Today" },
|
|
385
|
+
{ value: "7", label: "Last 7 days" },
|
|
386
|
+
{ value: "30", label: "Last 30 days" },
|
|
387
|
+
{ value: "90", label: "Last 90 days" }
|
|
388
|
+
];
|
|
389
|
+
const GRAPH_PERIODS = [
|
|
390
|
+
{ value: "one_week", label: "One week" },
|
|
391
|
+
{ value: "one_month", label: "One month" },
|
|
392
|
+
{ value: "one_year", label: "One year" }
|
|
393
|
+
];
|
|
394
|
+
const REGISTERED_COLOR = "var(--medusa-color-ui-fg-interactive)";
|
|
395
|
+
const GUEST_COLOR = "#6B7280";
|
|
396
|
+
function CustomersDashboard() {
|
|
397
|
+
const [data, setData] = react.useState(null);
|
|
398
|
+
const [loading, setLoading] = react.useState(true);
|
|
399
|
+
const [error, setError] = react.useState(null);
|
|
400
|
+
const [countDays, setCountDays] = react.useState("all");
|
|
401
|
+
const [series, setSeries] = react.useState([]);
|
|
402
|
+
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
403
|
+
const [graphLoading, setGraphLoading] = react.useState(true);
|
|
404
|
+
const [graphError, setGraphError] = react.useState(null);
|
|
405
|
+
react.useEffect(() => {
|
|
406
|
+
let cancelled = false;
|
|
407
|
+
setLoading(true);
|
|
408
|
+
setError(null);
|
|
409
|
+
const url = `/admin/analytics/customers-summary?days=${countDays}`;
|
|
410
|
+
fetch(url).then((res) => {
|
|
411
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
412
|
+
return res.json();
|
|
413
|
+
}).then((body) => {
|
|
414
|
+
if (!cancelled) setData(body);
|
|
415
|
+
}).catch((e) => {
|
|
416
|
+
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
417
|
+
}).finally(() => {
|
|
418
|
+
if (!cancelled) setLoading(false);
|
|
419
|
+
});
|
|
420
|
+
return () => {
|
|
421
|
+
cancelled = true;
|
|
422
|
+
};
|
|
423
|
+
}, [countDays]);
|
|
424
|
+
react.useEffect(() => {
|
|
425
|
+
let cancelled = false;
|
|
426
|
+
setGraphLoading(true);
|
|
427
|
+
setGraphError(null);
|
|
428
|
+
const url = `/admin/analytics/customers-over-time?period=${graphPeriod}`;
|
|
429
|
+
fetch(url).then((res) => {
|
|
430
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
431
|
+
return res.json();
|
|
432
|
+
}).then((body) => {
|
|
433
|
+
if (!cancelled) setSeries(body.series ?? []);
|
|
434
|
+
}).catch((e) => {
|
|
435
|
+
if (!cancelled) setGraphError(e instanceof Error ? e.message : String(e));
|
|
436
|
+
}).finally(() => {
|
|
437
|
+
if (!cancelled) setGraphLoading(false);
|
|
438
|
+
});
|
|
439
|
+
return () => {
|
|
440
|
+
cancelled = true;
|
|
441
|
+
};
|
|
442
|
+
}, [graphPeriod]);
|
|
443
|
+
if (loading) {
|
|
444
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
445
|
+
"div",
|
|
446
|
+
{
|
|
447
|
+
className: "flex items-center justify-center min-h-[320px]",
|
|
448
|
+
role: "status",
|
|
449
|
+
"aria-label": "Loading analytics",
|
|
450
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
3353
451
|
}
|
|
3354
|
-
)
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
/* @__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: "Product overview" }) }),
|
|
3365
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6", children: primaryStats.map((stat) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3366
|
-
AtlasKpiCard,
|
|
3367
|
-
{
|
|
3368
|
-
label: stat.label,
|
|
3369
|
-
value: stat.value,
|
|
3370
|
-
helper: stat.helper,
|
|
3371
|
-
accent: stat.accent
|
|
3372
|
-
},
|
|
3373
|
-
stat.label
|
|
3374
|
-
)) })
|
|
3375
|
-
] }),
|
|
3376
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 grid grid-cols-12 gap-2 lg:items-start", children: [
|
|
3377
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-12 lg:col-span-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3378
|
-
AnalyticsSection,
|
|
3379
|
-
{
|
|
3380
|
-
variant: "atlas",
|
|
3381
|
-
actionsBare: true,
|
|
3382
|
-
title: "Sales & views over time",
|
|
3383
|
-
description: "Units and views share the left axis; revenue uses the right. Window totals match this range.",
|
|
3384
|
-
actions: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
3385
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3386
|
-
ui.Label,
|
|
3387
|
-
{
|
|
3388
|
-
htmlFor: "products-graph-period",
|
|
3389
|
-
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
3390
|
-
children: "Range"
|
|
3391
|
-
}
|
|
3392
|
-
),
|
|
3393
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3394
|
-
"select",
|
|
3395
|
-
{
|
|
3396
|
-
id: "products-graph-period",
|
|
3397
|
-
value: graphPeriod,
|
|
3398
|
-
onChange: (event) => setGraphPeriod(event.target.value),
|
|
3399
|
-
className: SELECT_CLASS_NAME,
|
|
3400
|
-
children: GRAPH_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
3401
|
-
}
|
|
3402
|
-
)
|
|
3403
|
-
] }),
|
|
3404
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3405
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-3 sm:gap-1.5", children: [
|
|
3406
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "min-h-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
3407
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window units" }),
|
|
3408
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatShortNumber(trendTotals.totalUnits) }),
|
|
3409
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
3410
|
-
"Avg/day ",
|
|
3411
|
-
trendTotals.avgUnitsPerDay.toFixed(1)
|
|
3412
|
-
] })
|
|
3413
|
-
] }) }),
|
|
3414
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "min-h-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
3415
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window revenue" }),
|
|
3416
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatCompactCurrency(trendTotals.totalRevenue) }),
|
|
3417
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
3418
|
-
"Avg/day ",
|
|
3419
|
-
formatCompactCurrency(trendTotals.avgRevenuePerDay)
|
|
3420
|
-
] })
|
|
3421
|
-
] }) }),
|
|
3422
|
-
/* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "min-h-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
|
|
3423
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: "Window views" }),
|
|
3424
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-lg font-semibold tracking-tight", children: formatShortNumber(trendTotals.totalViews) }),
|
|
3425
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-[10px]", children: [
|
|
3426
|
-
"Avg/day ",
|
|
3427
|
-
trendTotals.avgViewsPerDay.toFixed(0)
|
|
3428
|
-
] })
|
|
3429
|
-
] }) })
|
|
3430
|
-
] }),
|
|
3431
|
-
/* @__PURE__ */ jsxRuntime.jsxs(AnalyticsChartSurface, { variant: "atlas", className: "relative overflow-hidden", children: [
|
|
3432
|
-
/* @__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" }),
|
|
3433
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative space-y-1", children: [
|
|
3434
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-1.5", children: [
|
|
3435
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0", children: [
|
|
3436
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.16em]", children: "Trend overview" }),
|
|
3437
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base text-sm font-semibold", children: selectedGraphPeriodLabel })
|
|
3438
|
-
] }),
|
|
3439
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap justify-end gap-2 text-[10px] text-ui-fg-muted", children: [
|
|
3440
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
3441
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-sky-400" }),
|
|
3442
|
-
"Units"
|
|
3443
|
-
] }),
|
|
3444
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
3445
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-violet-400" }),
|
|
3446
|
-
"Views"
|
|
3447
|
-
] }),
|
|
3448
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
3449
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-fuchsia-400" }),
|
|
3450
|
-
"Revenue"
|
|
3451
|
-
] })
|
|
3452
|
-
] })
|
|
3453
|
-
] }),
|
|
3454
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[min(168px,24vh)] min-h-[140px] sm:h-[min(184px,26vh)]", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3455
|
-
"div",
|
|
3456
|
-
{
|
|
3457
|
-
className: "flex h-full items-center justify-center",
|
|
3458
|
-
role: "status",
|
|
3459
|
-
"aria-label": "Loading product trend chart",
|
|
3460
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading chart…" })
|
|
3461
|
-
}
|
|
3462
|
-
) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: overTimeError }) }) : series.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 data in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3463
|
-
recharts.ComposedChart,
|
|
3464
|
-
{
|
|
3465
|
-
data: series,
|
|
3466
|
-
margin: { top: 4, right: 6, left: -6, bottom: 0 },
|
|
3467
|
-
children: [
|
|
3468
|
-
/* @__PURE__ */ jsxRuntime.jsxs("defs", { children: [
|
|
3469
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3470
|
-
"linearGradient",
|
|
3471
|
-
{
|
|
3472
|
-
id: gid("trend-units-line"),
|
|
3473
|
-
x1: "0",
|
|
3474
|
-
y1: "0",
|
|
3475
|
-
x2: "1",
|
|
3476
|
-
y2: "0",
|
|
3477
|
-
children: [
|
|
3478
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#67E8F9" }),
|
|
3479
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#3B82F6" })
|
|
3480
|
-
]
|
|
3481
|
-
}
|
|
3482
|
-
),
|
|
3483
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3484
|
-
"linearGradient",
|
|
3485
|
-
{
|
|
3486
|
-
id: gid("trend-revenue-line"),
|
|
3487
|
-
x1: "0",
|
|
3488
|
-
y1: "0",
|
|
3489
|
-
x2: "1",
|
|
3490
|
-
y2: "0",
|
|
3491
|
-
children: [
|
|
3492
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#F472B6" }),
|
|
3493
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC" })
|
|
3494
|
-
]
|
|
3495
|
-
}
|
|
3496
|
-
),
|
|
3497
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3498
|
-
"linearGradient",
|
|
3499
|
-
{
|
|
3500
|
-
id: gid("trend-units-area"),
|
|
3501
|
-
x1: "0",
|
|
3502
|
-
y1: "0",
|
|
3503
|
-
x2: "0",
|
|
3504
|
-
y2: "1",
|
|
3505
|
-
children: [
|
|
3506
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#38BDF8", stopOpacity: 0.22 }),
|
|
3507
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#38BDF8", stopOpacity: 0 })
|
|
3508
|
-
]
|
|
3509
|
-
}
|
|
3510
|
-
),
|
|
3511
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3512
|
-
"linearGradient",
|
|
3513
|
-
{
|
|
3514
|
-
id: gid("trend-revenue-area"),
|
|
3515
|
-
x1: "0",
|
|
3516
|
-
y1: "0",
|
|
3517
|
-
x2: "0",
|
|
3518
|
-
y2: "1",
|
|
3519
|
-
children: [
|
|
3520
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#E879F9", stopOpacity: 0.2 }),
|
|
3521
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#E879F9", stopOpacity: 0 })
|
|
3522
|
-
]
|
|
3523
|
-
}
|
|
3524
|
-
),
|
|
3525
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3526
|
-
"linearGradient",
|
|
3527
|
-
{
|
|
3528
|
-
id: gid("trend-views-area"),
|
|
3529
|
-
x1: "0",
|
|
3530
|
-
y1: "0",
|
|
3531
|
-
x2: "0",
|
|
3532
|
-
y2: "1",
|
|
3533
|
-
children: [
|
|
3534
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: "#C084FC", stopOpacity: 0.18 }),
|
|
3535
|
-
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: "#C084FC", stopOpacity: 0 })
|
|
3536
|
-
]
|
|
3537
|
-
}
|
|
3538
|
-
)
|
|
3539
|
-
] }),
|
|
3540
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3541
|
-
recharts.CartesianGrid,
|
|
3542
|
-
{
|
|
3543
|
-
stroke: "rgba(148,163,184,0.14)",
|
|
3544
|
-
strokeDasharray: "3 3",
|
|
3545
|
-
vertical: false
|
|
3546
|
-
}
|
|
3547
|
-
),
|
|
3548
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3549
|
-
recharts.XAxis,
|
|
3550
|
-
{
|
|
3551
|
-
dataKey: "date",
|
|
3552
|
-
tick: CHART_AXIS_TICK,
|
|
3553
|
-
tickLine: false,
|
|
3554
|
-
axisLine: false,
|
|
3555
|
-
tickFormatter: (value) => {
|
|
3556
|
-
const row = series.find((p) => p.date === String(value));
|
|
3557
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
),
|
|
3561
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3562
|
-
recharts.YAxis,
|
|
3563
|
-
{
|
|
3564
|
-
yAxisId: "counts",
|
|
3565
|
-
width: 32,
|
|
3566
|
-
tick: CHART_AXIS_TICK,
|
|
3567
|
-
tickLine: false,
|
|
3568
|
-
axisLine: false,
|
|
3569
|
-
allowDecimals: false,
|
|
3570
|
-
tickFormatter: (value) => Math.floor(Number(value) || 0).toLocaleString()
|
|
3571
|
-
}
|
|
3572
|
-
),
|
|
3573
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3574
|
-
recharts.YAxis,
|
|
3575
|
-
{
|
|
3576
|
-
yAxisId: "revenue",
|
|
3577
|
-
orientation: "right",
|
|
3578
|
-
width: 40,
|
|
3579
|
-
tick: CHART_AXIS_TICK,
|
|
3580
|
-
tickLine: false,
|
|
3581
|
-
axisLine: false,
|
|
3582
|
-
tickFormatter: (value) => formatCompactCurrency(Number(value) || 0)
|
|
3583
|
-
}
|
|
3584
|
-
),
|
|
3585
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3586
|
-
recharts.Tooltip,
|
|
3587
|
-
{
|
|
3588
|
-
content: /* @__PURE__ */ jsxRuntime.jsx(TrendTooltip, {}),
|
|
3589
|
-
cursor: {
|
|
3590
|
-
stroke: "rgba(248, 250, 252, 0.35)",
|
|
3591
|
-
strokeWidth: 1
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
),
|
|
3595
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3596
|
-
recharts.Area,
|
|
3597
|
-
{
|
|
3598
|
-
yAxisId: "counts",
|
|
3599
|
-
type: "natural",
|
|
3600
|
-
dataKey: "units_sold",
|
|
3601
|
-
stroke: "none",
|
|
3602
|
-
fill: `url(#${gid("trend-units-area")})`,
|
|
3603
|
-
isAnimationActive: false,
|
|
3604
|
-
legendType: "none"
|
|
3605
|
-
}
|
|
3606
|
-
),
|
|
3607
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3608
|
-
recharts.Area,
|
|
3609
|
-
{
|
|
3610
|
-
yAxisId: "counts",
|
|
3611
|
-
type: "natural",
|
|
3612
|
-
dataKey: "views",
|
|
3613
|
-
stroke: "none",
|
|
3614
|
-
fill: `url(#${gid("trend-views-area")})`,
|
|
3615
|
-
isAnimationActive: false,
|
|
3616
|
-
legendType: "none"
|
|
3617
|
-
}
|
|
3618
|
-
),
|
|
3619
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3620
|
-
recharts.Area,
|
|
3621
|
-
{
|
|
3622
|
-
yAxisId: "revenue",
|
|
3623
|
-
type: "natural",
|
|
3624
|
-
dataKey: "revenue",
|
|
3625
|
-
stroke: "none",
|
|
3626
|
-
fill: `url(#${gid("trend-revenue-area")})`,
|
|
3627
|
-
isAnimationActive: false,
|
|
3628
|
-
legendType: "none"
|
|
3629
|
-
}
|
|
3630
|
-
),
|
|
3631
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3632
|
-
recharts.Line,
|
|
3633
|
-
{
|
|
3634
|
-
yAxisId: "counts",
|
|
3635
|
-
type: "natural",
|
|
3636
|
-
dataKey: "units_sold",
|
|
3637
|
-
name: "Units sold",
|
|
3638
|
-
stroke: `url(#${gid("trend-units-line")})`,
|
|
3639
|
-
strokeWidth: 2,
|
|
3640
|
-
dot: false,
|
|
3641
|
-
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.units }
|
|
3642
|
-
}
|
|
3643
|
-
),
|
|
3644
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3645
|
-
recharts.Line,
|
|
3646
|
-
{
|
|
3647
|
-
yAxisId: "counts",
|
|
3648
|
-
type: "natural",
|
|
3649
|
-
dataKey: "views",
|
|
3650
|
-
name: "Views",
|
|
3651
|
-
stroke: PRODUCT_CHART_COLORS.views,
|
|
3652
|
-
strokeWidth: 1.5,
|
|
3653
|
-
strokeDasharray: "4 3",
|
|
3654
|
-
dot: false,
|
|
3655
|
-
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.views }
|
|
3656
|
-
}
|
|
3657
|
-
),
|
|
3658
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3659
|
-
recharts.Line,
|
|
3660
|
-
{
|
|
3661
|
-
yAxisId: "revenue",
|
|
3662
|
-
type: "natural",
|
|
3663
|
-
dataKey: "revenue",
|
|
3664
|
-
name: "Revenue",
|
|
3665
|
-
stroke: `url(#${gid("trend-revenue-line")})`,
|
|
3666
|
-
strokeWidth: 2,
|
|
3667
|
-
dot: false,
|
|
3668
|
-
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.revenue }
|
|
3669
|
-
}
|
|
3670
|
-
)
|
|
3671
|
-
]
|
|
3672
|
-
}
|
|
3673
|
-
) }) })
|
|
3674
|
-
] })
|
|
3675
|
-
] })
|
|
3676
|
-
] })
|
|
3677
|
-
}
|
|
3678
|
-
) }),
|
|
3679
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-12 flex flex-col gap-2 lg:col-span-6", children: [
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (error || !data) {
|
|
455
|
+
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" }) });
|
|
456
|
+
}
|
|
457
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
|
|
458
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Customers" }),
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
461
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3680
462
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3681
|
-
|
|
463
|
+
ui.Label,
|
|
3682
464
|
{
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2", children: quickPulseMetrics.map((metric) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3687
|
-
"div",
|
|
3688
|
-
{
|
|
3689
|
-
className: `rounded-lg border border-ui-border-base bg-ui-bg-base px-2 py-2 shadow-sm ${metric.accentClassName}`.trim(),
|
|
3690
|
-
children: [
|
|
3691
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-[10px] font-medium uppercase tracking-[0.14em]", children: metric.label }),
|
|
3692
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-base mt-0.5 text-sm font-semibold tracking-tight", children: metric.value }),
|
|
3693
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted mt-0.5 text-[9px] leading-snug", children: metric.helper })
|
|
3694
|
-
]
|
|
3695
|
-
},
|
|
3696
|
-
metric.label
|
|
3697
|
-
)) })
|
|
465
|
+
htmlFor: "customers-count-period",
|
|
466
|
+
className: "text-ui-fg-muted text-sm",
|
|
467
|
+
children: "Period (counts)"
|
|
3698
468
|
}
|
|
3699
469
|
),
|
|
3700
470
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3701
|
-
|
|
471
|
+
"select",
|
|
3702
472
|
{
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3709
|
-
ui.Label,
|
|
3710
|
-
{
|
|
3711
|
-
htmlFor: "products-top-seller-period",
|
|
3712
|
-
className: "shrink-0 text-ui-fg-muted text-xs font-medium",
|
|
3713
|
-
children: "Window"
|
|
3714
|
-
}
|
|
3715
|
-
),
|
|
3716
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3717
|
-
"select",
|
|
3718
|
-
{
|
|
3719
|
-
id: "products-top-seller-period",
|
|
3720
|
-
value: topSellerPeriod,
|
|
3721
|
-
onChange: (event) => setTopSellerPeriod(event.target.value),
|
|
3722
|
-
className: SELECT_CLASS_NAME,
|
|
3723
|
-
children: TOP_SELLER_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
3724
|
-
}
|
|
3725
|
-
)
|
|
3726
|
-
] }),
|
|
3727
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "min-w-0 overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[212px] min-w-[280px] w-full sm:h-[228px]", children: bestSellersTrendLoading ? /* @__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…" }) }) : bestSellersTrendError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: bestSellersTrendError }) }) : bestSellersTrendSeries.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 product sales for this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3728
|
-
recharts.ComposedChart,
|
|
3729
|
-
{
|
|
3730
|
-
data: bestSellersTrendSeries,
|
|
3731
|
-
margin: { left: 0, right: 8, top: 6, bottom: 2 },
|
|
3732
|
-
children: [
|
|
3733
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3734
|
-
"linearGradient",
|
|
3735
|
-
{
|
|
3736
|
-
id: gid("best-revenue-area"),
|
|
3737
|
-
x1: "0",
|
|
3738
|
-
y1: "0",
|
|
3739
|
-
x2: "0",
|
|
3740
|
-
y2: "1",
|
|
3741
|
-
children: [
|
|
3742
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3743
|
-
"stop",
|
|
3744
|
-
{
|
|
3745
|
-
offset: "0%",
|
|
3746
|
-
stopColor: PRODUCT_CHART_COLORS.revenue,
|
|
3747
|
-
stopOpacity: 0.22
|
|
3748
|
-
}
|
|
3749
|
-
),
|
|
3750
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3751
|
-
"stop",
|
|
3752
|
-
{
|
|
3753
|
-
offset: "100%",
|
|
3754
|
-
stopColor: PRODUCT_CHART_COLORS.revenue,
|
|
3755
|
-
stopOpacity: 0
|
|
3756
|
-
}
|
|
3757
|
-
)
|
|
3758
|
-
]
|
|
3759
|
-
}
|
|
3760
|
-
) }),
|
|
3761
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3762
|
-
recharts.CartesianGrid,
|
|
3763
|
-
{
|
|
3764
|
-
strokeDasharray: "3 3",
|
|
3765
|
-
vertical: false,
|
|
3766
|
-
stroke: "rgba(148,163,184,0.12)"
|
|
3767
|
-
}
|
|
3768
|
-
),
|
|
3769
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3770
|
-
recharts.XAxis,
|
|
3771
|
-
{
|
|
3772
|
-
dataKey: "date",
|
|
3773
|
-
tick: CHART_AXIS_TICK_SM,
|
|
3774
|
-
tickFormatter: (value) => {
|
|
3775
|
-
const row = bestSellersTrendSeries.find(
|
|
3776
|
-
(p) => p.date === String(value)
|
|
3777
|
-
);
|
|
3778
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3779
|
-
},
|
|
3780
|
-
tickLine: false,
|
|
3781
|
-
axisLine: { stroke: CHART_AXIS_LINE }
|
|
3782
|
-
}
|
|
3783
|
-
),
|
|
3784
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3785
|
-
recharts.YAxis,
|
|
3786
|
-
{
|
|
3787
|
-
yAxisId: "units",
|
|
3788
|
-
tick: CHART_AXIS_TICK_SM,
|
|
3789
|
-
tickFormatter: (value) => formatShortNumber(Number(value)),
|
|
3790
|
-
width: 36,
|
|
3791
|
-
tickLine: false,
|
|
3792
|
-
axisLine: { stroke: CHART_AXIS_LINE },
|
|
3793
|
-
allowDecimals: false
|
|
3794
|
-
}
|
|
3795
|
-
),
|
|
3796
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3797
|
-
recharts.YAxis,
|
|
3798
|
-
{
|
|
3799
|
-
yAxisId: "revenue",
|
|
3800
|
-
orientation: "right",
|
|
3801
|
-
tick: CHART_AXIS_TICK_SM,
|
|
3802
|
-
tickFormatter: (value) => formatCompactCurrency(Number(value)),
|
|
3803
|
-
width: 44,
|
|
3804
|
-
tickLine: false,
|
|
3805
|
-
axisLine: { stroke: CHART_AXIS_LINE }
|
|
3806
|
-
}
|
|
3807
|
-
),
|
|
3808
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3809
|
-
recharts.Tooltip,
|
|
3810
|
-
{
|
|
3811
|
-
content: (tooltipProps) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3812
|
-
MiniTrendTooltip,
|
|
3813
|
-
{
|
|
3814
|
-
...tooltipProps,
|
|
3815
|
-
productHeadline: bestSellerTrendProductTitle,
|
|
3816
|
-
hideViewsRow: true
|
|
3817
|
-
}
|
|
3818
|
-
)
|
|
3819
|
-
}
|
|
3820
|
-
),
|
|
3821
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3822
|
-
recharts.Area,
|
|
3823
|
-
{
|
|
3824
|
-
yAxisId: "revenue",
|
|
3825
|
-
type: "natural",
|
|
3826
|
-
dataKey: "revenue",
|
|
3827
|
-
name: "Revenue",
|
|
3828
|
-
stroke: "transparent",
|
|
3829
|
-
fill: `url(#${gid("best-revenue-area")})`,
|
|
3830
|
-
isAnimationActive: false
|
|
3831
|
-
}
|
|
3832
|
-
),
|
|
3833
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3834
|
-
recharts.Line,
|
|
3835
|
-
{
|
|
3836
|
-
yAxisId: "revenue",
|
|
3837
|
-
type: "natural",
|
|
3838
|
-
dataKey: "revenue",
|
|
3839
|
-
name: "Revenue",
|
|
3840
|
-
stroke: PRODUCT_CHART_COLORS.revenue,
|
|
3841
|
-
strokeWidth: 2,
|
|
3842
|
-
dot: false,
|
|
3843
|
-
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.revenue },
|
|
3844
|
-
isAnimationActive: false
|
|
3845
|
-
}
|
|
3846
|
-
),
|
|
3847
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3848
|
-
recharts.Line,
|
|
3849
|
-
{
|
|
3850
|
-
yAxisId: "units",
|
|
3851
|
-
type: "natural",
|
|
3852
|
-
dataKey: "units_sold",
|
|
3853
|
-
name: "Units sold",
|
|
3854
|
-
stroke: PRODUCT_CHART_COLORS.units,
|
|
3855
|
-
strokeWidth: 2,
|
|
3856
|
-
dot: false,
|
|
3857
|
-
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.units },
|
|
3858
|
-
isAnimationActive: false
|
|
3859
|
-
}
|
|
3860
|
-
)
|
|
3861
|
-
]
|
|
3862
|
-
}
|
|
3863
|
-
) }) }) })
|
|
473
|
+
id: "customers-count-period",
|
|
474
|
+
value: countDays,
|
|
475
|
+
onChange: (e) => setCountDays(e.target.value),
|
|
476
|
+
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",
|
|
477
|
+
children: COUNT_DAY_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
3864
478
|
}
|
|
3865
479
|
)
|
|
3866
|
-
] })
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3870
|
-
AnalyticsSection,
|
|
3871
|
-
{
|
|
3872
|
-
variant: "atlas",
|
|
3873
|
-
className: "min-w-0 md:col-span-1 xl:col-span-1",
|
|
3874
|
-
title: "Leaderboard table",
|
|
3875
|
-
description: "Products ranked by units sold — same window as Best sellers.",
|
|
3876
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-[212px] min-w-0 sm:min-h-[228px]", children: /* @__PURE__ */ jsxRuntime.jsx(AnalyticsTableSurface, { className: "shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { className: LEADERBOARD_TABLE_CLASS, children: [
|
|
3877
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
3878
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Product" }),
|
|
3879
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Units" }),
|
|
3880
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Orders" }),
|
|
3881
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Revenue" })
|
|
3882
|
-
] }) }),
|
|
3883
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
3884
|
-
topSellersError ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
3885
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-xs", children: topSellersError }) }),
|
|
3886
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
3887
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
3888
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
3889
|
-
] }) : null,
|
|
3890
|
-
!topSellersError ? topSellersByUnits.slice(0, 8).map((product) => {
|
|
3891
|
-
var _a2;
|
|
3892
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3893
|
-
ui.Table.Row,
|
|
3894
|
-
{
|
|
3895
|
-
className: product.product_id === ((_a2 = topSellersByUnits[0]) == null ? void 0 : _a2.product_id) ? "bg-emerald-500/5" : void 0,
|
|
3896
|
-
children: [
|
|
3897
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
3898
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
3899
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.order_count.toLocaleString() }),
|
|
3900
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatCurrency(product.revenue) })
|
|
3901
|
-
]
|
|
3902
|
-
},
|
|
3903
|
-
product.product_id
|
|
3904
|
-
);
|
|
3905
|
-
}) : null,
|
|
3906
|
-
!topSellersLoading && !topSellersError && (((_i = topSellers == null ? void 0 : topSellers.products) == null ? void 0 : _i.length) ?? 0) === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
3907
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "No best seller data yet." }) }),
|
|
3908
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
3909
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
3910
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
3911
|
-
] }) : null
|
|
3912
|
-
] })
|
|
3913
|
-
] }) }) })
|
|
3914
|
-
}
|
|
3915
|
-
),
|
|
3916
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3917
|
-
AnalyticsSection,
|
|
3918
|
-
{
|
|
3919
|
-
variant: "atlas",
|
|
3920
|
-
className: "min-w-0 md:col-span-1 xl:col-span-1",
|
|
3921
|
-
title: "Most viewed products",
|
|
3922
|
-
description: mostViewedTrendProductTitle ? `Views over time for “${mostViewedTrendProductTitle}” (top of the list for your summary period; buckets follow period: today / 7d → week, 30d → month, 90d / all → year).` : "Views over time for the most-viewed product in your summary period (buckets: today / 7d → week, 30d → month, 90d / all → year).",
|
|
3923
|
-
children: performanceLoading ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[212px] items-center justify-center sm:h-[228px]", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "Loading…" }) }) }) : performanceError ? /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-[212px] items-center justify-center px-2 sm:h-[228px]", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: performanceError }) }) }) : !(performance == null ? void 0 : performance.productViewsConnected) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-[212px] sm:min-h-[228px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3924
|
-
EmptyAnalyticsPanel,
|
|
3925
|
-
{
|
|
3926
|
-
title: "Views unavailable",
|
|
3927
|
-
description: "Product view tracking is not available in this environment."
|
|
3928
|
-
}
|
|
3929
|
-
) }) : ((performance == null ? void 0 : performance.topViewedProducts) ?? []).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-[212px] sm:min-h-[228px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3930
|
-
EmptyAnalyticsPanel,
|
|
3931
|
-
{
|
|
3932
|
-
title: "No views",
|
|
3933
|
-
description: "No product views found for this summary period."
|
|
3934
|
-
}
|
|
3935
|
-
) }) : /* @__PURE__ */ jsxRuntime.jsx(AnalyticsChartSurface, { variant: "atlas", className: "min-w-0 overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[212px] min-w-[280px] w-full sm:h-[228px]", children: mostViewedTrendLoading ? /* @__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 chart…" }) }) : mostViewedTrendError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center px-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-center text-xs", children: mostViewedTrendError }) }) : mostViewedTrendSeries.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 view data in this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3936
|
-
recharts.ComposedChart,
|
|
3937
|
-
{
|
|
3938
|
-
data: mostViewedTrendSeries,
|
|
3939
|
-
margin: { left: 0, right: 8, top: 6, bottom: 2 },
|
|
3940
|
-
children: [
|
|
3941
|
-
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3942
|
-
"linearGradient",
|
|
3943
|
-
{
|
|
3944
|
-
id: gid("viewed-views-area"),
|
|
3945
|
-
x1: "0",
|
|
3946
|
-
y1: "0",
|
|
3947
|
-
x2: "0",
|
|
3948
|
-
y2: "1",
|
|
3949
|
-
children: [
|
|
3950
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3951
|
-
"stop",
|
|
3952
|
-
{
|
|
3953
|
-
offset: "0%",
|
|
3954
|
-
stopColor: PRODUCT_CHART_COLORS.views,
|
|
3955
|
-
stopOpacity: 0.22
|
|
3956
|
-
}
|
|
3957
|
-
),
|
|
3958
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3959
|
-
"stop",
|
|
3960
|
-
{
|
|
3961
|
-
offset: "100%",
|
|
3962
|
-
stopColor: PRODUCT_CHART_COLORS.views,
|
|
3963
|
-
stopOpacity: 0
|
|
3964
|
-
}
|
|
3965
|
-
)
|
|
3966
|
-
]
|
|
3967
|
-
}
|
|
3968
|
-
) }),
|
|
3969
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3970
|
-
recharts.CartesianGrid,
|
|
3971
|
-
{
|
|
3972
|
-
strokeDasharray: "3 3",
|
|
3973
|
-
vertical: false,
|
|
3974
|
-
stroke: "rgba(148,163,184,0.12)"
|
|
3975
|
-
}
|
|
3976
|
-
),
|
|
3977
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3978
|
-
recharts.XAxis,
|
|
3979
|
-
{
|
|
3980
|
-
dataKey: "date",
|
|
3981
|
-
tick: CHART_AXIS_TICK_SM,
|
|
3982
|
-
tickFormatter: (value) => {
|
|
3983
|
-
const row = mostViewedTrendSeries.find(
|
|
3984
|
-
(p) => p.date === String(value)
|
|
3985
|
-
);
|
|
3986
|
-
return (row == null ? void 0 : row.label) ?? formatChartLabel(String(value));
|
|
3987
|
-
},
|
|
3988
|
-
tickLine: false,
|
|
3989
|
-
axisLine: { stroke: CHART_AXIS_LINE }
|
|
3990
|
-
}
|
|
3991
|
-
),
|
|
3992
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3993
|
-
recharts.YAxis,
|
|
3994
|
-
{
|
|
3995
|
-
tick: CHART_AXIS_TICK_SM,
|
|
3996
|
-
tickFormatter: (value) => formatShortNumber(Number(value)),
|
|
3997
|
-
width: 36,
|
|
3998
|
-
tickLine: false,
|
|
3999
|
-
axisLine: { stroke: CHART_AXIS_LINE },
|
|
4000
|
-
allowDecimals: false
|
|
4001
|
-
}
|
|
4002
|
-
),
|
|
4003
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4004
|
-
recharts.Tooltip,
|
|
4005
|
-
{
|
|
4006
|
-
content: (tooltipProps) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4007
|
-
MiniTrendTooltip,
|
|
4008
|
-
{
|
|
4009
|
-
...tooltipProps,
|
|
4010
|
-
productHeadline: mostViewedTrendProductTitle,
|
|
4011
|
-
hideUnitsRow: true,
|
|
4012
|
-
hideRevenueRow: true
|
|
4013
|
-
}
|
|
4014
|
-
)
|
|
4015
|
-
}
|
|
4016
|
-
),
|
|
4017
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4018
|
-
recharts.Area,
|
|
4019
|
-
{
|
|
4020
|
-
type: "natural",
|
|
4021
|
-
dataKey: "views",
|
|
4022
|
-
name: "Views",
|
|
4023
|
-
stroke: "transparent",
|
|
4024
|
-
fill: `url(#${gid("viewed-views-area")})`,
|
|
4025
|
-
isAnimationActive: false
|
|
4026
|
-
}
|
|
4027
|
-
),
|
|
4028
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4029
|
-
recharts.Line,
|
|
4030
|
-
{
|
|
4031
|
-
type: "natural",
|
|
4032
|
-
dataKey: "views",
|
|
4033
|
-
name: "Views",
|
|
4034
|
-
stroke: PRODUCT_CHART_COLORS.views,
|
|
4035
|
-
strokeWidth: 2,
|
|
4036
|
-
dot: false,
|
|
4037
|
-
activeDot: { r: 4, fill: PRODUCT_CHART_COLORS.views },
|
|
4038
|
-
isAnimationActive: false
|
|
4039
|
-
}
|
|
4040
|
-
)
|
|
4041
|
-
]
|
|
4042
|
-
}
|
|
4043
|
-
) }) }) })
|
|
4044
|
-
}
|
|
4045
|
-
),
|
|
4046
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4047
|
-
AnalyticsSection,
|
|
480
|
+
] }),
|
|
481
|
+
countDays !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
482
|
+
"button",
|
|
4048
483
|
{
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
children:
|
|
4054
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
4055
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Product" }),
|
|
4056
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Views" }),
|
|
4057
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Units" }),
|
|
4058
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: TABLE_HEAD_CELL_NOWRAP, children: "Views / unit" })
|
|
4059
|
-
] }) }),
|
|
4060
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
4061
|
-
((performance == null ? void 0 : performance.viewOpportunities) ?? []).slice(0, 8).map((product) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
4062
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
4063
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.total_views.toLocaleString() }),
|
|
4064
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
4065
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatRatio(product.views_per_unit) })
|
|
4066
|
-
] }, product.product_id)),
|
|
4067
|
-
!performanceLoading && !performanceError && (((_j = performance == null ? void 0 : performance.viewOpportunities) == null ? void 0 : _j.length) ?? 0) === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
4068
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-xs", children: "No product view opportunities yet." }) }),
|
|
4069
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
4070
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
4071
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
4072
|
-
] }) : null
|
|
4073
|
-
] })
|
|
4074
|
-
] }) }) })
|
|
484
|
+
type: "button",
|
|
485
|
+
onClick: () => setCountDays("all"),
|
|
486
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
487
|
+
"aria-label": "Show all customers (clear filter)",
|
|
488
|
+
children: "Clear filter"
|
|
4075
489
|
}
|
|
4076
490
|
)
|
|
491
|
+
] })
|
|
492
|
+
] }),
|
|
493
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
|
|
494
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
495
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Guest customers" }),
|
|
496
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.guestCount).toLocaleString() })
|
|
497
|
+
] }),
|
|
498
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
499
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Registered customers" }),
|
|
500
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.registeredCount).toLocaleString() })
|
|
501
|
+
] }),
|
|
502
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
503
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Overall customers" }),
|
|
504
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.totalCount).toLocaleString() })
|
|
505
|
+
] }),
|
|
506
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
507
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Deleted accounts" }),
|
|
508
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: (data.deletedAccountsCount ?? 0).toLocaleString() })
|
|
509
|
+
] })
|
|
510
|
+
] }),
|
|
511
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
512
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
513
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Customers over time" }),
|
|
514
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
515
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
516
|
+
ui.Label,
|
|
517
|
+
{
|
|
518
|
+
htmlFor: "customers-graph-period",
|
|
519
|
+
className: "text-ui-fg-muted text-sm",
|
|
520
|
+
children: "Range"
|
|
521
|
+
}
|
|
522
|
+
),
|
|
523
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
524
|
+
"select",
|
|
525
|
+
{
|
|
526
|
+
id: "customers-graph-period",
|
|
527
|
+
value: graphPeriod,
|
|
528
|
+
onChange: (e) => setGraphPeriod(e.target.value),
|
|
529
|
+
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",
|
|
530
|
+
children: GRAPH_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
531
|
+
}
|
|
532
|
+
)
|
|
533
|
+
] })
|
|
4077
534
|
] }),
|
|
4078
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4079
|
-
|
|
535
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: graphLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
536
|
+
"div",
|
|
4080
537
|
{
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
538
|
+
className: "flex items-center justify-center h-full",
|
|
539
|
+
role: "status",
|
|
540
|
+
"aria-label": "Loading customers over time",
|
|
541
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
|
|
542
|
+
}
|
|
543
|
+
) : graphError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: graphError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
544
|
+
recharts.LineChart,
|
|
545
|
+
{
|
|
546
|
+
data: series,
|
|
547
|
+
margin: { top: 8, right: 8, left: 8, bottom: 8 },
|
|
548
|
+
children: [
|
|
4090
549
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4091
550
|
recharts.CartesianGrid,
|
|
4092
551
|
{
|
|
4093
552
|
strokeDasharray: "3 3",
|
|
4094
|
-
|
|
553
|
+
className: "stroke-ui-border-base"
|
|
4095
554
|
}
|
|
4096
555
|
),
|
|
4097
556
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4098
557
|
recharts.XAxis,
|
|
4099
558
|
{
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
label: {
|
|
4108
|
-
value: "Views",
|
|
4109
|
-
position: "insideBottom",
|
|
4110
|
-
offset: -4,
|
|
4111
|
-
fontSize: 10,
|
|
4112
|
-
fill: "#9ca3af"
|
|
559
|
+
dataKey: "date",
|
|
560
|
+
tick: { fontSize: 12 },
|
|
561
|
+
tickFormatter: (v, index) => {
|
|
562
|
+
const row = series[index];
|
|
563
|
+
if (row == null ? void 0 : row.label) return row.label;
|
|
564
|
+
const d = new Date(v);
|
|
565
|
+
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
4113
566
|
}
|
|
4114
567
|
}
|
|
4115
568
|
),
|
|
4116
569
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4117
570
|
recharts.YAxis,
|
|
4118
571
|
{
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
tick: CHART_AXIS_TICK_SM,
|
|
4123
|
-
stroke: CHART_AXIS_LINE,
|
|
4124
|
-
tickLine: { stroke: CHART_AXIS_LINE },
|
|
4125
|
-
allowDecimals: false,
|
|
4126
|
-
label: {
|
|
4127
|
-
value: "Units sold",
|
|
4128
|
-
angle: -90,
|
|
4129
|
-
position: "insideLeft",
|
|
4130
|
-
fontSize: 10,
|
|
4131
|
-
fill: "#9ca3af"
|
|
4132
|
-
}
|
|
572
|
+
tick: { fontSize: 12 },
|
|
573
|
+
tickFormatter: (v) => String(Math.floor(Number(v))),
|
|
574
|
+
allowDecimals: false
|
|
4133
575
|
}
|
|
4134
576
|
),
|
|
577
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(CustomersOverTimeTooltip, {}) }),
|
|
578
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
4135
579
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4136
|
-
recharts.
|
|
580
|
+
recharts.Line,
|
|
4137
581
|
{
|
|
4138
|
-
|
|
4139
|
-
|
|
582
|
+
type: "monotone",
|
|
583
|
+
dataKey: "registered_count",
|
|
584
|
+
name: "Registered",
|
|
585
|
+
stroke: REGISTERED_COLOR,
|
|
586
|
+
strokeWidth: 2,
|
|
587
|
+
dot: { r: 3 }
|
|
4140
588
|
}
|
|
4141
589
|
),
|
|
4142
590
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4143
|
-
recharts.
|
|
591
|
+
recharts.Line,
|
|
4144
592
|
{
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
593
|
+
type: "monotone",
|
|
594
|
+
dataKey: "guest_count",
|
|
595
|
+
name: "Guest",
|
|
596
|
+
stroke: GUEST_COLOR,
|
|
597
|
+
strokeWidth: 2,
|
|
598
|
+
dot: { r: 3 }
|
|
4148
599
|
}
|
|
4149
600
|
)
|
|
4150
|
-
]
|
|
601
|
+
]
|
|
4151
602
|
}
|
|
4152
|
-
)
|
|
603
|
+
) }) })
|
|
4153
604
|
] })
|
|
4154
|
-
] })
|
|
605
|
+
] });
|
|
4155
606
|
}
|
|
4156
607
|
const ANALYTICS_MODULES = [
|
|
4157
608
|
{ id: "orders", label: "Orders" },
|
|
4158
|
-
{ id: "customers", label: "Customers" }
|
|
4159
|
-
{ id: "products", label: "Products" }
|
|
609
|
+
{ id: "customers", label: "Customers" }
|
|
4160
610
|
];
|
|
4161
611
|
const AnalyticsPage = () => {
|
|
4162
612
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
4163
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4164
|
-
"
|
|
4165
|
-
{
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
"aria-label": "Analytics modules",
|
|
4179
|
-
children: ANALYTICS_MODULES.map((m) => {
|
|
4180
|
-
const active = activeModule === m.id;
|
|
4181
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4182
|
-
"button",
|
|
4183
|
-
{
|
|
4184
|
-
type: "button",
|
|
4185
|
-
role: "tab",
|
|
4186
|
-
"aria-selected": active,
|
|
4187
|
-
onClick: () => setActiveModule(m.id),
|
|
4188
|
-
className: `rounded-md 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(),
|
|
4189
|
-
children: m.label
|
|
4190
|
-
},
|
|
4191
|
-
m.id
|
|
4192
|
-
);
|
|
4193
|
-
})
|
|
4194
|
-
}
|
|
4195
|
-
)
|
|
4196
|
-
] }) }),
|
|
4197
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 w-full", children: [
|
|
4198
|
-
activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
|
|
4199
|
-
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {}),
|
|
4200
|
-
activeModule === "products" && /* @__PURE__ */ jsxRuntime.jsx(ProductsDashboard, {})
|
|
4201
|
-
] })
|
|
4202
|
-
]
|
|
4203
|
-
}
|
|
4204
|
-
) }) }) });
|
|
613
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full bg-ui-bg-base", children: /* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-6 px-6", children: [
|
|
614
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", className: "mb-4", children: "Analytics" }),
|
|
615
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 mb-6", children: ANALYTICS_MODULES.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
616
|
+
ui.Button,
|
|
617
|
+
{
|
|
618
|
+
variant: activeModule === m.id ? "secondary" : "transparent",
|
|
619
|
+
size: "small",
|
|
620
|
+
onClick: () => setActiveModule(m.id),
|
|
621
|
+
children: m.label
|
|
622
|
+
},
|
|
623
|
+
m.id
|
|
624
|
+
)) }),
|
|
625
|
+
activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
|
|
626
|
+
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {})
|
|
627
|
+
] }) }) }) });
|
|
4205
628
|
};
|
|
4206
629
|
const config = adminSdk.defineRouteConfig({
|
|
4207
630
|
label: "Analytics",
|