Add warehouse stock edit prototype

This commit is contained in:
dominik-zeglen 2020-03-23 13:23:29 +01:00
parent 65dedb4263
commit f94fdba7b9
7 changed files with 211 additions and 5 deletions

View file

@ -64,6 +64,7 @@ export interface ProductUpdatePageProps extends ListActions {
saveButtonBarState: ConfirmButtonTransitionState; saveButtonBarState: ConfirmButtonTransitionState;
fetchCategories: (query: string) => void; fetchCategories: (query: string) => void;
fetchCollections: (query: string) => void; fetchCollections: (query: string) => void;
onWarehousesEdit: () => void;
onVariantsAdd: () => void; onVariantsAdd: () => void;
onVariantShow: (id: string) => () => void; onVariantShow: (id: string) => () => void;
onImageDelete: (id: string) => () => void; onImageDelete: (id: string) => () => void;
@ -109,6 +110,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
onVariantAdd, onVariantAdd,
onVariantsAdd, onVariantsAdd,
onVariantShow, onVariantShow,
onWarehousesEdit,
isChecked, isChecked,
selected, selected,
toggle, toggle,
@ -252,6 +254,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
stocks={stocks} stocks={stocks}
onChange={changeStockData} onChange={changeStockData}
onFormDataChange={change} onFormDataChange={change}
onWarehousesEdit={onWarehousesEdit}
/> />
)} )}
<CardSpacer /> <CardSpacer />

View file

@ -7,6 +7,7 @@ import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import makeStyles from "@material-ui/core/styles/makeStyles"; import makeStyles from "@material-ui/core/styles/makeStyles";
import { diff, DiffData } from "fast-array-diff";
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
@ -45,7 +46,7 @@ export interface ProductWarehousesDialogProps {
stocks: Product_variants_stocks[]; stocks: Product_variants_stocks[];
warehouses: SearchWarehouses_search_edges_node[]; warehouses: SearchWarehouses_search_edges_node[];
onClose: () => void; onClose: () => void;
onConfirm: (data: string[]) => void; onConfirm: (data: DiffData<string>) => void;
} }
const ProductWarehousesDialog: React.FC<ProductWarehousesDialogProps> = ({ const ProductWarehousesDialog: React.FC<ProductWarehousesDialogProps> = ({
@ -61,11 +62,12 @@ const ProductWarehousesDialog: React.FC<ProductWarehousesDialogProps> = ({
const classes = useStyles({}); const classes = useStyles({});
const intl = useIntl(); const intl = useIntl();
const initial = stocks?.map(stock => stock.warehouse.id) || [];
const [selectedWarehouses, setSelectedWarehouses] = useStateFromProps( const [selectedWarehouses, setSelectedWarehouses] = useStateFromProps(
stocks?.map(stock => stock.warehouse.id) || [] initial
); );
const handleConfirm = () => onConfirm(selectedWarehouses); const handleConfirm = () => onConfirm(diff(initial, selectedWarehouses));
return ( return (
<Dialog onClose={onClose} maxWidth="sm" fullWidth open={open}> <Dialog onClose={onClose} maxWidth="sm" fullWidth open={open}>

View file

@ -1,6 +1,7 @@
import gql from "graphql-tag"; import gql from "graphql-tag";
import { productErrorFragment } from "@saleor/attributes/mutations"; import { productErrorFragment } from "@saleor/attributes/mutations";
import makeMutation from "@saleor/hooks/makeMutation";
import { TypedMutation } from "../mutations"; import { TypedMutation } from "../mutations";
import { ProductCreate, ProductCreateVariables } from "./types/ProductCreate"; import { ProductCreate, ProductCreateVariables } from "./types/ProductCreate";
import { ProductDelete, ProductDeleteVariables } from "./types/ProductDelete"; import { ProductDelete, ProductDeleteVariables } from "./types/ProductDelete";
@ -37,7 +38,11 @@ import {
} from "./types/VariantImageUnassign"; } from "./types/VariantImageUnassign";
import { VariantUpdate, VariantUpdateVariables } from "./types/VariantUpdate"; import { VariantUpdate, VariantUpdateVariables } from "./types/VariantUpdate";
import { fragmentVariant, productFragmentDetails } from "./queries"; import {
fragmentVariant,
productFragmentDetails,
stockFragment
} from "./queries";
import { import {
productBulkDelete, productBulkDelete,
productBulkDeleteVariables productBulkDeleteVariables
@ -54,6 +59,10 @@ import {
ProductVariantBulkDelete, ProductVariantBulkDelete,
ProductVariantBulkDeleteVariables ProductVariantBulkDeleteVariables
} from "./types/ProductVariantBulkDelete"; } from "./types/ProductVariantBulkDelete";
import {
AddOrRemoveStocks,
AddOrRemoveStocksVariables
} from "./types/AddOrRemoveStocks";
export const bulkProductErrorFragment = gql` export const bulkProductErrorFragment = gql`
fragment BulkProductErrorFragment on BulkProductError { fragment BulkProductErrorFragment on BulkProductError {
@ -488,3 +497,42 @@ export const TypedProductVariantBulkDeleteMutation = TypedMutation<
ProductVariantBulkDelete, ProductVariantBulkDelete,
ProductVariantBulkDeleteVariables ProductVariantBulkDeleteVariables
>(ProductVariantBulkDeleteMutation); >(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);

View file

@ -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[];
}

View file

@ -66,7 +66,11 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string =>
productListPath + "?" + stringifyQs(params); productListPath + "?" + stringifyQs(params);
export const productPath = (id: string) => urlJoin(productSection + id); export const productPath = (id: string) => urlJoin(productSection + id);
export type ProductUrlDialog = "create-variants" | "remove" | "remove-variants"; export type ProductUrlDialog =
| "create-variants"
| "edit-stocks"
| "remove"
| "remove-variants";
export type ProductUrlQueryParams = BulkAction & Dialog<ProductUrlDialog>; export type ProductUrlQueryParams = BulkAction & Dialog<ProductUrlDialog>;
export const productUrl = (id: string, params?: ProductUrlQueryParams) => export const productUrl = (id: string, params?: ProductUrlQueryParams) =>
productPath(encodeURIComponent(id)) + "?" + stringifyQs(params); productPath(encodeURIComponent(id)) + "?" + stringifyQs(params);

View file

@ -19,6 +19,9 @@ import useCategorySearch from "@saleor/searches/useCategorySearch";
import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import NotFoundPage from "@saleor/components/NotFoundPage"; import NotFoundPage from "@saleor/components/NotFoundPage";
import ProductWarehousesDialog from "@saleor/products/components/ProductWarehousesDialog";
import useWarehouseSearch from "@saleor/searches/useWarehouseSearch";
import { useAddOrRemoveStocks } from "@saleor/products/mutations";
import { getMutationState, maybe } from "../../../misc"; import { getMutationState, maybe } from "../../../misc";
import ProductUpdatePage from "../../components/ProductUpdatePage"; import ProductUpdatePage from "../../components/ProductUpdatePage";
import ProductUpdateOperations from "../../containers/ProductUpdateOperations"; import ProductUpdateOperations from "../../containers/ProductUpdateOperations";
@ -71,6 +74,30 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
} = useCollectionSearch({ } = useCollectionSearch({
variables: DEFAULT_INITIAL_SEARCH_DATA 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< const [openModal, closeModal] = createDialogActionHandlers<
ProductUrlDialog, ProductUrlDialog,
@ -273,6 +300,7 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
loading: searchCollectionsOpts.loading, loading: searchCollectionsOpts.loading,
onFetchMore: loadMoreCollections onFetchMore: loadMoreCollections
}} }}
onWarehousesEdit={() => openModal("edit-stocks")}
/> />
<ActionDialog <ActionDialog
open={params.action === "remove"} open={params.action === "remove"}
@ -345,6 +373,35 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
}) })
} }
/> />
{!product?.productType?.hasVariants && (
<ProductWarehousesDialog
confirmButtonState={addOrRemoveStocksOpts.status}
errors={[
...(addOrRemoveStocksOpts.data
?.productVariantStocksCreate.bulkStockErrors || []),
addOrRemoveStocksOpts.data?.productVariantStocksDelete
.stockErrors || []
]}
onClose={closeModal}
stocks={product?.variants[0].stocks || []}
open={params.action === "edit-stocks"}
warehouses={searchWarehousesOpts.data?.search.edges.map(
edge => edge.node
)}
onConfirm={data =>
addOrRemoveStocks({
variables: {
add: data.added.map(id => ({
quantity: 0,
warehouse: id
})),
remove: data.removed,
variantId: product.variants[0].id
}
})
}
/>
)}
</> </>
); );
}} }}

View file

@ -689,6 +689,15 @@ export enum StockAvailability {
OUT_OF_STOCK = "OUT_OF_STOCK", 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 { export enum TaxRateType {
ACCOMMODATION = "ACCOMMODATION", ACCOMMODATION = "ACCOMMODATION",
ADMISSION_TO_CULTURAL_EVENTS = "ADMISSION_TO_CULTURAL_EVENTS", ADMISSION_TO_CULTURAL_EVENTS = "ADMISSION_TO_CULTURAL_EVENTS",