Update product stock management to newest design

This commit is contained in:
dominik-zeglen 2020-05-04 17:29:06 +02:00
parent ed7e26f98c
commit 6af08af910
29 changed files with 1084 additions and 920 deletions

View file

@ -172,7 +172,7 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
disabled={loading} disabled={loading}
errors={updateResult.data?.categoryUpdate.errors || []} errors={updateResult.data?.categoryUpdate.errors || []}
onAddCategory={() => navigate(categoryAddUrl(id))} onAddCategory={() => navigate(categoryAddUrl(id))}
onAddProduct={() => navigate(productAddUrl())} onAddProduct={() => navigate(productAddUrl)}
onBack={() => onBack={() =>
navigate( navigate(
maybe( maybe(

View file

@ -46,7 +46,7 @@ export function searchInCommands(
{ {
label: intl.formatMessage(messages.createProduct), label: intl.formatMessage(messages.createProduct),
onClick: () => { onClick: () => {
navigate(productAddUrl()); navigate(productAddUrl);
return false; return false;
} }
}, },

View file

@ -1,3 +1,4 @@
import { removeAtIndex } from "@saleor/utils/lists";
import useStateFromProps from "./useStateFromProps"; import useStateFromProps from "./useStateFromProps";
export type FormsetChange<TValue = any> = (id: string, value: TValue) => void; export type FormsetChange<TValue = any> = (id: string, value: TValue) => void;
@ -11,11 +12,13 @@ export type FormsetData<TData = object, TValue = any> = Array<
FormsetAtomicData<TData, TValue> FormsetAtomicData<TData, TValue>
>; >;
export interface UseFormsetOutput<TData = object, TValue = any> { export interface UseFormsetOutput<TData = object, TValue = any> {
add: (data: FormsetAtomicData<TData, TValue>) => void;
change: FormsetChange<TValue>; change: FormsetChange<TValue>;
data: FormsetData<TData, TValue>; data: FormsetData<TData, TValue>;
get: (id: string) => FormsetAtomicData<TData, TValue>; get: (id: string) => FormsetAtomicData<TData, TValue>;
// Used for some rare situations like dataset change // Used for some rare situations like dataset change
set: (data: FormsetData<TData, TValue>) => void; set: (data: FormsetData<TData, TValue>) => void;
remove: (id: string) => void;
} }
function useFormset<TData = object, TValue = any>( function useFormset<TData = object, TValue = any>(
initial: FormsetData<TData, TValue> initial: FormsetData<TData, TValue>
@ -24,10 +27,23 @@ function useFormset<TData = object, TValue = any>(
initial || [] initial || []
); );
function addItem(itemData: FormsetAtomicData<TData, TValue>) {
setData(prevData => [...prevData, itemData]);
}
function getItem(id: string): FormsetAtomicData<TData, TValue> { function getItem(id: string): FormsetAtomicData<TData, TValue> {
return data.find(item => item.id === id); return data.find(item => item.id === id);
} }
function removeItem(id: string) {
setData(prevData =>
removeAtIndex(
prevData,
prevData.findIndex(item => item.id === id)
)
);
}
function setItemValue(id: string, value: TValue) { function setItemValue(id: string, value: TValue) {
const itemIndex = data.findIndex(item => item.id === id); const itemIndex = data.findIndex(item => item.id === id);
setData([ setData([
@ -41,9 +57,11 @@ function useFormset<TData = object, TValue = any>(
} }
return { return {
add: addItem,
change: setItemValue, change: setItemValue,
data, data,
get: getItem, get: getItem,
remove: removeItem,
set: setData set: setData
}; };
} }

View file

@ -338,7 +338,10 @@ const OrderFulfillPage: React.FC<OrderFulfillPageProps> = props => {
warehouseStock.quantityAllocated; warehouseStock.quantityAllocated;
return ( return (
<TableCell className={classes.colQuantity}> <TableCell
className={classes.colQuantity}
key={warehouseStock.id}
>
<div className={classes.colQuantityContent}> <div className={classes.colQuantityContent}>
<TextField <TextField
type="number" type="number"

View file

@ -86,7 +86,6 @@ interface ProductCreatePageProps {
fetchCategories: (data: string) => void; fetchCategories: (data: string) => void;
fetchCollections: (data: string) => void; fetchCollections: (data: string) => void;
fetchProductTypes: (data: string) => void; fetchProductTypes: (data: string) => void;
onWarehouseEdit: () => void;
onBack?(); onBack?();
onSubmit?(data: ProductCreatePageSubmitData); onSubmit?(data: ProductCreatePageSubmitData);
} }
@ -108,8 +107,7 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
warehouses, warehouses,
onBack, onBack,
fetchProductTypes, fetchProductTypes,
onSubmit, onSubmit
onWarehouseEdit
}: ProductCreatePageProps) => { }: ProductCreatePageProps) => {
const intl = useIntl(); const intl = useIntl();
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
@ -119,18 +117,12 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
data: attributes, data: attributes,
set: setAttributeData set: setAttributeData
} = useFormset<ProductAttributeInputData>([]); } = useFormset<ProductAttributeInputData>([]);
const { change: changeStockData, data: stocks, set: setStocks } = useFormset< const {
null add: addStock,
>([]); change: changeStockData,
React.useEffect(() => { data: stocks,
const newStocks = warehouses.map(warehouse => ({ remove: removeStock
data: null, } = useFormset<null, string>([]);
id: warehouse.id,
label: warehouse.name,
value: stocks.find(stock => stock.id === warehouse.id)?.value || 0
}));
setStocks(newStocks);
}, [JSON.stringify(warehouses)]);
// Ensures that it will not change after component rerenders, because it // Ensures that it will not change after component rerenders, because it
// generates different block keys and it causes editor to lose its content. // generates different block keys and it causes editor to lose its content.
@ -253,11 +245,29 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
<ProductStocks <ProductStocks
data={data} data={data}
disabled={disabled} disabled={disabled}
onChange={changeStockData}
onFormDataChange={change} onFormDataChange={change}
errors={errors} errors={errors}
stocks={stocks} stocks={stocks}
onWarehousesEdit={onWarehouseEdit} warehouses={warehouses}
onChange={(id, value) => {
triggerChange();
changeStockData(id, value);
}}
onWarehouseStockAdd={id => {
triggerChange();
addStock({
data: null,
id,
label: warehouses.find(
warehouse => warehouse.id === id
).name,
value: "0"
});
}}
onWarehouseStockDelete={id => {
triggerChange();
removeStock(id);
}}
/> />
<CardSpacer /> <CardSpacer />
</> </>

View file

@ -1,4 +1,8 @@
import Button from "@material-ui/core/Button"; import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import Grow from "@material-ui/core/Grow";
import Popper from "@material-ui/core/Popper";
import { fade } from "@material-ui/core/styles/colorManipulator";
import IconButton from "@material-ui/core/IconButton";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import Table from "@material-ui/core/Table"; import Table from "@material-ui/core/Table";
@ -11,6 +15,10 @@ import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import { useIntl, FormattedMessage } from "react-intl"; import { useIntl, FormattedMessage } from "react-intl";
import makeStyles from "@material-ui/core/styles/makeStyles"; import makeStyles from "@material-ui/core/styles/makeStyles";
import AddIcon from "@material-ui/icons/Add";
import DeleteIcon from "@material-ui/icons/Delete";
import Paper from "@material-ui/core/Paper";
import MenuItem from "@material-ui/core/MenuItem";
import { FormChange } from "@saleor/hooks/useForm"; import { FormChange } from "@saleor/hooks/useForm";
import { FormsetChange, FormsetAtomicData } from "@saleor/hooks/useFormset"; import { FormsetChange, FormsetAtomicData } from "@saleor/hooks/useFormset";
@ -21,7 +29,8 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import { renderCollection } from "@saleor/misc"; import { renderCollection } from "@saleor/misc";
import Link from "@saleor/components/Link"; import { ICONBUTTON_SIZE } from "@saleor/theme";
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
export type ProductStockInput = FormsetAtomicData<null, string>; export type ProductStockInput = FormsetAtomicData<null, string>;
export interface ProductStockFormData { export interface ProductStockFormData {
@ -34,13 +43,19 @@ export interface ProductStocksProps {
disabled: boolean; disabled: boolean;
errors: UserError[]; errors: UserError[];
stocks: ProductStockInput[]; stocks: ProductStockInput[];
warehouses: WarehouseFragment[];
onChange: FormsetChange; onChange: FormsetChange;
onFormDataChange: FormChange; onFormDataChange: FormChange;
onWarehousesEdit: () => void; onWarehouseStockAdd: (warehouseId: string) => void;
onWarehouseStockDelete: (warehouseId: string) => void;
} }
const useStyles = makeStyles( const useStyles = makeStyles(
theme => ({ theme => ({
colAction: {
padding: 0,
width: ICONBUTTON_SIZE + theme.spacing()
},
colName: {}, colName: {},
colQuantity: { colQuantity: {
textAlign: "right", textAlign: "right",
@ -56,6 +71,19 @@ const useStyles = makeStyles(
inputComponent: { inputComponent: {
width: 100 width: 100
}, },
menuItem: {
"&:not(:last-of-type)": {
marginBottom: theme.spacing(2)
}
},
paper: {
padding: theme.spacing(2)
},
popper: {
boxShadow: `0px 5px 10px 0 ${fade(theme.palette.common.black, 0.05)}`,
marginTop: theme.spacing(1),
zIndex: 2
},
quantityContainer: { quantityContainer: {
paddingTop: theme.spacing() paddingTop: theme.spacing()
}, },
@ -80,12 +108,20 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
disabled, disabled,
errors, errors,
stocks, stocks,
warehouses,
onChange, onChange,
onFormDataChange, onFormDataChange,
onWarehousesEdit onWarehouseStockAdd,
onWarehouseStockDelete
}) => { }) => {
const classes = useStyles({}); const classes = useStyles({});
const intl = useIntl(); const intl = useIntl();
const anchor = React.useRef<HTMLDivElement>();
const [isExpanded, setExpansionState] = React.useState(false);
const warehousesToAssign = warehouses.filter(
warehouse => !stocks.some(stock => stock.id === warehouse.id)
);
return ( return (
<Card> <Card>
@ -140,17 +176,6 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
description="header" description="header"
/> />
</span> </span>
<Button
className={classes.editWarehouses}
color="primary"
data-cy="edit-warehouses"
onClick={onWarehousesEdit}
>
<FormattedMessage
defaultMessage="Edit Warehouses"
description="button"
/>
</Button>
</div> </div>
</Typography> </Typography>
</CardContent> </CardContent>
@ -169,12 +194,11 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
description="tabel column header" description="tabel column header"
/> />
</TableCell> </TableCell>
<TableCell className={classes.colAction} />
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{renderCollection( {renderCollection(stocks, stock => (
stocks,
stock => (
<TableRow key={stock.id}> <TableRow key={stock.id}>
<TableCell className={classes.colName}>{stock.label}</TableCell> <TableCell className={classes.colName}>{stock.label}</TableCell>
<TableCell className={classes.colQuantity}> <TableCell className={classes.colQuantity}>
@ -191,22 +215,68 @@ const ProductStocks: React.FC<ProductStocksProps> = ({
value={stock.value} value={stock.value}
/> />
</TableCell> </TableCell>
</TableRow> <TableCell className={classes.colAction}>
), <IconButton
() => ( color="primary"
<TableRow> onClick={() => onWarehouseStockDelete(stock.id)}
<TableCell colSpan={2}> >
<FormattedMessage <DeleteIcon />
defaultMessage={ </IconButton>
"This product doesn't have any stock. You can add it <l>here</l>." </TableCell>
} </TableRow>
values={{ ))}
l: str => <Link onClick={onWarehousesEdit}>{str}</Link> {warehousesToAssign.length > 0 && (
}} <TableRow>
/> <TableCell colSpan={2}>
<Typography variant="body2">
<FormattedMessage
defaultMessage="Assign Warehouse"
description="button"
/>
</Typography>
</TableCell>
<TableCell className={classes.colAction}>
<ClickAwayListener onClickAway={() => setExpansionState(false)}>
<div ref={anchor}>
<IconButton
color="primary"
onClick={() => setExpansionState(!isExpanded)}
>
<AddIcon />
</IconButton>
<Popper
className={classes.popper}
open={isExpanded}
anchorEl={anchor.current}
transition
placement="top-end"
>
{({ TransitionProps }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: "right top"
}}
>
<Paper className={classes.paper}>
{warehousesToAssign.map(warehouse => (
<MenuItem
className={classes.menuItem}
onClick={() =>
onWarehouseStockAdd(warehouse.id)
}
>
{warehouse.name}
</MenuItem>
))}
</Paper>
</Grow>
)}
</Popper>
</div>
</ClickAwayListener>
</TableCell> </TableCell>
</TableRow> </TableRow>
)
)} )}
</TableBody> </TableBody>
</Table> </Table>

View file

@ -1,4 +1,5 @@
import { convertFromRaw, RawDraftContentState } from "draft-js"; import { convertFromRaw, RawDraftContentState } from "draft-js";
import { diff } from "fast-array-diff";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -23,6 +24,7 @@ import { FetchMoreProps, ListActions } from "@saleor/types";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment";
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
import { import {
ProductDetails_product, ProductDetails_product,
ProductDetails_product_images, ProductDetails_product_images,
@ -62,9 +64,9 @@ export interface ProductUpdatePageProps extends ListActions {
product: ProductDetails_product; product: ProductDetails_product;
header: string; header: string;
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
warehouses: WarehouseFragment[];
fetchCategories: (query: string) => void; fetchCategories: (query: string) => void;
fetchCollections: (query: string) => void; fetchCollections: (query: string) => void;
onWarehousesEdit: () => void;
onVariantsAdd: () => void; onVariantsAdd: () => void;
onVariantShow: (id: string) => () => void; onVariantShow: (id: string) => () => void;
onImageDelete: (id: string) => () => void; onImageDelete: (id: string) => () => void;
@ -81,7 +83,9 @@ export interface ProductUpdatePageProps extends ListActions {
export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData { export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData {
attributes: ProductAttributeInput[]; attributes: ProductAttributeInput[];
collections: string[]; collections: string[];
stocks: ProductStockInput[]; addStocks: ProductStockInput[];
updateStocks: ProductStockInput[];
removeStocks: string[];
} }
export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
@ -99,6 +103,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
product, product,
saveButtonBarState, saveButtonBarState,
variants, variants,
warehouses,
onBack, onBack,
onDelete, onDelete,
onImageDelete, onImageDelete,
@ -110,7 +115,6 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onVariantAdd, onVariantAdd,
onVariantsAdd, onVariantsAdd,
onVariantShow, onVariantShow,
onWarehousesEdit,
isChecked, isChecked,
selected, selected,
toggle, toggle,
@ -129,7 +133,12 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
const { change: changeAttributeData, data: attributes } = useFormset( const { change: changeAttributeData, data: attributes } = useFormset(
attributeInput attributeInput
); );
const { change: changeStockData, data: stocks } = useFormset(stockInput); const {
add: addStock,
change: changeStockData,
data: stocks,
remove: removeStock
} = useFormset(stockInput);
const [selectedAttributes, setSelectedAttributes] = useStateFromProps< const [selectedAttributes, setSelectedAttributes] = useStateFromProps<
ProductAttributeValueChoices[] ProductAttributeValueChoices[]
@ -153,12 +162,25 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
const currency = maybe(() => product.basePrice.currency); const currency = maybe(() => product.basePrice.currency);
const hasVariants = maybe(() => product.productType.hasVariants, false); const hasVariants = maybe(() => product.productType.hasVariants, false);
const handleSubmit = (data: ProductUpdatePageFormData) => const handleSubmit = (data: ProductUpdatePageFormData) => {
const dataStocks = stocks.map(stock => stock.id);
const variantStocks = product.variants[0].stocks.map(
stock => stock.warehouse.id
);
const stockDiff = diff(variantStocks, dataStocks);
onSubmit({ onSubmit({
...data,
addStocks: stocks.filter(stock =>
stockDiff.added.some(addedStock => addedStock === stock.id)
),
attributes, attributes,
stocks, removeStocks: stockDiff.removed,
...data updateStocks: stocks.filter(
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
)
}); });
};
return ( return (
<Form onSubmit={handleSubmit} initial={initialData} confirmLeave> <Form onSubmit={handleSubmit} initial={initialData} confirmLeave>
@ -252,12 +274,27 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
disabled={disabled} disabled={disabled}
errors={errors} errors={errors}
stocks={stocks} stocks={stocks}
warehouses={warehouses}
onChange={(id, value) => { onChange={(id, value) => {
triggerChange(); triggerChange();
changeStockData(id, value); changeStockData(id, value);
}} }}
onFormDataChange={change} onFormDataChange={change}
onWarehousesEdit={onWarehousesEdit} onWarehouseStockAdd={id => {
triggerChange();
addStock({
data: null,
id,
label: warehouses.find(
warehouse => warehouse.id === id
).name,
value: "0"
});
}}
onWarehouseStockDelete={id => {
triggerChange();
removeStock(id);
}}
/> />
)} )}
<CardSpacer /> <CardSpacer />

View file

@ -51,7 +51,6 @@ interface ProductVariantCreatePageProps {
onBack: () => void; onBack: () => void;
onSubmit: (data: ProductVariantCreatePageSubmitData) => void; onSubmit: (data: ProductVariantCreatePageSubmitData) => void;
onVariantClick: (variantId: string) => void; onVariantClick: (variantId: string) => void;
onWarehouseEdit: () => void;
} }
const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
@ -64,8 +63,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
warehouses, warehouses,
onBack, onBack,
onSubmit, onSubmit,
onVariantClick, onVariantClick
onWarehouseEdit
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const attributeInput = React.useMemo( const attributeInput = React.useMemo(
@ -75,18 +73,12 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
const { change: changeAttributeData, data: attributes } = useFormset( const { change: changeAttributeData, data: attributes } = useFormset(
attributeInput attributeInput
); );
const { change: changeStockData, data: stocks, set: setStocks } = useFormset< const {
null add: addStock,
>([]); change: changeStockData,
React.useEffect(() => { data: stocks,
const newStocks = warehouses.map(warehouse => ({ remove: removeStock
data: null, } = useFormset<null, string>([]);
id: warehouse.id,
label: warehouse.name,
value: stocks.find(stock => stock.id === warehouse.id)?.value || 0
}));
setStocks(newStocks);
}, [JSON.stringify(warehouses)]);
const initialForm: ProductVariantCreatePageFormData = { const initialForm: ProductVariantCreatePageFormData = {
costPrice: "", costPrice: "",
@ -148,11 +140,28 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
<ProductStocks <ProductStocks
data={data} data={data}
disabled={disabled} disabled={disabled}
onChange={changeStockData}
onFormDataChange={change} onFormDataChange={change}
errors={errors} errors={errors}
stocks={stocks} stocks={stocks}
onWarehousesEdit={onWarehouseEdit} warehouses={warehouses}
onChange={(id, value) => {
triggerChange();
changeStockData(id, value);
}}
onWarehouseStockAdd={id => {
triggerChange();
addStock({
data: null,
id,
label: warehouses.find(warehouse => warehouse.id === id)
.name,
value: "0"
});
}}
onWarehouseStockDelete={id => {
triggerChange();
removeStock(id);
}}
/> />
</div> </div>
</Grid> </Grid>

View file

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { diff } from "fast-array-diff";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
@ -17,6 +18,7 @@ import {
getAttributeInputFromVariant, getAttributeInputFromVariant,
getStockInputFromVariant getStockInputFromVariant
} from "@saleor/products/utils/data"; } from "@saleor/products/utils/data";
import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { ProductVariant } from "../../types/ProductVariant"; import { ProductVariant } from "../../types/ProductVariant";
import ProductVariantAttributes, { import ProductVariantAttributes, {
@ -38,7 +40,9 @@ export interface ProductVariantPageFormData {
export interface ProductVariantPageSubmitData export interface ProductVariantPageSubmitData
extends ProductVariantPageFormData { extends ProductVariantPageFormData {
attributes: FormsetData<VariantAttributeInputData, string>; attributes: FormsetData<VariantAttributeInputData, string>;
stocks: ProductStockInput[]; addStocks: ProductStockInput[];
updateStocks: ProductStockInput[];
removeStocks: string[];
} }
interface ProductVariantPageProps { interface ProductVariantPageProps {
@ -48,7 +52,7 @@ interface ProductVariantPageProps {
loading?: boolean; loading?: boolean;
placeholderImage?: string; placeholderImage?: string;
header: string; header: string;
onWarehousesEdit: () => void; warehouses: WarehouseFragment[];
onAdd(); onAdd();
onBack(); onBack();
onDelete(); onDelete();
@ -64,12 +68,12 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
placeholderImage, placeholderImage,
saveButtonBarState, saveButtonBarState,
variant, variant,
warehouses,
onAdd, onAdd,
onBack, onBack,
onDelete, onDelete,
onImageSelect, onImageSelect,
onSubmit, onSubmit,
onWarehousesEdit,
onVariantClick onVariantClick
}) => { }) => {
const attributeInput = React.useMemo( const attributeInput = React.useMemo(
@ -82,7 +86,12 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
const { change: changeAttributeData, data: attributes } = useFormset( const { change: changeAttributeData, data: attributes } = useFormset(
attributeInput attributeInput
); );
const { change: changeStockData, data: stocks } = useFormset(stockInput); const {
add: addStock,
change: changeStockData,
data: stocks,
remove: removeStock
} = useFormset(stockInput);
const [isModalOpened, setModalStatus] = React.useState(false); const [isModalOpened, setModalStatus] = React.useState(false);
const toggleModal = () => setModalStatus(!isModalOpened); const toggleModal = () => setModalStatus(!isModalOpened);
@ -106,12 +115,23 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
trackInventory: variant?.trackInventory trackInventory: variant?.trackInventory
}; };
const handleSubmit = (data: ProductVariantPageFormData) => const handleSubmit = (data: ProductVariantPageFormData) => {
const dataStocks = stocks.map(stock => stock.id);
const variantStocks = variant.stocks.map(stock => stock.warehouse.id);
const stockDiff = diff(variantStocks, dataStocks);
onSubmit({ onSubmit({
...data, ...data,
addStocks: stocks.filter(stock =>
stockDiff.added.some(addedStock => addedStock === stock.id)
),
attributes, attributes,
stocks removeStocks: stockDiff.removed,
updateStocks: stocks.filter(
stock => !stockDiff.added.some(addedStock => addedStock === stock.id)
)
}); });
};
return ( return (
<> <>
@ -180,12 +200,27 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
disabled={loading} disabled={loading}
errors={errors} errors={errors}
stocks={stocks} stocks={stocks}
warehouses={warehouses}
onChange={(id, value) => { onChange={(id, value) => {
triggerChange(); triggerChange();
changeStockData(id, value); changeStockData(id, value);
}} }}
onFormDataChange={change} onFormDataChange={change}
onWarehousesEdit={onWarehousesEdit} onWarehouseStockAdd={id => {
triggerChange();
addStock({
data: null,
id,
label: warehouses.find(
warehouse => warehouse.id === id
).name,
value: "0"
});
}}
onWarehouseStockDelete={id => {
triggerChange();
removeStock(id);
}}
/> />
</div> </div>
</Grid> </Grid>

View file

@ -1,46 +0,0 @@
import { storiesOf } from "@storybook/react";
import React from "react";
import Decorator from "@saleor/storybook//Decorator";
import { warehouseList } from "@saleor/warehouses/fixtures";
import { StockErrorCode } from "@saleor/types/globalTypes";
import ProductWarehousesDialog, {
ProductWarehousesDialogProps
} from "./ProductWarehousesDialog";
const props: ProductWarehousesDialogProps = {
confirmButtonState: "default",
disabled: false,
errors: [],
onClose: () => undefined,
onConfirm: () => undefined,
open: true,
warehouses: warehouseList,
warehousesWithStocks: [warehouseList[0].id, warehouseList[2].id]
};
storiesOf("Views / Products / Edit warehouses", module)
.addDecorator(Decorator)
.add("default", () => <ProductWarehousesDialog {...props} />)
.add("loading warehouses", () => (
<ProductWarehousesDialog {...props} warehouses={undefined} />
))
.add("loading confirmation", () => (
<ProductWarehousesDialog
{...props}
confirmButtonState="loading"
disabled={true}
/>
))
.add("with error", () => (
<ProductWarehousesDialog
{...props}
errors={[
{
__typename: "StockError",
code: StockErrorCode.INVALID,
field: null
}
]}
/>
));

View file

@ -1,152 +0,0 @@
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 Typography from "@material-ui/core/Typography";
import React from "react";
import { FormattedMessage, useIntl, IntlShape } from "react-intl";
import makeStyles from "@material-ui/core/styles/makeStyles";
import { diff, DiffData } from "fast-array-diff";
import ConfirmButton, {
ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton";
import { buttonMessages } from "@saleor/intl";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import Skeleton from "@saleor/components/Skeleton";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import { isSelected, toggle } from "@saleor/utils/lists";
import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { BulkStockErrorFragment } from "@saleor/products/types/BulkStockErrorFragment";
import { StockErrorFragment } from "@saleor/products/types/StockErrorFragment";
import getStockErrorMessage, {
getBulkStockErrorMessage
} from "@saleor/utils/errors/stock";
const useStyles = makeStyles(
theme => ({
dropShadow: {
boxShadow: `0px -5px 10px 0px ${theme.palette.divider}`
},
errorParagraph: {
paddingTop: 0
},
helperText: {
marginBottom: theme.spacing(1)
}
}),
{
name: "ProductWarehousesDialog"
}
);
export interface ProductWarehousesDialogProps {
confirmButtonState: ConfirmButtonTransitionState;
disabled: boolean;
errors: Array<BulkStockErrorFragment | StockErrorFragment>;
open: boolean;
warehouses: SearchWarehouses_search_edges_node[];
warehousesWithStocks: string[];
onClose: () => void;
onConfirm: (data: DiffData<string>) => void;
}
function getErrorMessage(
err: BulkStockErrorFragment | StockErrorFragment,
intl: IntlShape
): string {
switch (err?.__typename) {
case "BulkStockError":
return getBulkStockErrorMessage(err, intl);
default:
return getStockErrorMessage(err, intl);
}
}
const ProductWarehousesDialog: React.FC<ProductWarehousesDialogProps> = ({
confirmButtonState,
disabled,
errors,
onClose,
onConfirm,
open,
warehousesWithStocks,
warehouses
}) => {
const classes = useStyles({});
const intl = useIntl();
const [selectedWarehouses, setSelectedWarehouses] = useStateFromProps(
warehousesWithStocks || []
);
const handleConfirm = () =>
onConfirm(diff(warehousesWithStocks, selectedWarehouses));
return (
<Dialog onClose={onClose} maxWidth="sm" fullWidth open={open}>
<DialogTitle>
<FormattedMessage
defaultMessage="Edit Warehouses"
description="dialog header"
/>
</DialogTitle>
<form>
<DialogContent>
<Typography className={classes.helperText}>
<FormattedMessage defaultMessage="Select warehouses that stock selected product" />
</Typography>
{warehouses === undefined ? (
<Skeleton />
) : (
warehouses.map(warehouse => (
<div key={warehouse.id}>
<ControlledCheckbox
checked={isSelected(
warehouse.id,
selectedWarehouses,
(a, b) => a === b
)}
name={`warehouse:${warehouse.id}`}
onChange={() =>
setSelectedWarehouses(
toggle(
warehouse.id,
selectedWarehouses,
(a, b) => a === b
)
)
}
disabled={disabled}
label={warehouse.name}
/>
</div>
))
)}
</DialogContent>
{errors.length > 0 && (
<DialogContent className={classes.errorParagraph}>
<Typography color="error">
{getErrorMessage(errors[0], intl)}
</Typography>
</DialogContent>
)}
<DialogActions>
<Button onClick={onClose}>
<FormattedMessage {...buttonMessages.back} />
</Button>
<ConfirmButton
transitionState={confirmButtonState}
onClick={handleConfirm}
>
<FormattedMessage {...buttonMessages.save} />
</ConfirmButton>
</DialogActions>
</form>
</Dialog>
);
};
ProductWarehousesDialog.displayName = "ProductWarehousesDialog";
export default ProductWarehousesDialog;

View file

@ -1,2 +0,0 @@
export { default } from "./ProductWarehousesDialog";
export * from "./ProductWarehousesDialog";

View file

@ -19,11 +19,9 @@ import {
productVariantAddPath, productVariantAddPath,
productVariantEditPath, productVariantEditPath,
ProductVariantEditUrlQueryParams, ProductVariantEditUrlQueryParams,
ProductAddUrlQueryParams,
ProductVariantAddUrlQueryParams,
productVariantCreatorPath productVariantCreatorPath
} from "./urls"; } from "./urls";
import ProductCreateComponent from "./views/ProductCreate"; import ProductCreate from "./views/ProductCreate";
import ProductImageComponent from "./views/ProductImage"; import ProductImageComponent from "./views/ProductImage";
import ProductListComponent from "./views/ProductList"; import ProductListComponent from "./views/ProductList";
import ProductUpdateComponent from "./views/ProductUpdate"; import ProductUpdateComponent from "./views/ProductUpdate";
@ -90,17 +88,11 @@ const ProductImage: React.FC<RouteComponentProps<any>> = ({
const ProductVariantCreate: React.FC<RouteComponentProps<any>> = ({ const ProductVariantCreate: React.FC<RouteComponentProps<any>> = ({
match match
}) => { }) => (
const qs = parseQs(location.search.substr(1));
const params: ProductVariantAddUrlQueryParams = qs;
return (
<ProductVariantCreateComponent <ProductVariantCreateComponent
productId={decodeURIComponent(match.params.id)} productId={decodeURIComponent(match.params.id)}
params={params}
/> />
); );
};
const ProductVariantCreator: React.FC<RouteComponentProps<{ const ProductVariantCreator: React.FC<RouteComponentProps<{
id: string; id: string;
@ -108,13 +100,6 @@ const ProductVariantCreator: React.FC<RouteComponentProps<{
<ProductVariantCreatorComponent id={decodeURIComponent(match.params.id)} /> <ProductVariantCreatorComponent id={decodeURIComponent(match.params.id)} />
); );
const ProductCreate: React.FC<RouteComponentProps> = ({ location }) => {
const qs = parseQs(location.search.substr(1));
const params: ProductAddUrlQueryParams = qs;
return <ProductCreateComponent params={params} />;
};
const Component = () => { const Component = () => {
const intl = useIntl(); const intl = useIntl();

View file

@ -38,11 +38,7 @@ import {
} from "./types/VariantImageUnassign"; } from "./types/VariantImageUnassign";
import { VariantUpdate, VariantUpdateVariables } from "./types/VariantUpdate"; import { VariantUpdate, VariantUpdateVariables } from "./types/VariantUpdate";
import { import { fragmentVariant, productFragmentDetails } from "./queries";
fragmentVariant,
productFragmentDetails,
stockFragment
} from "./queries";
import { import {
productBulkDelete, productBulkDelete,
productBulkDeleteVariables productBulkDeleteVariables
@ -59,10 +55,6 @@ import {
ProductVariantBulkDelete, ProductVariantBulkDelete,
ProductVariantBulkDeleteVariables ProductVariantBulkDeleteVariables
} from "./types/ProductVariantBulkDelete"; } from "./types/ProductVariantBulkDelete";
import {
AddOrRemoveStocks,
AddOrRemoveStocksVariables
} from "./types/AddOrRemoveStocks";
export const bulkProductErrorFragment = gql` export const bulkProductErrorFragment = gql`
fragment BulkProductErrorFragment on BulkProductError { fragment BulkProductErrorFragment on BulkProductError {
@ -359,6 +351,8 @@ export const variantUpdateMutation = gql`
${fragmentVariant} ${fragmentVariant}
${productErrorFragment} ${productErrorFragment}
mutation VariantUpdate( mutation VariantUpdate(
$addStocks: [StockInput!]!
$removeStocks: [ID!]!
$id: ID! $id: ID!
$attributes: [AttributeValueInput] $attributes: [AttributeValueInput]
$costPrice: Decimal $costPrice: Decimal
@ -392,6 +386,29 @@ export const variantUpdateMutation = gql`
...ProductVariant ...ProductVariant
} }
} }
productVariantStocksCreate(stocks: $addStocks, variantId: $id) {
errors: bulkStockErrors {
...BulkStockErrorFragment
}
productVariant {
id
stocks {
...StockFragment
}
}
}
productVariantStocksDelete(warehouseIds: $removeStocks, variantId: $id) {
errors: stockErrors {
code
field
}
productVariant {
id
stocks {
...StockFragment
}
}
}
} }
`; `;
export const TypedVariantUpdateMutation = TypedMutation< export const TypedVariantUpdateMutation = TypedMutation<
@ -558,41 +575,3 @@ export const TypedProductVariantBulkDeleteMutation = TypedMutation<
ProductVariantBulkDelete, ProductVariantBulkDelete,
ProductVariantBulkDeleteVariables ProductVariantBulkDeleteVariables
>(ProductVariantBulkDeleteMutation); >(ProductVariantBulkDeleteMutation);
const addOrRemoveStocks = gql`
${bulkStockErrorFragment}
${stockFragment}
mutation AddOrRemoveStocks(
$variantId: ID!
$add: [StockInput!]!
$remove: [ID!]!
) {
productVariantStocksCreate(stocks: $add, variantId: $variantId) {
errors: bulkStockErrors {
...BulkStockErrorFragment
}
productVariant {
id
stocks {
...StockFragment
}
}
}
productVariantStocksDelete(warehouseIds: $remove, variantId: $variantId) {
errors: stockErrors {
code
field
}
productVariant {
id
stocks {
...StockFragment
}
}
}
}
`;
export const useAddOrRemoveStocks = makeMutation<
AddOrRemoveStocks,
AddOrRemoveStocksVariables
>(addOrRemoveStocks);

View file

@ -1,85 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
import { StockInput, ProductErrorCode, StockErrorCode } from "./../../types/globalTypes";
// ====================================================
// GraphQL mutation operation: AddOrRemoveStocks
// ====================================================
export interface AddOrRemoveStocks_productVariantStocksCreate_errors {
__typename: "BulkStockError";
code: ProductErrorCode;
field: string | null;
index: number | null;
}
export interface AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks_warehouse {
__typename: "Warehouse";
id: string;
name: string;
}
export interface AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks {
__typename: "Stock";
id: string;
quantity: number;
quantityAllocated: number;
warehouse: AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks_warehouse;
}
export interface AddOrRemoveStocks_productVariantStocksCreate_productVariant {
__typename: "ProductVariant";
id: string;
stocks: (AddOrRemoveStocks_productVariantStocksCreate_productVariant_stocks | null)[] | null;
}
export interface AddOrRemoveStocks_productVariantStocksCreate {
__typename: "ProductVariantStocksCreate";
errors: AddOrRemoveStocks_productVariantStocksCreate_errors[];
productVariant: AddOrRemoveStocks_productVariantStocksCreate_productVariant | null;
}
export interface AddOrRemoveStocks_productVariantStocksDelete_errors {
__typename: "StockError";
code: StockErrorCode;
field: string | null;
}
export interface AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks_warehouse {
__typename: "Warehouse";
id: string;
name: string;
}
export interface AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks {
__typename: "Stock";
id: string;
quantity: number;
quantityAllocated: number;
warehouse: AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks_warehouse;
}
export interface AddOrRemoveStocks_productVariantStocksDelete_productVariant {
__typename: "ProductVariant";
id: string;
stocks: (AddOrRemoveStocks_productVariantStocksDelete_productVariant_stocks | null)[] | null;
}
export interface AddOrRemoveStocks_productVariantStocksDelete {
__typename: "ProductVariantStocksDelete";
errors: AddOrRemoveStocks_productVariantStocksDelete_errors[];
productVariant: AddOrRemoveStocks_productVariantStocksDelete_productVariant | null;
}
export interface AddOrRemoveStocks {
productVariantStocksCreate: AddOrRemoveStocks_productVariantStocksCreate | null;
productVariantStocksDelete: AddOrRemoveStocks_productVariantStocksDelete | null;
}
export interface AddOrRemoveStocksVariables {
variantId: string;
add: StockInput[];
remove: string[];
}

View file

@ -2,7 +2,7 @@
/* eslint-disable */ /* eslint-disable */
// This file was automatically generated and should not be edited. // This file was automatically generated and should not be edited.
import { AttributeValueInput, StockInput, ProductErrorCode } from "./../../types/globalTypes"; import { StockInput, AttributeValueInput, ProductErrorCode, StockErrorCode } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL mutation operation: VariantUpdate // GraphQL mutation operation: VariantUpdate
@ -255,12 +255,81 @@ export interface VariantUpdate_productVariantStocksUpdate {
productVariant: VariantUpdate_productVariantStocksUpdate_productVariant | null; productVariant: VariantUpdate_productVariantStocksUpdate_productVariant | null;
} }
export interface VariantUpdate_productVariantStocksCreate_errors {
__typename: "BulkStockError";
code: ProductErrorCode;
field: string | null;
index: number | null;
}
export interface VariantUpdate_productVariantStocksCreate_productVariant_stocks_warehouse {
__typename: "Warehouse";
id: string;
name: string;
}
export interface VariantUpdate_productVariantStocksCreate_productVariant_stocks {
__typename: "Stock";
id: string;
quantity: number;
quantityAllocated: number;
warehouse: VariantUpdate_productVariantStocksCreate_productVariant_stocks_warehouse;
}
export interface VariantUpdate_productVariantStocksCreate_productVariant {
__typename: "ProductVariant";
id: string;
stocks: (VariantUpdate_productVariantStocksCreate_productVariant_stocks | null)[] | null;
}
export interface VariantUpdate_productVariantStocksCreate {
__typename: "ProductVariantStocksCreate";
errors: VariantUpdate_productVariantStocksCreate_errors[];
productVariant: VariantUpdate_productVariantStocksCreate_productVariant | null;
}
export interface VariantUpdate_productVariantStocksDelete_errors {
__typename: "StockError";
code: StockErrorCode;
field: string | null;
}
export interface VariantUpdate_productVariantStocksDelete_productVariant_stocks_warehouse {
__typename: "Warehouse";
id: string;
name: string;
}
export interface VariantUpdate_productVariantStocksDelete_productVariant_stocks {
__typename: "Stock";
id: string;
quantity: number;
quantityAllocated: number;
warehouse: VariantUpdate_productVariantStocksDelete_productVariant_stocks_warehouse;
}
export interface VariantUpdate_productVariantStocksDelete_productVariant {
__typename: "ProductVariant";
id: string;
stocks: (VariantUpdate_productVariantStocksDelete_productVariant_stocks | null)[] | null;
}
export interface VariantUpdate_productVariantStocksDelete {
__typename: "ProductVariantStocksDelete";
errors: VariantUpdate_productVariantStocksDelete_errors[];
productVariant: VariantUpdate_productVariantStocksDelete_productVariant | null;
}
export interface VariantUpdate { export interface VariantUpdate {
productVariantUpdate: VariantUpdate_productVariantUpdate | null; productVariantUpdate: VariantUpdate_productVariantUpdate | null;
productVariantStocksUpdate: VariantUpdate_productVariantStocksUpdate | null; productVariantStocksUpdate: VariantUpdate_productVariantStocksUpdate | null;
productVariantStocksCreate: VariantUpdate_productVariantStocksCreate | null;
productVariantStocksDelete: VariantUpdate_productVariantStocksDelete | null;
} }
export interface VariantUpdateVariables { export interface VariantUpdateVariables {
addStocks: StockInput[];
removeStocks: string[];
id: string; id: string;
attributes?: (AttributeValueInput | null)[] | null; attributes?: (AttributeValueInput | null)[] | null;
costPrice?: any | null; costPrice?: any | null;

View file

@ -17,10 +17,7 @@ import {
const productSection = "/products/"; const productSection = "/products/";
export const productAddPath = urlJoin(productSection, "add"); export const productAddPath = urlJoin(productSection, "add");
export type ProductAddUrlDialog = "edit-stocks"; export const productAddUrl = productAddPath;
export type ProductAddUrlQueryParams = Dialog<ProductAddUrlDialog>;
export const productAddUrl = (params?: ProductAddUrlQueryParams): string =>
productAddPath + "?" + stringifyQs(params);
export const productListPath = productSection; export const productListPath = productSection;
export type ProductListUrlDialog = export type ProductListUrlDialog =
@ -69,14 +66,14 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string =>
productListPath + "?" + stringifyQs(params); productListPath + "?" + stringifyQs(params);
export const productPath = (id: string) => urlJoin(productSection + id); export const productPath = (id: string) => urlJoin(productSection + id);
export type ProductUrlDialog = "edit-stocks" | "remove" | "remove-variants"; export type ProductUrlDialog = "remove" | "remove-variants";
export type ProductUrlQueryParams = BulkAction & Dialog<ProductUrlDialog>; export type ProductUrlQueryParams = BulkAction & Dialog<ProductUrlDialog>;
export const productUrl = (id: string, params?: ProductUrlQueryParams) => export const productUrl = (id: string, params?: ProductUrlQueryParams) =>
productPath(encodeURIComponent(id)) + "?" + stringifyQs(params); productPath(encodeURIComponent(id)) + "?" + stringifyQs(params);
export const productVariantEditPath = (productId: string, variantId: string) => export const productVariantEditPath = (productId: string, variantId: string) =>
urlJoin(productSection, productId, "variant", variantId); urlJoin(productSection, productId, "variant", variantId);
export type ProductVariantEditUrlDialog = "edit-stocks" | "remove"; export type ProductVariantEditUrlDialog = "remove";
export type ProductVariantEditUrlQueryParams = Dialog< export type ProductVariantEditUrlQueryParams = Dialog<
ProductVariantEditUrlDialog ProductVariantEditUrlDialog
>; >;
@ -99,17 +96,8 @@ export const productVariantCreatorUrl = (productId: string) =>
export const productVariantAddPath = (productId: string) => export const productVariantAddPath = (productId: string) =>
urlJoin(productSection, productId, "variant/add"); urlJoin(productSection, productId, "variant/add");
export type ProductVariantAddUrlDialog = "edit-stocks"; export const productVariantAddUrl = (productId: string): string =>
export type ProductVariantAddUrlQueryParams = Dialog< productVariantAddPath(encodeURIComponent(productId));
ProductVariantAddUrlDialog
>;
export const productVariantAddUrl = (
productId: string,
params?: ProductVariantAddUrlQueryParams
): string =>
productVariantAddPath(encodeURIComponent(productId)) +
"?" +
stringifyQs(params);
export const productImagePath = (productId: string, imageId: string) => export const productImagePath = (productId: string, imageId: string) =>
urlJoin(productSection, productId, "image", imageId); urlJoin(productSection, productId, "image", imageId);

View file

@ -9,6 +9,8 @@ import {
ProductDetails_product_variants ProductDetails_product_variants
} from "@saleor/products/types/ProductDetails"; } from "@saleor/products/types/ProductDetails";
import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes"; import { SearchProductTypes_search_edges_node_productAttributes } from "@saleor/searches/types/SearchProductTypes";
import { StockInput } from "@saleor/types/globalTypes";
import { FormsetAtomicData } from "@saleor/hooks/useFormset";
import { ProductAttributeInput } from "../components/ProductAttributes"; import { ProductAttributeInput } from "../components/ProductAttributes";
import { VariantAttributeInput } from "../components/ProductVariantAttributes"; import { VariantAttributeInput } from "../components/ProductVariantAttributes";
import { ProductVariant } from "../types/ProductVariant"; import { ProductVariant } from "../types/ProductVariant";
@ -211,3 +213,12 @@ export function getProductUpdatePageFormData(
trackInventory: !!product?.variants[0]?.trackInventory trackInventory: !!product?.variants[0]?.trackInventory
}; };
} }
export function mapFormsetStockToStockInput(
stock: FormsetAtomicData<null, string>
): StockInput {
return {
quantity: parseInt(stock.value, 10),
warehouse: stock.id
};
}

View file

@ -9,31 +9,16 @@ import useShop from "@saleor/hooks/useShop";
import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import useProductTypeSearch from "@saleor/searches/useProductTypeSearch"; import useProductTypeSearch from "@saleor/searches/useProductTypeSearch";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch"; import { useWarehouseList } from "@saleor/warehouses/queries";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { decimal, maybe } from "../../misc"; import { decimal, maybe } from "../../misc";
import ProductCreatePage, { import ProductCreatePage, {
ProductCreatePageSubmitData ProductCreatePageSubmitData
} from "../components/ProductCreatePage"; } from "../components/ProductCreatePage";
import { TypedProductCreateMutation } from "../mutations"; import { TypedProductCreateMutation } from "../mutations";
import { ProductCreate } from "../types/ProductCreate"; import { ProductCreate } from "../types/ProductCreate";
import { import { productListUrl, productUrl } from "../urls";
productListUrl,
productUrl,
ProductAddUrlDialog,
ProductAddUrlQueryParams,
productAddUrl
} from "../urls";
import ProductWarehousesDialog from "../components/ProductWarehousesDialog";
interface ProductCreateViewProps { export const ProductCreateView: React.FC = () => {
params: ProductAddUrlQueryParams;
}
export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
params
}) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const shop = useShop(); const shop = useShop();
@ -59,20 +44,12 @@ export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
} = useProductTypeSearch({ } = useProductTypeSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA variables: DEFAULT_INITIAL_SEARCH_DATA
}); });
const { result: searchWarehousesOpts } = useWarehouseSearch({ const warehouses = useWarehouseList({
displayLoader: true,
variables: { variables: {
...DEFAULT_INITIAL_SEARCH_DATA, first: 50
first: 20
} }
}); });
const [warehouses, setWarehouses] = React.useState<
SearchWarehouses_search_edges_node[]
>([]);
const [openModal, closeModal] = createDialogActionHandlers<
ProductAddUrlDialog,
ProductAddUrlQueryParams
>(navigate, productAddUrl, params);
const handleBack = () => navigate(productListUrl()); const handleBack = () => navigate(productListUrl());
@ -153,7 +130,6 @@ export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
productTypes={maybe(() => productTypes={maybe(() =>
searchProductTypesOpts.data.search.edges.map(edge => edge.node) searchProductTypesOpts.data.search.edges.map(edge => edge.node)
)} )}
warehouses={warehouses}
onBack={handleBack} onBack={handleBack}
onSubmit={handleSubmit} onSubmit={handleSubmit}
saveButtonBarState={productCreateOpts.status} saveButtonBarState={productCreateOpts.status}
@ -178,32 +154,9 @@ export const ProductCreateView: React.FC<ProductCreateViewProps> = ({
loading: searchProductTypesOpts.loading, loading: searchProductTypesOpts.loading,
onFetchMore: loadMoreProductTypes onFetchMore: loadMoreProductTypes
}} }}
onWarehouseEdit={() => openModal("edit-stocks")} warehouses={
/> warehouses.data?.warehouses.edges.map(edge => edge.node) || []
<ProductWarehousesDialog }
confirmButtonState="default"
disabled={false}
errors={[]}
onClose={closeModal}
open={params.action === "edit-stocks"}
warehouses={searchWarehousesOpts.data?.search.edges.map(
edge => edge.node
)}
warehousesWithStocks={warehouses.map(warehouse => warehouse.id)}
onConfirm={data => {
setWarehouses(
[
...warehouses,
...data.added.map(
addedId =>
searchWarehousesOpts.data.search.edges.find(
edge => edge.node.id === addedId
).node
)
].filter(warehouse => !data.removed.includes(warehouse.id))
);
closeModal();
}}
/> />
</> </>
); );

View file

@ -306,7 +306,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
.hasNextPage, .hasNextPage,
false false
)} )}
onAdd={() => navigate(productAddUrl())} onAdd={() => navigate(productAddUrl)}
disabled={loading} disabled={loading}
products={maybe(() => products={maybe(() =>
data.products.edges.map(edge => edge.node) data.products.edges.map(edge => edge.node)

View file

@ -16,9 +16,7 @@ import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import ProductWarehousesDialog from "@saleor/products/components/ProductWarehousesDialog"; import { useWarehouseList } from "@saleor/warehouses/queries";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
import { useAddOrRemoveStocks } from "@saleor/products/mutations";
import { getMutationState, maybe } from "../../../misc"; import { getMutationState, maybe } from "../../../misc";
import ProductUpdatePage from "../../components/ProductUpdatePage"; import ProductUpdatePage from "../../components/ProductUpdatePage";
import ProductUpdateOperations from "../../containers/ProductUpdateOperations"; import ProductUpdateOperations from "../../containers/ProductUpdateOperations";
@ -71,24 +69,10 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
} = useCollectionSearch({ } = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA variables: DEFAULT_INITIAL_SEARCH_DATA
}); });
const { result: searchWarehousesOpts } = useWarehouseSearch({ const warehouses = useWarehouseList({
displayLoader: true,
variables: { variables: {
...DEFAULT_INITIAL_SEARCH_DATA, first: 50
first: 20
}
});
const [addOrRemoveStocks, addOrRemoveStocksOpts] = useAddOrRemoveStocks({
onCompleted: data => {
if (
data.productVariantStocksCreate.errors.length === 0 &&
data.productVariantStocksDelete.errors.length === 0
) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
closeModal();
}
} }
}); });
@ -238,6 +222,11 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
header={maybe(() => product.name)} header={maybe(() => product.name)}
placeholderImage={placeholderImg} placeholderImage={placeholderImg}
product={product} product={product}
warehouses={
warehouses.data?.warehouses.edges.map(
edge => edge.node
) || []
}
variants={maybe(() => product.variants)} variants={maybe(() => product.variants)}
onBack={handleBack} onBack={handleBack}
onDelete={() => openModal("remove")} onDelete={() => openModal("remove")}
@ -282,7 +271,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
loading: searchCollectionsOpts.loading, loading: searchCollectionsOpts.loading,
onFetchMore: loadMoreCollections onFetchMore: loadMoreCollections
}} }}
onWarehousesEdit={() => openModal("edit-stocks")}
/> />
<ActionDialog <ActionDialog
open={params.action === "remove"} open={params.action === "remove"}
@ -333,40 +321,6 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
/> />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
{!product?.productType?.hasVariants && (
<ProductWarehousesDialog
confirmButtonState={addOrRemoveStocksOpts.status}
disabled={addOrRemoveStocksOpts.loading}
errors={[
...(addOrRemoveStocksOpts.data
?.productVariantStocksCreate.errors || []),
...(addOrRemoveStocksOpts.data
?.productVariantStocksDelete.errors || [])
]}
onClose={closeModal}
open={params.action === "edit-stocks"}
warehouses={searchWarehousesOpts.data?.search.edges.map(
edge => edge.node
)}
warehousesWithStocks={
product?.variants[0].stocks.map(
stock => stock.warehouse.id
) || []
}
onConfirm={data =>
addOrRemoveStocks({
variables: {
add: data.added.map(id => ({
quantity: 0,
warehouse: id
})),
remove: data.removed,
variantId: product.variants[0].id
}
})
}
/>
)}
</> </>
); );
}} }}

View file

@ -7,6 +7,7 @@ import { ProductUpdateVariables } from "@saleor/products/types/ProductUpdate";
import { SimpleProductUpdateVariables } from "@saleor/products/types/SimpleProductUpdate"; import { SimpleProductUpdateVariables } from "@saleor/products/types/SimpleProductUpdate";
import { ReorderEvent } from "@saleor/types"; import { ReorderEvent } from "@saleor/types";
import { arrayMove } from "react-sortable-hoc"; import { arrayMove } from "react-sortable-hoc";
import { mapFormsetStockToStockInput } from "@saleor/products/utils/data";
export function createUpdateHandler( export function createUpdateHandler(
product: ProductDetails_product, product: ProductDetails_product,
@ -40,17 +41,14 @@ export function createUpdateHandler(
} else { } else {
updateSimpleProduct({ updateSimpleProduct({
...productVariables, ...productVariables,
addStocks: [], addStocks: data.addStocks.map(mapFormsetStockToStockInput),
deleteStocks: [], deleteStocks: data.removeStocks,
productVariantId: product.variants[0].id, productVariantId: product.variants[0].id,
productVariantInput: { productVariantInput: {
sku: data.sku, sku: data.sku,
trackInventory: data.trackInventory trackInventory: data.trackInventory
}, },
updateStocks: data.stocks.map(stock => ({ updateStocks: data.updateStocks.map(mapFormsetStockToStockInput)
quantity: parseInt(stock.value, 0),
warehouse: stock.id
}))
}); });
} }
}; };

View file

@ -8,8 +8,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; import { useWarehouseList } from "@saleor/warehouses/queries";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
import { decimal } from "../../misc"; import { decimal } from "../../misc";
import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog"; import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog";
import ProductVariantPage, { import ProductVariantPage, {
@ -28,8 +27,7 @@ import {
ProductVariantEditUrlQueryParams, ProductVariantEditUrlQueryParams,
ProductVariantEditUrlDialog ProductVariantEditUrlDialog
} from "../urls"; } from "../urls";
import ProductWarehousesDialog from "../components/ProductWarehousesDialog"; import { mapFormsetStockToStockInput } from "../utils/data";
import { useAddOrRemoveStocks } from "../mutations";
interface ProductUpdateProps { interface ProductUpdateProps {
variantId: string; variantId: string;
@ -52,28 +50,14 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
setErrors([]); setErrors([]);
}, [variantId]); }, [variantId]);
const { result: searchWarehousesOpts } = useWarehouseSearch({ const warehouses = useWarehouseList({
displayLoader: true,
variables: { variables: {
...DEFAULT_INITIAL_SEARCH_DATA, first: 50
first: 20
} }
}); });
const [addOrRemoveStocks, addOrRemoveStocksOpts] = useAddOrRemoveStocks({ const [openModal] = createDialogActionHandlers<
onCompleted: data => {
if (
data.productVariantStocksCreate.errors.length === 0 &&
data.productVariantStocksDelete.errors.length === 0
) {
notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
closeModal();
}
}
});
const [openModal, closeModal] = createDialogActionHandlers<
ProductVariantEditUrlDialog, ProductVariantEditUrlDialog,
ProductVariantEditUrlQueryParams ProductVariantEditUrlQueryParams
>( >(
@ -151,18 +135,20 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
placeholderImage={placeholderImg} placeholderImage={placeholderImg}
variant={variant} variant={variant}
header={variant?.name || variant?.sku} header={variant?.name || variant?.sku}
warehouses={
warehouses.data?.warehouses.edges.map(
edge => edge.node
) || []
}
onAdd={() => navigate(productVariantAddUrl(productId))} onAdd={() => navigate(productVariantAddUrl(productId))}
onBack={handleBack} onBack={handleBack}
onDelete={() => onDelete={() => openModal("remove")}
navigate(
productVariantEditUrl(productId, variantId, {
action: "remove"
})
)
}
onImageSelect={handleImageSelect} onImageSelect={handleImageSelect}
onSubmit={(data: ProductVariantPageSubmitData) => onSubmit={(data: ProductVariantPageSubmitData) =>
updateVariant.mutate({ updateVariant.mutate({
addStocks: data.addStocks.map(
mapFormsetStockToStockInput
),
attributes: data.attributes.map(attribute => ({ attributes: data.attributes.map(attribute => ({
id: attribute.id, id: attribute.id,
values: [attribute.value] values: [attribute.value]
@ -170,18 +156,17 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
costPrice: decimal(data.costPrice), costPrice: decimal(data.costPrice),
id: variantId, id: variantId,
priceOverride: decimal(data.priceOverride), priceOverride: decimal(data.priceOverride),
removeStocks: data.removeStocks,
sku: data.sku, sku: data.sku,
stocks: data.stocks.map(stock => ({ stocks: data.updateStocks.map(
quantity: parseInt(stock.value, 10), mapFormsetStockToStockInput
warehouse: stock.id ),
})),
trackInventory: data.trackInventory trackInventory: data.trackInventory
}) })
} }
onVariantClick={variantId => { onVariantClick={variantId => {
navigate(productVariantEditUrl(productId, variantId)); navigate(productVariantEditUrl(productId, variantId));
}} }}
onWarehousesEdit={() => openModal("edit-stocks")}
/> />
<ProductVariantDeleteDialog <ProductVariantDeleteDialog
confirmButtonState={deleteVariant.opts.status} confirmButtonState={deleteVariant.opts.status}
@ -196,36 +181,6 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
open={params.action === "remove"} open={params.action === "remove"}
name={data?.productVariant?.name} name={data?.productVariant?.name}
/> />
<ProductWarehousesDialog
confirmButtonState={addOrRemoveStocksOpts.status}
disabled={addOrRemoveStocksOpts.loading}
errors={[
...(addOrRemoveStocksOpts.data?.productVariantStocksCreate
.errors || []),
...(addOrRemoveStocksOpts.data?.productVariantStocksDelete
.errors || [])
]}
onClose={closeModal}
open={params.action === "edit-stocks"}
warehouses={searchWarehousesOpts.data?.search.edges.map(
edge => edge.node
)}
warehousesWithStocks={
variant?.stocks.map(stock => stock.warehouse.id) || []
}
onConfirm={data =>
addOrRemoveStocks({
variables: {
add: data.added.map(id => ({
quantity: 0,
warehouse: id
})),
remove: data.removed,
variantId
}
})
}
/>
</> </>
); );
}} }}

View file

@ -7,10 +7,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import { useWarehouseList } from "@saleor/warehouses/queries";
import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
import { decimal } from "../../misc"; import { decimal } from "../../misc";
import ProductVariantCreatePage, { import ProductVariantCreatePage, {
ProductVariantCreatePageSubmitData ProductVariantCreatePageSubmitData
@ -18,43 +15,25 @@ import ProductVariantCreatePage, {
import { TypedVariantCreateMutation } from "../mutations"; import { TypedVariantCreateMutation } from "../mutations";
import { TypedProductVariantCreateQuery } from "../queries"; import { TypedProductVariantCreateQuery } from "../queries";
import { VariantCreate } from "../types/VariantCreate"; import { VariantCreate } from "../types/VariantCreate";
import { import { productUrl, productVariantEditUrl, productListUrl } from "../urls";
productUrl,
productVariantEditUrl,
productListUrl,
productVariantAddUrl,
ProductVariantAddUrlDialog,
ProductVariantAddUrlQueryParams
} from "../urls";
import ProductWarehousesDialog from "../components/ProductWarehousesDialog";
interface ProductVariantCreateProps { interface ProductVariantCreateProps {
params: ProductVariantAddUrlQueryParams;
productId: string; productId: string;
} }
export const ProductVariant: React.FC<ProductVariantCreateProps> = ({ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
params,
productId productId
}) => { }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const shop = useShop(); const shop = useShop();
const intl = useIntl(); const intl = useIntl();
const { result: searchWarehousesOpts } = useWarehouseSearch({ const warehouses = useWarehouseList({
displayLoader: true,
variables: { variables: {
...DEFAULT_INITIAL_SEARCH_DATA, first: 50
first: 20
} }
}); });
const [warehouses, setWarehouses] = React.useState<
SearchWarehouses_search_edges_node[]
>([]);
const [openModal, closeModal] = createDialogActionHandlers<
ProductVariantAddUrlDialog,
ProductVariantAddUrlQueryParams
>(navigate, params => productVariantAddUrl(productId, params), params);
return ( return (
<TypedProductVariantCreateQuery displayLoader variables={{ id: productId }}> <TypedProductVariantCreateQuery displayLoader variables={{ id: productId }}>
@ -136,37 +115,11 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
onSubmit={handleSubmit} onSubmit={handleSubmit}
onVariantClick={handleVariantClick} onVariantClick={handleVariantClick}
saveButtonBarState={variantCreateResult.status} saveButtonBarState={variantCreateResult.status}
warehouses={warehouses} warehouses={
onWarehouseEdit={() => openModal("edit-stocks")} warehouses.data?.warehouses.edges.map(
/>
<ProductWarehousesDialog
confirmButtonState="default"
disabled={false}
errors={[]}
onClose={closeModal}
open={params.action === "edit-stocks"}
warehouses={searchWarehousesOpts.data?.search.edges.map(
edge => edge.node edge => edge.node
)} ) || []
warehousesWithStocks={warehouses.map( }
warehouse => warehouse.id
)}
onConfirm={data => {
setWarehouses(
[
...warehouses,
...data.added.map(
addedId =>
searchWarehousesOpts.data.search.edges.find(
edge => edge.node.id === addedId
).node
)
].filter(
warehouse => !data.removed.includes(warehouse.id)
)
);
closeModal();
}}
/> />
</> </>
); );

File diff suppressed because it is too large Load diff

View file

@ -33,7 +33,6 @@ storiesOf("Views / Products / Create product", module)
onBack={() => undefined} onBack={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
onWarehouseEdit={() => undefined}
warehouses={warehouseList} warehouses={warehouseList}
/> />
)) ))
@ -55,7 +54,6 @@ storiesOf("Views / Products / Create product", module)
onBack={() => undefined} onBack={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
onWarehouseEdit={() => undefined}
warehouses={undefined} warehouses={undefined}
/> />
)) ))
@ -83,7 +81,6 @@ storiesOf("Views / Products / Create product", module)
onBack={() => undefined} onBack={() => undefined}
onSubmit={() => undefined} onSubmit={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
onWarehouseEdit={() => undefined}
warehouses={warehouseList} warehouses={warehouseList}
/> />
)); ));

View file

@ -10,6 +10,7 @@ import ProductUpdatePage, {
import { product as productFixture } from "@saleor/products/fixtures"; import { product as productFixture } from "@saleor/products/fixtures";
import { ProductUpdatePageFormData } from "@saleor/products/utils/data"; import { ProductUpdatePageFormData } from "@saleor/products/utils/data";
import { ProductErrorCode } from "@saleor/types/globalTypes"; import { ProductErrorCode } from "@saleor/types/globalTypes";
import { warehouseList } from "@saleor/warehouses/fixtures";
import Decorator from "../../Decorator"; import Decorator from "../../Decorator";
const product = productFixture(placeholderImage); const product = productFixture(placeholderImage);
@ -34,11 +35,11 @@ const props: ProductUpdatePageProps = {
onVariantAdd: () => undefined, onVariantAdd: () => undefined,
onVariantShow: () => undefined, onVariantShow: () => undefined,
onVariantsAdd: () => undefined, onVariantsAdd: () => undefined,
onWarehousesEdit: () => undefined,
placeholderImage, placeholderImage,
product, product,
saveButtonBarState: "default", saveButtonBarState: "default",
variants: product.variants variants: product.variants,
warehouses: warehouseList
}; };
storiesOf("Views / Products / Product edit", module) storiesOf("Views / Products / Product edit", module)

View file

@ -24,7 +24,6 @@ storiesOf("Views / Products / Create product variant", module)
onVariantClick={undefined} onVariantClick={undefined}
saveButtonBarState="default" saveButtonBarState="default"
warehouses={warehouseList} warehouses={warehouseList}
onWarehouseEdit={() => undefined}
/> />
)) ))
.add("with errors", () => ( .add("with errors", () => (
@ -55,7 +54,6 @@ storiesOf("Views / Products / Create product variant", module)
onVariantClick={undefined} onVariantClick={undefined}
saveButtonBarState="default" saveButtonBarState="default"
warehouses={warehouseList} warehouses={warehouseList}
onWarehouseEdit={() => undefined}
/> />
)) ))
.add("when loading data", () => ( .add("when loading data", () => (
@ -70,7 +68,6 @@ storiesOf("Views / Products / Create product variant", module)
onVariantClick={undefined} onVariantClick={undefined}
saveButtonBarState="default" saveButtonBarState="default"
warehouses={warehouseList} warehouses={warehouseList}
onWarehouseEdit={() => undefined}
/> />
)) ))
.add("add first variant", () => ( .add("add first variant", () => (
@ -88,6 +85,5 @@ storiesOf("Views / Products / Create product variant", module)
onVariantClick={undefined} onVariantClick={undefined}
saveButtonBarState="default" saveButtonBarState="default"
warehouses={warehouseList} warehouses={warehouseList}
onWarehouseEdit={() => undefined}
/> />
)); ));

View file

@ -3,6 +3,7 @@ import React from "react";
import placeholderImage from "@assets/images/placeholder60x60.png"; import placeholderImage from "@assets/images/placeholder60x60.png";
import { ProductErrorCode } from "@saleor/types/globalTypes"; import { ProductErrorCode } from "@saleor/types/globalTypes";
import { warehouseList } from "@saleor/warehouses/fixtures";
import ProductVariantPage from "../../../products/components/ProductVariantPage"; import ProductVariantPage from "../../../products/components/ProductVariantPage";
import { variant as variantFixture } from "../../../products/fixtures"; import { variant as variantFixture } from "../../../products/fixtures";
import Decorator from "../../Decorator"; import Decorator from "../../Decorator";
@ -23,7 +24,7 @@ storiesOf("Views / Products / Product variant details", module)
onSubmit={() => undefined} onSubmit={() => undefined}
onVariantClick={() => undefined} onVariantClick={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
onWarehousesEdit={() => undefined} warehouses={warehouseList}
/> />
)) ))
.add("when loading data", () => ( .add("when loading data", () => (
@ -39,7 +40,7 @@ storiesOf("Views / Products / Product variant details", module)
onSubmit={() => undefined} onSubmit={() => undefined}
onVariantClick={() => undefined} onVariantClick={() => undefined}
saveButtonBarState="default" saveButtonBarState="default"
onWarehousesEdit={() => undefined} warehouses={warehouseList}
/> />
)) ))
.add("attribute errors", () => ( .add("attribute errors", () => (
@ -71,6 +72,6 @@ storiesOf("Views / Products / Product variant details", module)
message: "Generic form error", message: "Generic form error",
...error ...error
}))} }))}
onWarehousesEdit={() => undefined} warehouses={warehouseList}
/> />
)); ));