@alphabite/medusa-wishlist 0.6.0 → 0.7.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.
@@ -28,8 +28,233 @@ const ProductWidget = ({ data: product }) => {
28
28
  adminSdk.defineWidgetConfig({
29
29
  zone: "product.details.before"
30
30
  });
31
+ const RANGE_PRESETS = {
32
+ "7": 7,
33
+ "30": 30,
34
+ "90": 90
35
+ };
36
+ const KpiCard = ({
37
+ label,
38
+ value,
39
+ delta
40
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 rounded-lg border p-4", children: [
41
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: label }),
42
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
43
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xlarge", weight: "plus", children: value }),
44
+ delta !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(
45
+ ui.Badge,
46
+ {
47
+ size: "2xsmall",
48
+ color: delta > 0 ? "green" : delta < 0 ? "red" : "grey",
49
+ children: [
50
+ delta > 0 ? "+" : "",
51
+ delta,
52
+ "%"
53
+ ]
54
+ }
55
+ )
56
+ ] })
57
+ ] });
58
+ const WishlistAnalyticsTab = () => {
59
+ var _a;
60
+ const [rangeDays, setRangeDays] = react.useState("30");
61
+ const [channelId, setChannelId] = react.useState("all");
62
+ const { data: channelsRes } = reactQuery.useQuery({
63
+ queryKey: ["wishlist", "analytics", "channels"],
64
+ queryFn: () => sdk.admin.salesChannel.list({ limit: 100 })
65
+ });
66
+ const range = react.useMemo(() => {
67
+ const to = /* @__PURE__ */ new Date();
68
+ const from = new Date(
69
+ to.getTime() - RANGE_PRESETS[rangeDays] * 24 * 60 * 60 * 1e3
70
+ );
71
+ return { from: from.toISOString(), to: to.toISOString() };
72
+ }, [rangeDays]);
73
+ const { data, isLoading, isError } = reactQuery.useQuery({
74
+ queryKey: ["wishlist", "analytics", range, channelId],
75
+ queryFn: () => {
76
+ const params = new URLSearchParams({ from: range.from, to: range.to });
77
+ if (channelId !== "all") params.set("sales_channel_id", channelId);
78
+ return sdk.client.fetch(
79
+ `/admin/wishlists/analytics?${params.toString()}`,
80
+ { method: "GET" }
81
+ );
82
+ }
83
+ });
84
+ const maxTrend = react.useMemo(
85
+ () => Math.max(
86
+ 1,
87
+ ...(data == null ? void 0 : data.trend.map((t) => Math.max(t.wishlists, t.items))) ?? [1]
88
+ ),
89
+ [data]
90
+ );
91
+ if (isError) {
92
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: "Failed to load analytics." }) });
93
+ }
94
+ if (isLoading || !data) {
95
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Loading analytics…" }) });
96
+ }
97
+ const hasData = data.kpis.total_wishlists.value > 0 || data.trend.length > 0 || data.top_products.length > 0;
98
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-6 px-6 py-6", children: [
99
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center", children: [
100
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full sm:w-48", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: rangeDays, onValueChange: setRangeDays, children: [
101
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {}) }),
102
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
103
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "7", children: "Last 7 days" }),
104
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "30", children: "Last 30 days" }),
105
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "90", children: "Last 90 days" })
106
+ ] })
107
+ ] }) }),
108
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full sm:w-64", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: channelId, onValueChange: setChannelId, children: [
109
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "All sales channels" }) }),
110
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
111
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "all", children: "All sales channels" }),
112
+ (_a = channelsRes == null ? void 0 : channelsRes.sales_channels) == null ? void 0 : _a.map((c) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: c.id, children: c.name }, c.id))
113
+ ] })
114
+ ] }) })
115
+ ] }),
116
+ !hasData ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "No wishlist activity in this period yet." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
117
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx(
119
+ KpiCard,
120
+ {
121
+ label: "Total wishlists",
122
+ value: String(data.kpis.total_wishlists.value),
123
+ delta: data.kpis.total_wishlists.delta_pct
124
+ }
125
+ ),
126
+ /* @__PURE__ */ jsxRuntime.jsx(
127
+ KpiCard,
128
+ {
129
+ label: "Saved items",
130
+ value: String(data.kpis.total_items.value),
131
+ delta: data.kpis.total_items.delta_pct
132
+ }
133
+ ),
134
+ /* @__PURE__ */ jsxRuntime.jsx(
135
+ KpiCard,
136
+ {
137
+ label: "Avg items / wishlist",
138
+ value: String(data.kpis.avg_items_per_wishlist.value),
139
+ delta: data.kpis.avg_items_per_wishlist.delta_pct
140
+ }
141
+ ),
142
+ /* @__PURE__ */ jsxRuntime.jsx(
143
+ KpiCard,
144
+ {
145
+ label: "Active vs empty",
146
+ value: `${data.kpis.active_wishlists} / ${data.kpis.empty_wishlists}`
147
+ }
148
+ ),
149
+ /* @__PURE__ */ jsxRuntime.jsx(
150
+ KpiCard,
151
+ {
152
+ label: "Guest vs registered",
153
+ value: `${data.kpis.guest_wishlists} / ${data.kpis.registered_wishlists}`
154
+ }
155
+ ),
156
+ /* @__PURE__ */ jsxRuntime.jsx(
157
+ KpiCard,
158
+ {
159
+ label: "Unique customers",
160
+ value: String(data.kpis.unique_customers.value),
161
+ delta: data.kpis.unique_customers.delta_pct
162
+ }
163
+ )
164
+ ] }),
165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 rounded-lg border p-4", children: [
166
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Activity over time" }),
167
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-40 items-end gap-1 overflow-x-auto", children: data.trend.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
168
+ "div",
169
+ {
170
+ className: "flex min-w-[10px] flex-1 flex-col items-center justify-end gap-0.5",
171
+ title: `${t.date}: ${t.wishlists} wishlists, ${t.items} items`,
172
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-32 w-full items-end justify-center gap-0.5", children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx(
174
+ "div",
175
+ {
176
+ className: "w-1/2 rounded-t bg-ui-fg-interactive",
177
+ style: {
178
+ height: `${t.wishlists / maxTrend * 100}%`,
179
+ minHeight: t.wishlists > 0 ? "2px" : void 0
180
+ }
181
+ }
182
+ ),
183
+ /* @__PURE__ */ jsxRuntime.jsx(
184
+ "div",
185
+ {
186
+ className: "w-1/2 rounded-t bg-ui-fg-muted",
187
+ style: {
188
+ height: `${t.items / maxTrend * 100}%`,
189
+ minHeight: t.items > 0 ? "2px" : void 0
190
+ }
191
+ }
192
+ )
193
+ ] })
194
+ },
195
+ t.date
196
+ )) }),
197
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
198
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: "▮ Wishlists" }),
199
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "▮ Items" })
200
+ ] })
201
+ ] }),
202
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
203
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Most-wishlisted products (top 10)" }),
204
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
205
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
206
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
207
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Wishlists" }),
208
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Items" })
209
+ ] }) }),
210
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: data.top_products.map((p) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
211
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
212
+ p.thumbnail ? /* @__PURE__ */ jsxRuntime.jsx(
213
+ "img",
214
+ {
215
+ src: p.thumbnail,
216
+ alt: "",
217
+ className: "h-8 w-8 rounded object-cover"
218
+ }
219
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 rounded bg-ui-bg-subtle" }),
220
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: p.title })
221
+ ] }) }),
222
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "text-right", children: p.wishlist_count }),
223
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "text-right", children: p.item_count })
224
+ ] }, p.product_id)) })
225
+ ] })
226
+ ] }),
227
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
228
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Most-wishlisted variants (top 10)" }),
229
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
230
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
231
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Variant" }),
232
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Wishlists" })
233
+ ] }) }),
234
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: data.top_variants.map((v) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
235
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: v.title }) }),
236
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "text-right", children: v.wishlist_count })
237
+ ] }, v.product_variant_id)) })
238
+ ] })
239
+ ] }),
240
+ data.by_sales_channel.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
241
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Wishlists by sales channel" }),
242
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
243
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
244
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Channel" }),
245
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { className: "text-right", children: "Wishlists" })
246
+ ] }) }),
247
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: data.by_sales_channel.map((c) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
248
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: c.name }),
249
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "text-right", children: c.wishlist_count })
250
+ ] }, c.sales_channel_id)) })
251
+ ] })
252
+ ] })
253
+ ] })
254
+ ] });
255
+ };
31
256
  const QUERY_KEY = ["wishlist", "settings"];
32
- const WishlistSettingsPage = () => {
257
+ const WishlistSettingsTab = () => {
33
258
  const queryClient = reactQuery.useQueryClient();
34
259
  const { data, isLoading } = reactQuery.useQuery({
35
260
  queryKey: QUERY_KEY,
@@ -71,12 +296,9 @@ const WishlistSettingsPage = () => {
71
296
  }
72
297
  update.mutate(patch);
73
298
  };
74
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
75
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
76
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Wishlists" }),
77
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Control who can use wishlists in your storefront." })
78
- ] }) }),
79
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-6 px-6 py-6", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Loading…" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
299
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-6 px-6 py-6", children: [
300
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Control who can use wishlists in your storefront." }) }),
301
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Loading…" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
80
302
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
81
303
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
82
304
  /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "allow-guest", className: "font-medium", children: "Allow guest wishlists" }),
@@ -115,9 +337,22 @@ const WishlistSettingsPage = () => {
115
337
  children: "Save"
116
338
  }
117
339
  ) })
118
- ] }) })
340
+ ] })
119
341
  ] });
120
342
  };
343
+ const WishlistPage = () => {
344
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "analytics", children: [
345
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 border-b px-6 py-4", children: [
346
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Wishlists" }),
347
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
348
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "analytics", children: "Analytics" }),
349
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Settings" })
350
+ ] })
351
+ ] }),
352
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "analytics", children: /* @__PURE__ */ jsxRuntime.jsx(WishlistAnalyticsTab, {}) }),
353
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", children: /* @__PURE__ */ jsxRuntime.jsx(WishlistSettingsTab, {}) })
354
+ ] }) });
355
+ };
121
356
  const config = adminSdk.defineRouteConfig({
122
357
  label: "Wishlists",
123
358
  icon: icons.Heart
@@ -131,8 +366,8 @@ const widgetModule = { widgets: [
131
366
  const routeModule = {
132
367
  routes: [
133
368
  {
134
- Component: WishlistSettingsPage,
135
- path: "/settings/wishlists"
369
+ Component: WishlistPage,
370
+ path: "/wishlists"
136
371
  }
137
372
  ]
138
373
  };
@@ -141,7 +376,7 @@ const menuItemModule = {
141
376
  {
142
377
  label: config.label,
143
378
  icon: config.icon,
144
- path: "/settings/wishlists",
379
+ path: "/wishlists",
145
380
  nested: void 0,
146
381
  rank: void 0,
147
382
  translationNs: void 0
@@ -1,10 +1,10 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { Container, Heading, Text, toast, Label, Switch, Button } from "@medusajs/ui";
3
+ import { Container, Heading, Text, Select, Table, Badge, toast, Label, Switch, Button, Tabs } from "@medusajs/ui";
4
4
  import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
5
5
  import Medusa from "@medusajs/js-sdk";
6
6
  import { Heart } from "@medusajs/icons";
7
- import { useState, useEffect } from "react";
7
+ import { useState, useMemo, useEffect } from "react";
8
8
  const sdk = new Medusa({
9
9
  baseUrl: __BACKEND_URL__ || "http://localhost:9000",
10
10
  debug: process.env.NODE_ENV === "development",
@@ -25,8 +25,233 @@ const ProductWidget = ({ data: product }) => {
25
25
  defineWidgetConfig({
26
26
  zone: "product.details.before"
27
27
  });
28
+ const RANGE_PRESETS = {
29
+ "7": 7,
30
+ "30": 30,
31
+ "90": 90
32
+ };
33
+ const KpiCard = ({
34
+ label,
35
+ value,
36
+ delta
37
+ }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 rounded-lg border p-4", children: [
38
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: label }),
39
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
40
+ /* @__PURE__ */ jsx(Text, { size: "xlarge", weight: "plus", children: value }),
41
+ delta !== void 0 && /* @__PURE__ */ jsxs(
42
+ Badge,
43
+ {
44
+ size: "2xsmall",
45
+ color: delta > 0 ? "green" : delta < 0 ? "red" : "grey",
46
+ children: [
47
+ delta > 0 ? "+" : "",
48
+ delta,
49
+ "%"
50
+ ]
51
+ }
52
+ )
53
+ ] })
54
+ ] });
55
+ const WishlistAnalyticsTab = () => {
56
+ var _a;
57
+ const [rangeDays, setRangeDays] = useState("30");
58
+ const [channelId, setChannelId] = useState("all");
59
+ const { data: channelsRes } = useQuery({
60
+ queryKey: ["wishlist", "analytics", "channels"],
61
+ queryFn: () => sdk.admin.salesChannel.list({ limit: 100 })
62
+ });
63
+ const range = useMemo(() => {
64
+ const to = /* @__PURE__ */ new Date();
65
+ const from = new Date(
66
+ to.getTime() - RANGE_PRESETS[rangeDays] * 24 * 60 * 60 * 1e3
67
+ );
68
+ return { from: from.toISOString(), to: to.toISOString() };
69
+ }, [rangeDays]);
70
+ const { data, isLoading, isError } = useQuery({
71
+ queryKey: ["wishlist", "analytics", range, channelId],
72
+ queryFn: () => {
73
+ const params = new URLSearchParams({ from: range.from, to: range.to });
74
+ if (channelId !== "all") params.set("sales_channel_id", channelId);
75
+ return sdk.client.fetch(
76
+ `/admin/wishlists/analytics?${params.toString()}`,
77
+ { method: "GET" }
78
+ );
79
+ }
80
+ });
81
+ const maxTrend = useMemo(
82
+ () => Math.max(
83
+ 1,
84
+ ...(data == null ? void 0 : data.trend.map((t) => Math.max(t.wishlists, t.items))) ?? [1]
85
+ ),
86
+ [data]
87
+ );
88
+ if (isError) {
89
+ return /* @__PURE__ */ jsx("div", { className: "px-6 py-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", children: "Failed to load analytics." }) });
90
+ }
91
+ if (isLoading || !data) {
92
+ return /* @__PURE__ */ jsx("div", { className: "px-6 py-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Loading analytics…" }) });
93
+ }
94
+ const hasData = data.kpis.total_wishlists.value > 0 || data.trend.length > 0 || data.top_products.length > 0;
95
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 px-6 py-6", children: [
96
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center", children: [
97
+ /* @__PURE__ */ jsx("div", { className: "w-full sm:w-48", children: /* @__PURE__ */ jsxs(Select, { value: rangeDays, onValueChange: setRangeDays, children: [
98
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, {}) }),
99
+ /* @__PURE__ */ jsxs(Select.Content, { children: [
100
+ /* @__PURE__ */ jsx(Select.Item, { value: "7", children: "Last 7 days" }),
101
+ /* @__PURE__ */ jsx(Select.Item, { value: "30", children: "Last 30 days" }),
102
+ /* @__PURE__ */ jsx(Select.Item, { value: "90", children: "Last 90 days" })
103
+ ] })
104
+ ] }) }),
105
+ /* @__PURE__ */ jsx("div", { className: "w-full sm:w-64", children: /* @__PURE__ */ jsxs(Select, { value: channelId, onValueChange: setChannelId, children: [
106
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "All sales channels" }) }),
107
+ /* @__PURE__ */ jsxs(Select.Content, { children: [
108
+ /* @__PURE__ */ jsx(Select.Item, { value: "all", children: "All sales channels" }),
109
+ (_a = channelsRes == null ? void 0 : channelsRes.sales_channels) == null ? void 0 : _a.map((c) => /* @__PURE__ */ jsx(Select.Item, { value: c.id, children: c.name }, c.id))
110
+ ] })
111
+ ] }) })
112
+ ] }),
113
+ !hasData ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "No wishlist activity in this period yet." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
114
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3", children: [
115
+ /* @__PURE__ */ jsx(
116
+ KpiCard,
117
+ {
118
+ label: "Total wishlists",
119
+ value: String(data.kpis.total_wishlists.value),
120
+ delta: data.kpis.total_wishlists.delta_pct
121
+ }
122
+ ),
123
+ /* @__PURE__ */ jsx(
124
+ KpiCard,
125
+ {
126
+ label: "Saved items",
127
+ value: String(data.kpis.total_items.value),
128
+ delta: data.kpis.total_items.delta_pct
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsx(
132
+ KpiCard,
133
+ {
134
+ label: "Avg items / wishlist",
135
+ value: String(data.kpis.avg_items_per_wishlist.value),
136
+ delta: data.kpis.avg_items_per_wishlist.delta_pct
137
+ }
138
+ ),
139
+ /* @__PURE__ */ jsx(
140
+ KpiCard,
141
+ {
142
+ label: "Active vs empty",
143
+ value: `${data.kpis.active_wishlists} / ${data.kpis.empty_wishlists}`
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsx(
147
+ KpiCard,
148
+ {
149
+ label: "Guest vs registered",
150
+ value: `${data.kpis.guest_wishlists} / ${data.kpis.registered_wishlists}`
151
+ }
152
+ ),
153
+ /* @__PURE__ */ jsx(
154
+ KpiCard,
155
+ {
156
+ label: "Unique customers",
157
+ value: String(data.kpis.unique_customers.value),
158
+ delta: data.kpis.unique_customers.delta_pct
159
+ }
160
+ )
161
+ ] }),
162
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-lg border p-4", children: [
163
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Activity over time" }),
164
+ /* @__PURE__ */ jsx("div", { className: "flex h-40 items-end gap-1 overflow-x-auto", children: data.trend.map((t) => /* @__PURE__ */ jsx(
165
+ "div",
166
+ {
167
+ className: "flex min-w-[10px] flex-1 flex-col items-center justify-end gap-0.5",
168
+ title: `${t.date}: ${t.wishlists} wishlists, ${t.items} items`,
169
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-32 w-full items-end justify-center gap-0.5", children: [
170
+ /* @__PURE__ */ jsx(
171
+ "div",
172
+ {
173
+ className: "w-1/2 rounded-t bg-ui-fg-interactive",
174
+ style: {
175
+ height: `${t.wishlists / maxTrend * 100}%`,
176
+ minHeight: t.wishlists > 0 ? "2px" : void 0
177
+ }
178
+ }
179
+ ),
180
+ /* @__PURE__ */ jsx(
181
+ "div",
182
+ {
183
+ className: "w-1/2 rounded-t bg-ui-fg-muted",
184
+ style: {
185
+ height: `${t.items / maxTrend * 100}%`,
186
+ minHeight: t.items > 0 ? "2px" : void 0
187
+ }
188
+ }
189
+ )
190
+ ] })
191
+ },
192
+ t.date
193
+ )) }),
194
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
195
+ /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-subtle", children: "▮ Wishlists" }),
196
+ /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-muted", children: "▮ Items" })
197
+ ] })
198
+ ] }),
199
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
200
+ /* @__PURE__ */ jsx(Heading, { level: "h3", children: "Most-wishlisted products (top 10)" }),
201
+ /* @__PURE__ */ jsxs(Table, { children: [
202
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
203
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Product" }),
204
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Wishlists" }),
205
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Items" })
206
+ ] }) }),
207
+ /* @__PURE__ */ jsx(Table.Body, { children: data.top_products.map((p) => /* @__PURE__ */ jsxs(Table.Row, { children: [
208
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
209
+ p.thumbnail ? /* @__PURE__ */ jsx(
210
+ "img",
211
+ {
212
+ src: p.thumbnail,
213
+ alt: "",
214
+ className: "h-8 w-8 rounded object-cover"
215
+ }
216
+ ) : /* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded bg-ui-bg-subtle" }),
217
+ /* @__PURE__ */ jsx(Text, { size: "small", children: p.title })
218
+ ] }) }),
219
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: p.wishlist_count }),
220
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: p.item_count })
221
+ ] }, p.product_id)) })
222
+ ] })
223
+ ] }),
224
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
225
+ /* @__PURE__ */ jsx(Heading, { level: "h3", children: "Most-wishlisted variants (top 10)" }),
226
+ /* @__PURE__ */ jsxs(Table, { children: [
227
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
228
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Variant" }),
229
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Wishlists" })
230
+ ] }) }),
231
+ /* @__PURE__ */ jsx(Table.Body, { children: data.top_variants.map((v) => /* @__PURE__ */ jsxs(Table.Row, { children: [
232
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Text, { size: "small", children: v.title }) }),
233
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: v.wishlist_count })
234
+ ] }, v.product_variant_id)) })
235
+ ] })
236
+ ] }),
237
+ data.by_sales_channel.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
238
+ /* @__PURE__ */ jsx(Heading, { level: "h3", children: "Wishlists by sales channel" }),
239
+ /* @__PURE__ */ jsxs(Table, { children: [
240
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
241
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Channel" }),
242
+ /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Wishlists" })
243
+ ] }) }),
244
+ /* @__PURE__ */ jsx(Table.Body, { children: data.by_sales_channel.map((c) => /* @__PURE__ */ jsxs(Table.Row, { children: [
245
+ /* @__PURE__ */ jsx(Table.Cell, { children: c.name }),
246
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: c.wishlist_count })
247
+ ] }, c.sales_channel_id)) })
248
+ ] })
249
+ ] })
250
+ ] })
251
+ ] });
252
+ };
28
253
  const QUERY_KEY = ["wishlist", "settings"];
29
- const WishlistSettingsPage = () => {
254
+ const WishlistSettingsTab = () => {
30
255
  const queryClient = useQueryClient();
31
256
  const { data, isLoading } = useQuery({
32
257
  queryKey: QUERY_KEY,
@@ -68,12 +293,9 @@ const WishlistSettingsPage = () => {
68
293
  }
69
294
  update.mutate(patch);
70
295
  };
71
- return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
72
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsxs("div", { children: [
73
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Wishlists" }),
74
- /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Control who can use wishlists in your storefront." })
75
- ] }) }),
76
- /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-6 px-6 py-6", children: isLoading ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Loading…" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
296
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 px-6 py-6", children: [
297
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Control who can use wishlists in your storefront." }) }),
298
+ isLoading ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Loading…" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
77
299
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
78
300
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
79
301
  /* @__PURE__ */ jsx(Label, { htmlFor: "allow-guest", className: "font-medium", children: "Allow guest wishlists" }),
@@ -112,9 +334,22 @@ const WishlistSettingsPage = () => {
112
334
  children: "Save"
113
335
  }
114
336
  ) })
115
- ] }) })
337
+ ] })
116
338
  ] });
117
339
  };
340
+ const WishlistPage = () => {
341
+ return /* @__PURE__ */ jsx(Container, { className: "p-0", children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "analytics", children: [
342
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 border-b px-6 py-4", children: [
343
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Wishlists" }),
344
+ /* @__PURE__ */ jsxs(Tabs.List, { children: [
345
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "analytics", children: "Analytics" }),
346
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "Settings" })
347
+ ] })
348
+ ] }),
349
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "analytics", children: /* @__PURE__ */ jsx(WishlistAnalyticsTab, {}) }),
350
+ /* @__PURE__ */ jsx(Tabs.Content, { value: "settings", children: /* @__PURE__ */ jsx(WishlistSettingsTab, {}) })
351
+ ] }) });
352
+ };
118
353
  const config = defineRouteConfig({
119
354
  label: "Wishlists",
120
355
  icon: Heart
@@ -128,8 +363,8 @@ const widgetModule = { widgets: [
128
363
  const routeModule = {
129
364
  routes: [
130
365
  {
131
- Component: WishlistSettingsPage,
132
- path: "/settings/wishlists"
366
+ Component: WishlistPage,
367
+ path: "/wishlists"
133
368
  }
134
369
  ]
135
370
  };
@@ -138,7 +373,7 @@ const menuItemModule = {
138
373
  {
139
374
  label: config.label,
140
375
  icon: config.icon,
141
- path: "/settings/wishlists",
376
+ path: "/wishlists",
142
377
  nested: void 0,
143
378
  rank: void 0,
144
379
  translationNs: void 0
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GET = GET;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const wishlist_1 = require("../../../../modules/wishlist");
6
+ const validators_1 = require("./validators");
7
+ async function GET(req, res) {
8
+ const logger = req.scope.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
9
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
10
+ const service = req.scope.resolve(wishlist_1.WISHLIST_MODULE);
11
+ const parsed = validators_1.WishlistAnalyticsQuerySchema.safeParse(req.query);
12
+ if (!parsed.success) {
13
+ return res.status(400).json({
14
+ error: "Invalid analytics query",
15
+ issues: parsed.error.flatten(),
16
+ });
17
+ }
18
+ try {
19
+ const view = await service.getAnalytics(parsed.data);
20
+ const productIds = view.top_products.map((p) => p.product_id);
21
+ const variantIds = view.top_variants.map((v) => v.product_variant_id);
22
+ const channelIds = view.by_sales_channel.map((c) => c.sales_channel_id);
23
+ const [products, variants, channels] = await Promise.all([
24
+ productIds.length
25
+ ? query.graph({
26
+ entity: "product",
27
+ fields: ["id", "title", "thumbnail"],
28
+ filters: { id: productIds },
29
+ })
30
+ : Promise.resolve({ data: [] }),
31
+ variantIds.length
32
+ ? query.graph({
33
+ entity: "product_variant",
34
+ fields: ["id", "title"],
35
+ filters: { id: variantIds },
36
+ })
37
+ : Promise.resolve({ data: [] }),
38
+ channelIds.length
39
+ ? query.graph({
40
+ entity: "sales_channel",
41
+ fields: ["id", "name"],
42
+ filters: { id: channelIds },
43
+ })
44
+ : Promise.resolve({ data: [] }),
45
+ ]);
46
+ const productRows = products.data;
47
+ const variantRows = variants.data;
48
+ const channelRows = channels.data;
49
+ const productMap = new Map(productRows.map((p) => [p.id, p]));
50
+ const variantMap = new Map(variantRows.map((v) => [v.id, v]));
51
+ const channelMap = new Map(channelRows.map((c) => [c.id, c]));
52
+ const response = {
53
+ ...view,
54
+ top_products: view.top_products.map((p) => {
55
+ const product = productMap.get(p.product_id);
56
+ return {
57
+ ...p,
58
+ title: product?.title ?? p.product_id,
59
+ thumbnail: product?.thumbnail ?? null,
60
+ };
61
+ }),
62
+ top_variants: view.top_variants.map((v) => ({
63
+ ...v,
64
+ title: variantMap.get(v.product_variant_id)?.title ?? v.product_variant_id,
65
+ })),
66
+ by_sales_channel: view.by_sales_channel.map((c) => ({
67
+ ...c,
68
+ name: channelMap.get(c.sales_channel_id)?.name ?? c.sales_channel_id,
69
+ })),
70
+ };
71
+ return res.status(200).json(response);
72
+ }
73
+ catch (error) {
74
+ logger.error("[admin/wishlists/analytics] GET error:", error);
75
+ return res.status(500).json({
76
+ error: "Failed to load wishlist analytics",
77
+ details: error instanceof Error ? error.message : "Unknown error",
78
+ });
79
+ }
80
+ }
81
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3dpc2hsaXN0cy9hbmFseXRpY3Mvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFXQSxrQkFpRkM7QUEzRkQscURBQXNFO0FBQ3RFLDJEQUErRDtBQUUvRCw2Q0FBNEQ7QUFPckQsS0FBSyxVQUFVLEdBQUcsQ0FBQyxHQUFrQixFQUFFLEdBQW1CO0lBQy9ELE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ25FLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2pFLE1BQU0sT0FBTyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUF3QiwwQkFBZSxDQUFDLENBQUM7SUFFMUUsTUFBTSxNQUFNLEdBQUcseUNBQTRCLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNqRSxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3BCLE9BQU8sR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7WUFDMUIsS0FBSyxFQUFFLHlCQUF5QjtZQUNoQyxNQUFNLEVBQUUsTUFBTSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUU7U0FDL0IsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELElBQUksQ0FBQztRQUNILE1BQU0sSUFBSSxHQUFHLE1BQU0sT0FBTyxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFckQsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxV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
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3dpc2hsaXN0cy9hbmFseXRpY3MvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WishlistAnalyticsQuerySchema = void 0;
4
+ const zod_1 = require("@medusajs/framework/zod");
5
+ exports.WishlistAnalyticsQuerySchema = zod_1.z.object({
6
+ from: zod_1.z.string().datetime().optional(),
7
+ to: zod_1.z.string().datetime().optional(),
8
+ sales_channel_id: zod_1.z.string().optional(),
9
+ });
10
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uL3NyYy9hcGkvYWRtaW4vd2lzaGxpc3RzL2FuYWx5dGljcy92YWxpZGF0b3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGlEQUE0QztBQUUvQixRQUFBLDRCQUE0QixHQUFHLE9BQUMsQ0FBQyxNQUFNLENBQUM7SUFDbkQsSUFBSSxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDdEMsRUFBRSxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDcEMsZ0JBQWdCLEVBQUUsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtDQUN4QyxDQUFDLENBQUMifQ==
@@ -53,6 +53,186 @@ class WishlistModuleService extends (0, utils_1.MedusaService)({
53
53
  .where("wi.product_id = ?", [productId])
54
54
  .execute())?.length || 0);
55
55
  }
56
+ async getAnalytics(params = {}, context = {}) {
57
+ const knex = context.manager.getConnection().getKnex();
58
+ const scopeItemsToChannel = (qb) => {
59
+ if (channelId) {
60
+ qb.join("wishlist as w", "w.id", "wi.wishlist_id").where("w.sales_channel_id", channelId);
61
+ }
62
+ return qb;
63
+ };
64
+ const n = (v) => Number(v ?? 0);
65
+ const DAY_MS = 24 * 60 * 60 * 1000;
66
+ const to = params.to ? new Date(params.to) : new Date();
67
+ const from = params.from
68
+ ? new Date(params.from)
69
+ : new Date(to.getTime() - 30 * DAY_MS);
70
+ const periodMs = to.getTime() - from.getTime();
71
+ const prevFrom = new Date(from.getTime() - periodMs);
72
+ const prevTo = from;
73
+ const channelId = params.sales_channel_id;
74
+ const bucket = periodMs <= 60 * DAY_MS ? "day" : "week";
75
+ const mkDelta = (value, previous) => ({
76
+ value,
77
+ previous,
78
+ delta_pct: previous === 0
79
+ ? value > 0
80
+ ? 100
81
+ : 0
82
+ : Math.round(((value - previous) / previous) * 1000) / 10,
83
+ });
84
+ const countWishlists = async (lo, hi) => {
85
+ const qb = knex("wishlist")
86
+ .where("created_at", ">=", lo)
87
+ .andWhere("created_at", "<", hi);
88
+ if (channelId)
89
+ qb.where("sales_channel_id", channelId);
90
+ const row = await qb.count("* as c");
91
+ return n(row[0]?.c);
92
+ };
93
+ const countItems = async (lo, hi) => {
94
+ const qb = scopeItemsToChannel(knex("wishlist_item as wi")
95
+ .where("wi.created_at", ">=", lo)
96
+ .andWhere("wi.created_at", "<", hi));
97
+ const row = await qb.count("* as c");
98
+ return n(row[0]?.c);
99
+ };
100
+ const countUniqueCustomers = async (lo, hi) => {
101
+ const qb = knex("wishlist")
102
+ .where("created_at", ">=", lo)
103
+ .andWhere("created_at", "<", hi)
104
+ .whereNotNull("customer_id");
105
+ if (channelId)
106
+ qb.where("sales_channel_id", channelId);
107
+ const row = await qb.countDistinct("customer_id as c");
108
+ return n(row[0]?.c);
109
+ };
110
+ const [totalWishlists, prevTotalWishlists, totalItems, prevTotalItems, uniqueCustomers, prevUniqueCustomers,] = await Promise.all([
111
+ countWishlists(from, to),
112
+ countWishlists(prevFrom, prevTo),
113
+ countItems(from, to),
114
+ countItems(prevFrom, prevTo),
115
+ countUniqueCustomers(from, to),
116
+ countUniqueCustomers(prevFrom, prevTo),
117
+ ]);
118
+ const activeQb = knex("wishlist as w")
119
+ .where("w.created_at", ">=", from)
120
+ .andWhere("w.created_at", "<", to)
121
+ .whereExists(function () {
122
+ this.select(knex.raw("1"))
123
+ .from("wishlist_item as wi")
124
+ .where("wi.wishlist_id", knex.ref("w.id"));
125
+ });
126
+ if (channelId)
127
+ activeQb.where("w.sales_channel_id", channelId);
128
+ const activeWishlists = n((await activeQb.count("* as c"))[0]?.c);
129
+ const emptyWishlists = totalWishlists - activeWishlists;
130
+ const guestQb = knex("wishlist")
131
+ .where("created_at", ">=", from)
132
+ .andWhere("created_at", "<", to)
133
+ .whereNull("customer_id");
134
+ if (channelId)
135
+ guestQb.where("sales_channel_id", channelId);
136
+ const guestWishlists = n((await guestQb.count("* as c"))[0]?.c);
137
+ const registeredWishlists = totalWishlists - guestWishlists;
138
+ const wishlistTrendQb = knex("wishlist")
139
+ .where("created_at", ">=", from)
140
+ .andWhere("created_at", "<", to)
141
+ .select(knex.raw("to_char(date_trunc(?, created_at), 'YYYY-MM-DD') as date", [
142
+ bucket,
143
+ ]))
144
+ .count("* as count")
145
+ // Group/order by the SELECT ordinal: repeating date_trunc(?, ...) here
146
+ // would bind a *separate* parameter, which Postgres does not treat as the
147
+ // same expression as the one in SELECT ("must appear in GROUP BY").
148
+ .groupByRaw("1")
149
+ .orderByRaw("1");
150
+ if (channelId)
151
+ wishlistTrendQb.where("sales_channel_id", channelId);
152
+ const itemTrendQb = scopeItemsToChannel(knex("wishlist_item as wi")
153
+ .where("wi.created_at", ">=", from)
154
+ .andWhere("wi.created_at", "<", to)
155
+ .select(knex.raw("to_char(date_trunc(?, wi.created_at), 'YYYY-MM-DD') as date", [bucket]))
156
+ .count("* as count")
157
+ .groupByRaw("1")
158
+ .orderByRaw("1"));
159
+ const [wishlistTrendRows, itemTrendRows] = (await Promise.all([
160
+ wishlistTrendQb,
161
+ itemTrendQb,
162
+ ]));
163
+ const trendMap = new Map();
164
+ for (const r of wishlistTrendRows) {
165
+ trendMap.set(r.date, { wishlists: n(r.count), items: 0 });
166
+ }
167
+ for (const r of itemTrendRows) {
168
+ const e = trendMap.get(r.date) ?? { wishlists: 0, items: 0 };
169
+ e.items = n(r.count);
170
+ trendMap.set(r.date, e);
171
+ }
172
+ const trend = [...trendMap.entries()]
173
+ .sort(([a], [b]) => a.localeCompare(b))
174
+ .map(([date, v]) => ({ date, wishlists: v.wishlists, items: v.items }));
175
+ const topProductsQb = scopeItemsToChannel(knex("wishlist_item as wi")
176
+ .where("wi.created_at", ">=", from)
177
+ .andWhere("wi.created_at", "<", to));
178
+ const topProductsRows = (await topProductsQb
179
+ .select("wi.product_id")
180
+ .countDistinct("wi.wishlist_id as wishlist_count")
181
+ .count("* as item_count")
182
+ .groupBy("wi.product_id")
183
+ .orderBy("wishlist_count", "desc")
184
+ .limit(10));
185
+ const topVariantsQb = scopeItemsToChannel(knex("wishlist_item as wi")
186
+ .where("wi.created_at", ">=", from)
187
+ .andWhere("wi.created_at", "<", to));
188
+ const topVariantsRows = (await topVariantsQb
189
+ .select("wi.product_variant_id", "wi.product_id")
190
+ .countDistinct("wi.wishlist_id as wishlist_count")
191
+ .groupBy("wi.product_variant_id", "wi.product_id")
192
+ .orderBy("wishlist_count", "desc")
193
+ .limit(10));
194
+ const bySalesChannel = channelId
195
+ ? []
196
+ : (await knex("wishlist")
197
+ .where("created_at", ">=", from)
198
+ .andWhere("created_at", "<", to)
199
+ .select("sales_channel_id")
200
+ .count("* as wishlist_count")
201
+ .groupBy("sales_channel_id")
202
+ .orderBy("wishlist_count", "desc")).map((r) => ({
203
+ sales_channel_id: r.sales_channel_id,
204
+ wishlist_count: n(r.wishlist_count),
205
+ }));
206
+ const avgCurrent = totalWishlists ? totalItems / totalWishlists : 0;
207
+ const avgPrev = prevTotalWishlists
208
+ ? prevTotalItems / prevTotalWishlists
209
+ : 0;
210
+ return {
211
+ range: { from: from.toISOString(), to: to.toISOString() },
212
+ kpis: {
213
+ total_wishlists: mkDelta(totalWishlists, prevTotalWishlists),
214
+ total_items: mkDelta(totalItems, prevTotalItems),
215
+ avg_items_per_wishlist: mkDelta(Math.round(avgCurrent * 100) / 100, Math.round(avgPrev * 100) / 100),
216
+ active_wishlists: activeWishlists,
217
+ empty_wishlists: emptyWishlists,
218
+ guest_wishlists: guestWishlists,
219
+ registered_wishlists: registeredWishlists,
220
+ unique_customers: mkDelta(uniqueCustomers, prevUniqueCustomers),
221
+ },
222
+ trend,
223
+ top_products: topProductsRows.map((r) => ({
224
+ product_id: r.product_id,
225
+ wishlist_count: n(r.wishlist_count),
226
+ item_count: n(r.item_count),
227
+ })),
228
+ top_variants: topVariantsRows.map((r) => ({
229
+ product_variant_id: r.product_variant_id,
230
+ product_id: r.product_id,
231
+ wishlist_count: n(r.wishlist_count),
232
+ })),
233
+ by_sales_channel: bySalesChannel,
234
+ };
235
+ }
56
236
  async totalItemsCount({ customer_id, wishlist_id, }, context = {}) {
57
237
  const wishlist_items_count = await context.manager?.count(wishlist_item_1.WishlistItem, {
58
238
  wishlist: {
@@ -133,6 +313,13 @@ __decorate([
133
313
  __metadata("design:paramtypes", [String, Object]),
134
314
  __metadata("design:returntype", Promise)
135
315
  ], WishlistModuleService.prototype, "getWishlistCountsOfProduct", null);
316
+ __decorate([
317
+ (0, utils_2.InjectManager)(),
318
+ __param(1, (0, utils_1.MedusaContext)()),
319
+ __metadata("design:type", Function),
320
+ __metadata("design:paramtypes", [Object, Object]),
321
+ __metadata("design:returntype", Promise)
322
+ ], WishlistModuleService.prototype, "getAnalytics", null);
136
323
  __decorate([
137
324
  (0, utils_2.InjectManager)(),
138
325
  __param(1, (0, utils_1.MedusaContext)()),
@@ -140,4 +327,4 @@ __decorate([
140
327
  __metadata("design:paramtypes", [Object, Object]),
141
328
  __metadata("design:returntype", Promise)
142
329
  ], WishlistModuleService.prototype, "totalItemsCount", null);
143
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL3dpc2hsaXN0L3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxxREFJbUM7QUFDbkMsZ0RBQTZDO0FBQzdDLDBEQUFzRDtBQUN0RCxrRUFBOEQ7QUFDOUQscURBQTBEO0FBRzFELGdFQUErQjtBQUMvQixpREFBNEM7QUFPNUMsTUFBTSw4QkFBOEIsR0FBRyxlQUFlLENBQUM7QUEyQ3ZELE1BQU0sYUFBYSxHQUFHLE9BQUMsQ0FBQyxNQUFNLENBQUM7SUFDN0IsY0FBYyxFQUFFLE9BQUMsQ0FBQyxLQUFLLENBQUMsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQzlDLG1CQUFtQixFQUFFLE9BQUMsQ0FBQyxLQUFLLENBQUMsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQ25ELG9CQUFvQixFQUFFLE9BQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO0lBQ2hELHdCQUF3QixFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQy9DLGtCQUFrQixFQUFFLE9BQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO0lBQzlDLGdCQUFnQixFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUM7Q0FDdkQsQ0FBQyxDQUFDO0FBSUgsTUFBcUIscUJBQXNCLFNBQVEsSUFBQSxxQkFBYSxFQUFDO0lBQy9ELFFBQVEsRUFBUixtQkFBUTtJQUNSLFlBQVksRUFBWiw0QkFBWTtJQUNaLGdCQUFnQixFQUFoQixvQ0FBZ0I7Q0FDakIsQ0FBQztJQUdBLE1BQU0sQ0FBQyxlQUFlLENBQ3BCLFFBQTRDO1FBRTVDLE1BQU0s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
330
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL3dpc2hsaXN0L3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxxREFJbUM7QUFDbkMsZ0RBQTZDO0FBQzdDLDBEQUFzRDtBQUN0RCxrRUFBOEQ7QUFDOUQscURBQTBEO0FBSTFELGdFQUErQjtBQUMvQixpREFBNEM7QUFZNUMsTUFBTSw4QkFBOEIsR0FBRyxlQUFlLENBQUM7QUEyQ3ZELE1BQU0sYUFBYSxHQUFHLE9BQUMsQ0FBQyxNQUFNLENBQUM7SUFDN0IsY0FBYyxFQUFFLE9BQUMsQ0FBQyxLQUFLLENBQUMsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQzlDLG1CQUFtQixFQUFFLE9BQUMsQ0FBQyxLQUFLLENBQUMsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsUUFBUSxFQUFFO0lBQ25ELG9CQUFvQixFQUFFLE9BQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO0lBQ2hELHdCQUF3QixFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQy9DLGtCQUFrQixFQUFFLE9BQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO0lBQzlDLGdCQUFnQixFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLENBQUM7Q0FDdkQsQ0FBQyxDQUFDO0FBSUgsTUFBcUIscUJBQXNCLFNBQVEsSUFBQSxxQkFBYSxFQUFDO0lBQy9ELFFBQVEsRUFBUixtQkFBUTtJQUNSLFlBQVksRUFBWiw0QkFBWTtJQUNaLGdCQUFnQixFQUFoQixvQ0FBZ0I7Q0FDakIsQ0FBQztJQUdBLE1BQU0sQ0FBQyxlQUFlLENBQ3BCLFFBQTRDO1FBRTVDLE1BQU0s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
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYW5hbHl0aWNzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvd2lzaGxpc3QvdHlwZXMvYW5hbHl0aWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIifQ==
@@ -3,6 +3,7 @@ import { EntityManager } from "@mikro-orm/knex";
3
3
  import { z } from "@medusajs/framework/zod";
4
4
  import { Wishlist as WishlistType } from "../../api/store/wishlists/types";
5
5
  import type { WishlistSettingsView, WishlistSettingsPatch } from "./types/settings";
6
+ import type { WishlistAnalyticsParams, WishlistAnalyticsView } from "./types/analytics";
6
7
  /**
7
8
  * Options for configuring the Alphabite Wishlist Plugin
8
9
  */
@@ -83,6 +84,7 @@ export default class WishlistModuleService extends WishlistModuleService_base {
83
84
  static validateOptions(_options: AlphabiteWishlistPluginOptionsType): void | never;
84
85
  constructor({}: {}, options: AlphabiteWishlistPluginOptionsType);
85
86
  getWishlistCountsOfProduct(productId: string, context?: Context<EntityManager>): Promise<number>;
87
+ getAnalytics(params?: WishlistAnalyticsParams, context?: Context<EntityManager>): Promise<WishlistAnalyticsView>;
86
88
  totalItemsCount({ customer_id, wishlist_id, }: {
87
89
  customer_id?: string;
88
90
  wishlist_id?: string;
@@ -0,0 +1,49 @@
1
+ export type WishlistAnalyticsParams = {
2
+ /** ISO date string; defaults to 30 days before `to`. */
3
+ from?: string;
4
+ /** ISO date string; defaults to now. */
5
+ to?: string;
6
+ /** Restrict all metrics to a single sales channel. */
7
+ sales_channel_id?: string;
8
+ };
9
+ export type Delta = {
10
+ value: number;
11
+ previous: number;
12
+ /** Percent change vs the previous equal-length period, rounded to 1 decimal. */
13
+ delta_pct: number;
14
+ };
15
+ export type WishlistAnalyticsView = {
16
+ range: {
17
+ from: string;
18
+ to: string;
19
+ };
20
+ kpis: {
21
+ total_wishlists: Delta;
22
+ total_items: Delta;
23
+ avg_items_per_wishlist: Delta;
24
+ active_wishlists: number;
25
+ empty_wishlists: number;
26
+ guest_wishlists: number;
27
+ registered_wishlists: number;
28
+ unique_customers: Delta;
29
+ };
30
+ trend: Array<{
31
+ date: string;
32
+ wishlists: number;
33
+ items: number;
34
+ }>;
35
+ top_products: Array<{
36
+ product_id: string;
37
+ wishlist_count: number;
38
+ item_count: number;
39
+ }>;
40
+ top_variants: Array<{
41
+ product_variant_id: string;
42
+ product_id: string;
43
+ wishlist_count: number;
44
+ }>;
45
+ by_sales_channel: Array<{
46
+ sales_channel_id: string;
47
+ wishlist_count: number;
48
+ }>;
49
+ };
package/README.md CHANGED
@@ -47,6 +47,7 @@ npm install @alphabite/medusa-wishlist@0.5.7
47
47
  - [✨ Features](#-features)
48
48
  - [📦 Installation](#-installation)
49
49
  - [🔧 Plugin Options](#-plugin-options)
50
+ - [🛠️ Admin](#-admin)
50
51
  - [📦 API Endpoints](#-api-endpoints)
51
52
  - [🧑‍💻 SDK Usage](#-sdk-usage)
52
53
  - [🧪 Guest Wishlist Flow](#-guest-wishlist-flow)
@@ -63,6 +64,7 @@ npm install @alphabite/medusa-wishlist@0.5.7
63
64
  - ✅ Guest wishlist supported + transfer when registered
64
65
  - ✅ Fully typed Medusa JS SDK integration with our SDK client
65
66
  - ✅ Pagination and filtering built-in
67
+ - ✅ Admin page with **Settings** + **Analytics** tabs
66
68
 
67
69
  ---
68
70
 
@@ -106,6 +108,18 @@ const plugins = [
106
108
 
107
109
  ---
108
110
 
111
+ ## 🛠️ Admin
112
+
113
+ The plugin adds a **Wishlists** page to the admin sidebar (`/app/wishlists`) with
114
+ two tabs: **Settings** (guest / multiple-wishlist toggles) and **Analytics** (a
115
+ read-only dashboard of KPIs, an activity trend, and the most-wishlisted products
116
+ and variants).
117
+
118
+ See [docs/admin-analytics.md](docs/admin-analytics.md) for the analytics endpoint
119
+ and response shape.
120
+
121
+ ---
122
+
109
123
  ## 📦 API Endpoints
110
124
 
111
125
  All endpoints are available under `/store/wishlists`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alphabite/medusa-wishlist",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Alphabite's Medusa Wishlist Plugin",
5
5
  "author": "Alphabite (https://alphabite.io)",
6
6
  "license": "MIT",