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