@open-mercato/core 0.4.5-develop-3ce83a8b24 → 0.4.5-develop-539cff4960
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/entities/catalog_product/index.js +16 -0
- package/dist/generated/entities/catalog_product/index.js.map +2 -2
- package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
- package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
- package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
- package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
- package/dist/generated/entities/sales_invoice_line/index.js +7 -1
- package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +6 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entities/sales_quote_line/index.js +6 -0
- package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/api/prices/route.js +123 -8
- package/dist/modules/catalog/api/prices/route.js.map +2 -2
- package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
- package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +351 -201
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/index.js +1 -0
- package/dist/modules/catalog/commands/index.js.map +2 -2
- package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
- package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +355 -73
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/shared.js +18 -4
- package/dist/modules/catalog/commands/shared.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
- package/dist/modules/catalog/components/products/productForm.js +66 -5
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
- package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
- package/dist/modules/catalog/data/entities.js +86 -0
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +65 -3
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/events.js +3 -0
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/lib/unitCodes.js +7 -0
- package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
- package/dist/modules/catalog/lib/unitResolution.js +53 -0
- package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
- package/dist/modules/catalog/search.js +69 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/catalog/seed/examples.js +91 -42
- package/dist/modules/catalog/seed/examples.js.map +2 -2
- package/dist/modules/dashboards/seed/analytics.js +3 -0
- package/dist/modules/dashboards/seed/analytics.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +98 -15
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +101 -14
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
- package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +1424 -260
- package/dist/modules/sales/commands/documents.js.map +3 -3
- package/dist/modules/sales/commands/shared.js +6 -2
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +59 -3
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +35 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
- package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
- package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
- package/dist/modules/sales/search.js +28 -0
- package/dist/modules/sales/search.js.map +2 -2
- package/dist/modules/sales/seed/examples.js +14 -1
- package/dist/modules/sales/seed/examples.js.map +2 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +28 -15
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/translations.js +9 -0
- package/dist/modules/staff/translations.js.map +7 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js +97 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +7 -0
- package/dist/modules/translations/lib/extract-record-id.js +31 -2
- package/dist/modules/translations/lib/extract-record-id.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +3 -0
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +105 -36
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/dist/modules/translations/widgets/injection-table.js +18 -29
- package/dist/modules/translations/widgets/injection-table.js.map +2 -2
- package/generated/entities/catalog_product/index.ts +8 -0
- package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
- package/generated/entities/sales_credit_memo_line/index.ts +3 -0
- package/generated/entities/sales_invoice_line/index.ts +3 -0
- package/generated/entities/sales_order_line/index.ts +3 -0
- package/generated/entities/sales_quote_line/index.ts +3 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/auth/i18n/de.json +1 -1
- package/src/modules/auth/i18n/en.json +1 -1
- package/src/modules/auth/i18n/es.json +1 -1
- package/src/modules/auth/i18n/pl.json +1 -1
- package/src/modules/catalog/api/prices/route.ts +213 -81
- package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
- package/src/modules/catalog/api/products/route.ts +638 -402
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
- package/src/modules/catalog/commands/index.ts +1 -0
- package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
- package/src/modules/catalog/commands/products.ts +1151 -693
- package/src/modules/catalog/commands/shared.ts +19 -5
- package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
- package/src/modules/catalog/components/products/productForm.ts +369 -256
- package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
- package/src/modules/catalog/data/entities.ts +82 -1
- package/src/modules/catalog/data/validators.ts +118 -34
- package/src/modules/catalog/events.ts +3 -0
- package/src/modules/catalog/i18n/de.json +56 -0
- package/src/modules/catalog/i18n/en.json +56 -0
- package/src/modules/catalog/i18n/es.json +56 -0
- package/src/modules/catalog/i18n/pl.json +56 -0
- package/src/modules/catalog/lib/unitCodes.ts +1 -0
- package/src/modules/catalog/lib/unitResolution.ts +62 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
- package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
- package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
- package/src/modules/catalog/search.ts +73 -1
- package/src/modules/catalog/seed/examples.ts +552 -479
- package/src/modules/dashboards/i18n/de.json +1 -1
- package/src/modules/dashboards/i18n/en.json +1 -1
- package/src/modules/dashboards/i18n/es.json +1 -1
- package/src/modules/dashboards/i18n/pl.json +1 -1
- package/src/modules/dashboards/seed/analytics.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +158 -68
- package/src/modules/sales/api/quote-lines/route.ts +161 -67
- package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
- package/src/modules/sales/commands/documents.ts +4250 -2424
- package/src/modules/sales/commands/shared.ts +7 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
- package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
- package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
- package/src/modules/sales/data/entities.ts +53 -0
- package/src/modules/sales/data/validators.ts +36 -0
- package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
- package/src/modules/sales/i18n/de.json +23 -3
- package/src/modules/sales/i18n/en.json +23 -3
- package/src/modules/sales/i18n/es.json +23 -3
- package/src/modules/sales/i18n/pl.json +23 -3
- package/src/modules/sales/lib/types.ts +30 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
- package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
- package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
- package/src/modules/sales/search.ts +28 -0
- package/src/modules/sales/seed/examples.ts +20 -1
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +8 -0
- package/src/modules/staff/translations.ts +5 -0
- package/src/modules/translations/components/TranslationDrawerAction.tsx +107 -0
- package/src/modules/translations/lib/extract-record-id.ts +47 -3
- package/src/modules/translations/lib/resolve-field-list.ts +4 -0
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +108 -36
- package/src/modules/translations/widgets/injection-table.ts +19 -33
- package/src/modules/workflows/i18n/de.json +4 -4
- package/src/modules/workflows/i18n/en.json +4 -4
- package/src/modules/workflows/i18n/es.json +4 -4
- package/src/modules/workflows/i18n/pl.json +4 -4
|
@@ -4,7 +4,9 @@ import * as React from "react";
|
|
|
4
4
|
import dynamic from "next/dynamic";
|
|
5
5
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
6
6
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
CrudForm
|
|
9
|
+
} from "@open-mercato/ui/backend/CrudForm";
|
|
8
10
|
import { createCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
9
11
|
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
10
12
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
@@ -13,11 +15,25 @@ import { Button } from "@open-mercato/ui/primitives/button";
|
|
|
13
15
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
14
16
|
import { Label } from "@open-mercato/ui/primitives/label";
|
|
15
17
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
+
import {
|
|
19
|
+
Plus,
|
|
20
|
+
Trash2,
|
|
21
|
+
FileText,
|
|
22
|
+
AlignLeft,
|
|
23
|
+
ChevronLeft,
|
|
24
|
+
ChevronRight,
|
|
25
|
+
AlertCircle,
|
|
26
|
+
Settings
|
|
27
|
+
} from "lucide-react";
|
|
28
|
+
import {
|
|
29
|
+
apiCall,
|
|
30
|
+
readApiResultOrThrow
|
|
31
|
+
} from "@open-mercato/ui/backend/utils/apiCall";
|
|
18
32
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
19
33
|
import { E } from "../../../../../../generated/entities.ids.generated.js";
|
|
20
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
ProductMediaManager
|
|
36
|
+
} from "@open-mercato/core/modules/catalog/components/products/ProductMediaManager";
|
|
21
37
|
import { ProductCategorizeSection } from "@open-mercato/core/modules/catalog/components/products/ProductCategorizeSection";
|
|
22
38
|
import {
|
|
23
39
|
PRODUCT_FORM_STEPS,
|
|
@@ -39,7 +55,18 @@ import {
|
|
|
39
55
|
updateDimensionValue,
|
|
40
56
|
updateWeightValue
|
|
41
57
|
} from "@open-mercato/core/modules/catalog/components/products/productForm";
|
|
42
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
buildAttachmentImageUrl,
|
|
60
|
+
slugifyAttachmentFileName
|
|
61
|
+
} from "@open-mercato/core/modules/attachments/lib/imageUrls";
|
|
62
|
+
import { ProductUomSection } from "@open-mercato/core/modules/catalog/components/products/ProductUomSection";
|
|
63
|
+
import { canonicalizeUnitCode } from "@open-mercato/core/modules/catalog/lib/unitCodes";
|
|
64
|
+
import {
|
|
65
|
+
UNIT_PRICE_REFERENCE_UNITS,
|
|
66
|
+
toPositiveNumberOrNull,
|
|
67
|
+
toIntegerInRangeOrDefault,
|
|
68
|
+
normalizeProductConversionInputs
|
|
69
|
+
} from "@open-mercato/core/modules/catalog/components/products/productFormUtils";
|
|
43
70
|
const productFormTypedSchema = productFormSchema;
|
|
44
71
|
const MarkdownEditor = dynamic(() => import("@uiw/react-md-editor"), {
|
|
45
72
|
ssr: false,
|
|
@@ -58,8 +85,27 @@ const STEP_FIELD_MATCHERS = {
|
|
|
58
85
|
matchPrefix("dimensions"),
|
|
59
86
|
matchPrefix("weight")
|
|
60
87
|
],
|
|
61
|
-
organize: [
|
|
62
|
-
|
|
88
|
+
organize: [
|
|
89
|
+
matchField("categoryIds"),
|
|
90
|
+
matchField("channelIds"),
|
|
91
|
+
matchField("tags")
|
|
92
|
+
],
|
|
93
|
+
uom: [
|
|
94
|
+
matchField("defaultUnit"),
|
|
95
|
+
matchField("defaultSalesUnit"),
|
|
96
|
+
matchField("defaultSalesUnitQuantity"),
|
|
97
|
+
matchField("uomRoundingScale"),
|
|
98
|
+
matchField("uomRoundingMode"),
|
|
99
|
+
matchField("unitPriceEnabled"),
|
|
100
|
+
matchField("unitPriceReferenceUnit"),
|
|
101
|
+
matchField("unitPriceBaseQuantity"),
|
|
102
|
+
matchPrefix("unitConversions")
|
|
103
|
+
],
|
|
104
|
+
variants: [
|
|
105
|
+
matchField("hasVariants"),
|
|
106
|
+
matchPrefix("options"),
|
|
107
|
+
matchPrefix("variants")
|
|
108
|
+
]
|
|
63
109
|
};
|
|
64
110
|
function resolveStepForField(fieldId) {
|
|
65
111
|
const normalized = fieldId?.trim();
|
|
@@ -116,11 +162,12 @@ function CreateCatalogProductPage() {
|
|
|
116
162
|
React.useEffect(() => {
|
|
117
163
|
const loadPriceKinds = async () => {
|
|
118
164
|
try {
|
|
119
|
-
const payload = await readApiResultOrThrow(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
165
|
+
const payload = await readApiResultOrThrow("/api/catalog/price-kinds?pageSize=100", void 0, {
|
|
166
|
+
errorMessage: t(
|
|
167
|
+
"catalog.priceKinds.errors.load",
|
|
168
|
+
"Failed to load price kinds."
|
|
169
|
+
)
|
|
170
|
+
});
|
|
124
171
|
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
125
172
|
setPriceKinds(
|
|
126
173
|
items.map((item) => normalizePriceKindSummary(item)).filter((item) => item !== null)
|
|
@@ -136,18 +183,23 @@ function CreateCatalogProductPage() {
|
|
|
136
183
|
React.useEffect(() => {
|
|
137
184
|
const loadTaxRates = async () => {
|
|
138
185
|
try {
|
|
139
|
-
const payload = await readApiResultOrThrow(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
186
|
+
const payload = await readApiResultOrThrow("/api/sales/tax-rates?pageSize=100", void 0, {
|
|
187
|
+
errorMessage: t(
|
|
188
|
+
"catalog.products.create.taxRates.error",
|
|
189
|
+
"Failed to load tax rates."
|
|
190
|
+
),
|
|
191
|
+
fallback: { items: [] }
|
|
192
|
+
});
|
|
144
193
|
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
145
194
|
setTaxRates(
|
|
146
195
|
items.map((item) => {
|
|
147
196
|
const rawRate = typeof item.rate === "number" ? item.rate : Number(item.rate ?? Number.NaN);
|
|
148
197
|
return {
|
|
149
198
|
id: String(item.id),
|
|
150
|
-
name: typeof item.name === "string" && item.name.trim().length ? item.name : t(
|
|
199
|
+
name: typeof item.name === "string" && item.name.trim().length ? item.name : t(
|
|
200
|
+
"catalog.products.create.taxRates.unnamed",
|
|
201
|
+
"Untitled tax rate"
|
|
202
|
+
),
|
|
151
203
|
code: typeof item.code === "string" && item.code.trim().length ? item.code : null,
|
|
152
204
|
rate: Number.isFinite(rawRate) ? rawRate : null,
|
|
153
205
|
isDefault: resolveBooleanFlag(
|
|
@@ -164,37 +216,51 @@ function CreateCatalogProductPage() {
|
|
|
164
216
|
loadTaxRates().catch(() => {
|
|
165
217
|
});
|
|
166
218
|
}, [t]);
|
|
167
|
-
const groups = React.useMemo(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
{
|
|
219
|
+
const groups = React.useMemo(
|
|
220
|
+
() => [
|
|
221
|
+
{
|
|
222
|
+
id: "builder",
|
|
223
|
+
column: 1,
|
|
224
|
+
component: ({
|
|
174
225
|
values,
|
|
175
226
|
setValue,
|
|
176
|
-
errors
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
227
|
+
errors
|
|
228
|
+
}) => /* @__PURE__ */ jsx(
|
|
229
|
+
ProductBuilder,
|
|
230
|
+
{
|
|
231
|
+
values,
|
|
232
|
+
setValue,
|
|
233
|
+
errors,
|
|
234
|
+
priceKinds,
|
|
235
|
+
taxRates
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "product-meta",
|
|
241
|
+
column: 2,
|
|
242
|
+
title: t("catalog.products.create.meta.title", "Product meta"),
|
|
243
|
+
description: t(
|
|
244
|
+
"catalog.products.create.meta.description",
|
|
245
|
+
"Manage subtitle and handle for storefronts."
|
|
246
|
+
),
|
|
247
|
+
component: ({
|
|
190
248
|
values,
|
|
191
249
|
setValue,
|
|
192
|
-
errors
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
250
|
+
errors
|
|
251
|
+
}) => /* @__PURE__ */ jsx(
|
|
252
|
+
ProductMetaSection,
|
|
253
|
+
{
|
|
254
|
+
values,
|
|
255
|
+
setValue,
|
|
256
|
+
errors,
|
|
257
|
+
taxRates
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
],
|
|
262
|
+
[priceKinds, taxRates, t]
|
|
263
|
+
);
|
|
198
264
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
|
|
199
265
|
CrudForm,
|
|
200
266
|
{
|
|
@@ -210,9 +276,18 @@ function CreateCatalogProductPage() {
|
|
|
210
276
|
onSubmit: async (formValues) => {
|
|
211
277
|
const title = formValues.title?.trim();
|
|
212
278
|
if (!title) {
|
|
213
|
-
throw createCrudFormError(
|
|
214
|
-
|
|
215
|
-
|
|
279
|
+
throw createCrudFormError(
|
|
280
|
+
t(
|
|
281
|
+
"catalog.products.create.errors.title",
|
|
282
|
+
"Provide a product title."
|
|
283
|
+
),
|
|
284
|
+
{
|
|
285
|
+
title: t(
|
|
286
|
+
"catalog.products.create.errors.title",
|
|
287
|
+
"Provide a product title."
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
);
|
|
216
291
|
}
|
|
217
292
|
const handle = formValues.handle?.trim() || void 0;
|
|
218
293
|
const description = formValues.description?.trim() || void 0;
|
|
@@ -224,8 +299,13 @@ function CreateCatalogProductPage() {
|
|
|
224
299
|
const defaultMediaUrl = defaultMediaEntry ? buildAttachmentImageUrl(defaultMediaEntry.id, {
|
|
225
300
|
slug: slugifyAttachmentFileName(defaultMediaEntry.fileName)
|
|
226
301
|
}) : null;
|
|
227
|
-
const optionSchemaDefinition = buildOptionSchemaDefinition(
|
|
228
|
-
|
|
302
|
+
const optionSchemaDefinition = buildOptionSchemaDefinition(
|
|
303
|
+
formValues.options,
|
|
304
|
+
title
|
|
305
|
+
);
|
|
306
|
+
const dimensions = sanitizeProductDimensions(
|
|
307
|
+
formValues.dimensions ?? null
|
|
308
|
+
);
|
|
229
309
|
const weight = sanitizeProductWeight(formValues.weight ?? null);
|
|
230
310
|
const resolveTaxRateValue = (taxRateId) => {
|
|
231
311
|
if (!taxRateId) return null;
|
|
@@ -239,6 +319,85 @@ function CreateCatalogProductPage() {
|
|
|
239
319
|
const resolvedVariantTaxRate = resolveTaxRateValue(resolvedVariantTaxRateId) ?? (resolvedVariantTaxRateId ? null : productTaxRate ?? null);
|
|
240
320
|
return { resolvedVariantTaxRateId, resolvedVariantTaxRate };
|
|
241
321
|
};
|
|
322
|
+
const defaultUnit = canonicalizeUnitCode(formValues.defaultUnit);
|
|
323
|
+
const defaultSalesUnit = canonicalizeUnitCode(
|
|
324
|
+
formValues.defaultSalesUnit
|
|
325
|
+
);
|
|
326
|
+
const defaultSalesUnitQuantity = toPositiveNumberOrNull(formValues.defaultSalesUnitQuantity) ?? 1;
|
|
327
|
+
const uomRoundingScale = toIntegerInRangeOrDefault(
|
|
328
|
+
formValues.uomRoundingScale,
|
|
329
|
+
0,
|
|
330
|
+
6,
|
|
331
|
+
4
|
|
332
|
+
);
|
|
333
|
+
const uomRoundingMode = formValues.uomRoundingMode === "down" || formValues.uomRoundingMode === "up" ? formValues.uomRoundingMode : "half_up";
|
|
334
|
+
const unitPriceEnabled = Boolean(formValues.unitPriceEnabled);
|
|
335
|
+
const unitPriceReferenceUnit = canonicalizeUnitCode(
|
|
336
|
+
formValues.unitPriceReferenceUnit
|
|
337
|
+
);
|
|
338
|
+
const unitPriceBaseQuantity = toPositiveNumberOrNull(
|
|
339
|
+
formValues.unitPriceBaseQuantity
|
|
340
|
+
);
|
|
341
|
+
if (defaultSalesUnit && !defaultUnit) {
|
|
342
|
+
const message = t(
|
|
343
|
+
"catalog.products.uom.errors.baseRequired",
|
|
344
|
+
"Base unit is required when default sales unit is set."
|
|
345
|
+
);
|
|
346
|
+
throw createCrudFormError(message, { defaultSalesUnit: message });
|
|
347
|
+
}
|
|
348
|
+
const conversionInputs = normalizeProductConversionInputs(
|
|
349
|
+
formValues.unitConversions,
|
|
350
|
+
t(
|
|
351
|
+
"catalog.products.uom.errors.duplicateConversion",
|
|
352
|
+
"Duplicate conversion unit is not allowed."
|
|
353
|
+
)
|
|
354
|
+
);
|
|
355
|
+
if (conversionInputs.length && !defaultUnit) {
|
|
356
|
+
const message = t(
|
|
357
|
+
"catalog.products.uom.errors.baseRequiredForConversions",
|
|
358
|
+
"Base unit is required when conversions are configured."
|
|
359
|
+
);
|
|
360
|
+
throw createCrudFormError(message, { defaultUnit: message });
|
|
361
|
+
}
|
|
362
|
+
const defaultUnitKey = defaultUnit?.toLowerCase() ?? null;
|
|
363
|
+
const defaultSalesUnitKey = defaultSalesUnit?.toLowerCase() ?? null;
|
|
364
|
+
if (defaultUnitKey && defaultSalesUnitKey && defaultSalesUnitKey !== defaultUnitKey) {
|
|
365
|
+
const hasDefaultSalesConversion = conversionInputs.some(
|
|
366
|
+
(entry) => entry.isActive && entry.unitCode.toLowerCase() === defaultSalesUnitKey
|
|
367
|
+
);
|
|
368
|
+
if (!hasDefaultSalesConversion) {
|
|
369
|
+
const message = t(
|
|
370
|
+
"catalog.products.uom.errors.defaultSalesConversionRequired",
|
|
371
|
+
"Active conversion for default sales unit is required when it differs from base unit."
|
|
372
|
+
);
|
|
373
|
+
throw createCrudFormError(message, {
|
|
374
|
+
defaultSalesUnit: message,
|
|
375
|
+
unitConversions: message
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (unitPriceEnabled) {
|
|
380
|
+
if (!unitPriceReferenceUnit || !UNIT_PRICE_REFERENCE_UNITS.has(
|
|
381
|
+
unitPriceReferenceUnit
|
|
382
|
+
)) {
|
|
383
|
+
const message = t(
|
|
384
|
+
"catalog.products.unitPrice.errors.referenceUnit",
|
|
385
|
+
"Reference unit is required when unit price display is enabled."
|
|
386
|
+
);
|
|
387
|
+
throw createCrudFormError(message, {
|
|
388
|
+
unitPriceReferenceUnit: message
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (unitPriceBaseQuantity === null) {
|
|
392
|
+
const message = t(
|
|
393
|
+
"catalog.products.unitPrice.errors.baseQuantity",
|
|
394
|
+
"Base quantity is required when unit price display is enabled."
|
|
395
|
+
);
|
|
396
|
+
throw createCrudFormError(message, {
|
|
397
|
+
unitPriceBaseQuantity: message
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
242
401
|
const productPayload = {
|
|
243
402
|
title,
|
|
244
403
|
subtitle: formValues.subtitle?.trim() || void 0,
|
|
@@ -251,7 +410,15 @@ function CreateCatalogProductPage() {
|
|
|
251
410
|
defaultMediaUrl: defaultMediaUrl ?? void 0,
|
|
252
411
|
dimensions,
|
|
253
412
|
weightValue: weight?.value ?? null,
|
|
254
|
-
weightUnit: weight?.unit ?? null
|
|
413
|
+
weightUnit: weight?.unit ?? null,
|
|
414
|
+
defaultUnit: defaultUnit ?? null,
|
|
415
|
+
defaultSalesUnit: defaultSalesUnit ?? defaultUnit ?? null,
|
|
416
|
+
defaultSalesUnitQuantity,
|
|
417
|
+
uomRoundingScale,
|
|
418
|
+
uomRoundingMode,
|
|
419
|
+
unitPriceEnabled,
|
|
420
|
+
unitPriceReferenceUnit: unitPriceEnabled ? unitPriceReferenceUnit : void 0,
|
|
421
|
+
unitPriceBaseQuantity: unitPriceEnabled ? unitPriceBaseQuantity : void 0
|
|
255
422
|
};
|
|
256
423
|
if (optionSchemaDefinition) {
|
|
257
424
|
productPayload.optionSchema = optionSchemaDefinition;
|
|
@@ -278,7 +445,11 @@ function CreateCatalogProductPage() {
|
|
|
278
445
|
defaultMediaUrl: defaultMediaUrl ?? void 0
|
|
279
446
|
}));
|
|
280
447
|
}
|
|
281
|
-
const variantDrafts = (Array.isArray(formValues.variants) && formValues.variants.length ? formValues.variants : [
|
|
448
|
+
const variantDrafts = (Array.isArray(formValues.variants) && formValues.variants.length ? formValues.variants : [
|
|
449
|
+
createVariantDraft(formValues.taxRateId ?? null, {
|
|
450
|
+
isDefault: true
|
|
451
|
+
})
|
|
452
|
+
]) ?? [];
|
|
282
453
|
const priceRequests = [];
|
|
283
454
|
for (const variant of variantDrafts) {
|
|
284
455
|
const { resolvedVariantTaxRateId, resolvedVariantTaxRate } = resolveVariantTax(variant);
|
|
@@ -288,13 +459,19 @@ function CreateCatalogProductPage() {
|
|
|
288
459
|
const numeric = Number(value);
|
|
289
460
|
if (Number.isNaN(numeric) || !Number.isFinite(numeric) || numeric < 0) {
|
|
290
461
|
throw createCrudFormError(
|
|
291
|
-
t(
|
|
462
|
+
t(
|
|
463
|
+
"catalog.products.create.errors.priceNonNegative",
|
|
464
|
+
"Prices must be zero or greater."
|
|
465
|
+
)
|
|
292
466
|
);
|
|
293
467
|
}
|
|
294
468
|
const currencyCode = typeof priceKind.currencyCode === "string" && priceKind.currencyCode.trim().length ? priceKind.currencyCode.trim().toUpperCase() : "";
|
|
295
469
|
if (!currencyCode) {
|
|
296
470
|
throw createCrudFormError(
|
|
297
|
-
t(
|
|
471
|
+
t(
|
|
472
|
+
"catalog.products.create.errors.currency",
|
|
473
|
+
"Provide a currency for all price kinds."
|
|
474
|
+
),
|
|
298
475
|
{}
|
|
299
476
|
);
|
|
300
477
|
}
|
|
@@ -311,12 +488,29 @@ function CreateCatalogProductPage() {
|
|
|
311
488
|
}
|
|
312
489
|
const cleanupState = { productId: null, variantIds: [] };
|
|
313
490
|
try {
|
|
314
|
-
const { result: created } = await createCrud(
|
|
491
|
+
const { result: created } = await createCrud(
|
|
492
|
+
"catalog/products",
|
|
493
|
+
productPayload
|
|
494
|
+
);
|
|
315
495
|
const productId = created?.id;
|
|
316
496
|
if (!productId) {
|
|
317
|
-
throw createCrudFormError(
|
|
497
|
+
throw createCrudFormError(
|
|
498
|
+
t(
|
|
499
|
+
"catalog.products.create.errors.id",
|
|
500
|
+
"Product id missing after create."
|
|
501
|
+
)
|
|
502
|
+
);
|
|
318
503
|
}
|
|
319
504
|
cleanupState.productId = productId;
|
|
505
|
+
for (const conversion of conversionInputs) {
|
|
506
|
+
await createCrud("catalog/product-unit-conversions", {
|
|
507
|
+
productId,
|
|
508
|
+
unitCode: conversion.unitCode,
|
|
509
|
+
toBaseFactor: conversion.toBaseFactor,
|
|
510
|
+
sortOrder: conversion.sortOrder,
|
|
511
|
+
isActive: conversion.isActive
|
|
512
|
+
});
|
|
513
|
+
}
|
|
320
514
|
const variantIdMap = {};
|
|
321
515
|
for (const variant of variantDrafts) {
|
|
322
516
|
const { resolvedVariantTaxRateId, resolvedVariantTaxRate } = resolveVariantTax(variant);
|
|
@@ -330,13 +524,15 @@ function CreateCatalogProductPage() {
|
|
|
330
524
|
taxRateId: resolvedVariantTaxRateId ?? null,
|
|
331
525
|
taxRate: resolvedVariantTaxRate ?? null
|
|
332
526
|
};
|
|
333
|
-
const { result: variantResult } = await createCrud(
|
|
334
|
-
"catalog/variants",
|
|
335
|
-
variantPayload
|
|
336
|
-
);
|
|
527
|
+
const { result: variantResult } = await createCrud("catalog/variants", variantPayload);
|
|
337
528
|
const variantId = variantResult?.variantId ?? variantResult?.id;
|
|
338
529
|
if (!variantId) {
|
|
339
|
-
throw createCrudFormError(
|
|
530
|
+
throw createCrudFormError(
|
|
531
|
+
t(
|
|
532
|
+
"catalog.products.create.errors.variant",
|
|
533
|
+
"Failed to create variant."
|
|
534
|
+
)
|
|
535
|
+
);
|
|
340
536
|
}
|
|
341
537
|
variantIdMap[variant.id] = variantId;
|
|
342
538
|
cleanupState.variantIds.push(variantId);
|
|
@@ -378,7 +574,10 @@ function CreateCatalogProductPage() {
|
|
|
378
574
|
{ fallback: null }
|
|
379
575
|
);
|
|
380
576
|
if (!transfer.ok) {
|
|
381
|
-
console.error(
|
|
577
|
+
console.error(
|
|
578
|
+
"attachments.transfer.failed",
|
|
579
|
+
transfer.result?.error
|
|
580
|
+
);
|
|
382
581
|
}
|
|
383
582
|
}
|
|
384
583
|
if (inboxDraft) {
|
|
@@ -398,17 +597,31 @@ function CreateCatalogProductPage() {
|
|
|
398
597
|
}
|
|
399
598
|
);
|
|
400
599
|
} catch {
|
|
401
|
-
flash(
|
|
600
|
+
flash(
|
|
601
|
+
t(
|
|
602
|
+
"inbox_ops.flash.complete_failed",
|
|
603
|
+
"Product created but failed to update inbox action status."
|
|
604
|
+
),
|
|
605
|
+
"warning"
|
|
606
|
+
);
|
|
402
607
|
}
|
|
403
608
|
}
|
|
404
|
-
flash(
|
|
609
|
+
flash(
|
|
610
|
+
t("catalog.products.create.success", "Product created."),
|
|
611
|
+
"success"
|
|
612
|
+
);
|
|
405
613
|
if (inboxDraft) {
|
|
406
|
-
router.push(
|
|
614
|
+
router.push(
|
|
615
|
+
`/backend/inbox-ops/proposals/${encodeURIComponent(inboxDraft.proposalId)}`
|
|
616
|
+
);
|
|
407
617
|
} else {
|
|
408
618
|
router.push("/backend/catalog/products");
|
|
409
619
|
}
|
|
410
620
|
} catch (err) {
|
|
411
|
-
await cleanupFailedProduct(
|
|
621
|
+
await cleanupFailedProduct(
|
|
622
|
+
cleanupState.productId,
|
|
623
|
+
cleanupState.variantIds
|
|
624
|
+
);
|
|
412
625
|
throw err;
|
|
413
626
|
}
|
|
414
627
|
}
|
|
@@ -419,15 +632,22 @@ async function cleanupFailedProduct(productId, variantIds) {
|
|
|
419
632
|
if (!productId && variantIds.length === 0) return;
|
|
420
633
|
if (variantIds.length) {
|
|
421
634
|
const variantDeletes = variantIds.map(
|
|
422
|
-
(variantId) => apiCall(`/api/catalog/variants?id=${encodeURIComponent(variantId)}`, {
|
|
635
|
+
(variantId) => apiCall(`/api/catalog/variants?id=${encodeURIComponent(variantId)}`, {
|
|
636
|
+
method: "DELETE"
|
|
637
|
+
}).catch(() => null)
|
|
423
638
|
);
|
|
424
639
|
await Promise.allSettled(variantDeletes);
|
|
425
640
|
}
|
|
426
641
|
if (productId) {
|
|
427
|
-
await apiCall(`/api/catalog/products?id=${encodeURIComponent(productId)}`, {
|
|
642
|
+
await apiCall(`/api/catalog/products?id=${encodeURIComponent(productId)}`, {
|
|
643
|
+
method: "DELETE"
|
|
644
|
+
}).catch(() => null);
|
|
428
645
|
}
|
|
429
646
|
}
|
|
430
|
-
function ProductDimensionsFields({
|
|
647
|
+
function ProductDimensionsFields({
|
|
648
|
+
values,
|
|
649
|
+
setValue
|
|
650
|
+
}) {
|
|
431
651
|
const t = useT();
|
|
432
652
|
const dimensionValues = normalizeProductDimensions(values.dimensions);
|
|
433
653
|
const weightValues = normalizeProductWeight(values.weight);
|
|
@@ -441,7 +661,14 @@ function ProductDimensionsFields({ values, setValue }) {
|
|
|
441
661
|
{
|
|
442
662
|
type: "number",
|
|
443
663
|
value: dimensionValues?.width ?? "",
|
|
444
|
-
onChange: (event) => setValue(
|
|
664
|
+
onChange: (event) => setValue(
|
|
665
|
+
"dimensions",
|
|
666
|
+
updateDimensionValue(
|
|
667
|
+
values.dimensions ?? null,
|
|
668
|
+
"width",
|
|
669
|
+
event.target.value
|
|
670
|
+
)
|
|
671
|
+
),
|
|
445
672
|
placeholder: "0"
|
|
446
673
|
}
|
|
447
674
|
)
|
|
@@ -453,7 +680,14 @@ function ProductDimensionsFields({ values, setValue }) {
|
|
|
453
680
|
{
|
|
454
681
|
type: "number",
|
|
455
682
|
value: dimensionValues?.height ?? "",
|
|
456
|
-
onChange: (event) => setValue(
|
|
683
|
+
onChange: (event) => setValue(
|
|
684
|
+
"dimensions",
|
|
685
|
+
updateDimensionValue(
|
|
686
|
+
values.dimensions ?? null,
|
|
687
|
+
"height",
|
|
688
|
+
event.target.value
|
|
689
|
+
)
|
|
690
|
+
),
|
|
457
691
|
placeholder: "0"
|
|
458
692
|
}
|
|
459
693
|
)
|
|
@@ -465,7 +699,14 @@ function ProductDimensionsFields({ values, setValue }) {
|
|
|
465
699
|
{
|
|
466
700
|
type: "number",
|
|
467
701
|
value: dimensionValues?.depth ?? "",
|
|
468
|
-
onChange: (event) => setValue(
|
|
702
|
+
onChange: (event) => setValue(
|
|
703
|
+
"dimensions",
|
|
704
|
+
updateDimensionValue(
|
|
705
|
+
values.dimensions ?? null,
|
|
706
|
+
"depth",
|
|
707
|
+
event.target.value
|
|
708
|
+
)
|
|
709
|
+
),
|
|
469
710
|
placeholder: "0"
|
|
470
711
|
}
|
|
471
712
|
)
|
|
@@ -476,7 +717,14 @@ function ProductDimensionsFields({ values, setValue }) {
|
|
|
476
717
|
Input,
|
|
477
718
|
{
|
|
478
719
|
value: dimensionValues?.unit ?? "",
|
|
479
|
-
onChange: (event) => setValue(
|
|
720
|
+
onChange: (event) => setValue(
|
|
721
|
+
"dimensions",
|
|
722
|
+
updateDimensionValue(
|
|
723
|
+
values.dimensions ?? null,
|
|
724
|
+
"unit",
|
|
725
|
+
event.target.value
|
|
726
|
+
)
|
|
727
|
+
),
|
|
480
728
|
placeholder: "cm"
|
|
481
729
|
}
|
|
482
730
|
)
|
|
@@ -488,7 +736,14 @@ function ProductDimensionsFields({ values, setValue }) {
|
|
|
488
736
|
{
|
|
489
737
|
type: "number",
|
|
490
738
|
value: weightValues?.value ?? "",
|
|
491
|
-
onChange: (event) => setValue(
|
|
739
|
+
onChange: (event) => setValue(
|
|
740
|
+
"weight",
|
|
741
|
+
updateWeightValue(
|
|
742
|
+
values.weight ?? null,
|
|
743
|
+
"value",
|
|
744
|
+
event.target.value
|
|
745
|
+
)
|
|
746
|
+
),
|
|
492
747
|
placeholder: "0"
|
|
493
748
|
}
|
|
494
749
|
)
|
|
@@ -499,7 +754,14 @@ function ProductDimensionsFields({ values, setValue }) {
|
|
|
499
754
|
Input,
|
|
500
755
|
{
|
|
501
756
|
value: weightValues?.unit ?? "",
|
|
502
|
-
onChange: (event) => setValue(
|
|
757
|
+
onChange: (event) => setValue(
|
|
758
|
+
"weight",
|
|
759
|
+
updateWeightValue(
|
|
760
|
+
values.weight ?? null,
|
|
761
|
+
"unit",
|
|
762
|
+
event.target.value
|
|
763
|
+
)
|
|
764
|
+
),
|
|
503
765
|
placeholder: "kg"
|
|
504
766
|
}
|
|
505
767
|
)
|
|
@@ -507,7 +769,13 @@ function ProductDimensionsFields({ values, setValue }) {
|
|
|
507
769
|
] })
|
|
508
770
|
] });
|
|
509
771
|
}
|
|
510
|
-
function ProductBuilder({
|
|
772
|
+
function ProductBuilder({
|
|
773
|
+
values,
|
|
774
|
+
setValue,
|
|
775
|
+
errors,
|
|
776
|
+
priceKinds,
|
|
777
|
+
taxRates
|
|
778
|
+
}) {
|
|
511
779
|
const t = useT();
|
|
512
780
|
const steps = PRODUCT_FORM_STEPS;
|
|
513
781
|
const [currentStep, setCurrentStep] = React.useState(0);
|
|
@@ -523,10 +791,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
523
791
|
setValue("taxRateId", fallback.id);
|
|
524
792
|
}, [taxRates, setValue, values.taxRateId]);
|
|
525
793
|
const stepErrors = React.useMemo(() => {
|
|
526
|
-
const map = steps.reduce(
|
|
527
|
-
acc
|
|
528
|
-
|
|
529
|
-
|
|
794
|
+
const map = steps.reduce(
|
|
795
|
+
(acc, step) => {
|
|
796
|
+
acc[step] = [];
|
|
797
|
+
return acc;
|
|
798
|
+
},
|
|
799
|
+
{}
|
|
800
|
+
);
|
|
530
801
|
Object.entries(errors).forEach(([fieldId, message]) => {
|
|
531
802
|
const step = resolveStepForField(fieldId);
|
|
532
803
|
if (!step) return;
|
|
@@ -535,14 +806,20 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
535
806
|
});
|
|
536
807
|
return map;
|
|
537
808
|
}, [errors, steps]);
|
|
538
|
-
const errorSignature = React.useMemo(
|
|
809
|
+
const errorSignature = React.useMemo(
|
|
810
|
+
() => Object.keys(errors).sort().join("|"),
|
|
811
|
+
[errors]
|
|
812
|
+
);
|
|
539
813
|
const lastErrorSignatureRef = React.useRef(null);
|
|
540
814
|
React.useEffect(() => {
|
|
541
|
-
if (!errorSignature || errorSignature === lastErrorSignatureRef.current)
|
|
815
|
+
if (!errorSignature || errorSignature === lastErrorSignatureRef.current)
|
|
816
|
+
return;
|
|
542
817
|
lastErrorSignatureRef.current = errorSignature;
|
|
543
818
|
const currentStepKey2 = steps[currentStep];
|
|
544
819
|
if (currentStepKey2 && stepErrors[currentStepKey2]?.length) return;
|
|
545
|
-
const fallbackIndex = steps.findIndex(
|
|
820
|
+
const fallbackIndex = steps.findIndex(
|
|
821
|
+
(step) => (stepErrors[step] ?? []).length > 0
|
|
822
|
+
);
|
|
546
823
|
if (fallbackIndex >= 0 && fallbackIndex !== currentStep) {
|
|
547
824
|
setCurrentStep(fallbackIndex);
|
|
548
825
|
}
|
|
@@ -556,11 +833,16 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
556
833
|
if (currentStep >= steps.length) setCurrentStep(0);
|
|
557
834
|
}, [currentStep, steps.length]);
|
|
558
835
|
const currentStepKey = steps[currentStep] ?? steps[0];
|
|
559
|
-
const mediaItems =
|
|
836
|
+
const mediaItems = React.useMemo(
|
|
837
|
+
() => Array.isArray(values.mediaItems) ? values.mediaItems : [],
|
|
838
|
+
[values.mediaItems]
|
|
839
|
+
);
|
|
560
840
|
const handleMediaItemsChange = React.useCallback(
|
|
561
841
|
(nextItems) => {
|
|
562
842
|
setValue("mediaItems", nextItems);
|
|
563
|
-
const hasCurrent = nextItems.some(
|
|
843
|
+
const hasCurrent = nextItems.some(
|
|
844
|
+
(item) => item.id === values.defaultMediaId
|
|
845
|
+
);
|
|
564
846
|
if (!hasCurrent) {
|
|
565
847
|
const fallbackId = nextItems[0]?.id ?? null;
|
|
566
848
|
setValue("defaultMediaId", fallbackId);
|
|
@@ -589,7 +871,9 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
589
871
|
if (target) {
|
|
590
872
|
setValue(
|
|
591
873
|
"defaultMediaUrl",
|
|
592
|
-
buildAttachmentImageUrl(target.id, {
|
|
874
|
+
buildAttachmentImageUrl(target.id, {
|
|
875
|
+
slug: slugifyAttachmentFileName(target.fileName)
|
|
876
|
+
})
|
|
593
877
|
);
|
|
594
878
|
}
|
|
595
879
|
},
|
|
@@ -599,13 +883,20 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
599
883
|
const optionDefinitions = Array.isArray(values.options) ? values.options : [];
|
|
600
884
|
if (!values.hasVariants || !optionDefinitions.length) {
|
|
601
885
|
if (!values.variants || !values.variants.length) {
|
|
602
|
-
setValue("variants", [
|
|
886
|
+
setValue("variants", [
|
|
887
|
+
createVariantDraft(values.taxRateId ?? null, { isDefault: true })
|
|
888
|
+
]);
|
|
603
889
|
}
|
|
604
890
|
return;
|
|
605
891
|
}
|
|
606
892
|
const combos = buildVariantCombinations(optionDefinitions);
|
|
607
893
|
const existing = Array.isArray(values.variants) ? values.variants : [];
|
|
608
|
-
const existingByKey = new Map(
|
|
894
|
+
const existingByKey = new Map(
|
|
895
|
+
existing.map((variant) => [
|
|
896
|
+
buildOptionValuesKey(variant.optionValues),
|
|
897
|
+
variant
|
|
898
|
+
])
|
|
899
|
+
);
|
|
609
900
|
let hasDefault = existing.some((variant) => variant.isDefault);
|
|
610
901
|
let changed = existing.length !== combos.length;
|
|
611
902
|
const nextVariants = combos.map((combo, index) => {
|
|
@@ -636,7 +927,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
636
927
|
if (changed) {
|
|
637
928
|
setValue("variants", nextVariants);
|
|
638
929
|
}
|
|
639
|
-
}, [
|
|
930
|
+
}, [
|
|
931
|
+
values.options,
|
|
932
|
+
values.variants,
|
|
933
|
+
values.hasVariants,
|
|
934
|
+
values.taxRateId,
|
|
935
|
+
setValue
|
|
936
|
+
]);
|
|
640
937
|
React.useEffect(() => {
|
|
641
938
|
ensureVariants();
|
|
642
939
|
}, [ensureVariants]);
|
|
@@ -656,10 +953,12 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
656
953
|
}, [values.taxRateId, values.variants, setValue]);
|
|
657
954
|
const setVariantField = React.useCallback(
|
|
658
955
|
(variantId, field, value) => {
|
|
659
|
-
const next = (Array.isArray(values.variants) ? values.variants : []).map(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
956
|
+
const next = (Array.isArray(values.variants) ? values.variants : []).map(
|
|
957
|
+
(variant) => {
|
|
958
|
+
if (variant.id !== variantId) return variant;
|
|
959
|
+
return { ...variant, [field]: value };
|
|
960
|
+
}
|
|
961
|
+
);
|
|
663
962
|
setValue("variants", next);
|
|
664
963
|
},
|
|
665
964
|
[values.variants, setValue]
|
|
@@ -667,51 +966,72 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
667
966
|
const setVariantPrice = React.useCallback(
|
|
668
967
|
(variantId, priceKindId, amount) => {
|
|
669
968
|
if (amount.trim().startsWith("-")) return;
|
|
670
|
-
const next = (Array.isArray(values.variants) ? values.variants : []).map(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
969
|
+
const next = (Array.isArray(values.variants) ? values.variants : []).map(
|
|
970
|
+
(variant) => {
|
|
971
|
+
if (variant.id !== variantId) return variant;
|
|
972
|
+
const nextPrices = { ...variant.prices ?? {} };
|
|
973
|
+
if (amount === "") {
|
|
974
|
+
delete nextPrices[priceKindId];
|
|
975
|
+
} else {
|
|
976
|
+
nextPrices[priceKindId] = { amount };
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
...variant,
|
|
980
|
+
prices: nextPrices
|
|
981
|
+
};
|
|
677
982
|
}
|
|
678
|
-
|
|
983
|
+
);
|
|
984
|
+
setValue("variants", next);
|
|
985
|
+
},
|
|
986
|
+
[values.variants, setValue]
|
|
987
|
+
);
|
|
988
|
+
const markDefaultVariant = React.useCallback(
|
|
989
|
+
(variantId) => {
|
|
990
|
+
const next = (Array.isArray(values.variants) ? values.variants : []).map(
|
|
991
|
+
(variant) => ({
|
|
679
992
|
...variant,
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
|
|
993
|
+
isDefault: variant.id === variantId
|
|
994
|
+
})
|
|
995
|
+
);
|
|
683
996
|
setValue("variants", next);
|
|
684
997
|
},
|
|
685
998
|
[values.variants, setValue]
|
|
686
999
|
);
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1000
|
+
const handleOptionTitleChange = React.useCallback(
|
|
1001
|
+
(optionId, title) => {
|
|
1002
|
+
const next = (Array.isArray(values.options) ? values.options : []).map(
|
|
1003
|
+
(option) => {
|
|
1004
|
+
if (option.id !== optionId) return option;
|
|
1005
|
+
return { ...option, title };
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
setValue("options", next);
|
|
1009
|
+
},
|
|
1010
|
+
[values.options, setValue]
|
|
1011
|
+
);
|
|
1012
|
+
const setOptionValues = React.useCallback(
|
|
1013
|
+
(optionId, labels) => {
|
|
1014
|
+
const normalized = labels.map((label) => label.trim()).filter((label) => label.length);
|
|
1015
|
+
const unique = Array.from(new Set(normalized));
|
|
1016
|
+
const next = (Array.isArray(values.options) ? values.options : []).map(
|
|
1017
|
+
(option) => {
|
|
1018
|
+
if (option.id !== optionId) return option;
|
|
1019
|
+
const existingByLabel = new Map(
|
|
1020
|
+
option.values.map((value) => [value.label, value])
|
|
1021
|
+
);
|
|
1022
|
+
const nextValues = unique.map(
|
|
1023
|
+
(label) => existingByLabel.get(label) ?? { id: createLocalId(), label }
|
|
1024
|
+
);
|
|
1025
|
+
return {
|
|
1026
|
+
...option,
|
|
1027
|
+
values: nextValues
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
setValue("options", next);
|
|
1032
|
+
},
|
|
1033
|
+
[values.options, setValue]
|
|
1034
|
+
);
|
|
715
1035
|
const addOption = React.useCallback(() => {
|
|
716
1036
|
const next = [
|
|
717
1037
|
...Array.isArray(values.options) ? values.options : [],
|
|
@@ -719,10 +1039,15 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
719
1039
|
];
|
|
720
1040
|
setValue("options", next);
|
|
721
1041
|
}, [values.options, setValue]);
|
|
722
|
-
const removeOption = React.useCallback(
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
1042
|
+
const removeOption = React.useCallback(
|
|
1043
|
+
(optionId) => {
|
|
1044
|
+
const next = (Array.isArray(values.options) ? values.options : []).filter(
|
|
1045
|
+
(option) => option.id !== optionId
|
|
1046
|
+
);
|
|
1047
|
+
setValue("options", next);
|
|
1048
|
+
},
|
|
1049
|
+
[values.options, setValue]
|
|
1050
|
+
);
|
|
726
1051
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
727
1052
|
/* @__PURE__ */ jsx("nav", { className: "flex gap-6 border-b pb-2 text-sm font-medium", children: steps.map((step, index) => /* @__PURE__ */ jsxs(
|
|
728
1053
|
Button,
|
|
@@ -738,8 +1063,15 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
738
1063
|
children: [
|
|
739
1064
|
step === "general" && t("catalog.products.create.steps.general", "General data"),
|
|
740
1065
|
step === "organize" && t("catalog.products.create.steps.organize", "Organize"),
|
|
1066
|
+
step === "uom" && t("catalog.products.uom.title", "Units of measure"),
|
|
741
1067
|
step === "variants" && t("catalog.products.create.steps.variants", "Variants"),
|
|
742
|
-
(stepErrors[step]?.length ?? 0) > 0 ? /* @__PURE__ */ jsx(
|
|
1068
|
+
(stepErrors[step]?.length ?? 0) > 0 ? /* @__PURE__ */ jsx(
|
|
1069
|
+
"span",
|
|
1070
|
+
{
|
|
1071
|
+
className: "absolute -right-2 top-0 h-2 w-2 rounded-full bg-destructive",
|
|
1072
|
+
"aria-hidden": "true"
|
|
1073
|
+
}
|
|
1074
|
+
) : null,
|
|
743
1075
|
currentStep === index ? /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 -bottom-px h-0.5 bg-foreground rounded-full" }) : null
|
|
744
1076
|
]
|
|
745
1077
|
},
|
|
@@ -756,7 +1088,10 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
756
1088
|
{
|
|
757
1089
|
value: values.title,
|
|
758
1090
|
onChange: (event) => setValue("title", event.target.value),
|
|
759
|
-
placeholder: t(
|
|
1091
|
+
placeholder: t(
|
|
1092
|
+
"catalog.products.create.placeholders.title",
|
|
1093
|
+
"e.g., Summer sneaker"
|
|
1094
|
+
)
|
|
760
1095
|
}
|
|
761
1096
|
),
|
|
762
1097
|
errors.title ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.title }) : null
|
|
@@ -774,26 +1109,42 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
774
1109
|
className: "gap-2 text-xs",
|
|
775
1110
|
children: [
|
|
776
1111
|
values.useMarkdown ? /* @__PURE__ */ jsx(AlignLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }),
|
|
777
|
-
values.useMarkdown ? t(
|
|
1112
|
+
values.useMarkdown ? t(
|
|
1113
|
+
"catalog.products.create.actions.usePlain",
|
|
1114
|
+
"Use plain text"
|
|
1115
|
+
) : t(
|
|
1116
|
+
"catalog.products.create.actions.useMarkdown",
|
|
1117
|
+
"Use markdown"
|
|
1118
|
+
)
|
|
778
1119
|
]
|
|
779
1120
|
}
|
|
780
1121
|
)
|
|
781
1122
|
] }),
|
|
782
|
-
values.useMarkdown ? /* @__PURE__ */ jsx(
|
|
783
|
-
|
|
1123
|
+
values.useMarkdown ? /* @__PURE__ */ jsx(
|
|
1124
|
+
"div",
|
|
784
1125
|
{
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1126
|
+
"data-color-mode": "light",
|
|
1127
|
+
className: "overflow-hidden rounded-md border",
|
|
1128
|
+
children: /* @__PURE__ */ jsx(
|
|
1129
|
+
MarkdownEditor,
|
|
1130
|
+
{
|
|
1131
|
+
value: values.description,
|
|
1132
|
+
height: 260,
|
|
1133
|
+
onChange: (val) => setValue("description", val ?? ""),
|
|
1134
|
+
previewOptions: { remarkPlugins: [] }
|
|
1135
|
+
}
|
|
1136
|
+
)
|
|
789
1137
|
}
|
|
790
|
-
)
|
|
1138
|
+
) : /* @__PURE__ */ jsx(
|
|
791
1139
|
"textarea",
|
|
792
1140
|
{
|
|
793
1141
|
className: "min-h-[180px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
794
1142
|
value: values.description,
|
|
795
1143
|
onChange: (event) => setValue("description", event.target.value),
|
|
796
|
-
placeholder: t(
|
|
1144
|
+
placeholder: t(
|
|
1145
|
+
"catalog.products.create.placeholders.description",
|
|
1146
|
+
"Describe the product..."
|
|
1147
|
+
)
|
|
797
1148
|
}
|
|
798
1149
|
)
|
|
799
1150
|
] }),
|
|
@@ -808,7 +1159,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
808
1159
|
onDefaultChange: handleDefaultMediaChange
|
|
809
1160
|
}
|
|
810
1161
|
),
|
|
811
|
-
/* @__PURE__ */ jsx(
|
|
1162
|
+
/* @__PURE__ */ jsx(
|
|
1163
|
+
ProductDimensionsFields,
|
|
1164
|
+
{
|
|
1165
|
+
values,
|
|
1166
|
+
setValue
|
|
1167
|
+
}
|
|
1168
|
+
)
|
|
812
1169
|
] }) : null,
|
|
813
1170
|
currentStepKey === "organize" ? /* @__PURE__ */ jsx(
|
|
814
1171
|
ProductCategorizeSection,
|
|
@@ -818,6 +1175,15 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
818
1175
|
errors
|
|
819
1176
|
}
|
|
820
1177
|
) : null,
|
|
1178
|
+
currentStepKey === "uom" ? /* @__PURE__ */ jsx(
|
|
1179
|
+
ProductUomSection,
|
|
1180
|
+
{
|
|
1181
|
+
values,
|
|
1182
|
+
setValue,
|
|
1183
|
+
errors,
|
|
1184
|
+
embedded: true
|
|
1185
|
+
}
|
|
1186
|
+
) : null,
|
|
821
1187
|
currentStepKey === "variants" ? /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
822
1188
|
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [
|
|
823
1189
|
/* @__PURE__ */ jsx(
|
|
@@ -829,69 +1195,141 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
829
1195
|
onChange: (event) => setValue("hasVariants", event.target.checked)
|
|
830
1196
|
}
|
|
831
1197
|
),
|
|
832
|
-
t(
|
|
1198
|
+
t(
|
|
1199
|
+
"catalog.products.create.variantsBuilder.toggle",
|
|
1200
|
+
"Yes, this is a product with variants"
|
|
1201
|
+
)
|
|
833
1202
|
] }),
|
|
834
1203
|
values.hasVariants ? /* @__PURE__ */ jsxs("div", { className: "space-y-4 rounded-lg border p-4", children: [
|
|
835
1204
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
836
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1205
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t(
|
|
1206
|
+
"catalog.products.create.optionsBuilder.title",
|
|
1207
|
+
"Product options"
|
|
1208
|
+
) }),
|
|
1209
|
+
/* @__PURE__ */ jsxs(
|
|
1210
|
+
Button,
|
|
1211
|
+
{
|
|
1212
|
+
type: "button",
|
|
1213
|
+
variant: "outline",
|
|
1214
|
+
size: "sm",
|
|
1215
|
+
onClick: addOption,
|
|
1216
|
+
children: [
|
|
1217
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
|
|
1218
|
+
t(
|
|
1219
|
+
"catalog.products.create.optionsBuilder.add",
|
|
1220
|
+
"Add option"
|
|
1221
|
+
)
|
|
1222
|
+
]
|
|
1223
|
+
}
|
|
1224
|
+
)
|
|
841
1225
|
] }),
|
|
842
|
-
(Array.isArray(values.options) ? values.options : []).map(
|
|
843
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
844
|
-
/* @__PURE__ */
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1226
|
+
(Array.isArray(values.options) ? values.options : []).map(
|
|
1227
|
+
(option) => /* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted/40 p-4", children: [
|
|
1228
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1229
|
+
/* @__PURE__ */ jsx(
|
|
1230
|
+
Input,
|
|
1231
|
+
{
|
|
1232
|
+
value: option.title,
|
|
1233
|
+
onChange: (event) => handleOptionTitleChange(option.id, event.target.value),
|
|
1234
|
+
placeholder: t(
|
|
1235
|
+
"catalog.products.create.optionsBuilder.placeholder",
|
|
1236
|
+
"e.g., Color"
|
|
1237
|
+
),
|
|
1238
|
+
className: "flex-1"
|
|
1239
|
+
}
|
|
1240
|
+
),
|
|
1241
|
+
/* @__PURE__ */ jsx(
|
|
1242
|
+
Button,
|
|
1243
|
+
{
|
|
1244
|
+
variant: "ghost",
|
|
1245
|
+
size: "icon",
|
|
1246
|
+
type: "button",
|
|
1247
|
+
onClick: () => removeOption(option.id),
|
|
1248
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
1249
|
+
}
|
|
1250
|
+
)
|
|
1251
|
+
] }),
|
|
1252
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-2", children: [
|
|
1253
|
+
/* @__PURE__ */ jsx(Label, { className: "text-xs uppercase text-muted-foreground", children: t(
|
|
1254
|
+
"catalog.products.create.optionsBuilder.values",
|
|
1255
|
+
"Values"
|
|
1256
|
+
) }),
|
|
1257
|
+
/* @__PURE__ */ jsx(
|
|
1258
|
+
TagsInput,
|
|
1259
|
+
{
|
|
1260
|
+
value: option.values.map((value) => value.label),
|
|
1261
|
+
onChange: (labels) => setOptionValues(option.id, labels),
|
|
1262
|
+
placeholder: t(
|
|
1263
|
+
"catalog.products.create.optionsBuilder.valuePlaceholder",
|
|
1264
|
+
"Type a value and press Enter"
|
|
1265
|
+
)
|
|
1266
|
+
}
|
|
1267
|
+
)
|
|
1268
|
+
] })
|
|
1269
|
+
] }, option.id)
|
|
1270
|
+
),
|
|
1271
|
+
!values.options?.length ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
|
|
1272
|
+
"catalog.products.create.optionsBuilder.empty",
|
|
1273
|
+
"No options yet. Add your first option to generate variants."
|
|
1274
|
+
) }) : null
|
|
868
1275
|
] }) : null,
|
|
869
1276
|
/* @__PURE__ */ jsxs("div", { className: "rounded-lg border", children: [
|
|
870
1277
|
/* @__PURE__ */ jsx("div", { className: "w-full overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-[900px] table-fixed border-collapse text-sm", children: [
|
|
871
1278
|
/* @__PURE__ */ jsx("thead", { className: "bg-muted/40", children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
872
|
-
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t(
|
|
1279
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t(
|
|
1280
|
+
"catalog.products.create.variantsBuilder.defaultOption",
|
|
1281
|
+
"Default option"
|
|
1282
|
+
) }),
|
|
873
1283
|
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t("catalog.products.form.variants", "Variant title") }),
|
|
874
1284
|
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t("catalog.products.create.variantsBuilder.sku", "SKU") }),
|
|
875
|
-
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t(
|
|
1285
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t(
|
|
1286
|
+
"catalog.products.create.variantsBuilder.vatColumn",
|
|
1287
|
+
"Tax class"
|
|
1288
|
+
) }),
|
|
876
1289
|
priceKinds.map((kind) => /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
877
1290
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
878
|
-
/* @__PURE__ */ jsx("span", { children: t(
|
|
1291
|
+
/* @__PURE__ */ jsx("span", { children: t(
|
|
1292
|
+
"catalog.products.create.variantsBuilder.priceColumn",
|
|
1293
|
+
"Price {{title}}"
|
|
1294
|
+
).replace("{{title}}", kind.title) }),
|
|
879
1295
|
/* @__PURE__ */ jsx(
|
|
880
1296
|
"small",
|
|
881
1297
|
{
|
|
882
|
-
title: kind.displayMode === "including-tax" ? t(
|
|
1298
|
+
title: kind.displayMode === "including-tax" ? t(
|
|
1299
|
+
"catalog.priceKinds.form.displayMode.include",
|
|
1300
|
+
"Including tax"
|
|
1301
|
+
) : t(
|
|
1302
|
+
"catalog.priceKinds.form.displayMode.exclude",
|
|
1303
|
+
"Excluding tax"
|
|
1304
|
+
),
|
|
883
1305
|
className: "text-xs text-muted-foreground",
|
|
884
1306
|
children: kind.displayMode === "including-tax" ? "\u24C9" : "\u24C3"
|
|
885
1307
|
}
|
|
886
1308
|
)
|
|
887
1309
|
] }),
|
|
888
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: kind.currencyCode?.toUpperCase() ?? t(
|
|
1310
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: kind.currencyCode?.toUpperCase() ?? t(
|
|
1311
|
+
"catalog.products.create.variantsBuilder.currencyMissing",
|
|
1312
|
+
"Currency missing"
|
|
1313
|
+
) })
|
|
889
1314
|
] }) }, kind.id)),
|
|
890
|
-
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t(
|
|
891
|
-
|
|
892
|
-
|
|
1315
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t(
|
|
1316
|
+
"catalog.products.create.variantsBuilder.manageInventory",
|
|
1317
|
+
"Managed inventory"
|
|
1318
|
+
) }),
|
|
1319
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t(
|
|
1320
|
+
"catalog.products.create.variantsBuilder.allowBackorder",
|
|
1321
|
+
"Allow backorder"
|
|
1322
|
+
) }),
|
|
1323
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t(
|
|
1324
|
+
"catalog.products.create.variantsBuilder.inventoryKit",
|
|
1325
|
+
"Has inventory kit"
|
|
1326
|
+
) })
|
|
893
1327
|
] }) }),
|
|
894
|
-
/* @__PURE__ */ jsx("tbody", { children: (Array.isArray(values.variants) && values.variants.length ? values.variants : [
|
|
1328
|
+
/* @__PURE__ */ jsx("tbody", { children: (Array.isArray(values.variants) && values.variants.length ? values.variants : [
|
|
1329
|
+
createVariantDraft(values.taxRateId ?? null, {
|
|
1330
|
+
isDefault: true
|
|
1331
|
+
})
|
|
1332
|
+
]).map((variant) => /* @__PURE__ */ jsxs("tr", { className: "border-t", children: [
|
|
895
1333
|
/* @__PURE__ */ jsxs("td", { className: "px-3 py-2", children: [
|
|
896
1334
|
/* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-1 text-xs", children: [
|
|
897
1335
|
/* @__PURE__ */ jsx(
|
|
@@ -903,7 +1341,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
903
1341
|
onChange: () => markDefaultVariant(variant.id)
|
|
904
1342
|
}
|
|
905
1343
|
),
|
|
906
|
-
variant.isDefault ? t(
|
|
1344
|
+
variant.isDefault ? t(
|
|
1345
|
+
"catalog.products.create.variantsBuilder.defaultLabel",
|
|
1346
|
+
"Default option value"
|
|
1347
|
+
) : t(
|
|
1348
|
+
"catalog.products.create.variantsBuilder.makeDefault",
|
|
1349
|
+
"Set as default"
|
|
1350
|
+
)
|
|
907
1351
|
] }),
|
|
908
1352
|
values.hasVariants && variant.optionValues ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: Object.values(variant.optionValues).join(" / ") }) : null
|
|
909
1353
|
] }),
|
|
@@ -911,16 +1355,30 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
911
1355
|
Input,
|
|
912
1356
|
{
|
|
913
1357
|
value: variant.title,
|
|
914
|
-
onChange: (event) => setVariantField(
|
|
915
|
-
|
|
1358
|
+
onChange: (event) => setVariantField(
|
|
1359
|
+
variant.id,
|
|
1360
|
+
"title",
|
|
1361
|
+
event.target.value
|
|
1362
|
+
),
|
|
1363
|
+
placeholder: t(
|
|
1364
|
+
"catalog.products.create.variantsBuilder.titlePlaceholder",
|
|
1365
|
+
"Variant title"
|
|
1366
|
+
)
|
|
916
1367
|
}
|
|
917
1368
|
) }),
|
|
918
1369
|
/* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx(
|
|
919
1370
|
Input,
|
|
920
1371
|
{
|
|
921
1372
|
value: variant.sku,
|
|
922
|
-
onChange: (event) => setVariantField(
|
|
923
|
-
|
|
1373
|
+
onChange: (event) => setVariantField(
|
|
1374
|
+
variant.id,
|
|
1375
|
+
"sku",
|
|
1376
|
+
event.target.value
|
|
1377
|
+
),
|
|
1378
|
+
placeholder: t(
|
|
1379
|
+
"catalog.products.create.variantsBuilder.skuPlaceholder",
|
|
1380
|
+
"e.g., SKU-001"
|
|
1381
|
+
)
|
|
924
1382
|
}
|
|
925
1383
|
) }),
|
|
926
1384
|
/* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxs(
|
|
@@ -928,10 +1386,20 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
928
1386
|
{
|
|
929
1387
|
className: "flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
930
1388
|
value: variant.taxRateId ?? "",
|
|
931
|
-
onChange: (event) => setVariantField(
|
|
1389
|
+
onChange: (event) => setVariantField(
|
|
1390
|
+
variant.id,
|
|
1391
|
+
"taxRateId",
|
|
1392
|
+
event.target.value || null
|
|
1393
|
+
),
|
|
932
1394
|
disabled: !taxRates.length,
|
|
933
1395
|
children: [
|
|
934
|
-
/* @__PURE__ */ jsx("option", { value: "", children: defaultTaxRateLabel ? t(
|
|
1396
|
+
/* @__PURE__ */ jsx("option", { value: "", children: defaultTaxRateLabel ? t(
|
|
1397
|
+
"catalog.products.create.variantsBuilder.vatOptionDefault",
|
|
1398
|
+
"Use product tax class ({{label}})"
|
|
1399
|
+
).replace("{{label}}", defaultTaxRateLabel) : t(
|
|
1400
|
+
"catalog.products.create.variantsBuilder.vatOptionNone",
|
|
1401
|
+
"No tax class"
|
|
1402
|
+
) }),
|
|
935
1403
|
taxRates.map((rate) => /* @__PURE__ */ jsx("option", { value: rate.id, children: formatTaxRateLabel(rate) }, rate.id))
|
|
936
1404
|
]
|
|
937
1405
|
}
|
|
@@ -944,7 +1412,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
944
1412
|
type: "number",
|
|
945
1413
|
className: "w-full rounded-md border px-2 py-1",
|
|
946
1414
|
value: variant.prices?.[kind.id]?.amount ?? "",
|
|
947
|
-
onChange: (event) => setVariantPrice(
|
|
1415
|
+
onChange: (event) => setVariantPrice(
|
|
1416
|
+
variant.id,
|
|
1417
|
+
kind.id,
|
|
1418
|
+
event.target.value
|
|
1419
|
+
),
|
|
948
1420
|
placeholder: "0.00",
|
|
949
1421
|
min: 0
|
|
950
1422
|
}
|
|
@@ -956,7 +1428,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
956
1428
|
type: "checkbox",
|
|
957
1429
|
className: "h-4 w-4 rounded border disabled:cursor-not-allowed disabled:opacity-60",
|
|
958
1430
|
checked: variant.manageInventory,
|
|
959
|
-
onChange: (event) => setVariantField(
|
|
1431
|
+
onChange: (event) => setVariantField(
|
|
1432
|
+
variant.id,
|
|
1433
|
+
"manageInventory",
|
|
1434
|
+
event.target.checked
|
|
1435
|
+
),
|
|
960
1436
|
disabled: true,
|
|
961
1437
|
title: inventoryDisabledHint
|
|
962
1438
|
}
|
|
@@ -967,7 +1443,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
967
1443
|
type: "checkbox",
|
|
968
1444
|
className: "h-4 w-4 rounded border disabled:cursor-not-allowed disabled:opacity-60",
|
|
969
1445
|
checked: variant.allowBackorder,
|
|
970
|
-
onChange: (event) => setVariantField(
|
|
1446
|
+
onChange: (event) => setVariantField(
|
|
1447
|
+
variant.id,
|
|
1448
|
+
"allowBackorder",
|
|
1449
|
+
event.target.checked
|
|
1450
|
+
),
|
|
971
1451
|
disabled: true,
|
|
972
1452
|
title: inventoryDisabledHint
|
|
973
1453
|
}
|
|
@@ -978,7 +1458,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
978
1458
|
type: "checkbox",
|
|
979
1459
|
className: "h-4 w-4 rounded border disabled:cursor-not-allowed disabled:opacity-60",
|
|
980
1460
|
checked: variant.hasInventoryKit,
|
|
981
|
-
onChange: (event) => setVariantField(
|
|
1461
|
+
onChange: (event) => setVariantField(
|
|
1462
|
+
variant.id,
|
|
1463
|
+
"hasInventoryKit",
|
|
1464
|
+
event.target.checked
|
|
1465
|
+
),
|
|
982
1466
|
disabled: true,
|
|
983
1467
|
title: inventoryDisabledHint
|
|
984
1468
|
}
|
|
@@ -987,7 +1471,10 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
987
1471
|
] }) }),
|
|
988
1472
|
!priceKinds.length ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t px-4 py-3 text-sm text-muted-foreground", children: [
|
|
989
1473
|
/* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
|
|
990
|
-
t(
|
|
1474
|
+
t(
|
|
1475
|
+
"catalog.products.create.variantsBuilder.noPriceKinds",
|
|
1476
|
+
"Configure price kinds in Catalog settings to add price columns."
|
|
1477
|
+
)
|
|
991
1478
|
] }) : null
|
|
992
1479
|
] })
|
|
993
1480
|
] }) : null,
|
|
@@ -1024,7 +1511,12 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
|
|
|
1024
1511
|
] })
|
|
1025
1512
|
] });
|
|
1026
1513
|
}
|
|
1027
|
-
function ProductMetaSection({
|
|
1514
|
+
function ProductMetaSection({
|
|
1515
|
+
values,
|
|
1516
|
+
setValue,
|
|
1517
|
+
errors,
|
|
1518
|
+
taxRates
|
|
1519
|
+
}) {
|
|
1028
1520
|
const t = useT();
|
|
1029
1521
|
const handleValue = typeof values.handle === "string" ? values.handle : "";
|
|
1030
1522
|
const titleSource = typeof values.title === "string" ? values.title : "";
|
|
@@ -1064,7 +1556,10 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
|
|
|
1064
1556
|
{
|
|
1065
1557
|
value: typeof values.subtitle === "string" ? values.subtitle : "",
|
|
1066
1558
|
onChange: (event) => setValue("subtitle", event.target.value),
|
|
1067
|
-
placeholder: t(
|
|
1559
|
+
placeholder: t(
|
|
1560
|
+
"catalog.products.create.placeholders.subtitle",
|
|
1561
|
+
"Optional subtitle"
|
|
1562
|
+
)
|
|
1068
1563
|
}
|
|
1069
1564
|
),
|
|
1070
1565
|
errors.subtitle ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.subtitle }) : null
|
|
@@ -1077,7 +1572,10 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
|
|
|
1077
1572
|
{
|
|
1078
1573
|
value: handleValue,
|
|
1079
1574
|
onChange: handleHandleInputChange,
|
|
1080
|
-
placeholder: t(
|
|
1575
|
+
placeholder: t(
|
|
1576
|
+
"catalog.products.create.placeholders.handle",
|
|
1577
|
+
"e.g., summer-sneaker"
|
|
1578
|
+
),
|
|
1081
1579
|
className: "font-mono lowercase"
|
|
1082
1580
|
}
|
|
1083
1581
|
),
|
|
@@ -1091,7 +1589,10 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
|
|
|
1091
1589
|
}
|
|
1092
1590
|
)
|
|
1093
1591
|
] }),
|
|
1094
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
|
|
1592
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
|
|
1593
|
+
"catalog.products.create.handleHelp",
|
|
1594
|
+
"Handle is used for URLs and must be unique."
|
|
1595
|
+
) }),
|
|
1095
1596
|
errors.handle ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.handle }) : null
|
|
1096
1597
|
] }),
|
|
1097
1598
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
@@ -1105,14 +1606,24 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
|
|
|
1105
1606
|
size: "icon",
|
|
1106
1607
|
onClick: () => {
|
|
1107
1608
|
if (typeof window !== "undefined") {
|
|
1108
|
-
window.open(
|
|
1609
|
+
window.open(
|
|
1610
|
+
"/backend/config/sales?section=tax-rates",
|
|
1611
|
+
"_blank",
|
|
1612
|
+
"noopener,noreferrer"
|
|
1613
|
+
);
|
|
1109
1614
|
}
|
|
1110
1615
|
},
|
|
1111
|
-
title: t(
|
|
1616
|
+
title: t(
|
|
1617
|
+
"catalog.products.create.taxRates.manage",
|
|
1618
|
+
"Manage tax classes"
|
|
1619
|
+
),
|
|
1112
1620
|
className: "text-muted-foreground hover:text-foreground",
|
|
1113
1621
|
children: [
|
|
1114
1622
|
/* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }),
|
|
1115
|
-
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t(
|
|
1623
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: t(
|
|
1624
|
+
"catalog.products.create.taxRates.manage",
|
|
1625
|
+
"Manage tax classes"
|
|
1626
|
+
) })
|
|
1116
1627
|
]
|
|
1117
1628
|
}
|
|
1118
1629
|
)
|
|
@@ -1125,12 +1636,24 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
|
|
|
1125
1636
|
onChange: (event) => setValue("taxRateId", event.target.value || null),
|
|
1126
1637
|
disabled: !taxRates.length,
|
|
1127
1638
|
children: [
|
|
1128
|
-
/* @__PURE__ */ jsx("option", { value: "", children: taxRates.length ? t(
|
|
1639
|
+
/* @__PURE__ */ jsx("option", { value: "", children: taxRates.length ? t(
|
|
1640
|
+
"catalog.products.create.taxRates.noneSelected",
|
|
1641
|
+
"No tax class selected"
|
|
1642
|
+
) : t(
|
|
1643
|
+
"catalog.products.create.taxRates.emptyOption",
|
|
1644
|
+
"No tax classes available"
|
|
1645
|
+
) }),
|
|
1129
1646
|
taxRates.map((rate) => /* @__PURE__ */ jsx("option", { value: rate.id, children: formatTaxRateLabel(rate) }, rate.id))
|
|
1130
1647
|
]
|
|
1131
1648
|
}
|
|
1132
1649
|
),
|
|
1133
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: taxRates.length ? t(
|
|
1650
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: taxRates.length ? t(
|
|
1651
|
+
"catalog.products.create.taxRates.help",
|
|
1652
|
+
"Applied to new prices unless overridden per variant."
|
|
1653
|
+
) : t(
|
|
1654
|
+
"catalog.products.create.taxRates.empty",
|
|
1655
|
+
"Define tax classes under Sales \u2192 Configuration."
|
|
1656
|
+
) }),
|
|
1134
1657
|
errors.taxRateId ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.taxRateId }) : null
|
|
1135
1658
|
] })
|
|
1136
1659
|
] });
|