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