medusa-analytics 0.0.2 → 0.0.4
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 +431 -54
- package/.medusa/server/src/admin/index.mjs +432 -55
- package/.medusa/server/src/api/admin/analytics/customers-over-time/route.js +266 -0
- package/.medusa/server/src/api/admin/analytics/customers-over-time/types.js +7 -0
- package/.medusa/server/src/api/admin/analytics/customers-summary/route.js +90 -0
- package/.medusa/server/src/api/admin/analytics/customers-summary/types.js +7 -0
- package/.medusa/server/src/api/admin/analytics/orders-over-time/route.js +142 -0
- package/.medusa/server/src/api/admin/analytics/orders-over-time/types.js +7 -0
- package/.medusa/server/src/api/admin/analytics/orders-summary/route.js +14 -25
- package/.medusa/server/src/api/admin/analytics/orders-summary/types.js +2 -1
- package/package.json +1 -1
|
@@ -5,6 +5,39 @@ const adminSdk = require("@medusajs/admin-sdk");
|
|
|
5
5
|
const icons = require("@medusajs/icons");
|
|
6
6
|
const ui = require("@medusajs/ui");
|
|
7
7
|
const recharts = require("recharts");
|
|
8
|
+
function OrdersOverTimeTooltip({
|
|
9
|
+
active,
|
|
10
|
+
payload,
|
|
11
|
+
label
|
|
12
|
+
}) {
|
|
13
|
+
var _a, _b;
|
|
14
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
15
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
16
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : label);
|
|
17
|
+
const value = ((_b = payload[0]) == null ? void 0 : _b.value) ?? 0;
|
|
18
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
19
|
+
"div",
|
|
20
|
+
{
|
|
21
|
+
style: {
|
|
22
|
+
backgroundColor: "rgb(254, 233, 233)",
|
|
23
|
+
color: "rgba(5, 0, 0, 0)",
|
|
24
|
+
border: "1px solidrgb(0, 0, 0)",
|
|
25
|
+
borderRadius: "8px",
|
|
26
|
+
padding: "12px 16px",
|
|
27
|
+
boxShadow: "0 4px 14px rgba(102, 102, 102, 0.99)",
|
|
28
|
+
fontSize: "14px",
|
|
29
|
+
lineHeight: 1.5
|
|
30
|
+
},
|
|
31
|
+
children: [
|
|
32
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px", color: "#111827" }, children: displayLabel }),
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#374151" }, children: [
|
|
34
|
+
"Orders: ",
|
|
35
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: "#1f2937" }, children: value })
|
|
36
|
+
] })
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
}
|
|
8
41
|
const STATUS_COLORS = {
|
|
9
42
|
pending: "#F59E0B",
|
|
10
43
|
confirmed: "#3B82F6",
|
|
@@ -29,25 +62,32 @@ function formatCurrency(value) {
|
|
|
29
62
|
maximumFractionDigits: 0
|
|
30
63
|
}).format(value);
|
|
31
64
|
}
|
|
65
|
+
const SUMMARY_PERIODS = [
|
|
66
|
+
{ value: "0", label: "Today's orders" },
|
|
67
|
+
{ value: "7", label: "Last 7 days" },
|
|
68
|
+
{ value: "30", label: "Last 30 days" },
|
|
69
|
+
{ value: "90", label: "Last 90 days" }
|
|
70
|
+
];
|
|
71
|
+
const OVER_TIME_PERIODS = [
|
|
72
|
+
{ value: "daily", label: "Daily" },
|
|
73
|
+
{ value: "weekly", label: "Weekly" },
|
|
74
|
+
{ value: "monthly", label: "Monthly" }
|
|
75
|
+
];
|
|
32
76
|
function OrdersDashboard() {
|
|
33
77
|
const [data, setData] = react.useState(null);
|
|
34
78
|
const [loading, setLoading] = react.useState(true);
|
|
35
79
|
const [error, setError] = react.useState(null);
|
|
36
80
|
const [filter, setFilter] = react.useState("7");
|
|
81
|
+
const [dailyOrders, setDailyOrders] = react.useState([]);
|
|
82
|
+
const [overTimePeriod, setOverTimePeriod] = react.useState("weekly");
|
|
83
|
+
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
84
|
+
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
37
85
|
react.useEffect(() => {
|
|
38
86
|
let cancelled = false;
|
|
39
87
|
setLoading(true);
|
|
40
88
|
setError(null);
|
|
41
89
|
const url = `/admin/analytics/orders-summary?days=${filter}`;
|
|
42
|
-
if (typeof fetch !== "undefined") {
|
|
43
|
-
fetch("http://127.0.0.1:7242/ingest/47f434f8-0643-48e5-bd1a-edfe05973288", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "21bff8" }, body: JSON.stringify({ sessionId: "21bff8", location: "OrdersDashboard.tsx:useEffect", message: "OrdersDashboard fetch", data: { filter, url }, timestamp: Date.now(), hypothesisId: "C" }) }).catch(() => {
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
90
|
fetch(url).then((res) => {
|
|
47
|
-
if (typeof fetch !== "undefined") {
|
|
48
|
-
fetch("http://127.0.0.1:7242/ingest/47f434f8-0643-48e5-bd1a-edfe05973288", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "21bff8" }, body: JSON.stringify({ sessionId: "21bff8", location: "OrdersDashboard.tsx:fetch.then", message: "API response", data: { status: res.status, statusText: res.statusText, url: res.url }, timestamp: Date.now(), hypothesisId: "C" }) }).catch(() => {
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
91
|
if (!res.ok) throw new Error(res.statusText);
|
|
52
92
|
return res.json();
|
|
53
93
|
}).then((body) => {
|
|
@@ -61,6 +101,25 @@ function OrdersDashboard() {
|
|
|
61
101
|
cancelled = true;
|
|
62
102
|
};
|
|
63
103
|
}, [filter]);
|
|
104
|
+
react.useEffect(() => {
|
|
105
|
+
let cancelled = false;
|
|
106
|
+
setOverTimeLoading(true);
|
|
107
|
+
setOverTimeError(null);
|
|
108
|
+
const url = `/admin/analytics/orders-over-time?period=${overTimePeriod}`;
|
|
109
|
+
fetch(url).then((res) => {
|
|
110
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
111
|
+
return res.json();
|
|
112
|
+
}).then((body) => {
|
|
113
|
+
if (!cancelled) setDailyOrders(body.dailyOrders ?? []);
|
|
114
|
+
}).catch((e) => {
|
|
115
|
+
if (!cancelled) setOverTimeError(e instanceof Error ? e.message : String(e));
|
|
116
|
+
}).finally(() => {
|
|
117
|
+
if (!cancelled) setOverTimeLoading(false);
|
|
118
|
+
});
|
|
119
|
+
return () => {
|
|
120
|
+
cancelled = true;
|
|
121
|
+
};
|
|
122
|
+
}, [overTimePeriod]);
|
|
64
123
|
if (loading) {
|
|
65
124
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
66
125
|
"div",
|
|
@@ -85,24 +144,19 @@ function OrdersDashboard() {
|
|
|
85
144
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
|
|
86
145
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
87
146
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Orders Analytics" }),
|
|
88
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
147
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
89
148
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "analytics-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
|
|
90
|
-
/* @__PURE__ */ jsxRuntime.
|
|
149
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
91
150
|
"select",
|
|
92
151
|
{
|
|
93
152
|
id: "analytics-period",
|
|
94
153
|
value: filter,
|
|
95
154
|
onChange: (e) => setFilter(e.target.value),
|
|
96
155
|
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",
|
|
97
|
-
children:
|
|
98
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "0", children: "Today's orders" }),
|
|
99
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "7", children: "Last 7 days" }),
|
|
100
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "30", children: "Last 30 days" }),
|
|
101
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "90", children: "Last 90 days" })
|
|
102
|
-
]
|
|
156
|
+
children: SUMMARY_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
103
157
|
}
|
|
104
158
|
)
|
|
105
|
-
] })
|
|
159
|
+
] }) })
|
|
106
160
|
] }),
|
|
107
161
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
|
|
108
162
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
@@ -133,11 +187,56 @@ function OrdersDashboard() {
|
|
|
133
187
|
] })
|
|
134
188
|
] }),
|
|
135
189
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
136
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-4", children: "Orders
|
|
137
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
190
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-4", children: "Orders by status" }),
|
|
191
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { children: [
|
|
192
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
193
|
+
recharts.Pie,
|
|
194
|
+
{
|
|
195
|
+
data: pieData,
|
|
196
|
+
cx: "50%",
|
|
197
|
+
cy: "50%",
|
|
198
|
+
innerRadius: 60,
|
|
199
|
+
outerRadius: 100,
|
|
200
|
+
paddingAngle: 2,
|
|
201
|
+
dataKey: "value",
|
|
202
|
+
nameKey: "name",
|
|
203
|
+
label: ({ name, value }) => `${name}: ${value}`,
|
|
204
|
+
children: pieData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-${index}`))
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (value) => [value ?? 0, "Orders"] }),
|
|
208
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {})
|
|
209
|
+
] }) }) })
|
|
210
|
+
] }),
|
|
211
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
212
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
213
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Orders over time" }),
|
|
214
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
215
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "over-time-period", className: "text-ui-fg-muted text-sm", children: "Range" }),
|
|
216
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
217
|
+
"select",
|
|
218
|
+
{
|
|
219
|
+
id: "over-time-period",
|
|
220
|
+
value: overTimePeriod,
|
|
221
|
+
onChange: (e) => setOverTimePeriod(e.target.value),
|
|
222
|
+
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",
|
|
223
|
+
children: OVER_TIME_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
] })
|
|
227
|
+
] }),
|
|
228
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
229
|
+
"div",
|
|
230
|
+
{
|
|
231
|
+
className: "flex items-center justify-center h-full",
|
|
232
|
+
role: "status",
|
|
233
|
+
"aria-label": "Loading orders over time",
|
|
234
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
|
|
235
|
+
}
|
|
236
|
+
) : overTimeError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: overTimeError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
138
237
|
recharts.LineChart,
|
|
139
238
|
{
|
|
140
|
-
data:
|
|
239
|
+
data: dailyOrders,
|
|
141
240
|
margin: { top: 8, right: 8, left: 8, bottom: 8 },
|
|
142
241
|
children: [
|
|
143
242
|
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", className: "stroke-ui-border-base" }),
|
|
@@ -146,20 +245,16 @@ function OrdersDashboard() {
|
|
|
146
245
|
{
|
|
147
246
|
dataKey: "date",
|
|
148
247
|
tick: { fontSize: 12 },
|
|
149
|
-
tickFormatter: (v) => {
|
|
248
|
+
tickFormatter: (v, index) => {
|
|
249
|
+
const row = dailyOrders[index];
|
|
250
|
+
if (row == null ? void 0 : row.label) return row.label;
|
|
150
251
|
const d = new Date(v);
|
|
151
252
|
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
152
253
|
}
|
|
153
254
|
}
|
|
154
255
|
),
|
|
155
256
|
/* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { tick: { fontSize: 12 } }),
|
|
156
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
157
|
-
recharts.Tooltip,
|
|
158
|
-
{
|
|
159
|
-
labelFormatter: (v) => new Date(v).toLocaleDateString(),
|
|
160
|
-
formatter: (value) => [value ?? 0, "Orders"]
|
|
161
|
-
}
|
|
162
|
-
),
|
|
257
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(OrdersOverTimeTooltip, {}) }),
|
|
163
258
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
164
259
|
recharts.Line,
|
|
165
260
|
{
|
|
@@ -174,42 +269,311 @@ function OrdersDashboard() {
|
|
|
174
269
|
]
|
|
175
270
|
}
|
|
176
271
|
) }) })
|
|
177
|
-
] })
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
272
|
+
] })
|
|
273
|
+
] });
|
|
274
|
+
}
|
|
275
|
+
function CustomersOverTimeTooltip({
|
|
276
|
+
active,
|
|
277
|
+
payload,
|
|
278
|
+
label
|
|
279
|
+
}) {
|
|
280
|
+
var _a, _b, _c;
|
|
281
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
282
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
283
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : label);
|
|
284
|
+
const registered = Math.floor(((_b = payload.find((p) => p.name === "Registered")) == null ? void 0 : _b.value) ?? 0);
|
|
285
|
+
const guest = Math.floor(((_c = payload.find((p) => p.name === "Guest")) == null ? void 0 : _c.value) ?? 0);
|
|
286
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
287
|
+
"div",
|
|
288
|
+
{
|
|
289
|
+
style: {
|
|
290
|
+
backgroundColor: "rgb(254, 254, 254)",
|
|
291
|
+
color: "#111827",
|
|
292
|
+
border: "1px solid rgb(229, 231, 235)",
|
|
293
|
+
borderRadius: "8px",
|
|
294
|
+
padding: "12px 16px",
|
|
295
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.08)",
|
|
296
|
+
fontSize: "14px",
|
|
297
|
+
lineHeight: 1.5
|
|
298
|
+
},
|
|
299
|
+
children: [
|
|
300
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px", color: "#111827" }, children: displayLabel }),
|
|
301
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#374151" }, children: [
|
|
302
|
+
"Registered: ",
|
|
303
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: "#1f2937" }, children: registered })
|
|
304
|
+
] }),
|
|
305
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { color: "#374151" }, children: [
|
|
306
|
+
"Guest: ",
|
|
307
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { style: { color: "#1f2937" }, children: guest })
|
|
308
|
+
] })
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
const COUNT_DAY_PRESETS = [
|
|
314
|
+
{ value: "3", label: "Last 3 days" },
|
|
315
|
+
{ value: "5", label: "Last 5 days" },
|
|
316
|
+
{ value: "7", label: "Last 7 days" },
|
|
317
|
+
{ value: "9", label: "Last 9 days" },
|
|
318
|
+
{ value: "14", label: "Last 14 days" },
|
|
319
|
+
{ value: "30", label: "Last 30 days" },
|
|
320
|
+
{ value: "90", label: "Last 90 days" }
|
|
321
|
+
];
|
|
322
|
+
const GRAPH_PERIODS = [
|
|
323
|
+
{ value: "daily", label: "Daily" },
|
|
324
|
+
{ value: "weekly", label: "Weekly" },
|
|
325
|
+
{ value: "monthly", label: "Monthly" },
|
|
326
|
+
{ value: "yearly", label: "Yearly" }
|
|
327
|
+
];
|
|
328
|
+
const REGISTERED_COLOR = "var(--medusa-color-ui-fg-interactive)";
|
|
329
|
+
const GUEST_COLOR = "#6B7280";
|
|
330
|
+
function CustomersDashboard() {
|
|
331
|
+
const [data, setData] = react.useState(null);
|
|
332
|
+
const [loading, setLoading] = react.useState(true);
|
|
333
|
+
const [error, setError] = react.useState(null);
|
|
334
|
+
const [countDays, setCountDays] = react.useState("7");
|
|
335
|
+
const [series, setSeries] = react.useState([]);
|
|
336
|
+
const [graphPeriod, setGraphPeriod] = react.useState("weekly");
|
|
337
|
+
const [graphLoading, setGraphLoading] = react.useState(true);
|
|
338
|
+
const [graphError, setGraphError] = react.useState(null);
|
|
339
|
+
react.useEffect(() => {
|
|
340
|
+
let cancelled = false;
|
|
341
|
+
setLoading(true);
|
|
342
|
+
setError(null);
|
|
343
|
+
const url = `/admin/analytics/customers-summary?days=${countDays}`;
|
|
344
|
+
fetch(url).then((res) => {
|
|
345
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
346
|
+
return res.json();
|
|
347
|
+
}).then((body) => {
|
|
348
|
+
if (!cancelled) setData(body);
|
|
349
|
+
}).catch((e) => {
|
|
350
|
+
if (!cancelled) setError(e instanceof Error ? e.message : String(e));
|
|
351
|
+
}).finally(() => {
|
|
352
|
+
if (!cancelled) setLoading(false);
|
|
353
|
+
});
|
|
354
|
+
return () => {
|
|
355
|
+
cancelled = true;
|
|
356
|
+
};
|
|
357
|
+
}, [countDays]);
|
|
358
|
+
react.useEffect(() => {
|
|
359
|
+
let cancelled = false;
|
|
360
|
+
setGraphLoading(true);
|
|
361
|
+
setGraphError(null);
|
|
362
|
+
const url = `/admin/analytics/customers-over-time?period=${graphPeriod}`;
|
|
363
|
+
fetch("http://127.0.0.1:7242/ingest/47f434f8-0643-48e5-bd1a-edfe05973288", {
|
|
364
|
+
method: "POST",
|
|
365
|
+
headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "4a7f37" },
|
|
366
|
+
body: JSON.stringify({
|
|
367
|
+
sessionId: "4a7f37",
|
|
368
|
+
location: "CustomersDashboard.tsx:fetch",
|
|
369
|
+
message: "customers-over-time fetch start",
|
|
370
|
+
data: { graphPeriod, url },
|
|
371
|
+
timestamp: Date.now(),
|
|
372
|
+
hypothesisId: "H1-H2"
|
|
373
|
+
})
|
|
374
|
+
}).catch(() => {
|
|
375
|
+
});
|
|
376
|
+
fetch(url).then((res) => {
|
|
377
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
378
|
+
return res.json();
|
|
379
|
+
}).then((body) => {
|
|
380
|
+
var _a, _b;
|
|
381
|
+
const received = body.series ?? [];
|
|
382
|
+
fetch("http://127.0.0.1:7242/ingest/47f434f8-0643-48e5-bd1a-edfe05973288", {
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "4a7f37" },
|
|
385
|
+
body: JSON.stringify({
|
|
386
|
+
sessionId: "4a7f37",
|
|
387
|
+
location: "CustomersDashboard.tsx:response",
|
|
388
|
+
message: "customers-over-time response",
|
|
389
|
+
data: { graphPeriod, seriesLength: received.length, firstDate: (_a = received[0]) == null ? void 0 : _a.date, lastDate: (_b = received[received.length - 1]) == null ? void 0 : _b.date, cancelled },
|
|
390
|
+
timestamp: Date.now(),
|
|
391
|
+
hypothesisId: "H2-H5"
|
|
392
|
+
})
|
|
393
|
+
}).catch(() => {
|
|
394
|
+
});
|
|
395
|
+
if (!cancelled) setSeries(received);
|
|
396
|
+
}).catch((e) => {
|
|
397
|
+
if (!cancelled) setGraphError(e instanceof Error ? e.message : String(e));
|
|
398
|
+
}).finally(() => {
|
|
399
|
+
if (!cancelled) setGraphLoading(false);
|
|
400
|
+
});
|
|
401
|
+
return () => {
|
|
402
|
+
cancelled = true;
|
|
403
|
+
};
|
|
404
|
+
}, [graphPeriod]);
|
|
405
|
+
if (loading) {
|
|
406
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
407
|
+
"div",
|
|
408
|
+
{
|
|
409
|
+
className: "flex items-center justify-center min-h-[320px]",
|
|
410
|
+
role: "status",
|
|
411
|
+
"aria-label": "Loading analytics",
|
|
412
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (error || !data) {
|
|
417
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: error ?? "Failed to load analytics" }) });
|
|
418
|
+
}
|
|
419
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
421
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Customers Analytics" }),
|
|
422
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
181
423
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
182
|
-
|
|
424
|
+
ui.Label,
|
|
183
425
|
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
innerRadius: 60,
|
|
188
|
-
outerRadius: 100,
|
|
189
|
-
paddingAngle: 2,
|
|
190
|
-
dataKey: "value",
|
|
191
|
-
nameKey: "name",
|
|
192
|
-
label: ({ name, value }) => `${name}: ${value}`,
|
|
193
|
-
children: pieData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-${index}`))
|
|
426
|
+
htmlFor: "customers-count-period",
|
|
427
|
+
className: "text-ui-fg-muted text-sm",
|
|
428
|
+
children: "Period (counts)"
|
|
194
429
|
}
|
|
195
430
|
),
|
|
196
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
197
|
-
|
|
198
|
-
|
|
431
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
432
|
+
"select",
|
|
433
|
+
{
|
|
434
|
+
id: "customers-count-period",
|
|
435
|
+
value: countDays,
|
|
436
|
+
onChange: (e) => setCountDays(e.target.value),
|
|
437
|
+
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",
|
|
438
|
+
children: COUNT_DAY_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
] }) })
|
|
442
|
+
] }),
|
|
443
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-3 gap-4", children: [
|
|
444
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
445
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Guest customers" }),
|
|
446
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.guestCount).toLocaleString() })
|
|
447
|
+
] }),
|
|
448
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
449
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Registered customers" }),
|
|
450
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.registeredCount).toLocaleString() })
|
|
451
|
+
] }),
|
|
452
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
453
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Overall customers" }),
|
|
454
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: Math.floor(data.totalCount).toLocaleString() })
|
|
455
|
+
] })
|
|
456
|
+
] }),
|
|
457
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
458
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Customers over time" }),
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
461
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
462
|
+
ui.Label,
|
|
463
|
+
{
|
|
464
|
+
htmlFor: "customers-graph-period",
|
|
465
|
+
className: "text-ui-fg-muted text-sm",
|
|
466
|
+
children: "Range"
|
|
467
|
+
}
|
|
468
|
+
),
|
|
469
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
470
|
+
"select",
|
|
471
|
+
{
|
|
472
|
+
id: "customers-graph-period",
|
|
473
|
+
value: graphPeriod,
|
|
474
|
+
onChange: (e) => setGraphPeriod(e.target.value),
|
|
475
|
+
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",
|
|
476
|
+
children: GRAPH_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
] })
|
|
480
|
+
] }),
|
|
481
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: graphLoading ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
482
|
+
"div",
|
|
483
|
+
{
|
|
484
|
+
className: "flex items-center justify-center h-full",
|
|
485
|
+
role: "status",
|
|
486
|
+
"aria-label": "Loading customers over time",
|
|
487
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" })
|
|
488
|
+
}
|
|
489
|
+
) : graphError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: graphError }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
490
|
+
recharts.LineChart,
|
|
491
|
+
{
|
|
492
|
+
data: series,
|
|
493
|
+
margin: { top: 8, right: 8, left: 8, bottom: 8 },
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
496
|
+
recharts.CartesianGrid,
|
|
497
|
+
{
|
|
498
|
+
strokeDasharray: "3 3",
|
|
499
|
+
className: "stroke-ui-border-base"
|
|
500
|
+
}
|
|
501
|
+
),
|
|
502
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
503
|
+
recharts.XAxis,
|
|
504
|
+
{
|
|
505
|
+
dataKey: "date",
|
|
506
|
+
tick: { fontSize: 12 },
|
|
507
|
+
tickFormatter: (v, index) => {
|
|
508
|
+
const row = series[index];
|
|
509
|
+
if (row == null ? void 0 : row.label) return row.label;
|
|
510
|
+
const d = new Date(v);
|
|
511
|
+
return `${d.getUTCMonth() + 1}/${d.getUTCDate()}`;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
),
|
|
515
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
516
|
+
recharts.YAxis,
|
|
517
|
+
{
|
|
518
|
+
tick: { fontSize: 12 },
|
|
519
|
+
tickFormatter: (v) => String(Math.floor(Number(v))),
|
|
520
|
+
allowDecimals: false
|
|
521
|
+
}
|
|
522
|
+
),
|
|
523
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(CustomersOverTimeTooltip, {}) }),
|
|
524
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
525
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
526
|
+
recharts.Line,
|
|
527
|
+
{
|
|
528
|
+
type: "monotone",
|
|
529
|
+
dataKey: "registered_count",
|
|
530
|
+
name: "Registered",
|
|
531
|
+
stroke: REGISTERED_COLOR,
|
|
532
|
+
strokeWidth: 2,
|
|
533
|
+
dot: { r: 3 }
|
|
534
|
+
}
|
|
535
|
+
),
|
|
536
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
537
|
+
recharts.Line,
|
|
538
|
+
{
|
|
539
|
+
type: "monotone",
|
|
540
|
+
dataKey: "guest_count",
|
|
541
|
+
name: "Guest",
|
|
542
|
+
stroke: GUEST_COLOR,
|
|
543
|
+
strokeWidth: 2,
|
|
544
|
+
dot: { r: 3 }
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
]
|
|
548
|
+
}
|
|
549
|
+
) }) })
|
|
199
550
|
] })
|
|
200
551
|
] });
|
|
201
552
|
}
|
|
202
|
-
const ANALYTICS_MODULES = [
|
|
553
|
+
const ANALYTICS_MODULES = [
|
|
554
|
+
{ id: "orders", label: "Orders" },
|
|
555
|
+
{ id: "customers", label: "Customers" }
|
|
556
|
+
];
|
|
203
557
|
const PLUGIN_VERSION = "0.2";
|
|
204
558
|
const AnalyticsPage = () => {
|
|
205
559
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
206
|
-
|
|
207
|
-
fetch("http://127.0.0.1:7242/ingest/47f434f8-0643-48e5-bd1a-edfe05973288", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "21bff8" }, body: JSON.stringify({ sessionId: "21bff8", location: "page.tsx:AnalyticsPage", message: "Analytics page mounted", data: { pluginVersion: PLUGIN_VERSION, activeModule }, timestamp: Date.now(), hypothesisId: "A" }) }).catch(() => {
|
|
208
|
-
});
|
|
209
|
-
}
|
|
560
|
+
const [sidebarOpen, setSidebarOpen] = react.useState(false);
|
|
210
561
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full bg-ui-bg-base", children: [
|
|
211
|
-
/* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "w-56 shrink-0 border-r border-ui-border-base bg-ui-bg-subtle p-4 flex flex-col", children: [
|
|
212
|
-
/* @__PURE__ */ jsxRuntime.
|
|
562
|
+
sidebarOpen && /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "w-56 shrink-0 border-r border-ui-border-base bg-ui-bg-subtle p-4 flex flex-col relative", children: [
|
|
563
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
564
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "text-ui-fg-subtle", children: "Analysis" }),
|
|
565
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
566
|
+
ui.Button,
|
|
567
|
+
{
|
|
568
|
+
variant: "transparent",
|
|
569
|
+
size: "small",
|
|
570
|
+
className: "shrink-0",
|
|
571
|
+
onClick: () => setSidebarOpen(false),
|
|
572
|
+
"aria-label": "Close sidebar",
|
|
573
|
+
children: "×"
|
|
574
|
+
}
|
|
575
|
+
)
|
|
576
|
+
] }),
|
|
213
577
|
/* @__PURE__ */ jsxRuntime.jsx("nav", { className: "flex flex-col gap-1 flex-1", children: ANALYTICS_MODULES.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
214
578
|
ui.Button,
|
|
215
579
|
{
|
|
@@ -225,7 +589,20 @@ const AnalyticsPage = () => {
|
|
|
225
589
|
PLUGIN_VERSION
|
|
226
590
|
] })
|
|
227
591
|
] }),
|
|
228
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
592
|
+
!sidebarOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 border-r border-ui-border-base p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
593
|
+
ui.Button,
|
|
594
|
+
{
|
|
595
|
+
variant: "secondary",
|
|
596
|
+
size: "small",
|
|
597
|
+
onClick: () => setSidebarOpen(true),
|
|
598
|
+
"aria-label": "Show sidebar",
|
|
599
|
+
children: "Analysis"
|
|
600
|
+
}
|
|
601
|
+
) }),
|
|
602
|
+
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-6 px-6", children: [
|
|
603
|
+
activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
|
|
604
|
+
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {})
|
|
605
|
+
] }) }) })
|
|
229
606
|
] });
|
|
230
607
|
};
|
|
231
608
|
const config = adminSdk.defineRouteConfig({
|