From 11a10686ce8c3ffb3ca9abc718d20c96186306f1 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Thu, 19 Mar 2020 18:15:22 +0100 Subject: [PATCH] Add stock management components to simple product --- .../components/ProductStock/ProductStock.tsx | 96 --------- src/products/components/ProductStock/index.ts | 2 - .../ProductStocks/ProductStocks.tsx | 199 ++++++++++++++++++ .../components/ProductStocks/index.ts | 2 + .../ProductUpdatePage/ProductUpdatePage.tsx | 19 +- .../ProductVariants/ProductVariants.tsx | 16 +- src/products/fixtures.ts | 12 +- src/products/queries.ts | 2 + src/products/types/Product.ts | 1 + src/products/types/ProductCreate.ts | 1 + src/products/types/ProductDetails.ts | 1 + src/products/types/ProductImageCreate.ts | 1 + src/products/types/ProductImageUpdate.ts | 1 + src/products/types/ProductUpdate.ts | 1 + src/products/types/ProductVariant.ts | 1 + src/products/types/ProductVariantDetails.ts | 1 + src/products/types/SimpleProductUpdate.ts | 2 + src/products/types/VariantCreate.ts | 1 + src/products/types/VariantImageAssign.ts | 1 + src/products/types/VariantImageUnassign.ts | 1 + src/products/types/VariantUpdate.ts | 1 + src/products/utils/data.ts | 24 ++- .../views/ProductUpdate/ProductUpdate.tsx | 5 - 23 files changed, 256 insertions(+), 135 deletions(-) delete mode 100644 src/products/components/ProductStock/ProductStock.tsx delete mode 100644 src/products/components/ProductStock/index.ts create mode 100644 src/products/components/ProductStocks/ProductStocks.tsx create mode 100644 src/products/components/ProductStocks/index.ts diff --git a/src/products/components/ProductStock/ProductStock.tsx b/src/products/components/ProductStock/ProductStock.tsx deleted file mode 100644 index 6fce4b609..000000000 --- a/src/products/components/ProductStock/ProductStock.tsx +++ /dev/null @@ -1,96 +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 { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; -import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; -import { ProductDetails_product } from "../../types/ProductDetails"; - -const useStyles = makeStyles( - theme => ({ - root: { - display: "grid", - gridColumnGap: theme.spacing(2), - gridTemplateColumns: "1fr 1fr" - } - }), - { name: "ProductStock" } -); - -interface ProductStockProps { - data: { - sku: string; - stockQuantity: number; - }; - disabled: boolean; - errors: ProductErrorFragment[]; - product: ProductDetails_product; - onChange: (event: React.ChangeEvent) => void; -} - -const ProductStock: React.FC = props => { - const { data, disabled, product, onChange, errors } = props; - - const classes = useStyles(props); - const intl = useIntl(); - - const formErrors = getFormErrors(["sku", "stockQuantity"], errors); - - return ( - - - -
- - -
-
-
- ); -}; -ProductStock.displayName = "ProductStock"; -export default ProductStock; diff --git a/src/products/components/ProductStock/index.ts b/src/products/components/ProductStock/index.ts deleted file mode 100644 index f98d67440..000000000 --- a/src/products/components/ProductStock/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from "./ProductStock"; -export * from "./ProductStock"; diff --git a/src/products/components/ProductStocks/ProductStocks.tsx b/src/products/components/ProductStocks/ProductStocks.tsx new file mode 100644 index 000000000..02f14b64a --- /dev/null +++ b/src/products/components/ProductStocks/ProductStocks.tsx @@ -0,0 +1,199 @@ +import Button from "@material-ui/core/Button"; +import Card from "@material-ui/core/Card"; +import CardContent from "@material-ui/core/CardContent"; +import Table from "@material-ui/core/Table"; +import TableHead from "@material-ui/core/TableHead"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +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 { FormChange } from "@saleor/hooks/useForm"; +import { FormsetChange, FormsetAtomicData } from "@saleor/hooks/useFormset"; +import CardTitle from "@saleor/components/CardTitle"; +import { getFieldError } from "@saleor/utils/errors"; +import { UserError } from "@saleor/types"; +import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; +import FormSpacer from "@saleor/components/FormSpacer"; +import Hr from "@saleor/components/Hr"; +import { renderCollection } from "@saleor/misc"; + +export type ProductStockInput = FormsetAtomicData; +export interface ProductStockFormData { + sku: string; + trackInventory: boolean; +} + +export interface ProductStocksProps { + data: ProductStockFormData; + disabled: boolean; + errors: UserError[]; + stocks: ProductStockInput[]; + onChange: FormsetChange; + onFormDataChange: FormChange; + onWarehousesEdit: () => undefined; +} + +const useStyles = makeStyles( + theme => ({ + colName: {}, + colQuantity: { + textAlign: "right", + width: 200 + }, + editWarehouses: { + marginRight: -theme.spacing() + }, + input: { + padding: theme.spacing(1.5), + textAlign: "right" + }, + inputComponent: { + width: 100 + }, + quantityContainer: { + paddingTop: theme.spacing() + }, + quantityHeader: { + alignItems: "center", + display: "flex", + justifyContent: "space-between" + }, + skuInputContainer: { + display: "grid", + gridColumnGap: theme.spacing(3) + "px", + gridTemplateColumns: "repeat(2, 1fr)" + } + }), + { + name: "ProductStocks" + } +); + +const ProductStocks: React.FC = ({ + data, + disabled, + errors, + stocks, + onChange, + onFormDataChange, + onWarehousesEdit +}) => { + const classes = useStyles({}); + const intl = useIntl(); + + return ( + + + +
+ +
+ + + + + + + + } + /> +
+
+ + +
+ + + + +
+
+
+ + + + + + + + + + + + + {renderCollection(stocks, stock => ( + + {stock.label} + + onChange(stock.id, event.target.value)} + value={stock.value} + /> + + + ))} + +
+
+ ); +}; + +ProductStocks.displayName = "ProductStocks"; +export default ProductStocks; diff --git a/src/products/components/ProductStocks/index.ts b/src/products/components/ProductStocks/index.ts new file mode 100644 index 000000000..ef1830648 --- /dev/null +++ b/src/products/components/ProductStocks/index.ts @@ -0,0 +1,2 @@ +export * from "./ProductStocks"; +export { default } from "./ProductStocks"; diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index 0e7362403..53113317f 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -34,7 +34,8 @@ import { getProductUpdatePageFormData, getSelectedAttributesFromProduct, ProductAttributeValueChoices, - ProductUpdatePageFormData + ProductUpdatePageFormData, + getStockInputFromProduct } from "../../utils/data"; import { createAttributeChangeHandler, @@ -45,8 +46,8 @@ import ProductDetailsForm from "../ProductDetailsForm"; import ProductImages from "../ProductImages"; import ProductOrganization from "../ProductOrganization"; import ProductPricing from "../ProductPricing"; -import ProductStock from "../ProductStock"; import ProductVariants from "../ProductVariants"; +import ProductStocks, { ProductStockInput } from "../ProductStocks"; export interface ProductUpdatePageProps extends ListActions { errors: ProductErrorFragment[]; @@ -71,7 +72,6 @@ export interface ProductUpdatePageProps extends ListActions { onImageEdit?(id: string); onImageReorder?(event: { oldIndex: number; newIndex: number }); onImageUpload(file: File); - onProductShow?(); onSeoClick?(); onSubmit?(data: ProductUpdatePageSubmitData); onVariantAdd?(); @@ -80,6 +80,7 @@ export interface ProductUpdatePageProps extends ListActions { export interface ProductUpdatePageSubmitData extends ProductUpdatePageFormData { attributes: ProductAttributeInput[]; collections: string[]; + stocks: ProductStockInput[]; } export const ProductUpdatePage: React.FC = ({ @@ -120,9 +121,13 @@ export const ProductUpdatePage: React.FC = ({ () => getAttributeInputFromProduct(product), [product] ); + const stockInput = React.useMemo(() => getStockInputFromProduct(product), [ + product + ]); const { change: changeAttributeData, data: attributes } = useFormset( attributeInput ); + const { change: changeStockData, data: stocks } = useFormset(stockInput); const [selectedAttributes, setSelectedAttributes] = useStateFromProps< ProductAttributeValueChoices[] @@ -149,6 +154,7 @@ export const ProductUpdatePage: React.FC = ({ const handleSubmit = (data: ProductUpdatePageFormData) => onSubmit({ attributes, + stocks, ...data }); @@ -239,12 +245,13 @@ export const ProductUpdatePage: React.FC = ({ toggleAll={toggleAll} /> ) : ( - )} diff --git a/src/products/components/ProductVariants/ProductVariants.tsx b/src/products/components/ProductVariants/ProductVariants.tsx index f92aff08f..da53ce059 100644 --- a/src/products/components/ProductVariants/ProductVariants.tsx +++ b/src/products/components/ProductVariants/ProductVariants.tsx @@ -22,7 +22,7 @@ import { maybe, renderCollection } from "../../../misc"; import { ListActions } from "../../../types"; import { ProductDetails_product_variants, - ProductDetails_product_variants_stock_warehouse + ProductDetails_product_variants_stocks_warehouse } from "../../types/ProductDetails"; import { ProductVariant_costPrice } from "../../types/ProductVariant"; @@ -39,11 +39,11 @@ function getWarehouseChoices( value: null }, ...variants - .reduce( + .reduce( (warehouses, variant) => [ ...warehouses, - ...variant.stock.reduce< - ProductDetails_product_variants_stock_warehouse[] + ...variant.stocks.reduce< + ProductDetails_product_variants_stocks_warehouse[] >((variantStocks, stock) => { if (!!warehouses.find(w => w.id === stock.warehouse.id)) { return variantStocks; @@ -118,7 +118,7 @@ function getAvailabilityLabel( variant: ProductDetails_product_variants, numAvailable: number ): string { - const variantStock = variant.stock.find(s => s.warehouse.id === warehouse); + const variantStock = variant.stocks.find(s => s.warehouse.id === warehouse); if (!!warehouse) { if (!!variantStock) { @@ -155,7 +155,7 @@ function getAvailabilityLabel( }, { numAvailable, - numLocations: variant.stock.length + numLocations: variant.stocks.length } ); } else { @@ -295,8 +295,8 @@ export const ProductVariants: React.FC = props => { {renderCollection(variants, variant => { const isSelected = variant ? isChecked(variant.id) : false; const numAvailable = - variant && variant.stock - ? variant.stock.reduce((acc, s) => acc + s.quantity, 0) + variant && variant.stocks + ? variant.stocks.reduce((acc, s) => acc + s.quantity, 0) : null; return ( diff --git a/src/products/fixtures.ts b/src/products/fixtures.ts index 34355ff65..b45ac7bd4 100644 --- a/src/products/fixtures.ts +++ b/src/products/fixtures.ts @@ -260,10 +260,8 @@ export const product: ( amount: 678.78, currency: "USD" }, - quantity: 12, - quantityAllocated: 1, sku: "87192-94370", - stock: [ + stocks: [ { __typename: "Stock", id: "1", @@ -277,7 +275,7 @@ export const product: ( warehouse: warehouseList[1] } ], - stockQuantity: 48 + trackInventory: true }, { __typename: "ProductVariant", @@ -297,10 +295,8 @@ export const product: ( margin: 7, name: "silver", priceOverride: null, - quantity: 12, - quantityAllocated: 1, sku: "69055-15190", - stock: [ + stocks: [ { __typename: "Stock", id: "1", @@ -308,7 +304,7 @@ export const product: ( warehouse: warehouseList[0] } ], - stockQuantity: 14 + trackInventory: false } ] }); diff --git a/src/products/queries.ts b/src/products/queries.ts index a4b792488..a14d917ca 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -158,6 +158,7 @@ export const productFragmentDetails = gql` stocks { ...StockFragment } + trackInventory } productType { id @@ -225,6 +226,7 @@ export const fragmentVariant = gql` stocks { ...StockFragment } + trackInventory } `; diff --git a/src/products/types/Product.ts b/src/products/types/Product.ts index 66711a57d..7b1ce054e 100644 --- a/src/products/types/Product.ts +++ b/src/products/types/Product.ts @@ -148,6 +148,7 @@ export interface Product_variants { priceOverride: Product_variants_priceOverride | null; margin: number | null; stocks: (Product_variants_stocks | null)[] | null; + trackInventory: boolean; } export interface Product_productType { diff --git a/src/products/types/ProductCreate.ts b/src/products/types/ProductCreate.ts index 3782b4045..32cdd2304 100644 --- a/src/products/types/ProductCreate.ts +++ b/src/products/types/ProductCreate.ts @@ -154,6 +154,7 @@ export interface ProductCreate_productCreate_product_variants { priceOverride: ProductCreate_productCreate_product_variants_priceOverride | null; margin: number | null; stocks: (ProductCreate_productCreate_product_variants_stocks | null)[] | null; + trackInventory: boolean; } export interface ProductCreate_productCreate_product_productType { diff --git a/src/products/types/ProductDetails.ts b/src/products/types/ProductDetails.ts index 89ea83b2f..c426f408a 100644 --- a/src/products/types/ProductDetails.ts +++ b/src/products/types/ProductDetails.ts @@ -148,6 +148,7 @@ export interface ProductDetails_product_variants { priceOverride: ProductDetails_product_variants_priceOverride | null; margin: number | null; stocks: (ProductDetails_product_variants_stocks | null)[] | null; + trackInventory: boolean; } export interface ProductDetails_product_productType_variantAttributes_values { diff --git a/src/products/types/ProductImageCreate.ts b/src/products/types/ProductImageCreate.ts index f56195a1a..fcbb37415 100644 --- a/src/products/types/ProductImageCreate.ts +++ b/src/products/types/ProductImageCreate.ts @@ -154,6 +154,7 @@ export interface ProductImageCreate_productImageCreate_product_variants { priceOverride: ProductImageCreate_productImageCreate_product_variants_priceOverride | null; margin: number | null; stocks: (ProductImageCreate_productImageCreate_product_variants_stocks | null)[] | null; + trackInventory: boolean; } export interface ProductImageCreate_productImageCreate_product_productType { diff --git a/src/products/types/ProductImageUpdate.ts b/src/products/types/ProductImageUpdate.ts index 2f4d3a773..0bca7c7c9 100644 --- a/src/products/types/ProductImageUpdate.ts +++ b/src/products/types/ProductImageUpdate.ts @@ -154,6 +154,7 @@ export interface ProductImageUpdate_productImageUpdate_product_variants { priceOverride: ProductImageUpdate_productImageUpdate_product_variants_priceOverride | null; margin: number | null; stocks: (ProductImageUpdate_productImageUpdate_product_variants_stocks | null)[] | null; + trackInventory: boolean; } export interface ProductImageUpdate_productImageUpdate_product_productType { diff --git a/src/products/types/ProductUpdate.ts b/src/products/types/ProductUpdate.ts index e62b1e88d..90b5615c1 100644 --- a/src/products/types/ProductUpdate.ts +++ b/src/products/types/ProductUpdate.ts @@ -154,6 +154,7 @@ export interface ProductUpdate_productUpdate_product_variants { priceOverride: ProductUpdate_productUpdate_product_variants_priceOverride | null; margin: number | null; stocks: (ProductUpdate_productUpdate_product_variants_stocks | null)[] | null; + trackInventory: boolean; } export interface ProductUpdate_productUpdate_product_productType { diff --git a/src/products/types/ProductVariant.ts b/src/products/types/ProductVariant.ts index a2dee190f..9a9d2e743 100644 --- a/src/products/types/ProductVariant.ts +++ b/src/products/types/ProductVariant.ts @@ -113,4 +113,5 @@ export interface ProductVariant { product: ProductVariant_product; sku: string; stocks: (ProductVariant_stocks | null)[] | null; + trackInventory: boolean; } diff --git a/src/products/types/ProductVariantDetails.ts b/src/products/types/ProductVariantDetails.ts index 441436ec9..1bd3a8d52 100644 --- a/src/products/types/ProductVariantDetails.ts +++ b/src/products/types/ProductVariantDetails.ts @@ -113,6 +113,7 @@ export interface ProductVariantDetails_productVariant { product: ProductVariantDetails_productVariant_product; sku: string; stocks: (ProductVariantDetails_productVariant_stocks | null)[] | null; + trackInventory: boolean; } export interface ProductVariantDetails { diff --git a/src/products/types/SimpleProductUpdate.ts b/src/products/types/SimpleProductUpdate.ts index 90aaf877f..d9d137e59 100644 --- a/src/products/types/SimpleProductUpdate.ts +++ b/src/products/types/SimpleProductUpdate.ts @@ -154,6 +154,7 @@ export interface SimpleProductUpdate_productUpdate_product_variants { priceOverride: SimpleProductUpdate_productUpdate_product_variants_priceOverride | null; margin: number | null; stocks: (SimpleProductUpdate_productUpdate_product_variants_stocks | null)[] | null; + trackInventory: boolean; } export interface SimpleProductUpdate_productUpdate_product_productType { @@ -305,6 +306,7 @@ export interface SimpleProductUpdate_productVariantUpdate_productVariant { product: SimpleProductUpdate_productVariantUpdate_productVariant_product; sku: string; stocks: (SimpleProductUpdate_productVariantUpdate_productVariant_stocks | null)[] | null; + trackInventory: boolean; } export interface SimpleProductUpdate_productVariantUpdate { diff --git a/src/products/types/VariantCreate.ts b/src/products/types/VariantCreate.ts index dc2ddc181..67d363617 100644 --- a/src/products/types/VariantCreate.ts +++ b/src/products/types/VariantCreate.ts @@ -121,6 +121,7 @@ export interface VariantCreate_productVariantCreate_productVariant { product: VariantCreate_productVariantCreate_productVariant_product; sku: string; stocks: (VariantCreate_productVariantCreate_productVariant_stocks | null)[] | null; + trackInventory: boolean; } export interface VariantCreate_productVariantCreate { diff --git a/src/products/types/VariantImageAssign.ts b/src/products/types/VariantImageAssign.ts index a0edcd624..f85d92ee4 100644 --- a/src/products/types/VariantImageAssign.ts +++ b/src/products/types/VariantImageAssign.ts @@ -121,6 +121,7 @@ export interface VariantImageAssign_variantImageAssign_productVariant { product: VariantImageAssign_variantImageAssign_productVariant_product; sku: string; stocks: (VariantImageAssign_variantImageAssign_productVariant_stocks | null)[] | null; + trackInventory: boolean; } export interface VariantImageAssign_variantImageAssign { diff --git a/src/products/types/VariantImageUnassign.ts b/src/products/types/VariantImageUnassign.ts index 0d532d910..294514c50 100644 --- a/src/products/types/VariantImageUnassign.ts +++ b/src/products/types/VariantImageUnassign.ts @@ -121,6 +121,7 @@ export interface VariantImageUnassign_variantImageUnassign_productVariant { product: VariantImageUnassign_variantImageUnassign_productVariant_product; sku: string; stocks: (VariantImageUnassign_variantImageUnassign_productVariant_stocks | null)[] | null; + trackInventory: boolean; } export interface VariantImageUnassign_variantImageUnassign { diff --git a/src/products/types/VariantUpdate.ts b/src/products/types/VariantUpdate.ts index cd7b5644e..c0deeabd3 100644 --- a/src/products/types/VariantUpdate.ts +++ b/src/products/types/VariantUpdate.ts @@ -121,6 +121,7 @@ export interface VariantUpdate_productVariantUpdate_productVariant { product: VariantUpdate_productVariantUpdate_productVariant_product; sku: string; stocks: (VariantUpdate_productVariantUpdate_productVariant_stocks | null)[] | null; + trackInventory: boolean; } export interface VariantUpdate_productVariantUpdate { diff --git a/src/products/utils/data.ts b/src/products/utils/data.ts index 37036d1a6..23c19429c 100644 --- a/src/products/utils/data.ts +++ b/src/products/utils/data.ts @@ -13,6 +13,7 @@ import { ProductAttributeInput } from "../components/ProductAttributes"; import { VariantAttributeInput } from "../components/ProductVariantAttributes"; import { ProductVariant } from "../types/ProductVariant"; import { ProductVariantCreateData_product } from "../types/ProductVariantCreateData"; +import { ProductStockInput } from "../components/ProductStocks"; export interface Collection { id: string; @@ -117,6 +118,17 @@ export function getVariantAttributeInputFromProduct( ); } +export function getStockInputFromProduct( + product: ProductDetails_product +): ProductStockInput[] { + return product?.variants[0].stocks.map(stock => ({ + data: null, + id: stock.warehouse.id, + label: stock.warehouse.name, + value: stock.quantity.toString() + })); +} + export function getCollectionInput( productCollections: ProductDetails_product_collections[] ): Collection[] { @@ -153,7 +165,7 @@ export interface ProductUpdatePageFormData { seoDescription: string; seoTitle: string; sku: string; - stockQuantity: number; + trackInventory: boolean; } export function getProductUpdatePageFormData( @@ -183,14 +195,6 @@ export function getProductUpdatePageFormData( : undefined, "" ), - stockQuantity: maybe( - () => - product.productType.hasVariants - ? undefined - : variants && variants[0] - ? variants[0].quantity - : undefined, - 0 - ) + trackInventory: !!product?.variants[0]?.trackInventory }; } diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index 4de424226..6a509ebd4 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -232,11 +232,6 @@ export const ProductUpdate: React.FC = ({ id, params }) => { variants={maybe(() => product.variants)} onBack={handleBack} onDelete={() => openModal("remove")} - onProductShow={() => { - if (product) { - window.open(product.url); - } - }} onImageReorder={handleImageReorder} onSubmit={handleSubmit} onVariantAdd={handleVariantAdd}