+ onValueClick={(attributeId, valueId) =>
dispatchFormDataAction({
- id,
- type: "selectValue"
+ attributeId,
+ type: "selectValue",
+ valueId
})
}
/>
@@ -90,19 +95,23 @@ const ProductVariantCreateContent: React.FC<
value
})
}
- onAttributeSelect={(id, type) =>
+ onAttributeSelect={(attributeId, type) =>
dispatchFormDataAction({
- id,
+ attributeId,
type:
type === "price"
? "changeApplyPriceToAttributeId"
: "changeApplyStockToAttributeId"
})
}
- onValueClick={id =>
+ onAttributeValueChange={(valueId, value, type) =>
dispatchFormDataAction({
- id,
- type: "selectValue"
+ type:
+ type === "price"
+ ? "changeAttributeValuePrice"
+ : "changeAttributeValueStock",
+ value,
+ valueId
})
}
/>
diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx
index 4143e63a3..8ba0e5a9d 100644
--- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx
+++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx
@@ -21,7 +21,7 @@ const useStyles = makeStyles((theme: Theme) => ({
content: {
overflowX: "visible",
overflowY: "hidden",
- width: 600
+ width: 800
}
}));
diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx
index 324feb070..b81f4a54b 100644
--- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx
+++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreatePrices.tsx
@@ -13,7 +13,6 @@ import Grid from "@saleor/components/Grid";
import Hr from "@saleor/components/Hr";
import SingleSelectField from "@saleor/components/SingleSelectField";
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
-import { isSelected } from "@saleor/utils/lists";
import { ProductVariantCreateFormData } from "./form";
const useStyles = makeStyles((theme: Theme) => ({
@@ -33,10 +32,14 @@ export type PriceOrStock = "price" | "stock";
export interface ProductVariantCreatePricesProps {
attributes: ProductDetails_product_productType_variantAttributes[];
data: ProductVariantCreateFormData;
- onValueClick: (id: string) => void;
- onAttributeSelect: (id: string, type: PriceOrStock) => void;
onApplyPriceOrStockChange: (applyToAll: boolean, type: PriceOrStock) => void;
onApplyToAllChange: (value: string, type: PriceOrStock) => void;
+ onAttributeSelect: (id: string, type: PriceOrStock) => void;
+ onAttributeValueChange: (
+ id: string,
+ value: string,
+ type: PriceOrStock
+ ) => void;
}
const ProductVariantCreatePrices: React.FC<
@@ -47,31 +50,25 @@ const ProductVariantCreatePrices: React.FC<
data,
onApplyPriceOrStockChange,
onApplyToAllChange,
- onAttributeSelect
+ onAttributeSelect,
+ onAttributeValueChange
} = props;
const classes = useStyles(props);
const intl = useIntl();
- const selectedAttributes = attributes.filter(attribute =>
- isSelected(attribute.id, data.attributes, (a, b) => a === b)
- );
- const attributeChoices = selectedAttributes.map(attribute => ({
+ const attributeChoices = attributes.map(attribute => ({
label: attribute.name,
value: attribute.id
}));
const priceAttributeValues = data.price.all
? null
: data.price.attribute
- ? selectedAttributes.find(
- attribute => attribute.id === data.price.attribute
- ).values
+ ? attributes.find(attribute => attribute.id === data.price.attribute).values
: [];
const stockAttributeValues = data.stock.all
? null
: data.stock.attribute
- ? selectedAttributes.find(
- attribute => attribute.id === data.stock.attribute
- ).values
+ ? attributes.find(attribute => attribute.id === data.stock.attribute).values
: [];
return (
@@ -142,27 +139,36 @@ const ProductVariantCreatePrices: React.FC<
{priceAttributeValues &&
- priceAttributeValues.map((attribute, attributeIndex) => (
- <>
-
-
-
- {attribute.name}
-
-
-
-
-
- >
- ))}
+ priceAttributeValues.map(
+ (attributeValue, attributeValueIndex) => (
+ <>
+
+
+
+ {attributeValue.name}
+
+
+
+ onAttributeValueChange(
+ attributeValue.id,
+ event.target.value,
+ "price"
+ )
+ }
+ />
+
+
+ >
+ )
+ )}
>
)}
@@ -233,27 +239,36 @@ const ProductVariantCreatePrices: React.FC<
{stockAttributeValues &&
- stockAttributeValues.map((attribute, attributeIndex) => (
- <>
-
-
-
- {attribute.name}
-
-
-
-
-
- >
- ))}
+ stockAttributeValues.map(
+ (attributeValue, attributeValueIndex) => (
+ <>
+
+
+
+ {attributeValue.name}
+
+
+
+ onAttributeValueChange(
+ attributeValue.id,
+ event.target.value,
+ "stock"
+ )
+ }
+ />
+
+
+ >
+ )
+ )}
>
)}
diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx
index c2953e425..210a6d4fa 100644
--- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx
+++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx
@@ -32,13 +32,13 @@ const useStyles = makeStyles((theme: Theme) => ({
width: "auto"
},
colPrice: {
- width: 110
+ width: 160
},
colSku: {
- width: 110
+ width: 210
},
colStock: {
- width: 110
+ width: 160
},
hr: {
marginBottom: theme.spacing.unit,
diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx
index 84327bfd5..801dc222f 100644
--- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx
+++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx
@@ -14,7 +14,7 @@ import { ProductVariantCreateFormData } from "./form";
export interface ProductVariantCreateValuesProps {
attributes: ProductDetails_product_productType_variantAttributes[];
data: ProductVariantCreateFormData;
- onValueClick: (id: string) => void;
+ onValueClick: (attributeId: string, valueId: string) => void;
}
const useStyles = makeStyles((theme: Theme) => ({
@@ -47,10 +47,16 @@ const ProductVariantCreateValues: React.FC<
{attribute.values.map(value => (
a === b)}
+ checked={isSelected(
+ value.id,
+ data.attributes.find(
+ dataAttribute => attribute.id === dataAttribute.id
+ ).values,
+ (a, b) => a === b
+ )}
name={`value:${value.id}`}
label={value.name}
- onChange={() => onValueClick(value.id)}
+ onChange={() => onValueClick(attribute.id, value.id)}
/>
))}
diff --git a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap
new file mode 100644
index 000000000..d1b59f93b
--- /dev/null
+++ b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap
@@ -0,0 +1,457 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Reducer is able to select attribute values 1`] = `
+Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ "val-4-5",
+ ],
+ },
+ ],
+ "price": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "stock": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "variants": Array [],
+}
+`;
+
+exports[`Reducer is able to select attributes 1`] = `
+Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [],
+ },
+ ],
+ "price": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "stock": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "variants": Array [],
+}
+`;
+
+exports[`Reducer is able to select price for all variants 1`] = `
+Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ "val-4-5",
+ ],
+ },
+ ],
+ "price": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "45.99",
+ "values": Array [],
+ },
+ "stock": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "variants": Array [],
+}
+`;
+
+exports[`Reducer is able to select price to each attribute value 1`] = `
+Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ "val-4-5",
+ ],
+ },
+ ],
+ "price": Object {
+ "all": false,
+ "attribute": "attr-1",
+ "value": "",
+ "values": Array [
+ Object {
+ "id": "val-1-1",
+ "value": "45.99",
+ },
+ Object {
+ "id": "val-1-7",
+ "value": "51.99",
+ },
+ ],
+ },
+ "stock": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "variants": Array [],
+}
+`;
+
+exports[`Reducer is able to select stock for all variants 1`] = `
+Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ "val-4-5",
+ ],
+ },
+ ],
+ "price": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "stock": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "45.99",
+ "values": Array [],
+ },
+ "variants": Array [
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-5",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-5",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-5",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-5",
+ ],
+ },
+ ],
+ "priceOverride": "",
+ "product": "",
+ "quantity": 45,
+ },
+ ],
+}
+`;
+
+exports[`Reducer is able to select stock to each attribute value 1`] = `
+Object {
+ "attributes": Array [
+ Object {
+ "id": "attr-1",
+ "values": Array [
+ "val-1-1",
+ "val-1-7",
+ ],
+ },
+ Object {
+ "id": "attr-2",
+ "values": Array [
+ "val-2-2",
+ "val-2-4",
+ ],
+ },
+ Object {
+ "id": "attr-4",
+ "values": Array [
+ "val-4-1",
+ "val-4-5",
+ ],
+ },
+ ],
+ "price": Object {
+ "all": true,
+ "attribute": undefined,
+ "value": "",
+ "values": Array [],
+ },
+ "stock": Object {
+ "all": false,
+ "attribute": "attr-1",
+ "value": "",
+ "values": Array [
+ Object {
+ "id": "val-1-1",
+ "value": "13",
+ },
+ Object {
+ "id": "val-1-7",
+ "value": "19",
+ },
+ ],
+ },
+ "variants": Array [],
+}
+`;
diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts
new file mode 100644
index 000000000..86b2230e9
--- /dev/null
+++ b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts
@@ -0,0 +1,43 @@
+import {
+ createVariantFlatMatrixDimension,
+ createVariants
+} from "./createVariants";
+import { thirdStep } from "./fixtures";
+import { ProductVariantCreateFormData } from "./form";
+
+describe("Creates variant matrix", () => {
+ it("with proper size", () => {
+ const attributes = thirdStep.attributes;
+
+ const matrix = createVariantFlatMatrixDimension([[]], attributes);
+
+ expect(matrix).toHaveLength(
+ attributes.reduce((acc, attribute) => acc * attribute.values.length, 1)
+ );
+ });
+
+ it("with constant price and stock", () => {
+ const price = "49.99";
+ const stock = 80;
+
+ const data: ProductVariantCreateFormData = {
+ ...thirdStep,
+ price: {
+ ...thirdStep.price,
+ all: true,
+ value: price
+ },
+ stock: {
+ ...thirdStep.stock,
+ all: true,
+ value: stock.toString()
+ }
+ };
+
+ const variants = createVariants(data);
+ variants.forEach(variant => {
+ expect(variant.priceOverride).toBe(price);
+ expect(variant.quantity).toBe(stock);
+ });
+ });
+});
diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.ts b/src/products/components/ProductVariantCreateDialog/createVariants.ts
new file mode 100644
index 000000000..fa8072136
--- /dev/null
+++ b/src/products/components/ProductVariantCreateDialog/createVariants.ts
@@ -0,0 +1,89 @@
+import { ProductVariantCreateInput } from "@saleor/types/globalTypes";
+import { Attribute, ProductVariantCreateFormData } from "./form";
+
+interface CreateVariantAttributeValueInput {
+ attributeId: string;
+ attributeValueId: string;
+}
+type CreateVariantInput = CreateVariantAttributeValueInput[];
+function createVariant(
+ data: ProductVariantCreateFormData,
+ attributes: CreateVariantInput
+): ProductVariantCreateInput {
+ const priceOverride = data.price.all
+ ? data.price.value
+ : data.price.values.find(
+ value =>
+ attributes.find(
+ attribute => attribute.attributeId === data.price.attribute
+ ).attributeValueId === value.id
+ ).value;
+ const quantity = parseInt(
+ data.stock.all
+ ? data.stock.value
+ : data.stock.values.find(
+ value =>
+ attributes.find(
+ attribute => attribute.attributeId === data.stock.attribute
+ ).attributeValueId === value.id
+ ).value,
+ 10
+ );
+
+ return {
+ attributes: attributes.map(attribute => ({
+ id: attribute.attributeId,
+ values: [attribute.attributeValueId]
+ })),
+ priceOverride,
+ product: "",
+ quantity
+ };
+}
+
+function addAttributeToVariant(
+ attribute: Attribute,
+ variant: CreateVariantInput
+): CreateVariantInput[] {
+ return attribute.values.map(attributeValueId => [
+ ...variant,
+ {
+ attributeId: attribute.id,
+ attributeValueId
+ }
+ ]);
+}
+function addVariantAttributeInput(
+ data: CreateVariantInput[],
+ attribute: Attribute
+): CreateVariantInput[] {
+ const variants = data
+ .map(variant => addAttributeToVariant(attribute, variant))
+ .reduce((acc, variantInput) => [...acc, ...variantInput]);
+
+ return variants;
+}
+
+export function createVariantFlatMatrixDimension(
+ variants: CreateVariantInput[],
+ attributes: Attribute[]
+): CreateVariantInput[] {
+ if (attributes.length > 0) {
+ return createVariantFlatMatrixDimension(
+ addVariantAttributeInput(variants, attributes[0]),
+ attributes.slice(1)
+ );
+ } else {
+ return variants;
+ }
+}
+
+export function createVariants(
+ data: ProductVariantCreateFormData
+): ProductVariantCreateInput[] {
+ const variants = createVariantFlatMatrixDimension([[]], data.attributes).map(
+ variant => createVariant(data, variant)
+ );
+
+ return variants;
+}
diff --git a/src/products/components/ProductVariantCreateDialog/fixtures.ts b/src/products/components/ProductVariantCreateDialog/fixtures.ts
new file mode 100644
index 000000000..1a6180a2e
--- /dev/null
+++ b/src/products/components/ProductVariantCreateDialog/fixtures.ts
@@ -0,0 +1,63 @@
+import { initialForm, ProductVariantCreateFormData } from "./form";
+
+export const attributes = [
+ {
+ id: "attr-1",
+ values: Array(9)
+ .fill(0)
+ .map((_, index) => `val-1-${index + 1}`)
+ },
+ {
+ id: "attr-2",
+ values: Array(6)
+ .fill(0)
+ .map((_, index) => `val-2-${index + 1}`)
+ },
+ {
+ id: "attr-3",
+ values: Array(4)
+ .fill(0)
+ .map((_, index) => `val-3-${index + 1}`)
+ },
+ {
+ id: "attr-4",
+ values: Array(11)
+ .fill(0)
+ .map((_, index) => `val-4-${index + 1}`)
+ }
+];
+
+export const secondStep: ProductVariantCreateFormData = {
+ ...initialForm,
+ attributes: [
+ {
+ id: attributes[0].id,
+ values: []
+ },
+ {
+ id: attributes[1].id,
+ values: []
+ },
+ {
+ id: attributes[3].id,
+ values: []
+ }
+ ]
+};
+export const thirdStep: ProductVariantCreateFormData = {
+ ...secondStep,
+ attributes: [
+ {
+ id: attributes[0].id,
+ values: [0, 6].map(index => attributes[0].values[index])
+ },
+ {
+ id: attributes[1].id,
+ values: [1, 3].map(index => attributes[1].values[index])
+ },
+ {
+ id: attributes[3].id,
+ values: [0, 4].map(index => attributes[3].values[index])
+ }
+ ]
+};
diff --git a/src/products/components/ProductVariantCreateDialog/form.ts b/src/products/components/ProductVariantCreateDialog/form.ts
index 503ffbc8b..157ad2c55 100644
--- a/src/products/components/ProductVariantCreateDialog/form.ts
+++ b/src/products/components/ProductVariantCreateDialog/form.ts
@@ -1,16 +1,23 @@
import { ProductVariantCreateInput } from "../../../types/globalTypes";
+export interface AttributeValue {
+ id: string;
+ value: string;
+}
export interface AllOrAttribute {
all: boolean;
attribute: string;
value: string;
+ values: AttributeValue[];
+}
+export interface Attribute {
+ id: string;
values: string[];
}
export interface ProductVariantCreateFormData {
- attributes: string[];
+ attributes: Attribute[];
price: AllOrAttribute;
stock: AllOrAttribute;
- values: string[];
variants: ProductVariantCreateInput[];
}
@@ -28,6 +35,5 @@ export const initialForm: ProductVariantCreateFormData = {
value: "",
values: []
},
- values: [],
variants: []
};
diff --git a/src/products/components/ProductVariantCreateDialog/reducer.test.ts b/src/products/components/ProductVariantCreateDialog/reducer.test.ts
new file mode 100644
index 000000000..fecca566c
--- /dev/null
+++ b/src/products/components/ProductVariantCreateDialog/reducer.test.ts
@@ -0,0 +1,171 @@
+import { attributes, secondStep, thirdStep } from "./fixtures";
+import { initialForm } from "./form";
+import reducer from "./reducer";
+
+function execActions(
+ initialState: TState,
+ reducer: (state: TState, action: TAction) => TState,
+ actions: TAction[]
+): TState {
+ return actions.reduce((acc, action) => reducer(acc, action), initialState);
+}
+
+describe("Reducer is able to", () => {
+ it("select attributes", () => {
+ const state = execActions(initialForm, reducer, [
+ {
+ attributeId: attributes[0].id,
+ type: "selectAttribute"
+ },
+ {
+ attributeId: attributes[1].id,
+ type: "selectAttribute"
+ },
+ {
+ attributeId: attributes[3].id,
+ type: "selectAttribute"
+ }
+ ]);
+
+ expect(state.attributes).toHaveLength(3);
+ expect(state).toMatchSnapshot();
+ });
+
+ it("select attribute values", () => {
+ const state = execActions(secondStep, reducer, [
+ {
+ attributeId: attributes[0].id,
+ type: "selectValue",
+ valueId: attributes[0].values[0]
+ },
+ {
+ attributeId: attributes[0].id,
+ type: "selectValue",
+ valueId: attributes[0].values[6]
+ },
+ {
+ attributeId: attributes[1].id,
+ type: "selectValue",
+ valueId: attributes[1].values[1]
+ },
+ {
+ attributeId: attributes[1].id,
+ type: "selectValue",
+ valueId: attributes[1].values[3]
+ },
+ {
+ attributeId: attributes[3].id,
+ type: "selectValue",
+ valueId: attributes[3].values[0]
+ },
+ {
+ attributeId: attributes[3].id,
+ type: "selectValue",
+ valueId: attributes[3].values[4]
+ }
+ ]);
+
+ expect(state.attributes[0].values).toHaveLength(2);
+ expect(state.attributes[1].values).toHaveLength(2);
+ expect(state.attributes[2].values).toHaveLength(2);
+ expect(state).toMatchSnapshot();
+ });
+
+ it("select price for all variants", () => {
+ const value = "45.99";
+ const state = execActions(thirdStep, reducer, [
+ {
+ all: true,
+ type: "applyPriceToAll"
+ },
+ {
+ type: "changeApplyPriceToAllValue",
+ value
+ }
+ ]);
+
+ expect(state.price.all).toBeTruthy();
+ expect(state.price.value).toBe(value);
+ expect(state).toMatchSnapshot();
+ });
+
+ it("select stock for all variants", () => {
+ const value = 45.99;
+ const state = execActions(thirdStep, reducer, [
+ {
+ all: true,
+ type: "applyStockToAll"
+ },
+ {
+ type: "changeApplyStockToAllValue",
+ value: value.toString()
+ }
+ ]);
+
+ expect(state.stock.all).toBeTruthy();
+ expect(state.stock.value).toBe(value.toString());
+ expect(state).toMatchSnapshot();
+ });
+
+ it("select price to each attribute value", () => {
+ const value = 45.99;
+ const state = execActions(thirdStep, reducer, [
+ {
+ all: false,
+ type: "applyPriceToAll"
+ },
+ {
+ attributeId: attributes[0].id,
+ type: "changeApplyPriceToAttributeId"
+ },
+ {
+ type: "changeAttributeValuePrice",
+ value: value.toString(),
+ valueId: attributes[0].values[0]
+ },
+ {
+ type: "changeAttributeValuePrice",
+ value: (value + 6).toString(),
+ valueId: attributes[0].values[6]
+ }
+ ]);
+
+ expect(state.price.all).toBeFalsy();
+ expect(state.price.values).toHaveLength(
+ state.attributes.find(attribute => state.price.attribute === attribute.id)
+ .values.length
+ );
+ expect(state).toMatchSnapshot();
+ });
+
+ it("select stock to each attribute value", () => {
+ const value = 13;
+ const state = execActions(thirdStep, reducer, [
+ {
+ all: false,
+ type: "applyStockToAll"
+ },
+ {
+ attributeId: attributes[0].id,
+ type: "changeApplyStockToAttributeId"
+ },
+ {
+ type: "changeAttributeValueStock",
+ value: value.toString(),
+ valueId: attributes[0].values[0]
+ },
+ {
+ type: "changeAttributeValueStock",
+ value: (value + 6).toString(),
+ valueId: attributes[0].values[6]
+ }
+ ]);
+
+ expect(state.stock.all).toBeFalsy();
+ expect(state.stock.values).toHaveLength(
+ state.attributes.find(attribute => state.stock.attribute === attribute.id)
+ .values.length
+ );
+ expect(state).toMatchSnapshot();
+ });
+});
diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts
index 25379b91d..e8954066c 100644
--- a/src/products/components/ProductVariantCreateDialog/reducer.ts
+++ b/src/products/components/ProductVariantCreateDialog/reducer.ts
@@ -1,4 +1,5 @@
-import { toggle, updateAtIndex } from "@saleor/utils/lists";
+import { add, remove, toggle, updateAtIndex } from "@saleor/utils/lists";
+import { createVariants } from "./createVariants";
import { initialForm, ProductVariantCreateFormData } from "./form";
export type ProductVariantCreateReducerActionType =
@@ -10,22 +11,30 @@ export type ProductVariantCreateReducerActionType =
| "changeApplyPriceToAttributeId"
| "changeApplyStockToAllValue"
| "changeApplyStockToAttributeId"
- | "changeAttributePrice"
- | "changeAttributeStock"
+ | "changeAttributeValuePrice"
+ | "changeAttributeValueStock"
| "selectAttribute"
| "selectValue";
export interface ProductVariantCreateReducerAction {
all?: boolean;
- id?: string;
+ attributeId?: string;
type: ProductVariantCreateReducerActionType;
value?: string;
+ valueId?: string;
}
function selectAttribute(
state: ProductVariantCreateFormData,
- attribute: string
+ attributeId: string
): ProductVariantCreateFormData {
- const attributes = toggle(attribute, state.attributes, (a, b) => a === b);
+ const attributes = toggle(
+ {
+ id: attributeId,
+ values: []
+ },
+ state.attributes,
+ (a, b) => a.id === b.id
+ );
return {
...initialForm,
@@ -35,14 +44,24 @@ function selectAttribute(
function selectValue(
state: ProductVariantCreateFormData,
- value: string
+ attributeId: string,
+ valueId: string
): ProductVariantCreateFormData {
- const values = toggle(value, state.values, (a, b) => a === b);
+ const attribute = state.attributes.find(
+ attribute => attribute.id === attributeId
+ );
+ const values = toggle(valueId, attribute.values, (a, b) => a === b);
+ const updatedAttributes = add(
+ {
+ id: attributeId,
+ values
+ },
+ remove(attribute, state.attributes, (a, b) => a.id === b.id)
+ );
return {
...initialForm,
- attributes: state.attributes,
- values
+ attributes: updatedAttributes
};
}
@@ -72,13 +91,27 @@ function applyStockToAll(
};
}
-function changeAttributePrice(
+function changeAttributeValuePrice(
state: ProductVariantCreateFormData,
- attribute: string,
+ attributeValueId: string,
price: string
): ProductVariantCreateFormData {
- const index = state.price.values.indexOf(attribute);
- const values = updateAtIndex(price, state.price.values, index);
+ const index = state.price.values.findIndex(
+ value => value.id === attributeValueId
+ );
+
+ if (index === -1) {
+ throw new Error(`Value with id ${attributeValueId} not found`);
+ }
+
+ const values = updateAtIndex(
+ {
+ id: attributeValueId,
+ value: price
+ },
+ state.price.values,
+ index
+ );
return {
...state,
@@ -89,13 +122,27 @@ function changeAttributePrice(
};
}
-function changeAttributeStock(
+function changeAttributeValueStock(
state: ProductVariantCreateFormData,
- attribute: string,
+ attributeValueId: string,
stock: string
): ProductVariantCreateFormData {
- const index = state.stock.values.indexOf(attribute);
- const values = updateAtIndex(stock, state.stock.values, index);
+ const index = state.stock.values.findIndex(
+ value => value.id === attributeValueId
+ );
+
+ if (index === -1) {
+ throw new Error(`Value with id ${attributeValueId} not found`);
+ }
+
+ const values = updateAtIndex(
+ {
+ id: attributeValueId,
+ value: stock
+ },
+ state.stock.values,
+ index
+ );
return {
...state,
@@ -108,13 +155,22 @@ function changeAttributeStock(
function changeApplyPriceToAttributeId(
state: ProductVariantCreateFormData,
- attribute: string
+ attributeId: string
): ProductVariantCreateFormData {
+ const attribute = state.attributes.find(
+ attribute => attribute.id === attributeId
+ );
+ const values = attribute.values.map(id => ({
+ id,
+ value: ""
+ }));
+
return {
...state,
price: {
...state.price,
- attribute
+ attribute: attributeId,
+ values
}
};
}
@@ -127,7 +183,13 @@ function changeApplyStockToAttributeId(
...state,
stock: {
...state.stock,
- attribute
+ attribute,
+ values: state.attributes
+ .find(stateAttribute => stateAttribute.id === attribute)
+ .values.map(attributeValue => ({
+ id: attributeValue,
+ value: ""
+ }))
}
};
}
@@ -149,13 +211,18 @@ function changeApplyStockToAllValue(
state: ProductVariantCreateFormData,
value: string
): ProductVariantCreateFormData {
- return {
+ const data = {
...state,
stock: {
...state.stock,
value
}
};
+
+ return {
+ ...data,
+ variants: createVariants(data)
+ };
}
function reduceProductVariantCreateFormData(
@@ -164,29 +231,30 @@ function reduceProductVariantCreateFormData(
) {
switch (action.type) {
case "selectAttribute":
- return selectAttribute(prevState, action.id);
+ return selectAttribute(prevState, action.attributeId);
case "selectValue":
- return selectValue(prevState, action.id);
+ return selectValue(prevState, action.attributeId, action.valueId);
case "applyPriceToAll":
return applyPriceToAll(prevState, action.all);
case "applyStockToAll":
return applyStockToAll(prevState, action.all);
- case "changeAttributePrice":
- return changeAttributePrice(prevState, action.id, action.value);
- case "changeAttributeStock":
- return changeAttributeStock(prevState, action.id, action.value);
+ case "changeAttributeValuePrice":
+ return changeAttributeValuePrice(prevState, action.valueId, action.value);
+ case "changeAttributeValueStock":
+ return changeAttributeValueStock(prevState, action.valueId, action.value);
case "changeApplyPriceToAttributeId":
- return changeApplyPriceToAttributeId(prevState, action.id);
+ return changeApplyPriceToAttributeId(prevState, action.attributeId);
case "changeApplyStockToAttributeId":
- return changeApplyStockToAttributeId(prevState, action.id);
+ return changeApplyStockToAttributeId(prevState, action.attributeId);
case "changeApplyPriceToAllValue":
return changeApplyPriceToAllValue(prevState, action.value);
case "changeApplyStockToAllValue":
return changeApplyStockToAllValue(prevState, action.value);
+ default:
+ return prevState;
}
- return prevState;
}
export default reduceProductVariantCreateFormData;