From d38fa424625cb91413c83a5b6cd706ab7c592dd2 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 27 Mar 2020 12:06:11 +0100 Subject: [PATCH] Add stock in product variant create view --- .../ProductVariantCreatePage.tsx | 54 +++++++---- .../ProductVariantStock.tsx | 97 ------------------- .../components/ProductVariantStock/index.ts | 2 - src/products/index.tsx | 19 ++-- src/products/mutations.ts | 2 + src/products/types/ProductCreate.ts | 1 + src/products/urls.ts | 13 ++- src/products/views/ProductCreate.tsx | 3 +- src/products/views/ProductVariantCreate.tsx | 80 ++++++++++++++- 9 files changed, 139 insertions(+), 132 deletions(-) delete mode 100644 src/products/components/ProductVariantStock/ProductVariantStock.tsx delete mode 100644 src/products/components/ProductVariantStock/index.ts diff --git a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx index 47b3c4d13..27f8cb595 100644 --- a/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx +++ b/src/products/components/ProductVariantCreatePage/ProductVariantCreatePage.tsx @@ -15,6 +15,7 @@ import useFormset, { } from "@saleor/hooks/useFormset"; import { getVariantAttributeInputFromProduct } from "@saleor/products/utils/data"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; +import { SearchWarehouses_search_edges_node } from "@saleor/searches/types/SearchWarehouses"; import { maybe } from "../../../misc"; import { ProductVariantCreateData_product } from "../../types/ProductVariantCreateData"; import ProductVariantAttributes, { @@ -22,7 +23,7 @@ import ProductVariantAttributes, { } from "../ProductVariantAttributes"; import ProductVariantNavigation from "../ProductVariantNavigation"; import ProductVariantPrice from "../ProductVariantPrice"; -import ProductVariantStock from "../ProductVariantStock"; +import ProductStocks, { ProductStockInput } from "../ProductStocks"; interface ProductVariantCreatePageFormData { costPrice: string; @@ -30,35 +31,43 @@ interface ProductVariantCreatePageFormData { priceOverride: string; quantity: string; sku: string; + trackInventory: boolean; } export interface ProductVariantCreatePageSubmitData extends ProductVariantCreatePageFormData { attributes: FormsetData; + stocks: ProductStockInput[]; } interface ProductVariantCreatePageProps { currencySymbol: string; + disabled: boolean; errors: ProductErrorFragment[]; header: string; loading: boolean; product: ProductVariantCreateData_product; saveButtonBarState: ConfirmButtonTransitionState; + warehouses: SearchWarehouses_search_edges_node[]; onBack: () => void; onSubmit: (data: ProductVariantCreatePageSubmitData) => void; onVariantClick: (variantId: string) => void; + onWarehouseEdit: () => void; } const ProductVariantCreatePage: React.FC = ({ currencySymbol, + disabled, errors, loading, header, product, saveButtonBarState, + warehouses, onBack, onSubmit, - onVariantClick + onVariantClick, + onWarehouseEdit }) => { const intl = useIntl(); const attributeInput = React.useMemo( @@ -68,28 +77,33 @@ 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 initialForm = { - attributes: maybe( - () => - product.productType.variantAttributes.map(attribute => ({ - name: attribute.name, - slug: attribute.slug, - values: [""] - })), - [] - ), + const initialForm: ProductVariantCreatePageFormData = { costPrice: "", images: maybe(() => product.images.map(image => image.id)), priceOverride: "", quantity: "0", - sku: "" + sku: "", + trackInventory: true }; const handleSubmit = (data: ProductVariantCreatePageFormData) => onSubmit({ ...data, - attributes + attributes, + stocks }); return ( @@ -133,12 +147,14 @@ const ProductVariantCreatePage: React.FC = ({ onChange={change} /> - diff --git a/src/products/components/ProductVariantStock/ProductVariantStock.tsx b/src/products/components/ProductVariantStock/ProductVariantStock.tsx deleted file mode 100644 index fe8b52776..000000000 --- a/src/products/components/ProductVariantStock/ProductVariantStock.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import Card from "@material-ui/core/Card"; -import CardContent from "@material-ui/core/CardContent"; -import { makeStyles } from "@material-ui/core/styles"; -import TextField from "@material-ui/core/TextField"; -import React from "react"; -import { useIntl } from "react-intl"; - -import CardTitle from "@saleor/components/CardTitle"; -import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; -import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; - -const useStyles = makeStyles( - theme => ({ - grid: { - display: "grid", - gridColumnGap: theme.spacing(2), - gridTemplateColumns: "1fr 1fr" - } - }), - { name: "ProductVariantStock" } -); - -interface ProductVariantStockProps { - errors: ProductErrorFragment[]; - sku: string; - quantity: string; - stockAllocated?: number; - loading?: boolean; - onChange(event: any); -} - -const ProductVariantStock: React.FC = props => { - const { errors, sku, quantity, stockAllocated, loading, onChange } = props; - - const classes = useStyles(props); - const intl = useIntl(); - - const formErrors = getFormErrors(["quantity", "sku"], errors); - - return ( - - - -
-
- -
-
- -
-
-
-
- ); -}; -ProductVariantStock.displayName = "ProductVariantStock"; -export default ProductVariantStock; diff --git a/src/products/components/ProductVariantStock/index.ts b/src/products/components/ProductVariantStock/index.ts deleted file mode 100644 index 0cb07fabd..000000000 --- a/src/products/components/ProductVariantStock/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./ProductVariantStock"; -export * from "./ProductVariantStock"; diff --git a/src/products/index.tsx b/src/products/index.tsx index c17a75799..dfd75cd77 100644 --- a/src/products/index.tsx +++ b/src/products/index.tsx @@ -19,7 +19,8 @@ import { productVariantAddPath, productVariantEditPath, ProductVariantEditUrlQueryParams, - ProductAddUrlQueryParams + ProductAddUrlQueryParams, + ProductVariantAddUrlQueryParams } from "./urls"; import ProductCreateComponent from "./views/ProductCreate"; import ProductImageComponent from "./views/ProductImage"; @@ -87,11 +88,17 @@ const ProductImage: React.FC> = ({ const ProductVariantCreate: React.FC> = ({ match -}) => ( - -); +}) => { + const qs = parseQs(location.search.substr(1)); + const params: ProductVariantAddUrlQueryParams = qs; + + return ( + + ); +}; const ProductCreate: React.FC = ({ location }) => { const qs = parseQs(location.search.substr(1)); diff --git a/src/products/mutations.ts b/src/products/mutations.ts index a8252094d..72cb86adc 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -303,6 +303,7 @@ export const productCreateMutation = gql` $stockQuantity: Int $seo: SeoInput $stocks: [StockInput!]! + $trackInventory: Boolean! ) { productCreate( input: { @@ -320,6 +321,7 @@ export const productCreateMutation = gql` quantity: $stockQuantity seo: $seo stocks: $stocks + trackInventory: $trackInventory } ) { errors: productErrors { diff --git a/src/products/types/ProductCreate.ts b/src/products/types/ProductCreate.ts index a38b7cf40..710f81e8f 100644 --- a/src/products/types/ProductCreate.ts +++ b/src/products/types/ProductCreate.ts @@ -212,4 +212,5 @@ export interface ProductCreateVariables { stockQuantity?: number | null; seo?: SeoInput | null; stocks: StockInput[]; + trackInventory: boolean; } diff --git a/src/products/urls.ts b/src/products/urls.ts index 4229e7871..08b8aeb7a 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -98,8 +98,17 @@ export const productVariantEditUrl = ( export const productVariantAddPath = (productId: string) => urlJoin(productSection, productId, "variant/add"); -export const productVariantAddUrl = (productId: string) => - productVariantAddPath(encodeURIComponent(productId)); +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 productImagePath = (productId: string, imageId: string) => urlJoin(productSection, productId, "image", imageId); diff --git a/src/products/views/ProductCreate.tsx b/src/products/views/ProductCreate.tsx index 94d77c116..18cd8b1a8 100644 --- a/src/products/views/ProductCreate.tsx +++ b/src/products/views/ProductCreate.tsx @@ -121,7 +121,8 @@ export const ProductCreateView: React.FC = ({ stocks: formData.stocks.map(stock => ({ quantity: parseInt(stock.value, 0), warehouse: stock.id - })) + })), + trackInventory: formData.trackInventory } }); }; diff --git a/src/products/views/ProductVariantCreate.tsx b/src/products/views/ProductVariantCreate.tsx index c2e446ab1..103086e5e 100644 --- a/src/products/views/ProductVariantCreate.tsx +++ b/src/products/views/ProductVariantCreate.tsx @@ -7,24 +7,58 @@ import useNotifier from "@saleor/hooks/useNotifier"; import useShop from "@saleor/hooks/useShop"; import NotFoundPage from "@saleor/components/NotFoundPage"; import { commonMessages } from "@saleor/intl"; -import { decimal, maybe } from "../../misc"; +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 { decimal } from "../../misc"; import ProductVariantCreatePage, { ProductVariantCreatePageSubmitData } from "../components/ProductVariantCreatePage"; import { TypedVariantCreateMutation } from "../mutations"; import { TypedProductVariantCreateQuery } from "../queries"; import { VariantCreate } from "../types/VariantCreate"; -import { productUrl, productVariantEditUrl, productListUrl } from "../urls"; +import { + productUrl, + productVariantEditUrl, + productListUrl, + productVariantAddUrl, + ProductVariantAddUrlDialog, + ProductVariantAddUrlQueryParams +} from "../urls"; +import ProductWarehousesDialog from "../components/ProductWarehousesDialog"; -interface ProductUpdateProps { +interface ProductVariantCreateProps { + params: ProductVariantAddUrlQueryParams; productId: string; } -export const ProductVariant: React.FC = ({ productId }) => { +export const ProductVariant: React.FC = ({ + params, + productId +}) => { const navigate = useNavigator(); const notify = useNotifier(); const shop = useShop(); const intl = useIntl(); + const { + loadMore: loadMoreWarehouses, + search: searchWarehouses, + result: searchWarehousesOpts + } = useWarehouseSearch({ + variables: { + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 20 + } + }); + const [warehouses, setWarehouses] = React.useState< + SearchWarehouses_search_edges_node[] + >([]); + + const [openModal, closeModal] = createDialogActionHandlers< + ProductVariantAddUrlDialog, + ProductVariantAddUrlQueryParams + >(navigate, params => productVariantAddUrl(productId, params), params); return ( @@ -70,6 +104,10 @@ export const ProductVariant: React.FC = ({ productId }) => { product: productId, quantity: parseInt(formData.quantity, 0), sku: formData.sku, + stocks: formData.stocks.map(stock => ({ + quantity: parseInt(stock.value, 0), + warehouse: stock.id + })), trackInventory: true } } @@ -88,7 +126,8 @@ export const ProductVariant: React.FC = ({ productId }) => { })} /> shop.defaultCurrency)} + currencySymbol={shop?.defaultCurrency} + disabled={productLoading} errors={ variantCreateResult.data?.productVariantCreate.errors || [] @@ -103,6 +142,37 @@ export const ProductVariant: React.FC = ({ productId }) => { 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(); + }} /> );