From f94fdba7b957b35853d2d3ea9ce074f99047c131 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 23 Mar 2020 13:23:29 +0100 Subject: [PATCH] Add warehouse stock edit prototype --- .../ProductUpdatePage/ProductUpdatePage.tsx | 3 + .../ProductWarehousesDialog.tsx | 8 +- src/products/mutations.ts | 50 ++++++++++- src/products/types/AddOrRemoveStocks.ts | 83 +++++++++++++++++++ src/products/urls.ts | 6 +- .../views/ProductUpdate/ProductUpdate.tsx | 57 +++++++++++++ src/types/globalTypes.ts | 9 ++ 7 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 src/products/types/AddOrRemoveStocks.ts diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index 53113317f..b3740c60e 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -64,6 +64,7 @@ export interface ProductUpdatePageProps extends ListActions { saveButtonBarState: ConfirmButtonTransitionState; fetchCategories: (query: string) => void; fetchCollections: (query: string) => void; + onWarehousesEdit: () => void; onVariantsAdd: () => void; onVariantShow: (id: string) => () => void; onImageDelete: (id: string) => () => void; @@ -109,6 +110,7 @@ export const ProductUpdatePage: React.FC = ({ onVariantAdd, onVariantsAdd, onVariantShow, + onWarehousesEdit, isChecked, selected, toggle, @@ -252,6 +254,7 @@ export const ProductUpdatePage: React.FC = ({ stocks={stocks} onChange={changeStockData} onFormDataChange={change} + onWarehousesEdit={onWarehousesEdit} /> )} diff --git a/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx b/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx index 3482c28e7..ec602ea12 100644 --- a/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx +++ b/src/products/components/ProductWarehousesDialog/ProductWarehousesDialog.tsx @@ -7,6 +7,7 @@ import Typography from "@material-ui/core/Typography"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import makeStyles from "@material-ui/core/styles/makeStyles"; +import { diff, DiffData } from "fast-array-diff"; import ConfirmButton, { ConfirmButtonTransitionState @@ -45,7 +46,7 @@ export interface ProductWarehousesDialogProps { stocks: Product_variants_stocks[]; warehouses: SearchWarehouses_search_edges_node[]; onClose: () => void; - onConfirm: (data: string[]) => void; + onConfirm: (data: DiffData) => void; } const ProductWarehousesDialog: React.FC = ({ @@ -61,11 +62,12 @@ const ProductWarehousesDialog: React.FC = ({ const classes = useStyles({}); const intl = useIntl(); + const initial = stocks?.map(stock => stock.warehouse.id) || []; const [selectedWarehouses, setSelectedWarehouses] = useStateFromProps( - stocks?.map(stock => stock.warehouse.id) || [] + initial ); - const handleConfirm = () => onConfirm(selectedWarehouses); + const handleConfirm = () => onConfirm(diff(initial, selectedWarehouses)); return ( diff --git a/src/products/mutations.ts b/src/products/mutations.ts index e469e4adf..42605ba11 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -1,6 +1,7 @@ import gql from "graphql-tag"; import { productErrorFragment } from "@saleor/attributes/mutations"; +import makeMutation from "@saleor/hooks/makeMutation"; import { TypedMutation } from "../mutations"; import { ProductCreate, ProductCreateVariables } from "./types/ProductCreate"; import { ProductDelete, ProductDeleteVariables } from "./types/ProductDelete"; @@ -37,7 +38,11 @@ import { } from "./types/VariantImageUnassign"; import { VariantUpdate, VariantUpdateVariables } from "./types/VariantUpdate"; -import { fragmentVariant, productFragmentDetails } from "./queries"; +import { + fragmentVariant, + productFragmentDetails, + stockFragment +} from "./queries"; import { productBulkDelete, productBulkDeleteVariables @@ -54,6 +59,10 @@ import { ProductVariantBulkDelete, ProductVariantBulkDeleteVariables } from "./types/ProductVariantBulkDelete"; +import { + AddOrRemoveStocks, + AddOrRemoveStocksVariables +} from "./types/AddOrRemoveStocks"; export const bulkProductErrorFragment = gql` fragment BulkProductErrorFragment on BulkProductError { @@ -488,3 +497,42 @@ export const TypedProductVariantBulkDeleteMutation = TypedMutation< ProductVariantBulkDelete, ProductVariantBulkDeleteVariables >(ProductVariantBulkDeleteMutation); + +const addOrRemoveStocks = gql` + ${stockFragment} + mutation AddOrRemoveStocks( + $variantId: ID! + $add: [StockInput!]! + $remove: [ID!]! + ) { + productVariantStocksCreate(stocks: $add, variantId: $variantId) { + bulkStockErrors { + code + field + index + } + productVariant { + id + stocks { + ...StockFragment + } + } + } + productVariantStocksDelete(warehouseIds: $remove, variantId: $variantId) { + 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 new file mode 100644 index 000000000..4bf5efc6d --- /dev/null +++ b/src/products/types/AddOrRemoveStocks.ts @@ -0,0 +1,83 @@ +/* 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_bulkStockErrors { + __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; + 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"; + bulkStockErrors: AddOrRemoveStocks_productVariantStocksCreate_bulkStockErrors[]; + productVariant: AddOrRemoveStocks_productVariantStocksCreate_productVariant | null; +} + +export interface AddOrRemoveStocks_productVariantStocksDelete_stockErrors { + __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; + 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"; + stockErrors: AddOrRemoveStocks_productVariantStocksDelete_stockErrors[]; + 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/urls.ts b/src/products/urls.ts index 9441c04a5..5cf5e5d95 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -66,7 +66,11 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string => productListPath + "?" + stringifyQs(params); export const productPath = (id: string) => urlJoin(productSection + id); -export type ProductUrlDialog = "create-variants" | "remove" | "remove-variants"; +export type ProductUrlDialog = + | "create-variants" + | "edit-stocks" + | "remove" + | "remove-variants"; export type ProductUrlQueryParams = BulkAction & Dialog; export const productUrl = (id: string, params?: ProductUrlQueryParams) => productPath(encodeURIComponent(id)) + "?" + stringifyQs(params); diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index 6a509ebd4..b6a1f6f03 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -19,6 +19,9 @@ 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 { getMutationState, maybe } from "../../../misc"; import ProductUpdatePage from "../../components/ProductUpdatePage"; import ProductUpdateOperations from "../../containers/ProductUpdateOperations"; @@ -71,6 +74,30 @@ export const ProductUpdate: React.FC = ({ id, params }) => { } = useCollectionSearch({ variables: DEFAULT_INITIAL_SEARCH_DATA }); + const { + loadMore: loadMoreWarehouses, + search: searchWarehouses, + result: searchWarehousesOpts + } = useWarehouseSearch({ + variables: { + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 20 + } + }); + + const [addOrRemoveStocks, addOrRemoveStocksOpts] = useAddOrRemoveStocks({ + onCompleted: data => { + if ( + data.productVariantStocksCreate.bulkStockErrors.length === 0 && + data.productVariantStocksDelete.stockErrors.length === 0 + ) { + notify({ + text: intl.formatMessage(commonMessages.savedChanges) + }); + closeModal(); + } + } + }); const [openModal, closeModal] = createDialogActionHandlers< ProductUrlDialog, @@ -273,6 +300,7 @@ export const ProductUpdate: React.FC = ({ id, params }) => { loading: searchCollectionsOpts.loading, onFetchMore: loadMoreCollections }} + onWarehousesEdit={() => openModal("edit-stocks")} /> = ({ id, params }) => { }) } /> + {!product?.productType?.hasVariants && ( + edge.node + )} + 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/types/globalTypes.ts b/src/types/globalTypes.ts index 4f0b0a325..229e4662b 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -689,6 +689,15 @@ export enum StockAvailability { OUT_OF_STOCK = "OUT_OF_STOCK", } +export enum StockErrorCode { + ALREADY_EXISTS = "ALREADY_EXISTS", + GRAPHQL_ERROR = "GRAPHQL_ERROR", + INVALID = "INVALID", + NOT_FOUND = "NOT_FOUND", + REQUIRED = "REQUIRED", + UNIQUE = "UNIQUE", +} + export enum TaxRateType { ACCOMMODATION = "ACCOMMODATION", ADMISSION_TO_CULTURAL_EVENTS = "ADMISSION_TO_CULTURAL_EVENTS",