@alphabite/medusa-wishlist 0.6.1 → 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,102 +28,233 @@ const ProductWidget = ({ data: product }) => {
28
28
  adminSdk.defineWidgetConfig({
29
29
  zone: "product.details.before"
30
30
  });
31
- const QUERY_KEY$1 = ["wishlist", "settings"];
32
- const WishlistSettingsPage$1 = () => {
33
- const queryClient = reactQuery.useQueryClient();
34
- const { data, isLoading } = reactQuery.useQuery({
35
- queryKey: QUERY_KEY$1,
36
- queryFn: () => sdk.client.fetch("/admin/wishlists/settings", {
37
- method: "GET"
38
- })
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 })
39
65
  });
40
- const [allowGuest, setAllowGuest] = react.useState(false);
41
- const [allowMultiple, setAllowMultiple] = react.useState(false);
42
- react.useEffect(() => {
43
- if (data) {
44
- setAllowGuest(data.allow_guest_wishlist);
45
- setAllowMultiple(data.allow_multiple_wishlists);
46
- }
47
- }, [data]);
48
- const isDirty = data !== void 0 && (allowGuest !== data.allow_guest_wishlist || allowMultiple !== data.allow_multiple_wishlists);
49
- const update = reactQuery.useMutation({
50
- mutationFn: (patch) => sdk.client.fetch("/admin/wishlists/settings", {
51
- method: "PUT",
52
- body: patch
53
- }),
54
- onSuccess: (next) => {
55
- queryClient.setQueryData(QUERY_KEY$1, next);
56
- ui.toast.success("Wishlist settings updated");
57
- },
58
- onError: (err) => {
59
- const msg = err instanceof Error ? err.message : "Failed to update";
60
- ui.toast.error(msg);
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
+ );
61
82
  }
62
83
  });
63
- const onSave = () => {
64
- if (!data) return;
65
- const patch = {};
66
- if (allowGuest !== data.allow_guest_wishlist) {
67
- patch.allow_guest_wishlist = allowGuest;
68
- }
69
- if (allowMultiple !== data.allow_multiple_wishlists) {
70
- patch.allow_multiple_wishlists = allowMultiple;
71
- }
72
- update.mutate(patch);
73
- };
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: [
80
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
81
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
82
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "allow-guest", className: "font-medium", children: "Allow guest wishlists" }),
83
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Anonymous visitors can save items to a wishlist." })
84
- ] }),
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: [
85
118
  /* @__PURE__ */ jsxRuntime.jsx(
86
- ui.Switch,
119
+ KpiCard,
87
120
  {
88
- id: "allow-guest",
89
- checked: allowGuest,
90
- onCheckedChange: setAllowGuest
121
+ label: "Total wishlists",
122
+ value: String(data.kpis.total_wishlists.value),
123
+ delta: data.kpis.total_wishlists.delta_pct
91
124
  }
92
- )
93
- ] }),
94
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
95
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
96
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "allow-multiple", className: "font-medium", children: "Allow multiple wishlists per customer" }),
97
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Signed-in customers can create more than one wishlist." })
98
- ] }),
125
+ ),
99
126
  /* @__PURE__ */ jsxRuntime.jsx(
100
- ui.Switch,
127
+ KpiCard,
101
128
  {
102
- id: "allow-multiple",
103
- checked: allowMultiple,
104
- onCheckedChange: setAllowMultiple
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
105
162
  }
106
163
  )
107
164
  ] }),
108
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
109
- ui.Button,
110
- {
111
- variant: "primary",
112
- onClick: onSave,
113
- disabled: !isDirty || update.isPending,
114
- isLoading: update.isPending,
115
- children: "Save"
116
- }
117
- ) })
118
- ] }) })
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
+ ] })
119
254
  ] });
120
255
  };
121
- const config$1 = adminSdk.defineRouteConfig({
122
- label: "Wishlists",
123
- icon: icons.Heart
124
- });
125
256
  const QUERY_KEY = ["wishlist", "settings"];
126
- const WishlistSettingsPage = () => {
257
+ const WishlistSettingsTab = () => {
127
258
  const queryClient = reactQuery.useQueryClient();
128
259
  const { data, isLoading } = reactQuery.useQuery({
129
260
  queryKey: QUERY_KEY,
@@ -165,12 +296,9 @@ const WishlistSettingsPage = () => {
165
296
  }
166
297
  update.mutate(patch);
167
298
  };
168
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
169
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
170
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Wishlists" }),
171
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Control who can use wishlists in your storefront." })
172
- ] }) }),
173
- /* @__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: [
174
302
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4", children: [
175
303
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
176
304
  /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "allow-guest", className: "font-medium", children: "Allow guest wishlists" }),
@@ -209,9 +337,22 @@ const WishlistSettingsPage = () => {
209
337
  children: "Save"
210
338
  }
211
339
  ) })
212
- ] }) })
340
+ ] })
213
341
  ] });
214
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
+ };
215
356
  const config = adminSdk.defineRouteConfig({
216
357
  label: "Wishlists",
217
358
  icon: icons.Heart
@@ -225,29 +366,17 @@ const widgetModule = { widgets: [
225
366
  const routeModule = {
226
367
  routes: [
227
368
  {
228
- Component: WishlistSettingsPage$1,
369
+ Component: WishlistPage,
229
370
  path: "/wishlists"
230
- },
231
- {
232
- Component: WishlistSettingsPage,
233
- path: "/settings/wishlists"
234
371
  }
235
372
  ]
236
373
  };
237
374
  const menuItemModule = {
238
375
  menuItems: [
239
- {
240
- label: config$1.label,
241
- icon: config$1.icon,
242
- path: "/wishlists",
243
- nested: void 0,
244
- rank: void 0,
245
- translationNs: void 0
246
- },
247
376
  {
248
377
  label: config.label,
249
378
  icon: config.icon,
250
- path: "/settings/wishlists",
379
+ path: "/wishlists",
251
380
  nested: void 0,
252
381
  rank: void 0,
253
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,102 +25,233 @@ const ProductWidget = ({ data: product }) => {
25
25
  defineWidgetConfig({
26
26
  zone: "product.details.before"
27
27
  });
28
- const QUERY_KEY$1 = ["wishlist", "settings"];
29
- const WishlistSettingsPage$1 = () => {
30
- const queryClient = useQueryClient();
31
- const { data, isLoading } = useQuery({
32
- queryKey: QUERY_KEY$1,
33
- queryFn: () => sdk.client.fetch("/admin/wishlists/settings", {
34
- method: "GET"
35
- })
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 })
36
62
  });
37
- const [allowGuest, setAllowGuest] = useState(false);
38
- const [allowMultiple, setAllowMultiple] = useState(false);
39
- useEffect(() => {
40
- if (data) {
41
- setAllowGuest(data.allow_guest_wishlist);
42
- setAllowMultiple(data.allow_multiple_wishlists);
43
- }
44
- }, [data]);
45
- const isDirty = data !== void 0 && (allowGuest !== data.allow_guest_wishlist || allowMultiple !== data.allow_multiple_wishlists);
46
- const update = useMutation({
47
- mutationFn: (patch) => sdk.client.fetch("/admin/wishlists/settings", {
48
- method: "PUT",
49
- body: patch
50
- }),
51
- onSuccess: (next) => {
52
- queryClient.setQueryData(QUERY_KEY$1, next);
53
- toast.success("Wishlist settings updated");
54
- },
55
- onError: (err) => {
56
- const msg = err instanceof Error ? err.message : "Failed to update";
57
- toast.error(msg);
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
+ );
58
79
  }
59
80
  });
60
- const onSave = () => {
61
- if (!data) return;
62
- const patch = {};
63
- if (allowGuest !== data.allow_guest_wishlist) {
64
- patch.allow_guest_wishlist = allowGuest;
65
- }
66
- if (allowMultiple !== data.allow_multiple_wishlists) {
67
- patch.allow_multiple_wishlists = allowMultiple;
68
- }
69
- update.mutate(patch);
70
- };
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: [
77
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
78
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
79
- /* @__PURE__ */ jsx(Label, { htmlFor: "allow-guest", className: "font-medium", children: "Allow guest wishlists" }),
80
- /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Anonymous visitors can save items to a wishlist." })
81
- ] }),
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: [
82
115
  /* @__PURE__ */ jsx(
83
- Switch,
116
+ KpiCard,
84
117
  {
85
- id: "allow-guest",
86
- checked: allowGuest,
87
- onCheckedChange: setAllowGuest
118
+ label: "Total wishlists",
119
+ value: String(data.kpis.total_wishlists.value),
120
+ delta: data.kpis.total_wishlists.delta_pct
88
121
  }
89
- )
90
- ] }),
91
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
92
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
93
- /* @__PURE__ */ jsx(Label, { htmlFor: "allow-multiple", className: "font-medium", children: "Allow multiple wishlists per customer" }),
94
- /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Signed-in customers can create more than one wishlist." })
95
- ] }),
122
+ ),
96
123
  /* @__PURE__ */ jsx(
97
- Switch,
124
+ KpiCard,
98
125
  {
99
- id: "allow-multiple",
100
- checked: allowMultiple,
101
- onCheckedChange: setAllowMultiple
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
102
159
  }
103
160
  )
104
161
  ] }),
105
- /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
106
- Button,
107
- {
108
- variant: "primary",
109
- onClick: onSave,
110
- disabled: !isDirty || update.isPending,
111
- isLoading: update.isPending,
112
- children: "Save"
113
- }
114
- ) })
115
- ] }) })
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
+ ] })
116
251
  ] });
117
252
  };
118
- const config$1 = defineRouteConfig({
119
- label: "Wishlists",
120
- icon: Heart
121
- });
122
253
  const QUERY_KEY = ["wishlist", "settings"];
123
- const WishlistSettingsPage = () => {
254
+ const WishlistSettingsTab = () => {
124
255
  const queryClient = useQueryClient();
125
256
  const { data, isLoading } = useQuery({
126
257
  queryKey: QUERY_KEY,
@@ -162,12 +293,9 @@ const WishlistSettingsPage = () => {
162
293
  }
163
294
  update.mutate(patch);
164
295
  };
165
- return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
166
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsxs("div", { children: [
167
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Wishlists" }),
168
- /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Control who can use wishlists in your storefront." })
169
- ] }) }),
170
- /* @__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: [
171
299
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
172
300
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
173
301
  /* @__PURE__ */ jsx(Label, { htmlFor: "allow-guest", className: "font-medium", children: "Allow guest wishlists" }),
@@ -206,9 +334,22 @@ const WishlistSettingsPage = () => {
206
334
  children: "Save"
207
335
  }
208
336
  ) })
209
- ] }) })
337
+ ] })
210
338
  ] });
211
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
+ };
212
353
  const config = defineRouteConfig({
213
354
  label: "Wishlists",
214
355
  icon: Heart
@@ -222,29 +363,17 @@ const widgetModule = { widgets: [
222
363
  const routeModule = {
223
364
  routes: [
224
365
  {
225
- Component: WishlistSettingsPage$1,
366
+ Component: WishlistPage,
226
367
  path: "/wishlists"
227
- },
228
- {
229
- Component: WishlistSettingsPage,
230
- path: "/settings/wishlists"
231
368
  }
232
369
  ]
233
370
  };
234
371
  const menuItemModule = {
235
372
  menuItems: [
236
- {
237
- label: config$1.label,
238
- icon: config$1.icon,
239
- path: "/wishlists",
240
- nested: void 0,
241
- rank: void 0,
242
- translationNs: void 0
243
- },
244
373
  {
245
374
  label: config.label,
246
375
  icon: config.icon,
247
- path: "/settings/wishlists",
376
+ path: "/wishlists",
248
377
  nested: void 0,
249
378
  rank: void 0,
250
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.1",
3
+ "version": "0.7.0",
4
4
  "description": "Alphabite's Medusa Wishlist Plugin",
5
5
  "author": "Alphabite (https://alphabite.io)",
6
6
  "license": "MIT",