@zaamx/netme-bundle 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +381 -414
- package/.medusa/server/src/admin/index.mjs +383 -416
- package/.medusa/server/src/api/admin/bundled-products/route.js +66 -73
- package/.medusa/server/src/api/admin/products/[id]/bundle/route.js +2 -11
- package/.medusa/server/src/workflows/update-product-bundle.js +3 -1
- package/README.md +443 -48
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
3
|
import { CubeSolid } from "@medusajs/icons";
|
|
4
|
-
import { Container, Heading, Button, Text, Badge, FocusModal, Label,
|
|
4
|
+
import { Container, Heading, Button, Text, Badge, FocusModal, Label, Input, toast, Switch } from "@medusajs/ui";
|
|
5
5
|
import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query";
|
|
6
|
-
import { useState,
|
|
6
|
+
import { useState, useRef, useEffect } from "react";
|
|
7
7
|
import Medusa from "@medusajs/js-sdk";
|
|
8
8
|
import { Link } from "react-router-dom";
|
|
9
9
|
const sdk = new Medusa({
|
|
@@ -13,514 +13,481 @@ const sdk = new Medusa({
|
|
|
13
13
|
type: "session"
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
|
-
const
|
|
16
|
+
const ProductSearchSelect = ({
|
|
17
|
+
value,
|
|
18
|
+
valueLabel,
|
|
19
|
+
onChange,
|
|
20
|
+
disabled,
|
|
21
|
+
placeholder = "Search products…"
|
|
22
|
+
}) => {
|
|
23
|
+
const [search, setSearch] = useState("");
|
|
24
|
+
const [open, setOpen] = useState(false);
|
|
25
|
+
const [results, setResults] = useState([]);
|
|
26
|
+
const [loading, setLoading] = useState(false);
|
|
27
|
+
const containerRef = useRef(null);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const handler = (e) => {
|
|
30
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
31
|
+
setOpen(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
document.addEventListener("mousedown", handler);
|
|
35
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
36
|
+
}, []);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!open) return;
|
|
39
|
+
const delay = search.trim() ? 300 : 0;
|
|
40
|
+
const timer = setTimeout(async () => {
|
|
41
|
+
setLoading(true);
|
|
42
|
+
try {
|
|
43
|
+
const { products } = await sdk.admin.product.list({
|
|
44
|
+
q: search.trim() || void 0,
|
|
45
|
+
limit: 15
|
|
46
|
+
});
|
|
47
|
+
setResults(products.map((p) => ({ id: p.id, title: p.title ?? "" })));
|
|
48
|
+
} catch {
|
|
49
|
+
setResults([]);
|
|
50
|
+
} finally {
|
|
51
|
+
setLoading(false);
|
|
52
|
+
}
|
|
53
|
+
}, delay);
|
|
54
|
+
return () => clearTimeout(timer);
|
|
55
|
+
}, [search, open]);
|
|
56
|
+
if (disabled) {
|
|
57
|
+
return /* @__PURE__ */ jsx("div", { className: "border border-gray-200 rounded-md px-3 py-2 bg-gray-50 text-sm text-gray-700 min-h-[38px]", children: valueLabel || value || /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "—" }) });
|
|
58
|
+
}
|
|
59
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative", children: [
|
|
60
|
+
value && valueLabel && /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-2", children: [
|
|
61
|
+
/* @__PURE__ */ jsx(Badge, { className: "bg-blue-50 text-blue-700 text-xs", children: valueLabel }),
|
|
62
|
+
/* @__PURE__ */ jsx(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
type: "button",
|
|
66
|
+
className: "text-xs text-gray-400 hover:text-gray-600",
|
|
67
|
+
onClick: () => onChange("", ""),
|
|
68
|
+
children: "✕ clear"
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
] }),
|
|
72
|
+
/* @__PURE__ */ jsx(
|
|
73
|
+
Input,
|
|
74
|
+
{
|
|
75
|
+
placeholder: value ? "Search to change…" : placeholder,
|
|
76
|
+
value: search,
|
|
77
|
+
onChange: (e) => {
|
|
78
|
+
setSearch(e.target.value);
|
|
79
|
+
setOpen(true);
|
|
80
|
+
},
|
|
81
|
+
onFocus: () => setOpen(true)
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
open && /* @__PURE__ */ jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg max-h-60 overflow-y-auto", children: [
|
|
85
|
+
loading && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-gray-400", children: "Searching…" }),
|
|
86
|
+
!loading && results.length === 0 && search.trim() && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-sm text-gray-400", children: "No products found." }),
|
|
87
|
+
results.map((product) => /* @__PURE__ */ jsx(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
type: "button",
|
|
91
|
+
className: "w-full text-left px-3 py-2 hover:bg-gray-50 text-sm border-b border-gray-100 last:border-0",
|
|
92
|
+
onMouseDown: (e) => {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
onChange(product.id, product.title);
|
|
95
|
+
setSearch("");
|
|
96
|
+
setOpen(false);
|
|
97
|
+
},
|
|
98
|
+
children: product.title
|
|
99
|
+
},
|
|
100
|
+
product.id
|
|
101
|
+
))
|
|
102
|
+
] })
|
|
103
|
+
] });
|
|
104
|
+
};
|
|
105
|
+
const VariantPicker = ({
|
|
106
|
+
productId,
|
|
107
|
+
selectedIds,
|
|
108
|
+
onChange
|
|
109
|
+
}) => {
|
|
110
|
+
const [variants, setVariants] = useState([]);
|
|
111
|
+
const [loading, setLoading] = useState(false);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!productId) {
|
|
114
|
+
setVariants([]);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
setLoading(true);
|
|
118
|
+
sdk.admin.product.retrieve(productId).then(
|
|
119
|
+
({ product }) => setVariants(
|
|
120
|
+
(product.variants || []).map((v) => ({ id: v.id, title: v.title ?? v.id, sku: v.sku ?? void 0 }))
|
|
121
|
+
)
|
|
122
|
+
).catch(() => setVariants([])).finally(() => setLoading(false));
|
|
123
|
+
}, [productId]);
|
|
124
|
+
const toggle = (id) => onChange(
|
|
125
|
+
selectedIds.includes(id) ? selectedIds.filter((s) => s !== id) : [...selectedIds, id]
|
|
126
|
+
);
|
|
127
|
+
if (loading) return /* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-400", children: "Loading variants…" });
|
|
128
|
+
if (!loading && variants.length === 0)
|
|
129
|
+
return /* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-400", children: "No variants found." });
|
|
130
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-52 overflow-y-auto pr-1", children: variants.map((v) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer select-none", children: [
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
"input",
|
|
133
|
+
{
|
|
134
|
+
type: "checkbox",
|
|
135
|
+
checked: selectedIds.includes(v.id),
|
|
136
|
+
onChange: () => toggle(v.id),
|
|
137
|
+
className: "h-4 w-4 rounded border-gray-300"
|
|
138
|
+
}
|
|
139
|
+
),
|
|
140
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: v.title }),
|
|
141
|
+
v.sku && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 font-mono", children: v.sku })
|
|
142
|
+
] }, v.id)) });
|
|
143
|
+
};
|
|
144
|
+
const emptyBundleItem = () => ({
|
|
145
|
+
product_id: void 0,
|
|
146
|
+
product_title: "",
|
|
147
|
+
variant_mode: "all",
|
|
148
|
+
variant_ids: [],
|
|
149
|
+
min_quantity: 1,
|
|
150
|
+
max_quantity: 1,
|
|
151
|
+
default_quantity: 1,
|
|
152
|
+
optional: false,
|
|
153
|
+
separate_shipping: false,
|
|
154
|
+
individual_price: false
|
|
155
|
+
});
|
|
156
|
+
const LIMIT = 15;
|
|
17
157
|
const BundledProductsPage = () => {
|
|
18
158
|
const [page, setPage] = useState(0);
|
|
19
|
-
const [
|
|
159
|
+
const [openModal, setOpenModal] = useState(false);
|
|
20
160
|
const [isEditing, setIsEditing] = useState(false);
|
|
21
161
|
const [selectedProductId, setSelectedProductId] = useState();
|
|
162
|
+
const [selectedProductTitle, setSelectedProductTitle] = useState("");
|
|
22
163
|
const [bundleItems, setBundleItems] = useState([]);
|
|
23
164
|
const [bundleMeta, setBundleMeta] = useState([]);
|
|
24
|
-
const [products, setProducts] = useState([]);
|
|
25
|
-
const productsLimit = 15;
|
|
26
|
-
const [currentProductPage, setCurrentProductPage] = useState(0);
|
|
27
|
-
const [productsCount, setProductsCount] = useState(0);
|
|
28
|
-
const hasNextPage = useMemo(
|
|
29
|
-
() => productsCount ? productsCount > productsLimit : true,
|
|
30
|
-
[productsCount, productsLimit]
|
|
31
|
-
);
|
|
32
165
|
const queryClient = useQueryClient();
|
|
33
|
-
const offset = page *
|
|
166
|
+
const offset = page * LIMIT;
|
|
167
|
+
const closeModal = () => {
|
|
168
|
+
setOpenModal(false);
|
|
169
|
+
setIsEditing(false);
|
|
170
|
+
setSelectedProductId(void 0);
|
|
171
|
+
setSelectedProductTitle("");
|
|
172
|
+
setBundleItems([]);
|
|
173
|
+
setBundleMeta([]);
|
|
174
|
+
};
|
|
34
175
|
const { data, isLoading } = useQuery({
|
|
35
|
-
queryKey: ["bundled-products", offset,
|
|
176
|
+
queryKey: ["bundled-products", offset, LIMIT],
|
|
36
177
|
queryFn: () => sdk.client.fetch("/admin/bundled-products", {
|
|
37
178
|
method: "GET",
|
|
38
|
-
query: {
|
|
39
|
-
limit,
|
|
40
|
-
offset
|
|
41
|
-
}
|
|
179
|
+
query: { limit: LIMIT, offset }
|
|
42
180
|
})
|
|
43
181
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
queryFn: async () => {
|
|
47
|
-
const { products: products2, count } = await sdk.admin.product.list({
|
|
48
|
-
limit: productsLimit,
|
|
49
|
-
offset: currentProductPage * productsLimit
|
|
50
|
-
});
|
|
51
|
-
setProductsCount(count);
|
|
52
|
-
setProducts((prev) => [...prev, ...products2]);
|
|
53
|
-
return products2;
|
|
54
|
-
},
|
|
55
|
-
enabled: hasNextPage
|
|
56
|
-
});
|
|
57
|
-
const fetchMoreProducts = () => {
|
|
58
|
-
if (!hasNextPage) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
setCurrentProductPage(currentProductPage + 1);
|
|
62
|
-
};
|
|
63
|
-
const { mutateAsync: createBundle, isPending: isCreating } = useMutation({
|
|
64
|
-
mutationFn: async (data2) => {
|
|
182
|
+
const { mutateAsync: saveBundle, isPending: isSaving } = useMutation({
|
|
183
|
+
mutationFn: async () => {
|
|
65
184
|
if (!selectedProductId) throw new Error("No product selected");
|
|
185
|
+
const validItems = bundleItems.filter((i) => i.product_id);
|
|
66
186
|
await sdk.client.fetch(`/admin/products/${selectedProductId}/bundle`, {
|
|
67
187
|
method: "POST",
|
|
68
|
-
body:
|
|
188
|
+
body: {
|
|
189
|
+
child_product_ids: validItems.map((item) => ({
|
|
190
|
+
id: item.product_id,
|
|
191
|
+
variant_ids: item.variant_mode === "specific" ? item.variant_ids : [],
|
|
192
|
+
min_quantity: item.min_quantity,
|
|
193
|
+
max_quantity: item.max_quantity,
|
|
194
|
+
default_quantity: item.default_quantity,
|
|
195
|
+
optional: item.optional,
|
|
196
|
+
separate_shipping: item.separate_shipping,
|
|
197
|
+
individual_price: item.individual_price
|
|
198
|
+
})),
|
|
199
|
+
bundle_meta: bundleMeta,
|
|
200
|
+
is_bundle: true
|
|
201
|
+
}
|
|
69
202
|
});
|
|
70
203
|
}
|
|
71
204
|
});
|
|
72
|
-
const { mutateAsync: deleteBundle
|
|
73
|
-
mutationFn:
|
|
74
|
-
await sdk.client.fetch(`/admin/products/${productId}/bundle`, {
|
|
75
|
-
method: "DELETE"
|
|
76
|
-
});
|
|
77
|
-
}
|
|
205
|
+
const { mutateAsync: deleteBundle } = useMutation({
|
|
206
|
+
mutationFn: (productId) => sdk.client.fetch(`/admin/products/${productId}/bundle`, { method: "DELETE" })
|
|
78
207
|
});
|
|
79
|
-
const handleDeleteBundle = async (productId) => {
|
|
80
|
-
try {
|
|
81
|
-
await deleteBundle(productId);
|
|
82
|
-
toast.success("Bundle deleted successfully");
|
|
83
|
-
queryClient.invalidateQueries({
|
|
84
|
-
queryKey: ["bundled-products"]
|
|
85
|
-
});
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error("Error deleting bundle:", error);
|
|
88
|
-
toast.error("Failed to delete bundle");
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
208
|
const handleEditBundle = (bundle) => {
|
|
92
209
|
setIsEditing(true);
|
|
93
210
|
setSelectedProductId(bundle.product.id);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
setBundleMeta(bundle.bundle_meta || []);
|
|
104
|
-
setOpenCreateModal(true);
|
|
105
|
-
};
|
|
106
|
-
const handleCreateBundle = async () => {
|
|
107
|
-
try {
|
|
108
|
-
if (!selectedProductId) {
|
|
109
|
-
toast.error("Please select a product");
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const validItems = bundleItems.filter((item) => item.product_id);
|
|
113
|
-
if (validItems.length === 0) {
|
|
114
|
-
toast.error("Please add at least one product to the bundle");
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
await createBundle({
|
|
118
|
-
child_product_ids: validItems.map((item) => ({
|
|
119
|
-
id: item.product_id,
|
|
211
|
+
setSelectedProductTitle(bundle.title);
|
|
212
|
+
setBundleItems(
|
|
213
|
+
bundle.items.map((item) => {
|
|
214
|
+
var _a;
|
|
215
|
+
return {
|
|
216
|
+
product_id: item.product.id,
|
|
217
|
+
product_title: item.product.title,
|
|
218
|
+
variant_mode: ((_a = item.variant_ids) == null ? void 0 : _a.length) ? "specific" : "all",
|
|
219
|
+
variant_ids: item.variant_ids ?? [],
|
|
120
220
|
min_quantity: item.min_quantity,
|
|
121
221
|
max_quantity: item.max_quantity,
|
|
122
222
|
default_quantity: item.default_quantity,
|
|
123
223
|
optional: item.optional,
|
|
124
224
|
separate_shipping: item.separate_shipping,
|
|
125
225
|
individual_price: item.individual_price
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
toast.success(isEditing ? "Bundle updated successfully" : "Bundle created successfully");
|
|
132
|
-
queryClient.invalidateQueries({
|
|
133
|
-
queryKey: ["bundled-products"]
|
|
134
|
-
});
|
|
135
|
-
setIsEditing(false);
|
|
136
|
-
setSelectedProductId(void 0);
|
|
137
|
-
setBundleItems([]);
|
|
138
|
-
setBundleMeta([]);
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.error("Error creating bundle:", error);
|
|
141
|
-
toast.error("Failed to create bundle");
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
const addBundleItem = () => {
|
|
145
|
-
setBundleItems([
|
|
146
|
-
...bundleItems,
|
|
147
|
-
{
|
|
148
|
-
product_id: void 0,
|
|
149
|
-
min_quantity: 1,
|
|
150
|
-
max_quantity: 1,
|
|
151
|
-
default_quantity: 1,
|
|
152
|
-
optional: false,
|
|
153
|
-
separate_shipping: false,
|
|
154
|
-
individual_price: false
|
|
155
|
-
}
|
|
156
|
-
]);
|
|
157
|
-
};
|
|
158
|
-
const removeBundleItem = (index) => {
|
|
159
|
-
setBundleItems(bundleItems.filter((_, i) => i !== index));
|
|
160
|
-
};
|
|
161
|
-
const updateBundleItem = (index, field, value) => {
|
|
162
|
-
setBundleItems(bundleItems.map(
|
|
163
|
-
(item, i) => i === index ? { ...item, [field]: value } : item
|
|
164
|
-
));
|
|
165
|
-
};
|
|
166
|
-
const addBundleMeta = () => {
|
|
167
|
-
setBundleMeta([...bundleMeta, { key: "", value: "" }]);
|
|
226
|
+
};
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
setBundleMeta(bundle.bundle_meta || []);
|
|
230
|
+
setOpenModal(true);
|
|
168
231
|
};
|
|
169
|
-
const
|
|
170
|
-
|
|
232
|
+
const handleDeleteBundle = async (productId) => {
|
|
233
|
+
try {
|
|
234
|
+
await deleteBundle(productId);
|
|
235
|
+
toast.success("Bundle deleted");
|
|
236
|
+
queryClient.invalidateQueries({ queryKey: ["bundled-products"] });
|
|
237
|
+
} catch {
|
|
238
|
+
toast.error("Failed to delete bundle");
|
|
239
|
+
}
|
|
171
240
|
};
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
(
|
|
175
|
-
|
|
241
|
+
const handleSave = async () => {
|
|
242
|
+
if (!selectedProductId) {
|
|
243
|
+
toast.error("Please select a product");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!bundleItems.some((i) => i.product_id)) {
|
|
247
|
+
toast.error("Please add at least one product to the bundle");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await saveBundle();
|
|
252
|
+
toast.success(isEditing ? "Bundle updated" : "Bundle created");
|
|
253
|
+
queryClient.invalidateQueries({ queryKey: ["bundled-products"] });
|
|
254
|
+
closeModal();
|
|
255
|
+
} catch {
|
|
256
|
+
toast.error(isEditing ? "Failed to update bundle" : "Failed to create bundle");
|
|
257
|
+
}
|
|
176
258
|
};
|
|
177
|
-
const
|
|
259
|
+
const updateItem = (index, field, value) => setBundleItems((prev) => prev.map((item, i) => i === index ? { ...item, [field]: value } : item));
|
|
260
|
+
const totalPages = Math.ceil(((data == null ? void 0 : data.count) || 0) / LIMIT);
|
|
178
261
|
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
179
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-
|
|
262
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-6", children: [
|
|
180
263
|
/* @__PURE__ */ jsx(Heading, { children: "Bundled Products" }),
|
|
181
|
-
/* @__PURE__ */ jsx(
|
|
264
|
+
/* @__PURE__ */ jsx(
|
|
265
|
+
Button,
|
|
266
|
+
{
|
|
267
|
+
variant: "primary",
|
|
268
|
+
onClick: () => {
|
|
269
|
+
closeModal();
|
|
270
|
+
setOpenModal(true);
|
|
271
|
+
},
|
|
272
|
+
children: "Create Bundle"
|
|
273
|
+
}
|
|
274
|
+
)
|
|
182
275
|
] }),
|
|
183
|
-
isLoading ? /* @__PURE__ */ jsx("div", { className: "p-6", children: /* @__PURE__ */ jsx(Text, { children: "Loading
|
|
276
|
+
isLoading ? /* @__PURE__ */ jsx("div", { className: "p-6", children: /* @__PURE__ */ jsx(Text, { children: "Loading…" }) }) : /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
|
|
184
277
|
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse border border-gray-200", children: [
|
|
185
278
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "bg-gray-50", children: [
|
|
186
|
-
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "ID" }),
|
|
187
|
-
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Title" }),
|
|
188
|
-
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Items" }),
|
|
189
279
|
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Product" }),
|
|
190
|
-
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Status" })
|
|
280
|
+
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Status" }),
|
|
281
|
+
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Items" }),
|
|
282
|
+
/* @__PURE__ */ jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Actions" })
|
|
191
283
|
] }) }),
|
|
192
|
-
/* @__PURE__ */
|
|
193
|
-
/* @__PURE__ */ jsx("
|
|
194
|
-
/* @__PURE__ */
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
{
|
|
199
|
-
to: `/products/${item.product.id}`,
|
|
200
|
-
className: "text-blue-600 hover:underline",
|
|
201
|
-
children: item.product.title
|
|
202
|
-
}
|
|
203
|
-
),
|
|
204
|
-
/* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
|
|
205
|
-
" x ",
|
|
206
|
-
item.quantity
|
|
284
|
+
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
285
|
+
((data == null ? void 0 : data.bundled_products.length) ?? 0) === 0 && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 4, className: "border border-gray-200 px-4 py-6 text-center text-gray-400", children: "No bundled products found." }) }),
|
|
286
|
+
data == null ? void 0 : data.bundled_products.map((bundle) => /* @__PURE__ */ jsxs("tr", { className: "hover:bg-gray-50", children: [
|
|
287
|
+
/* @__PURE__ */ jsxs("td", { className: "border border-gray-200 px-4 py-3", children: [
|
|
288
|
+
/* @__PURE__ */ jsx(Link, { to: `/products/${bundle.product.id}`, className: "font-medium text-blue-600 hover:underline", children: bundle.title }),
|
|
289
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-gray-400 mt-0.5", children: bundle.id })
|
|
207
290
|
] }),
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
{
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
children: "Delete"
|
|
237
|
-
}
|
|
238
|
-
)
|
|
239
|
-
] }) })
|
|
240
|
-
] }, bundle.id)) })
|
|
291
|
+
/* @__PURE__ */ jsx("td", { className: "border border-gray-200 px-4 py-3", children: /* @__PURE__ */ jsx(Badge, { className: bundle.status === "published" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-600", children: bundle.status ?? "—" }) }),
|
|
292
|
+
/* @__PURE__ */ jsx("td", { className: "border border-gray-200 px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
293
|
+
bundle.items.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-400", children: "No items" }),
|
|
294
|
+
bundle.items.map((item) => {
|
|
295
|
+
var _a;
|
|
296
|
+
return /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
|
|
297
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
298
|
+
/* @__PURE__ */ jsx(Link, { to: `/products/${item.product.id}`, className: "text-blue-600 hover:underline", children: item.product.title }),
|
|
299
|
+
/* @__PURE__ */ jsxs("span", { className: "text-gray-400", children: [
|
|
300
|
+
"× ",
|
|
301
|
+
item.default_quantity
|
|
302
|
+
] }),
|
|
303
|
+
item.optional && /* @__PURE__ */ jsx(Badge, { className: "bg-gray-100 text-gray-500 text-xs", children: "Optional" })
|
|
304
|
+
] }),
|
|
305
|
+
((_a = item.variant_ids) == null ? void 0 : _a.length) > 0 && /* @__PURE__ */ jsxs("div", { className: "text-xs text-gray-400 ml-1", children: [
|
|
306
|
+
item.variant_ids.length,
|
|
307
|
+
" specific variant",
|
|
308
|
+
item.variant_ids.length > 1 ? "s" : ""
|
|
309
|
+
] })
|
|
310
|
+
] }, item.id);
|
|
311
|
+
})
|
|
312
|
+
] }) }),
|
|
313
|
+
/* @__PURE__ */ jsx("td", { className: "border border-gray-200 px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
314
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => handleEditBundle(bundle), children: "Edit" }),
|
|
315
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => handleDeleteBundle(bundle.id), className: "text-red-600 hover:text-red-700", children: "Delete" })
|
|
316
|
+
] }) })
|
|
317
|
+
] }, bundle.id))
|
|
318
|
+
] })
|
|
241
319
|
] }) }),
|
|
242
320
|
totalPages > 1 && /* @__PURE__ */ jsxs("div", { className: "flex justify-center gap-2 mt-6", children: [
|
|
243
|
-
/* @__PURE__ */ jsx(
|
|
244
|
-
Button,
|
|
245
|
-
{
|
|
246
|
-
variant: "secondary",
|
|
247
|
-
onClick: () => setPage(Math.max(0, page - 1)),
|
|
248
|
-
disabled: page === 0,
|
|
249
|
-
children: "Previous"
|
|
250
|
-
}
|
|
251
|
-
),
|
|
321
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setPage(Math.max(0, page - 1)), disabled: page === 0, children: "Previous" }),
|
|
252
322
|
/* @__PURE__ */ jsxs("span", { className: "flex items-center px-4", children: [
|
|
253
323
|
"Page ",
|
|
254
324
|
page + 1,
|
|
255
325
|
" of ",
|
|
256
326
|
totalPages
|
|
257
327
|
] }),
|
|
258
|
-
/* @__PURE__ */ jsx(
|
|
259
|
-
Button,
|
|
260
|
-
{
|
|
261
|
-
variant: "secondary",
|
|
262
|
-
onClick: () => setPage(Math.min(totalPages - 1, page + 1)),
|
|
263
|
-
disabled: page === totalPages - 1,
|
|
264
|
-
children: "Next"
|
|
265
|
-
}
|
|
266
|
-
)
|
|
328
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setPage(Math.min(totalPages - 1, page + 1)), disabled: page === totalPages - 1, children: "Next" })
|
|
267
329
|
] })
|
|
268
330
|
] }),
|
|
269
|
-
/* @__PURE__ */ jsx(FocusModal, { open:
|
|
331
|
+
/* @__PURE__ */ jsx(FocusModal, { open: openModal, onOpenChange: (open) => {
|
|
332
|
+
if (!open) closeModal();
|
|
333
|
+
}, children: /* @__PURE__ */ jsxs(FocusModal.Content, { children: [
|
|
270
334
|
/* @__PURE__ */ jsx(FocusModal.Header, { children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: isEditing ? "Edit Bundle" : "Create Bundle" }) }),
|
|
271
335
|
/* @__PURE__ */ jsx(FocusModal.Body, { children: /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col items-center h-[80vh] overflow-y-auto px-2 py-8", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-3xl flex flex-col gap-y-8", children: [
|
|
272
336
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
273
|
-
/* @__PURE__ */ jsx(Label, { children: "
|
|
274
|
-
/* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-
|
|
275
|
-
/* @__PURE__ */
|
|
276
|
-
|
|
337
|
+
/* @__PURE__ */ jsx(Label, { children: "Bundle Product" }),
|
|
338
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-500 mb-2", children: isEditing ? "The product this bundle is attached to" : "Choose the product that becomes the bundle" }),
|
|
339
|
+
/* @__PURE__ */ jsx(
|
|
340
|
+
ProductSearchSelect,
|
|
277
341
|
{
|
|
278
342
|
value: selectedProductId,
|
|
279
|
-
|
|
343
|
+
valueLabel: selectedProductTitle,
|
|
344
|
+
onChange: (id, title) => {
|
|
345
|
+
setSelectedProductId(id || void 0);
|
|
346
|
+
setSelectedProductTitle(title);
|
|
347
|
+
},
|
|
280
348
|
disabled: isEditing,
|
|
281
|
-
|
|
282
|
-
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select a product to bundle" }) }),
|
|
283
|
-
/* @__PURE__ */ jsx(Select.Content, { children: products == null ? void 0 : products.map((product) => /* @__PURE__ */ jsx(
|
|
284
|
-
Select.Item,
|
|
285
|
-
{
|
|
286
|
-
value: product.id,
|
|
287
|
-
children: product.title
|
|
288
|
-
},
|
|
289
|
-
product.id
|
|
290
|
-
)) })
|
|
291
|
-
]
|
|
349
|
+
placeholder: "Search for a product to bundle…"
|
|
292
350
|
}
|
|
293
351
|
)
|
|
294
352
|
] }),
|
|
295
353
|
selectedProductId && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
296
354
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
297
355
|
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Bundle Items" }),
|
|
298
|
-
/* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-
|
|
356
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-500 mb-4", children: "Products included in this bundle" }),
|
|
299
357
|
bundleItems.map((item, index) => /* @__PURE__ */ jsx(
|
|
300
358
|
BundleItemForm,
|
|
301
359
|
{
|
|
302
360
|
item,
|
|
303
361
|
index,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
onRemove: () => removeBundleItem(index),
|
|
307
|
-
fetchMoreProducts,
|
|
308
|
-
hasNextPage
|
|
362
|
+
onUpdate: (field, value) => updateItem(index, field, value),
|
|
363
|
+
onRemove: () => setBundleItems((prev) => prev.filter((_, i) => i !== index))
|
|
309
364
|
},
|
|
310
365
|
index
|
|
311
366
|
)),
|
|
312
|
-
/* @__PURE__ */ jsx(
|
|
313
|
-
Button,
|
|
314
|
-
{
|
|
315
|
-
variant: "secondary",
|
|
316
|
-
onClick: addBundleItem,
|
|
317
|
-
className: "mt-4",
|
|
318
|
-
children: "Add Item"
|
|
319
|
-
}
|
|
320
|
-
)
|
|
367
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setBundleItems((prev) => [...prev, emptyBundleItem()]), className: "mt-2", children: "Add Item" })
|
|
321
368
|
] }),
|
|
322
369
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
323
370
|
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Bundle Metadata" }),
|
|
324
|
-
/* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-
|
|
371
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm text-gray-500 mb-4", children: "Custom key-value pairs" }),
|
|
325
372
|
bundleMeta.map((meta, index) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2 mb-2", children: [
|
|
326
|
-
/* @__PURE__ */ jsx(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
placeholder: "Key",
|
|
330
|
-
value: meta.key,
|
|
331
|
-
onChange: (e) => updateBundleMeta(index, "key", e.target.value)
|
|
332
|
-
}
|
|
333
|
-
),
|
|
334
|
-
/* @__PURE__ */ jsx(
|
|
335
|
-
Input,
|
|
336
|
-
{
|
|
337
|
-
placeholder: "Value",
|
|
338
|
-
value: meta.value,
|
|
339
|
-
onChange: (e) => updateBundleMeta(index, "value", e.target.value)
|
|
340
|
-
}
|
|
341
|
-
),
|
|
342
|
-
/* @__PURE__ */ jsx(
|
|
343
|
-
Button,
|
|
344
|
-
{
|
|
345
|
-
variant: "secondary",
|
|
346
|
-
onClick: () => removeBundleMeta(index),
|
|
347
|
-
children: "Remove"
|
|
348
|
-
}
|
|
349
|
-
)
|
|
373
|
+
/* @__PURE__ */ jsx(Input, { placeholder: "Key", value: meta.key, onChange: (e) => setBundleMeta((prev) => prev.map((m, i) => i === index ? { ...m, key: e.target.value } : m)) }),
|
|
374
|
+
/* @__PURE__ */ jsx(Input, { placeholder: "Value", value: meta.value, onChange: (e) => setBundleMeta((prev) => prev.map((m, i) => i === index ? { ...m, value: e.target.value } : m)) }),
|
|
375
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setBundleMeta((prev) => prev.filter((_, i) => i !== index)), children: "Remove" })
|
|
350
376
|
] }, index)),
|
|
351
|
-
/* @__PURE__ */ jsx(
|
|
352
|
-
Button,
|
|
353
|
-
{
|
|
354
|
-
variant: "secondary",
|
|
355
|
-
onClick: addBundleMeta,
|
|
356
|
-
className: "mt-4",
|
|
357
|
-
children: "Add Metadata"
|
|
358
|
-
}
|
|
359
|
-
)
|
|
377
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setBundleMeta((prev) => [...prev, { key: "", value: "" }]), className: "mt-2", children: "Add Metadata" })
|
|
360
378
|
] })
|
|
361
379
|
] })
|
|
362
380
|
] }) }) }),
|
|
363
381
|
/* @__PURE__ */ jsx(FocusModal.Footer, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
|
|
364
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick:
|
|
365
|
-
|
|
366
|
-
setIsEditing(false);
|
|
367
|
-
setSelectedProductId(void 0);
|
|
368
|
-
setBundleItems([]);
|
|
369
|
-
setBundleMeta([]);
|
|
370
|
-
}, children: "Cancel" }),
|
|
371
|
-
/* @__PURE__ */ jsx(
|
|
372
|
-
Button,
|
|
373
|
-
{
|
|
374
|
-
variant: "primary",
|
|
375
|
-
onClick: handleCreateBundle,
|
|
376
|
-
isLoading: isCreating,
|
|
377
|
-
disabled: !selectedProductId,
|
|
378
|
-
children: isEditing ? "Update Bundle" : "Create Bundle"
|
|
379
|
-
}
|
|
380
|
-
)
|
|
382
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: closeModal, children: "Cancel" }),
|
|
383
|
+
/* @__PURE__ */ jsx(Button, { variant: "primary", onClick: handleSave, isLoading: isSaving, disabled: !selectedProductId, children: isEditing ? "Update Bundle" : "Create Bundle" })
|
|
381
384
|
] }) })
|
|
382
385
|
] }) })
|
|
383
386
|
] });
|
|
384
387
|
};
|
|
385
|
-
const BundleItemForm = ({
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
onRemove,
|
|
391
|
-
fetchMoreProducts,
|
|
392
|
-
hasNextPage
|
|
393
|
-
}) => {
|
|
394
|
-
const observer = useRef(
|
|
395
|
-
new IntersectionObserver(
|
|
396
|
-
(entries) => {
|
|
397
|
-
if (!hasNextPage) {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
const first = entries[0];
|
|
401
|
-
if (first.isIntersecting) {
|
|
402
|
-
fetchMoreProducts();
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
{ threshold: 1 }
|
|
406
|
-
)
|
|
407
|
-
);
|
|
408
|
-
const lastOptionRef = useCallback(
|
|
409
|
-
(node) => {
|
|
410
|
-
if (!hasNextPage) {
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (observer.current) {
|
|
414
|
-
observer.current.disconnect();
|
|
415
|
-
}
|
|
416
|
-
if (node) {
|
|
417
|
-
observer.current.observe(node);
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
[hasNextPage]
|
|
421
|
-
);
|
|
422
|
-
return /* @__PURE__ */ jsxs("div", { className: "border rounded-lg p-4 space-y-4", children: [
|
|
423
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
424
|
-
/* @__PURE__ */ jsxs(Heading, { level: "h3", children: [
|
|
425
|
-
"Item ",
|
|
426
|
-
index + 1
|
|
427
|
-
] }),
|
|
428
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onRemove, children: "Remove" })
|
|
429
|
-
] }),
|
|
430
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
431
|
-
/* @__PURE__ */ jsx(Label, { children: "Product" }),
|
|
432
|
-
/* @__PURE__ */ jsxs(
|
|
433
|
-
Select,
|
|
434
|
-
{
|
|
435
|
-
value: item.product_id,
|
|
436
|
-
onValueChange: (value) => onUpdate("product_id", value),
|
|
437
|
-
children: [
|
|
438
|
-
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select Product" }) }),
|
|
439
|
-
/* @__PURE__ */ jsx(Select.Content, { children: products == null ? void 0 : products.map((product, productIndex) => /* @__PURE__ */ jsx(
|
|
440
|
-
Select.Item,
|
|
441
|
-
{
|
|
442
|
-
value: product.id,
|
|
443
|
-
ref: productIndex === products.length - 1 ? lastOptionRef : null,
|
|
444
|
-
children: product.title
|
|
445
|
-
},
|
|
446
|
-
product.id
|
|
447
|
-
)) })
|
|
448
|
-
]
|
|
449
|
-
}
|
|
450
|
-
)
|
|
451
|
-
] }),
|
|
452
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4", children: [
|
|
453
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
454
|
-
/* @__PURE__ */ jsx(Label, { children: "Min Quantity" }),
|
|
455
|
-
/* @__PURE__ */ jsx(
|
|
456
|
-
Input,
|
|
457
|
-
{
|
|
458
|
-
type: "number",
|
|
459
|
-
min: 0,
|
|
460
|
-
value: item.min_quantity,
|
|
461
|
-
onChange: (e) => onUpdate("min_quantity", parseInt(e.target.value) || 0)
|
|
462
|
-
}
|
|
463
|
-
)
|
|
464
|
-
] }),
|
|
465
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
466
|
-
/* @__PURE__ */ jsx(Label, { children: "Max Quantity" }),
|
|
467
|
-
/* @__PURE__ */ jsx(
|
|
468
|
-
Input,
|
|
469
|
-
{
|
|
470
|
-
type: "number",
|
|
471
|
-
min: 0,
|
|
472
|
-
value: item.max_quantity,
|
|
473
|
-
onChange: (e) => onUpdate("max_quantity", parseInt(e.target.value) || 0)
|
|
474
|
-
}
|
|
475
|
-
)
|
|
476
|
-
] }),
|
|
477
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
478
|
-
/* @__PURE__ */ jsx(Label, { children: "Default Quantity" }),
|
|
479
|
-
/* @__PURE__ */ jsx(
|
|
480
|
-
Input,
|
|
481
|
-
{
|
|
482
|
-
type: "number",
|
|
483
|
-
min: 0,
|
|
484
|
-
value: item.default_quantity,
|
|
485
|
-
onChange: (e) => onUpdate("default_quantity", parseInt(e.target.value) || 0)
|
|
486
|
-
}
|
|
487
|
-
)
|
|
488
|
-
] })
|
|
388
|
+
const BundleItemForm = ({ item, index, onUpdate, onRemove }) => /* @__PURE__ */ jsxs("div", { className: "border rounded-lg p-4 space-y-4 mb-4", children: [
|
|
389
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
390
|
+
/* @__PURE__ */ jsxs(Heading, { level: "h3", children: [
|
|
391
|
+
"Item ",
|
|
392
|
+
index + 1
|
|
489
393
|
] }),
|
|
490
|
-
/* @__PURE__ */
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
394
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onRemove, children: "Remove" })
|
|
395
|
+
] }),
|
|
396
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
397
|
+
/* @__PURE__ */ jsx(Label, { children: "Product" }),
|
|
398
|
+
/* @__PURE__ */ jsx(
|
|
399
|
+
ProductSearchSelect,
|
|
400
|
+
{
|
|
401
|
+
value: item.product_id,
|
|
402
|
+
valueLabel: item.product_title,
|
|
403
|
+
onChange: (id, title) => {
|
|
404
|
+
onUpdate("product_id", id || void 0);
|
|
405
|
+
onUpdate("product_title", title);
|
|
406
|
+
onUpdate("variant_mode", "all");
|
|
407
|
+
onUpdate("variant_ids", []);
|
|
408
|
+
},
|
|
409
|
+
placeholder: "Search for a product…"
|
|
410
|
+
}
|
|
411
|
+
)
|
|
412
|
+
] }),
|
|
413
|
+
item.product_id && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
414
|
+
/* @__PURE__ */ jsx(Label, { children: "Applies to" }),
|
|
415
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-6", children: [
|
|
416
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer select-none", children: [
|
|
502
417
|
/* @__PURE__ */ jsx(
|
|
503
|
-
|
|
418
|
+
"input",
|
|
504
419
|
{
|
|
505
|
-
|
|
506
|
-
|
|
420
|
+
type: "radio",
|
|
421
|
+
name: `variant-mode-${index}`,
|
|
422
|
+
checked: item.variant_mode === "all",
|
|
423
|
+
onChange: () => {
|
|
424
|
+
onUpdate("variant_mode", "all");
|
|
425
|
+
onUpdate("variant_ids", []);
|
|
426
|
+
}
|
|
507
427
|
}
|
|
508
428
|
),
|
|
509
|
-
|
|
429
|
+
"All variations"
|
|
510
430
|
] }),
|
|
511
|
-
/* @__PURE__ */ jsxs("
|
|
431
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer select-none", children: [
|
|
512
432
|
/* @__PURE__ */ jsx(
|
|
513
|
-
|
|
433
|
+
"input",
|
|
514
434
|
{
|
|
515
|
-
|
|
516
|
-
|
|
435
|
+
type: "radio",
|
|
436
|
+
name: `variant-mode-${index}`,
|
|
437
|
+
checked: item.variant_mode === "specific",
|
|
438
|
+
onChange: () => onUpdate("variant_mode", "specific")
|
|
517
439
|
}
|
|
518
440
|
),
|
|
519
|
-
|
|
441
|
+
"Specific variations"
|
|
442
|
+
] })
|
|
443
|
+
] }),
|
|
444
|
+
item.variant_mode === "specific" && /* @__PURE__ */ jsxs("div", { className: "border border-gray-200 rounded-md p-3 bg-gray-50", children: [
|
|
445
|
+
/* @__PURE__ */ jsx(Text, { className: "text-xs text-gray-500 mb-3", children: 'Check each variation that qualifies for this bundle item. Switching back to "All variations" will clear this selection.' }),
|
|
446
|
+
/* @__PURE__ */ jsx(
|
|
447
|
+
VariantPicker,
|
|
448
|
+
{
|
|
449
|
+
productId: item.product_id,
|
|
450
|
+
selectedIds: item.variant_ids,
|
|
451
|
+
onChange: (ids) => onUpdate("variant_ids", ids)
|
|
452
|
+
}
|
|
453
|
+
),
|
|
454
|
+
item.variant_ids.length > 0 && /* @__PURE__ */ jsxs(Text, { className: "text-xs text-gray-400 mt-2", children: [
|
|
455
|
+
item.variant_ids.length,
|
|
456
|
+
" variation",
|
|
457
|
+
item.variant_ids.length > 1 ? "s" : "",
|
|
458
|
+
" selected"
|
|
520
459
|
] })
|
|
521
460
|
] })
|
|
522
|
-
] })
|
|
523
|
-
|
|
461
|
+
] }),
|
|
462
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4", children: [
|
|
463
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
464
|
+
/* @__PURE__ */ jsx(Label, { children: "Min Quantity" }),
|
|
465
|
+
/* @__PURE__ */ jsx(Input, { type: "number", min: 0, value: item.min_quantity, onChange: (e) => onUpdate("min_quantity", parseInt(e.target.value) || 0) })
|
|
466
|
+
] }),
|
|
467
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
468
|
+
/* @__PURE__ */ jsx(Label, { children: "Max Quantity" }),
|
|
469
|
+
/* @__PURE__ */ jsx(Input, { type: "number", min: 0, value: item.max_quantity, onChange: (e) => onUpdate("max_quantity", parseInt(e.target.value) || 0) })
|
|
470
|
+
] }),
|
|
471
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
472
|
+
/* @__PURE__ */ jsx(Label, { children: "Default Quantity" }),
|
|
473
|
+
/* @__PURE__ */ jsx(Input, { type: "number", min: 0, value: item.default_quantity, onChange: (e) => onUpdate("default_quantity", parseInt(e.target.value) || 0) })
|
|
474
|
+
] })
|
|
475
|
+
] }),
|
|
476
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
477
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
478
|
+
/* @__PURE__ */ jsx(Switch, { checked: item.optional, onCheckedChange: (v) => onUpdate("optional", v) }),
|
|
479
|
+
/* @__PURE__ */ jsx(Label, { children: "Optional" })
|
|
480
|
+
] }),
|
|
481
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
482
|
+
/* @__PURE__ */ jsx(Switch, { checked: item.separate_shipping, onCheckedChange: (v) => onUpdate("separate_shipping", v) }),
|
|
483
|
+
/* @__PURE__ */ jsx(Label, { children: "Separate Shipping" })
|
|
484
|
+
] }),
|
|
485
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
486
|
+
/* @__PURE__ */ jsx(Switch, { checked: item.individual_price, onCheckedChange: (v) => onUpdate("individual_price", v) }),
|
|
487
|
+
/* @__PURE__ */ jsx(Label, { children: "Individual Price" })
|
|
488
|
+
] })
|
|
489
|
+
] })
|
|
490
|
+
] });
|
|
524
491
|
const config = defineRouteConfig({
|
|
525
492
|
label: "Bundled Products",
|
|
526
493
|
icon: CubeSolid
|