@empty-complete-org/medusa-product-attributes 0.14.1 → 1.0.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 +441 -202
- package/.medusa/server/src/admin/index.mjs +442 -203
- package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/[id]/apply/route.js +1 -1
- package/.medusa/server/src/api/admin/attribute-templates/[id]/apply/route.js.map +1 -0
- package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/route.js +6 -6
- package/.medusa/server/src/api/admin/attribute-templates/route.js.map +1 -0
- package/.medusa/server/src/api/admin/category/[categoryId]/custom-attributes/route.js +1 -1
- package/.medusa/server/src/api/admin/category/[categoryId]/custom-attributes/route.js.map +1 -1
- package/.medusa/server/src/api/admin/global-attributes/route.d.ts +4 -0
- package/.medusa/server/src/api/admin/global-attributes/route.js +30 -0
- package/.medusa/server/src/api/admin/global-attributes/route.js.map +1 -0
- package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.d.ts +2 -0
- package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.js +34 -0
- package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/index.d.ts +5 -5
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.d.ts +5 -0
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.js +58 -0
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/models/{attribute-preset.d.ts → attribute-template.d.ts} +3 -3
- package/.medusa/server/src/modules/product-attributes/models/{attribute-preset.js → attribute-template.js} +3 -3
- package/.medusa/server/src/modules/product-attributes/models/attribute-template.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.d.ts +2 -1
- package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.js +2 -1
- package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.js.map +1 -1
- package/.medusa/server/src/modules/product-attributes/models/product-custom-attribute.d.ts +2 -1
- package/.medusa/server/src/modules/product-attributes/service.d.ts +56 -17
- package/.medusa/server/src/modules/product-attributes/service.js +44 -18
- package/.medusa/server/src/modules/product-attributes/service.js.map +1 -1
- package/README.md +96 -312
- package/README.ru.md +85 -0
- package/package.json +1 -1
- package/.medusa/server/src/api/admin/attribute-presets/[id]/apply/route.js.map +0 -1
- package/.medusa/server/src/api/admin/attribute-presets/route.js.map +0 -1
- package/.medusa/server/src/modules/product-attributes/models/attribute-preset.js.map +0 -1
- /package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/[id]/apply/route.d.ts +0 -0
- /package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/route.d.ts +0 -0
|
@@ -32,35 +32,201 @@ const sdk = new Medusa__default.default({
|
|
|
32
32
|
type: "session"
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
|
-
const
|
|
35
|
+
const attributes$1 = "Атрибуты";
|
|
36
|
+
const characteristics$1 = "Характеристики";
|
|
37
|
+
const inherited$1 = "Унаследованные";
|
|
38
|
+
const own$1 = "Свои";
|
|
39
|
+
const global$1 = "глобальный";
|
|
40
|
+
const fromParent$1 = "из родителя";
|
|
41
|
+
const add$1 = "Добавить";
|
|
42
|
+
const fromTemplate$1 = "Из шаблона";
|
|
43
|
+
const chooseTemplate$1 = "Выберите шаблон";
|
|
44
|
+
const close$1 = "Закрыть";
|
|
45
|
+
const loading$1 = "Загрузка…";
|
|
46
|
+
const loadFailed$1 = "Не удалось загрузить.";
|
|
47
|
+
const noAttributes$1 = "Нет атрибутов. Добавьте первый.";
|
|
48
|
+
const noTemplates$1 = "Шаблонов нет. Создайте в Settings → Attribute Templates.";
|
|
49
|
+
const noGlobals$1 = "Глобальных атрибутов нет.";
|
|
50
|
+
const cancel$1 = "Отмена";
|
|
51
|
+
const confirmDelete$1 = "Удалить «{name}»?";
|
|
52
|
+
const create$1 = "Создать";
|
|
53
|
+
const save$1 = "Сохранить";
|
|
54
|
+
const saved$1 = "Сохранено ✓";
|
|
55
|
+
const name$1 = "Название";
|
|
56
|
+
const description$1 = "Описание (необязательно)";
|
|
57
|
+
const unit$1 = "ед.";
|
|
58
|
+
const value$1 = "Значение";
|
|
59
|
+
const yes$1 = "Да";
|
|
60
|
+
const no$1 = "Нет";
|
|
61
|
+
const upload$1 = "Загрузить";
|
|
62
|
+
const uploadError$1 = "Ошибка загрузки файла";
|
|
63
|
+
const fileUrl$1 = "URL файла";
|
|
64
|
+
const templates$1 = "Шаблоны атрибутов";
|
|
65
|
+
const templatesDesc$1 = "Заготовки атрибутов, которые можно применить к любой категории.";
|
|
66
|
+
const globals$1 = "Глобальные атрибуты";
|
|
67
|
+
const globalsDesc$1 = "Применяются ко всем товарам автоматически. Значения у продуктов необязательны.";
|
|
68
|
+
const ru = {
|
|
69
|
+
attributes: attributes$1,
|
|
70
|
+
characteristics: characteristics$1,
|
|
71
|
+
inherited: inherited$1,
|
|
72
|
+
own: own$1,
|
|
73
|
+
global: global$1,
|
|
74
|
+
fromParent: fromParent$1,
|
|
75
|
+
add: add$1,
|
|
76
|
+
fromTemplate: fromTemplate$1,
|
|
77
|
+
chooseTemplate: chooseTemplate$1,
|
|
78
|
+
close: close$1,
|
|
79
|
+
loading: loading$1,
|
|
80
|
+
loadFailed: loadFailed$1,
|
|
81
|
+
noAttributes: noAttributes$1,
|
|
82
|
+
noTemplates: noTemplates$1,
|
|
83
|
+
noGlobals: noGlobals$1,
|
|
84
|
+
"delete": "Удалить",
|
|
85
|
+
cancel: cancel$1,
|
|
86
|
+
confirmDelete: confirmDelete$1,
|
|
87
|
+
create: create$1,
|
|
88
|
+
save: save$1,
|
|
89
|
+
saved: saved$1,
|
|
90
|
+
name: name$1,
|
|
91
|
+
description: description$1,
|
|
92
|
+
unit: unit$1,
|
|
93
|
+
value: value$1,
|
|
94
|
+
"type.text": "Текст",
|
|
95
|
+
"type.number": "Число",
|
|
96
|
+
"type.file": "Файл",
|
|
97
|
+
"type.boolean": "Да/Нет",
|
|
98
|
+
yes: yes$1,
|
|
99
|
+
no: no$1,
|
|
100
|
+
upload: upload$1,
|
|
101
|
+
uploadError: uploadError$1,
|
|
102
|
+
fileUrl: fileUrl$1,
|
|
103
|
+
templates: templates$1,
|
|
104
|
+
templatesDesc: templatesDesc$1,
|
|
105
|
+
globals: globals$1,
|
|
106
|
+
globalsDesc: globalsDesc$1
|
|
107
|
+
};
|
|
108
|
+
const attributes = "Attributes";
|
|
109
|
+
const characteristics = "Characteristics";
|
|
110
|
+
const inherited = "Inherited";
|
|
111
|
+
const own = "Own";
|
|
112
|
+
const global = "global";
|
|
113
|
+
const fromParent = "from parent";
|
|
114
|
+
const add = "Add";
|
|
115
|
+
const fromTemplate = "From template";
|
|
116
|
+
const chooseTemplate = "Choose a template";
|
|
117
|
+
const close = "Close";
|
|
118
|
+
const loading = "Loading…";
|
|
119
|
+
const loadFailed = "Failed to load.";
|
|
120
|
+
const noAttributes = "No attributes. Add your first.";
|
|
121
|
+
const noTemplates = "No templates. Create in Settings → Attribute Templates.";
|
|
122
|
+
const noGlobals = "No global attributes.";
|
|
123
|
+
const cancel = "Cancel";
|
|
124
|
+
const confirmDelete = "Delete «{name}»?";
|
|
125
|
+
const create = "Create";
|
|
126
|
+
const save = "Save";
|
|
127
|
+
const saved = "Saved ✓";
|
|
128
|
+
const name = "Name";
|
|
129
|
+
const description = "Description (optional)";
|
|
130
|
+
const unit = "unit";
|
|
131
|
+
const value = "Value";
|
|
132
|
+
const yes = "Yes";
|
|
133
|
+
const no = "No";
|
|
134
|
+
const upload = "Upload";
|
|
135
|
+
const uploadError = "Upload failed";
|
|
136
|
+
const fileUrl = "File URL";
|
|
137
|
+
const templates = "Attribute Templates";
|
|
138
|
+
const templatesDesc = "Reusable attribute blueprints. Apply to any category in one click.";
|
|
139
|
+
const globals = "Global Attributes";
|
|
140
|
+
const globalsDesc = "Applied to every product automatically. Product values are optional.";
|
|
141
|
+
const en = {
|
|
142
|
+
attributes,
|
|
143
|
+
characteristics,
|
|
144
|
+
inherited,
|
|
145
|
+
own,
|
|
146
|
+
global,
|
|
147
|
+
fromParent,
|
|
148
|
+
add,
|
|
149
|
+
fromTemplate,
|
|
150
|
+
chooseTemplate,
|
|
151
|
+
close,
|
|
152
|
+
loading,
|
|
153
|
+
loadFailed,
|
|
154
|
+
noAttributes,
|
|
155
|
+
noTemplates,
|
|
156
|
+
noGlobals,
|
|
157
|
+
"delete": "Delete",
|
|
158
|
+
cancel,
|
|
159
|
+
confirmDelete,
|
|
160
|
+
create,
|
|
161
|
+
save,
|
|
162
|
+
saved,
|
|
163
|
+
name,
|
|
164
|
+
description,
|
|
165
|
+
unit,
|
|
166
|
+
value,
|
|
167
|
+
"type.text": "Text",
|
|
168
|
+
"type.number": "Number",
|
|
169
|
+
"type.file": "File",
|
|
170
|
+
"type.boolean": "Yes/No",
|
|
171
|
+
yes,
|
|
172
|
+
no,
|
|
173
|
+
upload,
|
|
174
|
+
uploadError,
|
|
175
|
+
fileUrl,
|
|
176
|
+
templates,
|
|
177
|
+
templatesDesc,
|
|
178
|
+
globals,
|
|
179
|
+
globalsDesc
|
|
180
|
+
};
|
|
181
|
+
const locales = { ru, en };
|
|
182
|
+
function detectLang() {
|
|
183
|
+
var _a;
|
|
184
|
+
if (typeof window === "undefined") return "en";
|
|
185
|
+
const stored = window.localStorage.getItem("i18nextLng");
|
|
186
|
+
if (stored) {
|
|
187
|
+
const short = stored.slice(0, 2).toLowerCase();
|
|
188
|
+
if (locales[short]) return short;
|
|
189
|
+
}
|
|
190
|
+
const nav = (((_a = window.navigator) == null ? void 0 : _a.language) || "en").slice(0, 2).toLowerCase();
|
|
191
|
+
return locales[nav] ? nav : "en";
|
|
192
|
+
}
|
|
193
|
+
function useT() {
|
|
194
|
+
const dict = React.useMemo(() => {
|
|
195
|
+
const lang = detectLang();
|
|
196
|
+
return locales[lang] ?? locales.en;
|
|
197
|
+
}, []);
|
|
198
|
+
return (key, fallback) => dict[key] ?? fallback ?? key;
|
|
199
|
+
}
|
|
200
|
+
const emptyForm$2 = () => ({ label: "", type: "text", unit: "" });
|
|
36
201
|
const CategoryAttributeTemplatesWidget = ({
|
|
37
202
|
data
|
|
38
203
|
}) => {
|
|
39
204
|
var _a, _b;
|
|
40
205
|
const categoryId = data.id;
|
|
206
|
+
const t = useT();
|
|
41
207
|
const qc = reactQuery.useQueryClient();
|
|
42
208
|
const queryKey = ["category-custom-attributes", categoryId];
|
|
43
209
|
const [showAddForm, setShowAddForm] = React.useState(false);
|
|
44
|
-
const [
|
|
45
|
-
const [addForm, setAddForm] = React.useState(emptyForm$
|
|
210
|
+
const [showTemplateList, setShowTemplateList] = React.useState(false);
|
|
211
|
+
const [addForm, setAddForm] = React.useState(emptyForm$2());
|
|
46
212
|
const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
|
|
47
213
|
const [mutationError, setMutationError] = React.useState(null);
|
|
48
|
-
const
|
|
49
|
-
queryKey: ["attribute-
|
|
50
|
-
queryFn: () => sdk.client.fetch(`/admin/attribute-
|
|
51
|
-
enabled:
|
|
214
|
+
const templatesQuery = reactQuery.useQuery({
|
|
215
|
+
queryKey: ["attribute-templates"],
|
|
216
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-templates`),
|
|
217
|
+
enabled: showTemplateList
|
|
52
218
|
});
|
|
53
|
-
const
|
|
54
|
-
mutationFn: (
|
|
219
|
+
const applyTemplateMutation = reactQuery.useMutation({
|
|
220
|
+
mutationFn: (templateId) => sdk.client.fetch(`/admin/attribute-templates/${templateId}/apply`, {
|
|
55
221
|
method: "POST",
|
|
56
222
|
body: { category_id: categoryId }
|
|
57
223
|
}),
|
|
58
224
|
onSuccess: () => {
|
|
59
225
|
qc.invalidateQueries({ queryKey });
|
|
60
|
-
|
|
226
|
+
setShowTemplateList(false);
|
|
61
227
|
},
|
|
62
228
|
onError: (err) => {
|
|
63
|
-
setMutationError((err == null ? void 0 : err.message) || "Ошибка при применении
|
|
229
|
+
setMutationError((err == null ? void 0 : err.message) || "Ошибка при применении шаблона");
|
|
64
230
|
}
|
|
65
231
|
});
|
|
66
232
|
const {
|
|
@@ -71,9 +237,9 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
71
237
|
queryKey,
|
|
72
238
|
queryFn: () => sdk.client.fetch(`/admin/category/${categoryId}/custom-attributes`)
|
|
73
239
|
});
|
|
74
|
-
const
|
|
75
|
-
const ownAttributes =
|
|
76
|
-
const inheritedAttributes =
|
|
240
|
+
const attributes2 = (result == null ? void 0 : result.category_custom_attributes) ?? [];
|
|
241
|
+
const ownAttributes = attributes2.filter((a) => !a.inherited);
|
|
242
|
+
const inheritedAttributes = attributes2.filter((a) => a.inherited);
|
|
77
243
|
const createMutation = reactQuery.useMutation({
|
|
78
244
|
mutationFn: (body) => sdk.client.fetch(`/admin/category/${categoryId}/custom-attributes`, {
|
|
79
245
|
method: "POST",
|
|
@@ -82,7 +248,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
82
248
|
onSuccess: () => {
|
|
83
249
|
qc.invalidateQueries({ queryKey });
|
|
84
250
|
setShowAddForm(false);
|
|
85
|
-
setAddForm(emptyForm$
|
|
251
|
+
setAddForm(emptyForm$2());
|
|
86
252
|
setMutationError(null);
|
|
87
253
|
},
|
|
88
254
|
onError: (err) => {
|
|
@@ -107,51 +273,54 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
107
273
|
unit: addForm.type === "number" && addForm.unit.trim() ? addForm.unit.trim() : null
|
|
108
274
|
});
|
|
109
275
|
};
|
|
110
|
-
const typeLabel2 = (
|
|
276
|
+
const typeLabel2 = (v) => t(`type.${v}`, v);
|
|
111
277
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
112
278
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
113
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "
|
|
114
|
-
!showAddForm && !
|
|
279
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("attributes") }),
|
|
280
|
+
!showAddForm && !showTemplateList && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
115
281
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
116
282
|
ui.Button,
|
|
117
283
|
{
|
|
118
284
|
variant: "secondary",
|
|
119
285
|
size: "small",
|
|
120
|
-
onClick: () =>
|
|
121
|
-
children: "
|
|
286
|
+
onClick: () => setShowTemplateList(true),
|
|
287
|
+
children: t("fromTemplate")
|
|
122
288
|
}
|
|
123
289
|
),
|
|
124
|
-
/* @__PURE__ */ jsxRuntime.
|
|
290
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
125
291
|
ui.Button,
|
|
126
292
|
{
|
|
127
293
|
variant: "secondary",
|
|
128
294
|
size: "small",
|
|
129
295
|
onClick: () => setShowAddForm(true),
|
|
130
|
-
children:
|
|
296
|
+
children: [
|
|
297
|
+
"+ ",
|
|
298
|
+
t("add")
|
|
299
|
+
]
|
|
131
300
|
}
|
|
132
301
|
)
|
|
133
302
|
] })
|
|
134
303
|
] }),
|
|
135
|
-
|
|
304
|
+
showTemplateList && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-3", children: [
|
|
136
305
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
137
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Выберите
|
|
306
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Выберите шаблон" }),
|
|
138
307
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
139
308
|
ui.Button,
|
|
140
309
|
{
|
|
141
310
|
size: "small",
|
|
142
311
|
variant: "secondary",
|
|
143
|
-
onClick: () =>
|
|
312
|
+
onClick: () => setShowTemplateList(false),
|
|
144
313
|
children: "Закрыть"
|
|
145
314
|
}
|
|
146
315
|
)
|
|
147
316
|
] }),
|
|
148
|
-
|
|
149
|
-
((_a =
|
|
150
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: (_b =
|
|
317
|
+
templatesQuery.isLoading && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: t("loading") }),
|
|
318
|
+
((_a = templatesQuery.data) == null ? void 0 : _a.attribute_templates.length) === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Пресетов нет. Создайте в настройках Product Attributes." }),
|
|
319
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: (_b = templatesQuery.data) == null ? void 0 : _b.attribute_templates.map((p) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
151
320
|
"button",
|
|
152
321
|
{
|
|
153
|
-
onClick: () =>
|
|
154
|
-
disabled:
|
|
322
|
+
onClick: () => applyTemplateMutation.mutate(p.id),
|
|
323
|
+
disabled: applyTemplateMutation.isPending,
|
|
155
324
|
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
325
|
children: [
|
|
157
326
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: p.label }),
|
|
@@ -164,7 +333,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
164
333
|
p.id
|
|
165
334
|
)) })
|
|
166
335
|
] }),
|
|
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: "
|
|
336
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: t("loading") }) }),
|
|
168
337
|
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить атрибуты." }) }),
|
|
169
338
|
inheritedAttributes.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
170
339
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-2 bg-ui-bg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-muted uppercase", children: "Унаследованные" }) }),
|
|
@@ -243,7 +412,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
243
412
|
)
|
|
244
413
|
) })
|
|
245
414
|
] }),
|
|
246
|
-
!isLoading && !isError &&
|
|
415
|
+
!isLoading && !isError && attributes2.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: "Нет атрибутов. Добавьте первый." }) }),
|
|
247
416
|
showAddForm && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-6 py-3", children: [
|
|
248
417
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
249
418
|
ui.Input,
|
|
@@ -298,7 +467,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
298
467
|
size: "small",
|
|
299
468
|
onClick: () => {
|
|
300
469
|
setShowAddForm(false);
|
|
301
|
-
setAddForm(emptyForm$
|
|
470
|
+
setAddForm(emptyForm$2());
|
|
302
471
|
setMutationError(null);
|
|
303
472
|
},
|
|
304
473
|
children: "Отмена"
|
|
@@ -314,19 +483,17 @@ adminSdk.defineWidgetConfig({
|
|
|
314
483
|
const ProductAttributeValuesWidget = ({
|
|
315
484
|
data
|
|
316
485
|
}) => {
|
|
317
|
-
var _a
|
|
486
|
+
var _a;
|
|
318
487
|
const productId = data.id;
|
|
319
488
|
const productHandle = data.handle || productId;
|
|
320
|
-
const categoryId = ((_b = (_a = data.categories) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? null;
|
|
321
489
|
const qc = reactQuery.useQueryClient();
|
|
322
490
|
const [formValues, setFormValues] = React.useState({});
|
|
323
491
|
const [saveSuccess, setSaveSuccess] = React.useState(false);
|
|
324
492
|
const [uploadingId, setUploadingId] = React.useState(null);
|
|
325
493
|
const fileInputs = React.useRef({});
|
|
326
494
|
const attributesQuery = reactQuery.useQuery({
|
|
327
|
-
queryKey: ["
|
|
328
|
-
queryFn: () => sdk.client.fetch(`/admin/
|
|
329
|
-
enabled: !!categoryId
|
|
495
|
+
queryKey: ["product-attribute-schema", productId],
|
|
496
|
+
queryFn: () => sdk.client.fetch(`/admin/product/${productId}/attribute-schema`)
|
|
330
497
|
});
|
|
331
498
|
const valuesQuery = reactQuery.useQuery({
|
|
332
499
|
queryKey: ["product-custom-attributes", productId],
|
|
@@ -334,10 +501,10 @@ const ProductAttributeValuesWidget = ({
|
|
|
334
501
|
});
|
|
335
502
|
React.useEffect(() => {
|
|
336
503
|
if (!attributesQuery.data || !valuesQuery.data) return;
|
|
337
|
-
const
|
|
504
|
+
const attributes22 = attributesQuery.data.attributes;
|
|
338
505
|
const values = valuesQuery.data.product_custom_attributes;
|
|
339
506
|
const initial = {};
|
|
340
|
-
for (const attr of
|
|
507
|
+
for (const attr of attributes22) {
|
|
341
508
|
const existing = values.find(
|
|
342
509
|
(v) => {
|
|
343
510
|
var _a2;
|
|
@@ -363,12 +530,12 @@ const ProductAttributeValuesWidget = ({
|
|
|
363
530
|
});
|
|
364
531
|
const handleSave = () => {
|
|
365
532
|
if (!attributesQuery.data) return;
|
|
366
|
-
const
|
|
367
|
-
const attributesToUpdate =
|
|
533
|
+
const attributes22 = attributesQuery.data.attributes;
|
|
534
|
+
const attributesToUpdate = attributes22.filter((attr) => formValues[attr.id] !== void 0).map((attr) => ({ id: attr.id, value: formValues[attr.id] ?? "" }));
|
|
368
535
|
saveMutation.mutate({ attributes: attributesToUpdate });
|
|
369
536
|
};
|
|
370
537
|
const handleFileUpload = async (attr, file) => {
|
|
371
|
-
var _a2,
|
|
538
|
+
var _a2, _b;
|
|
372
539
|
const attrId = attr.id;
|
|
373
540
|
setUploadingId(attrId);
|
|
374
541
|
try {
|
|
@@ -397,7 +564,7 @@ const ProductAttributeValuesWidget = ({
|
|
|
397
564
|
return;
|
|
398
565
|
}
|
|
399
566
|
const res = await response.json();
|
|
400
|
-
const url = (
|
|
567
|
+
const url = (_b = (_a2 = res == null ? void 0 : res.files) == null ? void 0 : _a2[0]) == null ? void 0 : _b.url;
|
|
401
568
|
if (url) {
|
|
402
569
|
setFormValues((prev) => ({ ...prev, [attrId]: url }));
|
|
403
570
|
} else {
|
|
@@ -411,22 +578,16 @@ const ProductAttributeValuesWidget = ({
|
|
|
411
578
|
setUploadingId(null);
|
|
412
579
|
}
|
|
413
580
|
};
|
|
414
|
-
if (!categoryId) {
|
|
415
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
416
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Характеристики" }) }),
|
|
417
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Назначьте категорию товару, чтобы заполнить характеристики." }) })
|
|
418
|
-
] });
|
|
419
|
-
}
|
|
420
581
|
const isLoading = attributesQuery.isLoading || valuesQuery.isLoading;
|
|
421
582
|
const isError = attributesQuery.isError || valuesQuery.isError;
|
|
422
|
-
const
|
|
583
|
+
const attributes2 = ((_a = attributesQuery.data) == null ? void 0 : _a.attributes) ?? [];
|
|
423
584
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
424
585
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Характеристики" }) }),
|
|
425
586
|
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
426
587
|
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить характеристики." }) }),
|
|
427
|
-
!isLoading && !isError &&
|
|
428
|
-
!isLoading && !isError &&
|
|
429
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children:
|
|
588
|
+
!isLoading && !isError && attributes2.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "В категории нет атрибутов. Добавьте их в настройках категории." }) }),
|
|
589
|
+
!isLoading && !isError && attributes2.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
590
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: attributes2.map((attr) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
430
591
|
"div",
|
|
431
592
|
{
|
|
432
593
|
className: "grid grid-cols-2 items-center gap-4 px-6 py-3",
|
|
@@ -524,11 +685,76 @@ const ProductAttributeValuesWidget = ({
|
|
|
524
685
|
adminSdk.defineWidgetConfig({
|
|
525
686
|
zone: "product.details.after"
|
|
526
687
|
});
|
|
688
|
+
var __defProp$1 = Object.defineProperty;
|
|
689
|
+
var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
|
|
690
|
+
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
|
|
691
|
+
var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
|
|
692
|
+
var __defNormalProp$1 = (obj, key, value2) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
693
|
+
var __spreadValues$1 = (a, b) => {
|
|
694
|
+
for (var prop in b || (b = {}))
|
|
695
|
+
if (__hasOwnProp$1.call(b, prop))
|
|
696
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
697
|
+
if (__getOwnPropSymbols$1)
|
|
698
|
+
for (var prop of __getOwnPropSymbols$1(b)) {
|
|
699
|
+
if (__propIsEnum$1.call(b, prop))
|
|
700
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
701
|
+
}
|
|
702
|
+
return a;
|
|
703
|
+
};
|
|
704
|
+
var __objRest$1 = (source, exclude) => {
|
|
705
|
+
var target = {};
|
|
706
|
+
for (var prop in source)
|
|
707
|
+
if (__hasOwnProp$1.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
708
|
+
target[prop] = source[prop];
|
|
709
|
+
if (source != null && __getOwnPropSymbols$1)
|
|
710
|
+
for (var prop of __getOwnPropSymbols$1(source)) {
|
|
711
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum$1.call(source, prop))
|
|
712
|
+
target[prop] = source[prop];
|
|
713
|
+
}
|
|
714
|
+
return target;
|
|
715
|
+
};
|
|
716
|
+
const Globe = React__namespace.forwardRef(
|
|
717
|
+
(_a, ref) => {
|
|
718
|
+
var _b = _a, { color = "currentColor" } = _b, props = __objRest$1(_b, ["color"]);
|
|
719
|
+
return /* @__PURE__ */ React__namespace.createElement(
|
|
720
|
+
"svg",
|
|
721
|
+
__spreadValues$1({
|
|
722
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
723
|
+
width: 15,
|
|
724
|
+
height: 15,
|
|
725
|
+
viewBox: "0 0 15 15",
|
|
726
|
+
fill: "none",
|
|
727
|
+
ref
|
|
728
|
+
}, props),
|
|
729
|
+
/* @__PURE__ */ React__namespace.createElement(
|
|
730
|
+
"path",
|
|
731
|
+
{
|
|
732
|
+
stroke: color,
|
|
733
|
+
strokeLinecap: "round",
|
|
734
|
+
strokeLinejoin: "round",
|
|
735
|
+
strokeWidth: 1.5,
|
|
736
|
+
d: "M7.5 13.945c1.473 0 2.667-2.886 2.667-6.445S8.973 1.056 7.5 1.056 4.833 3.94 4.833 7.5s1.194 6.445 2.667 6.445M1.056 7.5h12.888"
|
|
737
|
+
}
|
|
738
|
+
),
|
|
739
|
+
/* @__PURE__ */ React__namespace.createElement(
|
|
740
|
+
"path",
|
|
741
|
+
{
|
|
742
|
+
stroke: color,
|
|
743
|
+
strokeLinecap: "round",
|
|
744
|
+
strokeLinejoin: "round",
|
|
745
|
+
strokeWidth: 1.5,
|
|
746
|
+
d: "M7.5 13.945a6.444 6.444 0 1 0 0-12.89 6.444 6.444 0 0 0 0 12.89"
|
|
747
|
+
}
|
|
748
|
+
)
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
);
|
|
752
|
+
Globe.displayName = "Globe";
|
|
527
753
|
var __defProp = Object.defineProperty;
|
|
528
754
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
529
755
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
530
756
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
531
|
-
var __defNormalProp = (obj, key,
|
|
757
|
+
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
532
758
|
var __spreadValues = (a, b) => {
|
|
533
759
|
for (var prop in b || (b = {}))
|
|
534
760
|
if (__hasOwnProp.call(b, prop))
|
|
@@ -552,7 +778,7 @@ var __objRest = (source, exclude) => {
|
|
|
552
778
|
}
|
|
553
779
|
return target;
|
|
554
780
|
};
|
|
555
|
-
const
|
|
781
|
+
const SquaresPlus = React__namespace.forwardRef(
|
|
556
782
|
(_a, ref) => {
|
|
557
783
|
var _b = _a, { color = "currentColor" } = _b, props = __objRest(_b, ["color"]);
|
|
558
784
|
return /* @__PURE__ */ React__namespace.createElement(
|
|
@@ -566,42 +792,39 @@ const CogSixTooth = React__namespace.forwardRef(
|
|
|
566
792
|
ref
|
|
567
793
|
}, props),
|
|
568
794
|
/* @__PURE__ */ React__namespace.createElement(
|
|
569
|
-
"
|
|
795
|
+
"path",
|
|
570
796
|
{
|
|
571
797
|
stroke: color,
|
|
572
798
|
strokeLinecap: "round",
|
|
573
799
|
strokeLinejoin: "round",
|
|
574
800
|
strokeWidth: 1.5,
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/* @__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" })
|
|
579
|
-
),
|
|
580
|
-
/* @__PURE__ */ React__namespace.createElement("defs", null, /* @__PURE__ */ React__namespace.createElement("clipPath", { id: "a" }, /* @__PURE__ */ React__namespace.createElement("path", { fill: "#fff", d: "M0 0h15v15H0z" })))
|
|
801
|
+
d: "M5.056 1.944H2.833a.89.89 0 0 0-.889.89v2.221c0 .491.398.89.89.89h2.222c.49 0 .888-.399.888-.89V2.833a.89.89 0 0 0-.888-.889M12.167 1.944H9.944a.89.89 0 0 0-.888.89v2.221c0 .491.398.89.888.89h2.223c.49 0 .889-.399.889-.89V2.833a.89.89 0 0 0-.89-.889M5.056 9.056H2.833a.89.89 0 0 0-.889.889v2.222c0 .49.398.889.89.889h2.222c.49 0 .888-.398.888-.89V9.946a.89.89 0 0 0-.888-.89M11.056 8.611v4.445M13.278 10.833H8.833"
|
|
802
|
+
}
|
|
803
|
+
)
|
|
581
804
|
);
|
|
582
805
|
}
|
|
583
806
|
);
|
|
584
|
-
|
|
585
|
-
const emptyForm = () => ({
|
|
807
|
+
SquaresPlus.displayName = "SquaresPlus";
|
|
808
|
+
const emptyForm$1 = () => ({
|
|
586
809
|
label: "",
|
|
587
810
|
type: "text",
|
|
588
811
|
unit: "",
|
|
589
812
|
description: ""
|
|
590
813
|
});
|
|
591
|
-
const typeLabel = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
592
|
-
const
|
|
814
|
+
const typeLabel$1 = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
815
|
+
const AttributeTemplatesSettingsPage = () => {
|
|
593
816
|
const qc = reactQuery.useQueryClient();
|
|
594
|
-
const queryKey = ["attribute-
|
|
817
|
+
const queryKey = ["attribute-templates"];
|
|
595
818
|
const [showAddForm, setShowAddForm] = React.useState(false);
|
|
596
|
-
const [addForm, setAddForm] = React.useState(emptyForm());
|
|
819
|
+
const [addForm, setAddForm] = React.useState(emptyForm$1());
|
|
597
820
|
const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
|
|
598
821
|
const { data, isLoading, isError } = reactQuery.useQuery({
|
|
599
822
|
queryKey,
|
|
600
|
-
queryFn: () => sdk.client.fetch(`/admin/attribute-
|
|
823
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
|
|
601
824
|
});
|
|
602
|
-
const
|
|
825
|
+
const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
|
|
603
826
|
const createMutation = reactQuery.useMutation({
|
|
604
|
-
mutationFn: (body) => sdk.client.fetch(`/admin/attribute-
|
|
827
|
+
mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
|
|
605
828
|
method: "POST",
|
|
606
829
|
body: {
|
|
607
830
|
label: body.label,
|
|
@@ -613,11 +836,11 @@ const ProductAttributesSettingsPage = () => {
|
|
|
613
836
|
onSuccess: () => {
|
|
614
837
|
qc.invalidateQueries({ queryKey });
|
|
615
838
|
setShowAddForm(false);
|
|
616
|
-
setAddForm(emptyForm());
|
|
839
|
+
setAddForm(emptyForm$1());
|
|
617
840
|
}
|
|
618
841
|
});
|
|
619
842
|
const deleteMutation = reactQuery.useMutation({
|
|
620
|
-
mutationFn: (id) => sdk.client.fetch(`/admin/attribute-
|
|
843
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
|
|
621
844
|
method: "PATCH",
|
|
622
845
|
body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
623
846
|
}),
|
|
@@ -638,154 +861,158 @@ const ProductAttributesSettingsPage = () => {
|
|
|
638
861
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
639
862
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
640
863
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
641
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "
|
|
642
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "
|
|
864
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Шаблоны атрибутов" }),
|
|
865
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Заготовки атрибутов, которые можно применить к любой категории." })
|
|
643
866
|
] }),
|
|
644
|
-
!showAddForm && /* @__PURE__ */ jsxRuntime.jsx(
|
|
645
|
-
ui.Button,
|
|
646
|
-
{
|
|
647
|
-
variant: "secondary",
|
|
648
|
-
size: "small",
|
|
649
|
-
onClick: () => setShowAddForm(true),
|
|
650
|
-
children: "+ Добавить пресет"
|
|
651
|
-
}
|
|
652
|
-
)
|
|
867
|
+
!showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
|
|
653
868
|
] }),
|
|
654
869
|
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
655
|
-
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить
|
|
656
|
-
!isLoading && !isError &&
|
|
657
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children:
|
|
658
|
-
(p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
659
|
-
"
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
] }),
|
|
668
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
669
|
-
ui.Button,
|
|
670
|
-
{
|
|
671
|
-
size: "small",
|
|
672
|
-
variant: "danger",
|
|
673
|
-
onClick: () => deleteMutation.mutate(p.id),
|
|
674
|
-
isLoading: deleteMutation.isPending,
|
|
675
|
-
children: "Удалить"
|
|
676
|
-
}
|
|
677
|
-
),
|
|
678
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
679
|
-
ui.Button,
|
|
680
|
-
{
|
|
681
|
-
size: "small",
|
|
682
|
-
variant: "secondary",
|
|
683
|
-
onClick: () => setConfirmDeleteId(null),
|
|
684
|
-
children: "Отмена"
|
|
685
|
-
}
|
|
686
|
-
)
|
|
687
|
-
]
|
|
688
|
-
},
|
|
689
|
-
p.id
|
|
690
|
-
) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
870
|
+
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить шаблоны." }) }),
|
|
871
|
+
!isLoading && !isError && templates2.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: "Шаблонов пока нет." }) }),
|
|
872
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: templates2.map(
|
|
873
|
+
(p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
|
|
874
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
|
|
875
|
+
"Удалить «",
|
|
876
|
+
p.label,
|
|
877
|
+
"»?"
|
|
878
|
+
] }),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(p.id), isLoading: deleteMutation.isPending, children: "Удалить" }),
|
|
880
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: "Отмена" })
|
|
881
|
+
] }, p.id) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
691
882
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
692
883
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
693
884
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: p.label }),
|
|
694
885
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
|
|
695
|
-
typeLabel(p.type),
|
|
886
|
+
typeLabel$1(p.type),
|
|
696
887
|
p.unit ? `, ${p.unit}` : ""
|
|
697
888
|
] })
|
|
698
889
|
] }),
|
|
699
890
|
p.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
|
|
700
891
|
] }),
|
|
701
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
702
|
-
"button",
|
|
703
|
-
{
|
|
704
|
-
onClick: () => setConfirmDeleteId(p.id),
|
|
705
|
-
className: "text-xs text-ui-fg-error hover:underline",
|
|
706
|
-
children: "Удалить"
|
|
707
|
-
}
|
|
708
|
-
)
|
|
892
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
|
|
709
893
|
] }, p.id)
|
|
710
894
|
) }),
|
|
711
895
|
showAddForm && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
|
|
712
896
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
713
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
714
|
-
|
|
715
|
-
{
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
),
|
|
723
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
724
|
-
"select",
|
|
725
|
-
{
|
|
726
|
-
value: addForm.type,
|
|
727
|
-
onChange: (e) => setAddForm((f) => ({
|
|
728
|
-
...f,
|
|
729
|
-
type: e.target.value,
|
|
730
|
-
unit: e.target.value === "number" ? f.unit : ""
|
|
731
|
-
})),
|
|
732
|
-
className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
|
|
733
|
-
children: [
|
|
734
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: "Текст" }),
|
|
735
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: "Число" }),
|
|
736
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: "Файл" }),
|
|
737
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
738
|
-
]
|
|
739
|
-
}
|
|
740
|
-
),
|
|
741
|
-
addForm.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
742
|
-
ui.Input,
|
|
743
|
-
{
|
|
744
|
-
value: addForm.unit,
|
|
745
|
-
onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })),
|
|
746
|
-
placeholder: "ед. (кг, м...)",
|
|
747
|
-
className: "w-28 h-8 text-sm"
|
|
748
|
-
}
|
|
749
|
-
)
|
|
897
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: addForm.label, onChange: (e) => setAddForm((f) => ({ ...f, label: e.target.value })), placeholder: "Название", className: "flex-1 h-8 text-sm", autoFocus: true }),
|
|
898
|
+
/* @__PURE__ */ jsxRuntime.jsxs("select", { value: addForm.type, onChange: (e) => setAddForm((f) => ({ ...f, type: e.target.value, unit: e.target.value === "number" ? f.unit : "" })), className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm", children: [
|
|
899
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: "Текст" }),
|
|
900
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: "Число" }),
|
|
901
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: "Файл" }),
|
|
902
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
903
|
+
] }),
|
|
904
|
+
addForm.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: addForm.unit, onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })), placeholder: "ед.", className: "w-28 h-8 text-sm" })
|
|
750
905
|
] }),
|
|
751
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
752
|
-
ui.Input,
|
|
753
|
-
{
|
|
754
|
-
value: addForm.description,
|
|
755
|
-
onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })),
|
|
756
|
-
placeholder: "Описание (необязательно)",
|
|
757
|
-
className: "h-8 text-sm"
|
|
758
|
-
}
|
|
759
|
-
),
|
|
906
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: addForm.description, onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })), placeholder: "Описание (необязательно)", className: "h-8 text-sm" }),
|
|
760
907
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
761
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
onClick: () => {
|
|
767
|
-
setShowAddForm(false);
|
|
768
|
-
setAddForm(emptyForm());
|
|
769
|
-
},
|
|
770
|
-
children: "Отмена"
|
|
771
|
-
}
|
|
772
|
-
),
|
|
773
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
774
|
-
ui.Button,
|
|
775
|
-
{
|
|
776
|
-
size: "small",
|
|
777
|
-
onClick: handleAdd,
|
|
778
|
-
isLoading: createMutation.isPending,
|
|
779
|
-
children: "Создать"
|
|
780
|
-
}
|
|
781
|
-
)
|
|
908
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => {
|
|
909
|
+
setShowAddForm(false);
|
|
910
|
+
setAddForm(emptyForm$1());
|
|
911
|
+
}, children: "Отмена" }),
|
|
912
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", onClick: handleAdd, isLoading: createMutation.isPending, children: "Создать" })
|
|
782
913
|
] })
|
|
783
914
|
] })
|
|
784
915
|
] });
|
|
785
916
|
};
|
|
917
|
+
const config$1 = adminSdk.defineRouteConfig({
|
|
918
|
+
label: "Attribute Templates",
|
|
919
|
+
icon: SquaresPlus
|
|
920
|
+
});
|
|
921
|
+
const emptyForm = () => ({ label: "", type: "text", unit: "" });
|
|
922
|
+
const typeLabel = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
923
|
+
const GlobalAttributesSettingsPage = () => {
|
|
924
|
+
const qc = reactQuery.useQueryClient();
|
|
925
|
+
const queryKey = ["global-attributes"];
|
|
926
|
+
const [showAddForm, setShowAddForm] = React.useState(false);
|
|
927
|
+
const [addForm, setAddForm] = React.useState(emptyForm());
|
|
928
|
+
const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
|
|
929
|
+
const { data, isLoading, isError } = reactQuery.useQuery({
|
|
930
|
+
queryKey,
|
|
931
|
+
queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
|
|
932
|
+
});
|
|
933
|
+
const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
|
|
934
|
+
const createMutation = reactQuery.useMutation({
|
|
935
|
+
mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
|
|
936
|
+
method: "POST",
|
|
937
|
+
body: {
|
|
938
|
+
label: body.label,
|
|
939
|
+
type: body.type,
|
|
940
|
+
unit: body.type === "number" && body.unit ? body.unit : null
|
|
941
|
+
}
|
|
942
|
+
}),
|
|
943
|
+
onSuccess: () => {
|
|
944
|
+
qc.invalidateQueries({ queryKey });
|
|
945
|
+
setShowAddForm(false);
|
|
946
|
+
setAddForm(emptyForm());
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
const deleteMutation = reactQuery.useMutation({
|
|
950
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
|
|
951
|
+
method: "PATCH",
|
|
952
|
+
body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
953
|
+
}),
|
|
954
|
+
onSuccess: () => {
|
|
955
|
+
qc.invalidateQueries({ queryKey });
|
|
956
|
+
setConfirmDeleteId(null);
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
const handleAdd = () => {
|
|
960
|
+
if (!addForm.label.trim()) return;
|
|
961
|
+
createMutation.mutate({
|
|
962
|
+
label: addForm.label.trim(),
|
|
963
|
+
type: addForm.type,
|
|
964
|
+
unit: addForm.unit.trim()
|
|
965
|
+
});
|
|
966
|
+
};
|
|
967
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
968
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
969
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
970
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Глобальные атрибуты" }),
|
|
971
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Применяются ко всем товарам автоматически. Значения у продуктов необязательны." })
|
|
972
|
+
] }),
|
|
973
|
+
!showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
|
|
974
|
+
] }),
|
|
975
|
+
isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
976
|
+
isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить." }) }),
|
|
977
|
+
!isLoading && !isError && attrs.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: "Глобальных атрибутов нет." }) }),
|
|
978
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: attrs.map(
|
|
979
|
+
(a) => confirmDeleteId === a.id ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
|
|
980
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
|
|
981
|
+
"Удалить «",
|
|
982
|
+
a.label,
|
|
983
|
+
"»?"
|
|
984
|
+
] }),
|
|
985
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(a.id), isLoading: deleteMutation.isPending, children: "Удалить" }),
|
|
986
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: "Отмена" })
|
|
987
|
+
] }, a.id) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
988
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm", children: a.label }),
|
|
989
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
|
|
990
|
+
typeLabel(a.type),
|
|
991
|
+
a.unit ? `, ${a.unit}` : ""
|
|
992
|
+
] }),
|
|
993
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
|
|
994
|
+
] }, a.id)
|
|
995
|
+
) }),
|
|
996
|
+
showAddForm && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-6 py-4", children: [
|
|
997
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: addForm.label, onChange: (e) => setAddForm((f) => ({ ...f, label: e.target.value })), placeholder: "Название", className: "flex-1 h-8 text-sm", autoFocus: true }),
|
|
998
|
+
/* @__PURE__ */ jsxRuntime.jsxs("select", { value: addForm.type, onChange: (e) => setAddForm((f) => ({ ...f, type: e.target.value, unit: e.target.value === "number" ? f.unit : "" })), className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm", children: [
|
|
999
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: "Текст" }),
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: "Число" }),
|
|
1001
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: "Файл" }),
|
|
1002
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
1003
|
+
] }),
|
|
1004
|
+
addForm.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: addForm.unit, onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })), placeholder: "ед.", className: "w-28 h-8 text-sm" }),
|
|
1005
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", onClick: handleAdd, isLoading: createMutation.isPending, children: "Создать" }),
|
|
1006
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => {
|
|
1007
|
+
setShowAddForm(false);
|
|
1008
|
+
setAddForm(emptyForm());
|
|
1009
|
+
}, children: "Отмена" })
|
|
1010
|
+
] })
|
|
1011
|
+
] });
|
|
1012
|
+
};
|
|
786
1013
|
const config = adminSdk.defineRouteConfig({
|
|
787
|
-
label: "
|
|
788
|
-
icon:
|
|
1014
|
+
label: "Global Attributes",
|
|
1015
|
+
icon: Globe
|
|
789
1016
|
});
|
|
790
1017
|
const widgetModule = { widgets: [
|
|
791
1018
|
{
|
|
@@ -800,17 +1027,29 @@ const widgetModule = { widgets: [
|
|
|
800
1027
|
const routeModule = {
|
|
801
1028
|
routes: [
|
|
802
1029
|
{
|
|
803
|
-
Component:
|
|
804
|
-
path: "/settings/
|
|
1030
|
+
Component: AttributeTemplatesSettingsPage,
|
|
1031
|
+
path: "/settings/attribute-templates"
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
Component: GlobalAttributesSettingsPage,
|
|
1035
|
+
path: "/settings/global-attributes"
|
|
805
1036
|
}
|
|
806
1037
|
]
|
|
807
1038
|
};
|
|
808
1039
|
const menuItemModule = {
|
|
809
1040
|
menuItems: [
|
|
1041
|
+
{
|
|
1042
|
+
label: config$1.label,
|
|
1043
|
+
icon: config$1.icon,
|
|
1044
|
+
path: "/settings/attribute-templates",
|
|
1045
|
+
nested: void 0,
|
|
1046
|
+
rank: void 0,
|
|
1047
|
+
translationNs: void 0
|
|
1048
|
+
},
|
|
810
1049
|
{
|
|
811
1050
|
label: config.label,
|
|
812
1051
|
icon: config.icon,
|
|
813
|
-
path: "/settings/
|
|
1052
|
+
path: "/settings/global-attributes",
|
|
814
1053
|
nested: void 0,
|
|
815
1054
|
rank: void 0,
|
|
816
1055
|
translationNs: void 0
|