From 6af08af91010f1e0b0d6c0a92324f2b1e698011f Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 4 May 2020 17:29:06 +0200 Subject: [PATCH 1/2] Update product stock management to newest design --- src/categories/views/CategoryDetails.tsx | 2 +- .../Navigator/modes/commands/actions.ts | 2 +- src/hooks/useFormset.ts | 18 + .../OrderFulfillPage/OrderFulfillPage.tsx | 5 +- .../ProductCreatePage/ProductCreatePage.tsx | 44 +- .../ProductStocks/ProductStocks.tsx | 166 +++- .../ProductUpdatePage/ProductUpdatePage.tsx | 53 +- .../ProductVariantCreatePage.tsx | 43 +- .../ProductVariantPage/ProductVariantPage.tsx | 49 +- .../ProductWarehousesDialog.stories.tsx | 46 - .../ProductWarehousesDialog.tsx | 152 ---- .../ProductWarehousesDialog/index.ts | 2 - src/products/index.tsx | 27 +- src/products/mutations.ts | 73 +- src/products/types/AddOrRemoveStocks.ts | 85 -- src/products/types/VariantUpdate.ts | 71 +- src/products/urls.ts | 22 +- src/products/utils/data.ts | 11 + src/products/views/ProductCreate.tsx | 65 +- .../views/ProductList/ProductList.tsx | 2 +- .../views/ProductUpdate/ProductUpdate.tsx | 64 +- src/products/views/ProductUpdate/handlers.ts | 10 +- src/products/views/ProductVariant.tsx | 83 +- src/products/views/ProductVariantCreate.tsx | 67 +- .../__snapshots__/Stories.test.ts.snap | 823 +++++++++++++----- .../stories/products/ProductCreatePage.tsx | 3 - .../stories/products/ProductUpdatePage.tsx | 5 +- .../products/ProductVariantCreatePage.tsx | 4 - .../stories/products/ProductVariantPage.tsx | 7 +- 29 files changed, 1084 insertions(+), 920 deletions(-) delete mode 100644 src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.stories.tsx delete mode 100644 src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx delete mode 100644 src/products/components/ProductWarehousesDialog/index.ts delete mode 100644 src/products/types/AddOrRemoveStocks.ts diff --git a/src/categories/views/CategoryDetails.tsx b/src/categories/views/CategoryDetails.tsx index 5e8f5ab12..e33d91b96 100644 --- a/src/categories/views/CategoryDetails.tsx +++ b/src/categories/views/CategoryDetails.tsx @@ -172,7 +172,7 @@ export const CategoryDetails: React.FC = ({ disabled={loading} errors={updateResult.data?.categoryUpdate.errors || []} onAddCategory={() => navigate(categoryAddUrl(id))} - onAddProduct={() => navigate(productAddUrl())} + onAddProduct={() => navigate(productAddUrl)} onBack={() => navigate( maybe( diff --git a/src/components/Navigator/modes/commands/actions.ts b/src/components/Navigator/modes/commands/actions.ts index 9956ba8b1..6a85d568a 100644 --- a/src/components/Navigator/modes/commands/actions.ts +++ b/src/components/Navigator/modes/commands/actions.ts @@ -46,7 +46,7 @@ export function searchInCommands( { label: intl.formatMessage(messages.createProduct), onClick: () => { - navigate(productAddUrl()); + navigate(productAddUrl); return false; } }, diff --git a/src/hooks/useFormset.ts b/src/hooks/useFormset.ts index eb506e272..be1143ebe 100644 --- a/src/hooks/useFormset.ts +++ b/src/hooks/useFormset.ts @@ -1,3 +1,4 @@ +import { removeAtIndex } from "@saleor/utils/lists"; import useStateFromProps from "./useStateFromProps"; export type FormsetChange = (id: string, value: TValue) => void; @@ -11,11 +12,13 @@ export type FormsetData = Array< FormsetAtomicData >; export interface UseFormsetOutput { + add: (data: FormsetAtomicData) => void; change: FormsetChange; data: FormsetData; get: (id: string) => FormsetAtomicData; // Used for some rare situations like dataset change set: (data: FormsetData) => void; + remove: (id: string) => void; } function useFormset( initial: FormsetData @@ -24,10 +27,23 @@ function useFormset( initial || [] ); + function addItem(itemData: FormsetAtomicData) { + setData(prevData => [...prevData, itemData]); + } + function getItem(id: string): FormsetAtomicData { 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) { const itemIndex = data.findIndex(item => item.id === id); setData([ @@ -41,9 +57,11 @@ function useFormset( } return { + add: addItem, change: setItemValue, data, get: getItem, + remove: removeItem, set: setData }; } diff --git a/src/orders/components/OrderFulfillPage/OrderFulfillPage.tsx b/src/orders/components/OrderFulfillPage/OrderFulfillPage.tsx index 092c0409e..49311c1c0 100644 --- a/src/orders/components/OrderFulfillPage/OrderFulfillPage.tsx +++ b/src/orders/components/OrderFulfillPage/OrderFulfillPage.tsx @@ -338,7 +338,10 @@ const OrderFulfillPage: React.FC = props => { warehouseStock.quantityAllocated; return ( - +
void; fetchCollections: (data: string) => void; fetchProductTypes: (data: string) => void; - onWarehouseEdit: () => void; onBack?(); onSubmit?(data: ProductCreatePageSubmitData); } @@ -108,8 +107,7 @@ export const ProductCreatePage: React.FC = ({ warehouses, onBack, fetchProductTypes, - onSubmit, - onWarehouseEdit + onSubmit }: ProductCreatePageProps) => { const intl = useIntl(); const localizeDate = useDateLocalize(); @@ -119,18 +117,12 @@ export const ProductCreatePage: React.FC = ({ data: attributes, set: setAttributeData } = useFormset([]); - const { change: changeStockData, data: stocks, set: setStocks } = useFormset< - null - >([]); - React.useEffect(() => { - const newStocks = warehouses.map(warehouse => ({ - data: null, - id: warehouse.id, - label: warehouse.name, - value: stocks.find(stock => stock.id === warehouse.id)?.value || 0 - })); - setStocks(newStocks); - }, [JSON.stringify(warehouses)]); + const { + add: addStock, + change: changeStockData, + data: stocks, + remove: removeStock + } = useFormset([]); // Ensures that it will not change after component rerenders, because it // generates different block keys and it causes editor to lose its content. @@ -253,11 +245,29 @@ export const ProductCreatePage: React.FC = ({ { + 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); + }} /> diff --git a/src/products/components/ProductStocks/ProductStocks.tsx b/src/products/components/ProductStocks/ProductStocks.tsx index e66532cd3..b4d2af73c 100644 --- a/src/products/components/ProductStocks/ProductStocks.tsx +++ b/src/products/components/ProductStocks/ProductStocks.tsx @@ -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 CardContent from "@material-ui/core/CardContent"; import Table from "@material-ui/core/Table"; @@ -11,6 +15,10 @@ import Typography from "@material-ui/core/Typography"; import React from "react"; import { useIntl, FormattedMessage } from "react-intl"; 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 { FormsetChange, FormsetAtomicData } from "@saleor/hooks/useFormset"; @@ -21,7 +29,8 @@ import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; import FormSpacer from "@saleor/components/FormSpacer"; import Hr from "@saleor/components/Hr"; 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; export interface ProductStockFormData { @@ -34,13 +43,19 @@ export interface ProductStocksProps { disabled: boolean; errors: UserError[]; stocks: ProductStockInput[]; + warehouses: WarehouseFragment[]; onChange: FormsetChange; onFormDataChange: FormChange; - onWarehousesEdit: () => void; + onWarehouseStockAdd: (warehouseId: string) => void; + onWarehouseStockDelete: (warehouseId: string) => void; } const useStyles = makeStyles( theme => ({ + colAction: { + padding: 0, + width: ICONBUTTON_SIZE + theme.spacing() + }, colName: {}, colQuantity: { textAlign: "right", @@ -56,6 +71,19 @@ const useStyles = makeStyles( inputComponent: { 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: { paddingTop: theme.spacing() }, @@ -80,12 +108,20 @@ const ProductStocks: React.FC = ({ disabled, errors, stocks, + warehouses, onChange, onFormDataChange, - onWarehousesEdit + onWarehouseStockAdd, + onWarehouseStockDelete }) => { const classes = useStyles({}); const intl = useIntl(); + const anchor = React.useRef(); + const [isExpanded, setExpansionState] = React.useState(false); + + const warehousesToAssign = warehouses.filter( + warehouse => !stocks.some(stock => stock.id === warehouse.id) + ); return ( @@ -140,17 +176,6 @@ const ProductStocks: React.FC = ({ description="header" /> -
@@ -169,44 +194,89 @@ const ProductStocks: React.FC = ({ description="tabel column header" />
+ - {renderCollection( - stocks, - stock => ( - - {stock.label} - - onChange(stock.id, event.target.value)} - value={stock.value} - /> - - - ), - () => ( - - + {renderCollection(stocks, stock => ( + + {stock.label} + + onChange(stock.id, event.target.value)} + value={stock.value} + /> + + + onWarehouseStockDelete(stock.id)} + > + + + + + ))} + {warehousesToAssign.length > 0 && ( + + + here." - } - values={{ - l: str => {str} - }} + defaultMessage="Assign Warehouse" + description="button" /> - - - ) + + + + setExpansionState(false)}> +
+ setExpansionState(!isExpanded)} + > + + + + {({ TransitionProps }) => ( + + + {warehousesToAssign.map(warehouse => ( + + onWarehouseStockAdd(warehouse.id) + } + > + {warehouse.name} + + ))} + + + )} + +
+
+
+
)}
diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index d8c23ed2e..0d3a4a28f 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -1,4 +1,5 @@ import { convertFromRaw, RawDraftContentState } from "draft-js"; +import { diff } from "fast-array-diff"; import React from "react"; import { useIntl } from "react-intl"; @@ -23,6 +24,7 @@ import { FetchMoreProps, ListActions } from "@saleor/types"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment"; import { ProductDetails_product, ProductDetails_product_images, @@ -62,9 +64,9 @@ export interface ProductUpdatePageProps extends ListActions { product: ProductDetails_product; header: string; saveButtonBarState: ConfirmButtonTransitionState; + warehouses: WarehouseFragment[]; fetchCategories: (query: string) => void; fetchCollections: (query: string) => void; - onWarehousesEdit: () => void; onVariantsAdd: () => void; onVariantShow: (id: string) => () => void; onImageDelete: (id: string) => () => void; @@ -81,7 +83,9 @@ export interface ProductUpdatePageProps extends ListActions { export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData { attributes: ProductAttributeInput[]; collections: string[]; - stocks: ProductStockInput[]; + addStocks: ProductStockInput[]; + updateStocks: ProductStockInput[]; + removeStocks: string[]; } export const ProductUpdatePage: React.FC = ({ @@ -99,6 +103,7 @@ export const ProductUpdatePage: React.FC = ({ product, saveButtonBarState, variants, + warehouses, onBack, onDelete, onImageDelete, @@ -110,7 +115,6 @@ export const ProductUpdatePage: React.FC = ({ onVariantAdd, onVariantsAdd, onVariantShow, - onWarehousesEdit, isChecked, selected, toggle, @@ -129,7 +133,12 @@ export const ProductUpdatePage: React.FC = ({ const { change: changeAttributeData, data: attributes } = useFormset( attributeInput ); - const { change: changeStockData, data: stocks } = useFormset(stockInput); + const { + add: addStock, + change: changeStockData, + data: stocks, + remove: removeStock + } = useFormset(stockInput); const [selectedAttributes, setSelectedAttributes] = useStateFromProps< ProductAttributeValueChoices[] @@ -153,12 +162,25 @@ export const ProductUpdatePage: React.FC = ({ const currency = maybe(() => product.basePrice.currency); 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({ + ...data, + addStocks: stocks.filter(stock => + stockDiff.added.some(addedStock => addedStock === stock.id) + ), attributes, - stocks, - ...data + removeStocks: stockDiff.removed, + updateStocks: stocks.filter( + stock => !stockDiff.added.some(addedStock => addedStock === stock.id) + ) }); + }; return (
@@ -252,12 +274,27 @@ export const ProductUpdatePage: React.FC = ({ disabled={disabled} errors={errors} stocks={stocks} + warehouses={warehouses} onChange={(id, value) => { triggerChange(); changeStockData(id, value); }} 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); + }} /> )} diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index 5204732bd..f768787db 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -51,7 +51,6 @@ interface ProductVariantCreatePageProps { onBack: () => void; onSubmit: (data: ProductVariantCreatePageSubmitData) => void; onVariantClick: (variantId: string) => void; - onWarehouseEdit: () => void; } const ProductVariantCreatePage: React.FC = ({ @@ -64,8 +63,7 @@ const ProductVariantCreatePage: React.FC = ({ warehouses, onBack, onSubmit, - onVariantClick, - onWarehouseEdit + onVariantClick }) => { const intl = useIntl(); const attributeInput = React.useMemo( @@ -75,18 +73,12 @@ const ProductVariantCreatePage: React.FC = ({ const { change: changeAttributeData, data: attributes } = useFormset( attributeInput ); - const { change: changeStockData, data: stocks, set: setStocks } = useFormset< - null - >([]); - React.useEffect(() => { - const newStocks = warehouses.map(warehouse => ({ - data: null, - id: warehouse.id, - label: warehouse.name, - value: stocks.find(stock => stock.id === warehouse.id)?.value || 0 - })); - setStocks(newStocks); - }, [JSON.stringify(warehouses)]); + const { + add: addStock, + change: changeStockData, + data: stocks, + remove: removeStock + } = useFormset([]); const initialForm: ProductVariantCreatePageFormData = { costPrice: "", @@ -148,11 +140,28 @@ const ProductVariantCreatePage: React.FC = ({ { + 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); + }} /> diff --git a/src/products/components/ProductVariantPage/ProductVariantPage.tsx b/src/products/components/ProductVariantPage/ProductVariantPage.tsx index fb12161d2..b0ee12928 100644 --- a/src/products/components/ProductVariantPage/ProductVariantPage.tsx +++ b/src/products/components/ProductVariantPage/ProductVariantPage.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { diff } from "fast-array-diff"; import AppHeader from "@saleor/components/AppHeader"; import CardSpacer from "@saleor/components/CardSpacer"; @@ -17,6 +18,7 @@ import { getAttributeInputFromVariant, getStockInputFromVariant } from "@saleor/products/utils/data"; +import { WarehouseFragment } from "@saleor/warehouses/types/WarehouseFragment"; import { maybe } from "../../../misc"; import { ProductVariant } from "../../types/ProductVariant"; import ProductVariantAttributes, { @@ -38,7 +40,9 @@ export interface ProductVariantPageFormData { export interface ProductVariantPageSubmitData extends ProductVariantPageFormData { attributes: FormsetData; - stocks: ProductStockInput[]; + addStocks: ProductStockInput[]; + updateStocks: ProductStockInput[]; + removeStocks: string[]; } interface ProductVariantPageProps { @@ -48,7 +52,7 @@ interface ProductVariantPageProps { loading?: boolean; placeholderImage?: string; header: string; - onWarehousesEdit: () => void; + warehouses: WarehouseFragment[]; onAdd(); onBack(); onDelete(); @@ -64,12 +68,12 @@ const ProductVariantPage: React.FC = ({ placeholderImage, saveButtonBarState, variant, + warehouses, onAdd, onBack, onDelete, onImageSelect, onSubmit, - onWarehousesEdit, onVariantClick }) => { const attributeInput = React.useMemo( @@ -82,7 +86,12 @@ const ProductVariantPage: React.FC = ({ const { change: changeAttributeData, data: attributes } = useFormset( 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 toggleModal = () => setModalStatus(!isModalOpened); @@ -106,12 +115,23 @@ const ProductVariantPage: React.FC = ({ 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({ ...data, + addStocks: stocks.filter(stock => + stockDiff.added.some(addedStock => addedStock === stock.id) + ), attributes, - stocks + removeStocks: stockDiff.removed, + updateStocks: stocks.filter( + stock => !stockDiff.added.some(addedStock => addedStock === stock.id) + ) }); + }; return ( <> @@ -180,12 +200,27 @@ const ProductVariantPage: React.FC = ({ disabled={loading} errors={errors} stocks={stocks} + warehouses={warehouses} onChange={(id, value) => { triggerChange(); changeStockData(id, value); }} 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); + }} /> diff --git a/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.stories.tsx b/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.stories.tsx deleted file mode 100644 index 28f8734d6..000000000 --- a/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.stories.tsx +++ /dev/null @@ -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", () => ) - .add("loading warehouses", () => ( - - )) - .add("loading confirmation", () => ( - - )) - .add("with error", () => ( - - )); diff --git a/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx b/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx deleted file mode 100644 index 0b9192c1d..000000000 --- a/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx +++ /dev/null @@ -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; - open: boolean; - warehouses: SearchWarehouses_search_edges_node[]; - warehousesWithStocks: string[]; - onClose: () => void; - onConfirm: (data: DiffData) => 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 = ({ - 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 ( - - - - - - - - - - {warehouses === undefined ? ( - - ) : ( - warehouses.map(warehouse => ( -
- a === b - )} - name={`warehouse:${warehouse.id}`} - onChange={() => - setSelectedWarehouses( - toggle( - warehouse.id, - selectedWarehouses, - (a, b) => a === b - ) - ) - } - disabled={disabled} - label={warehouse.name} - /> -
- )) - )} -
- {errors.length > 0 && ( - - - {getErrorMessage(errors[0], intl)} - - - )} - - - - - - - -
- ); -}; - -ProductWarehousesDialog.displayName = "ProductWarehousesDialog"; -export default ProductWarehousesDialog; diff --git a/src/products/components/ProductWarehousesDialog/index.ts b/src/products/components/ProductWarehousesDialog/index.ts deleted file mode 100644 index 233e2f720..000000000 --- a/src/products/components/ProductWarehousesDialog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./ProductWarehousesDialog"; -export * from "./ProductWarehousesDialog"; diff --git a/src/products/index.tsx b/src/products/index.tsx index 624a50346..b80a26e0c 100644 --- a/src/products/index.tsx +++ b/src/products/index.tsx @@ -19,11 +19,9 @@ import { productVariantAddPath, productVariantEditPath, ProductVariantEditUrlQueryParams, - ProductAddUrlQueryParams, - ProductVariantAddUrlQueryParams, productVariantCreatorPath } from "./urls"; -import ProductCreateComponent from "./views/ProductCreate"; +import ProductCreate from "./views/ProductCreate"; import ProductImageComponent from "./views/ProductImage"; import ProductListComponent from "./views/ProductList"; import ProductUpdateComponent from "./views/ProductUpdate"; @@ -90,17 +88,11 @@ const ProductImage: React.FC> = ({ const ProductVariantCreate: React.FC> = ({ match -}) => { - const qs = parseQs(location.search.substr(1)); - const params: ProductVariantAddUrlQueryParams = qs; - - return ( - - ); -}; +}) => ( + +); const ProductVariantCreator: React.FC ); -const ProductCreate: React.FC = ({ location }) => { - const qs = parseQs(location.search.substr(1)); - const params: ProductAddUrlQueryParams = qs; - - return ; -}; - const Component = () => { const intl = useIntl(); diff --git a/src/products/mutations.ts b/src/products/mutations.ts index 2206e56a0..d877e1b1a 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -38,11 +38,7 @@ import { } from "./types/VariantImageUnassign"; import { VariantUpdate, VariantUpdateVariables } from "./types/VariantUpdate"; -import { - fragmentVariant, - productFragmentDetails, - stockFragment -} from "./queries"; +import { fragmentVariant, productFragmentDetails } from "./queries"; import { productBulkDelete, productBulkDeleteVariables @@ -59,10 +55,6 @@ import { ProductVariantBulkDelete, ProductVariantBulkDeleteVariables } from "./types/ProductVariantBulkDelete"; -import { - AddOrRemoveStocks, - AddOrRemoveStocksVariables -} from "./types/AddOrRemoveStocks"; export const bulkProductErrorFragment = gql` fragment BulkProductErrorFragment on BulkProductError { @@ -359,6 +351,8 @@ export const variantUpdateMutation = gql` ${fragmentVariant} ${productErrorFragment} mutation VariantUpdate( + $addStocks: [StockInput!]! + $removeStocks: [ID!]! $id: ID! $attributes: [AttributeValueInput] $costPrice: Decimal @@ -392,6 +386,29 @@ export const variantUpdateMutation = gql` ...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< @@ -558,41 +575,3 @@ export const TypedProductVariantBulkDeleteMutation = TypedMutation< ProductVariantBulkDelete, ProductVariantBulkDeleteVariables >(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); diff --git a/src/products/types/AddOrRemoveStocks.ts b/src/products/types/AddOrRemoveStocks.ts deleted file mode 100644 index 0ce1d9d9c..000000000 --- a/src/products/types/AddOrRemoveStocks.ts +++ /dev/null @@ -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[]; -} diff --git a/src/products/types/VariantUpdate.ts b/src/products/types/VariantUpdate.ts index 94d6bcd9a..4ed244606 100644 --- a/src/products/types/VariantUpdate.ts +++ b/src/products/types/VariantUpdate.ts @@ -2,7 +2,7 @@ /* eslint-disable */ // 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 @@ -255,12 +255,81 @@ export interface VariantUpdate_productVariantStocksUpdate { 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 { productVariantUpdate: VariantUpdate_productVariantUpdate | null; productVariantStocksUpdate: VariantUpdate_productVariantStocksUpdate | null; + productVariantStocksCreate: VariantUpdate_productVariantStocksCreate | null; + productVariantStocksDelete: VariantUpdate_productVariantStocksDelete | null; } export interface VariantUpdateVariables { + addStocks: StockInput[]; + removeStocks: string[]; id: string; attributes?: (AttributeValueInput | null)[] | null; costPrice?: any | null; diff --git a/src/products/urls.ts b/src/products/urls.ts index 5160034a8..327737745 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -17,10 +17,7 @@ import { const productSection = "/products/"; export const productAddPath = urlJoin(productSection, "add"); -export type ProductAddUrlDialog = "edit-stocks"; -export type ProductAddUrlQueryParams = Dialog; -export const productAddUrl = (params?: ProductAddUrlQueryParams): string => - productAddPath + "?" + stringifyQs(params); +export const productAddUrl = productAddPath; export const productListPath = productSection; export type ProductListUrlDialog = @@ -69,14 +66,14 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string => productListPath + "?" + stringifyQs(params); 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; export const productUrl = (id: string, params?: ProductUrlQueryParams) => productPath(encodeURIComponent(id)) + "?" + stringifyQs(params); export const productVariantEditPath = (productId: string, variantId: string) => urlJoin(productSection, productId, "variant", variantId); -export type ProductVariantEditUrlDialog = "edit-stocks" | "remove"; +export type ProductVariantEditUrlDialog = "remove"; export type ProductVariantEditUrlQueryParams = Dialog< ProductVariantEditUrlDialog >; @@ -99,17 +96,8 @@ export const productVariantCreatorUrl = (productId: string) => export const productVariantAddPath = (productId: string) => urlJoin(productSection, productId, "variant/add"); -export type ProductVariantAddUrlDialog = "edit-stocks"; -export type ProductVariantAddUrlQueryParams = Dialog< - ProductVariantAddUrlDialog ->; -export const productVariantAddUrl = ( - productId: string, - params?: ProductVariantAddUrlQueryParams -): string => - productVariantAddPath(encodeURIComponent(productId)) + - "?" + - stringifyQs(params); +export const productVariantAddUrl = (productId: string): string => + productVariantAddPath(encodeURIComponent(productId)); export const productImagePath = (productId: string, imageId: string) => urlJoin(productSection, productId, "image", imageId); diff --git a/src/products/utils/data.ts b/src/products/utils/data.ts index f5b6098a5..e348a6f84 100644 --- a/src/products/utils/data.ts +++ b/src/products/utils/data.ts @@ -9,6 +9,8 @@ import { ProductDetails_product_variants } from "@saleor/products/types/ProductDetails"; 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 { VariantAttributeInput } from "../components/ProductVariantAttributes"; import { ProductVariant } from "../types/ProductVariant"; @@ -211,3 +213,12 @@ export function getProductUpdatePageFormData( trackInventory: !!product?.variants[0]?.trackInventory }; } + +export function mapFormsetStockToStockInput( + stock: FormsetAtomicData +): StockInput { + return { + quantity: parseInt(stock.value, 10), + warehouse: stock.id + }; +} diff --git a/src/products/views/ProductCreate.tsx b/src/products/views/ProductCreate.tsx index 84928e85a..b9f329070 100644 --- a/src/products/views/ProductCreate.tsx +++ b/src/products/views/ProductCreate.tsx @@ -9,31 +9,16 @@ import useShop from "@saleor/hooks/useShop"; import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useProductTypeSearch from "@saleor/searches/useProductTypeSearch"; -import useWarehouseSearch from "@saleor/searches/useWarehouseSearch"; -import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; +import { useWarehouseList } from "@saleor/warehouses/queries"; import { decimal, maybe } from "../../misc"; import ProductCreatePage, { ProductCreatePageSubmitData } from "../components/ProductCreatePage"; import { TypedProductCreateMutation } from "../mutations"; import { ProductCreate } from "../types/ProductCreate"; -import { - productListUrl, - productUrl, - ProductAddUrlDialog, - ProductAddUrlQueryParams, - productAddUrl -} from "../urls"; -import ProductWarehousesDialog from "../components/ProductWarehousesDialog"; +import { productListUrl, productUrl } from "../urls"; -interface ProductCreateViewProps { - params: ProductAddUrlQueryParams; -} - -export const ProductCreateView: React.FC = ({ - params -}) => { +export const ProductCreateView: React.FC = () => { const navigate = useNavigator(); const notify = useNotifier(); const shop = useShop(); @@ -59,20 +44,12 @@ export const ProductCreateView: React.FC = ({ } = useProductTypeSearch({ variables: DEFAULT_INITIAL_SEARCH_DATA }); - const { result: searchWarehousesOpts } = useWarehouseSearch({ + const warehouses = useWarehouseList({ + displayLoader: true, variables: { - ...DEFAULT_INITIAL_SEARCH_DATA, - first: 20 + first: 50 } }); - const [warehouses, setWarehouses] = React.useState< - SearchWarehouses_search_edges_node[] - >([]); - - const [openModal, closeModal] = createDialogActionHandlers< - ProductAddUrlDialog, - ProductAddUrlQueryParams - >(navigate, productAddUrl, params); const handleBack = () => navigate(productListUrl()); @@ -153,7 +130,6 @@ export const ProductCreateView: React.FC = ({ productTypes={maybe(() => searchProductTypesOpts.data.search.edges.map(edge => edge.node) )} - warehouses={warehouses} onBack={handleBack} onSubmit={handleSubmit} saveButtonBarState={productCreateOpts.status} @@ -178,32 +154,9 @@ export const ProductCreateView: React.FC = ({ loading: searchProductTypesOpts.loading, onFetchMore: loadMoreProductTypes }} - onWarehouseEdit={() => openModal("edit-stocks")} - /> - 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(); - }} + warehouses={ + warehouses.data?.warehouses.edges.map(edge => edge.node) || [] + } /> ); diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 302006ca0..de9ebe432 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -306,7 +306,7 @@ export const ProductList: React.FC = ({ params }) => { .hasNextPage, false )} - onAdd={() => navigate(productAddUrl())} + onAdd={() => navigate(productAddUrl)} disabled={loading} products={maybe(() => data.products.edges.map(edge => edge.node) diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index c0c35acf8..91d28edd3 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -16,9 +16,7 @@ import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import NotFoundPage from "@saleor/components/NotFoundPage"; -import ProductWarehousesDialog from "@saleor/products/components/ProductWarehousesDialog"; -import useWarehouseSearch from "@saleor/searches/useWarehouseSearch"; -import { useAddOrRemoveStocks } from "@saleor/products/mutations"; +import { useWarehouseList } from "@saleor/warehouses/queries"; import { getMutationState, maybe } from "../../../misc"; import ProductUpdatePage from "../../components/ProductUpdatePage"; import ProductUpdateOperations from "../../containers/ProductUpdateOperations"; @@ -71,24 +69,10 @@ export const ProductUpdate: React.FC = ({ id, params }) => { } = useCollectionSearch({ variables: DEFAULT_INITIAL_SEARCH_DATA }); - const { result: searchWarehousesOpts } = useWarehouseSearch({ + const warehouses = useWarehouseList({ + displayLoader: true, variables: { - ...DEFAULT_INITIAL_SEARCH_DATA, - 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(); - } + first: 50 } }); @@ -238,6 +222,11 @@ export const ProductUpdate: React.FC = ({ id, params }) => { header={maybe(() => product.name)} placeholderImage={placeholderImg} product={product} + warehouses={ + warehouses.data?.warehouses.edges.map( + edge => edge.node + ) || [] + } variants={maybe(() => product.variants)} onBack={handleBack} onDelete={() => openModal("remove")} @@ -282,7 +271,6 @@ export const ProductUpdate: React.FC = ({ id, params }) => { loading: searchCollectionsOpts.loading, onFetchMore: loadMoreCollections }} - onWarehousesEdit={() => openModal("edit-stocks")} /> = ({ id, params }) => { /> - {!product?.productType?.hasVariants && ( - 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 - } - }) - } - /> - )} ); }} diff --git a/src/products/views/ProductUpdate/handlers.ts b/src/products/views/ProductUpdate/handlers.ts index 3fad989ff..3d8f88340 100644 --- a/src/products/views/ProductUpdate/handlers.ts +++ b/src/products/views/ProductUpdate/handlers.ts @@ -7,6 +7,7 @@ import { ProductUpdateVariables } from "@saleor/products/types/ProductUpdate"; import { SimpleProductUpdateVariables } from "@saleor/products/types/SimpleProductUpdate"; import { ReorderEvent } from "@saleor/types"; import { arrayMove } from "react-sortable-hoc"; +import { mapFormsetStockToStockInput } from "@saleor/products/utils/data"; export function createUpdateHandler( product: ProductDetails_product, @@ -40,17 +41,14 @@ export function createUpdateHandler( } else { updateSimpleProduct({ ...productVariables, - addStocks: [], - deleteStocks: [], + addStocks: data.addStocks.map(mapFormsetStockToStockInput), + deleteStocks: data.removeStocks, productVariantId: product.variants[0].id, productVariantInput: { sku: data.sku, trackInventory: data.trackInventory }, - updateStocks: data.stocks.map(stock => ({ - quantity: parseInt(stock.value, 0), - warehouse: stock.id - })) + updateStocks: data.updateStocks.map(mapFormsetStockToStockInput) }); } }; diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index b03f3a33e..5f9c10584 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -8,8 +8,7 @@ import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; import NotFoundPage from "@saleor/components/NotFoundPage"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config"; -import useWarehouseSearch from "@saleor/searches/useWarehouseSearch"; +import { useWarehouseList } from "@saleor/warehouses/queries"; import { decimal } from "../../misc"; import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog"; import ProductVariantPage, { @@ -28,8 +27,7 @@ import { ProductVariantEditUrlQueryParams, ProductVariantEditUrlDialog } from "../urls"; -import ProductWarehousesDialog from "../components/ProductWarehousesDialog"; -import { useAddOrRemoveStocks } from "../mutations"; +import { mapFormsetStockToStockInput } from "../utils/data"; interface ProductUpdateProps { variantId: string; @@ -52,28 +50,14 @@ export const ProductVariant: React.FC = ({ setErrors([]); }, [variantId]); - const { result: searchWarehousesOpts } = useWarehouseSearch({ + const warehouses = useWarehouseList({ + displayLoader: true, variables: { - ...DEFAULT_INITIAL_SEARCH_DATA, - first: 20 + first: 50 } }); - const [addOrRemoveStocks, addOrRemoveStocksOpts] = useAddOrRemoveStocks({ - onCompleted: data => { - if ( - data.productVariantStocksCreate.errors.length === 0 && - data.productVariantStocksDelete.errors.length === 0 - ) { - notify({ - text: intl.formatMessage(commonMessages.savedChanges) - }); - closeModal(); - } - } - }); - - const [openModal, closeModal] = createDialogActionHandlers< + const [openModal] = createDialogActionHandlers< ProductVariantEditUrlDialog, ProductVariantEditUrlQueryParams >( @@ -151,18 +135,20 @@ export const ProductVariant: React.FC = ({ placeholderImage={placeholderImg} variant={variant} header={variant?.name || variant?.sku} + warehouses={ + warehouses.data?.warehouses.edges.map( + edge => edge.node + ) || [] + } onAdd={() => navigate(productVariantAddUrl(productId))} onBack={handleBack} - onDelete={() => - navigate( - productVariantEditUrl(productId, variantId, { - action: "remove" - }) - ) - } + onDelete={() => openModal("remove")} onImageSelect={handleImageSelect} onSubmit={(data: ProductVariantPageSubmitData) => updateVariant.mutate({ + addStocks: data.addStocks.map( + mapFormsetStockToStockInput + ), attributes: data.attributes.map(attribute => ({ id: attribute.id, values: [attribute.value] @@ -170,18 +156,17 @@ export const ProductVariant: React.FC = ({ costPrice: decimal(data.costPrice), id: variantId, priceOverride: decimal(data.priceOverride), + removeStocks: data.removeStocks, sku: data.sku, - stocks: data.stocks.map(stock => ({ - quantity: parseInt(stock.value, 10), - warehouse: stock.id - })), + stocks: data.updateStocks.map( + mapFormsetStockToStockInput + ), trackInventory: data.trackInventory }) } onVariantClick={variantId => { navigate(productVariantEditUrl(productId, variantId)); }} - onWarehousesEdit={() => openModal("edit-stocks")} /> = ({ open={params.action === "remove"} name={data?.productVariant?.name} /> - 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 - } - }) - } - /> ); }} diff --git a/src/products/views/ProductVariantCreate.tsx b/src/products/views/ProductVariantCreate.tsx index aeeb3643a..fce437e40 100644 --- a/src/products/views/ProductVariantCreate.tsx +++ b/src/products/views/ProductVariantCreate.tsx @@ -7,10 +7,7 @@ import useNotifier from "@saleor/hooks/useNotifier"; import useShop from "@saleor/hooks/useShop"; import NotFoundPage from "@saleor/components/NotFoundPage"; import { commonMessages } from "@saleor/intl"; -import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; -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 { useWarehouseList } from "@saleor/warehouses/queries"; import { decimal } from "../../misc"; import ProductVariantCreatePage, { ProductVariantCreatePageSubmitData @@ -18,43 +15,25 @@ import ProductVariantCreatePage, { import { TypedVariantCreateMutation } from "../mutations"; import { TypedProductVariantCreateQuery } from "../queries"; import { VariantCreate } from "../types/VariantCreate"; -import { - productUrl, - productVariantEditUrl, - productListUrl, - productVariantAddUrl, - ProductVariantAddUrlDialog, - ProductVariantAddUrlQueryParams -} from "../urls"; -import ProductWarehousesDialog from "../components/ProductWarehousesDialog"; +import { productUrl, productVariantEditUrl, productListUrl } from "../urls"; interface ProductVariantCreateProps { - params: ProductVariantAddUrlQueryParams; productId: string; } export const ProductVariant: React.FC = ({ - params, productId }) => { const navigate = useNavigator(); const notify = useNotifier(); const shop = useShop(); const intl = useIntl(); - const { result: searchWarehousesOpts } = useWarehouseSearch({ + const warehouses = useWarehouseList({ + displayLoader: true, variables: { - ...DEFAULT_INITIAL_SEARCH_DATA, - first: 20 + first: 50 } }); - const [warehouses, setWarehouses] = React.useState< - SearchWarehouses_search_edges_node[] - >([]); - - const [openModal, closeModal] = createDialogActionHandlers< - ProductVariantAddUrlDialog, - ProductVariantAddUrlQueryParams - >(navigate, params => productVariantAddUrl(productId, params), params); return ( @@ -136,37 +115,11 @@ export const ProductVariant: React.FC = ({ onSubmit={handleSubmit} onVariantClick={handleVariantClick} saveButtonBarState={variantCreateResult.status} - warehouses={warehouses} - onWarehouseEdit={() => openModal("edit-stocks")} - /> - 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(); - }} + warehouses={ + warehouses.data?.warehouses.edges.map( + edge => edge.node + ) || [] + } /> ); diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index f9f9a5cf2..383ddcd27 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -108380,18 +108380,6 @@ exports[`Storyshots Views / Products / Create product variant add first variant Quantity - @@ -108416,6 +108404,10 @@ exports[`Storyshots Views / Products / Create product variant add first variant > Quantity Available + - This product doesn't have any stock. You can add it - - here - - . + Assign Warehouse + + + +
+ +
@@ -108957,18 +108974,6 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` Quantity - @@ -108993,6 +108998,10 @@ exports[`Storyshots Views / Products / Create product variant default 1`] = ` > Quantity Available + - This product doesn't have any stock. You can add it - - here - - . + Assign Warehouse + + + +
+ +
@@ -109461,18 +109495,6 @@ exports[`Storyshots Views / Products / Create product variant when loading data Quantity - @@ -109497,6 +109519,10 @@ exports[`Storyshots Views / Products / Create product variant when loading data > Quantity Available + - This product doesn't have any stock. You can add it - - here - - . + Assign Warehouse + + + +
+ +
@@ -110051,18 +110102,6 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = Quantity - @@ -110087,6 +110126,10 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = > Quantity Available + - This product doesn't have any stock. You can add it - - here - - . + Assign Warehouse + + + +
+ +
@@ -110118,30 +110186,6 @@ exports[`Storyshots Views / Products / Create product variant with errors 1`] = `; -exports[`Storyshots Views / Products / Edit warehouses default 1`] = ` -
-`; - -exports[`Storyshots Views / Products / Edit warehouses loading confirmation 1`] = ` -
-`; - -exports[`Storyshots Views / Products / Edit warehouses loading warehouses 1`] = ` -
-`; - -exports[`Storyshots Views / Products / Edit warehouses with error 1`] = ` -
-`; - exports[`Storyshots Views / Products / Product edit form errors 1`] = `
Quantity -
@@ -114834,6 +114866,10 @@ Ctrl + K" > Quantity Available + - This product doesn't have any stock. You can add it - - here - - . + Assign Warehouse +
+ + +
+ +
@@ -116578,18 +116639,6 @@ Ctrl + K" Quantity -
@@ -116614,6 +116663,10 @@ Ctrl + K" > Quantity Available + + + + + + + + + + +
+ Assign Warehouse +
+ + +
+ +
+ @@ -119646,18 +119790,6 @@ Ctrl + K" Quantity - @@ -119682,6 +119814,10 @@ Ctrl + K" > Quantity Available + - This product doesn't have any stock. You can add it - - here - - . + Assign Warehouse + + + +
+ +
@@ -123115,18 +123276,6 @@ Ctrl + K" Quantity - @@ -123151,6 +123300,10 @@ Ctrl + K" > Quantity Available + + + + + + + + + + +
+ Assign Warehouse +
+ + +
+ +
+ @@ -129482,18 +129726,6 @@ exports[`Storyshots Views / Products / Product variant details attribute errors Quantity - @@ -129518,6 +129750,10 @@ exports[`Storyshots Views / Products / Product variant details attribute errors > Quantity Available + + + + + + + + + + +
+ Assign Warehouse +
+ + +
+ +
+ @@ -130286,18 +130613,6 @@ exports[`Storyshots Views / Products / Product variant details when loaded data Quantity - @@ -130322,6 +130637,10 @@ exports[`Storyshots Views / Products / Product variant details when loaded data > Quantity Available + + + + + + + + + + +
+ Assign Warehouse +
+ + +
+ +
+ @@ -130864,18 +131274,6 @@ exports[`Storyshots Views / Products / Product variant details when loading data Quantity - @@ -130900,6 +131298,10 @@ exports[`Storyshots Views / Products / Product variant details when loading data > Quantity Available + - This product doesn't have any stock. You can add it - - here - - . + Assign Warehouse + + + +
+ +
diff --git a/src/storybook/stories/products/ProductCreatePage.tsx b/src/storybook/stories/products/ProductCreatePage.tsx index 4d37c89ed..145b5759b 100644 --- a/src/storybook/stories/products/ProductCreatePage.tsx +++ b/src/storybook/stories/products/ProductCreatePage.tsx @@ -33,7 +33,6 @@ storiesOf("Views / Products / Create product", module) onBack={() => undefined} onSubmit={() => undefined} saveButtonBarState="default" - onWarehouseEdit={() => undefined} warehouses={warehouseList} /> )) @@ -55,7 +54,6 @@ storiesOf("Views / Products / Create product", module) onBack={() => undefined} onSubmit={() => undefined} saveButtonBarState="default" - onWarehouseEdit={() => undefined} warehouses={undefined} /> )) @@ -83,7 +81,6 @@ storiesOf("Views / Products / Create product", module) onBack={() => undefined} onSubmit={() => undefined} saveButtonBarState="default" - onWarehouseEdit={() => undefined} warehouses={warehouseList} /> )); diff --git a/src/storybook/stories/products/ProductUpdatePage.tsx b/src/storybook/stories/products/ProductUpdatePage.tsx index acff0e2d3..9bc5478f7 100644 --- a/src/storybook/stories/products/ProductUpdatePage.tsx +++ b/src/storybook/stories/products/ProductUpdatePage.tsx @@ -10,6 +10,7 @@ import ProductUpdatePage, { import { product as productFixture } from "@saleor/products/fixtures"; import { ProductUpdatePageFormData } from "@saleor/products/utils/data"; import { ProductErrorCode } from "@saleor/types/globalTypes"; +import { warehouseList } from "@saleor/warehouses/fixtures"; import Decorator from "../../Decorator"; const product = productFixture(placeholderImage); @@ -34,11 +35,11 @@ const props: ProductUpdatePageProps = { onVariantAdd: () => undefined, onVariantShow: () => undefined, onVariantsAdd: () => undefined, - onWarehousesEdit: () => undefined, placeholderImage, product, saveButtonBarState: "default", - variants: product.variants + variants: product.variants, + warehouses: warehouseList }; storiesOf("Views / Products / Product edit", module) diff --git a/src/storybook/stories/products/ProductVariantCreatePage.tsx b/src/storybook/stories/products/ProductVariantCreatePage.tsx index 2d83d76c5..d5ef0ac0b 100644 --- a/src/storybook/stories/products/ProductVariantCreatePage.tsx +++ b/src/storybook/stories/products/ProductVariantCreatePage.tsx @@ -24,7 +24,6 @@ storiesOf("Views / Products / Create product variant", module) onVariantClick={undefined} saveButtonBarState="default" warehouses={warehouseList} - onWarehouseEdit={() => undefined} /> )) .add("with errors", () => ( @@ -55,7 +54,6 @@ storiesOf("Views / Products / Create product variant", module) onVariantClick={undefined} saveButtonBarState="default" warehouses={warehouseList} - onWarehouseEdit={() => undefined} /> )) .add("when loading data", () => ( @@ -70,7 +68,6 @@ storiesOf("Views / Products / Create product variant", module) onVariantClick={undefined} saveButtonBarState="default" warehouses={warehouseList} - onWarehouseEdit={() => undefined} /> )) .add("add first variant", () => ( @@ -88,6 +85,5 @@ storiesOf("Views / Products / Create product variant", module) onVariantClick={undefined} saveButtonBarState="default" warehouses={warehouseList} - onWarehouseEdit={() => undefined} /> )); diff --git a/src/storybook/stories/products/ProductVariantPage.tsx b/src/storybook/stories/products/ProductVariantPage.tsx index 4cd3853ce..91a12b654 100644 --- a/src/storybook/stories/products/ProductVariantPage.tsx +++ b/src/storybook/stories/products/ProductVariantPage.tsx @@ -3,6 +3,7 @@ import React from "react"; import placeholderImage from "@assets/images/placeholder60x60.png"; import { ProductErrorCode } from "@saleor/types/globalTypes"; +import { warehouseList } from "@saleor/warehouses/fixtures"; import ProductVariantPage from "../../../products/components/ProductVariantPage"; import { variant as variantFixture } from "../../../products/fixtures"; import Decorator from "../../Decorator"; @@ -23,7 +24,7 @@ storiesOf("Views / Products / Product variant details", module) onSubmit={() => undefined} onVariantClick={() => undefined} saveButtonBarState="default" - onWarehousesEdit={() => undefined} + warehouses={warehouseList} /> )) .add("when loading data", () => ( @@ -39,7 +40,7 @@ storiesOf("Views / Products / Product variant details", module) onSubmit={() => undefined} onVariantClick={() => undefined} saveButtonBarState="default" - onWarehousesEdit={() => undefined} + warehouses={warehouseList} /> )) .add("attribute errors", () => ( @@ -71,6 +72,6 @@ storiesOf("Views / Products / Product variant details", module) message: "Generic form error", ...error }))} - onWarehousesEdit={() => undefined} + warehouses={warehouseList} /> )); From f969ddf376e807fd8592a64eec77e2bc80e96482 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 4 May 2020 18:07:02 +0200 Subject: [PATCH 2/2] Update messages and changelog --- CHANGELOG.md | 2 ++ locale/defaultMessages.json | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6abdd7def..c35d300aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable, unreleased changes to this project will be documented in this file. ## [Unreleased] +- Update product stock management to newest design - #515 by @dominik-zeglen + ## 2.10.0 - Fix minor bugs - #244 by @dominik-zeglen diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 4d1654739..9f56ff639 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -1427,6 +1427,9 @@ "src_dot_configuration_dot_1233229030": { "string": "Miscellaneous" }, + "src_dot_configuration_dot_1440737903": { + "string": "Shipping Settings" + }, "src_dot_configuration_dot_1639245766": { "string": "View and update your webhook and their settings" }, @@ -3074,6 +3077,10 @@ "context": "dialog title", "string": "Delete permission group" }, + "src_dot_permissionGroups_dot_components_dot_PermissionGroupDeleteDialog_dot_956177443": { + "context": "deletion error message", + "string": "Cant's delete group which is out of your permission scope" + }, "src_dot_permissionGroups_dot_components_dot_PermissionGroupDetailsPage_dot_3765873075": { "context": "checkbox label", "string": "Group has full access to the store"