medusa-analytics 0.0.7 → 0.0.8
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.
|
@@ -46,13 +46,12 @@ const STATUS_COLORS = {
|
|
|
46
46
|
delivered: "#10B981",
|
|
47
47
|
cancelled: "#EF4444"
|
|
48
48
|
};
|
|
49
|
-
const
|
|
50
|
-
"pending",
|
|
51
|
-
"confirmed",
|
|
52
|
-
"processing",
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"cancelled"
|
|
49
|
+
const ORDER_CHART_STATUSES = [
|
|
50
|
+
{ key: "pending", label: "Pending", color: STATUS_COLORS.pending },
|
|
51
|
+
{ key: "confirmed", label: "Confirmed", color: STATUS_COLORS.confirmed },
|
|
52
|
+
{ key: "processing", label: "Processing", color: STATUS_COLORS.processing },
|
|
53
|
+
{ key: "fulfilled", label: "Fulfilled", color: "#10B981" },
|
|
54
|
+
{ key: "cancelled", label: "Cancelled", color: STATUS_COLORS.cancelled }
|
|
56
55
|
];
|
|
57
56
|
function formatCurrency(value) {
|
|
58
57
|
return new Intl.NumberFormat("en-IN", {
|
|
@@ -134,16 +133,18 @@ function OrdersDashboard() {
|
|
|
134
133
|
if (error || !data) {
|
|
135
134
|
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" }) });
|
|
136
135
|
}
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
136
|
+
const fulfilledCount = (data.statusCounts.shipped ?? 0) + (data.statusCounts.delivered ?? 0);
|
|
137
|
+
const pieData = ORDER_CHART_STATUSES.map(({ key, label, color }) => {
|
|
138
|
+
const value = key === "fulfilled" ? fulfilledCount : data.statusCounts[key] ?? 0;
|
|
139
|
+
return { name: label, value, color };
|
|
140
|
+
}).filter((d) => d.value > 0);
|
|
141
|
+
const returnsExchangesData = [
|
|
142
|
+
{ name: "Return orders", value: data.returnOrders, color: "#EF4444" },
|
|
143
|
+
{ name: "Exchange orders", value: data.exchangeOrders, color: "#F59E0B" }
|
|
144
|
+
].filter((d) => d.value > 0);
|
|
144
145
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
|
|
145
146
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
146
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "
|
|
147
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Orders" }),
|
|
147
148
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
148
149
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "analytics-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
|
|
149
150
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -186,27 +187,69 @@ function OrdersDashboard() {
|
|
|
186
187
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "—" })
|
|
187
188
|
] })
|
|
188
189
|
] }),
|
|
190
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
|
|
191
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
192
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Cancelled orders" }),
|
|
193
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.cancelledOrders.toLocaleString() })
|
|
194
|
+
] }),
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Delivered orders" }),
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.deliveredOrders.toLocaleString() })
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Exchange orders" }),
|
|
201
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.exchangeOrders.toLocaleString() })
|
|
202
|
+
] }),
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
204
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Return orders" }),
|
|
205
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: data.returnOrders.toLocaleString() })
|
|
206
|
+
] })
|
|
207
|
+
] }),
|
|
189
208
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
190
209
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-4", children: "Orders by status" }),
|
|
191
|
-
/* @__PURE__ */ jsxRuntime.
|
|
192
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8", children: [
|
|
211
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { children: [
|
|
212
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
213
|
+
recharts.Pie,
|
|
214
|
+
{
|
|
215
|
+
data: pieData,
|
|
216
|
+
cx: "50%",
|
|
217
|
+
cy: "50%",
|
|
218
|
+
innerRadius: 60,
|
|
219
|
+
outerRadius: 100,
|
|
220
|
+
paddingAngle: 2,
|
|
221
|
+
dataKey: "value",
|
|
222
|
+
nameKey: "name",
|
|
223
|
+
label: ({ name, value }) => `${name}: ${value}`,
|
|
224
|
+
children: pieData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-${index}`))
|
|
225
|
+
}
|
|
226
|
+
),
|
|
227
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (value) => [value ?? 0, "Orders"] }),
|
|
228
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {})
|
|
229
|
+
] }) }) }),
|
|
230
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
231
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle mb-3 text-sm font-medium", children: "Returns & exchanges" }),
|
|
232
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-72", children: returnsExchangesData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.PieChart, { children: [
|
|
233
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
234
|
+
recharts.Pie,
|
|
235
|
+
{
|
|
236
|
+
data: returnsExchangesData,
|
|
237
|
+
cx: "50%",
|
|
238
|
+
cy: "50%",
|
|
239
|
+
innerRadius: 60,
|
|
240
|
+
outerRadius: 100,
|
|
241
|
+
paddingAngle: 2,
|
|
242
|
+
dataKey: "value",
|
|
243
|
+
nameKey: "name",
|
|
244
|
+
label: ({ name, value }) => `${name}: ${value}`,
|
|
245
|
+
children: returnsExchangesData.map((entry, index) => /* @__PURE__ */ jsxRuntime.jsx(recharts.Cell, { fill: entry.color }, `cell-re-${index}`))
|
|
246
|
+
}
|
|
247
|
+
),
|
|
248
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { formatter: (value) => [value ?? 0, "Count"] }),
|
|
249
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {})
|
|
250
|
+
] }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-ui-fg-muted text-sm", children: "No returns or exchanges in this period" }) })
|
|
251
|
+
] })
|
|
252
|
+
] })
|
|
210
253
|
] }),
|
|
211
254
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
212
255
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
@@ -408,7 +451,7 @@ function CustomersDashboard() {
|
|
|
408
451
|
}
|
|
409
452
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
|
|
410
453
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
411
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "
|
|
454
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Customers" }),
|
|
412
455
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
413
456
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
414
457
|
ui.Label,
|
|
@@ -544,7 +587,6 @@ const ANALYTICS_MODULES = [
|
|
|
544
587
|
{ id: "orders", label: "Orders" },
|
|
545
588
|
{ id: "customers", label: "Customers" }
|
|
546
589
|
];
|
|
547
|
-
const PLUGIN_VERSION = "0.2";
|
|
548
590
|
const AnalyticsPage = () => {
|
|
549
591
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
550
592
|
const [sidebarOpen, setSidebarOpen] = react.useState(false);
|
|
@@ -573,11 +615,7 @@ const AnalyticsPage = () => {
|
|
|
573
615
|
children: m.label
|
|
574
616
|
},
|
|
575
617
|
m.id
|
|
576
|
-
)) })
|
|
577
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-xs mt-4", "data-analytics-build": PLUGIN_VERSION, children: [
|
|
578
|
-
"Analytics v",
|
|
579
|
-
PLUGIN_VERSION
|
|
580
|
-
] })
|
|
618
|
+
)) })
|
|
581
619
|
] }),
|
|
582
620
|
!sidebarOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 border-r border-ui-border-base p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
583
621
|
ui.Button,
|
|
@@ -590,6 +628,19 @@ const AnalyticsPage = () => {
|
|
|
590
628
|
}
|
|
591
629
|
) }),
|
|
592
630
|
/* @__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: [
|
|
631
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-6", children: [
|
|
632
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Analytics" }),
|
|
633
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: ANALYTICS_MODULES.map((m) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
634
|
+
ui.Button,
|
|
635
|
+
{
|
|
636
|
+
variant: activeModule === m.id ? "secondary" : "transparent",
|
|
637
|
+
size: "small",
|
|
638
|
+
onClick: () => setActiveModule(m.id),
|
|
639
|
+
children: m.label
|
|
640
|
+
},
|
|
641
|
+
m.id
|
|
642
|
+
)) })
|
|
643
|
+
] }),
|
|
593
644
|
activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
|
|
594
645
|
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {})
|
|
595
646
|
] }) }) })
|
|
@@ -45,13 +45,12 @@ const STATUS_COLORS = {
|
|
|
45
45
|
delivered: "#10B981",
|
|
46
46
|
cancelled: "#EF4444"
|
|
47
47
|
};
|
|
48
|
-
const
|
|
49
|
-
"pending",
|
|
50
|
-
"confirmed",
|
|
51
|
-
"processing",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"cancelled"
|
|
48
|
+
const ORDER_CHART_STATUSES = [
|
|
49
|
+
{ key: "pending", label: "Pending", color: STATUS_COLORS.pending },
|
|
50
|
+
{ key: "confirmed", label: "Confirmed", color: STATUS_COLORS.confirmed },
|
|
51
|
+
{ key: "processing", label: "Processing", color: STATUS_COLORS.processing },
|
|
52
|
+
{ key: "fulfilled", label: "Fulfilled", color: "#10B981" },
|
|
53
|
+
{ key: "cancelled", label: "Cancelled", color: STATUS_COLORS.cancelled }
|
|
55
54
|
];
|
|
56
55
|
function formatCurrency(value) {
|
|
57
56
|
return new Intl.NumberFormat("en-IN", {
|
|
@@ -133,16 +132,18 @@ function OrdersDashboard() {
|
|
|
133
132
|
if (error || !data) {
|
|
134
133
|
return /* @__PURE__ */ jsx(Container, { className: "p-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-danger", children: error ?? "Failed to load analytics" }) });
|
|
135
134
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
const fulfilledCount = (data.statusCounts.shipped ?? 0) + (data.statusCounts.delivered ?? 0);
|
|
136
|
+
const pieData = ORDER_CHART_STATUSES.map(({ key, label, color }) => {
|
|
137
|
+
const value = key === "fulfilled" ? fulfilledCount : data.statusCounts[key] ?? 0;
|
|
138
|
+
return { name: label, value, color };
|
|
139
|
+
}).filter((d) => d.value > 0);
|
|
140
|
+
const returnsExchangesData = [
|
|
141
|
+
{ name: "Return orders", value: data.returnOrders, color: "#EF4444" },
|
|
142
|
+
{ name: "Exchange orders", value: data.exchangeOrders, color: "#F59E0B" }
|
|
143
|
+
].filter((d) => d.value > 0);
|
|
143
144
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
144
145
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
145
|
-
/* @__PURE__ */ jsx(Heading, { level: "
|
|
146
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Orders" }),
|
|
146
147
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
147
148
|
/* @__PURE__ */ jsx(Label, { htmlFor: "analytics-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
|
|
148
149
|
/* @__PURE__ */ jsx(
|
|
@@ -185,27 +186,69 @@ function OrdersDashboard() {
|
|
|
185
186
|
/* @__PURE__ */ jsx(Heading, { level: "h2", children: data.avgFulfillmentTimeHours != null ? `${data.avgFulfillmentTimeHours}h` : "—" })
|
|
186
187
|
] })
|
|
187
188
|
] }),
|
|
189
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [
|
|
190
|
+
/* @__PURE__ */ jsxs(Container, { className: "p-4", children: [
|
|
191
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Cancelled orders" }),
|
|
192
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: data.cancelledOrders.toLocaleString() })
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsxs(Container, { className: "p-4", children: [
|
|
195
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Delivered orders" }),
|
|
196
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: data.deliveredOrders.toLocaleString() })
|
|
197
|
+
] }),
|
|
198
|
+
/* @__PURE__ */ jsxs(Container, { className: "p-4", children: [
|
|
199
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Exchange orders" }),
|
|
200
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: data.exchangeOrders.toLocaleString() })
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ jsxs(Container, { className: "p-4", children: [
|
|
203
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Return orders" }),
|
|
204
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: data.returnOrders.toLocaleString() })
|
|
205
|
+
] })
|
|
206
|
+
] }),
|
|
188
207
|
/* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
|
|
189
208
|
/* @__PURE__ */ jsx(Heading, { level: "h3", className: "mb-4", children: "Orders by status" }),
|
|
190
|
-
/* @__PURE__ */
|
|
191
|
-
/* @__PURE__ */ jsx(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-8", children: [
|
|
210
|
+
/* @__PURE__ */ jsx("div", { className: "h-72", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(PieChart, { children: [
|
|
211
|
+
/* @__PURE__ */ jsx(
|
|
212
|
+
Pie,
|
|
213
|
+
{
|
|
214
|
+
data: pieData,
|
|
215
|
+
cx: "50%",
|
|
216
|
+
cy: "50%",
|
|
217
|
+
innerRadius: 60,
|
|
218
|
+
outerRadius: 100,
|
|
219
|
+
paddingAngle: 2,
|
|
220
|
+
dataKey: "value",
|
|
221
|
+
nameKey: "name",
|
|
222
|
+
label: ({ name, value }) => `${name}: ${value}`,
|
|
223
|
+
children: pieData.map((entry, index) => /* @__PURE__ */ jsx(Cell, { fill: entry.color }, `cell-${index}`))
|
|
224
|
+
}
|
|
225
|
+
),
|
|
226
|
+
/* @__PURE__ */ jsx(Tooltip, { formatter: (value) => [value ?? 0, "Orders"] }),
|
|
227
|
+
/* @__PURE__ */ jsx(Legend, {})
|
|
228
|
+
] }) }) }),
|
|
229
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
230
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle mb-3 text-sm font-medium", children: "Returns & exchanges" }),
|
|
231
|
+
/* @__PURE__ */ jsx("div", { className: "h-72", children: returnsExchangesData.length > 0 ? /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(PieChart, { children: [
|
|
232
|
+
/* @__PURE__ */ jsx(
|
|
233
|
+
Pie,
|
|
234
|
+
{
|
|
235
|
+
data: returnsExchangesData,
|
|
236
|
+
cx: "50%",
|
|
237
|
+
cy: "50%",
|
|
238
|
+
innerRadius: 60,
|
|
239
|
+
outerRadius: 100,
|
|
240
|
+
paddingAngle: 2,
|
|
241
|
+
dataKey: "value",
|
|
242
|
+
nameKey: "name",
|
|
243
|
+
label: ({ name, value }) => `${name}: ${value}`,
|
|
244
|
+
children: returnsExchangesData.map((entry, index) => /* @__PURE__ */ jsx(Cell, { fill: entry.color }, `cell-re-${index}`))
|
|
245
|
+
}
|
|
246
|
+
),
|
|
247
|
+
/* @__PURE__ */ jsx(Tooltip, { formatter: (value) => [value ?? 0, "Count"] }),
|
|
248
|
+
/* @__PURE__ */ jsx(Legend, {})
|
|
249
|
+
] }) }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-ui-fg-muted text-sm", children: "No returns or exchanges in this period" }) })
|
|
250
|
+
] })
|
|
251
|
+
] })
|
|
209
252
|
] }),
|
|
210
253
|
/* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
|
|
211
254
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
@@ -407,7 +450,7 @@ function CustomersDashboard() {
|
|
|
407
450
|
}
|
|
408
451
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-8", children: [
|
|
409
452
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
410
|
-
/* @__PURE__ */ jsx(Heading, { level: "
|
|
453
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Customers" }),
|
|
411
454
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
412
455
|
/* @__PURE__ */ jsx(
|
|
413
456
|
Label,
|
|
@@ -543,7 +586,6 @@ const ANALYTICS_MODULES = [
|
|
|
543
586
|
{ id: "orders", label: "Orders" },
|
|
544
587
|
{ id: "customers", label: "Customers" }
|
|
545
588
|
];
|
|
546
|
-
const PLUGIN_VERSION = "0.2";
|
|
547
589
|
const AnalyticsPage = () => {
|
|
548
590
|
const [activeModule, setActiveModule] = useState("orders");
|
|
549
591
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
@@ -572,11 +614,7 @@ const AnalyticsPage = () => {
|
|
|
572
614
|
children: m.label
|
|
573
615
|
},
|
|
574
616
|
m.id
|
|
575
|
-
)) })
|
|
576
|
-
/* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-muted text-xs mt-4", "data-analytics-build": PLUGIN_VERSION, children: [
|
|
577
|
-
"Analytics v",
|
|
578
|
-
PLUGIN_VERSION
|
|
579
|
-
] })
|
|
617
|
+
)) })
|
|
580
618
|
] }),
|
|
581
619
|
!sidebarOpen && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-r border-ui-border-base p-2", children: /* @__PURE__ */ jsx(
|
|
582
620
|
Button,
|
|
@@ -589,6 +627,19 @@ const AnalyticsPage = () => {
|
|
|
589
627
|
}
|
|
590
628
|
) }),
|
|
591
629
|
/* @__PURE__ */ jsx("main", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsx(Container, { className: "divide-y p-0", children: /* @__PURE__ */ jsxs("div", { className: "py-6 px-6", children: [
|
|
630
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-6", children: [
|
|
631
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "Analytics" }),
|
|
632
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: ANALYTICS_MODULES.map((m) => /* @__PURE__ */ jsx(
|
|
633
|
+
Button,
|
|
634
|
+
{
|
|
635
|
+
variant: activeModule === m.id ? "secondary" : "transparent",
|
|
636
|
+
size: "small",
|
|
637
|
+
onClick: () => setActiveModule(m.id),
|
|
638
|
+
children: m.label
|
|
639
|
+
},
|
|
640
|
+
m.id
|
|
641
|
+
)) })
|
|
642
|
+
] }),
|
|
592
643
|
activeModule === "orders" && /* @__PURE__ */ jsx(OrdersDashboard, {}),
|
|
593
644
|
activeModule === "customers" && /* @__PURE__ */ jsx(CustomersDashboard, {})
|
|
594
645
|
] }) }) })
|
|
@@ -40,6 +40,16 @@ function parseDaysParam(value) {
|
|
|
40
40
|
return MAX_DAYS;
|
|
41
41
|
return n;
|
|
42
42
|
}
|
|
43
|
+
function toValidDate(value) {
|
|
44
|
+
if (value instanceof Date) {
|
|
45
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
48
|
+
const d = new Date(value);
|
|
49
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
43
53
|
async function GET(req, res) {
|
|
44
54
|
const remoteQuery = req.scope.resolve(utils_1.ContainerRegistrationKeys.REMOTE_QUERY);
|
|
45
55
|
const days = parseDaysParam(req.query?.days);
|
|
@@ -53,16 +63,10 @@ async function GET(req, res) {
|
|
|
53
63
|
const rangeEnd = new Date(today);
|
|
54
64
|
rangeEnd.setUTCHours(23, 59, 59, 999);
|
|
55
65
|
try {
|
|
66
|
+
// Query only order fields; avoid fulfillments relation (not always available in remote query)
|
|
56
67
|
const queryObject = (0, utils_1.remoteQueryObjectFromString)({
|
|
57
68
|
entryPoint: "order",
|
|
58
|
-
fields: [
|
|
59
|
-
"id",
|
|
60
|
-
"status",
|
|
61
|
-
"total",
|
|
62
|
-
"created_at",
|
|
63
|
-
"fulfillments.id",
|
|
64
|
-
"fulfillments.shipped_at",
|
|
65
|
-
],
|
|
69
|
+
fields: ["id", "status", "total", "created_at"],
|
|
66
70
|
take: 50000,
|
|
67
71
|
});
|
|
68
72
|
const ordersRaw = await remoteQuery(queryObject);
|
|
@@ -77,10 +81,10 @@ async function GET(req, res) {
|
|
|
77
81
|
let ordersTodayCount = 0;
|
|
78
82
|
let pendingOrders = 0;
|
|
79
83
|
let totalOrders = 0;
|
|
80
|
-
const
|
|
84
|
+
const orderIdsInRange = new Set();
|
|
81
85
|
for (const order of allOrders) {
|
|
82
86
|
const o = order;
|
|
83
|
-
const createdAt =
|
|
87
|
+
const createdAt = toValidDate(o.created_at);
|
|
84
88
|
const dateKey = createdAt ? toDateKey(createdAt) : null;
|
|
85
89
|
const inRange = createdAt &&
|
|
86
90
|
dateKey &&
|
|
@@ -92,6 +96,9 @@ async function GET(req, res) {
|
|
|
92
96
|
const normalized = normalizeStatus(status);
|
|
93
97
|
statusCounts[normalized] = (statusCounts[normalized] ?? 0) + 1;
|
|
94
98
|
totalOrders += 1;
|
|
99
|
+
if (typeof o.id === "string" && o.id) {
|
|
100
|
+
orderIdsInRange.add(o.id);
|
|
101
|
+
}
|
|
95
102
|
// Total revenue and AOV: only include non-cancelled orders
|
|
96
103
|
if (normalized !== "cancelled") {
|
|
97
104
|
const total = typeof o.total === "number" ? o.total : 0;
|
|
@@ -102,20 +109,43 @@ async function GET(req, res) {
|
|
|
102
109
|
ordersTodayCount += 1;
|
|
103
110
|
if (isPendingStatus(status))
|
|
104
111
|
pendingOrders += 1;
|
|
105
|
-
const fulfillments = o.fulfillments ?? [];
|
|
106
|
-
for (const f of fulfillments) {
|
|
107
|
-
const shippedAt = f.shipped_at ? new Date(f.shipped_at) : null;
|
|
108
|
-
if (shippedAt && createdAt) {
|
|
109
|
-
fulfillmentTimesMs.push(shippedAt.getTime() - createdAt.getTime());
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
112
|
}
|
|
114
113
|
const aov = nonCancelledCount > 0 ? totalRevenue / nonCancelledCount : 0;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
// Avg fulfillment time would require fulfillments relation; leave null for now
|
|
115
|
+
const avgFulfillmentTimeHours = null;
|
|
116
|
+
const cancelledOrders = Math.floor(statusCounts.cancelled ?? 0);
|
|
117
|
+
const deliveredOrders = Math.floor(statusCounts.delivered ?? 0);
|
|
118
|
+
let exchangeOrders = 0;
|
|
119
|
+
let returnOrders = 0;
|
|
120
|
+
if (orderIdsInRange.size > 0) {
|
|
121
|
+
const orderModuleService = req.scope.resolve(utils_1.Modules.ORDER);
|
|
122
|
+
const orderIds = Array.from(orderIdsInRange);
|
|
123
|
+
try {
|
|
124
|
+
const [returns] = await orderModuleService.listAndCountReturns({ order_id: orderIds }, { take: 50000, order: { created_at: "DESC" } });
|
|
125
|
+
returnOrders = (returns ?? []).filter((r) => {
|
|
126
|
+
const createdAt = toValidDate(r.created_at);
|
|
127
|
+
return (createdAt !== null &&
|
|
128
|
+
createdAt >= rangeStart &&
|
|
129
|
+
createdAt <= rangeEnd);
|
|
130
|
+
}).length;
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
console.error("Orders summary: returns analytics error:", e);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const changes = await orderModuleService.listOrderChanges({ order_id: orderIds }, { take: 50000, order: { created_at: "DESC" } });
|
|
137
|
+
exchangeOrders = (changes ?? []).filter((c) => {
|
|
138
|
+
const changeType = c.change_type;
|
|
139
|
+
const createdAt = toValidDate(c.created_at);
|
|
140
|
+
return (changeType === "exchange" &&
|
|
141
|
+
createdAt !== null &&
|
|
142
|
+
createdAt >= rangeStart &&
|
|
143
|
+
createdAt <= rangeEnd);
|
|
144
|
+
}).length;
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
console.error("Orders summary: exchange analytics error:", e);
|
|
148
|
+
}
|
|
119
149
|
}
|
|
120
150
|
const body = {
|
|
121
151
|
totalOrders,
|
|
@@ -123,6 +153,10 @@ async function GET(req, res) {
|
|
|
123
153
|
aov,
|
|
124
154
|
ordersToday: ordersTodayCount,
|
|
125
155
|
pendingOrders,
|
|
156
|
+
cancelledOrders,
|
|
157
|
+
deliveredOrders,
|
|
158
|
+
exchangeOrders: Math.floor(exchangeOrders),
|
|
159
|
+
returnOrders: Math.floor(returnOrders),
|
|
126
160
|
avgFulfillmentTimeHours,
|
|
127
161
|
statusCounts,
|
|
128
162
|
};
|
|
@@ -136,4 +170,4 @@ async function GET(req, res) {
|
|
|
136
170
|
});
|
|
137
171
|
}
|
|
138
172
|
}
|
|
139
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
173
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL2FuYWx5dGljcy9vcmRlcnMtc3VtbWFyeS9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQStEQSxrQkFtS0M7QUE1TkQscURBSWtDO0FBSWxDLHlEQUF5RDtBQUN6RCxNQUFNLGNBQWMsR0FBRztJQUNyQixTQUFTO0lBQ1QsV0FBVztJQUNYLFlBQVk7SUFDWixTQUFTO0lBQ1QsV0FBVztJQUNYLFdBQVc7Q0FDSCxDQUFBO0FBRVYsU0FBUyxlQUFlLENBQUMsTUFBMEI7SUFDakQsSUFBSSxDQUFDLE1BQU0sSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRO1FBQUUsT0FBTyxTQUFTLENBQUE7SUFDM0QsTUFBTSxDQUFDLEdBQUcsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFBO0lBQzlCLElBQUksQ0FBQyxLQUFLLFVBQVUsSUFBSSxDQUFDLEtBQUssV0FBVztRQUFFLE9BQU8sV0FBVyxDQUFBO0lBQzdELElBQUksY0FBYyxDQUFDLFFBQVEsQ0FBQyxDQUFvQyxDQUFDO1FBQUUsT0FBTyxDQUFvQyxDQUFBO0lBQzlHLE9BQU8sU0FBUyxDQUFBO0FBQ2xCLENBQUM7QUFFRCxTQUFTLGVBQWUsQ0FBQyxNQUEwQjtJQUNqRCxNQUFNLFVBQVUsR0FBRyxlQUFlLENBQUMsTUFBTSxDQUFDLENBQUE7SUFDMUMsT0FBTyxVQUFVLEtBQUssV0FBVyxJQUFJLFVBQVUsS0FBSyxXQUFXLENBQUE7QUFDakUsQ0FBQztBQUVELE1BQU0sUUFBUSxHQUFHLENBQUMsQ0FBQTtBQUNsQixNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUE7QUFFcEIsU0FBUyxTQUFTLENBQUMsQ0FBTztJQUN4QixPQUFPLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0FBQ3JDLENBQUM7QUFFRCxTQUFTLGNB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
|