wip
This commit is contained in:
parent
688fc83f55
commit
64371d04a6
14 changed files with 354 additions and 155 deletions
60
package-lock.json
generated
60
package-lock.json
generated
|
@ -16,56 +16,56 @@
|
|||
}
|
||||
},
|
||||
"@apollo/react-common": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.3.tgz",
|
||||
"integrity": "sha512-Q7ZjDOeqjJf/AOGxUMdGxKF+JVClRXrYBGVq+SuVFqANRpd68MxtVV2OjCWavsFAN0eqYnRqRUrl7vtUCiJqeg==",
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.4.tgz",
|
||||
"integrity": "sha512-X5Kyro73bthWSCBJUC5XYQqMnG0dLWuDZmVkzog9dynovhfiVCV4kPSdgSIkqnb++cwCzOVuQ4rDKVwo2XRzQA==",
|
||||
"requires": {
|
||||
"ts-invariant": "^0.4.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@apollo/react-components": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-components/-/react-components-3.1.3.tgz",
|
||||
"integrity": "sha512-H0l2JKDQMz+LkM93QK7j3ThbNXkWQCduN3s3eKxFN3Rdg7rXsrikJWvx2wQ868jmqy0VhwJbS1vYdRLdh114uQ==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-components/-/react-components-3.1.5.tgz",
|
||||
"integrity": "sha512-c82VyUuE9VBnJB7bnX+3dmwpIPMhyjMwyoSLyQWPHxz8jK4ak30XszJtqFf4eC4hwvvLYa+Ou6X73Q8V8e2/jg==",
|
||||
"requires": {
|
||||
"@apollo/react-common": "^3.1.3",
|
||||
"@apollo/react-hooks": "^3.1.3",
|
||||
"@apollo/react-common": "^3.1.4",
|
||||
"@apollo/react-hooks": "^3.1.5",
|
||||
"prop-types": "^15.7.2",
|
||||
"ts-invariant": "^0.4.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@apollo/react-hoc": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-hoc/-/react-hoc-3.1.3.tgz",
|
||||
"integrity": "sha512-oCPma0uBVPTcYTR5sOvtMbpaWll4xDBvYfKr6YkDorUcQVeNzFu1LK1kmQjJP64bKsaziKYji5ibFaeCnVptmA==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-hoc/-/react-hoc-3.1.5.tgz",
|
||||
"integrity": "sha512-jlZ2pvEnRevLa54H563BU0/xrYSgWQ72GksarxUzCHQW85nmn9wQln0kLBX7Ua7SBt9WgiuYQXQVechaaCulfQ==",
|
||||
"requires": {
|
||||
"@apollo/react-common": "^3.1.3",
|
||||
"@apollo/react-components": "^3.1.3",
|
||||
"@apollo/react-common": "^3.1.4",
|
||||
"@apollo/react-components": "^3.1.5",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"ts-invariant": "^0.4.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@apollo/react-hooks": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.1.3.tgz",
|
||||
"integrity": "sha512-reIRO9xKdfi+B4gT/o/hnXuopUnm7WED/ru8VQydPw+C/KG/05Ssg1ZdxFKHa3oxwiTUIDnevtccIH35POanbA==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.1.5.tgz",
|
||||
"integrity": "sha512-y0CJ393DLxIIkksRup4nt+vSjxalbZBXnnXxYbviq/woj+zKa431zy0yT4LqyRKpFy9ahMIwxBnBwfwIoupqLQ==",
|
||||
"requires": {
|
||||
"@apollo/react-common": "^3.1.3",
|
||||
"@apollo/react-common": "^3.1.4",
|
||||
"@wry/equality": "^0.1.9",
|
||||
"ts-invariant": "^0.4.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"@apollo/react-ssr": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-ssr/-/react-ssr-3.1.3.tgz",
|
||||
"integrity": "sha512-fUTmEYHxSTX1GA43B8vICxXXplpcEBnDwn0IgdAc3eG0p2YK97ZrJDRFCJ5vD7fyDZsrYhMf+rAI3sd+H2SS+A==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/react-ssr/-/react-ssr-3.1.5.tgz",
|
||||
"integrity": "sha512-wuLPkKlctNn3u8EU8rlECyktpOUCeekFfb0KhIKknpGY6Lza2Qu0bThx7D9MIbVEzhKadNNrzLcpk0Y8/5UuWg==",
|
||||
"requires": {
|
||||
"@apollo/react-common": "^3.1.3",
|
||||
"@apollo/react-hooks": "^3.1.3",
|
||||
"@apollo/react-common": "^3.1.4",
|
||||
"@apollo/react-hooks": "^3.1.5",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
},
|
||||
|
@ -17593,15 +17593,15 @@
|
|||
}
|
||||
},
|
||||
"react-apollo": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-3.1.3.tgz",
|
||||
"integrity": "sha512-orCZNoAkgveaK5b75y7fw1MSqSHOU/Wuu9rRFOGmRQBSQVZjvV4DI+hj604rHmuN9+WDABxb5W48wTa0F/xNZQ==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-3.1.5.tgz",
|
||||
"integrity": "sha512-xOxMqxORps+WHrUYbjVHPliviomefOpu5Sh35oO3osuOyPTxvrljdfTLGCggMhcXBsDljtS5Oy4g+ijWg3D4JQ==",
|
||||
"requires": {
|
||||
"@apollo/react-common": "^3.1.3",
|
||||
"@apollo/react-components": "^3.1.3",
|
||||
"@apollo/react-hoc": "^3.1.3",
|
||||
"@apollo/react-hooks": "^3.1.3",
|
||||
"@apollo/react-ssr": "^3.1.3"
|
||||
"@apollo/react-common": "^3.1.4",
|
||||
"@apollo/react-components": "^3.1.5",
|
||||
"@apollo/react-hoc": "^3.1.5",
|
||||
"@apollo/react-hooks": "^3.1.5",
|
||||
"@apollo/react-ssr": "^3.1.5"
|
||||
}
|
||||
},
|
||||
"react-clientside-effect": {
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
"moment-timezone": "^0.5.26",
|
||||
"qs": "^6.9.0",
|
||||
"react": "^16.12.0",
|
||||
"react-apollo": "^3.1.3",
|
||||
"react-apollo": "^3.1.4",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-dropzone": "^8.2.0",
|
||||
"react-error-boundary": "^1.2.5",
|
||||
|
|
|
@ -19,8 +19,8 @@ const selectedAttributes = [1, 4, 5].map(index => attributes[index]);
|
|||
const selectedWarehouses = [0, 1, 3].map(index => warehouseList[index]);
|
||||
|
||||
const price: AllOrAttribute<string> = {
|
||||
all: false,
|
||||
attribute: selectedAttributes[0].id,
|
||||
mode: "attribute",
|
||||
value: "2.79",
|
||||
values: selectedAttributes[0].values.map((attribute, attributeIndex) => ({
|
||||
slug: attribute.slug,
|
||||
|
@ -29,8 +29,8 @@ const price: AllOrAttribute<string> = {
|
|||
};
|
||||
|
||||
const stock: AllOrAttribute<number[]> = {
|
||||
all: false,
|
||||
attribute: selectedAttributes[0].id,
|
||||
mode: "attribute",
|
||||
value: selectedWarehouses.map(
|
||||
(_, warehouseIndex) => (warehouseIndex + 2) * 3
|
||||
),
|
||||
|
@ -88,8 +88,28 @@ const props: ProductVariantCreatorContentProps = {
|
|||
storiesOf("Views / Products / Create multiple variants", module)
|
||||
.addDecorator(storyFn => <Container>{storyFn()}</Container>)
|
||||
.addDecorator(Decorator)
|
||||
.add("choose values", () => <ProductVariantCreatorContent {...props} />)
|
||||
.add("prices and SKU", () => (
|
||||
.add("choose values", () => <ProductVariantCreatorContent {...props} />);
|
||||
|
||||
storiesOf(
|
||||
"Views / Products / Create multiple variants / prices and SKUs",
|
||||
module
|
||||
)
|
||||
.addDecorator(storyFn => <Container>{storyFn()}</Container>)
|
||||
.addDecorator(Decorator)
|
||||
.add("apply to all", () => (
|
||||
<ProductVariantCreatorContent
|
||||
{...props}
|
||||
data={{
|
||||
...data,
|
||||
stock: {
|
||||
...data.stock,
|
||||
mode: "all"
|
||||
}
|
||||
}}
|
||||
step={ProductVariantCreatorStep.prices}
|
||||
/>
|
||||
))
|
||||
.add("apply to attribute", () => (
|
||||
<ProductVariantCreatorContent
|
||||
{...props}
|
||||
step={ProductVariantCreatorStep.prices}
|
||||
|
|
|
@ -2,8 +2,8 @@ import React from "react";
|
|||
|
||||
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||
import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
import { ProductVariantCreateFormData } from "./form";
|
||||
import ProductVariantCreatePriceAndSku from "./ProductVariantCreatorPriceAndSku";
|
||||
import ProductVariantCreateSummary from "./ProductVariantCreatorSummary";
|
||||
|
@ -64,10 +64,11 @@ const ProductVariantCreatorContent: React.FC<ProductVariantCreatorContentProps>
|
|||
attributes={selectedAttributes}
|
||||
currencySymbol={currencySymbol}
|
||||
data={data}
|
||||
onApplyToAllChange={(all, type) =>
|
||||
warehouses={warehouses}
|
||||
onApplyToAllChange={(mode, type) =>
|
||||
dispatchFormDataAction({
|
||||
applyPriceOrStockToAll: {
|
||||
all
|
||||
mode
|
||||
},
|
||||
type:
|
||||
type === "price"
|
||||
|
@ -109,6 +110,14 @@ const ProductVariantCreatorContent: React.FC<ProductVariantCreatorContentProps>
|
|||
}
|
||||
)
|
||||
}
|
||||
onWarehouseToggle={warehouseId =>
|
||||
dispatchFormDataAction({
|
||||
changeWarehouses: {
|
||||
warehouseId
|
||||
},
|
||||
type: ProductVariantCreateReducerActionType.changeWarehouses
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{step === ProductVariantCreatorStep.summary && (
|
||||
|
|
|
@ -39,20 +39,22 @@ function canHitNext(
|
|||
case ProductVariantCreatorStep.values:
|
||||
return data.attributes.every(attribute => attribute.values.length > 0);
|
||||
case ProductVariantCreatorStep.prices:
|
||||
if (data.price.all) {
|
||||
if (data.price.mode === "all") {
|
||||
if (data.price.value === "") {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
} else if (data.price.mode === "attribute") {
|
||||
if (
|
||||
data.price.attribute === "" ||
|
||||
data.price.values.some(attributeValue => attributeValue.value === "")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!data.stock.all || data.stock.attribute) {
|
||||
if (data.stock.mode === "attribute" || data.stock.attribute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@ import React from "react";
|
|||
|
||||
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import { ProductVariantCreateFormData } from "./form";
|
||||
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
|
||||
import {
|
||||
ProductVariantCreateFormData,
|
||||
VariantCreatorPricesAndSkuMode
|
||||
} from "./form";
|
||||
import ProductVariantCreatorPrices from "./ProductVariantCreatorPrices";
|
||||
import ProductVariantCreatorStock from "./ProductVariantCreatorStock";
|
||||
|
||||
|
@ -11,7 +15,11 @@ export interface ProductVariantCreatorPriceAndSkuProps {
|
|||
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||
currencySymbol: string;
|
||||
data: ProductVariantCreateFormData;
|
||||
onApplyToAllChange: (applyToAll: boolean, type: PriceOrStock) => void;
|
||||
warehouses: WarehouseFragment[];
|
||||
onApplyToAllChange: (
|
||||
value: VariantCreatorPricesAndSkuMode,
|
||||
type: PriceOrStock
|
||||
) => void;
|
||||
onApplyToAllPriceOrStockChange: (value: string, type: PriceOrStock) => void;
|
||||
onAttributeSelect: (id: string, type: PriceOrStock) => void;
|
||||
onAttributeValueChange: (
|
||||
|
@ -19,16 +27,19 @@ export interface ProductVariantCreatorPriceAndSkuProps {
|
|||
value: string,
|
||||
type: PriceOrStock
|
||||
) => void;
|
||||
onWarehouseToggle: (id: string) => void;
|
||||
}
|
||||
|
||||
const ProductVariantCreatorPriceAndSku: React.FC<ProductVariantCreatorPriceAndSkuProps> = ({
|
||||
attributes,
|
||||
currencySymbol,
|
||||
data,
|
||||
warehouses,
|
||||
onApplyToAllPriceOrStockChange,
|
||||
onApplyToAllChange,
|
||||
onAttributeSelect,
|
||||
onAttributeValueChange
|
||||
onAttributeValueChange,
|
||||
onWarehouseToggle
|
||||
}) => (
|
||||
<>
|
||||
<ProductVariantCreatorPrices
|
||||
|
@ -48,6 +59,7 @@ const ProductVariantCreatorPriceAndSku: React.FC<ProductVariantCreatorPriceAndSk
|
|||
<ProductVariantCreatorStock
|
||||
attributes={attributes}
|
||||
data={data}
|
||||
warehouses={warehouses}
|
||||
onApplyToAllChange={value => onApplyToAllChange(value, "stock")}
|
||||
onApplyToAllStockChange={value =>
|
||||
onApplyToAllPriceOrStockChange(value, "stock")
|
||||
|
@ -56,6 +68,7 @@ const ProductVariantCreatorPriceAndSku: React.FC<ProductVariantCreatorPriceAndSk
|
|||
onAttributeValueChange={(id, value) =>
|
||||
onAttributeValueChange(id, value, "stock")
|
||||
}
|
||||
onWarehouseToggle={onWarehouseToggle}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,10 @@ import Hr from "@saleor/components/Hr";
|
|||
import SingleSelectField from "@saleor/components/SingleSelectField";
|
||||
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { ProductVariantCreateFormData } from "./form";
|
||||
import {
|
||||
ProductVariantCreateFormData,
|
||||
VariantCreatorPricesAndSkuMode
|
||||
} from "./form";
|
||||
import { getPriceAttributeValues } from "./utils";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -41,7 +44,7 @@ export interface ProductVariantCreatorPricesProps {
|
|||
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||
currencySymbol: string;
|
||||
data: ProductVariantCreateFormData;
|
||||
onApplyToAllChange: (applyToAll: boolean) => void;
|
||||
onApplyToAllChange: (applyToAll: VariantCreatorPricesAndSkuMode) => void;
|
||||
onApplyToAllPriceChange: (value: string) => void;
|
||||
onAttributeSelect: (id: string) => void;
|
||||
onAttributeValueChange: (id: string, value: string) => void;
|
||||
|
@ -75,14 +78,14 @@ const ProductVariantCreatorPrices: React.FC<ProductVariantCreatorPricesProps> =
|
|||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<RadioGroup value={data.price.all ? "applyToAll" : "applyToAttribute"}>
|
||||
<RadioGroup value={data.price.mode}>
|
||||
<FormControlLabel
|
||||
value="applyToAll"
|
||||
value="all"
|
||||
control={<Radio color="primary" />}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Apply single price to all SKUs"
|
||||
})}
|
||||
onChange={() => onApplyToAllChange(true)}
|
||||
onChange={() => onApplyToAllChange("all")}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
|
@ -103,15 +106,15 @@ const ProductVariantCreatorPrices: React.FC<ProductVariantCreatorPricesProps> =
|
|||
/>
|
||||
<FormSpacer />
|
||||
<FormControlLabel
|
||||
value="applyToAttribute"
|
||||
value="attribute"
|
||||
control={<Radio color="primary" />}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Apply unique prices by attribute to each SKU"
|
||||
})}
|
||||
onChange={() => onApplyToAllChange(false)}
|
||||
onChange={() => onApplyToAllChange("attribute")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
{!data.price.all && (
|
||||
{data.price.mode === "attribute" && (
|
||||
<>
|
||||
<FormSpacer />
|
||||
<Grid variant="uniform">
|
||||
|
|
|
@ -15,7 +15,14 @@ import Hr from "@saleor/components/Hr";
|
|||
import SingleSelectField from "@saleor/components/SingleSelectField";
|
||||
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import { ProductVariantCreateFormData } from "./form";
|
||||
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import { isSelected, toggle } from "@saleor/utils/lists";
|
||||
import {
|
||||
ProductVariantCreateFormData,
|
||||
VariantCreatorPricesAndSkuMode
|
||||
} from "./form";
|
||||
import { getStockAttributeValues } from "./utils";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -32,6 +39,30 @@ const useStyles = makeStyles(
|
|||
},
|
||||
shortInput: {
|
||||
width: "33%"
|
||||
},
|
||||
stockContainer: {
|
||||
columnGap: theme.spacing(3) + "px",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 288px)",
|
||||
rowGap: theme.spacing(2) + "px"
|
||||
},
|
||||
stockHeader: {
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
warehouseContainer: {
|
||||
columnGap: theme.spacing(3) + "px",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(4, 1fr)",
|
||||
rowGap: theme.spacing(2) + "px"
|
||||
},
|
||||
warehouseHeader: {
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
warehouseName: {
|
||||
marginBottom: theme.spacing()
|
||||
},
|
||||
warehouseSubheader: {
|
||||
marginBottom: theme.spacing(2)
|
||||
}
|
||||
}),
|
||||
{ name: "ProductVariantCreatorStock" }
|
||||
|
@ -40,20 +71,24 @@ const useStyles = makeStyles(
|
|||
export interface ProductVariantCreatorStockProps {
|
||||
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||
data: ProductVariantCreateFormData;
|
||||
onApplyToAllChange: (applyToAll: boolean) => void;
|
||||
warehouses: WarehouseFragment[];
|
||||
onApplyToAllChange: (mode: VariantCreatorPricesAndSkuMode) => void;
|
||||
onApplyToAllStockChange: (value: string) => void;
|
||||
onAttributeSelect: (id: string) => void;
|
||||
onAttributeValueChange: (id: string, value: string) => void;
|
||||
onWarehouseToggle: (id: string) => void;
|
||||
}
|
||||
|
||||
const ProductVariantCreatorStock: React.FC<ProductVariantCreatorStockProps> = props => {
|
||||
const {
|
||||
attributes,
|
||||
data,
|
||||
warehouses,
|
||||
onApplyToAllChange,
|
||||
onApplyToAllStockChange,
|
||||
onAttributeSelect,
|
||||
onAttributeValueChange
|
||||
onAttributeValueChange,
|
||||
onWarehouseToggle
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
const intl = useIntl();
|
||||
|
@ -73,98 +108,173 @@ const ProductVariantCreatorStock: React.FC<ProductVariantCreatorStockProps> = pr
|
|||
})}
|
||||
/>
|
||||
<CardContent>
|
||||
<RadioGroup value={data.stock.all ? "applyToAll" : "applyToAttribute"}>
|
||||
{warehouses.length > 1 && (
|
||||
<>
|
||||
<Typography className={classes.warehouseHeader} variant="h5">
|
||||
<FormattedMessage
|
||||
defaultMessage="Warehouses"
|
||||
description="header"
|
||||
id="productVariantCreatorStockSectionHeader"
|
||||
/>
|
||||
</Typography>
|
||||
<Typography className={classes.warehouseSubheader}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Based on your selections we will create {numberOfProducts} products. Use this step to customize price and stocks for your new products"
|
||||
id="productVariantCreatorStockSectionHeader"
|
||||
values={{
|
||||
numberOfProducts: data.attributes.reduce(
|
||||
(acc, attr) => acc + attr.values.length,
|
||||
0
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
<div className={classes.warehouseContainer}>
|
||||
{warehouses.map(warehouse => (
|
||||
<ControlledCheckbox
|
||||
checked={isSelected(
|
||||
warehouse.id,
|
||||
data.warehouses,
|
||||
(a, b) => a === b
|
||||
)}
|
||||
name={`warehouse:${warehouse.id}`}
|
||||
label={warehouse.name}
|
||||
onChange={() => onWarehouseToggle(warehouse.id)}
|
||||
key={warehouse.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<CardSpacer />
|
||||
<Hr />
|
||||
<CardSpacer />
|
||||
</>
|
||||
)}
|
||||
<Typography className={classes.stockHeader} variant="h5">
|
||||
<FormattedMessage
|
||||
defaultMessage="Stock"
|
||||
description="variant stock, header"
|
||||
id="productVariantCreatorStockSectionHeader"
|
||||
/>
|
||||
</Typography>
|
||||
<RadioGroup value={data.stock.mode}>
|
||||
<FormControlLabel
|
||||
value="applyToAll"
|
||||
value="all"
|
||||
control={<Radio color="primary" />}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Apply single stock to all SKUs"
|
||||
})}
|
||||
onChange={() => onApplyToAllChange(true)}
|
||||
/>
|
||||
<FormSpacer />
|
||||
<TextField
|
||||
className={classes.shortInput}
|
||||
inputProps={{
|
||||
min: 0,
|
||||
type: "number"
|
||||
}}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Stock",
|
||||
id: "productVariantCreatePricesStockInputLabel"
|
||||
})}
|
||||
value={data.stock.value}
|
||||
onChange={event => onApplyToAllStockChange(event.target.value)}
|
||||
onChange={() => onApplyToAllChange("all")}
|
||||
/>
|
||||
{data.stock.mode === "all" && (
|
||||
<div className={classes.stockContainer}>
|
||||
{data.warehouses.map((warehouseId, warehouseIndex) => (
|
||||
<div>
|
||||
<Typography
|
||||
className={classes.warehouseName}
|
||||
key={warehouseId}
|
||||
>
|
||||
{
|
||||
warehouses.find(warehouse => warehouse.id === warehouseId)
|
||||
.name
|
||||
}
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
inputProps={{
|
||||
min: 0,
|
||||
type: "number"
|
||||
}}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Stock",
|
||||
id: "productVariantCreatePricesStockInputLabel"
|
||||
})}
|
||||
value={data.stock.value[warehouseIndex]}
|
||||
onChange={event =>
|
||||
onApplyToAllStockChange(event.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<FormSpacer />
|
||||
<FormControlLabel
|
||||
value="applyToAttribute"
|
||||
value="attribute"
|
||||
control={<Radio color="primary" />}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Apply unique stock by attribute to each SKU"
|
||||
})}
|
||||
onChange={() => onApplyToAllChange(false)}
|
||||
onChange={() => onApplyToAllChange("attribute")}
|
||||
/>
|
||||
{data.stock.mode === "attribute" && (
|
||||
<>
|
||||
<FormSpacer />
|
||||
<Grid variant="uniform">
|
||||
<div className={classes.label}>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="Choose attribute"
|
||||
description="variant attribute"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<SingleSelectField
|
||||
choices={attributeChoices}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Attribute",
|
||||
description: "variant attribute"
|
||||
})}
|
||||
value={data.stock.attribute}
|
||||
onChange={onAttributeSelect}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
{stockAttributeValues &&
|
||||
stockAttributeValues.map(attributeValue => (
|
||||
<React.Fragment key={attributeValue.id}>
|
||||
<Hr className={classes.hrAttribute} />
|
||||
<FormSpacer />
|
||||
<Grid variant="uniform">
|
||||
<div className={classes.label}>
|
||||
<Typography>{attributeValue.name}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Stock",
|
||||
description: "variant stock",
|
||||
id: "productVariantCreatePricesSetStockPlaceholder"
|
||||
})}
|
||||
fullWidth
|
||||
value={
|
||||
data.stock.values.find(
|
||||
value => value.slug === attributeValue.slug
|
||||
).value
|
||||
}
|
||||
onChange={event =>
|
||||
onAttributeValueChange(
|
||||
attributeValue.slug,
|
||||
event.target.value
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<FormSpacer />
|
||||
<FormControlLabel
|
||||
value="skip"
|
||||
control={<Radio color="primary" />}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Skip stock for now"
|
||||
})}
|
||||
onChange={() => onApplyToAllChange("skip")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
{!data.stock.all && (
|
||||
<>
|
||||
<FormSpacer />
|
||||
<Grid variant="uniform">
|
||||
<div className={classes.label}>
|
||||
<Typography>
|
||||
<FormattedMessage
|
||||
defaultMessage="Choose attribute"
|
||||
description="variant attribute"
|
||||
/>
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<SingleSelectField
|
||||
choices={attributeChoices}
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Attribute",
|
||||
description: "variant attribute"
|
||||
})}
|
||||
value={data.stock.attribute}
|
||||
onChange={onAttributeSelect}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
{stockAttributeValues &&
|
||||
stockAttributeValues.map(attributeValue => (
|
||||
<React.Fragment key={attributeValue.id}>
|
||||
<Hr className={classes.hrAttribute} />
|
||||
<FormSpacer />
|
||||
<Grid variant="uniform">
|
||||
<div className={classes.label}>
|
||||
<Typography>{attributeValue.name}</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label={intl.formatMessage({
|
||||
defaultMessage: "Stock",
|
||||
description: "variant stock",
|
||||
id: "productVariantCreatePricesSetStockPlaceholder"
|
||||
})}
|
||||
fullWidth
|
||||
value={
|
||||
data.stock.values.find(
|
||||
value => value.slug === attributeValue.slug
|
||||
).value
|
||||
}
|
||||
onChange={event =>
|
||||
onAttributeValueChange(
|
||||
attributeValue.slug,
|
||||
event.target.value
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -52,7 +52,7 @@ const ProductVariantCreatorValues: React.FC<ProductVariantCreatorValuesProps> =
|
|||
value.slug,
|
||||
data.attributes.find(
|
||||
dataAttribute => attribute.id === dataAttribute.id
|
||||
).values,
|
||||
)?.values || [],
|
||||
(a, b) => a === b
|
||||
)}
|
||||
name={`value:${value.slug}`}
|
||||
|
|
|
@ -26,16 +26,32 @@ function getAttributeValuePriceOrStock<T>(
|
|||
return attributeValue.value;
|
||||
}
|
||||
|
||||
function getValueFromMode<T>(
|
||||
attributes: CreateVariantInput,
|
||||
priceOrStock: AllOrAttribute<T>,
|
||||
skipValue: T
|
||||
): T {
|
||||
switch (priceOrStock.mode) {
|
||||
case "all":
|
||||
return priceOrStock.value;
|
||||
case "attribute":
|
||||
return getAttributeValuePriceOrStock(attributes, priceOrStock);
|
||||
case "skip":
|
||||
return skipValue;
|
||||
}
|
||||
}
|
||||
|
||||
function createVariant(
|
||||
data: ProductVariantCreateFormData,
|
||||
attributes: CreateVariantInput
|
||||
): ProductVariantBulkCreateInput {
|
||||
const priceOverride = data.price.all
|
||||
? data.price.value
|
||||
: getAttributeValuePriceOrStock(attributes, data.price);
|
||||
const stocks = data.stock.all
|
||||
? data.stock.value
|
||||
: getAttributeValuePriceOrStock(attributes, data.stock);
|
||||
const priceOverride = getValueFromMode(attributes, data.price, "0");
|
||||
const stocks = getValueFromMode(
|
||||
attributes,
|
||||
data.stock,
|
||||
data.warehouses.map(() => 0)
|
||||
);
|
||||
|
||||
return {
|
||||
attributes: attributes.map(attribute => ({
|
||||
id: attribute.attributeId,
|
||||
|
@ -91,8 +107,8 @@ export function createVariants(
|
|||
data: ProductVariantCreateFormData
|
||||
): ProductVariantBulkCreateInput[] {
|
||||
if (
|
||||
(!data.price.all && !data.price.attribute) ||
|
||||
(!data.stock.all && !data.stock.attribute)
|
||||
(data.price.mode === "attribute" && !data.price.attribute) ||
|
||||
(data.stock.mode === "attribute" && !data.stock.attribute)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ export interface AttributeValue<T> {
|
|||
slug: string;
|
||||
value: T;
|
||||
}
|
||||
export type VariantCreatorPricesAndSkuMode = "all" | "attribute" | "skip";
|
||||
export interface AllOrAttribute<T> {
|
||||
all: boolean;
|
||||
mode: VariantCreatorPricesAndSkuMode;
|
||||
attribute: string;
|
||||
value: T;
|
||||
values: Array<AttributeValue<T>>;
|
||||
|
@ -34,14 +35,14 @@ export const createInitialForm = (
|
|||
values: []
|
||||
})),
|
||||
price: {
|
||||
all: true,
|
||||
attribute: undefined,
|
||||
mode: "all",
|
||||
value: price || "",
|
||||
values: []
|
||||
},
|
||||
stock: {
|
||||
all: true,
|
||||
attribute: undefined,
|
||||
mode: "all",
|
||||
value: warehouses.length === 1 ? [0] : [],
|
||||
values: []
|
||||
},
|
||||
|
|
|
@ -8,11 +8,16 @@ import {
|
|||
} from "@saleor/utils/lists";
|
||||
import { StockInput } from "@saleor/types/globalTypes";
|
||||
import { createVariants } from "./createVariants";
|
||||
import { ProductVariantCreateFormData } from "./form";
|
||||
import {
|
||||
ProductVariantCreateFormData,
|
||||
VariantCreatorPricesAndSkuMode
|
||||
} from "./form";
|
||||
|
||||
export enum ProductVariantCreateReducerActionType {
|
||||
applyPriceToAll,
|
||||
applyPriceToAttribute,
|
||||
applyStockToAll,
|
||||
applyStockToAttribute,
|
||||
changeApplyPriceToAllValue,
|
||||
changeApplyPriceToAttributeId,
|
||||
changeApplyStockToAllValue,
|
||||
|
@ -21,6 +26,7 @@ export enum ProductVariantCreateReducerActionType {
|
|||
changeAttributeValueStock,
|
||||
changeVariantData,
|
||||
changeVariantStockData,
|
||||
changeWarehouses,
|
||||
deleteVariant,
|
||||
reload,
|
||||
selectValue
|
||||
|
@ -28,7 +34,7 @@ export enum ProductVariantCreateReducerActionType {
|
|||
export type VariantField = "price" | "sku";
|
||||
export interface ProductVariantCreateReducerAction {
|
||||
applyPriceOrStockToAll?: {
|
||||
all: boolean;
|
||||
mode: VariantCreatorPricesAndSkuMode;
|
||||
};
|
||||
changeApplyPriceToAllValue?: {
|
||||
price: string;
|
||||
|
@ -52,6 +58,9 @@ export interface ProductVariantCreateReducerAction {
|
|||
stock: StockInput;
|
||||
variantIndex: number;
|
||||
};
|
||||
changeWarehouses?: {
|
||||
warehouseId: string;
|
||||
};
|
||||
deleteVariant?: {
|
||||
variantIndex: number;
|
||||
};
|
||||
|
@ -119,26 +128,26 @@ function selectValue(
|
|||
|
||||
function applyPriceToAll(
|
||||
state: ProductVariantCreateFormData,
|
||||
value: boolean
|
||||
mode: VariantCreatorPricesAndSkuMode
|
||||
): ProductVariantCreateFormData {
|
||||
return {
|
||||
...state,
|
||||
price: {
|
||||
...state.price,
|
||||
all: value
|
||||
mode
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function applyStockToAll(
|
||||
state: ProductVariantCreateFormData,
|
||||
value: boolean
|
||||
mode: VariantCreatorPricesAndSkuMode
|
||||
): ProductVariantCreateFormData {
|
||||
return {
|
||||
...state,
|
||||
stock: {
|
||||
...state.stock,
|
||||
all: value
|
||||
mode
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -318,6 +327,16 @@ function changeVariantStockData(
|
|||
};
|
||||
}
|
||||
|
||||
function changeWarehouses(
|
||||
state: ProductVariantCreateFormData,
|
||||
warehouseId: string
|
||||
): ProductVariantCreateFormData {
|
||||
return {
|
||||
...state,
|
||||
warehouses: toggle(warehouseId, state.warehouses, (a, b) => a === b)
|
||||
};
|
||||
}
|
||||
|
||||
function deleteVariant(
|
||||
state: ProductVariantCreateFormData,
|
||||
variantIndex: number
|
||||
|
@ -349,9 +368,9 @@ function reduceProductVariantCreateFormData(
|
|||
action.selectValue.valueId
|
||||
);
|
||||
case ProductVariantCreateReducerActionType.applyPriceToAll:
|
||||
return applyPriceToAll(prevState, action.applyPriceOrStockToAll.all);
|
||||
return applyPriceToAll(prevState, action.applyPriceOrStockToAll.mode);
|
||||
case ProductVariantCreateReducerActionType.applyStockToAll:
|
||||
return applyStockToAll(prevState, action.applyPriceOrStockToAll.all);
|
||||
return applyStockToAll(prevState, action.applyPriceOrStockToAll.mode);
|
||||
case ProductVariantCreateReducerActionType.changeAttributeValuePrice:
|
||||
return changeAttributeValuePrice(
|
||||
prevState,
|
||||
|
@ -383,8 +402,8 @@ function reduceProductVariantCreateFormData(
|
|||
case ProductVariantCreateReducerActionType.changeApplyStockToAllValue:
|
||||
return changeApplyStockToAllValue(
|
||||
prevState,
|
||||
action.changeApplyStockToAllValue.quantity,
|
||||
action.changeApplyStockToAllValue.warehouseIndex
|
||||
action.changeApplyStockToAllValue.warehouseIndex,
|
||||
action.changeApplyStockToAllValue.quantity
|
||||
);
|
||||
case ProductVariantCreateReducerActionType.changeVariantData:
|
||||
return changeVariantData(
|
||||
|
@ -399,6 +418,8 @@ function reduceProductVariantCreateFormData(
|
|||
action.changeVariantStockData.stock,
|
||||
action.changeVariantStockData.variantIndex
|
||||
);
|
||||
case ProductVariantCreateReducerActionType.changeWarehouses:
|
||||
return changeWarehouses(prevState, action.changeWarehouses.warehouseId);
|
||||
case ProductVariantCreateReducerActionType.deleteVariant:
|
||||
return deleteVariant(prevState, action.deleteVariant.variantIndex);
|
||||
case ProductVariantCreateReducerActionType.reload:
|
||||
|
|
|
@ -103,6 +103,7 @@ const productVariantAttributesFragment = gql`
|
|||
}
|
||||
}
|
||||
productType {
|
||||
id
|
||||
variantAttributes {
|
||||
id
|
||||
name
|
||||
|
|
|
@ -45,6 +45,9 @@ export function updateAtIndex<TData>(
|
|||
list: List<TData>,
|
||||
index: number
|
||||
) {
|
||||
if (!index.toFixed) {
|
||||
throw new Error("Index is not a number");
|
||||
}
|
||||
return addAtIndex(data, removeAtIndex(list, index), index);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue