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