medusa-plugin-statistics 0.1.0

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.
Files changed (44) hide show
  1. package/.medusa/server/medusa-config.d.ts +1 -0
  2. package/.medusa/server/medusa-config.js +23 -0
  3. package/.medusa/server/src/admin/index.js +790 -0
  4. package/.medusa/server/src/admin/index.mjs +789 -0
  5. package/.medusa/server/src/api/admin/mcp/route.d.ts +4 -0
  6. package/.medusa/server/src/api/admin/mcp/route.js +60 -0
  7. package/.medusa/server/src/api/admin/statistics/layout/route.d.ts +4 -0
  8. package/.medusa/server/src/api/admin/statistics/layout/route.js +37 -0
  9. package/.medusa/server/src/api/admin/statistics/low-stock/route.d.ts +3 -0
  10. package/.medusa/server/src/api/admin/statistics/low-stock/route.js +85 -0
  11. package/.medusa/server/src/api/admin/statistics/recalculate/route.d.ts +3 -0
  12. package/.medusa/server/src/api/admin/statistics/recalculate/route.js +32 -0
  13. package/.medusa/server/src/api/admin/statistics/recent-orders/route.d.ts +3 -0
  14. package/.medusa/server/src/api/admin/statistics/recent-orders/route.js +19 -0
  15. package/.medusa/server/src/api/admin/statistics/route.d.ts +3 -0
  16. package/.medusa/server/src/api/admin/statistics/route.js +55 -0
  17. package/.medusa/server/src/api/middlewares.d.ts +2 -0
  18. package/.medusa/server/src/api/middlewares.js +44 -0
  19. package/.medusa/server/src/api/validators.d.ts +85 -0
  20. package/.medusa/server/src/api/validators.js +30 -0
  21. package/.medusa/server/src/jobs/update-statistics.d.ts +7 -0
  22. package/.medusa/server/src/jobs/update-statistics.js +165 -0
  23. package/.medusa/server/src/mcp/server.d.ts +3 -0
  24. package/.medusa/server/src/mcp/server.js +23 -0
  25. package/.medusa/server/src/mcp/tools/customers.d.ts +3 -0
  26. package/.medusa/server/src/mcp/tools/customers.js +72 -0
  27. package/.medusa/server/src/mcp/tools/inventory.d.ts +3 -0
  28. package/.medusa/server/src/mcp/tools/inventory.js +70 -0
  29. package/.medusa/server/src/mcp/tools/orders.d.ts +3 -0
  30. package/.medusa/server/src/mcp/tools/orders.js +80 -0
  31. package/.medusa/server/src/mcp/tools/products.d.ts +3 -0
  32. package/.medusa/server/src/mcp/tools/products.js +72 -0
  33. package/.medusa/server/src/mcp/tools/query.d.ts +3 -0
  34. package/.medusa/server/src/mcp/tools/query.js +42 -0
  35. package/.medusa/server/src/modules/statistics/index.d.ts +22 -0
  36. package/.medusa/server/src/modules/statistics/index.js +25 -0
  37. package/.medusa/server/src/modules/statistics/migrations/Migration20260409171831.d.ts +5 -0
  38. package/.medusa/server/src/modules/statistics/migrations/Migration20260409171831.js +15 -0
  39. package/.medusa/server/src/modules/statistics/models/statistics-daily.d.ts +13 -0
  40. package/.medusa/server/src/modules/statistics/models/statistics-daily.js +18 -0
  41. package/.medusa/server/src/modules/statistics/service.d.ts +18 -0
  42. package/.medusa/server/src/modules/statistics/service.js +9 -0
  43. package/LICENSE +21 -0
  44. package/package.json +97 -0
@@ -0,0 +1,790 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const react = require("react");
4
+ const adminSdk = require("@medusajs/admin-sdk");
5
+ const icons = require("@medusajs/icons");
6
+ const ui = require("@medusajs/ui");
7
+ const reactGridLayout = require("react-grid-layout");
8
+ require("react-grid-layout/css/styles.css");
9
+ const reactQuery = require("@tanstack/react-query");
10
+ const Medusa = require("@medusajs/js-sdk");
11
+ const recharts = require("recharts");
12
+ const reactRouterDom = require("react-router-dom");
13
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
14
+ const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
15
+ const sdk = new Medusa__default.default({
16
+ baseUrl: "/",
17
+ debug: false,
18
+ auth: {
19
+ type: "session"
20
+ }
21
+ });
22
+ const useStatistics = (period) => {
23
+ return reactQuery.useQuery({
24
+ queryFn: () => sdk.client.fetch("/admin/statistics", { query: { period } }),
25
+ queryKey: ["statistics", period]
26
+ });
27
+ };
28
+ const useRecentOrders = (limit = 10) => {
29
+ return reactQuery.useQuery({
30
+ queryFn: () => sdk.client.fetch("/admin/statistics/recent-orders", { query: { limit } }),
31
+ queryKey: ["statistics-recent-orders", limit]
32
+ });
33
+ };
34
+ const useLowStock = (threshold = 10) => {
35
+ return reactQuery.useQuery({
36
+ queryFn: () => sdk.client.fetch("/admin/statistics/low-stock", { query: { threshold } }),
37
+ queryKey: ["statistics-low-stock", threshold]
38
+ });
39
+ };
40
+ const useStatisticsLayout = () => {
41
+ return reactQuery.useQuery({
42
+ queryFn: () => sdk.client.fetch("/admin/statistics/layout"),
43
+ queryKey: ["statistics-layout"]
44
+ });
45
+ };
46
+ const useSaveStatisticsLayout = () => {
47
+ const queryClient = reactQuery.useQueryClient();
48
+ return reactQuery.useMutation({
49
+ mutationFn: (layout) => sdk.client.fetch("/admin/statistics/layout", { method: "POST", body: { layout } }),
50
+ onSuccess: () => {
51
+ queryClient.invalidateQueries({ queryKey: ["statistics-layout"] });
52
+ }
53
+ });
54
+ };
55
+ const useRecalculateStatistics = () => {
56
+ const queryClient = reactQuery.useQueryClient();
57
+ return reactQuery.useMutation({
58
+ mutationFn: () => sdk.client.fetch("/admin/statistics/recalculate", { method: "POST", body: {} }),
59
+ onSuccess: () => {
60
+ queryClient.invalidateQueries({ queryKey: ["statistics"] });
61
+ }
62
+ });
63
+ };
64
+ const RevenueWidget = ({ statistics, totals }) => {
65
+ var _a;
66
+ const chartData = statistics.map((s) => ({
67
+ date: new Date(s.date).toLocaleDateString("en-US", { month: "short", day: "numeric" }),
68
+ revenue: s.revenue_total
69
+ }));
70
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-4 flex flex-col", children: [
71
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
72
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Revenue" }),
73
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xlarge", weight: "plus", className: "text-ui-fg-base", children: [
74
+ "$",
75
+ ((_a = totals.revenue_total) == null ? void 0 : _a.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })) ?? "0.00"
76
+ ] })
77
+ ] }),
78
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: chartData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, minHeight: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.AreaChart, { data: chartData, children: [
79
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: "revGrad", x1: "0", y1: "0", x2: "0", y2: "1", children: [
80
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "5%", stopColor: "#6366f1", stopOpacity: 0.3 }),
81
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "95%", stopColor: "#6366f1", stopOpacity: 0 })
82
+ ] }) }),
83
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", tick: { fontSize: 11 } }),
84
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { tick: { fontSize: 11 }, width: 60 }),
85
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(2)}`, "Revenue"] }),
86
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Area, { type: "monotone", dataKey: "revenue", stroke: "#6366f1", fill: "url(#revGrad)" })
87
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: "No data for this period." }) })
88
+ ] });
89
+ };
90
+ const OrdersCountWidget = ({ statistics, totals }) => {
91
+ var _a;
92
+ const chartData = statistics.map((s) => ({
93
+ date: new Date(s.date).toLocaleDateString("en-US", { month: "short", day: "numeric" }),
94
+ orders: s.order_count
95
+ }));
96
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-4 flex flex-col", children: [
97
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
98
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Orders" }),
99
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", className: "text-ui-fg-base", children: ((_a = totals.order_count) == null ? void 0 : _a.toLocaleString()) ?? 0 })
100
+ ] }),
101
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: chartData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, minHeight: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data: chartData, children: [
102
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", tick: { fontSize: 11 } }),
103
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { tick: { fontSize: 11 }, width: 40, allowDecimals: false }),
104
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, {}),
105
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "orders", fill: "#10b981", radius: [4, 4, 0, 0] })
106
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: "No data for this period." }) })
107
+ ] });
108
+ };
109
+ const PERIOD_LABELS$1 = {
110
+ today: "Today",
111
+ week: "Last 7 days",
112
+ month: "Last 30 days"
113
+ };
114
+ const AovWidget = ({ statistics, totals, period }) => {
115
+ var _a;
116
+ const chartData = statistics.map((s) => ({
117
+ date: new Date(s.date).toLocaleDateString("en-US", { month: "short", day: "numeric" }),
118
+ aov: s.average_order_value
119
+ }));
120
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-4 flex flex-col", children: [
121
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
122
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Avg Order Value" }),
123
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: PERIOD_LABELS$1[period] ?? period })
124
+ ] }),
125
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xlarge", weight: "plus", className: "text-ui-fg-base", children: [
126
+ "$",
127
+ ((_a = totals.average_order_value) == null ? void 0 : _a.toFixed(2)) ?? "0.00"
128
+ ] }),
129
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0 mt-2", children: chartData.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, minHeight: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data: chartData, children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "date", tick: { fontSize: 11 } }),
131
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.YAxis, { tick: { fontSize: 11 }, width: 50, tickFormatter: (v) => `$${v}` }),
132
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(2)}`, "AOV"] }),
133
+ /* @__PURE__ */ jsxRuntime.jsx(
134
+ recharts.Line,
135
+ {
136
+ type: "monotone",
137
+ dataKey: "aov",
138
+ stroke: "#f59e0b",
139
+ strokeWidth: 2,
140
+ dot: false
141
+ }
142
+ )
143
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: "Not enough data to chart." }) })
144
+ ] });
145
+ };
146
+ const TopProductsWidget = ({ statistics }) => {
147
+ const aggregated = {};
148
+ for (const stat of statistics) {
149
+ for (const p of (stat == null ? void 0 : stat.top_products) ?? []) {
150
+ if (!(p == null ? void 0 : p.product_id)) continue;
151
+ const existing = aggregated[p.product_id];
152
+ if (existing) {
153
+ existing.quantity_sold += p.quantity_sold;
154
+ } else {
155
+ aggregated[p.product_id] = { title: p.title, quantity_sold: p.quantity_sold };
156
+ }
157
+ }
158
+ }
159
+ const products = Object.values(aggregated).sort((a, b) => b.quantity_sold - a.quantity_sold).slice(0, 8);
160
+ const chartData = products.map((p) => ({
161
+ name: p.title.length > 20 ? p.title.slice(0, 20) + "..." : p.title,
162
+ sold: p.quantity_sold
163
+ }));
164
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-4 flex flex-col", children: [
165
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-2", children: "Top Products" }),
166
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: chartData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, minHeight: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data: chartData, layout: "vertical", children: [
167
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", tick: { fontSize: 11 }, allowDecimals: false }),
168
+ /* @__PURE__ */ jsxRuntime.jsx(
169
+ recharts.YAxis,
170
+ {
171
+ type: "category",
172
+ dataKey: "name",
173
+ tick: { fontSize: 11 },
174
+ width: 120
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, {}),
178
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "sold", fill: "#8b5cf6", radius: [0, 4, 4, 0] })
179
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: "No data yet." }) })
180
+ ] });
181
+ };
182
+ const STATUS_COLORS = {
183
+ completed: "green",
184
+ pending: "orange",
185
+ canceled: "red",
186
+ archived: "grey",
187
+ requires_action: "blue"
188
+ };
189
+ const RecentOrdersWidget = (_props) => {
190
+ const { data, isLoading } = useRecentOrders(8);
191
+ const orders = (data == null ? void 0 : data.orders) ?? [];
192
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-0 flex flex-col", children: [
193
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Recent Orders" }) }),
194
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
195
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "px-4 text-ui-fg-muted", children: "Loading..." }),
196
+ !isLoading && orders.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "px-4 text-ui-fg-muted", children: "No orders yet." }),
197
+ orders.map((order) => {
198
+ var _a;
199
+ return /* @__PURE__ */ jsxRuntime.jsxs(
200
+ reactRouterDom.Link,
201
+ {
202
+ to: `/orders/${order.id}`,
203
+ className: "flex items-center justify-between px-4 py-2 border-b border-ui-border-base last:border-b-0 hover:bg-ui-bg-base-hover transition-colors no-underline",
204
+ children: [
205
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
206
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", weight: "plus", children: [
207
+ "#",
208
+ order.display_id
209
+ ] }),
210
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: ((_a = order.customer) == null ? void 0 : _a.first_name) ? `${order.customer.first_name} ${order.customer.last_name ?? ""}` : order.email })
211
+ ] }),
212
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
213
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "font-mono", children: [
214
+ "$",
215
+ (order.total ?? 0).toFixed(2)
216
+ ] }),
217
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: STATUS_COLORS[order.status] ?? "grey", children: order.status })
218
+ ] })
219
+ ]
220
+ },
221
+ order.id
222
+ );
223
+ })
224
+ ] })
225
+ ] });
226
+ };
227
+ const COLORS = ["#6366f1", "#a5b4fc"];
228
+ const CustomerCountWidget = ({ totals }) => {
229
+ const newCount = totals.new_customer_count ?? 0;
230
+ const returningCount = totals.returning_customer_count ?? 0;
231
+ const total = newCount + returningCount;
232
+ const chartData = [
233
+ { name: "New", value: newCount },
234
+ { name: "Returning", value: returningCount }
235
+ ];
236
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-4 flex flex-col", children: [
237
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Customers" }),
238
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", className: "text-ui-fg-base mt-1", children: total }),
239
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0 mt-1", children: total > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", minWidth: 0, minHeight: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { children: [
240
+ /* @__PURE__ */ jsxRuntime.jsx(
241
+ recharts.Pie,
242
+ {
243
+ data: chartData,
244
+ dataKey: "value",
245
+ innerRadius: "50%",
246
+ outerRadius: "80%",
247
+ paddingAngle: 2,
248
+ children: chartData.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: COLORS[i] }, i))
249
+ }
250
+ ),
251
+ /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, {})
252
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: "No data yet." }) }),
253
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 text-xs text-ui-fg-subtle", children: [
254
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
255
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-2 h-2 rounded-full mr-1", style: { backgroundColor: COLORS[0] } }),
256
+ "New: ",
257
+ newCount
258
+ ] }),
259
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
260
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block w-2 h-2 rounded-full mr-1", style: { backgroundColor: COLORS[1] } }),
261
+ "Returning: ",
262
+ returningCount
263
+ ] })
264
+ ] })
265
+ ] });
266
+ };
267
+ const FulfillmentWidget = ({ totals }) => {
268
+ const count = totals.pending_fulfillment_count ?? 0;
269
+ const color = count === 0 ? "green" : count <= 5 ? "orange" : "red";
270
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-4 flex flex-col", children: [
271
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Pending Fulfillment" }),
272
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center", children: [
273
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", className: "text-ui-fg-base text-4xl", children: count }),
274
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "small", color, className: "mt-2", children: count === 0 ? "All fulfilled" : `${count} awaiting` })
275
+ ] })
276
+ ] });
277
+ };
278
+ const LowStockWidget = (_props) => {
279
+ const { data, isLoading } = useLowStock(50);
280
+ const warnings = (data == null ? void 0 : data.warnings) ?? [];
281
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "h-full p-0 flex flex-col", children: [
282
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 pt-4 pb-2 flex items-center justify-between", children: [
283
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Inventory Warnings" }),
284
+ warnings.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: "red", children: warnings.length })
285
+ ] }),
286
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
287
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "px-4 text-ui-fg-muted", children: "Loading..." }),
288
+ !isLoading && warnings.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "px-4 text-ui-fg-muted", children: "All inventory levels OK." }),
289
+ warnings.map((w, i) => /* @__PURE__ */ jsxRuntime.jsxs(
290
+ reactRouterDom.Link,
291
+ {
292
+ to: `/inventory/${w.inventory_item_id}`,
293
+ className: "flex items-start gap-2 px-4 py-2 border-b border-ui-border-base last:border-b-0 hover:bg-ui-bg-base-hover transition-colors no-underline",
294
+ children: [
295
+ w.reason === "no_lots" ? /* @__PURE__ */ jsxRuntime.jsx(icons.XCircle, { className: "text-ui-fg-error mt-0.5 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(icons.ExclamationCircle, { className: "text-ui-tag-orange-icon mt-0.5 shrink-0" }),
296
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
297
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", className: "truncate", children: w.title || w.sku }),
298
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: [
299
+ w.location_name,
300
+ w.reason === "no_lots" ? " — No stock lots" : ` — ${w.available_quantity} available`
301
+ ] })
302
+ ] })
303
+ ]
304
+ },
305
+ `${w.inventory_item_id}-${w.location_id}`
306
+ ))
307
+ ] })
308
+ ] });
309
+ };
310
+ const WIDGETS = [
311
+ {
312
+ id: "revenue",
313
+ title: "Revenue",
314
+ layouts: {
315
+ lg: { x: 0, y: 0, w: 4, h: 3, minW: 3, minH: 2 },
316
+ md: { x: 0, y: 0, w: 4, h: 3, minW: 3, minH: 2 },
317
+ sm: { x: 0, y: 0, w: 4, h: 3, minW: 2, minH: 2 }
318
+ },
319
+ component: RevenueWidget
320
+ },
321
+ {
322
+ id: "orders",
323
+ title: "Orders",
324
+ layouts: {
325
+ lg: { x: 4, y: 0, w: 4, h: 3, minW: 3, minH: 2 },
326
+ md: { x: 4, y: 0, w: 4, h: 3, minW: 3, minH: 2 },
327
+ sm: { x: 0, y: 3, w: 4, h: 3, minW: 2, minH: 2 }
328
+ },
329
+ component: OrdersCountWidget
330
+ },
331
+ {
332
+ id: "aov",
333
+ title: "Avg Order Value",
334
+ layouts: {
335
+ lg: { x: 8, y: 0, w: 4, h: 3, minW: 2, minH: 2 },
336
+ md: { x: 0, y: 3, w: 4, h: 3, minW: 2, minH: 2 },
337
+ sm: { x: 0, y: 6, w: 4, h: 3, minW: 2, minH: 2 }
338
+ },
339
+ component: AovWidget
340
+ },
341
+ {
342
+ id: "top-products",
343
+ title: "Top Products",
344
+ layouts: {
345
+ lg: { x: 0, y: 3, w: 4, h: 4, minW: 3, minH: 3 },
346
+ md: { x: 4, y: 3, w: 4, h: 3, minW: 3, minH: 3 },
347
+ sm: { x: 0, y: 9, w: 4, h: 4, minW: 2, minH: 3 }
348
+ },
349
+ component: TopProductsWidget
350
+ },
351
+ {
352
+ id: "recent-orders",
353
+ title: "Recent Orders",
354
+ layouts: {
355
+ lg: { x: 4, y: 3, w: 8, h: 4, minW: 4, minH: 3 },
356
+ md: { x: 0, y: 6, w: 8, h: 4, minW: 4, minH: 3 },
357
+ sm: { x: 0, y: 13, w: 4, h: 4, minW: 2, minH: 3 }
358
+ },
359
+ component: RecentOrdersWidget
360
+ },
361
+ {
362
+ id: "customers",
363
+ title: "Customers",
364
+ layouts: {
365
+ lg: { x: 0, y: 7, w: 3, h: 3, minW: 2, minH: 2 },
366
+ md: { x: 0, y: 10, w: 4, h: 3, minW: 2, minH: 2 },
367
+ sm: { x: 0, y: 17, w: 4, h: 3, minW: 2, minH: 2 }
368
+ },
369
+ component: CustomerCountWidget
370
+ },
371
+ {
372
+ id: "fulfillment",
373
+ title: "Pending Fulfillment",
374
+ layouts: {
375
+ lg: { x: 3, y: 7, w: 3, h: 3, minW: 2, minH: 2 },
376
+ md: { x: 4, y: 10, w: 4, h: 3, minW: 2, minH: 2 },
377
+ sm: { x: 0, y: 20, w: 4, h: 3, minW: 2, minH: 2 }
378
+ },
379
+ component: FulfillmentWidget
380
+ },
381
+ {
382
+ id: "low-stock",
383
+ title: "Low Stock",
384
+ layouts: {
385
+ lg: { x: 6, y: 7, w: 6, h: 3, minW: 4, minH: 2 },
386
+ md: { x: 0, y: 13, w: 8, h: 3, minW: 4, minH: 2 },
387
+ sm: { x: 0, y: 23, w: 4, h: 3, minW: 2, minH: 2 }
388
+ },
389
+ component: LowStockWidget
390
+ }
391
+ ];
392
+ const baseUrl = "";
393
+ let rpcId = 1;
394
+ let mcpSessionId = null;
395
+ async function mcpRequest(method, params) {
396
+ const headers = {
397
+ "Content-Type": "application/json",
398
+ Accept: "application/json, text/event-stream"
399
+ };
400
+ if (mcpSessionId) {
401
+ headers["mcp-session-id"] = mcpSessionId;
402
+ }
403
+ const res = await fetch(`${baseUrl}/admin/mcp`, {
404
+ method: "POST",
405
+ credentials: "include",
406
+ headers,
407
+ body: JSON.stringify({
408
+ jsonrpc: "2.0",
409
+ id: rpcId++,
410
+ method,
411
+ params
412
+ })
413
+ });
414
+ const newSessionId = res.headers.get("mcp-session-id");
415
+ if (newSessionId) {
416
+ mcpSessionId = newSessionId;
417
+ }
418
+ if (!res.ok) {
419
+ const text = await res.text();
420
+ throw new Error(`MCP error ${res.status}: ${text}`);
421
+ }
422
+ return res.json();
423
+ }
424
+ async function mcpInitialize() {
425
+ mcpSessionId = null;
426
+ return mcpRequest("initialize", {
427
+ protocolVersion: "2025-03-26",
428
+ capabilities: {},
429
+ clientInfo: { name: "medusa-admin-chat", version: "1.0.0" }
430
+ });
431
+ }
432
+ async function mcpListTools() {
433
+ var _a;
434
+ const res = await mcpRequest("tools/list", {});
435
+ return ((_a = res == null ? void 0 : res.result) == null ? void 0 : _a.tools) ?? (res == null ? void 0 : res.tools) ?? [];
436
+ }
437
+ async function mcpCallTool(name, args) {
438
+ var _a;
439
+ const res = await mcpRequest("tools/call", { name, arguments: args });
440
+ const content = ((_a = res == null ? void 0 : res.result) == null ? void 0 : _a.content) ?? (res == null ? void 0 : res.content) ?? [];
441
+ return content.map((c) => c.text ?? JSON.stringify(c)).join("\n");
442
+ }
443
+ const McpQuery = () => {
444
+ var _a, _b;
445
+ const [open, setOpen] = react.useState(false);
446
+ const [tools, setTools] = react.useState([]);
447
+ const [selectedToolName, setSelectedToolName] = react.useState("");
448
+ const [paramValues, setParamValues] = react.useState({});
449
+ const [messages, setMessages] = react.useState([]);
450
+ const [loading, setLoading] = react.useState(false);
451
+ const [initialized, setInitialized] = react.useState(false);
452
+ const messagesEndRef = react.useRef(null);
453
+ const selectedTool = tools.find((t) => t.name === selectedToolName) ?? null;
454
+ react.useEffect(() => {
455
+ var _a2;
456
+ (_a2 = messagesEndRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
457
+ }, [messages]);
458
+ const handleOpen = async (isOpen) => {
459
+ setOpen(isOpen);
460
+ if (isOpen && !initialized) {
461
+ try {
462
+ await mcpInitialize();
463
+ const toolList = await mcpListTools();
464
+ setTools(toolList);
465
+ setInitialized(true);
466
+ } catch (err) {
467
+ console.error("[MCP] Connection error:", err);
468
+ setMessages([{ role: "assistant", content: "Failed to connect to MCP server." }]);
469
+ }
470
+ }
471
+ };
472
+ const handleSelectTool = (value) => {
473
+ setSelectedToolName(value);
474
+ setParamValues({});
475
+ };
476
+ const handleExecute = async () => {
477
+ var _a2;
478
+ if (!selectedTool) return;
479
+ setLoading(true);
480
+ const args = {};
481
+ const props = ((_a2 = selectedTool.inputSchema) == null ? void 0 : _a2.properties) ?? {};
482
+ for (const [key, schema] of Object.entries(props)) {
483
+ const raw = paramValues[key];
484
+ if (raw === void 0 || raw === "") continue;
485
+ if (schema.type === "number" || schema.type === "integer") {
486
+ args[key] = Number(raw);
487
+ } else if (schema.type === "array") {
488
+ try {
489
+ args[key] = JSON.parse(raw);
490
+ } catch {
491
+ args[key] = raw.split(",").map((s) => s.trim());
492
+ }
493
+ } else if (schema.type === "object") {
494
+ try {
495
+ args[key] = JSON.parse(raw);
496
+ } catch {
497
+ args[key] = raw;
498
+ }
499
+ } else {
500
+ args[key] = raw;
501
+ }
502
+ }
503
+ setMessages((prev) => [
504
+ ...prev,
505
+ { role: "user", content: `${selectedTool.name}(${JSON.stringify(args)})` }
506
+ ]);
507
+ try {
508
+ const result = await mcpCallTool(selectedTool.name, args);
509
+ setMessages((prev) => [...prev, { role: "assistant", content: result }]);
510
+ } catch (err) {
511
+ setMessages((prev) => [
512
+ ...prev,
513
+ { role: "assistant", content: `Error: ${err.message ?? "Unknown error"}` }
514
+ ]);
515
+ }
516
+ setLoading(false);
517
+ setSelectedToolName("");
518
+ setParamValues({});
519
+ };
520
+ const allParams = selectedTool ? [
521
+ ...Object.entries(((_a = selectedTool.inputSchema) == null ? void 0 : _a.properties) ?? {}).filter(
522
+ ([key]) => {
523
+ var _a2;
524
+ return (((_a2 = selectedTool.inputSchema) == null ? void 0 : _a2.required) ?? []).includes(key);
525
+ }
526
+ ),
527
+ ...Object.entries(((_b = selectedTool.inputSchema) == null ? void 0 : _b.properties) ?? {}).filter(
528
+ ([key]) => {
529
+ var _a2;
530
+ return !(((_a2 = selectedTool.inputSchema) == null ? void 0 : _a2.required) ?? []).includes(key);
531
+ }
532
+ )
533
+ ] : [];
534
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer, { open, onOpenChange: handleOpen, children: [
535
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", children: [
536
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ChatBubbleLeftRight, { className: "mr-1" }),
537
+ "Query Data"
538
+ ] }) }),
539
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
540
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Header, { children: [
541
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Query Data" }),
542
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Description, { children: "Run queries against your store data using MCP tools." })
543
+ ] }),
544
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Body, { className: "flex flex-col gap-4 overflow-hidden", children: [
545
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
546
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: selectedToolName, onValueChange: handleSelectTool, children: [
547
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select a tool..." }) }),
548
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: tools.map((tool) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: tool.name, children: tool.name }, tool.name)) })
549
+ ] }),
550
+ selectedTool && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
551
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: selectedTool.description }),
552
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 items-end", children: [
553
+ allParams.map(([key, schema]) => {
554
+ var _a2;
555
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
556
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: [
557
+ key,
558
+ (((_a2 = selectedTool.inputSchema) == null ? void 0 : _a2.required) ?? []).includes(key) ? " *" : ""
559
+ ] }),
560
+ /* @__PURE__ */ jsxRuntime.jsx(
561
+ ui.Input,
562
+ {
563
+ size: "small",
564
+ placeholder: schema.description ?? key,
565
+ value: paramValues[key] ?? "",
566
+ onChange: (e) => setParamValues((prev) => ({ ...prev, [key]: e.target.value }))
567
+ }
568
+ )
569
+ ] }, key);
570
+ }),
571
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", onClick: handleExecute, disabled: loading, children: "Run" })
572
+ ] })
573
+ ] })
574
+ ] }),
575
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto flex flex-col gap-2 min-h-0", children: [
576
+ messages.map(
577
+ (msg, i) => msg.role === "user" ? /* @__PURE__ */ jsxRuntime.jsx(
578
+ "div",
579
+ {
580
+ className: "max-w-[90%] self-end rounded-lg px-3 py-2 text-xs bg-ui-bg-subtle text-ui-fg-base",
581
+ children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "whitespace-pre-wrap font-mono break-all", children: msg.content })
582
+ },
583
+ i
584
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "self-start w-full", children: /* @__PURE__ */ jsxRuntime.jsx(
585
+ ui.CodeBlock,
586
+ {
587
+ snippets: [
588
+ {
589
+ label: "Result",
590
+ language: "json",
591
+ code: msg.content,
592
+ hideLineNumbers: true
593
+ }
594
+ ],
595
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.CodeBlock.Body, { className: "text-xs [&_code]:text-xs" })
596
+ }
597
+ ) }, i)
598
+ ),
599
+ loading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Running..." }),
600
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
601
+ ] })
602
+ ] })
603
+ ] })
604
+ ] });
605
+ };
606
+ const config = adminSdk.defineRouteConfig({
607
+ label: "Statistics",
608
+ icon: icons.ChartBar,
609
+ rank: 0
610
+ });
611
+ const handle = { breadcrumb: () => "Statistics" };
612
+ const PERIOD_LABELS = {
613
+ today: "Today",
614
+ week: "This Week",
615
+ month: "This Month"
616
+ };
617
+ function buildDefaultLayouts() {
618
+ return {
619
+ lg: WIDGETS.map((w) => ({ i: w.id, ...w.layouts.lg })),
620
+ md: WIDGETS.map((w) => ({ i: w.id, ...w.layouts.md })),
621
+ sm: WIDGETS.map((w) => ({ i: w.id, ...w.layouts.sm }))
622
+ };
623
+ }
624
+ function mergeLayouts(saved, widgets) {
625
+ const defaults = buildDefaultLayouts();
626
+ if (!saved || saved.length === 0) return defaults;
627
+ const savedMap = new Map(saved.map((s) => [s.widget_id, s]));
628
+ return {
629
+ ...defaults,
630
+ lg: widgets.map((w) => {
631
+ const s = savedMap.get(w.id);
632
+ if (s) return { i: w.id, ...w.layouts.lg, x: s.x, y: s.y, w: s.w, h: s.h };
633
+ return { i: w.id, ...w.layouts.lg };
634
+ })
635
+ };
636
+ }
637
+ const StatisticsPage = () => {
638
+ const [period, setPeriod] = react.useState("week");
639
+ const [editMode, setEditMode] = react.useState(false);
640
+ const [layouts, setLayouts] = react.useState(buildDefaultLayouts);
641
+ const { width: gridWidth, containerRef: gridRef, mounted: gridMounted } = reactGridLayout.useContainerWidth({
642
+ measureBeforeMount: true
643
+ });
644
+ const { data: statsData, isLoading } = useStatistics(period);
645
+ const { data: layoutData } = useStatisticsLayout();
646
+ const { mutate: saveLayout } = useSaveStatisticsLayout();
647
+ const { mutate: recalculate, isPending: recalculating } = useRecalculateStatistics();
648
+ react.useEffect(() => {
649
+ if (layoutData == null ? void 0 : layoutData.layout) {
650
+ setLayouts(mergeLayouts(layoutData.layout, WIDGETS));
651
+ }
652
+ }, [layoutData]);
653
+ const handleLayoutChange = react.useCallback(
654
+ (_layout, allLayouts) => {
655
+ if (!editMode) return;
656
+ setLayouts((prev) => ({
657
+ ...prev,
658
+ ...Object.fromEntries(
659
+ Object.entries(allLayouts).map(([bp, l]) => [bp, [...l]])
660
+ )
661
+ }));
662
+ },
663
+ [editMode]
664
+ );
665
+ const handleSaveLayout = () => {
666
+ const payload = layouts.lg.map((l) => ({
667
+ widget_id: l.i,
668
+ x: l.x,
669
+ y: l.y,
670
+ w: l.w,
671
+ h: l.h,
672
+ visible: true
673
+ }));
674
+ saveLayout(payload, {
675
+ onSuccess: () => {
676
+ ui.toast.success("Layout saved");
677
+ setEditMode(false);
678
+ },
679
+ onError: () => ui.toast.error("Failed to save layout")
680
+ });
681
+ };
682
+ const widgetMap = react.useMemo(
683
+ () => new Map(WIDGETS.map((w) => [w.id, w])),
684
+ []
685
+ );
686
+ const statistics = (statsData == null ? void 0 : statsData.statistics) ?? [];
687
+ const totals = (statsData == null ? void 0 : statsData.totals) ?? {
688
+ revenue_total: 0,
689
+ order_count: 0,
690
+ average_order_value: 0,
691
+ new_customer_count: 0,
692
+ returning_customer_count: 0,
693
+ pending_fulfillment_count: 0,
694
+ low_stock_count: 0
695
+ };
696
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-4", children: [
697
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-6 py-4 md:flex-row md:items-center md:justify-between", children: [
698
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Statistics" }),
699
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
700
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex rounded-lg border border-ui-border-base overflow-hidden", children: Object.keys(PERIOD_LABELS).map((p) => /* @__PURE__ */ jsxRuntime.jsx(
701
+ "button",
702
+ {
703
+ onClick: () => setPeriod(p),
704
+ className: `px-3 py-1.5 text-xs font-medium transition-colors ${period === p ? "bg-ui-bg-base-pressed text-ui-fg-base" : "text-ui-fg-subtle hover:bg-ui-bg-base-hover"}`,
705
+ children: PERIOD_LABELS[p]
706
+ },
707
+ p
708
+ )) }),
709
+ /* @__PURE__ */ jsxRuntime.jsx(
710
+ ui.Button,
711
+ {
712
+ size: "small",
713
+ variant: "secondary",
714
+ onClick: () => recalculate(void 0, {
715
+ onSuccess: () => ui.toast.success("Statistics recalculated"),
716
+ onError: () => ui.toast.error("Recalculation failed")
717
+ }),
718
+ isLoading: recalculating,
719
+ children: "Recalculate"
720
+ }
721
+ ),
722
+ editMode ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
723
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setEditMode(false), children: "Cancel" }),
724
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", onClick: handleSaveLayout, children: "Save Layout" })
725
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setEditMode(true), children: "Customize" }),
726
+ /* @__PURE__ */ jsxRuntime.jsx(McpQuery, {})
727
+ ] })
728
+ ] }) }),
729
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: gridRef, className: editMode ? "ring-2 ring-ui-border-interactive ring-offset-2 rounded-lg" : "", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading statistics..." }) }) : gridMounted && /* @__PURE__ */ jsxRuntime.jsx(
730
+ reactGridLayout.ResponsiveGridLayout,
731
+ {
732
+ width: gridWidth,
733
+ layouts,
734
+ breakpoints: { lg: 1200, md: 768, sm: 480 },
735
+ cols: { lg: 12, md: 8, sm: 4 },
736
+ rowHeight: 80,
737
+ dragConfig: { enabled: editMode, handle: ".drag-handle" },
738
+ resizeConfig: { enabled: editMode },
739
+ onLayoutChange: handleLayoutChange,
740
+ containerPadding: [0, 0],
741
+ compactor: reactGridLayout.verticalCompactor,
742
+ children: layouts.lg.map((l) => {
743
+ const widget = widgetMap.get(l.i);
744
+ if (!widget) return null;
745
+ const Component = widget.component;
746
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full", children: [
747
+ editMode && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "drag-handle absolute top-0 left-0 right-0 h-6 bg-ui-bg-subtle-hover cursor-move rounded-t-lg flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Drag to move" }) }),
748
+ /* @__PURE__ */ jsxRuntime.jsx(Component, { statistics, totals, period })
749
+ ] }, l.i);
750
+ })
751
+ }
752
+ ) })
753
+ ] });
754
+ };
755
+ const widgetModule = { widgets: [] };
756
+ const routeModule = {
757
+ routes: [
758
+ {
759
+ Component: StatisticsPage,
760
+ path: "/statistics",
761
+ handle
762
+ }
763
+ ]
764
+ };
765
+ const menuItemModule = {
766
+ menuItems: [
767
+ {
768
+ label: config.label,
769
+ icon: config.icon,
770
+ path: "/statistics",
771
+ nested: void 0,
772
+ rank: 0,
773
+ translationNs: void 0
774
+ }
775
+ ]
776
+ };
777
+ const formModule = { customFields: {} };
778
+ const displayModule = {
779
+ displays: {}
780
+ };
781
+ const i18nModule = { resources: {} };
782
+ const plugin = {
783
+ widgetModule,
784
+ routeModule,
785
+ menuItemModule,
786
+ formModule,
787
+ displayModule,
788
+ i18nModule
789
+ };
790
+ module.exports = plugin;