@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
|
@@ -3,7 +3,7 @@ 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
5
|
import * as React from "react";
|
|
6
|
-
import { useState, useRef, useEffect } from "react";
|
|
6
|
+
import { useMemo, useState, useRef, useEffect } from "react";
|
|
7
7
|
import Medusa from "@medusajs/js-sdk";
|
|
8
8
|
const sdk = new Medusa({
|
|
9
9
|
baseUrl: "/",
|
|
@@ -12,35 +12,201 @@ const sdk = new Medusa({
|
|
|
12
12
|
type: "session"
|
|
13
13
|
}
|
|
14
14
|
});
|
|
15
|
-
const
|
|
15
|
+
const attributes$1 = "Атрибуты";
|
|
16
|
+
const characteristics$1 = "Характеристики";
|
|
17
|
+
const inherited$1 = "Унаследованные";
|
|
18
|
+
const own$1 = "Свои";
|
|
19
|
+
const global$1 = "глобальный";
|
|
20
|
+
const fromParent$1 = "из родителя";
|
|
21
|
+
const add$1 = "Добавить";
|
|
22
|
+
const fromTemplate$1 = "Из шаблона";
|
|
23
|
+
const chooseTemplate$1 = "Выберите шаблон";
|
|
24
|
+
const close$1 = "Закрыть";
|
|
25
|
+
const loading$1 = "Загрузка…";
|
|
26
|
+
const loadFailed$1 = "Не удалось загрузить.";
|
|
27
|
+
const noAttributes$1 = "Нет атрибутов. Добавьте первый.";
|
|
28
|
+
const noTemplates$1 = "Шаблонов нет. Создайте в Settings → Attribute Templates.";
|
|
29
|
+
const noGlobals$1 = "Глобальных атрибутов нет.";
|
|
30
|
+
const cancel$1 = "Отмена";
|
|
31
|
+
const confirmDelete$1 = "Удалить «{name}»?";
|
|
32
|
+
const create$1 = "Создать";
|
|
33
|
+
const save$1 = "Сохранить";
|
|
34
|
+
const saved$1 = "Сохранено ✓";
|
|
35
|
+
const name$1 = "Название";
|
|
36
|
+
const description$1 = "Описание (необязательно)";
|
|
37
|
+
const unit$1 = "ед.";
|
|
38
|
+
const value$1 = "Значение";
|
|
39
|
+
const yes$1 = "Да";
|
|
40
|
+
const no$1 = "Нет";
|
|
41
|
+
const upload$1 = "Загрузить";
|
|
42
|
+
const uploadError$1 = "Ошибка загрузки файла";
|
|
43
|
+
const fileUrl$1 = "URL файла";
|
|
44
|
+
const templates$1 = "Шаблоны атрибутов";
|
|
45
|
+
const templatesDesc$1 = "Заготовки атрибутов, которые можно применить к любой категории.";
|
|
46
|
+
const globals$1 = "Глобальные атрибуты";
|
|
47
|
+
const globalsDesc$1 = "Применяются ко всем товарам автоматически. Значения у продуктов необязательны.";
|
|
48
|
+
const ru = {
|
|
49
|
+
attributes: attributes$1,
|
|
50
|
+
characteristics: characteristics$1,
|
|
51
|
+
inherited: inherited$1,
|
|
52
|
+
own: own$1,
|
|
53
|
+
global: global$1,
|
|
54
|
+
fromParent: fromParent$1,
|
|
55
|
+
add: add$1,
|
|
56
|
+
fromTemplate: fromTemplate$1,
|
|
57
|
+
chooseTemplate: chooseTemplate$1,
|
|
58
|
+
close: close$1,
|
|
59
|
+
loading: loading$1,
|
|
60
|
+
loadFailed: loadFailed$1,
|
|
61
|
+
noAttributes: noAttributes$1,
|
|
62
|
+
noTemplates: noTemplates$1,
|
|
63
|
+
noGlobals: noGlobals$1,
|
|
64
|
+
"delete": "Удалить",
|
|
65
|
+
cancel: cancel$1,
|
|
66
|
+
confirmDelete: confirmDelete$1,
|
|
67
|
+
create: create$1,
|
|
68
|
+
save: save$1,
|
|
69
|
+
saved: saved$1,
|
|
70
|
+
name: name$1,
|
|
71
|
+
description: description$1,
|
|
72
|
+
unit: unit$1,
|
|
73
|
+
value: value$1,
|
|
74
|
+
"type.text": "Текст",
|
|
75
|
+
"type.number": "Число",
|
|
76
|
+
"type.file": "Файл",
|
|
77
|
+
"type.boolean": "Да/Нет",
|
|
78
|
+
yes: yes$1,
|
|
79
|
+
no: no$1,
|
|
80
|
+
upload: upload$1,
|
|
81
|
+
uploadError: uploadError$1,
|
|
82
|
+
fileUrl: fileUrl$1,
|
|
83
|
+
templates: templates$1,
|
|
84
|
+
templatesDesc: templatesDesc$1,
|
|
85
|
+
globals: globals$1,
|
|
86
|
+
globalsDesc: globalsDesc$1
|
|
87
|
+
};
|
|
88
|
+
const attributes = "Attributes";
|
|
89
|
+
const characteristics = "Characteristics";
|
|
90
|
+
const inherited = "Inherited";
|
|
91
|
+
const own = "Own";
|
|
92
|
+
const global = "global";
|
|
93
|
+
const fromParent = "from parent";
|
|
94
|
+
const add = "Add";
|
|
95
|
+
const fromTemplate = "From template";
|
|
96
|
+
const chooseTemplate = "Choose a template";
|
|
97
|
+
const close = "Close";
|
|
98
|
+
const loading = "Loading…";
|
|
99
|
+
const loadFailed = "Failed to load.";
|
|
100
|
+
const noAttributes = "No attributes. Add your first.";
|
|
101
|
+
const noTemplates = "No templates. Create in Settings → Attribute Templates.";
|
|
102
|
+
const noGlobals = "No global attributes.";
|
|
103
|
+
const cancel = "Cancel";
|
|
104
|
+
const confirmDelete = "Delete «{name}»?";
|
|
105
|
+
const create = "Create";
|
|
106
|
+
const save = "Save";
|
|
107
|
+
const saved = "Saved ✓";
|
|
108
|
+
const name = "Name";
|
|
109
|
+
const description = "Description (optional)";
|
|
110
|
+
const unit = "unit";
|
|
111
|
+
const value = "Value";
|
|
112
|
+
const yes = "Yes";
|
|
113
|
+
const no = "No";
|
|
114
|
+
const upload = "Upload";
|
|
115
|
+
const uploadError = "Upload failed";
|
|
116
|
+
const fileUrl = "File URL";
|
|
117
|
+
const templates = "Attribute Templates";
|
|
118
|
+
const templatesDesc = "Reusable attribute blueprints. Apply to any category in one click.";
|
|
119
|
+
const globals = "Global Attributes";
|
|
120
|
+
const globalsDesc = "Applied to every product automatically. Product values are optional.";
|
|
121
|
+
const en = {
|
|
122
|
+
attributes,
|
|
123
|
+
characteristics,
|
|
124
|
+
inherited,
|
|
125
|
+
own,
|
|
126
|
+
global,
|
|
127
|
+
fromParent,
|
|
128
|
+
add,
|
|
129
|
+
fromTemplate,
|
|
130
|
+
chooseTemplate,
|
|
131
|
+
close,
|
|
132
|
+
loading,
|
|
133
|
+
loadFailed,
|
|
134
|
+
noAttributes,
|
|
135
|
+
noTemplates,
|
|
136
|
+
noGlobals,
|
|
137
|
+
"delete": "Delete",
|
|
138
|
+
cancel,
|
|
139
|
+
confirmDelete,
|
|
140
|
+
create,
|
|
141
|
+
save,
|
|
142
|
+
saved,
|
|
143
|
+
name,
|
|
144
|
+
description,
|
|
145
|
+
unit,
|
|
146
|
+
value,
|
|
147
|
+
"type.text": "Text",
|
|
148
|
+
"type.number": "Number",
|
|
149
|
+
"type.file": "File",
|
|
150
|
+
"type.boolean": "Yes/No",
|
|
151
|
+
yes,
|
|
152
|
+
no,
|
|
153
|
+
upload,
|
|
154
|
+
uploadError,
|
|
155
|
+
fileUrl,
|
|
156
|
+
templates,
|
|
157
|
+
templatesDesc,
|
|
158
|
+
globals,
|
|
159
|
+
globalsDesc
|
|
160
|
+
};
|
|
161
|
+
const locales = { ru, en };
|
|
162
|
+
function detectLang() {
|
|
163
|
+
var _a;
|
|
164
|
+
if (typeof window === "undefined") return "en";
|
|
165
|
+
const stored = window.localStorage.getItem("i18nextLng");
|
|
166
|
+
if (stored) {
|
|
167
|
+
const short = stored.slice(0, 2).toLowerCase();
|
|
168
|
+
if (locales[short]) return short;
|
|
169
|
+
}
|
|
170
|
+
const nav = (((_a = window.navigator) == null ? void 0 : _a.language) || "en").slice(0, 2).toLowerCase();
|
|
171
|
+
return locales[nav] ? nav : "en";
|
|
172
|
+
}
|
|
173
|
+
function useT() {
|
|
174
|
+
const dict = useMemo(() => {
|
|
175
|
+
const lang = detectLang();
|
|
176
|
+
return locales[lang] ?? locales.en;
|
|
177
|
+
}, []);
|
|
178
|
+
return (key, fallback) => dict[key] ?? fallback ?? key;
|
|
179
|
+
}
|
|
180
|
+
const emptyForm$2 = () => ({ label: "", type: "text", unit: "" });
|
|
16
181
|
const CategoryAttributeTemplatesWidget = ({
|
|
17
182
|
data
|
|
18
183
|
}) => {
|
|
19
184
|
var _a, _b;
|
|
20
185
|
const categoryId = data.id;
|
|
186
|
+
const t = useT();
|
|
21
187
|
const qc = useQueryClient();
|
|
22
188
|
const queryKey = ["category-custom-attributes", categoryId];
|
|
23
189
|
const [showAddForm, setShowAddForm] = useState(false);
|
|
24
|
-
const [
|
|
25
|
-
const [addForm, setAddForm] = useState(emptyForm$
|
|
190
|
+
const [showTemplateList, setShowTemplateList] = useState(false);
|
|
191
|
+
const [addForm, setAddForm] = useState(emptyForm$2());
|
|
26
192
|
const [confirmDeleteId, setConfirmDeleteId] = useState(null);
|
|
27
193
|
const [mutationError, setMutationError] = useState(null);
|
|
28
|
-
const
|
|
29
|
-
queryKey: ["attribute-
|
|
30
|
-
queryFn: () => sdk.client.fetch(`/admin/attribute-
|
|
31
|
-
enabled:
|
|
194
|
+
const templatesQuery = useQuery({
|
|
195
|
+
queryKey: ["attribute-templates"],
|
|
196
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-templates`),
|
|
197
|
+
enabled: showTemplateList
|
|
32
198
|
});
|
|
33
|
-
const
|
|
34
|
-
mutationFn: (
|
|
199
|
+
const applyTemplateMutation = useMutation({
|
|
200
|
+
mutationFn: (templateId) => sdk.client.fetch(`/admin/attribute-templates/${templateId}/apply`, {
|
|
35
201
|
method: "POST",
|
|
36
202
|
body: { category_id: categoryId }
|
|
37
203
|
}),
|
|
38
204
|
onSuccess: () => {
|
|
39
205
|
qc.invalidateQueries({ queryKey });
|
|
40
|
-
|
|
206
|
+
setShowTemplateList(false);
|
|
41
207
|
},
|
|
42
208
|
onError: (err) => {
|
|
43
|
-
setMutationError((err == null ? void 0 : err.message) || "Ошибка при применении
|
|
209
|
+
setMutationError((err == null ? void 0 : err.message) || "Ошибка при применении шаблона");
|
|
44
210
|
}
|
|
45
211
|
});
|
|
46
212
|
const {
|
|
@@ -51,9 +217,9 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
51
217
|
queryKey,
|
|
52
218
|
queryFn: () => sdk.client.fetch(`/admin/category/${categoryId}/custom-attributes`)
|
|
53
219
|
});
|
|
54
|
-
const
|
|
55
|
-
const ownAttributes =
|
|
56
|
-
const inheritedAttributes =
|
|
220
|
+
const attributes2 = (result == null ? void 0 : result.category_custom_attributes) ?? [];
|
|
221
|
+
const ownAttributes = attributes2.filter((a) => !a.inherited);
|
|
222
|
+
const inheritedAttributes = attributes2.filter((a) => a.inherited);
|
|
57
223
|
const createMutation = useMutation({
|
|
58
224
|
mutationFn: (body) => sdk.client.fetch(`/admin/category/${categoryId}/custom-attributes`, {
|
|
59
225
|
method: "POST",
|
|
@@ -62,7 +228,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
62
228
|
onSuccess: () => {
|
|
63
229
|
qc.invalidateQueries({ queryKey });
|
|
64
230
|
setShowAddForm(false);
|
|
65
|
-
setAddForm(emptyForm$
|
|
231
|
+
setAddForm(emptyForm$2());
|
|
66
232
|
setMutationError(null);
|
|
67
233
|
},
|
|
68
234
|
onError: (err) => {
|
|
@@ -87,51 +253,54 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
87
253
|
unit: addForm.type === "number" && addForm.unit.trim() ? addForm.unit.trim() : null
|
|
88
254
|
});
|
|
89
255
|
};
|
|
90
|
-
const typeLabel2 = (
|
|
256
|
+
const typeLabel2 = (v) => t(`type.${v}`, v);
|
|
91
257
|
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
92
258
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
93
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "
|
|
94
|
-
!showAddForm && !
|
|
259
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: t("attributes") }),
|
|
260
|
+
!showAddForm && !showTemplateList && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
95
261
|
/* @__PURE__ */ jsx(
|
|
96
262
|
Button,
|
|
97
263
|
{
|
|
98
264
|
variant: "secondary",
|
|
99
265
|
size: "small",
|
|
100
|
-
onClick: () =>
|
|
101
|
-
children: "
|
|
266
|
+
onClick: () => setShowTemplateList(true),
|
|
267
|
+
children: t("fromTemplate")
|
|
102
268
|
}
|
|
103
269
|
),
|
|
104
|
-
/* @__PURE__ */
|
|
270
|
+
/* @__PURE__ */ jsxs(
|
|
105
271
|
Button,
|
|
106
272
|
{
|
|
107
273
|
variant: "secondary",
|
|
108
274
|
size: "small",
|
|
109
275
|
onClick: () => setShowAddForm(true),
|
|
110
|
-
children:
|
|
276
|
+
children: [
|
|
277
|
+
"+ ",
|
|
278
|
+
t("add")
|
|
279
|
+
]
|
|
111
280
|
}
|
|
112
281
|
)
|
|
113
282
|
] })
|
|
114
283
|
] }),
|
|
115
|
-
|
|
284
|
+
showTemplateList && /* @__PURE__ */ jsxs("div", { className: "px-6 py-3", children: [
|
|
116
285
|
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
117
|
-
/* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Выберите
|
|
286
|
+
/* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Выберите шаблон" }),
|
|
118
287
|
/* @__PURE__ */ jsx(
|
|
119
288
|
Button,
|
|
120
289
|
{
|
|
121
290
|
size: "small",
|
|
122
291
|
variant: "secondary",
|
|
123
|
-
onClick: () =>
|
|
292
|
+
onClick: () => setShowTemplateList(false),
|
|
124
293
|
children: "Закрыть"
|
|
125
294
|
}
|
|
126
295
|
)
|
|
127
296
|
] }),
|
|
128
|
-
|
|
129
|
-
((_a =
|
|
130
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: (_b =
|
|
297
|
+
templatesQuery.isLoading && /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: t("loading") }),
|
|
298
|
+
((_a = templatesQuery.data) == null ? void 0 : _a.attribute_templates.length) === 0 && /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Пресетов нет. Создайте в настройках Product Attributes." }),
|
|
299
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-1", children: (_b = templatesQuery.data) == null ? void 0 : _b.attribute_templates.map((p) => /* @__PURE__ */ jsxs(
|
|
131
300
|
"button",
|
|
132
301
|
{
|
|
133
|
-
onClick: () =>
|
|
134
|
-
disabled:
|
|
302
|
+
onClick: () => applyTemplateMutation.mutate(p.id),
|
|
303
|
+
disabled: applyTemplateMutation.isPending,
|
|
135
304
|
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
305
|
children: [
|
|
137
306
|
/* @__PURE__ */ jsx("span", { children: p.label }),
|
|
@@ -144,7 +313,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
144
313
|
p.id
|
|
145
314
|
)) })
|
|
146
315
|
] }),
|
|
147
|
-
isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "
|
|
316
|
+
isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: t("loading") }) }),
|
|
148
317
|
isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить атрибуты." }) }),
|
|
149
318
|
inheritedAttributes.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
319
|
/* @__PURE__ */ jsx("div", { className: "px-6 py-2 bg-ui-bg-subtle", children: /* @__PURE__ */ jsx(Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-muted uppercase", children: "Унаследованные" }) }),
|
|
@@ -223,7 +392,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
223
392
|
)
|
|
224
393
|
) })
|
|
225
394
|
] }),
|
|
226
|
-
!isLoading && !isError &&
|
|
395
|
+
!isLoading && !isError && attributes2.length === 0 && !showAddForm && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Нет атрибутов. Добавьте первый." }) }),
|
|
227
396
|
showAddForm && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-6 py-3", children: [
|
|
228
397
|
/* @__PURE__ */ jsx(
|
|
229
398
|
Input,
|
|
@@ -278,7 +447,7 @@ const CategoryAttributeTemplatesWidget = ({
|
|
|
278
447
|
size: "small",
|
|
279
448
|
onClick: () => {
|
|
280
449
|
setShowAddForm(false);
|
|
281
|
-
setAddForm(emptyForm$
|
|
450
|
+
setAddForm(emptyForm$2());
|
|
282
451
|
setMutationError(null);
|
|
283
452
|
},
|
|
284
453
|
children: "Отмена"
|
|
@@ -294,19 +463,17 @@ defineWidgetConfig({
|
|
|
294
463
|
const ProductAttributeValuesWidget = ({
|
|
295
464
|
data
|
|
296
465
|
}) => {
|
|
297
|
-
var _a
|
|
466
|
+
var _a;
|
|
298
467
|
const productId = data.id;
|
|
299
468
|
const productHandle = data.handle || productId;
|
|
300
|
-
const categoryId = ((_b = (_a = data.categories) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? null;
|
|
301
469
|
const qc = useQueryClient();
|
|
302
470
|
const [formValues, setFormValues] = useState({});
|
|
303
471
|
const [saveSuccess, setSaveSuccess] = useState(false);
|
|
304
472
|
const [uploadingId, setUploadingId] = useState(null);
|
|
305
473
|
const fileInputs = useRef({});
|
|
306
474
|
const attributesQuery = useQuery({
|
|
307
|
-
queryKey: ["
|
|
308
|
-
queryFn: () => sdk.client.fetch(`/admin/
|
|
309
|
-
enabled: !!categoryId
|
|
475
|
+
queryKey: ["product-attribute-schema", productId],
|
|
476
|
+
queryFn: () => sdk.client.fetch(`/admin/product/${productId}/attribute-schema`)
|
|
310
477
|
});
|
|
311
478
|
const valuesQuery = useQuery({
|
|
312
479
|
queryKey: ["product-custom-attributes", productId],
|
|
@@ -314,10 +481,10 @@ const ProductAttributeValuesWidget = ({
|
|
|
314
481
|
});
|
|
315
482
|
useEffect(() => {
|
|
316
483
|
if (!attributesQuery.data || !valuesQuery.data) return;
|
|
317
|
-
const
|
|
484
|
+
const attributes22 = attributesQuery.data.attributes;
|
|
318
485
|
const values = valuesQuery.data.product_custom_attributes;
|
|
319
486
|
const initial = {};
|
|
320
|
-
for (const attr of
|
|
487
|
+
for (const attr of attributes22) {
|
|
321
488
|
const existing = values.find(
|
|
322
489
|
(v) => {
|
|
323
490
|
var _a2;
|
|
@@ -343,12 +510,12 @@ const ProductAttributeValuesWidget = ({
|
|
|
343
510
|
});
|
|
344
511
|
const handleSave = () => {
|
|
345
512
|
if (!attributesQuery.data) return;
|
|
346
|
-
const
|
|
347
|
-
const attributesToUpdate =
|
|
513
|
+
const attributes22 = attributesQuery.data.attributes;
|
|
514
|
+
const attributesToUpdate = attributes22.filter((attr) => formValues[attr.id] !== void 0).map((attr) => ({ id: attr.id, value: formValues[attr.id] ?? "" }));
|
|
348
515
|
saveMutation.mutate({ attributes: attributesToUpdate });
|
|
349
516
|
};
|
|
350
517
|
const handleFileUpload = async (attr, file) => {
|
|
351
|
-
var _a2,
|
|
518
|
+
var _a2, _b;
|
|
352
519
|
const attrId = attr.id;
|
|
353
520
|
setUploadingId(attrId);
|
|
354
521
|
try {
|
|
@@ -377,7 +544,7 @@ const ProductAttributeValuesWidget = ({
|
|
|
377
544
|
return;
|
|
378
545
|
}
|
|
379
546
|
const res = await response.json();
|
|
380
|
-
const url = (
|
|
547
|
+
const url = (_b = (_a2 = res == null ? void 0 : res.files) == null ? void 0 : _a2[0]) == null ? void 0 : _b.url;
|
|
381
548
|
if (url) {
|
|
382
549
|
setFormValues((prev) => ({ ...prev, [attrId]: url }));
|
|
383
550
|
} else {
|
|
@@ -391,22 +558,16 @@ const ProductAttributeValuesWidget = ({
|
|
|
391
558
|
setUploadingId(null);
|
|
392
559
|
}
|
|
393
560
|
};
|
|
394
|
-
if (!categoryId) {
|
|
395
|
-
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
396
|
-
/* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Характеристики" }) }),
|
|
397
|
-
/* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Назначьте категорию товару, чтобы заполнить характеристики." }) })
|
|
398
|
-
] });
|
|
399
|
-
}
|
|
400
561
|
const isLoading = attributesQuery.isLoading || valuesQuery.isLoading;
|
|
401
562
|
const isError = attributesQuery.isError || valuesQuery.isError;
|
|
402
|
-
const
|
|
563
|
+
const attributes2 = ((_a = attributesQuery.data) == null ? void 0 : _a.attributes) ?? [];
|
|
403
564
|
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
404
565
|
/* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Характеристики" }) }),
|
|
405
566
|
isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
406
567
|
isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить характеристики." }) }),
|
|
407
|
-
!isLoading && !isError &&
|
|
408
|
-
!isLoading && !isError &&
|
|
409
|
-
/* @__PURE__ */ jsx("div", { className: "divide-y", children:
|
|
568
|
+
!isLoading && !isError && attributes2.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "В категории нет атрибутов. Добавьте их в настройках категории." }) }),
|
|
569
|
+
!isLoading && !isError && attributes2.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
570
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y", children: attributes2.map((attr) => /* @__PURE__ */ jsxs(
|
|
410
571
|
"div",
|
|
411
572
|
{
|
|
412
573
|
className: "grid grid-cols-2 items-center gap-4 px-6 py-3",
|
|
@@ -504,11 +665,76 @@ const ProductAttributeValuesWidget = ({
|
|
|
504
665
|
defineWidgetConfig({
|
|
505
666
|
zone: "product.details.after"
|
|
506
667
|
});
|
|
668
|
+
var __defProp$1 = Object.defineProperty;
|
|
669
|
+
var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
|
|
670
|
+
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
|
|
671
|
+
var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
|
|
672
|
+
var __defNormalProp$1 = (obj, key, value2) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
673
|
+
var __spreadValues$1 = (a, b) => {
|
|
674
|
+
for (var prop in b || (b = {}))
|
|
675
|
+
if (__hasOwnProp$1.call(b, prop))
|
|
676
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
677
|
+
if (__getOwnPropSymbols$1)
|
|
678
|
+
for (var prop of __getOwnPropSymbols$1(b)) {
|
|
679
|
+
if (__propIsEnum$1.call(b, prop))
|
|
680
|
+
__defNormalProp$1(a, prop, b[prop]);
|
|
681
|
+
}
|
|
682
|
+
return a;
|
|
683
|
+
};
|
|
684
|
+
var __objRest$1 = (source, exclude) => {
|
|
685
|
+
var target = {};
|
|
686
|
+
for (var prop in source)
|
|
687
|
+
if (__hasOwnProp$1.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
688
|
+
target[prop] = source[prop];
|
|
689
|
+
if (source != null && __getOwnPropSymbols$1)
|
|
690
|
+
for (var prop of __getOwnPropSymbols$1(source)) {
|
|
691
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum$1.call(source, prop))
|
|
692
|
+
target[prop] = source[prop];
|
|
693
|
+
}
|
|
694
|
+
return target;
|
|
695
|
+
};
|
|
696
|
+
const Globe = React.forwardRef(
|
|
697
|
+
(_a, ref) => {
|
|
698
|
+
var _b = _a, { color = "currentColor" } = _b, props = __objRest$1(_b, ["color"]);
|
|
699
|
+
return /* @__PURE__ */ React.createElement(
|
|
700
|
+
"svg",
|
|
701
|
+
__spreadValues$1({
|
|
702
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
703
|
+
width: 15,
|
|
704
|
+
height: 15,
|
|
705
|
+
viewBox: "0 0 15 15",
|
|
706
|
+
fill: "none",
|
|
707
|
+
ref
|
|
708
|
+
}, props),
|
|
709
|
+
/* @__PURE__ */ React.createElement(
|
|
710
|
+
"path",
|
|
711
|
+
{
|
|
712
|
+
stroke: color,
|
|
713
|
+
strokeLinecap: "round",
|
|
714
|
+
strokeLinejoin: "round",
|
|
715
|
+
strokeWidth: 1.5,
|
|
716
|
+
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"
|
|
717
|
+
}
|
|
718
|
+
),
|
|
719
|
+
/* @__PURE__ */ React.createElement(
|
|
720
|
+
"path",
|
|
721
|
+
{
|
|
722
|
+
stroke: color,
|
|
723
|
+
strokeLinecap: "round",
|
|
724
|
+
strokeLinejoin: "round",
|
|
725
|
+
strokeWidth: 1.5,
|
|
726
|
+
d: "M7.5 13.945a6.444 6.444 0 1 0 0-12.89 6.444 6.444 0 0 0 0 12.89"
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
);
|
|
732
|
+
Globe.displayName = "Globe";
|
|
507
733
|
var __defProp = Object.defineProperty;
|
|
508
734
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
509
735
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
510
736
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
511
|
-
var __defNormalProp = (obj, key,
|
|
737
|
+
var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj[key] = value2;
|
|
512
738
|
var __spreadValues = (a, b) => {
|
|
513
739
|
for (var prop in b || (b = {}))
|
|
514
740
|
if (__hasOwnProp.call(b, prop))
|
|
@@ -532,7 +758,7 @@ var __objRest = (source, exclude) => {
|
|
|
532
758
|
}
|
|
533
759
|
return target;
|
|
534
760
|
};
|
|
535
|
-
const
|
|
761
|
+
const SquaresPlus = React.forwardRef(
|
|
536
762
|
(_a, ref) => {
|
|
537
763
|
var _b = _a, { color = "currentColor" } = _b, props = __objRest(_b, ["color"]);
|
|
538
764
|
return /* @__PURE__ */ React.createElement(
|
|
@@ -546,42 +772,39 @@ const CogSixTooth = React.forwardRef(
|
|
|
546
772
|
ref
|
|
547
773
|
}, props),
|
|
548
774
|
/* @__PURE__ */ React.createElement(
|
|
549
|
-
"
|
|
775
|
+
"path",
|
|
550
776
|
{
|
|
551
777
|
stroke: color,
|
|
552
778
|
strokeLinecap: "round",
|
|
553
779
|
strokeLinejoin: "round",
|
|
554
780
|
strokeWidth: 1.5,
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/* @__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" })
|
|
559
|
-
),
|
|
560
|
-
/* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("clipPath", { id: "a" }, /* @__PURE__ */ React.createElement("path", { fill: "#fff", d: "M0 0h15v15H0z" })))
|
|
781
|
+
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"
|
|
782
|
+
}
|
|
783
|
+
)
|
|
561
784
|
);
|
|
562
785
|
}
|
|
563
786
|
);
|
|
564
|
-
|
|
565
|
-
const emptyForm = () => ({
|
|
787
|
+
SquaresPlus.displayName = "SquaresPlus";
|
|
788
|
+
const emptyForm$1 = () => ({
|
|
566
789
|
label: "",
|
|
567
790
|
type: "text",
|
|
568
791
|
unit: "",
|
|
569
792
|
description: ""
|
|
570
793
|
});
|
|
571
|
-
const typeLabel = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
572
|
-
const
|
|
794
|
+
const typeLabel$1 = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
795
|
+
const AttributeTemplatesSettingsPage = () => {
|
|
573
796
|
const qc = useQueryClient();
|
|
574
|
-
const queryKey = ["attribute-
|
|
797
|
+
const queryKey = ["attribute-templates"];
|
|
575
798
|
const [showAddForm, setShowAddForm] = useState(false);
|
|
576
|
-
const [addForm, setAddForm] = useState(emptyForm());
|
|
799
|
+
const [addForm, setAddForm] = useState(emptyForm$1());
|
|
577
800
|
const [confirmDeleteId, setConfirmDeleteId] = useState(null);
|
|
578
801
|
const { data, isLoading, isError } = useQuery({
|
|
579
802
|
queryKey,
|
|
580
|
-
queryFn: () => sdk.client.fetch(`/admin/attribute-
|
|
803
|
+
queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
|
|
581
804
|
});
|
|
582
|
-
const
|
|
805
|
+
const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
|
|
583
806
|
const createMutation = useMutation({
|
|
584
|
-
mutationFn: (body) => sdk.client.fetch(`/admin/attribute-
|
|
807
|
+
mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
|
|
585
808
|
method: "POST",
|
|
586
809
|
body: {
|
|
587
810
|
label: body.label,
|
|
@@ -593,11 +816,11 @@ const ProductAttributesSettingsPage = () => {
|
|
|
593
816
|
onSuccess: () => {
|
|
594
817
|
qc.invalidateQueries({ queryKey });
|
|
595
818
|
setShowAddForm(false);
|
|
596
|
-
setAddForm(emptyForm());
|
|
819
|
+
setAddForm(emptyForm$1());
|
|
597
820
|
}
|
|
598
821
|
});
|
|
599
822
|
const deleteMutation = useMutation({
|
|
600
|
-
mutationFn: (id) => sdk.client.fetch(`/admin/attribute-
|
|
823
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
|
|
601
824
|
method: "PATCH",
|
|
602
825
|
body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
603
826
|
}),
|
|
@@ -618,154 +841,158 @@ const ProductAttributesSettingsPage = () => {
|
|
|
618
841
|
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
619
842
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
620
843
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
621
|
-
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "
|
|
622
|
-
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "
|
|
844
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "Шаблоны атрибутов" }),
|
|
845
|
+
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Заготовки атрибутов, которые можно применить к любой категории." })
|
|
623
846
|
] }),
|
|
624
|
-
!showAddForm && /* @__PURE__ */ jsx(
|
|
625
|
-
Button,
|
|
626
|
-
{
|
|
627
|
-
variant: "secondary",
|
|
628
|
-
size: "small",
|
|
629
|
-
onClick: () => setShowAddForm(true),
|
|
630
|
-
children: "+ Добавить пресет"
|
|
631
|
-
}
|
|
632
|
-
)
|
|
847
|
+
!showAddForm && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
|
|
633
848
|
] }),
|
|
634
849
|
isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
635
|
-
isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить
|
|
636
|
-
!isLoading && !isError &&
|
|
637
|
-
/* @__PURE__ */ jsx("div", { className: "divide-y", children:
|
|
638
|
-
(p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxs(
|
|
639
|
-
"
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
] }),
|
|
648
|
-
/* @__PURE__ */ jsx(
|
|
649
|
-
Button,
|
|
650
|
-
{
|
|
651
|
-
size: "small",
|
|
652
|
-
variant: "danger",
|
|
653
|
-
onClick: () => deleteMutation.mutate(p.id),
|
|
654
|
-
isLoading: deleteMutation.isPending,
|
|
655
|
-
children: "Удалить"
|
|
656
|
-
}
|
|
657
|
-
),
|
|
658
|
-
/* @__PURE__ */ jsx(
|
|
659
|
-
Button,
|
|
660
|
-
{
|
|
661
|
-
size: "small",
|
|
662
|
-
variant: "secondary",
|
|
663
|
-
onClick: () => setConfirmDeleteId(null),
|
|
664
|
-
children: "Отмена"
|
|
665
|
-
}
|
|
666
|
-
)
|
|
667
|
-
]
|
|
668
|
-
},
|
|
669
|
-
p.id
|
|
670
|
-
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
850
|
+
isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить шаблоны." }) }),
|
|
851
|
+
!isLoading && !isError && templates2.length === 0 && !showAddForm && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Шаблонов пока нет." }) }),
|
|
852
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y", children: templates2.map(
|
|
853
|
+
(p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
|
|
854
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
|
|
855
|
+
"Удалить «",
|
|
856
|
+
p.label,
|
|
857
|
+
"»?"
|
|
858
|
+
] }),
|
|
859
|
+
/* @__PURE__ */ jsx(Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(p.id), isLoading: deleteMutation.isPending, children: "Удалить" }),
|
|
860
|
+
/* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: "Отмена" })
|
|
861
|
+
] }, p.id) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
671
862
|
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
672
863
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
673
864
|
/* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: p.label }),
|
|
674
865
|
/* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
|
|
675
|
-
typeLabel(p.type),
|
|
866
|
+
typeLabel$1(p.type),
|
|
676
867
|
p.unit ? `, ${p.unit}` : ""
|
|
677
868
|
] })
|
|
678
869
|
] }),
|
|
679
870
|
p.description && /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
|
|
680
871
|
] }),
|
|
681
|
-
/* @__PURE__ */ jsx(
|
|
682
|
-
"button",
|
|
683
|
-
{
|
|
684
|
-
onClick: () => setConfirmDeleteId(p.id),
|
|
685
|
-
className: "text-xs text-ui-fg-error hover:underline",
|
|
686
|
-
children: "Удалить"
|
|
687
|
-
}
|
|
688
|
-
)
|
|
872
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
|
|
689
873
|
] }, p.id)
|
|
690
874
|
) }),
|
|
691
875
|
showAddForm && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
|
|
692
876
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
693
|
-
/* @__PURE__ */ jsx(
|
|
694
|
-
|
|
695
|
-
{
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
),
|
|
703
|
-
/* @__PURE__ */ jsxs(
|
|
704
|
-
"select",
|
|
705
|
-
{
|
|
706
|
-
value: addForm.type,
|
|
707
|
-
onChange: (e) => setAddForm((f) => ({
|
|
708
|
-
...f,
|
|
709
|
-
type: e.target.value,
|
|
710
|
-
unit: e.target.value === "number" ? f.unit : ""
|
|
711
|
-
})),
|
|
712
|
-
className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
|
|
713
|
-
children: [
|
|
714
|
-
/* @__PURE__ */ jsx("option", { value: "text", children: "Текст" }),
|
|
715
|
-
/* @__PURE__ */ jsx("option", { value: "number", children: "Число" }),
|
|
716
|
-
/* @__PURE__ */ jsx("option", { value: "file", children: "Файл" }),
|
|
717
|
-
/* @__PURE__ */ jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
718
|
-
]
|
|
719
|
-
}
|
|
720
|
-
),
|
|
721
|
-
addForm.type === "number" && /* @__PURE__ */ jsx(
|
|
722
|
-
Input,
|
|
723
|
-
{
|
|
724
|
-
value: addForm.unit,
|
|
725
|
-
onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })),
|
|
726
|
-
placeholder: "ед. (кг, м...)",
|
|
727
|
-
className: "w-28 h-8 text-sm"
|
|
728
|
-
}
|
|
729
|
-
)
|
|
877
|
+
/* @__PURE__ */ jsx(Input, { value: addForm.label, onChange: (e) => setAddForm((f) => ({ ...f, label: e.target.value })), placeholder: "Название", className: "flex-1 h-8 text-sm", autoFocus: true }),
|
|
878
|
+
/* @__PURE__ */ 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: [
|
|
879
|
+
/* @__PURE__ */ jsx("option", { value: "text", children: "Текст" }),
|
|
880
|
+
/* @__PURE__ */ jsx("option", { value: "number", children: "Число" }),
|
|
881
|
+
/* @__PURE__ */ jsx("option", { value: "file", children: "Файл" }),
|
|
882
|
+
/* @__PURE__ */ jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
883
|
+
] }),
|
|
884
|
+
addForm.type === "number" && /* @__PURE__ */ jsx(Input, { value: addForm.unit, onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })), placeholder: "ед.", className: "w-28 h-8 text-sm" })
|
|
730
885
|
] }),
|
|
731
|
-
/* @__PURE__ */ jsx(
|
|
732
|
-
Input,
|
|
733
|
-
{
|
|
734
|
-
value: addForm.description,
|
|
735
|
-
onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })),
|
|
736
|
-
placeholder: "Описание (необязательно)",
|
|
737
|
-
className: "h-8 text-sm"
|
|
738
|
-
}
|
|
739
|
-
),
|
|
886
|
+
/* @__PURE__ */ jsx(Input, { value: addForm.description, onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })), placeholder: "Описание (необязательно)", className: "h-8 text-sm" }),
|
|
740
887
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
741
|
-
/* @__PURE__ */ jsx(
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
onClick: () => {
|
|
747
|
-
setShowAddForm(false);
|
|
748
|
-
setAddForm(emptyForm());
|
|
749
|
-
},
|
|
750
|
-
children: "Отмена"
|
|
751
|
-
}
|
|
752
|
-
),
|
|
753
|
-
/* @__PURE__ */ jsx(
|
|
754
|
-
Button,
|
|
755
|
-
{
|
|
756
|
-
size: "small",
|
|
757
|
-
onClick: handleAdd,
|
|
758
|
-
isLoading: createMutation.isPending,
|
|
759
|
-
children: "Создать"
|
|
760
|
-
}
|
|
761
|
-
)
|
|
888
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => {
|
|
889
|
+
setShowAddForm(false);
|
|
890
|
+
setAddForm(emptyForm$1());
|
|
891
|
+
}, children: "Отмена" }),
|
|
892
|
+
/* @__PURE__ */ jsx(Button, { size: "small", onClick: handleAdd, isLoading: createMutation.isPending, children: "Создать" })
|
|
762
893
|
] })
|
|
763
894
|
] })
|
|
764
895
|
] });
|
|
765
896
|
};
|
|
897
|
+
const config$1 = defineRouteConfig({
|
|
898
|
+
label: "Attribute Templates",
|
|
899
|
+
icon: SquaresPlus
|
|
900
|
+
});
|
|
901
|
+
const emptyForm = () => ({ label: "", type: "text", unit: "" });
|
|
902
|
+
const typeLabel = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
|
|
903
|
+
const GlobalAttributesSettingsPage = () => {
|
|
904
|
+
const qc = useQueryClient();
|
|
905
|
+
const queryKey = ["global-attributes"];
|
|
906
|
+
const [showAddForm, setShowAddForm] = useState(false);
|
|
907
|
+
const [addForm, setAddForm] = useState(emptyForm());
|
|
908
|
+
const [confirmDeleteId, setConfirmDeleteId] = useState(null);
|
|
909
|
+
const { data, isLoading, isError } = useQuery({
|
|
910
|
+
queryKey,
|
|
911
|
+
queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
|
|
912
|
+
});
|
|
913
|
+
const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
|
|
914
|
+
const createMutation = useMutation({
|
|
915
|
+
mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
|
|
916
|
+
method: "POST",
|
|
917
|
+
body: {
|
|
918
|
+
label: body.label,
|
|
919
|
+
type: body.type,
|
|
920
|
+
unit: body.type === "number" && body.unit ? body.unit : null
|
|
921
|
+
}
|
|
922
|
+
}),
|
|
923
|
+
onSuccess: () => {
|
|
924
|
+
qc.invalidateQueries({ queryKey });
|
|
925
|
+
setShowAddForm(false);
|
|
926
|
+
setAddForm(emptyForm());
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
const deleteMutation = useMutation({
|
|
930
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
|
|
931
|
+
method: "PATCH",
|
|
932
|
+
body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
933
|
+
}),
|
|
934
|
+
onSuccess: () => {
|
|
935
|
+
qc.invalidateQueries({ queryKey });
|
|
936
|
+
setConfirmDeleteId(null);
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
const handleAdd = () => {
|
|
940
|
+
if (!addForm.label.trim()) return;
|
|
941
|
+
createMutation.mutate({
|
|
942
|
+
label: addForm.label.trim(),
|
|
943
|
+
type: addForm.type,
|
|
944
|
+
unit: addForm.unit.trim()
|
|
945
|
+
});
|
|
946
|
+
};
|
|
947
|
+
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
948
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
949
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
950
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: "Глобальные атрибуты" }),
|
|
951
|
+
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Применяются ко всем товарам автоматически. Значения у продуктов необязательны." })
|
|
952
|
+
] }),
|
|
953
|
+
!showAddForm && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
|
|
954
|
+
] }),
|
|
955
|
+
isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Загрузка…" }) }),
|
|
956
|
+
isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: "Не удалось загрузить." }) }),
|
|
957
|
+
!isLoading && !isError && attrs.length === 0 && !showAddForm && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: "Глобальных атрибутов нет." }) }),
|
|
958
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y", children: attrs.map(
|
|
959
|
+
(a) => confirmDeleteId === a.id ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
|
|
960
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
|
|
961
|
+
"Удалить «",
|
|
962
|
+
a.label,
|
|
963
|
+
"»?"
|
|
964
|
+
] }),
|
|
965
|
+
/* @__PURE__ */ jsx(Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(a.id), isLoading: deleteMutation.isPending, children: "Удалить" }),
|
|
966
|
+
/* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: "Отмена" })
|
|
967
|
+
] }, a.id) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
|
|
968
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-sm", children: a.label }),
|
|
969
|
+
/* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
|
|
970
|
+
typeLabel(a.type),
|
|
971
|
+
a.unit ? `, ${a.unit}` : ""
|
|
972
|
+
] }),
|
|
973
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
|
|
974
|
+
] }, a.id)
|
|
975
|
+
) }),
|
|
976
|
+
showAddForm && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-6 py-4", children: [
|
|
977
|
+
/* @__PURE__ */ jsx(Input, { value: addForm.label, onChange: (e) => setAddForm((f) => ({ ...f, label: e.target.value })), placeholder: "Название", className: "flex-1 h-8 text-sm", autoFocus: true }),
|
|
978
|
+
/* @__PURE__ */ 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: [
|
|
979
|
+
/* @__PURE__ */ jsx("option", { value: "text", children: "Текст" }),
|
|
980
|
+
/* @__PURE__ */ jsx("option", { value: "number", children: "Число" }),
|
|
981
|
+
/* @__PURE__ */ jsx("option", { value: "file", children: "Файл" }),
|
|
982
|
+
/* @__PURE__ */ jsx("option", { value: "boolean", children: "Да/Нет" })
|
|
983
|
+
] }),
|
|
984
|
+
addForm.type === "number" && /* @__PURE__ */ jsx(Input, { value: addForm.unit, onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })), placeholder: "ед.", className: "w-28 h-8 text-sm" }),
|
|
985
|
+
/* @__PURE__ */ jsx(Button, { size: "small", onClick: handleAdd, isLoading: createMutation.isPending, children: "Создать" }),
|
|
986
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => {
|
|
987
|
+
setShowAddForm(false);
|
|
988
|
+
setAddForm(emptyForm());
|
|
989
|
+
}, children: "Отмена" })
|
|
990
|
+
] })
|
|
991
|
+
] });
|
|
992
|
+
};
|
|
766
993
|
const config = defineRouteConfig({
|
|
767
|
-
label: "
|
|
768
|
-
icon:
|
|
994
|
+
label: "Global Attributes",
|
|
995
|
+
icon: Globe
|
|
769
996
|
});
|
|
770
997
|
const widgetModule = { widgets: [
|
|
771
998
|
{
|
|
@@ -780,17 +1007,29 @@ const widgetModule = { widgets: [
|
|
|
780
1007
|
const routeModule = {
|
|
781
1008
|
routes: [
|
|
782
1009
|
{
|
|
783
|
-
Component:
|
|
784
|
-
path: "/settings/
|
|
1010
|
+
Component: AttributeTemplatesSettingsPage,
|
|
1011
|
+
path: "/settings/attribute-templates"
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
Component: GlobalAttributesSettingsPage,
|
|
1015
|
+
path: "/settings/global-attributes"
|
|
785
1016
|
}
|
|
786
1017
|
]
|
|
787
1018
|
};
|
|
788
1019
|
const menuItemModule = {
|
|
789
1020
|
menuItems: [
|
|
1021
|
+
{
|
|
1022
|
+
label: config$1.label,
|
|
1023
|
+
icon: config$1.icon,
|
|
1024
|
+
path: "/settings/attribute-templates",
|
|
1025
|
+
nested: void 0,
|
|
1026
|
+
rank: void 0,
|
|
1027
|
+
translationNs: void 0
|
|
1028
|
+
},
|
|
790
1029
|
{
|
|
791
1030
|
label: config.label,
|
|
792
1031
|
icon: config.icon,
|
|
793
|
-
path: "/settings/
|
|
1032
|
+
path: "/settings/global-attributes",
|
|
794
1033
|
nested: void 0,
|
|
795
1034
|
rank: void 0,
|
|
796
1035
|
translationNs: void 0
|