Add variant creator wip
This commit is contained in:
parent
2637ec3fdf
commit
5a40f619e1
12 changed files with 1263 additions and 1 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||||
import {
|
import {
|
||||||
AttributeInputTypeEnum,
|
AttributeInputTypeEnum,
|
||||||
AttributeValueType
|
AttributeValueType
|
||||||
|
@ -35,7 +36,10 @@ export const attribute: AttributeDetailsFragment = {
|
||||||
visibleInStorefront: true
|
visibleInStorefront: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const attributes: AttributeList_attributes_edges_node[] = [
|
export const attributes: Array<
|
||||||
|
AttributeList_attributes_edges_node &
|
||||||
|
ProductDetails_product_productType_variantAttributes
|
||||||
|
> = [
|
||||||
{
|
{
|
||||||
node: {
|
node: {
|
||||||
__typename: "Attribute" as "Attribute",
|
__typename: "Attribute" as "Attribute",
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
import Card from "@material-ui/core/Card";
|
||||||
|
import CardContent from "@material-ui/core/CardContent";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { attributes } from "@saleor/attributes/fixtures";
|
||||||
|
import { isSelected } from "@saleor/utils/lists";
|
||||||
|
import Decorator from "../../../storybook/Decorator";
|
||||||
|
import ProductVariantCreateContent, {
|
||||||
|
ProductVariantCreateContentProps
|
||||||
|
} from "./ProductVariantCreateContent";
|
||||||
|
import ProductVariantCreateDialog from "./ProductVariantCreateDialog";
|
||||||
|
|
||||||
|
const selectedAttributes = [1, 2, 4].map(index => attributes[index].id);
|
||||||
|
const selectedValues = attributes
|
||||||
|
.filter(attribute =>
|
||||||
|
isSelected(attribute.id, selectedAttributes, (a, b) => a === b)
|
||||||
|
)
|
||||||
|
.map(attribute => attribute.values.map(value => value.id))
|
||||||
|
.reduce((acc, curr) => [...acc, ...curr], [])
|
||||||
|
.filter((_, valueIndex) => valueIndex % 2);
|
||||||
|
|
||||||
|
const props: ProductVariantCreateContentProps = {
|
||||||
|
attributes,
|
||||||
|
currencySymbol: "USD",
|
||||||
|
data: {
|
||||||
|
attributes: selectedAttributes,
|
||||||
|
price: {
|
||||||
|
all: false,
|
||||||
|
attribute: selectedAttributes[1],
|
||||||
|
value: "2.79",
|
||||||
|
values: selectedAttributes.map((_, attributeIndex) =>
|
||||||
|
(attributeIndex + 4).toFixed(2)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
stock: {
|
||||||
|
all: false,
|
||||||
|
attribute: selectedAttributes[1],
|
||||||
|
value: "8",
|
||||||
|
values: selectedAttributes.map((_, attributeIndex) =>
|
||||||
|
(selectedAttributes.length * 10 - attributeIndex).toString()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
values: selectedValues,
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
attributes: attributes
|
||||||
|
.filter(attribute => selectedAttributes.includes(attribute.id))
|
||||||
|
.map(attribute => ({
|
||||||
|
id: attribute.id,
|
||||||
|
values: [attribute.values[0].id]
|
||||||
|
})),
|
||||||
|
product: "=1uahc98nas"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
dispatchFormDataAction: () => undefined,
|
||||||
|
step: "attributes"
|
||||||
|
};
|
||||||
|
|
||||||
|
storiesOf("Views / Products / Create multiple variants", module)
|
||||||
|
.addDecorator(storyFn => (
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
margin: "auto",
|
||||||
|
overflow: "visible",
|
||||||
|
width: 600
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent>{storyFn()}</CardContent>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("choose attributes", () => <ProductVariantCreateContent {...props} />)
|
||||||
|
.add("select values", () => (
|
||||||
|
<ProductVariantCreateContent {...props} step="values" />
|
||||||
|
))
|
||||||
|
.add("prices and SKU", () => (
|
||||||
|
<ProductVariantCreateContent {...props} step="prices" />
|
||||||
|
))
|
||||||
|
.add("summary", () => (
|
||||||
|
<ProductVariantCreateContent {...props} step="summary" />
|
||||||
|
));
|
||||||
|
|
||||||
|
storiesOf("Views / Products / Create multiple variants", module)
|
||||||
|
.addDecorator(Decorator)
|
||||||
|
.add("interactive", () => (
|
||||||
|
<ProductVariantCreateDialog
|
||||||
|
{...props}
|
||||||
|
open={true}
|
||||||
|
onClose={() => undefined}
|
||||||
|
onSubmit={() => undefined}
|
||||||
|
/>
|
||||||
|
));
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import Table from "@material-ui/core/Table";
|
||||||
|
import TableBody from "@material-ui/core/TableBody";
|
||||||
|
import TableCell from "@material-ui/core/TableCell";
|
||||||
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
|
import makeStyles from "@material-ui/styles/makeStyles";
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import Checkbox from "@saleor/components/Checkbox";
|
||||||
|
import { maybe, renderCollection } from "@saleor/misc";
|
||||||
|
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
|
export interface ProductVariantCreateAttributesProps {
|
||||||
|
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||||
|
data: ProductVariantCreateFormData;
|
||||||
|
onAttributeClick: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
checkboxCell: {
|
||||||
|
paddingLeft: 0
|
||||||
|
},
|
||||||
|
wideCell: {
|
||||||
|
width: "100%"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ProductVariantCreateAttributes: React.FC<
|
||||||
|
ProductVariantCreateAttributesProps
|
||||||
|
> = props => {
|
||||||
|
const { attributes, data, onAttributeClick } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table key="table">
|
||||||
|
<TableBody>
|
||||||
|
{renderCollection(
|
||||||
|
attributes,
|
||||||
|
attribute => {
|
||||||
|
if (!attribute) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const isChecked = !!data.attributes.find(
|
||||||
|
selectedAttribute => selectedAttribute === attribute.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={maybe(() => attribute.id)}>
|
||||||
|
<TableCell padding="checkbox" className={classes.checkboxCell}>
|
||||||
|
<Checkbox
|
||||||
|
checked={isChecked}
|
||||||
|
disableClickPropagation={true}
|
||||||
|
onChange={() => onAttributeClick(attribute.id)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.wideCell}>
|
||||||
|
{attribute.name}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
() => (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={2}>
|
||||||
|
<FormattedMessage defaultMessage="This product type has no variant attributes" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductVariantCreateAttributes.displayName = "ProductVariantCreateAttributes";
|
||||||
|
export default ProductVariantCreateAttributes;
|
|
@ -0,0 +1,123 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { makeStyles } from "@material-ui/styles";
|
||||||
|
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { isSelected } from "@saleor/utils/lists";
|
||||||
|
import { ProductVariantCreateFormData } from "./form";
|
||||||
|
import ProductVariantCreateAttributes from "./ProductVariantCreateAttributes";
|
||||||
|
import ProductVariantCreatePrices from "./ProductVariantCreatePrices";
|
||||||
|
import ProductVariantCreateSummary from "./ProductVariantCreateSummary";
|
||||||
|
import ProductVariantCreateTabs from "./ProductVariantCreateTabs";
|
||||||
|
import ProductVariantCreateValues from "./ProductVariantCreateValues";
|
||||||
|
import { ProductVariantCreateReducerAction } from "./reducer";
|
||||||
|
import { ProductVariantCreateStep } from "./types";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
root: {
|
||||||
|
maxHeight: 400,
|
||||||
|
overflowY: "scroll"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface ProductVariantCreateContentProps {
|
||||||
|
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||||
|
currencySymbol: string;
|
||||||
|
data: ProductVariantCreateFormData;
|
||||||
|
dispatchFormDataAction: React.Dispatch<ProductVariantCreateReducerAction>;
|
||||||
|
step: ProductVariantCreateStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductVariantCreateContent: React.FC<
|
||||||
|
ProductVariantCreateContentProps
|
||||||
|
> = props => {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
currencySymbol,
|
||||||
|
data,
|
||||||
|
dispatchFormDataAction,
|
||||||
|
step
|
||||||
|
} = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
const selectedAttributes = attributes.filter(attribute =>
|
||||||
|
isSelected(attribute.id, data.attributes, (a, b) => a === b)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ProductVariantCreateTabs step={step} />
|
||||||
|
<div className={classes.root}>
|
||||||
|
{step === "attributes" && (
|
||||||
|
<ProductVariantCreateAttributes
|
||||||
|
attributes={attributes}
|
||||||
|
data={data}
|
||||||
|
onAttributeClick={id =>
|
||||||
|
dispatchFormDataAction({
|
||||||
|
id,
|
||||||
|
type: "selectAttribute"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step === "values" && (
|
||||||
|
<ProductVariantCreateValues
|
||||||
|
attributes={selectedAttributes}
|
||||||
|
data={data}
|
||||||
|
onValueClick={id =>
|
||||||
|
dispatchFormDataAction({
|
||||||
|
id,
|
||||||
|
type: "selectValue"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step === "prices" && (
|
||||||
|
<ProductVariantCreatePrices
|
||||||
|
attributes={selectedAttributes}
|
||||||
|
data={data}
|
||||||
|
onApplyPriceOrStockChange={(all, type) =>
|
||||||
|
dispatchFormDataAction({
|
||||||
|
all,
|
||||||
|
type: type === "price" ? "applyPriceToAll" : "applyStockToAll"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onApplyToAllChange={(value, type) =>
|
||||||
|
dispatchFormDataAction({
|
||||||
|
type:
|
||||||
|
type === "price"
|
||||||
|
? "changeApplyPriceToAllValue"
|
||||||
|
: "changeApplyStockToAllValue",
|
||||||
|
value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onAttributeSelect={(id, type) =>
|
||||||
|
dispatchFormDataAction({
|
||||||
|
id,
|
||||||
|
type:
|
||||||
|
type === "price"
|
||||||
|
? "changeApplyPriceToAttributeId"
|
||||||
|
: "changeApplyStockToAttributeId"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onValueClick={id =>
|
||||||
|
dispatchFormDataAction({
|
||||||
|
id,
|
||||||
|
type: "selectValue"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step === "summary" && (
|
||||||
|
<ProductVariantCreateSummary
|
||||||
|
attributes={selectedAttributes}
|
||||||
|
currencySymbol={currencySymbol}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductVariantCreateContent.displayName = "ProductVariantCreateContent";
|
||||||
|
export default ProductVariantCreateContent;
|
|
@ -0,0 +1,132 @@
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
|
import DialogActions from "@material-ui/core/DialogActions";
|
||||||
|
import DialogContent from "@material-ui/core/DialogContent";
|
||||||
|
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import { makeStyles } from "@material-ui/styles";
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { initialForm, ProductVariantCreateFormData } from "./form";
|
||||||
|
import ProductVariantCreateContent, {
|
||||||
|
ProductVariantCreateContentProps
|
||||||
|
} from "./ProductVariantCreateContent";
|
||||||
|
import reduceProductVariantCreateFormData from "./reducer";
|
||||||
|
import { ProductVariantCreateStep } from "./types";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
button: {
|
||||||
|
marginLeft: theme.spacing.unit * 2
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
overflowX: "visible",
|
||||||
|
overflowY: "hidden",
|
||||||
|
width: 600
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
export interface ProductVariantCreateDialogProps
|
||||||
|
extends Omit<
|
||||||
|
ProductVariantCreateContentProps,
|
||||||
|
"dispatchFormDataAction" | "step"
|
||||||
|
> {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => undefined;
|
||||||
|
onSubmit: (data: ProductVariantCreateFormData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductVariantCreateDialog: React.FC<
|
||||||
|
ProductVariantCreateDialogProps
|
||||||
|
> = props => {
|
||||||
|
const { open, onClose, ...contentProps } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
const [step, setStep] = React.useState<ProductVariantCreateStep>(
|
||||||
|
"attributes"
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleNextStep() {
|
||||||
|
switch (step) {
|
||||||
|
case "attributes":
|
||||||
|
setStep("values");
|
||||||
|
break;
|
||||||
|
case "values":
|
||||||
|
setStep("prices");
|
||||||
|
break;
|
||||||
|
case "prices":
|
||||||
|
setStep("summary");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePrevStep() {
|
||||||
|
switch (step) {
|
||||||
|
case "values":
|
||||||
|
setStep("attributes");
|
||||||
|
break;
|
||||||
|
case "prices":
|
||||||
|
setStep("values");
|
||||||
|
break;
|
||||||
|
case "summary":
|
||||||
|
setStep("prices");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [data, dispatchFormDataAction] = React.useReducer(
|
||||||
|
reduceProductVariantCreateFormData,
|
||||||
|
initialForm
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} maxWidth="md">
|
||||||
|
<DialogTitle>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Assign Attribute"
|
||||||
|
description="dialog header"
|
||||||
|
/>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent className={classes.content}>
|
||||||
|
<ProductVariantCreateContent
|
||||||
|
{...contentProps}
|
||||||
|
data={data}
|
||||||
|
dispatchFormDataAction={dispatchFormDataAction}
|
||||||
|
step={step}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button className={classes.button} onClick={onClose}>
|
||||||
|
<FormattedMessage defaultMessage="Cancel" description="button" />
|
||||||
|
</Button>
|
||||||
|
{step !== "attributes" && (
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
color="primary"
|
||||||
|
onClick={handlePrevStep}
|
||||||
|
>
|
||||||
|
<FormattedMessage defaultMessage="Back" description="button" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{step !== "summary" ? (
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
color="primary"
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleNextStep}
|
||||||
|
>
|
||||||
|
<FormattedMessage defaultMessage="Next" description="button" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button className={classes.button} variant="contained">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Create"
|
||||||
|
description="create multiple variants, button"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductVariantCreateDialog.displayName = "ProductVariantCreateDialog";
|
||||||
|
export default ProductVariantCreateDialog;
|
|
@ -0,0 +1,265 @@
|
||||||
|
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||||
|
import Radio from "@material-ui/core/Radio";
|
||||||
|
import RadioGroup from "@material-ui/core/RadioGroup";
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { makeStyles } from "@material-ui/styles";
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import FormSpacer from "@saleor/components/FormSpacer";
|
||||||
|
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) => ({
|
||||||
|
hr: {
|
||||||
|
marginBottom: theme.spacing.unit,
|
||||||
|
marginTop: theme.spacing.unit / 2
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
alignSelf: "center"
|
||||||
|
},
|
||||||
|
shortInput: {
|
||||||
|
width: "50%"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductVariantCreatePrices: React.FC<
|
||||||
|
ProductVariantCreatePricesProps
|
||||||
|
> = props => {
|
||||||
|
const {
|
||||||
|
attributes,
|
||||||
|
data,
|
||||||
|
onApplyPriceOrStockChange,
|
||||||
|
onApplyToAllChange,
|
||||||
|
onAttributeSelect
|
||||||
|
} = 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 => ({
|
||||||
|
label: attribute.name,
|
||||||
|
value: attribute.id
|
||||||
|
}));
|
||||||
|
const priceAttributeValues = data.price.all
|
||||||
|
? null
|
||||||
|
: data.price.attribute
|
||||||
|
? selectedAttributes.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
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography color="textSecondary" variant="headline">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Price"
|
||||||
|
description="variant price, header"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
<RadioGroup value={data.price.all ? "applyToAll" : "applyToAttribute"}>
|
||||||
|
<FormControlLabel
|
||||||
|
value="applyToAll"
|
||||||
|
control={<Radio color="primary" />}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Apply single price to all SKUs"
|
||||||
|
})}
|
||||||
|
onChange={() => onApplyPriceOrStockChange(true, "price")}
|
||||||
|
/>
|
||||||
|
<FormSpacer />
|
||||||
|
<TextField
|
||||||
|
className={classes.shortInput}
|
||||||
|
inputProps={{
|
||||||
|
min: 0,
|
||||||
|
type: "number"
|
||||||
|
}}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Price",
|
||||||
|
id: "productVariantCreatePricesPriceInputLabel"
|
||||||
|
})}
|
||||||
|
value={data.price.value}
|
||||||
|
onChange={event => onApplyToAllChange(event.target.value, "price")}
|
||||||
|
/>
|
||||||
|
<FormSpacer />
|
||||||
|
<FormControlLabel
|
||||||
|
value="applyToAttribute"
|
||||||
|
control={<Radio color="primary" />}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Apply unique prices by attribute to each SKU"
|
||||||
|
})}
|
||||||
|
onChange={() => onApplyPriceOrStockChange(false, "price")}
|
||||||
|
/>
|
||||||
|
{!data.price.all && (
|
||||||
|
<>
|
||||||
|
<FormSpacer />
|
||||||
|
<Grid variant="inverted">
|
||||||
|
<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.price.attribute}
|
||||||
|
onChange={event =>
|
||||||
|
onAttributeSelect(event.target.value, "price")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
{priceAttributeValues &&
|
||||||
|
priceAttributeValues.map((attribute, attributeIndex) => (
|
||||||
|
<>
|
||||||
|
<FormSpacer />
|
||||||
|
<Grid variant="inverted">
|
||||||
|
<div className={classes.label}>
|
||||||
|
<Typography>{attribute.name}</Typography>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Price",
|
||||||
|
description: "variant price",
|
||||||
|
id: "productVariantCreatePricesSetPricePlaceholder"
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
value={data.price.values[attributeIndex]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</RadioGroup>
|
||||||
|
<FormSpacer />
|
||||||
|
<Typography color="textSecondary" variant="headline">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Stock"
|
||||||
|
description="variant stock, header"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
<RadioGroup value={data.stock.all ? "applyToAll" : "applyToAttribute"}>
|
||||||
|
<FormControlLabel
|
||||||
|
value="applyToAll"
|
||||||
|
control={<Radio color="primary" />}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Apply single stock to all SKUs"
|
||||||
|
})}
|
||||||
|
onChange={() => onApplyPriceOrStockChange(true, "stock")}
|
||||||
|
/>
|
||||||
|
<FormSpacer />
|
||||||
|
<TextField
|
||||||
|
className={classes.shortInput}
|
||||||
|
inputProps={{
|
||||||
|
min: 0,
|
||||||
|
type: "number"
|
||||||
|
}}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Stock",
|
||||||
|
id: "productVariantCreatePricesStockInputLabel"
|
||||||
|
})}
|
||||||
|
value={data.stock.value}
|
||||||
|
onChange={event => onApplyToAllChange(event.target.value, "stock")}
|
||||||
|
/>
|
||||||
|
<FormSpacer />
|
||||||
|
<FormControlLabel
|
||||||
|
value="applyToAttribute"
|
||||||
|
control={<Radio color="primary" />}
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Apply unique stock by attribute to each SKU"
|
||||||
|
})}
|
||||||
|
onChange={() => onApplyPriceOrStockChange(false, "stock")}
|
||||||
|
/>
|
||||||
|
{!data.stock.all && (
|
||||||
|
<>
|
||||||
|
<FormSpacer />
|
||||||
|
<Grid variant="inverted">
|
||||||
|
<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={event =>
|
||||||
|
onAttributeSelect(event.target.value, "stock")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
{stockAttributeValues &&
|
||||||
|
stockAttributeValues.map((attribute, attributeIndex) => (
|
||||||
|
<>
|
||||||
|
<FormSpacer />
|
||||||
|
<Grid variant="inverted">
|
||||||
|
<div className={classes.label}>
|
||||||
|
<Typography>{attribute.name}</Typography>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={intl.formatMessage({
|
||||||
|
defaultMessage: "Stock",
|
||||||
|
description: "variant stock",
|
||||||
|
id: "productVariantCreatePricesSetStockPlaceholder"
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
value={data.stock.values[attributeIndex]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</RadioGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductVariantCreatePrices.displayName = "ProductVariantCreatePrices";
|
||||||
|
export default ProductVariantCreatePrices;
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import Table from "@material-ui/core/Table";
|
||||||
|
import TableBody from "@material-ui/core/TableBody";
|
||||||
|
import TableCell from "@material-ui/core/TableCell";
|
||||||
|
import TableHead from "@material-ui/core/TableHead";
|
||||||
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
|
import TextField from "@material-ui/core/TextField";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { makeStyles } from "@material-ui/styles";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import Hr from "@saleor/components/Hr";
|
||||||
|
import { ProductVariantCreateInput } from "@saleor/types/globalTypes";
|
||||||
|
import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails";
|
||||||
|
import { ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
|
export interface ProductVariantCreateSummaryProps {
|
||||||
|
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||||
|
currencySymbol: string;
|
||||||
|
data: ProductVariantCreateFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
col: {
|
||||||
|
paddingLeft: theme.spacing.unit,
|
||||||
|
paddingRight: theme.spacing.unit
|
||||||
|
},
|
||||||
|
colName: {
|
||||||
|
paddingLeft: "0 !important",
|
||||||
|
width: "auto"
|
||||||
|
},
|
||||||
|
colPrice: {
|
||||||
|
width: 110
|
||||||
|
},
|
||||||
|
colSku: {
|
||||||
|
width: 110
|
||||||
|
},
|
||||||
|
colStock: {
|
||||||
|
width: 110
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
marginBottom: theme.spacing.unit,
|
||||||
|
marginTop: theme.spacing.unit / 2
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
"& input": {
|
||||||
|
padding: "16px 12px 17px"
|
||||||
|
},
|
||||||
|
marginTop: theme.spacing.unit / 2,
|
||||||
|
width: 104
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
function getVariantName(
|
||||||
|
variant: ProductVariantCreateInput,
|
||||||
|
attributes: ProductDetails_product_productType_variantAttributes[]
|
||||||
|
): string[] {
|
||||||
|
return attributes.reduce(
|
||||||
|
(acc, attribute) => [
|
||||||
|
...acc,
|
||||||
|
attribute.values.find(
|
||||||
|
value =>
|
||||||
|
value.id ===
|
||||||
|
variant.attributes.find(
|
||||||
|
variantAttribute => variantAttribute.id === attribute.id
|
||||||
|
).values[0]
|
||||||
|
).name
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductVariantCreateSummary: React.FC<
|
||||||
|
ProductVariantCreateSummaryProps
|
||||||
|
> = props => {
|
||||||
|
const { attributes, currencySymbol, data } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography color="textSecondary" variant="headline">
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="You will create variants below"
|
||||||
|
description="header"
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colName)}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Variant"
|
||||||
|
description="variant name"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colStock)}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Inventory"
|
||||||
|
description="variant stock amount"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colPrice)}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Price"
|
||||||
|
description="variant price"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colSku)}>
|
||||||
|
<FormattedMessage defaultMessage="SKU" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{data.variants.map(variant => (
|
||||||
|
<TableRow
|
||||||
|
key={variant.attributes.map(attribute => attribute.id).join(":")}
|
||||||
|
>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colName)}>
|
||||||
|
{getVariantName(variant, attributes).join(" ")}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colStock)}>
|
||||||
|
<TextField
|
||||||
|
className={classes.input}
|
||||||
|
inputProps={{
|
||||||
|
min: 0,
|
||||||
|
type: "number"
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
value={variant.quantity}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colPrice)}>
|
||||||
|
<TextField
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: currencySymbol
|
||||||
|
}}
|
||||||
|
className={classes.input}
|
||||||
|
inputProps={{
|
||||||
|
min: 0,
|
||||||
|
type: "number"
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
value={variant.priceOverride}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classNames(classes.col, classes.colSku)}>
|
||||||
|
<TextField
|
||||||
|
className={classes.input}
|
||||||
|
fullWidth
|
||||||
|
value={variant.sku}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductVariantCreateSummary.displayName = "ProductVariantCreateSummary";
|
||||||
|
export default ProductVariantCreateSummary;
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { makeStyles } from "@material-ui/styles";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import React from "react";
|
||||||
|
import { IntlShape, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { ProductVariantCreateStep } from "./types";
|
||||||
|
|
||||||
|
interface Step {
|
||||||
|
label: string;
|
||||||
|
value: ProductVariantCreateStep;
|
||||||
|
}
|
||||||
|
function getSteps(intl: IntlShape): Step[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
defaultMessage: "Choose Attributes",
|
||||||
|
description: "variant creation step"
|
||||||
|
}),
|
||||||
|
value: "attributes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
defaultMessage: "Select Values",
|
||||||
|
description: "attribute values, variant creation step"
|
||||||
|
}),
|
||||||
|
value: "values"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
defaultMessage: "Prices and SKU",
|
||||||
|
description: "variant creation step"
|
||||||
|
}),
|
||||||
|
value: "prices"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
defaultMessage: "Summary",
|
||||||
|
description: "variant creation step"
|
||||||
|
}),
|
||||||
|
value: "summary"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(
|
||||||
|
(theme: Theme) => ({
|
||||||
|
label: {
|
||||||
|
fontSize: 14,
|
||||||
|
textAlign: "center"
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: theme.spacing.unit * 3
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
flex: 1,
|
||||||
|
paddingBottom: theme.spacing.unit
|
||||||
|
},
|
||||||
|
tabActive: {
|
||||||
|
fontWeight: 600
|
||||||
|
},
|
||||||
|
tabUnderline: {
|
||||||
|
borderBottom: `3px solid ${theme.palette.primary.main}`
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "ProductVariantCreateTabs"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface ProductVariantCreateTabsProps {
|
||||||
|
step: ProductVariantCreateStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductVariantCreateTabs: React.FC<
|
||||||
|
ProductVariantCreateTabsProps
|
||||||
|
> = props => {
|
||||||
|
const { step: currentStep } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
const intl = useIntl();
|
||||||
|
const steps = getSteps(intl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
{steps.map((step, stepIndex) => (
|
||||||
|
<div
|
||||||
|
className={classNames(classes.tab, {
|
||||||
|
[classes.tabActive]: step.value === currentStep,
|
||||||
|
[classes.tabUnderline]:
|
||||||
|
steps.findIndex(step => step.value === currentStep) >= stepIndex
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Typography className={classes.label} variant="caption">
|
||||||
|
{step.label}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductVariantCreateTabs.displayName = "ProductVariantCreateTabs";
|
||||||
|
export default ProductVariantCreateTabs;
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import makeStyles from "@material-ui/styles/makeStyles";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||||
|
import Hr from "@saleor/components/Hr";
|
||||||
|
import Skeleton from "@saleor/components/Skeleton";
|
||||||
|
import { maybe } from "@saleor/misc";
|
||||||
|
import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails";
|
||||||
|
import { isSelected } from "@saleor/utils/lists";
|
||||||
|
import { ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
|
export interface ProductVariantCreateValuesProps {
|
||||||
|
attributes: ProductDetails_product_productType_variantAttributes[];
|
||||||
|
data: ProductVariantCreateFormData;
|
||||||
|
onValueClick: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
hr: {
|
||||||
|
marginBottom: theme.spacing.unit,
|
||||||
|
marginTop: theme.spacing.unit / 2
|
||||||
|
},
|
||||||
|
valueContainer: {
|
||||||
|
display: "grid",
|
||||||
|
gridColumnGap: theme.spacing.unit * 3 + "px",
|
||||||
|
gridTemplateColumns: "repeat(3, 1fr)",
|
||||||
|
marginBottom: theme.spacing.unit * 3
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ProductVariantCreateValues: React.FC<
|
||||||
|
ProductVariantCreateValuesProps
|
||||||
|
> = props => {
|
||||||
|
const { attributes, data, onValueClick } = props;
|
||||||
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{attributes.map(attribute => (
|
||||||
|
<>
|
||||||
|
<Typography color="textSecondary" variant="headline">
|
||||||
|
{maybe<React.ReactNode>(() => attribute.name, <Skeleton />)}
|
||||||
|
</Typography>
|
||||||
|
<Hr className={classes.hr} />
|
||||||
|
<div className={classes.valueContainer}>
|
||||||
|
{attribute.values.map(value => (
|
||||||
|
<ControlledCheckbox
|
||||||
|
checked={isSelected(value.id, data.values, (a, b) => a === b)}
|
||||||
|
name={`value:${value.id}`}
|
||||||
|
label={value.name}
|
||||||
|
onChange={() => onValueClick(value.id)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductVariantCreateValues.displayName = "ProductVariantCreateValues";
|
||||||
|
export default ProductVariantCreateValues;
|
33
src/products/components/ProductVariantCreateDialog/form.ts
Normal file
33
src/products/components/ProductVariantCreateDialog/form.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { ProductVariantCreateInput } from "../../../types/globalTypes";
|
||||||
|
|
||||||
|
export interface AllOrAttribute {
|
||||||
|
all: boolean;
|
||||||
|
attribute: string;
|
||||||
|
value: string;
|
||||||
|
values: string[];
|
||||||
|
}
|
||||||
|
export interface ProductVariantCreateFormData {
|
||||||
|
attributes: string[];
|
||||||
|
price: AllOrAttribute;
|
||||||
|
stock: AllOrAttribute;
|
||||||
|
values: string[];
|
||||||
|
variants: ProductVariantCreateInput[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialForm: ProductVariantCreateFormData = {
|
||||||
|
attributes: [],
|
||||||
|
price: {
|
||||||
|
all: true,
|
||||||
|
attribute: undefined,
|
||||||
|
value: "",
|
||||||
|
values: []
|
||||||
|
},
|
||||||
|
stock: {
|
||||||
|
all: true,
|
||||||
|
attribute: undefined,
|
||||||
|
value: "",
|
||||||
|
values: []
|
||||||
|
},
|
||||||
|
values: [],
|
||||||
|
variants: []
|
||||||
|
};
|
192
src/products/components/ProductVariantCreateDialog/reducer.ts
Normal file
192
src/products/components/ProductVariantCreateDialog/reducer.ts
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
import { toggle, updateAtIndex } from "@saleor/utils/lists";
|
||||||
|
import { initialForm, ProductVariantCreateFormData } from "./form";
|
||||||
|
|
||||||
|
export type ProductVariantCreateReducerActionType =
|
||||||
|
| "applyPriceToAll"
|
||||||
|
| "applyPriceToAttribute"
|
||||||
|
| "applyStockToAll"
|
||||||
|
| "applyStockToAttribute"
|
||||||
|
| "changeApplyPriceToAllValue"
|
||||||
|
| "changeApplyPriceToAttributeId"
|
||||||
|
| "changeApplyStockToAllValue"
|
||||||
|
| "changeApplyStockToAttributeId"
|
||||||
|
| "changeAttributePrice"
|
||||||
|
| "changeAttributeStock"
|
||||||
|
| "selectAttribute"
|
||||||
|
| "selectValue";
|
||||||
|
export interface ProductVariantCreateReducerAction {
|
||||||
|
all?: boolean;
|
||||||
|
id?: string;
|
||||||
|
type: ProductVariantCreateReducerActionType;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAttribute(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
attribute: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
const attributes = toggle(attribute, state.attributes, (a, b) => a === b);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...initialForm,
|
||||||
|
attributes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectValue(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
value: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
const values = toggle(value, state.values, (a, b) => a === b);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...initialForm,
|
||||||
|
attributes: state.attributes,
|
||||||
|
values
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPriceToAll(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
value: boolean
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
price: {
|
||||||
|
...state.price,
|
||||||
|
all: value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStockToAll(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
value: boolean
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
stock: {
|
||||||
|
...state.stock,
|
||||||
|
all: value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeAttributePrice(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
attribute: string,
|
||||||
|
price: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
const index = state.price.values.indexOf(attribute);
|
||||||
|
const values = updateAtIndex(price, state.price.values, index);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
price: {
|
||||||
|
...state.price,
|
||||||
|
values
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeAttributeStock(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
attribute: string,
|
||||||
|
stock: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
const index = state.stock.values.indexOf(attribute);
|
||||||
|
const values = updateAtIndex(stock, state.stock.values, index);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
stock: {
|
||||||
|
...state.stock,
|
||||||
|
values
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeApplyPriceToAttributeId(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
attribute: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
price: {
|
||||||
|
...state.price,
|
||||||
|
attribute
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeApplyStockToAttributeId(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
attribute: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
stock: {
|
||||||
|
...state.stock,
|
||||||
|
attribute
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeApplyPriceToAllValue(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
value: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
price: {
|
||||||
|
...state.price,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeApplyStockToAllValue(
|
||||||
|
state: ProductVariantCreateFormData,
|
||||||
|
value: string
|
||||||
|
): ProductVariantCreateFormData {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
stock: {
|
||||||
|
...state.stock,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function reduceProductVariantCreateFormData(
|
||||||
|
prevState: ProductVariantCreateFormData,
|
||||||
|
action: ProductVariantCreateReducerAction
|
||||||
|
) {
|
||||||
|
switch (action.type) {
|
||||||
|
case "selectAttribute":
|
||||||
|
return selectAttribute(prevState, action.id);
|
||||||
|
|
||||||
|
case "selectValue":
|
||||||
|
return selectValue(prevState, action.id);
|
||||||
|
|
||||||
|
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 "changeApplyPriceToAttributeId":
|
||||||
|
return changeApplyPriceToAttributeId(prevState, action.id);
|
||||||
|
case "changeApplyStockToAttributeId":
|
||||||
|
return changeApplyStockToAttributeId(prevState, action.id);
|
||||||
|
case "changeApplyPriceToAllValue":
|
||||||
|
return changeApplyPriceToAllValue(prevState, action.value);
|
||||||
|
case "changeApplyStockToAllValue":
|
||||||
|
return changeApplyStockToAllValue(prevState, action.value);
|
||||||
|
}
|
||||||
|
return prevState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default reduceProductVariantCreateFormData;
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type ProductVariantCreateStep =
|
||||||
|
| "attributes"
|
||||||
|
| "values"
|
||||||
|
| "prices"
|
||||||
|
| "summary";
|
Loading…
Reference in a new issue