@empty-complete-org/medusa-product-attributes 0.12.2 → 0.14.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.
- package/.medusa/server/src/admin/index.js +396 -29
- package/.medusa/server/src/admin/index.mjs +371 -21
- package/.medusa/server/src/api/admin/attribute-presets/[id]/apply/route.d.ts +2 -0
- package/.medusa/server/src/api/admin/attribute-presets/[id]/apply/route.js +12 -0
- package/.medusa/server/src/api/admin/attribute-presets/[id]/apply/route.js.map +1 -0
- package/.medusa/server/src/api/admin/attribute-presets/route.d.ts +4 -0
- package/.medusa/server/src/api/admin/attribute-presets/route.js +24 -0
- package/.medusa/server/src/api/admin/attribute-presets/route.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/index.d.ts +14 -0
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260406120000.d.ts +5 -0
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260406120000.js +32 -0
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260406120000.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/models/attribute-preset.d.ts +9 -0
- package/.medusa/server/src/modules/product-attributes/models/attribute-preset.js +13 -0
- package/.medusa/server/src/modules/product-attributes/models/attribute-preset.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/service.d.ts +78 -0
- package/.medusa/server/src/modules/product-attributes/service.js +38 -4
- package/.medusa/server/src/modules/product-attributes/service.js.map +1 -1
- package/package.json +4 -1
|
@@ -3,9 +3,27 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
3
3
|
const adminSdk = require("@medusajs/admin-sdk");
|
|
4
4
|
const ui = require("@medusajs/ui");
|
|
5
5
|
const reactQuery = require("@tanstack/react-query");
|
|
6
|
-
const
|
|
6
|
+
const React = require("react");
|
|
7
7
|
const Medusa = require("@medusajs/js-sdk");
|
|
8
8
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
12
|
+
if (e) {
|
|
13
|
+
for (const k in e) {
|
|
14
|
+
if (k !== "default") {
|
|
15
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: () => e[k]
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
const React__namespace = /* @__PURE__ */ _interopNamespace(React);
|
|
9
27
|
const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
|
|
10
28
|
const sdk = new Medusa__default.default({
|
|
11
29
|
baseUrl: "/",
|
|
@@ -14,17 +32,37 @@ const sdk = new Medusa__default.default({
|
|
|
14
32
|
type: "session"
|
|
15
33
|
}
|
|
16
34
|
});
|
|
17
|
-
const emptyForm = () => ({ label: "", type: "text", unit: "" });
|
|
35
|
+
const emptyForm$1 = () => ({ label: "", type: "text", unit: "" });
|
|
18
36
|
const CategoryAttributeTemplatesWidget = ({
|
|
19
37
|
data
|
|
20
38
|
}) => {
|
|
39
|
+
var _a, _b;
|
|
21
40
|
const categoryId = data.id;
|
|
22
41
|
const qc = reactQuery.useQueryClient();
|
|
23
42
|
const queryKey = ["category-custom-attributes", categoryId];
|
|
24
|
-
const [showAddForm, setShowAddForm] =
|
|
25
|
-
const [
|
|
26
|
-
const [
|
|
27
|
-
const [
|
|
43
|
+
const [showAddForm, setShowAddForm] = React.useState(false);
|
|
44
|
+
const [showPresetList, setShowPresetList] = React.useState(false);
|
|
45
|
+
const [addForm, setAddForm] = React.useState(emptyForm$1());
|
|
46
|
+
const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
|
|
47
|
+
const [mutationError, setMutationError] = React.useState(null);
|
|
48
|
+
const presetsQuery = reactQuery.useQuery({
|
|
49
|
+
queryKey: ["attribute-presets"],
|
|
50
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-presets`),
|
|
51
|
+
enabled: showPresetList
|
|
52
|
+
});
|
|
53
|
+
const applyPresetMutation = reactQuery.useMutation({
|
|
54
|
+
mutationFn: (presetId) => sdk.client.fetch(`/admin/attribute-presets/${presetId}/apply`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: { category_id: categoryId }
|
|
57
|
+
}),
|
|
58
|
+
onSuccess: () => {
|
|
59
|
+
qc.invalidateQueries({ queryKey });
|
|
60
|
+
setShowPresetList(false);
|
|
61
|
+
},
|
|
62
|
+
onError: (err) => {
|
|
63
|
+
setMutationError((err == null ? void 0 : err.message) || "Ошибка при применении пресета");
|
|
64
|
+
}
|
|
65
|
+
});
|
|
28
66
|
const {
|
|
29
67
|
data: result,
|
|
30
68
|
isLoading,
|
|
@@ -44,7 +82,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
44
82
|
onSuccess: () => {
|
|
45
83
|
qc.invalidateQueries({ queryKey });
|
|
46
84
|
setShowAddForm(false);
|
|
47
|
-
setAddForm(emptyForm());
|
|
85
|
+
setAddForm(emptyForm$1());
|
|
48
86
|
setMutationError(null);
|
|
49
87
|
},
|
|
50
88
|
onError: (err) => {
|
|
@@ -69,19 +107,62 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
69
107
|
unit: addForm.type === "number" && addForm.unit.trim() ? addForm.unit.trim() : null
|
|
70
108
|
});
|
|
71
109
|
};
|
|
72
|
-
const
|
|
110
|
+
const typeLabel2 = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
73
111
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
74
112
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
75
113
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Атрибуты" }),
|
|
76
|
-
!showAddForm && /* @__PURE__ */ jsxRuntime.
|
|
77
|
-
|
|
114
|
+
!showAddForm && !showPresetList && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
115
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
116
|
+
ui.Button,
|
|
117
|
+
{
|
|
118
|
+
variant: "secondary",
|
|
119
|
+
size: "small",
|
|
120
|
+
onClick: () => setShowPresetList(true),
|
|
121
|
+
children: "Из пресета"
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
125
|
+
ui.Button,
|
|
126
|
+
{
|
|
127
|
+
variant: "secondary",
|
|
128
|
+
size: "small",
|
|
129
|
+
onClick: () => setShowAddForm(true),
|
|
130
|
+
children: "+ Добавить"
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
] })
|
|
134
|
+
] }),
|
|
135
|
+
showPresetList && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-3", children: [
|
|
136
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Выберите пресет" }),
|
|
138
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
139
|
+
ui.Button,
|
|
140
|
+
{
|
|
141
|
+
size: "small",
|
|
142
|
+
variant: "secondary",
|
|
143
|
+
onClick: () => setShowPresetList(false),
|
|
144
|
+
children: "Закрыть"
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
] }),
|
|
148
|
+
presetsQuery.isLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }),
|
|
149
|
+
((_a = presetsQuery.data) == null ? void 0 : _a.attribute_presets.length) === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Пресетов нет. Создайте в настройках Product Attributes." }),
|
|
150
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: (_b = presetsQuery.data) == null ? void 0 : _b.attribute_presets.map((p) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
151
|
+
"button",
|
|
78
152
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
children:
|
|
83
|
-
|
|
84
|
-
|
|
153
|
+
onClick: () => applyPresetMutation.mutate(p.id),
|
|
154
|
+
disabled: applyPresetMutation.isPending,
|
|
155
|
+
className: "flex items-center justify-between rounded border border-ui-border-base px-3 py-2 text-left text-sm hover:bg-ui-bg-subtle disabled:opacity-50",
|
|
156
|
+
children: [
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: p.label }),
|
|
158
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
|
|
159
|
+
typeLabel2(p.type),
|
|
160
|
+
p.unit ? `, ${p.unit}` : ""
|
|
161
|
+
] })
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
p.id
|
|
165
|
+
)) })
|
|
85
166
|
] }),
|
|
86
167
|
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
87
168
|
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить атрибуты." }) }),
|
|
@@ -94,7 +175,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
94
175
|
children: [
|
|
95
176
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm text-ui-fg-subtle", children: attr.label }),
|
|
96
177
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
|
|
97
|
-
|
|
178
|
+
typeLabel2(attr.type),
|
|
98
179
|
attr.unit ? `, ${attr.unit}` : ""
|
|
99
180
|
] }),
|
|
100
181
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "blue", children: "из родителя" })
|
|
@@ -145,7 +226,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
145
226
|
children: [
|
|
146
227
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm text-ui-fg-base", children: attr.label }),
|
|
147
228
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
|
|
148
|
-
|
|
229
|
+
typeLabel2(attr.type),
|
|
149
230
|
attr.unit ? `, ${attr.unit}` : ""
|
|
150
231
|
] }),
|
|
151
232
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -217,7 +298,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
217
298
|
size: "small",
|
|
218
299
|
onClick: () => {
|
|
219
300
|
setShowAddForm(false);
|
|
220
|
-
setAddForm(emptyForm());
|
|
301
|
+
setAddForm(emptyForm$1());
|
|
221
302
|
setMutationError(null);
|
|
222
303
|
},
|
|
223
304
|
children: "Отмена"
|
|
@@ -235,12 +316,13 @@ const ProductAttributeValuesWidget = ({
|
|
|
235
316
|
}) => {
|
|
236
317
|
var _a, _b, _c;
|
|
237
318
|
const productId = data.id;
|
|
319
|
+
const productHandle = data.handle || productId;
|
|
238
320
|
const categoryId = ((_b = (_a = data.categories) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? null;
|
|
239
321
|
const qc = reactQuery.useQueryClient();
|
|
240
|
-
const [formValues, setFormValues] =
|
|
241
|
-
const [saveSuccess, setSaveSuccess] =
|
|
242
|
-
const [uploadingId, setUploadingId] =
|
|
243
|
-
const fileInputs =
|
|
322
|
+
const [formValues, setFormValues] = React.useState({});
|
|
323
|
+
const [saveSuccess, setSaveSuccess] = React.useState(false);
|
|
324
|
+
const [uploadingId, setUploadingId] = React.useState(null);
|
|
325
|
+
const fileInputs = React.useRef({});
|
|
244
326
|
const attributesQuery = reactQuery.useQuery({
|
|
245
327
|
queryKey: ["category-custom-attributes", categoryId],
|
|
246
328
|
queryFn: () => sdk.client.fetch(`/admin/category/${categoryId}/custom-attributes`),
|
|
@@ -250,7 +332,7 @@ const ProductAttributeValuesWidget = ({
|
|
|
250
332
|
queryKey: ["product-custom-attributes", productId],
|
|
251
333
|
queryFn: () => sdk.client.fetch(`/admin/product/${productId}/custom-attributes`)
|
|
252
334
|
});
|
|
253
|
-
|
|
335
|
+
React.useEffect(() => {
|
|
254
336
|
if (!attributesQuery.data || !valuesQuery.data) return;
|
|
255
337
|
const attributes2 = attributesQuery.data.category_custom_attributes;
|
|
256
338
|
const values = valuesQuery.data.product_custom_attributes;
|
|
@@ -285,12 +367,20 @@ const ProductAttributeValuesWidget = ({
|
|
|
285
367
|
const attributesToUpdate = attributes2.filter((attr) => formValues[attr.id] !== void 0).map((attr) => ({ id: attr.id, value: formValues[attr.id] ?? "" }));
|
|
286
368
|
saveMutation.mutate({ attributes: attributesToUpdate });
|
|
287
369
|
};
|
|
288
|
-
const handleFileUpload = async (
|
|
370
|
+
const handleFileUpload = async (attr, file) => {
|
|
289
371
|
var _a2, _b2;
|
|
372
|
+
const attrId = attr.id;
|
|
290
373
|
setUploadingId(attrId);
|
|
291
374
|
try {
|
|
375
|
+
const dot = file.name.lastIndexOf(".");
|
|
376
|
+
const ext = dot > -1 ? file.name.slice(dot) : "";
|
|
377
|
+
const renamed = new File(
|
|
378
|
+
[file],
|
|
379
|
+
`${productHandle}_${attr.key}${ext}`,
|
|
380
|
+
{ type: file.type }
|
|
381
|
+
);
|
|
292
382
|
const formData = new FormData();
|
|
293
|
-
formData.append("files",
|
|
383
|
+
formData.append("files", renamed);
|
|
294
384
|
const response = await fetch(`/admin/uploads`, {
|
|
295
385
|
method: "POST",
|
|
296
386
|
credentials: "include",
|
|
@@ -368,7 +458,7 @@ const ProductAttributeValuesWidget = ({
|
|
|
368
458
|
onChange: (e) => {
|
|
369
459
|
var _a2;
|
|
370
460
|
const f = (_a2 = e.target.files) == null ? void 0 : _a2[0];
|
|
371
|
-
if (f) handleFileUpload(attr
|
|
461
|
+
if (f) handleFileUpload(attr, f);
|
|
372
462
|
}
|
|
373
463
|
}
|
|
374
464
|
),
|
|
@@ -430,6 +520,269 @@ const ProductAttributeValuesWidget = ({
|
|
|
430
520
|
adminSdk.defineWidgetConfig({
|
|
431
521
|
zone: "product.details.after"
|
|
432
522
|
});
|
|
523
|
+
var __defProp = Object.defineProperty;
|
|
524
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
525
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
526
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
527
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
528
|
+
var __spreadValues = (a, b) => {
|
|
529
|
+
for (var prop in b || (b = {}))
|
|
530
|
+
if (__hasOwnProp.call(b, prop))
|
|
531
|
+
__defNormalProp(a, prop, b[prop]);
|
|
532
|
+
if (__getOwnPropSymbols)
|
|
533
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
534
|
+
if (__propIsEnum.call(b, prop))
|
|
535
|
+
__defNormalProp(a, prop, b[prop]);
|
|
536
|
+
}
|
|
537
|
+
return a;
|
|
538
|
+
};
|
|
539
|
+
var __objRest = (source, exclude) => {
|
|
540
|
+
var target = {};
|
|
541
|
+
for (var prop in source)
|
|
542
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
543
|
+
target[prop] = source[prop];
|
|
544
|
+
if (source != null && __getOwnPropSymbols)
|
|
545
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
546
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
547
|
+
target[prop] = source[prop];
|
|
548
|
+
}
|
|
549
|
+
return target;
|
|
550
|
+
};
|
|
551
|
+
const CogSixTooth = React__namespace.forwardRef(
|
|
552
|
+
(_a, ref) => {
|
|
553
|
+
var _b = _a, { color = "currentColor" } = _b, props = __objRest(_b, ["color"]);
|
|
554
|
+
return /* @__PURE__ */ React__namespace.createElement(
|
|
555
|
+
"svg",
|
|
556
|
+
__spreadValues({
|
|
557
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
558
|
+
width: 15,
|
|
559
|
+
height: 15,
|
|
560
|
+
viewBox: "0 0 15 15",
|
|
561
|
+
fill: "none",
|
|
562
|
+
ref
|
|
563
|
+
}, props),
|
|
564
|
+
/* @__PURE__ */ React__namespace.createElement(
|
|
565
|
+
"g",
|
|
566
|
+
{
|
|
567
|
+
stroke: color,
|
|
568
|
+
strokeLinecap: "round",
|
|
569
|
+
strokeLinejoin: "round",
|
|
570
|
+
strokeWidth: 1.5,
|
|
571
|
+
clipPath: "url(#a)"
|
|
572
|
+
},
|
|
573
|
+
/* @__PURE__ */ React__namespace.createElement("path", { d: "M7.5 9.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4" }),
|
|
574
|
+
/* @__PURE__ */ React__namespace.createElement("path", { d: "m12.989 5.97-.826-.292a5 5 0 0 0-.323-.685 5 5 0 0 0-.43-.621l.16-.86a1.43 1.43 0 0 0-.692-1.503l-.312-.18a1.43 1.43 0 0 0-1.647.152l-.663.566a5 5 0 0 0-1.513 0L6.08 1.98a1.43 1.43 0 0 0-1.647-.152l-.312.18a1.43 1.43 0 0 0-.691 1.503l.16.857c-.32.4-.574.841-.758 1.31l-.82.29a1.43 1.43 0 0 0-.956 1.35v.36c0 .608.383 1.15.955 1.35l.826.292c.09.232.194.462.323.684.128.222.275.427.43.622l-.16.86c-.111.597.166 1.2.691 1.503l.312.18a1.43 1.43 0 0 0 1.647-.152l.663-.567a5 5 0 0 0 1.512 0l.663.568a1.43 1.43 0 0 0 1.647.152l.312-.18c.526-.304.803-.906.691-1.502l-.16-.86c.32-.398.575-.84.757-1.308l.822-.29c.572-.202.956-.743.956-1.35v-.36c0-.608-.383-1.149-.956-1.35z" })
|
|
575
|
+
),
|
|
576
|
+
/* @__PURE__ */ React__namespace.createElement("defs", null, /* @__PURE__ */ React__namespace.createElement("clipPath", { id: "a" }, /* @__PURE__ */ React__namespace.createElement("path", { fill: "#fff", d: "M0 0h15v15H0z" })))
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
);
|
|
580
|
+
CogSixTooth.displayName = "CogSixTooth";
|
|
581
|
+
const emptyForm = () => ({
|
|
582
|
+
label: "",
|
|
583
|
+
type: "text",
|
|
584
|
+
unit: "",
|
|
585
|
+
description: ""
|
|
586
|
+
});
|
|
587
|
+
const typeLabel = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
588
|
+
const ProductAttributesSettingsPage = () => {
|
|
589
|
+
const qc = reactQuery.useQueryClient();
|
|
590
|
+
const queryKey = ["attribute-presets"];
|
|
591
|
+
const [showAddForm, setShowAddForm] = React.useState(false);
|
|
592
|
+
const [addForm, setAddForm] = React.useState(emptyForm());
|
|
593
|
+
const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
|
|
594
|
+
const { data, isLoading, isError } = reactQuery.useQuery({
|
|
595
|
+
queryKey,
|
|
596
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-presets`)
|
|
597
|
+
});
|
|
598
|
+
const presets = (data == null ? void 0 : data.attribute_presets) ?? [];
|
|
599
|
+
const createMutation = reactQuery.useMutation({
|
|
600
|
+
mutationFn: (body) => sdk.client.fetch(`/admin/attribute-presets`, {
|
|
601
|
+
method: "POST",
|
|
602
|
+
body: {
|
|
603
|
+
label: body.label,
|
|
604
|
+
type: body.type,
|
|
605
|
+
unit: body.type === "number" && body.unit ? body.unit : null,
|
|
606
|
+
description: body.description || null
|
|
607
|
+
}
|
|
608
|
+
}),
|
|
609
|
+
onSuccess: () => {
|
|
610
|
+
qc.invalidateQueries({ queryKey });
|
|
611
|
+
setShowAddForm(false);
|
|
612
|
+
setAddForm(emptyForm());
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
const deleteMutation = reactQuery.useMutation({
|
|
616
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/attribute-presets`, {
|
|
617
|
+
method: "PATCH",
|
|
618
|
+
body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
619
|
+
}),
|
|
620
|
+
onSuccess: () => {
|
|
621
|
+
qc.invalidateQueries({ queryKey });
|
|
622
|
+
setConfirmDeleteId(null);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
const handleAdd = () => {
|
|
626
|
+
if (!addForm.label.trim()) return;
|
|
627
|
+
createMutation.mutate({
|
|
628
|
+
...addForm,
|
|
629
|
+
label: addForm.label.trim(),
|
|
630
|
+
unit: addForm.unit.trim(),
|
|
631
|
+
description: addForm.description.trim()
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
635
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
636
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
637
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Пресеты атрибутов" }),
|
|
638
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Глобальные шаблоны атрибутов, которые можно применить к любой категории." })
|
|
639
|
+
] }),
|
|
640
|
+
!showAddForm && /* @__PURE__ */ jsxRuntime.jsx(
|
|
641
|
+
ui.Button,
|
|
642
|
+
{
|
|
643
|
+
variant: "secondary",
|
|
644
|
+
size: "small",
|
|
645
|
+
onClick: () => setShowAddForm(true),
|
|
646
|
+
children: "+ Добавить пресет"
|
|
647
|
+
}
|
|
648
|
+
)
|
|
649
|
+
] }),
|
|
650
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
651
|
+
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить пресеты." }) }),
|
|
652
|
+
!isLoading && !isError && presets.length === 0 && !showAddForm && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Пресетов пока нет. Добавьте первый." }) }),
|
|
653
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: presets.map(
|
|
654
|
+
(p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
655
|
+
"div",
|
|
656
|
+
{
|
|
657
|
+
className: "flex items-center gap-3 px-6 py-3 text-sm",
|
|
658
|
+
children: [
|
|
659
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
|
|
660
|
+
"Удалить «",
|
|
661
|
+
p.label,
|
|
662
|
+
"»?"
|
|
663
|
+
] }),
|
|
664
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
665
|
+
ui.Button,
|
|
666
|
+
{
|
|
667
|
+
size: "small",
|
|
668
|
+
variant: "danger",
|
|
669
|
+
onClick: () => deleteMutation.mutate(p.id),
|
|
670
|
+
isLoading: deleteMutation.isPending,
|
|
671
|
+
children: "Удалить"
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
675
|
+
ui.Button,
|
|
676
|
+
{
|
|
677
|
+
size: "small",
|
|
678
|
+
variant: "secondary",
|
|
679
|
+
onClick: () => setConfirmDeleteId(null),
|
|
680
|
+
children: "Отмена"
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
]
|
|
684
|
+
},
|
|
685
|
+
p.id
|
|
686
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
687
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
688
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
689
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: p.label }),
|
|
690
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
|
|
691
|
+
typeLabel(p.type),
|
|
692
|
+
p.unit ? `, ${p.unit}` : ""
|
|
693
|
+
] })
|
|
694
|
+
] }),
|
|
695
|
+
p.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
|
|
696
|
+
] }),
|
|
697
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
698
|
+
"button",
|
|
699
|
+
{
|
|
700
|
+
onClick: () => setConfirmDeleteId(p.id),
|
|
701
|
+
className: "text-xs text-ui-fg-error hover:underline",
|
|
702
|
+
children: "Удалить"
|
|
703
|
+
}
|
|
704
|
+
)
|
|
705
|
+
] }, p.id)
|
|
706
|
+
) }),
|
|
707
|
+
showAddForm && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
|
|
708
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
709
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
710
|
+
ui.Input,
|
|
711
|
+
{
|
|
712
|
+
value: addForm.label,
|
|
713
|
+
onChange: (e) => setAddForm((f) => ({ ...f, label: e.target.value })),
|
|
714
|
+
placeholder: "Название (например, Сертификат)",
|
|
715
|
+
className: "flex-1 h-8 text-sm",
|
|
716
|
+
autoFocus: true
|
|
717
|
+
}
|
|
718
|
+
),
|
|
719
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
720
|
+
"select",
|
|
721
|
+
{
|
|
722
|
+
value: addForm.type,
|
|
723
|
+
onChange: (e) => setAddForm((f) => ({
|
|
724
|
+
...f,
|
|
725
|
+
type: e.target.value,
|
|
726
|
+
unit: e.target.value === "number" ? f.unit : ""
|
|
727
|
+
})),
|
|
728
|
+
className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
|
|
729
|
+
children: [
|
|
730
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: "Текст" }),
|
|
731
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: "Число" }),
|
|
732
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: "Файл" }),
|
|
733
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
734
|
+
]
|
|
735
|
+
}
|
|
736
|
+
),
|
|
737
|
+
addForm.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
738
|
+
ui.Input,
|
|
739
|
+
{
|
|
740
|
+
value: addForm.unit,
|
|
741
|
+
onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })),
|
|
742
|
+
placeholder: "ед. (кг, м...)",
|
|
743
|
+
className: "w-28 h-8 text-sm"
|
|
744
|
+
}
|
|
745
|
+
)
|
|
746
|
+
] }),
|
|
747
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
748
|
+
ui.Input,
|
|
749
|
+
{
|
|
750
|
+
value: addForm.description,
|
|
751
|
+
onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })),
|
|
752
|
+
placeholder: "Описание (необязательно)",
|
|
753
|
+
className: "h-8 text-sm"
|
|
754
|
+
}
|
|
755
|
+
),
|
|
756
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
757
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
758
|
+
ui.Button,
|
|
759
|
+
{
|
|
760
|
+
variant: "secondary",
|
|
761
|
+
size: "small",
|
|
762
|
+
onClick: () => {
|
|
763
|
+
setShowAddForm(false);
|
|
764
|
+
setAddForm(emptyForm());
|
|
765
|
+
},
|
|
766
|
+
children: "Отмена"
|
|
767
|
+
}
|
|
768
|
+
),
|
|
769
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
770
|
+
ui.Button,
|
|
771
|
+
{
|
|
772
|
+
size: "small",
|
|
773
|
+
onClick: handleAdd,
|
|
774
|
+
isLoading: createMutation.isPending,
|
|
775
|
+
children: "Создать"
|
|
776
|
+
}
|
|
777
|
+
)
|
|
778
|
+
] })
|
|
779
|
+
] })
|
|
780
|
+
] });
|
|
781
|
+
};
|
|
782
|
+
const config = adminSdk.defineRouteConfig({
|
|
783
|
+
label: "Product Attributes",
|
|
784
|
+
icon: CogSixTooth
|
|
785
|
+
});
|
|
433
786
|
const widgetModule = { widgets: [
|
|
434
787
|
{
|
|
435
788
|
Component: CategoryAttributeTemplatesWidget,
|
|
@@ -441,10 +794,24 @@ const widgetModule = { widgets: [
|
|
|
441
794
|
}
|
|
442
795
|
] };
|
|
443
796
|
const routeModule = {
|
|
444
|
-
routes: [
|
|
797
|
+
routes: [
|
|
798
|
+
{
|
|
799
|
+
Component: ProductAttributesSettingsPage,
|
|
800
|
+
path: "/settings/product-attributes"
|
|
801
|
+
}
|
|
802
|
+
]
|
|
445
803
|
};
|
|
446
804
|
const menuItemModule = {
|
|
447
|
-
menuItems: [
|
|
805
|
+
menuItems: [
|
|
806
|
+
{
|
|
807
|
+
label: config.label,
|
|
808
|
+
icon: config.icon,
|
|
809
|
+
path: "/settings/product-attributes",
|
|
810
|
+
nested: void 0,
|
|
811
|
+
rank: void 0,
|
|
812
|
+
translationNs: void 0
|
|
813
|
+
}
|
|
814
|
+
]
|
|
448
815
|
};
|
|
449
816
|
const formModule = { customFields: {} };
|
|
450
817
|
const displayModule = {
|