@empty-complete-org/medusa-product-attributes 0.13.0 → 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 +384 -26
- package/.medusa/server/src/admin/index.mjs +359 -18
- 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 +36 -0
- package/.medusa/server/src/modules/product-attributes/service.js.map +1 -1
- package/package.json +1 -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: "Отмена"
|
|
@@ -238,10 +319,10 @@ const ProductAttributeValuesWidget = ({
|
|
|
238
319
|
const productHandle = data.handle || productId;
|
|
239
320
|
const categoryId = ((_b = (_a = data.categories) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? null;
|
|
240
321
|
const qc = reactQuery.useQueryClient();
|
|
241
|
-
const [formValues, setFormValues] =
|
|
242
|
-
const [saveSuccess, setSaveSuccess] =
|
|
243
|
-
const [uploadingId, setUploadingId] =
|
|
244
|
-
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({});
|
|
245
326
|
const attributesQuery = reactQuery.useQuery({
|
|
246
327
|
queryKey: ["category-custom-attributes", categoryId],
|
|
247
328
|
queryFn: () => sdk.client.fetch(`/admin/category/${categoryId}/custom-attributes`),
|
|
@@ -251,7 +332,7 @@ const ProductAttributeValuesWidget = ({
|
|
|
251
332
|
queryKey: ["product-custom-attributes", productId],
|
|
252
333
|
queryFn: () => sdk.client.fetch(`/admin/product/${productId}/custom-attributes`)
|
|
253
334
|
});
|
|
254
|
-
|
|
335
|
+
React.useEffect(() => {
|
|
255
336
|
if (!attributesQuery.data || !valuesQuery.data) return;
|
|
256
337
|
const attributes2 = attributesQuery.data.category_custom_attributes;
|
|
257
338
|
const values = valuesQuery.data.product_custom_attributes;
|
|
@@ -439,6 +520,269 @@ const ProductAttributeValuesWidget = ({
|
|
|
439
520
|
adminSdk.defineWidgetConfig({
|
|
440
521
|
zone: "product.details.after"
|
|
441
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
|
+
});
|
|
442
786
|
const widgetModule = { widgets: [
|
|
443
787
|
{
|
|
444
788
|
Component: CategoryAttributeTemplatesWidget,
|
|
@@ -450,10 +794,24 @@ const widgetModule = { widgets: [
|
|
|
450
794
|
}
|
|
451
795
|
] };
|
|
452
796
|
const routeModule = {
|
|
453
|
-
routes: [
|
|
797
|
+
routes: [
|
|
798
|
+
{
|
|
799
|
+
Component: ProductAttributesSettingsPage,
|
|
800
|
+
path: "/settings/product-attributes"
|
|
801
|
+
}
|
|
802
|
+
]
|
|
454
803
|
};
|
|
455
804
|
const menuItemModule = {
|
|
456
|
-
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
|
+
]
|
|
457
815
|
};
|
|
458
816
|
const formModule = { customFields: {} };
|
|
459
817
|
const displayModule = {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { defineWidgetConfig } from "@medusajs/admin-sdk";
|
|
2
|
+
import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
3
|
import { Container, Heading, Button, Text, Badge, Input } from "@medusajs/ui";
|
|
4
4
|
import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query";
|
|
5
|
+
import * as React from "react";
|
|
5
6
|
import { useState, useRef, useEffect } from "react";
|
|
6
7
|
import Medusa from "@medusajs/js-sdk";
|
|
7
8
|
const sdk = new Medusa({
|
|
@@ -11,17 +12,37 @@ const sdk = new Medusa({
|
|
|
11
12
|
type: "session"
|
|
12
13
|
}
|
|
13
14
|
});
|
|
14
|
-
const emptyForm = () => ({ label: "", type: "text", unit: "" });
|
|
15
|
+
const emptyForm$1 = () => ({ label: "", type: "text", unit: "" });
|
|
15
16
|
const CategoryAttributeTemplatesWidget = ({
|
|
16
17
|
data
|
|
17
18
|
}) => {
|
|
19
|
+
var _a, _b;
|
|
18
20
|
const categoryId = data.id;
|
|
19
21
|
const qc = useQueryClient();
|
|
20
22
|
const queryKey = ["category-custom-attributes", categoryId];
|
|
21
23
|
const [showAddForm, setShowAddForm] = useState(false);
|
|
22
|
-
const [
|
|
24
|
+
const [showPresetList, setShowPresetList] = useState(false);
|
|
25
|
+
const [addForm, setAddForm] = useState(emptyForm$1());
|
|
23
26
|
const [confirmDeleteId, setConfirmDeleteId] = useState(null);
|
|
24
27
|
const [mutationError, setMutationError] = useState(null);
|
|
28
|
+
const presetsQuery = useQuery({
|
|
29
|
+
queryKey: ["attribute-presets"],
|
|
30
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-presets`),
|
|
31
|
+
enabled: showPresetList
|
|
32
|
+
});
|
|
33
|
+
const applyPresetMutation = useMutation({
|
|
34
|
+
mutationFn: (presetId) => sdk.client.fetch(`/admin/attribute-presets/${presetId}/apply`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
body: { category_id: categoryId }
|
|
37
|
+
}),
|
|
38
|
+
onSuccess: () => {
|
|
39
|
+
qc.invalidateQueries({ queryKey });
|
|
40
|
+
setShowPresetList(false);
|
|
41
|
+
},
|
|
42
|
+
onError: (err) => {
|
|
43
|
+
setMutationError((err == null ? void 0 : err.message) || "Ошибка при применении пресета");
|
|
44
|
+
}
|
|
45
|
+
});
|
|
25
46
|
const {
|
|
26
47
|
data: result,
|
|
27
48
|
isLoading,
|
|
@@ -41,7 +62,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
41
62
|
onSuccess: () => {
|
|
42
63
|
qc.invalidateQueries({ queryKey });
|
|
43
64
|
setShowAddForm(false);
|
|
44
|
-
setAddForm(emptyForm());
|
|
65
|
+
setAddForm(emptyForm$1());
|
|
45
66
|
setMutationError(null);
|
|
46
67
|
},
|
|
47
68
|
onError: (err) => {
|
|
@@ -66,19 +87,62 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
66
87
|
unit: addForm.type === "number" && addForm.unit.trim() ? addForm.unit.trim() : null
|
|
67
88
|
});
|
|
68
89
|
};
|
|
69
|
-
const
|
|
90
|
+
const typeLabel2 = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
70
91
|
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
71
92
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
72
93
|
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Атрибуты" }),
|
|
73
|
-
!showAddForm && /* @__PURE__ */
|
|
74
|
-
|
|
94
|
+
!showAddForm && !showPresetList && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
95
|
+
/* @__PURE__ */ jsx(
|
|
96
|
+
Button,
|
|
97
|
+
{
|
|
98
|
+
variant: "secondary",
|
|
99
|
+
size: "small",
|
|
100
|
+
onClick: () => setShowPresetList(true),
|
|
101
|
+
children: "Из пресета"
|
|
102
|
+
}
|
|
103
|
+
),
|
|
104
|
+
/* @__PURE__ */ jsx(
|
|
105
|
+
Button,
|
|
106
|
+
{
|
|
107
|
+
variant: "secondary",
|
|
108
|
+
size: "small",
|
|
109
|
+
onClick: () => setShowAddForm(true),
|
|
110
|
+
children: "+ Добавить"
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
] })
|
|
114
|
+
] }),
|
|
115
|
+
showPresetList && /* @__PURE__ */ jsxs("div", { className: "px-6 py-3", children: [
|
|
116
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
117
|
+
/* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Выберите пресет" }),
|
|
118
|
+
/* @__PURE__ */ jsx(
|
|
119
|
+
Button,
|
|
120
|
+
{
|
|
121
|
+
size: "small",
|
|
122
|
+
variant: "secondary",
|
|
123
|
+
onClick: () => setShowPresetList(false),
|
|
124
|
+
children: "Закрыть"
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
] }),
|
|
128
|
+
presetsQuery.isLoading && /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }),
|
|
129
|
+
((_a = presetsQuery.data) == null ? void 0 : _a.attribute_presets.length) === 0 && /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Пресетов нет. Создайте в настройках Product Attributes." }),
|
|
130
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: (_b = presetsQuery.data) == null ? void 0 : _b.attribute_presets.map((p) => /* @__PURE__ */ jsxs(
|
|
131
|
+
"button",
|
|
75
132
|
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
children:
|
|
80
|
-
|
|
81
|
-
|
|
133
|
+
onClick: () => applyPresetMutation.mutate(p.id),
|
|
134
|
+
disabled: applyPresetMutation.isPending,
|
|
135
|
+
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",
|
|
136
|
+
children: [
|
|
137
|
+
/* @__PURE__ */ jsx("span", { children: p.label }),
|
|
138
|
+
/* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
|
|
139
|
+
typeLabel2(p.type),
|
|
140
|
+
p.unit ? `, ${p.unit}` : ""
|
|
141
|
+
] })
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
p.id
|
|
145
|
+
)) })
|
|
82
146
|
] }),
|
|
83
147
|
isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
84
148
|
isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить атрибуты." }) }),
|
|
@@ -91,7 +155,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
91
155
|
children: [
|
|
92
156
|
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm text-ui-fg-subtle", children: attr.label }),
|
|
93
157
|
/* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
|
|
94
|
-
|
|
158
|
+
typeLabel2(attr.type),
|
|
95
159
|
attr.unit ? `, ${attr.unit}` : ""
|
|
96
160
|
] }),
|
|
97
161
|
/* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "blue", children: "из родителя" })
|
|
@@ -142,7 +206,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
142
206
|
children: [
|
|
143
207
|
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm text-ui-fg-base", children: attr.label }),
|
|
144
208
|
/* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
|
|
145
|
-
|
|
209
|
+
typeLabel2(attr.type),
|
|
146
210
|
attr.unit ? `, ${attr.unit}` : ""
|
|
147
211
|
] }),
|
|
148
212
|
/* @__PURE__ */ jsx(
|
|
@@ -214,7 +278,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
214
278
|
size: "small",
|
|
215
279
|
onClick: () => {
|
|
216
280
|
setShowAddForm(false);
|
|
217
|
-
setAddForm(emptyForm());
|
|
281
|
+
setAddForm(emptyForm$1());
|
|
218
282
|
setMutationError(null);
|
|
219
283
|
},
|
|
220
284
|
children: "Отмена"
|
|
@@ -436,6 +500,269 @@ const ProductAttributeValuesWidget = ({
|
|
|
436
500
|
defineWidgetConfig({
|
|
437
501
|
zone: "product.details.after"
|
|
438
502
|
});
|
|
503
|
+
var __defProp = Object.defineProperty;
|
|
504
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
505
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
506
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
507
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
508
|
+
var __spreadValues = (a, b) => {
|
|
509
|
+
for (var prop in b || (b = {}))
|
|
510
|
+
if (__hasOwnProp.call(b, prop))
|
|
511
|
+
__defNormalProp(a, prop, b[prop]);
|
|
512
|
+
if (__getOwnPropSymbols)
|
|
513
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
514
|
+
if (__propIsEnum.call(b, prop))
|
|
515
|
+
__defNormalProp(a, prop, b[prop]);
|
|
516
|
+
}
|
|
517
|
+
return a;
|
|
518
|
+
};
|
|
519
|
+
var __objRest = (source, exclude) => {
|
|
520
|
+
var target = {};
|
|
521
|
+
for (var prop in source)
|
|
522
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
523
|
+
target[prop] = source[prop];
|
|
524
|
+
if (source != null && __getOwnPropSymbols)
|
|
525
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
526
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
527
|
+
target[prop] = source[prop];
|
|
528
|
+
}
|
|
529
|
+
return target;
|
|
530
|
+
};
|
|
531
|
+
const CogSixTooth = React.forwardRef(
|
|
532
|
+
(_a, ref) => {
|
|
533
|
+
var _b = _a, { color = "currentColor" } = _b, props = __objRest(_b, ["color"]);
|
|
534
|
+
return /* @__PURE__ */ React.createElement(
|
|
535
|
+
"svg",
|
|
536
|
+
__spreadValues({
|
|
537
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
538
|
+
width: 15,
|
|
539
|
+
height: 15,
|
|
540
|
+
viewBox: "0 0 15 15",
|
|
541
|
+
fill: "none",
|
|
542
|
+
ref
|
|
543
|
+
}, props),
|
|
544
|
+
/* @__PURE__ */ React.createElement(
|
|
545
|
+
"g",
|
|
546
|
+
{
|
|
547
|
+
stroke: color,
|
|
548
|
+
strokeLinecap: "round",
|
|
549
|
+
strokeLinejoin: "round",
|
|
550
|
+
strokeWidth: 1.5,
|
|
551
|
+
clipPath: "url(#a)"
|
|
552
|
+
},
|
|
553
|
+
/* @__PURE__ */ React.createElement("path", { d: "M7.5 9.5a2 2 0 1 0 0-4 2 2 0 0 0 0 4" }),
|
|
554
|
+
/* @__PURE__ */ React.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" })
|
|
555
|
+
),
|
|
556
|
+
/* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("clipPath", { id: "a" }, /* @__PURE__ */ React.createElement("path", { fill: "#fff", d: "M0 0h15v15H0z" })))
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
);
|
|
560
|
+
CogSixTooth.displayName = "CogSixTooth";
|
|
561
|
+
const emptyForm = () => ({
|
|
562
|
+
label: "",
|
|
563
|
+
type: "text",
|
|
564
|
+
unit: "",
|
|
565
|
+
description: ""
|
|
566
|
+
});
|
|
567
|
+
const typeLabel = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
568
|
+
const ProductAttributesSettingsPage = () => {
|
|
569
|
+
const qc = useQueryClient();
|
|
570
|
+
const queryKey = ["attribute-presets"];
|
|
571
|
+
const [showAddForm, setShowAddForm] = useState(false);
|
|
572
|
+
const [addForm, setAddForm] = useState(emptyForm());
|
|
573
|
+
const [confirmDeleteId, setConfirmDeleteId] = useState(null);
|
|
574
|
+
const { data, isLoading, isError } = useQuery({
|
|
575
|
+
queryKey,
|
|
576
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-presets`)
|
|
577
|
+
});
|
|
578
|
+
const presets = (data == null ? void 0 : data.attribute_presets) ?? [];
|
|
579
|
+
const createMutation = useMutation({
|
|
580
|
+
mutationFn: (body) => sdk.client.fetch(`/admin/attribute-presets`, {
|
|
581
|
+
method: "POST",
|
|
582
|
+
body: {
|
|
583
|
+
label: body.label,
|
|
584
|
+
type: body.type,
|
|
585
|
+
unit: body.type === "number" && body.unit ? body.unit : null,
|
|
586
|
+
description: body.description || null
|
|
587
|
+
}
|
|
588
|
+
}),
|
|
589
|
+
onSuccess: () => {
|
|
590
|
+
qc.invalidateQueries({ queryKey });
|
|
591
|
+
setShowAddForm(false);
|
|
592
|
+
setAddForm(emptyForm());
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
const deleteMutation = useMutation({
|
|
596
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/attribute-presets`, {
|
|
597
|
+
method: "PATCH",
|
|
598
|
+
body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
599
|
+
}),
|
|
600
|
+
onSuccess: () => {
|
|
601
|
+
qc.invalidateQueries({ queryKey });
|
|
602
|
+
setConfirmDeleteId(null);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
const handleAdd = () => {
|
|
606
|
+
if (!addForm.label.trim()) return;
|
|
607
|
+
createMutation.mutate({
|
|
608
|
+
...addForm,
|
|
609
|
+
label: addForm.label.trim(),
|
|
610
|
+
unit: addForm.unit.trim(),
|
|
611
|
+
description: addForm.description.trim()
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
615
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
616
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
617
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "Пресеты атрибутов" }),
|
|
618
|
+
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Глобальные шаблоны атрибутов, которые можно применить к любой категории." })
|
|
619
|
+
] }),
|
|
620
|
+
!showAddForm && /* @__PURE__ */ jsx(
|
|
621
|
+
Button,
|
|
622
|
+
{
|
|
623
|
+
variant: "secondary",
|
|
624
|
+
size: "small",
|
|
625
|
+
onClick: () => setShowAddForm(true),
|
|
626
|
+
children: "+ Добавить пресет"
|
|
627
|
+
}
|
|
628
|
+
)
|
|
629
|
+
] }),
|
|
630
|
+
isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
631
|
+
isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить пресеты." }) }),
|
|
632
|
+
!isLoading && !isError && presets.length === 0 && !showAddForm && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Пресетов пока нет. Добавьте первый." }) }),
|
|
633
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y", children: presets.map(
|
|
634
|
+
(p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxs(
|
|
635
|
+
"div",
|
|
636
|
+
{
|
|
637
|
+
className: "flex items-center gap-3 px-6 py-3 text-sm",
|
|
638
|
+
children: [
|
|
639
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
|
|
640
|
+
"Удалить «",
|
|
641
|
+
p.label,
|
|
642
|
+
"»?"
|
|
643
|
+
] }),
|
|
644
|
+
/* @__PURE__ */ jsx(
|
|
645
|
+
Button,
|
|
646
|
+
{
|
|
647
|
+
size: "small",
|
|
648
|
+
variant: "danger",
|
|
649
|
+
onClick: () => deleteMutation.mutate(p.id),
|
|
650
|
+
isLoading: deleteMutation.isPending,
|
|
651
|
+
children: "Удалить"
|
|
652
|
+
}
|
|
653
|
+
),
|
|
654
|
+
/* @__PURE__ */ jsx(
|
|
655
|
+
Button,
|
|
656
|
+
{
|
|
657
|
+
size: "small",
|
|
658
|
+
variant: "secondary",
|
|
659
|
+
onClick: () => setConfirmDeleteId(null),
|
|
660
|
+
children: "Отмена"
|
|
661
|
+
}
|
|
662
|
+
)
|
|
663
|
+
]
|
|
664
|
+
},
|
|
665
|
+
p.id
|
|
666
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
667
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
668
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
669
|
+
/* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: p.label }),
|
|
670
|
+
/* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
|
|
671
|
+
typeLabel(p.type),
|
|
672
|
+
p.unit ? `, ${p.unit}` : ""
|
|
673
|
+
] })
|
|
674
|
+
] }),
|
|
675
|
+
p.description && /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
|
|
676
|
+
] }),
|
|
677
|
+
/* @__PURE__ */ jsx(
|
|
678
|
+
"button",
|
|
679
|
+
{
|
|
680
|
+
onClick: () => setConfirmDeleteId(p.id),
|
|
681
|
+
className: "text-xs text-ui-fg-error hover:underline",
|
|
682
|
+
children: "Удалить"
|
|
683
|
+
}
|
|
684
|
+
)
|
|
685
|
+
] }, p.id)
|
|
686
|
+
) }),
|
|
687
|
+
showAddForm && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
|
|
688
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
689
|
+
/* @__PURE__ */ jsx(
|
|
690
|
+
Input,
|
|
691
|
+
{
|
|
692
|
+
value: addForm.label,
|
|
693
|
+
onChange: (e) => setAddForm((f) => ({ ...f, label: e.target.value })),
|
|
694
|
+
placeholder: "Название (например, Сертификат)",
|
|
695
|
+
className: "flex-1 h-8 text-sm",
|
|
696
|
+
autoFocus: true
|
|
697
|
+
}
|
|
698
|
+
),
|
|
699
|
+
/* @__PURE__ */ jsxs(
|
|
700
|
+
"select",
|
|
701
|
+
{
|
|
702
|
+
value: addForm.type,
|
|
703
|
+
onChange: (e) => setAddForm((f) => ({
|
|
704
|
+
...f,
|
|
705
|
+
type: e.target.value,
|
|
706
|
+
unit: e.target.value === "number" ? f.unit : ""
|
|
707
|
+
})),
|
|
708
|
+
className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
|
|
709
|
+
children: [
|
|
710
|
+
/* @__PURE__ */ jsx("option", { value: "text", children: "Текст" }),
|
|
711
|
+
/* @__PURE__ */ jsx("option", { value: "number", children: "Число" }),
|
|
712
|
+
/* @__PURE__ */ jsx("option", { value: "file", children: "Файл" }),
|
|
713
|
+
/* @__PURE__ */ jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
),
|
|
717
|
+
addForm.type === "number" && /* @__PURE__ */ jsx(
|
|
718
|
+
Input,
|
|
719
|
+
{
|
|
720
|
+
value: addForm.unit,
|
|
721
|
+
onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })),
|
|
722
|
+
placeholder: "ед. (кг, м...)",
|
|
723
|
+
className: "w-28 h-8 text-sm"
|
|
724
|
+
}
|
|
725
|
+
)
|
|
726
|
+
] }),
|
|
727
|
+
/* @__PURE__ */ jsx(
|
|
728
|
+
Input,
|
|
729
|
+
{
|
|
730
|
+
value: addForm.description,
|
|
731
|
+
onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })),
|
|
732
|
+
placeholder: "Описание (необязательно)",
|
|
733
|
+
className: "h-8 text-sm"
|
|
734
|
+
}
|
|
735
|
+
),
|
|
736
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
737
|
+
/* @__PURE__ */ jsx(
|
|
738
|
+
Button,
|
|
739
|
+
{
|
|
740
|
+
variant: "secondary",
|
|
741
|
+
size: "small",
|
|
742
|
+
onClick: () => {
|
|
743
|
+
setShowAddForm(false);
|
|
744
|
+
setAddForm(emptyForm());
|
|
745
|
+
},
|
|
746
|
+
children: "Отмена"
|
|
747
|
+
}
|
|
748
|
+
),
|
|
749
|
+
/* @__PURE__ */ jsx(
|
|
750
|
+
Button,
|
|
751
|
+
{
|
|
752
|
+
size: "small",
|
|
753
|
+
onClick: handleAdd,
|
|
754
|
+
isLoading: createMutation.isPending,
|
|
755
|
+
children: "Создать"
|
|
756
|
+
}
|
|
757
|
+
)
|
|
758
|
+
] })
|
|
759
|
+
] })
|
|
760
|
+
] });
|
|
761
|
+
};
|
|
762
|
+
const config = defineRouteConfig({
|
|
763
|
+
label: "Product Attributes",
|
|
764
|
+
icon: CogSixTooth
|
|
765
|
+
});
|
|
439
766
|
const widgetModule = { widgets: [
|
|
440
767
|
{
|
|
441
768
|
Component: CategoryAttributeTemplatesWidget,
|
|
@@ -447,10 +774,24 @@ const widgetModule = { widgets: [
|
|
|
447
774
|
}
|
|
448
775
|
] };
|
|
449
776
|
const routeModule = {
|
|
450
|
-
routes: [
|
|
777
|
+
routes: [
|
|
778
|
+
{
|
|
779
|
+
Component: ProductAttributesSettingsPage,
|
|
780
|
+
path: "/settings/product-attributes"
|
|
781
|
+
}
|
|
782
|
+
]
|
|
451
783
|
};
|
|
452
784
|
const menuItemModule = {
|
|
453
|
-
menuItems: [
|
|
785
|
+
menuItems: [
|
|
786
|
+
{
|
|
787
|
+
label: config.label,
|
|
788
|
+
icon: config.icon,
|
|
789
|
+
path: "/settings/product-attributes",
|
|
790
|
+
nested: void 0,
|
|
791
|
+
rank: void 0,
|
|
792
|
+
translationNs: void 0
|
|
793
|
+
}
|
|
794
|
+
]
|
|
454
795
|
};
|
|
455
796
|
const formModule = { customFields: {} };
|
|
456
797
|
const displayModule = {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POST = POST;
|
|
4
|
+
const product_attributes_1 = require("../../../../../modules/product-attributes");
|
|
5
|
+
async function POST(req, res) {
|
|
6
|
+
const { id } = req.params;
|
|
7
|
+
const { category_id } = req.body;
|
|
8
|
+
const service = req.scope.resolve(product_attributes_1.CUSTOM_ATTRIBUTE_MODULE);
|
|
9
|
+
const category_custom_attribute = await service.applyPresetToCategory(id, category_id);
|
|
10
|
+
res.status(201).json({ category_custom_attribute });
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../../../../../src/api/admin/attribute-presets/[id]/apply/route.ts"],"names":[],"mappings":";;AAIA,oBAMC;AATD,kFAAmF;AAG5E,KAAK,UAAU,IAAI,CAAC,GAAkB,EAAE,GAAmB;IAChE,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAA;IACzB,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,IAA+B,CAAA;IAC3D,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,yBAAyB,GAAG,MAAM,OAAO,CAAC,qBAAqB,CAAC,EAAE,EAAE,WAAW,CAAC,CAAA;IACtF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,yBAAyB,EAAE,CAAC,CAAA;AACrD,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
|
|
2
|
+
export declare function GET(req: MedusaRequest, res: MedusaResponse): Promise<void>;
|
|
3
|
+
export declare function POST(req: MedusaRequest, res: MedusaResponse): Promise<void>;
|
|
4
|
+
export declare function PATCH(req: MedusaRequest, res: MedusaResponse): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GET = GET;
|
|
4
|
+
exports.POST = POST;
|
|
5
|
+
exports.PATCH = PATCH;
|
|
6
|
+
const product_attributes_1 = require("../../../modules/product-attributes");
|
|
7
|
+
async function GET(req, res) {
|
|
8
|
+
const service = req.scope.resolve(product_attributes_1.CUSTOM_ATTRIBUTE_MODULE);
|
|
9
|
+
const attribute_presets = await service.listPresets();
|
|
10
|
+
res.json({ attribute_presets });
|
|
11
|
+
}
|
|
12
|
+
async function POST(req, res) {
|
|
13
|
+
const service = req.scope.resolve(product_attributes_1.CUSTOM_ATTRIBUTE_MODULE);
|
|
14
|
+
const body = req.body;
|
|
15
|
+
const attribute_preset = await service.createPreset(body);
|
|
16
|
+
res.status(201).json({ attribute_preset });
|
|
17
|
+
}
|
|
18
|
+
async function PATCH(req, res) {
|
|
19
|
+
const service = req.scope.resolve(product_attributes_1.CUSTOM_ATTRIBUTE_MODULE);
|
|
20
|
+
const { id, ...data } = req.body;
|
|
21
|
+
const attribute_preset = await service.updatePreset(id, data);
|
|
22
|
+
res.json({ attribute_preset });
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../../../src/api/admin/attribute-presets/route.ts"],"names":[],"mappings":";;AAIA,kBAIC;AAED,oBAUC;AAED,sBAYC;AAjCD,4EAA6E;AAGtE,KAAK,UAAU,GAAG,CAAC,GAAkB,EAAE,GAAmB;IAC/D,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAA;IACrD,GAAG,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAA;AACjC,CAAC;AAEM,KAAK,UAAU,IAAI,CAAC,GAAkB,EAAE,GAAmB;IAChE,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,GAAG,CAAC,IAKhB,CAAA;IACD,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;IACzD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAA;AAC5C,CAAC;AAEM,KAAK,UAAU,KAAK,CAAC,GAAkB,EAAE,GAAmB;IACjE,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC,IAO3B,CAAA;IACD,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAC7D,GAAG,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAA;AAChC,CAAC"}
|
|
@@ -30,6 +30,20 @@ declare const _default: import("@medusajs/types").ModuleExports<typeof CustomAtt
|
|
|
30
30
|
primaryKey: "id";
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
|
+
attributePreset: {
|
|
34
|
+
id: {
|
|
35
|
+
serviceName: "customAttributeModule";
|
|
36
|
+
field: "attributePreset";
|
|
37
|
+
linkable: "attribute_preset_id";
|
|
38
|
+
primaryKey: "id";
|
|
39
|
+
};
|
|
40
|
+
toJSON: () => {
|
|
41
|
+
serviceName: "customAttributeModule";
|
|
42
|
+
field: "attributePreset";
|
|
43
|
+
linkable: "attribute_preset_id";
|
|
44
|
+
primaryKey: "id";
|
|
45
|
+
};
|
|
46
|
+
};
|
|
33
47
|
};
|
|
34
48
|
};
|
|
35
49
|
export default _default;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20260406120000 = void 0;
|
|
4
|
+
const migrations_1 = require("@mikro-orm/migrations");
|
|
5
|
+
class Migration20260406120000 extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS "attribute_preset" (
|
|
9
|
+
"id" text NOT NULL,
|
|
10
|
+
"key" text NOT NULL,
|
|
11
|
+
"label" text NOT NULL,
|
|
12
|
+
"type" text NOT NULL DEFAULT 'text',
|
|
13
|
+
"unit" text NULL,
|
|
14
|
+
"description" text NULL,
|
|
15
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
16
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
17
|
+
"deleted_at" timestamptz NULL,
|
|
18
|
+
CONSTRAINT "attribute_preset_pkey" PRIMARY KEY ("id")
|
|
19
|
+
);
|
|
20
|
+
`);
|
|
21
|
+
this.addSql(`
|
|
22
|
+
CREATE INDEX IF NOT EXISTS "IDX_attribute_preset_deleted_at"
|
|
23
|
+
ON "attribute_preset" ("deleted_at")
|
|
24
|
+
WHERE "deleted_at" IS NOT NULL;
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
async down() {
|
|
28
|
+
this.addSql(`DROP TABLE IF EXISTS "attribute_preset" CASCADE;`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.Migration20260406120000 = Migration20260406120000;
|
|
32
|
+
//# sourceMappingURL=Migration20260406120000.js.map
|
package/.medusa/server/src/modules/product-attributes/migrations/Migration20260406120000.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Migration20260406120000.js","sourceRoot":"","sources":["../../../../../../src/modules/product-attributes/migrations/Migration20260406120000.ts"],"names":[],"mappings":";;;AAAA,sDAAiD;AAEjD,MAAa,uBAAwB,SAAQ,sBAAS;IAC3C,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;KAaX,CAAC,CAAA;QAEF,IAAI,CAAC,MAAM,CAAC;;;;KAIX,CAAC,CAAA;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,kDAAkD,CAAC,CAAA;IACjE,CAAC;CACF;AA3BD,0DA2BC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare const AttributePreset: import("@medusajs/framework/utils").DmlEntity<import("@medusajs/framework/utils").DMLEntitySchemaBuilder<{
|
|
2
|
+
id: import("@medusajs/framework/utils").PrimaryKeyModifier<string, import("@medusajs/framework/utils").IdProperty>;
|
|
3
|
+
key: import("@medusajs/framework/utils").TextProperty;
|
|
4
|
+
label: import("@medusajs/framework/utils").TextProperty;
|
|
5
|
+
type: import("@medusajs/framework/utils").TextProperty;
|
|
6
|
+
unit: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
|
|
7
|
+
description: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
|
|
8
|
+
}>, "attribute_preset">;
|
|
9
|
+
export default AttributePreset;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
4
|
+
const AttributePreset = utils_1.model.define("attribute_preset", {
|
|
5
|
+
id: utils_1.model.id().primaryKey(),
|
|
6
|
+
key: utils_1.model.text(),
|
|
7
|
+
label: utils_1.model.text(),
|
|
8
|
+
type: utils_1.model.text().default("text"),
|
|
9
|
+
unit: utils_1.model.text().nullable(),
|
|
10
|
+
description: utils_1.model.text().nullable(),
|
|
11
|
+
});
|
|
12
|
+
exports.default = AttributePreset;
|
|
13
|
+
//# sourceMappingURL=attribute-preset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribute-preset.js","sourceRoot":"","sources":["../../../../../../src/modules/product-attributes/models/attribute-preset.ts"],"names":[],"mappings":";;AAAA,qDAAiD;AAEjD,MAAM,eAAe,GAAG,aAAK,CAAC,MAAM,CAAC,kBAAkB,EAAE;IACvD,EAAE,EAAE,aAAK,CAAC,EAAE,EAAE,CAAC,UAAU,EAAE;IAC3B,GAAG,EAAE,aAAK,CAAC,IAAI,EAAE;IACjB,KAAK,EAAE,aAAK,CAAC,IAAI,EAAE;IACnB,IAAI,EAAE,aAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,aAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC7B,WAAW,EAAE,aAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAA;AAEF,kBAAe,eAAe,CAAA"}
|
|
@@ -35,6 +35,14 @@ declare const CustomAttributeService_base: import("@medusajs/framework/utils").M
|
|
|
35
35
|
product_custom_attributes: import("@medusajs/framework/utils").HasMany<() => import("@medusajs/framework/utils").DmlEntity<import("@medusajs/framework/utils").DMLEntitySchemaBuilder</*elided*/ any>, "product_custom_attribute">>;
|
|
36
36
|
}>, "category_custom_attribute">, undefined>;
|
|
37
37
|
}>, "product_custom_attribute">;
|
|
38
|
+
AttributePreset: import("@medusajs/framework/utils").DmlEntity<import("@medusajs/framework/utils").DMLEntitySchemaBuilder<{
|
|
39
|
+
id: import("@medusajs/framework/utils").PrimaryKeyModifier<string, import("@medusajs/framework/utils").IdProperty>;
|
|
40
|
+
key: import("@medusajs/framework/utils").TextProperty;
|
|
41
|
+
label: import("@medusajs/framework/utils").TextProperty;
|
|
42
|
+
type: import("@medusajs/framework/utils").TextProperty;
|
|
43
|
+
unit: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
|
|
44
|
+
description: import("@medusajs/framework/utils").NullableModifier<string, import("@medusajs/framework/utils").TextProperty>;
|
|
45
|
+
}>, "attribute_preset">;
|
|
38
46
|
}>>;
|
|
39
47
|
declare class CustomAttributeService extends CustomAttributeService_base {
|
|
40
48
|
getCategoryAttributes(categoryId: string): Promise<{
|
|
@@ -218,6 +226,76 @@ declare class CustomAttributeService extends CustomAttributeService_base {
|
|
|
218
226
|
deleted_at: Date | null;
|
|
219
227
|
category_custom_attribute_id: string;
|
|
220
228
|
}[]>;
|
|
229
|
+
listPresets(): Promise<{
|
|
230
|
+
id: string;
|
|
231
|
+
key: string;
|
|
232
|
+
label: string;
|
|
233
|
+
type: string;
|
|
234
|
+
unit: string | null;
|
|
235
|
+
description: string | null;
|
|
236
|
+
created_at: Date;
|
|
237
|
+
updated_at: Date;
|
|
238
|
+
deleted_at: Date | null;
|
|
239
|
+
}[]>;
|
|
240
|
+
createPreset(data: {
|
|
241
|
+
label: string;
|
|
242
|
+
type?: string;
|
|
243
|
+
unit?: string | null;
|
|
244
|
+
description?: string | null;
|
|
245
|
+
}): Promise<{
|
|
246
|
+
id: string;
|
|
247
|
+
key: string;
|
|
248
|
+
label: string;
|
|
249
|
+
type: string;
|
|
250
|
+
unit: string | null;
|
|
251
|
+
description: string | null;
|
|
252
|
+
created_at: Date;
|
|
253
|
+
updated_at: Date;
|
|
254
|
+
deleted_at: Date | null;
|
|
255
|
+
}>;
|
|
256
|
+
updatePreset(id: string, data: {
|
|
257
|
+
label?: string;
|
|
258
|
+
type?: string;
|
|
259
|
+
unit?: string | null;
|
|
260
|
+
description?: string | null;
|
|
261
|
+
deleted_at?: string;
|
|
262
|
+
}): Promise<{
|
|
263
|
+
id: string;
|
|
264
|
+
key: string;
|
|
265
|
+
label: string;
|
|
266
|
+
type: string;
|
|
267
|
+
unit: string | null;
|
|
268
|
+
description: string | null;
|
|
269
|
+
created_at: Date;
|
|
270
|
+
updated_at: Date;
|
|
271
|
+
deleted_at: Date | null;
|
|
272
|
+
}[]>;
|
|
273
|
+
applyPresetToCategory(presetId: string, categoryId: string): Promise<{
|
|
274
|
+
id: string;
|
|
275
|
+
key: string;
|
|
276
|
+
type: string;
|
|
277
|
+
label: string;
|
|
278
|
+
unit: string | null;
|
|
279
|
+
sort_order: number;
|
|
280
|
+
category_id: string;
|
|
281
|
+
is_standard: boolean;
|
|
282
|
+
product_custom_attributes: {
|
|
283
|
+
id: string;
|
|
284
|
+
product_id: string;
|
|
285
|
+
value: string;
|
|
286
|
+
value_numeric: number | null;
|
|
287
|
+
value_file: string | null;
|
|
288
|
+
category_custom_attribute: /*elided*/ any;
|
|
289
|
+
raw_value_numeric: Record<string, unknown> | null;
|
|
290
|
+
created_at: Date;
|
|
291
|
+
updated_at: Date;
|
|
292
|
+
deleted_at: Date | null;
|
|
293
|
+
category_custom_attribute_id: string;
|
|
294
|
+
}[];
|
|
295
|
+
created_at: Date;
|
|
296
|
+
updated_at: Date;
|
|
297
|
+
deleted_at: Date | null;
|
|
298
|
+
}>;
|
|
221
299
|
private generateKey;
|
|
222
300
|
}
|
|
223
301
|
export default CustomAttributeService;
|
|
@@ -7,9 +7,11 @@ const utils_1 = require("@medusajs/framework/utils");
|
|
|
7
7
|
const transliteration_1 = require("transliteration");
|
|
8
8
|
const category_custom_attribute_1 = __importDefault(require("./models/category-custom-attribute"));
|
|
9
9
|
const product_custom_attribute_1 = __importDefault(require("./models/product-custom-attribute"));
|
|
10
|
+
const attribute_preset_1 = __importDefault(require("./models/attribute-preset"));
|
|
10
11
|
const models = {
|
|
11
12
|
CategoryCustomAttribute: category_custom_attribute_1.default,
|
|
12
13
|
ProductCustomAttribute: product_custom_attribute_1.default,
|
|
14
|
+
AttributePreset: attribute_preset_1.default,
|
|
13
15
|
};
|
|
14
16
|
// @ts-ignore - MedusaService dynamically generates methods based on models
|
|
15
17
|
class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
@@ -74,6 +76,40 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
|
74
76
|
relations: ["category_custom_attribute"],
|
|
75
77
|
});
|
|
76
78
|
}
|
|
79
|
+
async listPresets() {
|
|
80
|
+
// @ts-ignore - generated
|
|
81
|
+
return await this.listAttributePresets({ deleted_at: null });
|
|
82
|
+
}
|
|
83
|
+
async createPreset(data) {
|
|
84
|
+
const key = this.generateKey(data.label);
|
|
85
|
+
// @ts-ignore - generated
|
|
86
|
+
return await this.createAttributePresets({
|
|
87
|
+
label: data.label,
|
|
88
|
+
key,
|
|
89
|
+
type: data.type || "text",
|
|
90
|
+
unit: data.unit ?? null,
|
|
91
|
+
description: data.description ?? null,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async updatePreset(id, data) {
|
|
95
|
+
const updateData = { ...data };
|
|
96
|
+
if (data.label) {
|
|
97
|
+
updateData.key = this.generateKey(data.label);
|
|
98
|
+
}
|
|
99
|
+
// @ts-ignore - generated
|
|
100
|
+
return await this.updateAttributePresets([{ id, ...updateData }]);
|
|
101
|
+
}
|
|
102
|
+
async applyPresetToCategory(presetId, categoryId) {
|
|
103
|
+
// @ts-ignore - generated
|
|
104
|
+
const preset = await this.retrieveAttributePreset(presetId);
|
|
105
|
+
return await this.createCategoryAttribute({
|
|
106
|
+
label: preset.label,
|
|
107
|
+
type: preset.type,
|
|
108
|
+
unit: preset.unit,
|
|
109
|
+
category_id: categoryId,
|
|
110
|
+
is_standard: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
77
113
|
generateKey(label) {
|
|
78
114
|
return (0, transliteration_1.slugify)(label, { separator: "_", lowercase: true }) || "attr";
|
|
79
115
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../../src/modules/product-attributes/service.ts"],"names":[],"mappings":";;;;;AAAA,qDAAyD;AACzD,qDAAyC;AACzC,mGAAwE;AACxE,iGAAsE;
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../../src/modules/product-attributes/service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empty-complete-org/medusa-product-attributes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Custom attributes module for Medusa v2 with support for text, number, file types and units of measurement",
|
|
5
5
|
"author": "empty-complete",
|
|
6
6
|
"license": "MIT",
|