This commit is contained in:
dominik-zeglen 2020-04-07 14:46:10 +02:00
parent 688fc83f55
commit 64371d04a6
14 changed files with 354 additions and 155 deletions

60
package-lock.json generated
View file

@ -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": {

View file

@ -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",

View file

@ -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}

View file

@ -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 && (

View file

@ -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;
}

View file

@ -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}
/>
</>
);

View file

@ -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">

View file

@ -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>
);

View file

@ -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}`}

View file

@ -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 [];
}

View file

@ -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: []
},

View file

@ -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:

View file

@ -103,6 +103,7 @@ const productVariantAttributesFragment = gql`
}
}
productType {
id
variantAttributes {
id
name

View file

@ -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);
}