@empty-complete-org/medusa-product-attributes 1.0.1 → 1.1.1

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.
@@ -61,6 +61,8 @@ const no$1 = "Нет";
61
61
  const upload$1 = "Загрузить";
62
62
  const uploadError$1 = "Ошибка загрузки файла";
63
63
  const fileUrl$1 = "URL файла";
64
+ const applyTemplateError$1 = "Ошибка при применении шаблона";
65
+ const createError$1 = "Ошибка при создании атрибута";
64
66
  const templates$1 = "Шаблоны атрибутов";
65
67
  const templatesDesc$1 = "Заготовки атрибутов, которые можно применить к любой категории.";
66
68
  const globals$1 = "Глобальные атрибуты";
@@ -100,6 +102,8 @@ const ru = {
100
102
  upload: upload$1,
101
103
  uploadError: uploadError$1,
102
104
  fileUrl: fileUrl$1,
105
+ applyTemplateError: applyTemplateError$1,
106
+ createError: createError$1,
103
107
  templates: templates$1,
104
108
  templatesDesc: templatesDesc$1,
105
109
  globals: globals$1,
@@ -134,6 +138,8 @@ const no = "No";
134
138
  const upload = "Upload";
135
139
  const uploadError = "Upload failed";
136
140
  const fileUrl = "File URL";
141
+ const applyTemplateError = "Failed to apply template";
142
+ const createError = "Failed to create attribute";
137
143
  const templates = "Attribute Templates";
138
144
  const templatesDesc = "Reusable attribute blueprints. Apply to any category in one click.";
139
145
  const globals = "Global Attributes";
@@ -173,6 +179,8 @@ const en = {
173
179
  upload,
174
180
  uploadError,
175
181
  fileUrl,
182
+ applyTemplateError,
183
+ createError,
176
184
  templates,
177
185
  templatesDesc,
178
186
  globals,
@@ -180,14 +188,16 @@ const en = {
180
188
  };
181
189
  const locales = { ru, en };
182
190
  function detectLang() {
183
- var _a;
191
+ var _a, _b, _c;
184
192
  if (typeof window === "undefined") return "en";
185
193
  const stored = window.localStorage.getItem("i18nextLng");
186
194
  if (stored) {
187
195
  const short = stored.slice(0, 2).toLowerCase();
188
196
  if (locales[short]) return short;
189
197
  }
190
- const nav = (((_a = window.navigator) == null ? void 0 : _a.language) || "en").slice(0, 2).toLowerCase();
198
+ const htmlLang = (_b = (_a = document.documentElement) == null ? void 0 : _a.lang) == null ? void 0 : _b.slice(0, 2).toLowerCase();
199
+ if (htmlLang && locales[htmlLang]) return htmlLang;
200
+ const nav = (((_c = window.navigator) == null ? void 0 : _c.language) || "en").slice(0, 2).toLowerCase();
191
201
  return locales[nav] ? nav : "en";
192
202
  }
193
203
  function useT() {
@@ -197,7 +207,7 @@ function useT() {
197
207
  }, []);
198
208
  return (key, fallback) => dict[key] ?? fallback ?? key;
199
209
  }
200
- const emptyForm$2 = () => ({ label: "", type: "text", unit: "" });
210
+ const emptyForm$1 = () => ({ label: "", type: "text", unit: "" });
201
211
  const CategoryAttributeTemplatesWidget = ({
202
212
  data
203
213
  }) => {
@@ -208,7 +218,7 @@ const CategoryAttributeTemplatesWidget = ({
208
218
  const queryKey = ["category-custom-attributes", categoryId];
209
219
  const [showAddForm, setShowAddForm] = React.useState(false);
210
220
  const [showTemplateList, setShowTemplateList] = React.useState(false);
211
- const [addForm, setAddForm] = React.useState(emptyForm$2());
221
+ const [addForm, setAddForm] = React.useState(emptyForm$1());
212
222
  const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
213
223
  const [mutationError, setMutationError] = React.useState(null);
214
224
  const templatesQuery = reactQuery.useQuery({
@@ -226,7 +236,7 @@ const CategoryAttributeTemplatesWidget = ({
226
236
  setShowTemplateList(false);
227
237
  },
228
238
  onError: (err) => {
229
- setMutationError((err == null ? void 0 : err.message) || "Ошибка при применении шаблона");
239
+ setMutationError((err == null ? void 0 : err.message) || t("applyTemplateError"));
230
240
  }
231
241
  });
232
242
  const {
@@ -248,11 +258,11 @@ const CategoryAttributeTemplatesWidget = ({
248
258
  onSuccess: () => {
249
259
  qc.invalidateQueries({ queryKey });
250
260
  setShowAddForm(false);
251
- setAddForm(emptyForm$2());
261
+ setAddForm(emptyForm$1());
252
262
  setMutationError(null);
253
263
  },
254
264
  onError: (err) => {
255
- setMutationError((err == null ? void 0 : err.message) || "Ошибка при создании атрибута");
265
+ setMutationError((err == null ? void 0 : err.message) || t("createError"));
256
266
  }
257
267
  });
258
268
  const deleteMutation = reactQuery.useMutation({
@@ -273,7 +283,7 @@ const CategoryAttributeTemplatesWidget = ({
273
283
  unit: addForm.type === "number" && addForm.unit.trim() ? addForm.unit.trim() : null
274
284
  });
275
285
  };
276
- const typeLabel2 = (v) => t(`type.${v}`, v);
286
+ const typeLabel = (v) => t(`type.${v}`, v);
277
287
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
278
288
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
279
289
  /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("attributes") }),
@@ -287,35 +297,32 @@ const CategoryAttributeTemplatesWidget = ({
287
297
  children: t("fromTemplate")
288
298
  }
289
299
  ),
290
- /* @__PURE__ */ jsxRuntime.jsxs(
300
+ /* @__PURE__ */ jsxRuntime.jsx(
291
301
  ui.Button,
292
302
  {
293
303
  variant: "secondary",
294
304
  size: "small",
295
305
  onClick: () => setShowAddForm(true),
296
- children: [
297
- "+ ",
298
- t("add")
299
- ]
306
+ children: t("add")
300
307
  }
301
308
  )
302
309
  ] })
303
310
  ] }),
304
311
  showTemplateList && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-3", children: [
305
312
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
306
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Выберите шаблон" }),
313
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: t("chooseTemplate") }),
307
314
  /* @__PURE__ */ jsxRuntime.jsx(
308
315
  ui.Button,
309
316
  {
310
317
  size: "small",
311
318
  variant: "secondary",
312
319
  onClick: () => setShowTemplateList(false),
313
- children: "Закрыть"
320
+ children: t("close")
314
321
  }
315
322
  )
316
323
  ] }),
317
324
  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." }),
325
+ ((_a = templatesQuery.data) == null ? void 0 : _a.attribute_templates.length) === 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: t("noTemplates") }),
319
326
  /* @__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(
320
327
  "button",
321
328
  {
@@ -325,7 +332,7 @@ const CategoryAttributeTemplatesWidget = ({
325
332
  children: [
326
333
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: p.label }),
327
334
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
328
- typeLabel2(p.type),
335
+ typeLabel(p.type),
329
336
  p.unit ? `, ${p.unit}` : ""
330
337
  ] })
331
338
  ]
@@ -334,9 +341,9 @@ const CategoryAttributeTemplatesWidget = ({
334
341
  )) })
335
342
  ] }),
336
343
  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") }) }),
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: "Не удалось загрузить атрибуты." }) }),
344
+ isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: t("loadFailed") }) }),
338
345
  inheritedAttributes.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
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: "Унаследованные" }) }),
346
+ /* @__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: t("inherited") }) }),
340
347
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: inheritedAttributes.map((attr) => /* @__PURE__ */ jsxRuntime.jsxs(
341
348
  "div",
342
349
  {
@@ -344,28 +351,23 @@ const CategoryAttributeTemplatesWidget = ({
344
351
  children: [
345
352
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm text-ui-fg-subtle", children: attr.label }),
346
353
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
347
- typeLabel2(attr.type),
354
+ typeLabel(attr.type),
348
355
  attr.unit ? `, ${attr.unit}` : ""
349
- ] }),
350
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "blue", children: "из родителя" })
356
+ ] })
351
357
  ]
352
358
  },
353
359
  attr.id
354
360
  )) })
355
361
  ] }),
356
362
  ownAttributes.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
357
- inheritedAttributes.length > 0 && /* @__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: "Свои" }) }),
363
+ inheritedAttributes.length > 0 && /* @__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: t("own") }) }),
358
364
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: ownAttributes.map(
359
365
  (attr) => confirmDeleteId === attr.id ? /* @__PURE__ */ jsxRuntime.jsxs(
360
366
  "div",
361
367
  {
362
368
  className: "flex items-center gap-3 px-6 py-3 text-sm",
363
369
  children: [
364
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1 text-ui-fg-base", children: [
365
- "Удалить «",
366
- attr.label,
367
- "»?"
368
- ] }),
370
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-ui-fg-base", children: t("confirmDelete").replace("{name}", attr.label) }),
369
371
  /* @__PURE__ */ jsxRuntime.jsx(
370
372
  ui.Button,
371
373
  {
@@ -373,7 +375,7 @@ const CategoryAttributeTemplatesWidget = ({
373
375
  variant: "danger",
374
376
  onClick: () => deleteMutation.mutate(attr.id),
375
377
  isLoading: deleteMutation.isPending,
376
- children: "Удалить"
378
+ children: t("delete")
377
379
  }
378
380
  ),
379
381
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -382,7 +384,7 @@ const CategoryAttributeTemplatesWidget = ({
382
384
  size: "small",
383
385
  variant: "secondary",
384
386
  onClick: () => setConfirmDeleteId(null),
385
- children: "Отмена"
387
+ children: t("cancel")
386
388
  }
387
389
  )
388
390
  ]
@@ -395,7 +397,7 @@ const CategoryAttributeTemplatesWidget = ({
395
397
  children: [
396
398
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm text-ui-fg-base", children: attr.label }),
397
399
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
398
- typeLabel2(attr.type),
400
+ typeLabel(attr.type),
399
401
  attr.unit ? `, ${attr.unit}` : ""
400
402
  ] }),
401
403
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -403,7 +405,7 @@ const CategoryAttributeTemplatesWidget = ({
403
405
  {
404
406
  onClick: () => setConfirmDeleteId(attr.id),
405
407
  className: "text-xs text-ui-fg-error hover:underline",
406
- children: "Удалить"
408
+ children: t("delete")
407
409
  }
408
410
  )
409
411
  ]
@@ -412,14 +414,14 @@ const CategoryAttributeTemplatesWidget = ({
412
414
  )
413
415
  ) })
414
416
  ] }),
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: "Нет атрибутов. Добавьте первый." }) }),
417
+ !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: t("noAttributes") }) }),
416
418
  showAddForm && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-6 py-3", children: [
417
419
  /* @__PURE__ */ jsxRuntime.jsx(
418
420
  ui.Input,
419
421
  {
420
422
  value: addForm.label,
421
423
  onChange: (e) => setAddForm((f) => ({ ...f, label: e.target.value })),
422
- placeholder: "Название атрибута",
424
+ placeholder: t("name"),
423
425
  className: "flex-1 h-8 text-sm",
424
426
  autoFocus: true
425
427
  }
@@ -435,10 +437,10 @@ const CategoryAttributeTemplatesWidget = ({
435
437
  })),
436
438
  className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
437
439
  children: [
438
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: "Текст" }),
439
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: "Число" }),
440
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: "Файл" }),
441
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: "Да/Нет" })
440
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: t("type.text") }),
441
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: t("type.number") }),
442
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: t("type.file") }),
443
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: t("type.boolean") })
442
444
  ]
443
445
  }
444
446
  ),
@@ -447,7 +449,7 @@ const CategoryAttributeTemplatesWidget = ({
447
449
  {
448
450
  value: addForm.unit,
449
451
  onChange: (e) => setAddForm((f) => ({ ...f, unit: e.target.value })),
450
- placeholder: "ед. (кг, м, шт...)",
452
+ placeholder: t("unit"),
451
453
  className: "w-28 h-8 text-sm"
452
454
  }
453
455
  ),
@@ -457,7 +459,7 @@ const CategoryAttributeTemplatesWidget = ({
457
459
  size: "small",
458
460
  onClick: handleAdd,
459
461
  isLoading: createMutation.isPending,
460
- children: "Добавить"
462
+ children: t("add")
461
463
  }
462
464
  ),
463
465
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -467,10 +469,10 @@ const CategoryAttributeTemplatesWidget = ({
467
469
  size: "small",
468
470
  onClick: () => {
469
471
  setShowAddForm(false);
470
- setAddForm(emptyForm$2());
472
+ setAddForm(emptyForm$1());
471
473
  setMutationError(null);
472
474
  },
473
- children: "Отмена"
475
+ children: t("cancel")
474
476
  }
475
477
  )
476
478
  ] }),
@@ -486,6 +488,7 @@ const ProductAttributeValuesWidget = ({
486
488
  var _a;
487
489
  const productId = data.id;
488
490
  const productHandle = data.handle || productId;
491
+ const t = useT();
489
492
  const qc = reactQuery.useQueryClient();
490
493
  const [formValues, setFormValues] = React.useState({});
491
494
  const [saveSuccess, setSaveSuccess] = React.useState(false);
@@ -560,7 +563,7 @@ const ProductAttributeValuesWidget = ({
560
563
  if (!response.ok) {
561
564
  const text = await response.text();
562
565
  console.error("Upload failed", response.status, text);
563
- alert(`Ошибка загрузки файла: ${response.status}`);
566
+ alert(`${t("uploadError")}: ${response.status}`);
564
567
  return;
565
568
  }
566
569
  const res = await response.json();
@@ -569,11 +572,11 @@ const ProductAttributeValuesWidget = ({
569
572
  setFormValues((prev) => ({ ...prev, [attrId]: url }));
570
573
  } else {
571
574
  console.error("No URL in upload response", res);
572
- alert("Файл загружен, но URL не получен");
575
+ alert(t("uploadError"));
573
576
  }
574
577
  } catch (err) {
575
578
  console.error("Upload error", err);
576
- alert("Ошибка загрузки файла");
579
+ alert(t("uploadError"));
577
580
  } finally {
578
581
  setUploadingId(null);
579
582
  }
@@ -582,10 +585,10 @@ const ProductAttributeValuesWidget = ({
582
585
  const isError = attributesQuery.isError || valuesQuery.isError;
583
586
  const attributes2 = ((_a = attributesQuery.data) == null ? void 0 : _a.attributes) ?? [];
584
587
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
585
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Характеристики" }) }),
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: "Загрузка…" }) }),
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: "Не удалось загрузить характеристики." }) }),
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: "В категории нет атрибутов. Добавьте их в настройках категории." }) }),
588
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("characteristics") }) }),
589
+ 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") }) }),
590
+ isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: t("loadFailed") }) }),
591
+ !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: t("noAttributes") }) }),
589
592
  !isLoading && !isError && attributes2.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
590
593
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: attributes2.map((attr) => /* @__PURE__ */ jsxRuntime.jsxs(
591
594
  "div",
@@ -607,8 +610,8 @@ const ProductAttributeValuesWidget = ({
607
610
  className: "h-8 flex-1 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
608
611
  children: [
609
612
  /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "—" }),
610
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "true", children: "Да" }),
611
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "false", children: "Нет" })
613
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "true", children: t("yes") }),
614
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "false", children: t("no") })
612
615
  ]
613
616
  }
614
617
  ) : attr.type === "file" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -635,7 +638,7 @@ const ProductAttributeValuesWidget = ({
635
638
  ...prev,
636
639
  [attr.id]: e.target.value
637
640
  })),
638
- placeholder: "URL файла",
641
+ placeholder: t("fileUrl"),
639
642
  className: "h-8 text-sm flex-1"
640
643
  }
641
644
  ),
@@ -650,7 +653,7 @@ const ProductAttributeValuesWidget = ({
650
653
  return (_a2 = fileInputs.current[attr.id]) == null ? void 0 : _a2.click();
651
654
  },
652
655
  isLoading: uploadingId === attr.id,
653
- children: "Загрузить"
656
+ children: t("upload")
654
657
  }
655
658
  )
656
659
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -662,7 +665,7 @@ const ProductAttributeValuesWidget = ({
662
665
  ...prev,
663
666
  [attr.id]: e.target.value
664
667
  })),
665
- placeholder: attr.type === "number" ? "0" : "Значение",
668
+ placeholder: attr.type === "number" ? "0" : t("value"),
666
669
  className: "h-8 text-sm flex-1"
667
670
  }
668
671
  ) })
@@ -676,7 +679,7 @@ const ProductAttributeValuesWidget = ({
676
679
  size: "small",
677
680
  onClick: handleSave,
678
681
  isLoading: saveMutation.isPending,
679
- children: saveSuccess ? "Сохранено ✓" : "Сохранить"
682
+ children: saveSuccess ? t("saved") : t("save")
680
683
  }
681
684
  ) })
682
685
  ] })
@@ -685,71 +688,6 @@ const ProductAttributeValuesWidget = ({
685
688
  adminSdk.defineWidgetConfig({
686
689
  zone: "product.details.after"
687
690
  });
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";
753
691
  var __defProp = Object.defineProperty;
754
692
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
755
693
  var __hasOwnProp = Object.prototype.hasOwnProperty;
@@ -778,7 +716,7 @@ var __objRest = (source, exclude) => {
778
716
  }
779
717
  return target;
780
718
  };
781
- const SquaresPlus = React__namespace.forwardRef(
719
+ const Tag = React__namespace.forwardRef(
782
720
  (_a, ref) => {
783
721
  var _b = _a, { color = "currentColor" } = _b, props = __objRest(_b, ["color"]);
784
722
  return /* @__PURE__ */ React__namespace.createElement(
@@ -791,56 +729,73 @@ const SquaresPlus = React__namespace.forwardRef(
791
729
  fill: "none",
792
730
  ref
793
731
  }, props),
794
- /* @__PURE__ */ React__namespace.createElement(
732
+ /* @__PURE__ */ React__namespace.createElement("g", { clipPath: "url(#a)" }, /* @__PURE__ */ React__namespace.createElement(
795
733
  "path",
796
734
  {
797
- stroke: color,
798
- strokeLinecap: "round",
799
- strokeLinejoin: "round",
800
- strokeWidth: 1.5,
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"
735
+ fill: color,
736
+ fillRule: "evenodd",
737
+ d: "M2.25 2.389a.14.14 0 0 1 .139-.139h4.375c.272 0 .534.108.727.301l5.11 5.111a1.027 1.027 0 0 1 0 1.453l-3.486 3.487a1.027 1.027 0 0 1-1.453 0L2.552 7.49a1.03 1.03 0 0 1-.302-.727zM2.389.75A1.64 1.64 0 0 0 .75 2.389v4.375c0 .67.267 1.313.74 1.787l5.112 5.111a2.527 2.527 0 0 0 3.574 0l3.486-3.486a2.527 2.527 0 0 0 0-3.574L8.552 1.49A2.53 2.53 0 0 0 6.763.75zm3.778 4.305a1.111 1.111 0 1 1-2.223 0 1.111 1.111 0 0 1 2.223 0",
738
+ clipRule: "evenodd"
802
739
  }
803
- )
740
+ )),
741
+ /* @__PURE__ */ React__namespace.createElement("defs", null, /* @__PURE__ */ React__namespace.createElement("clipPath", { id: "a" }, /* @__PURE__ */ React__namespace.createElement("path", { fill: "#fff", d: "M0 0h15v15H0z" })))
804
742
  );
805
743
  }
806
744
  );
807
- SquaresPlus.displayName = "SquaresPlus";
808
- const emptyForm$1 = () => ({
809
- label: "",
810
- type: "text",
811
- unit: "",
812
- description: ""
813
- });
814
- const typeLabel$1 = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
815
- const AttributeTemplatesSettingsPage = () => {
745
+ Tag.displayName = "Tag";
746
+ const emptyForm = () => ({ label: "", type: "text", unit: "", description: "" });
747
+ const ProductAttributesSettingsPage = () => {
748
+ const t = useT();
749
+ const [tab, setTab] = React.useState("globals");
750
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-0", children: [
751
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-6 border-b border-ui-border-base px-6 pt-4", children: [
752
+ /* @__PURE__ */ jsxRuntime.jsx(TabButton, { active: tab === "globals", onClick: () => setTab("globals"), children: t("globals") }),
753
+ /* @__PURE__ */ jsxRuntime.jsx(TabButton, { active: tab === "templates", onClick: () => setTab("templates"), children: t("templates") })
754
+ ] }),
755
+ tab === "globals" ? /* @__PURE__ */ jsxRuntime.jsx(GlobalsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(TemplatesTab, {})
756
+ ] });
757
+ };
758
+ const TabButton = ({
759
+ active,
760
+ onClick,
761
+ children
762
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
763
+ "button",
764
+ {
765
+ onClick,
766
+ className: `-mb-px border-b-2 px-1 pb-3 text-sm font-medium transition-colors ${active ? "border-ui-border-interactive text-ui-fg-base" : "border-transparent text-ui-fg-subtle hover:text-ui-fg-base"}`,
767
+ children
768
+ }
769
+ );
770
+ const GlobalsTab = () => {
771
+ const t = useT();
816
772
  const qc = reactQuery.useQueryClient();
817
- const queryKey = ["attribute-templates"];
773
+ const queryKey = ["global-attributes"];
818
774
  const [showAddForm, setShowAddForm] = React.useState(false);
819
- const [addForm, setAddForm] = React.useState(emptyForm$1());
775
+ const [addForm, setAddForm] = React.useState(emptyForm());
820
776
  const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
821
777
  const { data, isLoading, isError } = reactQuery.useQuery({
822
778
  queryKey,
823
- queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
779
+ queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
824
780
  });
825
- const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
781
+ const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
826
782
  const createMutation = reactQuery.useMutation({
827
- mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
783
+ mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
828
784
  method: "POST",
829
785
  body: {
830
786
  label: body.label,
831
787
  type: body.type,
832
- unit: body.type === "number" && body.unit ? body.unit : null,
833
- description: body.description || null
788
+ unit: body.type === "number" && body.unit ? body.unit : null
834
789
  }
835
790
  }),
836
791
  onSuccess: () => {
837
792
  qc.invalidateQueries({ queryKey });
838
793
  setShowAddForm(false);
839
- setAddForm(emptyForm$1());
794
+ setAddForm(emptyForm());
840
795
  }
841
796
  });
842
797
  const deleteMutation = reactQuery.useMutation({
843
- mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
798
+ mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
844
799
  method: "PATCH",
845
800
  body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
846
801
  }),
@@ -858,86 +813,57 @@ const AttributeTemplatesSettingsPage = () => {
858
813
  description: addForm.description.trim()
859
814
  });
860
815
  };
861
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
862
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
816
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "divide-y", children: [
817
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
863
818
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
864
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Шаблоны атрибутов" }),
865
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Заготовки атрибутов, которые можно применить к любой категории." })
819
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("globals") }),
820
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("globalsDesc") })
866
821
  ] }),
867
- !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
822
+ !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: t("add") })
868
823
  ] }),
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: "Загрузка…" }) }),
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: [
882
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
883
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
884
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: p.label }),
885
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
886
- typeLabel$1(p.type),
887
- p.unit ? `, ${p.unit}` : ""
888
- ] })
889
- ] }),
890
- p.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
824
+ 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") }) }),
825
+ isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: t("loadFailed") }) }),
826
+ !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: t("noGlobals") }) }),
827
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: attrs.map(
828
+ (a) => confirmDeleteId === a.id ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
829
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: t("confirmDelete").replace("{name}", a.label) }),
830
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(a.id), isLoading: deleteMutation.isPending, children: t("delete") }),
831
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: t("cancel") })
832
+ ] }, a.id) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
833
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm", children: a.label }),
834
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
835
+ t(`type.${a.type}`, a.type),
836
+ a.unit ? `, ${a.unit}` : ""
891
837
  ] }),
892
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
893
- ] }, p.id)
838
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: t("delete") })
839
+ ] }, a.id)
894
840
  ) }),
895
- showAddForm && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
896
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
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" })
905
- ] }),
906
- /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: addForm.description, onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })), placeholder: "Описание (необязательно)", className: "h-8 text-sm" }),
907
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
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: "Создать" })
913
- ] })
914
- ] })
841
+ showAddForm && /* @__PURE__ */ jsxRuntime.jsx(AddForm, { t, form: addForm, setForm: setAddForm, onCancel: () => {
842
+ setShowAddForm(false);
843
+ setAddForm(emptyForm());
844
+ }, onSubmit: handleAdd, isLoading: createMutation.isPending, withDescription: false })
915
845
  ] });
916
846
  };
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 = () => {
847
+ const TemplatesTab = () => {
848
+ const t = useT();
924
849
  const qc = reactQuery.useQueryClient();
925
- const queryKey = ["global-attributes"];
850
+ const queryKey = ["attribute-templates"];
926
851
  const [showAddForm, setShowAddForm] = React.useState(false);
927
852
  const [addForm, setAddForm] = React.useState(emptyForm());
928
853
  const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
929
854
  const { data, isLoading, isError } = reactQuery.useQuery({
930
855
  queryKey,
931
- queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
856
+ queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
932
857
  });
933
- const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
858
+ const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
934
859
  const createMutation = reactQuery.useMutation({
935
- mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
860
+ mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
936
861
  method: "POST",
937
862
  body: {
938
863
  label: body.label,
939
864
  type: body.type,
940
- unit: body.type === "number" && body.unit ? body.unit : null
865
+ unit: body.type === "number" && body.unit ? body.unit : null,
866
+ description: body.description || null
941
867
  }
942
868
  }),
943
869
  onSuccess: () => {
@@ -947,7 +873,7 @@ const GlobalAttributesSettingsPage = () => {
947
873
  }
948
874
  });
949
875
  const deleteMutation = reactQuery.useMutation({
950
- mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
876
+ mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
951
877
  method: "PATCH",
952
878
  body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
953
879
  }),
@@ -959,60 +885,113 @@ const GlobalAttributesSettingsPage = () => {
959
885
  const handleAdd = () => {
960
886
  if (!addForm.label.trim()) return;
961
887
  createMutation.mutate({
888
+ ...addForm,
962
889
  label: addForm.label.trim(),
963
- type: addForm.type,
964
- unit: addForm.unit.trim()
890
+ unit: addForm.unit.trim(),
891
+ description: addForm.description.trim()
965
892
  });
966
893
  };
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: [
894
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "divide-y", children: [
895
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
969
896
  /* @__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: "Применяются ко всем товарам автоматически. Значения у продуктов необязательны." })
897
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("templates") }),
898
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("templatesDesc") })
972
899
  ] }),
973
- !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
900
+ !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: t("add") })
974
901
  ] }),
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}` : ""
902
+ 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") }) }),
903
+ isError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error text-sm", children: t("loadFailed") }) }),
904
+ !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: t("noTemplates") }) }),
905
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: templates2.map(
906
+ (p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
907
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: t("confirmDelete").replace("{name}", p.label) }),
908
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(p.id), isLoading: deleteMutation.isPending, children: t("delete") }),
909
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: t("cancel") })
910
+ ] }, p.id) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
911
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
912
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
913
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: p.label }),
914
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
915
+ t(`type.${p.type}`, p.type),
916
+ p.unit ? `, ${p.unit}` : ""
917
+ ] })
918
+ ] }),
919
+ p.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
992
920
  ] }),
993
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
994
- ] }, a.id)
921
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: t("delete") })
922
+ ] }, p.id)
995
923
  ) }),
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
- ] })
924
+ showAddForm && /* @__PURE__ */ jsxRuntime.jsx(AddForm, { t, form: addForm, setForm: setAddForm, onCancel: () => {
925
+ setShowAddForm(false);
926
+ setAddForm(emptyForm());
927
+ }, onSubmit: handleAdd, isLoading: createMutation.isPending, withDescription: true })
1011
928
  ] });
1012
929
  };
930
+ const AddForm = ({
931
+ t,
932
+ form,
933
+ setForm,
934
+ onCancel,
935
+ onSubmit,
936
+ isLoading,
937
+ withDescription
938
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
939
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
940
+ /* @__PURE__ */ jsxRuntime.jsx(
941
+ ui.Input,
942
+ {
943
+ value: form.label,
944
+ onChange: (e) => setForm((f) => ({ ...f, label: e.target.value })),
945
+ placeholder: t("name"),
946
+ className: "flex-1 h-8 text-sm",
947
+ autoFocus: true
948
+ }
949
+ ),
950
+ /* @__PURE__ */ jsxRuntime.jsxs(
951
+ "select",
952
+ {
953
+ value: form.type,
954
+ onChange: (e) => setForm((f) => ({
955
+ ...f,
956
+ type: e.target.value,
957
+ unit: e.target.value === "number" ? f.unit : ""
958
+ })),
959
+ className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
960
+ children: [
961
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: t("type.text") }),
962
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: t("type.number") }),
963
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: t("type.file") }),
964
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: t("type.boolean") })
965
+ ]
966
+ }
967
+ ),
968
+ form.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(
969
+ ui.Input,
970
+ {
971
+ value: form.unit,
972
+ onChange: (e) => setForm((f) => ({ ...f, unit: e.target.value })),
973
+ placeholder: t("unit"),
974
+ className: "w-28 h-8 text-sm"
975
+ }
976
+ )
977
+ ] }),
978
+ withDescription && /* @__PURE__ */ jsxRuntime.jsx(
979
+ ui.Input,
980
+ {
981
+ value: form.description,
982
+ onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })),
983
+ placeholder: t("description"),
984
+ className: "h-8 text-sm"
985
+ }
986
+ ),
987
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
988
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: onCancel, children: t("cancel") }),
989
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", onClick: onSubmit, isLoading, children: t("create") })
990
+ ] })
991
+ ] });
1013
992
  const config = adminSdk.defineRouteConfig({
1014
- label: "Global Attributes",
1015
- icon: Globe
993
+ label: "Product Attributes",
994
+ icon: Tag
1016
995
  });
1017
996
  const widgetModule = { widgets: [
1018
997
  {
@@ -1027,29 +1006,17 @@ const widgetModule = { widgets: [
1027
1006
  const routeModule = {
1028
1007
  routes: [
1029
1008
  {
1030
- Component: AttributeTemplatesSettingsPage,
1031
- path: "/settings/attribute-templates"
1032
- },
1033
- {
1034
- Component: GlobalAttributesSettingsPage,
1035
- path: "/settings/global-attributes"
1009
+ Component: ProductAttributesSettingsPage,
1010
+ path: "/settings/product-attributes"
1036
1011
  }
1037
1012
  ]
1038
1013
  };
1039
1014
  const menuItemModule = {
1040
1015
  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
- },
1049
1016
  {
1050
1017
  label: config.label,
1051
1018
  icon: config.icon,
1052
- path: "/settings/global-attributes",
1019
+ path: "/settings/product-attributes",
1053
1020
  nested: void 0,
1054
1021
  rank: void 0,
1055
1022
  translationNs: void 0