@empty-complete-org/medusa-product-attributes 1.0.1 → 1.1.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.
@@ -180,15 +180,17 @@ const en = {
180
180
  };
181
181
  const locales = { ru, en };
182
182
  function detectLang() {
183
- var _a;
184
- if (typeof window === "undefined") return "en";
183
+ var _a, _b, _c;
184
+ if (typeof window === "undefined") return "ru";
185
185
  const stored = window.localStorage.getItem("i18nextLng");
186
186
  if (stored) {
187
187
  const short = stored.slice(0, 2).toLowerCase();
188
188
  if (locales[short]) return short;
189
189
  }
190
- const nav = (((_a = window.navigator) == null ? void 0 : _a.language) || "en").slice(0, 2).toLowerCase();
191
- return locales[nav] ? nav : "en";
190
+ const htmlLang = (_b = (_a = document.documentElement) == null ? void 0 : _a.lang) == null ? void 0 : _b.slice(0, 2).toLowerCase();
191
+ if (htmlLang && locales[htmlLang]) return htmlLang;
192
+ const nav = (((_c = window.navigator) == null ? void 0 : _c.language) || "ru").slice(0, 2).toLowerCase();
193
+ return locales[nav] ? nav : "ru";
192
194
  }
193
195
  function useT() {
194
196
  const dict = React.useMemo(() => {
@@ -197,7 +199,7 @@ function useT() {
197
199
  }, []);
198
200
  return (key, fallback) => dict[key] ?? fallback ?? key;
199
201
  }
200
- const emptyForm$2 = () => ({ label: "", type: "text", unit: "" });
202
+ const emptyForm$1 = () => ({ label: "", type: "text", unit: "" });
201
203
  const CategoryAttributeTemplatesWidget = ({
202
204
  data
203
205
  }) => {
@@ -208,7 +210,7 @@ const CategoryAttributeTemplatesWidget = ({
208
210
  const queryKey = ["category-custom-attributes", categoryId];
209
211
  const [showAddForm, setShowAddForm] = React.useState(false);
210
212
  const [showTemplateList, setShowTemplateList] = React.useState(false);
211
- const [addForm, setAddForm] = React.useState(emptyForm$2());
213
+ const [addForm, setAddForm] = React.useState(emptyForm$1());
212
214
  const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
213
215
  const [mutationError, setMutationError] = React.useState(null);
214
216
  const templatesQuery = reactQuery.useQuery({
@@ -248,7 +250,7 @@ const CategoryAttributeTemplatesWidget = ({
248
250
  onSuccess: () => {
249
251
  qc.invalidateQueries({ queryKey });
250
252
  setShowAddForm(false);
251
- setAddForm(emptyForm$2());
253
+ setAddForm(emptyForm$1());
252
254
  setMutationError(null);
253
255
  },
254
256
  onError: (err) => {
@@ -273,7 +275,7 @@ const CategoryAttributeTemplatesWidget = ({
273
275
  unit: addForm.type === "number" && addForm.unit.trim() ? addForm.unit.trim() : null
274
276
  });
275
277
  };
276
- const typeLabel2 = (v) => t(`type.${v}`, v);
278
+ const typeLabel = (v) => t(`type.${v}`, v);
277
279
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
278
280
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
279
281
  /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("attributes") }),
@@ -287,16 +289,13 @@ const CategoryAttributeTemplatesWidget = ({
287
289
  children: t("fromTemplate")
288
290
  }
289
291
  ),
290
- /* @__PURE__ */ jsxRuntime.jsxs(
292
+ /* @__PURE__ */ jsxRuntime.jsx(
291
293
  ui.Button,
292
294
  {
293
295
  variant: "secondary",
294
296
  size: "small",
295
297
  onClick: () => setShowAddForm(true),
296
- children: [
297
- "+ ",
298
- t("add")
299
- ]
298
+ children: t("add")
300
299
  }
301
300
  )
302
301
  ] })
@@ -325,7 +324,7 @@ const CategoryAttributeTemplatesWidget = ({
325
324
  children: [
326
325
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: p.label }),
327
326
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
328
- typeLabel2(p.type),
327
+ typeLabel(p.type),
329
328
  p.unit ? `, ${p.unit}` : ""
330
329
  ] })
331
330
  ]
@@ -344,10 +343,9 @@ const CategoryAttributeTemplatesWidget = ({
344
343
  children: [
345
344
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm text-ui-fg-subtle", children: attr.label }),
346
345
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
347
- typeLabel2(attr.type),
346
+ typeLabel(attr.type),
348
347
  attr.unit ? `, ${attr.unit}` : ""
349
- ] }),
350
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "blue", children: "из родителя" })
348
+ ] })
351
349
  ]
352
350
  },
353
351
  attr.id
@@ -395,7 +393,7 @@ const CategoryAttributeTemplatesWidget = ({
395
393
  children: [
396
394
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm text-ui-fg-base", children: attr.label }),
397
395
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
398
- typeLabel2(attr.type),
396
+ typeLabel(attr.type),
399
397
  attr.unit ? `, ${attr.unit}` : ""
400
398
  ] }),
401
399
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -467,7 +465,7 @@ const CategoryAttributeTemplatesWidget = ({
467
465
  size: "small",
468
466
  onClick: () => {
469
467
  setShowAddForm(false);
470
- setAddForm(emptyForm$2());
468
+ setAddForm(emptyForm$1());
471
469
  setMutationError(null);
472
470
  },
473
471
  children: "Отмена"
@@ -685,71 +683,6 @@ const ProductAttributeValuesWidget = ({
685
683
  adminSdk.defineWidgetConfig({
686
684
  zone: "product.details.after"
687
685
  });
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
686
  var __defProp = Object.defineProperty;
754
687
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
755
688
  var __hasOwnProp = Object.prototype.hasOwnProperty;
@@ -778,7 +711,7 @@ var __objRest = (source, exclude) => {
778
711
  }
779
712
  return target;
780
713
  };
781
- const SquaresPlus = React__namespace.forwardRef(
714
+ const Tag = React__namespace.forwardRef(
782
715
  (_a, ref) => {
783
716
  var _b = _a, { color = "currentColor" } = _b, props = __objRest(_b, ["color"]);
784
717
  return /* @__PURE__ */ React__namespace.createElement(
@@ -791,56 +724,73 @@ const SquaresPlus = React__namespace.forwardRef(
791
724
  fill: "none",
792
725
  ref
793
726
  }, props),
794
- /* @__PURE__ */ React__namespace.createElement(
727
+ /* @__PURE__ */ React__namespace.createElement("g", { clipPath: "url(#a)" }, /* @__PURE__ */ React__namespace.createElement(
795
728
  "path",
796
729
  {
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"
730
+ fill: color,
731
+ fillRule: "evenodd",
732
+ 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",
733
+ clipRule: "evenodd"
802
734
  }
803
- )
735
+ )),
736
+ /* @__PURE__ */ React__namespace.createElement("defs", null, /* @__PURE__ */ React__namespace.createElement("clipPath", { id: "a" }, /* @__PURE__ */ React__namespace.createElement("path", { fill: "#fff", d: "M0 0h15v15H0z" })))
804
737
  );
805
738
  }
806
739
  );
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 = () => {
740
+ Tag.displayName = "Tag";
741
+ const emptyForm = () => ({ label: "", type: "text", unit: "", description: "" });
742
+ const ProductAttributesSettingsPage = () => {
743
+ const t = useT();
744
+ const [tab, setTab] = React.useState("globals");
745
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-0", children: [
746
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-6 border-b border-ui-border-base px-6 pt-4", children: [
747
+ /* @__PURE__ */ jsxRuntime.jsx(TabButton, { active: tab === "globals", onClick: () => setTab("globals"), children: t("globals") }),
748
+ /* @__PURE__ */ jsxRuntime.jsx(TabButton, { active: tab === "templates", onClick: () => setTab("templates"), children: t("templates") })
749
+ ] }),
750
+ tab === "globals" ? /* @__PURE__ */ jsxRuntime.jsx(GlobalsTab, {}) : /* @__PURE__ */ jsxRuntime.jsx(TemplatesTab, {})
751
+ ] });
752
+ };
753
+ const TabButton = ({
754
+ active,
755
+ onClick,
756
+ children
757
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
758
+ "button",
759
+ {
760
+ onClick,
761
+ 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"}`,
762
+ children
763
+ }
764
+ );
765
+ const GlobalsTab = () => {
766
+ const t = useT();
816
767
  const qc = reactQuery.useQueryClient();
817
- const queryKey = ["attribute-templates"];
768
+ const queryKey = ["global-attributes"];
818
769
  const [showAddForm, setShowAddForm] = React.useState(false);
819
- const [addForm, setAddForm] = React.useState(emptyForm$1());
770
+ const [addForm, setAddForm] = React.useState(emptyForm());
820
771
  const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
821
772
  const { data, isLoading, isError } = reactQuery.useQuery({
822
773
  queryKey,
823
- queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
774
+ queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
824
775
  });
825
- const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
776
+ const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
826
777
  const createMutation = reactQuery.useMutation({
827
- mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
778
+ mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
828
779
  method: "POST",
829
780
  body: {
830
781
  label: body.label,
831
782
  type: body.type,
832
- unit: body.type === "number" && body.unit ? body.unit : null,
833
- description: body.description || null
783
+ unit: body.type === "number" && body.unit ? body.unit : null
834
784
  }
835
785
  }),
836
786
  onSuccess: () => {
837
787
  qc.invalidateQueries({ queryKey });
838
788
  setShowAddForm(false);
839
- setAddForm(emptyForm$1());
789
+ setAddForm(emptyForm());
840
790
  }
841
791
  });
842
792
  const deleteMutation = reactQuery.useMutation({
843
- mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
793
+ mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
844
794
  method: "PATCH",
845
795
  body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
846
796
  }),
@@ -858,86 +808,57 @@ const AttributeTemplatesSettingsPage = () => {
858
808
  description: addForm.description.trim()
859
809
  });
860
810
  };
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: [
811
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "divide-y", children: [
812
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
863
813
  /* @__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: "Заготовки атрибутов, которые можно применить к любой категории." })
814
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("globals") }),
815
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("globalsDesc") })
866
816
  ] }),
867
- !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
817
+ !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: t("add") })
868
818
  ] }),
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 })
819
+ 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") }) }),
820
+ 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") }) }),
821
+ !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") }) }),
822
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: attrs.map(
823
+ (a) => confirmDeleteId === a.id ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
824
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: t("confirmDelete").replace("{name}", a.label) }),
825
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(a.id), isLoading: deleteMutation.isPending, children: t("delete") }),
826
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: t("cancel") })
827
+ ] }, a.id) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
828
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 text-sm", children: a.label }),
829
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
830
+ t(`type.${a.type}`, a.type),
831
+ a.unit ? `, ${a.unit}` : ""
891
832
  ] }),
892
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
893
- ] }, p.id)
833
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: t("delete") })
834
+ ] }, a.id)
894
835
  ) }),
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
- ] })
836
+ showAddForm && /* @__PURE__ */ jsxRuntime.jsx(AddForm, { t, form: addForm, setForm: setAddForm, onCancel: () => {
837
+ setShowAddForm(false);
838
+ setAddForm(emptyForm());
839
+ }, onSubmit: handleAdd, isLoading: createMutation.isPending, withDescription: false })
915
840
  ] });
916
841
  };
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 = () => {
842
+ const TemplatesTab = () => {
843
+ const t = useT();
924
844
  const qc = reactQuery.useQueryClient();
925
- const queryKey = ["global-attributes"];
845
+ const queryKey = ["attribute-templates"];
926
846
  const [showAddForm, setShowAddForm] = React.useState(false);
927
847
  const [addForm, setAddForm] = React.useState(emptyForm());
928
848
  const [confirmDeleteId, setConfirmDeleteId] = React.useState(null);
929
849
  const { data, isLoading, isError } = reactQuery.useQuery({
930
850
  queryKey,
931
- queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
851
+ queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
932
852
  });
933
- const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
853
+ const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
934
854
  const createMutation = reactQuery.useMutation({
935
- mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
855
+ mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
936
856
  method: "POST",
937
857
  body: {
938
858
  label: body.label,
939
859
  type: body.type,
940
- unit: body.type === "number" && body.unit ? body.unit : null
860
+ unit: body.type === "number" && body.unit ? body.unit : null,
861
+ description: body.description || null
941
862
  }
942
863
  }),
943
864
  onSuccess: () => {
@@ -947,7 +868,7 @@ const GlobalAttributesSettingsPage = () => {
947
868
  }
948
869
  });
949
870
  const deleteMutation = reactQuery.useMutation({
950
- mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
871
+ mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
951
872
  method: "PATCH",
952
873
  body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
953
874
  }),
@@ -959,60 +880,113 @@ const GlobalAttributesSettingsPage = () => {
959
880
  const handleAdd = () => {
960
881
  if (!addForm.label.trim()) return;
961
882
  createMutation.mutate({
883
+ ...addForm,
962
884
  label: addForm.label.trim(),
963
- type: addForm.type,
964
- unit: addForm.unit.trim()
885
+ unit: addForm.unit.trim(),
886
+ description: addForm.description.trim()
965
887
  });
966
888
  };
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: [
889
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "divide-y", children: [
890
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
969
891
  /* @__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: "Применяются ко всем товарам автоматически. Значения у продуктов необязательны." })
892
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("templates") }),
893
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("templatesDesc") })
972
894
  ] }),
973
- !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
895
+ !showAddForm && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: t("add") })
974
896
  ] }),
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}` : ""
897
+ 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") }) }),
898
+ 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") }) }),
899
+ !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") }) }),
900
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: templates2.map(
901
+ (p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
902
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: t("confirmDelete").replace("{name}", p.label) }),
903
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(p.id), isLoading: deleteMutation.isPending, children: t("delete") }),
904
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: t("cancel") })
905
+ ] }, p.id) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
906
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
907
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
908
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: p.label }),
909
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "grey", children: [
910
+ t(`type.${p.type}`, p.type),
911
+ p.unit ? `, ${p.unit}` : ""
912
+ ] })
913
+ ] }),
914
+ p.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
992
915
  ] }),
993
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
994
- ] }, a.id)
916
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: t("delete") })
917
+ ] }, p.id)
995
918
  ) }),
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
- ] })
919
+ showAddForm && /* @__PURE__ */ jsxRuntime.jsx(AddForm, { t, form: addForm, setForm: setAddForm, onCancel: () => {
920
+ setShowAddForm(false);
921
+ setAddForm(emptyForm());
922
+ }, onSubmit: handleAdd, isLoading: createMutation.isPending, withDescription: true })
1011
923
  ] });
1012
924
  };
925
+ const AddForm = ({
926
+ t,
927
+ form,
928
+ setForm,
929
+ onCancel,
930
+ onSubmit,
931
+ isLoading,
932
+ withDescription
933
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
934
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
935
+ /* @__PURE__ */ jsxRuntime.jsx(
936
+ ui.Input,
937
+ {
938
+ value: form.label,
939
+ onChange: (e) => setForm((f) => ({ ...f, label: e.target.value })),
940
+ placeholder: t("name"),
941
+ className: "flex-1 h-8 text-sm",
942
+ autoFocus: true
943
+ }
944
+ ),
945
+ /* @__PURE__ */ jsxRuntime.jsxs(
946
+ "select",
947
+ {
948
+ value: form.type,
949
+ onChange: (e) => setForm((f) => ({
950
+ ...f,
951
+ type: e.target.value,
952
+ unit: e.target.value === "number" ? f.unit : ""
953
+ })),
954
+ className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
955
+ children: [
956
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "text", children: t("type.text") }),
957
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "number", children: t("type.number") }),
958
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "file", children: t("type.file") }),
959
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "boolean", children: t("type.boolean") })
960
+ ]
961
+ }
962
+ ),
963
+ form.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(
964
+ ui.Input,
965
+ {
966
+ value: form.unit,
967
+ onChange: (e) => setForm((f) => ({ ...f, unit: e.target.value })),
968
+ placeholder: t("unit"),
969
+ className: "w-28 h-8 text-sm"
970
+ }
971
+ )
972
+ ] }),
973
+ withDescription && /* @__PURE__ */ jsxRuntime.jsx(
974
+ ui.Input,
975
+ {
976
+ value: form.description,
977
+ onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })),
978
+ placeholder: t("description"),
979
+ className: "h-8 text-sm"
980
+ }
981
+ ),
982
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
983
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: onCancel, children: t("cancel") }),
984
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", onClick: onSubmit, isLoading, children: t("create") })
985
+ ] })
986
+ ] });
1013
987
  const config = adminSdk.defineRouteConfig({
1014
- label: "Global Attributes",
1015
- icon: Globe
988
+ label: "Product Attributes",
989
+ icon: Tag
1016
990
  });
1017
991
  const widgetModule = { widgets: [
1018
992
  {
@@ -1027,29 +1001,17 @@ const widgetModule = { widgets: [
1027
1001
  const routeModule = {
1028
1002
  routes: [
1029
1003
  {
1030
- Component: AttributeTemplatesSettingsPage,
1031
- path: "/settings/attribute-templates"
1032
- },
1033
- {
1034
- Component: GlobalAttributesSettingsPage,
1035
- path: "/settings/global-attributes"
1004
+ Component: ProductAttributesSettingsPage,
1005
+ path: "/settings/product-attributes"
1036
1006
  }
1037
1007
  ]
1038
1008
  };
1039
1009
  const menuItemModule = {
1040
1010
  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
1011
  {
1050
1012
  label: config.label,
1051
1013
  icon: config.icon,
1052
- path: "/settings/global-attributes",
1014
+ path: "/settings/product-attributes",
1053
1015
  nested: void 0,
1054
1016
  rank: void 0,
1055
1017
  translationNs: void 0
@@ -160,15 +160,17 @@ const en = {
160
160
  };
161
161
  const locales = { ru, en };
162
162
  function detectLang() {
163
- var _a;
164
- if (typeof window === "undefined") return "en";
163
+ var _a, _b, _c;
164
+ if (typeof window === "undefined") return "ru";
165
165
  const stored = window.localStorage.getItem("i18nextLng");
166
166
  if (stored) {
167
167
  const short = stored.slice(0, 2).toLowerCase();
168
168
  if (locales[short]) return short;
169
169
  }
170
- const nav = (((_a = window.navigator) == null ? void 0 : _a.language) || "en").slice(0, 2).toLowerCase();
171
- return locales[nav] ? nav : "en";
170
+ const htmlLang = (_b = (_a = document.documentElement) == null ? void 0 : _a.lang) == null ? void 0 : _b.slice(0, 2).toLowerCase();
171
+ if (htmlLang && locales[htmlLang]) return htmlLang;
172
+ const nav = (((_c = window.navigator) == null ? void 0 : _c.language) || "ru").slice(0, 2).toLowerCase();
173
+ return locales[nav] ? nav : "ru";
172
174
  }
173
175
  function useT() {
174
176
  const dict = useMemo(() => {
@@ -177,7 +179,7 @@ function useT() {
177
179
  }, []);
178
180
  return (key, fallback) => dict[key] ?? fallback ?? key;
179
181
  }
180
- const emptyForm$2 = () => ({ label: "", type: "text", unit: "" });
182
+ const emptyForm$1 = () => ({ label: "", type: "text", unit: "" });
181
183
  const CategoryAttributeTemplatesWidget = ({
182
184
  data
183
185
  }) => {
@@ -188,7 +190,7 @@ const CategoryAttributeTemplatesWidget = ({
188
190
  const queryKey = ["category-custom-attributes", categoryId];
189
191
  const [showAddForm, setShowAddForm] = useState(false);
190
192
  const [showTemplateList, setShowTemplateList] = useState(false);
191
- const [addForm, setAddForm] = useState(emptyForm$2());
193
+ const [addForm, setAddForm] = useState(emptyForm$1());
192
194
  const [confirmDeleteId, setConfirmDeleteId] = useState(null);
193
195
  const [mutationError, setMutationError] = useState(null);
194
196
  const templatesQuery = useQuery({
@@ -228,7 +230,7 @@ const CategoryAttributeTemplatesWidget = ({
228
230
  onSuccess: () => {
229
231
  qc.invalidateQueries({ queryKey });
230
232
  setShowAddForm(false);
231
- setAddForm(emptyForm$2());
233
+ setAddForm(emptyForm$1());
232
234
  setMutationError(null);
233
235
  },
234
236
  onError: (err) => {
@@ -253,7 +255,7 @@ const CategoryAttributeTemplatesWidget = ({
253
255
  unit: addForm.type === "number" && addForm.unit.trim() ? addForm.unit.trim() : null
254
256
  });
255
257
  };
256
- const typeLabel2 = (v) => t(`type.${v}`, v);
258
+ const typeLabel = (v) => t(`type.${v}`, v);
257
259
  return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
258
260
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
259
261
  /* @__PURE__ */ jsx(Heading, { level: "h2", children: t("attributes") }),
@@ -267,16 +269,13 @@ const CategoryAttributeTemplatesWidget = ({
267
269
  children: t("fromTemplate")
268
270
  }
269
271
  ),
270
- /* @__PURE__ */ jsxs(
272
+ /* @__PURE__ */ jsx(
271
273
  Button,
272
274
  {
273
275
  variant: "secondary",
274
276
  size: "small",
275
277
  onClick: () => setShowAddForm(true),
276
- children: [
277
- "+ ",
278
- t("add")
279
- ]
278
+ children: t("add")
280
279
  }
281
280
  )
282
281
  ] })
@@ -305,7 +304,7 @@ const CategoryAttributeTemplatesWidget = ({
305
304
  children: [
306
305
  /* @__PURE__ */ jsx("span", { children: p.label }),
307
306
  /* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
308
- typeLabel2(p.type),
307
+ typeLabel(p.type),
309
308
  p.unit ? `, ${p.unit}` : ""
310
309
  ] })
311
310
  ]
@@ -324,10 +323,9 @@ const CategoryAttributeTemplatesWidget = ({
324
323
  children: [
325
324
  /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm text-ui-fg-subtle", children: attr.label }),
326
325
  /* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
327
- typeLabel2(attr.type),
326
+ typeLabel(attr.type),
328
327
  attr.unit ? `, ${attr.unit}` : ""
329
- ] }),
330
- /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "blue", children: "из родителя" })
328
+ ] })
331
329
  ]
332
330
  },
333
331
  attr.id
@@ -375,7 +373,7 @@ const CategoryAttributeTemplatesWidget = ({
375
373
  children: [
376
374
  /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm text-ui-fg-base", children: attr.label }),
377
375
  /* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
378
- typeLabel2(attr.type),
376
+ typeLabel(attr.type),
379
377
  attr.unit ? `, ${attr.unit}` : ""
380
378
  ] }),
381
379
  /* @__PURE__ */ jsx(
@@ -447,7 +445,7 @@ const CategoryAttributeTemplatesWidget = ({
447
445
  size: "small",
448
446
  onClick: () => {
449
447
  setShowAddForm(false);
450
- setAddForm(emptyForm$2());
448
+ setAddForm(emptyForm$1());
451
449
  setMutationError(null);
452
450
  },
453
451
  children: "Отмена"
@@ -665,71 +663,6 @@ const ProductAttributeValuesWidget = ({
665
663
  defineWidgetConfig({
666
664
  zone: "product.details.after"
667
665
  });
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";
733
666
  var __defProp = Object.defineProperty;
734
667
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
735
668
  var __hasOwnProp = Object.prototype.hasOwnProperty;
@@ -758,7 +691,7 @@ var __objRest = (source, exclude) => {
758
691
  }
759
692
  return target;
760
693
  };
761
- const SquaresPlus = React.forwardRef(
694
+ const Tag = React.forwardRef(
762
695
  (_a, ref) => {
763
696
  var _b = _a, { color = "currentColor" } = _b, props = __objRest(_b, ["color"]);
764
697
  return /* @__PURE__ */ React.createElement(
@@ -771,56 +704,73 @@ const SquaresPlus = React.forwardRef(
771
704
  fill: "none",
772
705
  ref
773
706
  }, props),
774
- /* @__PURE__ */ React.createElement(
707
+ /* @__PURE__ */ React.createElement("g", { clipPath: "url(#a)" }, /* @__PURE__ */ React.createElement(
775
708
  "path",
776
709
  {
777
- stroke: color,
778
- strokeLinecap: "round",
779
- strokeLinejoin: "round",
780
- strokeWidth: 1.5,
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"
710
+ fill: color,
711
+ fillRule: "evenodd",
712
+ 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",
713
+ clipRule: "evenodd"
782
714
  }
783
- )
715
+ )),
716
+ /* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("clipPath", { id: "a" }, /* @__PURE__ */ React.createElement("path", { fill: "#fff", d: "M0 0h15v15H0z" })))
784
717
  );
785
718
  }
786
719
  );
787
- SquaresPlus.displayName = "SquaresPlus";
788
- const emptyForm$1 = () => ({
789
- label: "",
790
- type: "text",
791
- unit: "",
792
- description: ""
793
- });
794
- const typeLabel$1 = (t) => t === "text" ? "Текст" : t === "number" ? "Число" : t === "file" ? "Файл" : t === "boolean" ? "Да/Нет" : t;
795
- const AttributeTemplatesSettingsPage = () => {
720
+ Tag.displayName = "Tag";
721
+ const emptyForm = () => ({ label: "", type: "text", unit: "", description: "" });
722
+ const ProductAttributesSettingsPage = () => {
723
+ const t = useT();
724
+ const [tab, setTab] = useState("globals");
725
+ return /* @__PURE__ */ jsxs(Container, { className: "p-0", children: [
726
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 border-b border-ui-border-base px-6 pt-4", children: [
727
+ /* @__PURE__ */ jsx(TabButton, { active: tab === "globals", onClick: () => setTab("globals"), children: t("globals") }),
728
+ /* @__PURE__ */ jsx(TabButton, { active: tab === "templates", onClick: () => setTab("templates"), children: t("templates") })
729
+ ] }),
730
+ tab === "globals" ? /* @__PURE__ */ jsx(GlobalsTab, {}) : /* @__PURE__ */ jsx(TemplatesTab, {})
731
+ ] });
732
+ };
733
+ const TabButton = ({
734
+ active,
735
+ onClick,
736
+ children
737
+ }) => /* @__PURE__ */ jsx(
738
+ "button",
739
+ {
740
+ onClick,
741
+ 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"}`,
742
+ children
743
+ }
744
+ );
745
+ const GlobalsTab = () => {
746
+ const t = useT();
796
747
  const qc = useQueryClient();
797
- const queryKey = ["attribute-templates"];
748
+ const queryKey = ["global-attributes"];
798
749
  const [showAddForm, setShowAddForm] = useState(false);
799
- const [addForm, setAddForm] = useState(emptyForm$1());
750
+ const [addForm, setAddForm] = useState(emptyForm());
800
751
  const [confirmDeleteId, setConfirmDeleteId] = useState(null);
801
752
  const { data, isLoading, isError } = useQuery({
802
753
  queryKey,
803
- queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
754
+ queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
804
755
  });
805
- const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
756
+ const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
806
757
  const createMutation = useMutation({
807
- mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
758
+ mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
808
759
  method: "POST",
809
760
  body: {
810
761
  label: body.label,
811
762
  type: body.type,
812
- unit: body.type === "number" && body.unit ? body.unit : null,
813
- description: body.description || null
763
+ unit: body.type === "number" && body.unit ? body.unit : null
814
764
  }
815
765
  }),
816
766
  onSuccess: () => {
817
767
  qc.invalidateQueries({ queryKey });
818
768
  setShowAddForm(false);
819
- setAddForm(emptyForm$1());
769
+ setAddForm(emptyForm());
820
770
  }
821
771
  });
822
772
  const deleteMutation = useMutation({
823
- mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
773
+ mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
824
774
  method: "PATCH",
825
775
  body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
826
776
  }),
@@ -838,86 +788,57 @@ const AttributeTemplatesSettingsPage = () => {
838
788
  description: addForm.description.trim()
839
789
  });
840
790
  };
841
- return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
842
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
791
+ return /* @__PURE__ */ jsxs("div", { className: "divide-y", children: [
792
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
843
793
  /* @__PURE__ */ jsxs("div", { children: [
844
- /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Шаблоны атрибутов" }),
845
- /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Заготовки атрибутов, которые можно применить к любой категории." })
794
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: t("globals") }),
795
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t("globalsDesc") })
846
796
  ] }),
847
- !showAddForm && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
797
+ !showAddForm && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: t("add") })
848
798
  ] }),
849
- isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", 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: [
862
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
863
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
864
- /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: p.label }),
865
- /* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
866
- typeLabel$1(p.type),
867
- p.unit ? `, ${p.unit}` : ""
868
- ] })
869
- ] }),
870
- p.description && /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
799
+ isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: t("loading") }) }),
800
+ isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: t("loadFailed") }) }),
801
+ !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: t("noGlobals") }) }),
802
+ /* @__PURE__ */ jsx("div", { className: "divide-y", children: attrs.map(
803
+ (a) => confirmDeleteId === a.id ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
804
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: t("confirmDelete").replace("{name}", a.label) }),
805
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(a.id), isLoading: deleteMutation.isPending, children: t("delete") }),
806
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: t("cancel") })
807
+ ] }, a.id) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
808
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-sm", children: a.label }),
809
+ /* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
810
+ t(`type.${a.type}`, a.type),
811
+ a.unit ? `, ${a.unit}` : ""
871
812
  ] }),
872
- /* @__PURE__ */ jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
873
- ] }, p.id)
813
+ /* @__PURE__ */ jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: t("delete") })
814
+ ] }, a.id)
874
815
  ) }),
875
- showAddForm && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
876
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
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" })
885
- ] }),
886
- /* @__PURE__ */ jsx(Input, { value: addForm.description, onChange: (e) => setAddForm((f) => ({ ...f, description: e.target.value })), placeholder: "Описание (необязательно)", className: "h-8 text-sm" }),
887
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
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: "Создать" })
893
- ] })
894
- ] })
816
+ showAddForm && /* @__PURE__ */ jsx(AddForm, { t, form: addForm, setForm: setAddForm, onCancel: () => {
817
+ setShowAddForm(false);
818
+ setAddForm(emptyForm());
819
+ }, onSubmit: handleAdd, isLoading: createMutation.isPending, withDescription: false })
895
820
  ] });
896
821
  };
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 = () => {
822
+ const TemplatesTab = () => {
823
+ const t = useT();
904
824
  const qc = useQueryClient();
905
- const queryKey = ["global-attributes"];
825
+ const queryKey = ["attribute-templates"];
906
826
  const [showAddForm, setShowAddForm] = useState(false);
907
827
  const [addForm, setAddForm] = useState(emptyForm());
908
828
  const [confirmDeleteId, setConfirmDeleteId] = useState(null);
909
829
  const { data, isLoading, isError } = useQuery({
910
830
  queryKey,
911
- queryFn: () => sdk.client.fetch(`/admin/global-attributes`)
831
+ queryFn: () => sdk.client.fetch(`/admin/attribute-templates`)
912
832
  });
913
- const attrs = (data == null ? void 0 : data.global_attributes) ?? [];
833
+ const templates2 = (data == null ? void 0 : data.attribute_templates) ?? [];
914
834
  const createMutation = useMutation({
915
- mutationFn: (body) => sdk.client.fetch(`/admin/global-attributes`, {
835
+ mutationFn: (body) => sdk.client.fetch(`/admin/attribute-templates`, {
916
836
  method: "POST",
917
837
  body: {
918
838
  label: body.label,
919
839
  type: body.type,
920
- unit: body.type === "number" && body.unit ? body.unit : null
840
+ unit: body.type === "number" && body.unit ? body.unit : null,
841
+ description: body.description || null
921
842
  }
922
843
  }),
923
844
  onSuccess: () => {
@@ -927,7 +848,7 @@ const GlobalAttributesSettingsPage = () => {
927
848
  }
928
849
  });
929
850
  const deleteMutation = useMutation({
930
- mutationFn: (id) => sdk.client.fetch(`/admin/global-attributes`, {
851
+ mutationFn: (id) => sdk.client.fetch(`/admin/attribute-templates`, {
931
852
  method: "PATCH",
932
853
  body: { id, deleted_at: (/* @__PURE__ */ new Date()).toISOString() }
933
854
  }),
@@ -939,60 +860,113 @@ const GlobalAttributesSettingsPage = () => {
939
860
  const handleAdd = () => {
940
861
  if (!addForm.label.trim()) return;
941
862
  createMutation.mutate({
863
+ ...addForm,
942
864
  label: addForm.label.trim(),
943
- type: addForm.type,
944
- unit: addForm.unit.trim()
865
+ unit: addForm.unit.trim(),
866
+ description: addForm.description.trim()
945
867
  });
946
868
  };
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: [
869
+ return /* @__PURE__ */ jsxs("div", { className: "divide-y", children: [
870
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
949
871
  /* @__PURE__ */ jsxs("div", { children: [
950
- /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Глобальные атрибуты" }),
951
- /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Применяются ко всем товарам автоматически. Значения у продуктов необязательны." })
872
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: t("templates") }),
873
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t("templatesDesc") })
952
874
  ] }),
953
- !showAddForm && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: "+ Добавить" })
875
+ !showAddForm && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowAddForm(true), children: t("add") })
954
876
  ] }),
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}` : ""
877
+ isLoading && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-muted text-sm", children: t("loading") }) }),
878
+ isError && /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error text-sm", children: t("loadFailed") }) }),
879
+ !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: t("noTemplates") }) }),
880
+ /* @__PURE__ */ jsx("div", { className: "divide-y", children: templates2.map(
881
+ (p) => confirmDeleteId === p.id ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3 text-sm", children: [
882
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: t("confirmDelete").replace("{name}", p.label) }),
883
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "danger", onClick: () => deleteMutation.mutate(p.id), isLoading: deleteMutation.isPending, children: t("delete") }),
884
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", onClick: () => setConfirmDeleteId(null), children: t("cancel") })
885
+ ] }, p.id) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-6 py-3", children: [
886
+ /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
887
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
888
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: p.label }),
889
+ /* @__PURE__ */ jsxs(Badge, { size: "2xsmall", color: "grey", children: [
890
+ t(`type.${p.type}`, p.type),
891
+ p.unit ? `, ${p.unit}` : ""
892
+ ] })
893
+ ] }),
894
+ p.description && /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-subtle", children: p.description })
972
895
  ] }),
973
- /* @__PURE__ */ jsx("button", { onClick: () => setConfirmDeleteId(a.id), className: "text-xs text-ui-fg-error hover:underline", children: "Удалить" })
974
- ] }, a.id)
896
+ /* @__PURE__ */ jsx("button", { onClick: () => setConfirmDeleteId(p.id), className: "text-xs text-ui-fg-error hover:underline", children: t("delete") })
897
+ ] }, p.id)
975
898
  ) }),
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
- ] })
899
+ showAddForm && /* @__PURE__ */ jsx(AddForm, { t, form: addForm, setForm: setAddForm, onCancel: () => {
900
+ setShowAddForm(false);
901
+ setAddForm(emptyForm());
902
+ }, onSubmit: handleAdd, isLoading: createMutation.isPending, withDescription: true })
991
903
  ] });
992
904
  };
905
+ const AddForm = ({
906
+ t,
907
+ form,
908
+ setForm,
909
+ onCancel,
910
+ onSubmit,
911
+ isLoading,
912
+ withDescription
913
+ }) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 px-6 py-4", children: [
914
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
915
+ /* @__PURE__ */ jsx(
916
+ Input,
917
+ {
918
+ value: form.label,
919
+ onChange: (e) => setForm((f) => ({ ...f, label: e.target.value })),
920
+ placeholder: t("name"),
921
+ className: "flex-1 h-8 text-sm",
922
+ autoFocus: true
923
+ }
924
+ ),
925
+ /* @__PURE__ */ jsxs(
926
+ "select",
927
+ {
928
+ value: form.type,
929
+ onChange: (e) => setForm((f) => ({
930
+ ...f,
931
+ type: e.target.value,
932
+ unit: e.target.value === "number" ? f.unit : ""
933
+ })),
934
+ className: "h-8 rounded border border-ui-border-base bg-ui-bg-base px-2 text-sm",
935
+ children: [
936
+ /* @__PURE__ */ jsx("option", { value: "text", children: t("type.text") }),
937
+ /* @__PURE__ */ jsx("option", { value: "number", children: t("type.number") }),
938
+ /* @__PURE__ */ jsx("option", { value: "file", children: t("type.file") }),
939
+ /* @__PURE__ */ jsx("option", { value: "boolean", children: t("type.boolean") })
940
+ ]
941
+ }
942
+ ),
943
+ form.type === "number" && /* @__PURE__ */ jsx(
944
+ Input,
945
+ {
946
+ value: form.unit,
947
+ onChange: (e) => setForm((f) => ({ ...f, unit: e.target.value })),
948
+ placeholder: t("unit"),
949
+ className: "w-28 h-8 text-sm"
950
+ }
951
+ )
952
+ ] }),
953
+ withDescription && /* @__PURE__ */ jsx(
954
+ Input,
955
+ {
956
+ value: form.description,
957
+ onChange: (e) => setForm((f) => ({ ...f, description: e.target.value })),
958
+ placeholder: t("description"),
959
+ className: "h-8 text-sm"
960
+ }
961
+ ),
962
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
963
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onCancel, children: t("cancel") }),
964
+ /* @__PURE__ */ jsx(Button, { size: "small", onClick: onSubmit, isLoading, children: t("create") })
965
+ ] })
966
+ ] });
993
967
  const config = defineRouteConfig({
994
- label: "Global Attributes",
995
- icon: Globe
968
+ label: "Product Attributes",
969
+ icon: Tag
996
970
  });
997
971
  const widgetModule = { widgets: [
998
972
  {
@@ -1007,29 +981,17 @@ const widgetModule = { widgets: [
1007
981
  const routeModule = {
1008
982
  routes: [
1009
983
  {
1010
- Component: AttributeTemplatesSettingsPage,
1011
- path: "/settings/attribute-templates"
1012
- },
1013
- {
1014
- Component: GlobalAttributesSettingsPage,
1015
- path: "/settings/global-attributes"
984
+ Component: ProductAttributesSettingsPage,
985
+ path: "/settings/product-attributes"
1016
986
  }
1017
987
  ]
1018
988
  };
1019
989
  const menuItemModule = {
1020
990
  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
- },
1029
991
  {
1030
992
  label: config.label,
1031
993
  icon: config.icon,
1032
- path: "/settings/global-attributes",
994
+ path: "/settings/product-attributes",
1033
995
  nested: void 0,
1034
996
  rank: void 0,
1035
997
  translationNs: void 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empty-complete-org/medusa-product-attributes",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Custom attributes module for Medusa v2 with support for text, number, file types and units of measurement",
5
5
  "author": "empty-complete",
6
6
  "license": "MIT",