Merge pull request #667 from mirumee/ref/product-update-hooks
Use hooks instead of containers with render props in product mutations
This commit is contained in:
commit
03f03d436b
13 changed files with 1156 additions and 1479 deletions
|
@ -27,6 +27,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
||||||
- Add warehouse choice - #646 by @dominik-zeglen
|
- Add warehouse choice - #646 by @dominik-zeglen
|
||||||
- Fix user management modal actions - #637 by @eaglesemanation
|
- Fix user management modal actions - #637 by @eaglesemanation
|
||||||
- Fix navigator button rendering on safari browser - #656 by @dominik-zeglen
|
- Fix navigator button rendering on safari browser - #656 by @dominik-zeglen
|
||||||
|
- Use hooks instead of containers with render props in product mutations - #667 by @dominik-zeglen
|
||||||
|
|
||||||
## 2.10.1
|
## 2.10.1
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { PAGINATE_BY } from "../../config";
|
import { PAGINATE_BY } from "../../config";
|
||||||
import { maybe } from "../../misc";
|
import { maybe } from "../../misc";
|
||||||
import { TypedProductBulkDeleteMutation } from "../../products/mutations";
|
import { useProductBulkDeleteMutation } from "../../products/mutations";
|
||||||
import { productBulkDelete } from "../../products/types/productBulkDelete";
|
|
||||||
import { productAddUrl, productUrl } from "../../products/urls";
|
import { productAddUrl, productUrl } from "../../products/urls";
|
||||||
import { CategoryInput } from "../../types/globalTypes";
|
import { CategoryInput } from "../../types/globalTypes";
|
||||||
import {
|
import {
|
||||||
|
@ -129,6 +128,23 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
onCompleted: handleBulkCategoryDelete
|
onCompleted: handleBulkCategoryDelete
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
productBulkDelete,
|
||||||
|
productBulkDeleteOpts
|
||||||
|
] = useProductBulkDeleteMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.productBulkDelete.errors.length === 0) {
|
||||||
|
closeModal();
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const changeTab = (tabName: CategoryPageTab) => {
|
const changeTab = (tabName: CategoryPageTab) => {
|
||||||
reset();
|
reset();
|
||||||
navigate(
|
navigate(
|
||||||
|
@ -143,18 +159,6 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
CategoryUrlQueryParams
|
CategoryUrlQueryParams
|
||||||
>(navigate, params => categoryUrl(id, params), params);
|
>(navigate, params => categoryUrl(id, params), params);
|
||||||
|
|
||||||
const handleBulkProductDelete = (data: productBulkDelete) => {
|
|
||||||
if (data.productBulkDelete.errors.length === 0) {
|
|
||||||
closeModal();
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
refetch();
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
params.activeTab === CategoryPageTab.categories
|
params.activeTab === CategoryPageTab.categories
|
||||||
? maybe(() => data.category.children.pageInfo)
|
? maybe(() => data.category.children.pageInfo)
|
||||||
|
@ -166,191 +170,178 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle title={maybe(() => data.category.name)} />
|
<WindowTitle title={maybe(() => data.category.name)} />
|
||||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkProductDelete}>
|
<CategoryUpdatePage
|
||||||
{(productBulkDelete, productBulkDeleteOpts) => (
|
changeTab={changeTab}
|
||||||
<>
|
currentTab={params.activeTab}
|
||||||
<CategoryUpdatePage
|
category={maybe(() => data.category)}
|
||||||
changeTab={changeTab}
|
disabled={loading}
|
||||||
currentTab={params.activeTab}
|
errors={updateResult.data?.categoryUpdate.errors || []}
|
||||||
category={maybe(() => data.category)}
|
onAddCategory={() => navigate(categoryAddUrl(id))}
|
||||||
disabled={loading}
|
onAddProduct={() => navigate(productAddUrl)}
|
||||||
errors={updateResult.data?.categoryUpdate.errors || []}
|
onBack={() =>
|
||||||
onAddCategory={() => navigate(categoryAddUrl(id))}
|
navigate(
|
||||||
onAddProduct={() => navigate(productAddUrl)}
|
maybe(() => categoryUrl(data.category.parent.id), categoryListUrl())
|
||||||
onBack={() =>
|
)
|
||||||
navigate(
|
}
|
||||||
maybe(
|
onCategoryClick={id => () => navigate(categoryUrl(id))}
|
||||||
() => categoryUrl(data.category.parent.id),
|
onDelete={() => openModal("delete")}
|
||||||
categoryListUrl()
|
onImageDelete={() =>
|
||||||
)
|
updateCategory({
|
||||||
)
|
variables: {
|
||||||
|
id,
|
||||||
|
input: {
|
||||||
|
backgroundImage: null
|
||||||
}
|
}
|
||||||
onCategoryClick={id => () => navigate(categoryUrl(id))}
|
}
|
||||||
onDelete={() => openModal("delete")}
|
})
|
||||||
onImageDelete={() =>
|
}
|
||||||
updateCategory({
|
onImageUpload={file =>
|
||||||
variables: {
|
updateCategory({
|
||||||
id,
|
variables: {
|
||||||
input: {
|
id,
|
||||||
backgroundImage: null
|
input: {
|
||||||
}
|
backgroundImage: file
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
onImageUpload={file =>
|
}
|
||||||
updateCategory({
|
})
|
||||||
variables: {
|
}
|
||||||
id,
|
onNextPage={loadNextPage}
|
||||||
input: {
|
onPreviousPage={loadPreviousPage}
|
||||||
backgroundImage: file
|
pageInfo={pageInfo}
|
||||||
}
|
onProductClick={id => () => navigate(productUrl(id))}
|
||||||
}
|
onSubmit={formData =>
|
||||||
})
|
updateCategory({
|
||||||
|
variables: {
|
||||||
|
id,
|
||||||
|
input: {
|
||||||
|
backgroundImageAlt: formData.backgroundImageAlt,
|
||||||
|
descriptionJson: JSON.stringify(formData.description),
|
||||||
|
name: formData.name,
|
||||||
|
seo: {
|
||||||
|
description: formData.seoDescription,
|
||||||
|
title: formData.seoTitle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
onNextPage={loadNextPage}
|
}
|
||||||
onPreviousPage={loadPreviousPage}
|
})
|
||||||
pageInfo={pageInfo}
|
}
|
||||||
onProductClick={id => () => navigate(productUrl(id))}
|
products={maybe(() =>
|
||||||
onSubmit={formData =>
|
data.category.products.edges.map(edge => edge.node)
|
||||||
updateCategory({
|
|
||||||
variables: {
|
|
||||||
id,
|
|
||||||
input: {
|
|
||||||
backgroundImageAlt: formData.backgroundImageAlt,
|
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
|
||||||
name: formData.name,
|
|
||||||
seo: {
|
|
||||||
description: formData.seoDescription,
|
|
||||||
title: formData.seoTitle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
products={maybe(() =>
|
|
||||||
data.category.products.edges.map(edge => edge.node)
|
|
||||||
)}
|
|
||||||
saveButtonBarState={updateResult.status}
|
|
||||||
subcategories={maybe(() =>
|
|
||||||
data.category.children.edges.map(edge => edge.node)
|
|
||||||
)}
|
|
||||||
subcategoryListToolbar={
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("delete-categories", {
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
productListToolbar={
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("delete-products", {
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
/>
|
|
||||||
<ActionDialog
|
|
||||||
confirmButtonState={deleteResult.status}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() => deleteCategory({ variables: { id } })}
|
|
||||||
open={params.action === "delete"}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete category",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
variant="delete"
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Are you sure you want to delete {categoryName}?"
|
|
||||||
values={{
|
|
||||||
categoryName: (
|
|
||||||
<strong>{maybe(() => data.category.name, "...")}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage defaultMessage="Remember this will also unpin all products assigned to this category, making them unavailable in storefront." />
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
open={
|
|
||||||
params.action === "delete-categories" &&
|
|
||||||
maybe(() => params.ids.length > 0)
|
|
||||||
}
|
|
||||||
confirmButtonState={categoryBulkDeleteOpts.status}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
categoryBulkDelete({
|
|
||||||
variables: { ids: params.ids }
|
|
||||||
}).then(() => refetch())
|
|
||||||
}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete categories",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
variant="delete"
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this category?} other{Are you sure you want to delete {displayQuantity} categories?}}"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
open={params.action === "delete-products"}
|
|
||||||
confirmButtonState={productBulkDeleteOpts.status}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
productBulkDelete({
|
|
||||||
variables: { ids: params.ids }
|
|
||||||
}).then(() => refetch())
|
|
||||||
}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete products",
|
|
||||||
description: "dialog title"
|
|
||||||
})}
|
|
||||||
variant="delete"
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this product?} other{Are you sure you want to delete {displayQuantity} products?}}"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</TypedProductBulkDeleteMutation>
|
saveButtonBarState={updateResult.status}
|
||||||
|
subcategories={maybe(() =>
|
||||||
|
data.category.children.edges.map(edge => edge.node)
|
||||||
|
)}
|
||||||
|
subcategoryListToolbar={
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
onClick={() =>
|
||||||
|
openModal("delete-categories", {
|
||||||
|
ids: listElements
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
productListToolbar={
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
onClick={() =>
|
||||||
|
openModal("delete-products", {
|
||||||
|
ids: listElements
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
isChecked={isSelected}
|
||||||
|
selected={listElements.length}
|
||||||
|
toggle={toggle}
|
||||||
|
toggleAll={toggleAll}
|
||||||
|
/>
|
||||||
|
<ActionDialog
|
||||||
|
confirmButtonState={deleteResult.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={() => deleteCategory({ variables: { id } })}
|
||||||
|
open={params.action === "delete"}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Delete category",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
variant="delete"
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Are you sure you want to delete {categoryName}?"
|
||||||
|
values={{
|
||||||
|
categoryName: (
|
||||||
|
<strong>{maybe(() => data.category.name, "...")}</strong>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage defaultMessage="Remember this will also unpin all products assigned to this category, making them unavailable in storefront." />
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
<ActionDialog
|
||||||
|
open={
|
||||||
|
params.action === "delete-categories" &&
|
||||||
|
maybe(() => params.ids.length > 0)
|
||||||
|
}
|
||||||
|
confirmButtonState={categoryBulkDeleteOpts.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={() =>
|
||||||
|
categoryBulkDelete({
|
||||||
|
variables: { ids: params.ids }
|
||||||
|
}).then(() => refetch())
|
||||||
|
}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Delete categories",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
variant="delete"
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this category?} other{Are you sure you want to delete {displayQuantity} categories?}}"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
<ActionDialog
|
||||||
|
open={params.action === "delete-products"}
|
||||||
|
confirmButtonState={productBulkDeleteOpts.status}
|
||||||
|
onClose={closeModal}
|
||||||
|
onConfirm={() =>
|
||||||
|
productBulkDelete({
|
||||||
|
variables: { ids: params.ids }
|
||||||
|
}).then(() => refetch())
|
||||||
|
}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Delete products",
|
||||||
|
description: "dialog title"
|
||||||
|
})}
|
||||||
|
variant="delete"
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this product?} other{Are you sure you want to delete {displayQuantity} products?}}"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { TypedMutationInnerProps } from "../../mutations";
|
import { TypedMutationInnerProps } from "../../mutations";
|
||||||
import { TypedProductImagesReorder } from "../mutations";
|
import { useProductImagesReorder } from "../mutations";
|
||||||
import {
|
import {
|
||||||
ProductImageReorder,
|
ProductImageReorder,
|
||||||
ProductImageReorderVariables
|
ProductImageReorderVariables
|
||||||
|
@ -24,10 +24,12 @@ const ProductImagesReorderProvider: React.FC<ProductImagesReorderProviderProps>
|
||||||
productId,
|
productId,
|
||||||
productImages,
|
productImages,
|
||||||
...mutationProps
|
...mutationProps
|
||||||
}) => (
|
}) => {
|
||||||
<TypedProductImagesReorder {...mutationProps}>
|
const [mutate, mutationResult] = useProductImagesReorder(mutationProps);
|
||||||
{(mutate, mutationResult) =>
|
|
||||||
children(opts => {
|
return (
|
||||||
|
<>
|
||||||
|
{children(opts => {
|
||||||
const productImagesMap = productImages.reduce((prev, curr) => {
|
const productImagesMap = productImages.reduce((prev, curr) => {
|
||||||
prev[curr.id] = curr;
|
prev[curr.id] = curr;
|
||||||
return prev;
|
return prev;
|
||||||
|
@ -52,9 +54,9 @@ const ProductImagesReorderProvider: React.FC<ProductImagesReorderProviderProps>
|
||||||
...opts,
|
...opts,
|
||||||
optimisticResponse
|
optimisticResponse
|
||||||
});
|
});
|
||||||
}, mutationResult)
|
}, mutationResult)}
|
||||||
}
|
</>
|
||||||
</TypedProductImagesReorder>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
export default ProductImagesReorderProvider;
|
export default ProductImagesReorderProvider;
|
||||||
|
|
|
@ -1,153 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { getMutationProviderData, maybe } from "../../misc";
|
|
||||||
import { PartialMutationProviderOutput } from "../../types";
|
|
||||||
import {
|
|
||||||
TypedProductDeleteMutation,
|
|
||||||
TypedProductImageCreateMutation,
|
|
||||||
TypedProductImageDeleteMutation,
|
|
||||||
TypedProductUpdateMutation,
|
|
||||||
TypedProductVariantBulkDeleteMutation,
|
|
||||||
TypedSimpleProductUpdateMutation
|
|
||||||
} from "../mutations";
|
|
||||||
import { ProductDelete, ProductDeleteVariables } from "../types/ProductDelete";
|
|
||||||
import { ProductDetails_product } from "../types/ProductDetails";
|
|
||||||
import {
|
|
||||||
ProductImageCreate,
|
|
||||||
ProductImageCreateVariables
|
|
||||||
} from "../types/ProductImageCreate";
|
|
||||||
import {
|
|
||||||
ProductImageDelete,
|
|
||||||
ProductImageDeleteVariables
|
|
||||||
} from "../types/ProductImageDelete";
|
|
||||||
import {
|
|
||||||
ProductImageReorder,
|
|
||||||
ProductImageReorderVariables
|
|
||||||
} from "../types/ProductImageReorder";
|
|
||||||
import { ProductUpdate, ProductUpdateVariables } from "../types/ProductUpdate";
|
|
||||||
import {
|
|
||||||
ProductVariantBulkDelete,
|
|
||||||
ProductVariantBulkDeleteVariables
|
|
||||||
} from "../types/ProductVariantBulkDelete";
|
|
||||||
import {
|
|
||||||
SimpleProductUpdate,
|
|
||||||
SimpleProductUpdateVariables
|
|
||||||
} from "../types/SimpleProductUpdate";
|
|
||||||
import ProductImagesReorderProvider from "./ProductImagesReorder";
|
|
||||||
|
|
||||||
interface ProductUpdateOperationsProps {
|
|
||||||
product: ProductDetails_product;
|
|
||||||
children: (props: {
|
|
||||||
bulkProductVariantDelete: PartialMutationProviderOutput<
|
|
||||||
ProductVariantBulkDelete,
|
|
||||||
ProductVariantBulkDeleteVariables
|
|
||||||
>;
|
|
||||||
createProductImage: PartialMutationProviderOutput<
|
|
||||||
ProductImageCreate,
|
|
||||||
ProductImageCreateVariables
|
|
||||||
>;
|
|
||||||
deleteProduct: PartialMutationProviderOutput<
|
|
||||||
ProductDelete,
|
|
||||||
ProductDeleteVariables
|
|
||||||
>;
|
|
||||||
deleteProductImage: PartialMutationProviderOutput<
|
|
||||||
ProductImageDelete,
|
|
||||||
ProductImageDeleteVariables
|
|
||||||
>;
|
|
||||||
reorderProductImages: PartialMutationProviderOutput<
|
|
||||||
ProductImageReorder,
|
|
||||||
ProductImageReorderVariables
|
|
||||||
>;
|
|
||||||
updateProduct: PartialMutationProviderOutput<
|
|
||||||
ProductUpdate,
|
|
||||||
ProductUpdateVariables
|
|
||||||
>;
|
|
||||||
updateSimpleProduct: PartialMutationProviderOutput<
|
|
||||||
SimpleProductUpdate,
|
|
||||||
SimpleProductUpdateVariables
|
|
||||||
>;
|
|
||||||
}) => React.ReactNode;
|
|
||||||
onBulkProductVariantDelete?: (data: ProductVariantBulkDelete) => void;
|
|
||||||
onDelete?: (data: ProductDelete) => void;
|
|
||||||
onImageCreate?: (data: ProductImageCreate) => void;
|
|
||||||
onImageDelete?: (data: ProductImageDelete) => void;
|
|
||||||
onImageReorder?: (data: ProductImageReorder) => void;
|
|
||||||
onUpdate?: (data: ProductUpdate) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProductUpdateOperations: React.FC<ProductUpdateOperationsProps> = ({
|
|
||||||
product,
|
|
||||||
children,
|
|
||||||
onBulkProductVariantDelete,
|
|
||||||
onDelete,
|
|
||||||
onImageDelete,
|
|
||||||
onImageCreate,
|
|
||||||
onImageReorder,
|
|
||||||
onUpdate
|
|
||||||
}) => {
|
|
||||||
const productId = product ? product.id : "";
|
|
||||||
return (
|
|
||||||
<TypedProductUpdateMutation onCompleted={onUpdate}>
|
|
||||||
{(...updateProduct) => (
|
|
||||||
<ProductImagesReorderProvider
|
|
||||||
productId={productId}
|
|
||||||
productImages={maybe(() => product.images, [])}
|
|
||||||
onCompleted={onImageReorder}
|
|
||||||
>
|
|
||||||
{(...reorderProductImages) => (
|
|
||||||
<TypedProductImageCreateMutation onCompleted={onImageCreate}>
|
|
||||||
{(...createProductImage) => (
|
|
||||||
<TypedProductDeleteMutation onCompleted={onDelete}>
|
|
||||||
{(...deleteProduct) => (
|
|
||||||
<TypedProductImageDeleteMutation
|
|
||||||
onCompleted={onImageDelete}
|
|
||||||
>
|
|
||||||
{(...deleteProductImage) => (
|
|
||||||
<TypedSimpleProductUpdateMutation
|
|
||||||
onCompleted={onUpdate}
|
|
||||||
>
|
|
||||||
{(...updateSimpleProduct) => (
|
|
||||||
<TypedProductVariantBulkDeleteMutation
|
|
||||||
onCompleted={onBulkProductVariantDelete}
|
|
||||||
>
|
|
||||||
{(...bulkProductVariantDelete) =>
|
|
||||||
children({
|
|
||||||
bulkProductVariantDelete: getMutationProviderData(
|
|
||||||
...bulkProductVariantDelete
|
|
||||||
),
|
|
||||||
createProductImage: getMutationProviderData(
|
|
||||||
...createProductImage
|
|
||||||
),
|
|
||||||
deleteProduct: getMutationProviderData(
|
|
||||||
...deleteProduct
|
|
||||||
),
|
|
||||||
deleteProductImage: getMutationProviderData(
|
|
||||||
...deleteProductImage
|
|
||||||
),
|
|
||||||
reorderProductImages: getMutationProviderData(
|
|
||||||
...reorderProductImages
|
|
||||||
),
|
|
||||||
updateProduct: getMutationProviderData(
|
|
||||||
...updateProduct
|
|
||||||
),
|
|
||||||
updateSimpleProduct: getMutationProviderData(
|
|
||||||
...updateSimpleProduct
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TypedProductVariantBulkDeleteMutation>
|
|
||||||
)}
|
|
||||||
</TypedSimpleProductUpdateMutation>
|
|
||||||
)}
|
|
||||||
</TypedProductImageDeleteMutation>
|
|
||||||
)}
|
|
||||||
</TypedProductDeleteMutation>
|
|
||||||
)}
|
|
||||||
</TypedProductImageCreateMutation>
|
|
||||||
)}
|
|
||||||
</ProductImagesReorderProvider>
|
|
||||||
)}
|
|
||||||
</TypedProductUpdateMutation>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ProductUpdateOperations;
|
|
|
@ -1,77 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { getMutationProviderData } from "../../misc";
|
|
||||||
import { PartialMutationProviderOutput } from "../../types";
|
|
||||||
import {
|
|
||||||
TypedVariantDeleteMutation,
|
|
||||||
TypedVariantImageAssignMutation,
|
|
||||||
TypedVariantImageUnassignMutation,
|
|
||||||
TypedVariantUpdateMutation
|
|
||||||
} from "../mutations";
|
|
||||||
import { VariantDelete, VariantDeleteVariables } from "../types/VariantDelete";
|
|
||||||
import {
|
|
||||||
VariantImageAssign,
|
|
||||||
VariantImageAssignVariables
|
|
||||||
} from "../types/VariantImageAssign";
|
|
||||||
import {
|
|
||||||
VariantImageUnassign,
|
|
||||||
VariantImageUnassignVariables
|
|
||||||
} from "../types/VariantImageUnassign";
|
|
||||||
import { VariantUpdate, VariantUpdateVariables } from "../types/VariantUpdate";
|
|
||||||
|
|
||||||
interface VariantDeleteOperationsProps {
|
|
||||||
children: (props: {
|
|
||||||
deleteVariant: PartialMutationProviderOutput<
|
|
||||||
VariantDelete,
|
|
||||||
VariantDeleteVariables
|
|
||||||
>;
|
|
||||||
updateVariant: PartialMutationProviderOutput<
|
|
||||||
VariantUpdate,
|
|
||||||
VariantUpdateVariables
|
|
||||||
>;
|
|
||||||
assignImage: PartialMutationProviderOutput<
|
|
||||||
VariantImageAssign,
|
|
||||||
VariantImageAssignVariables
|
|
||||||
>;
|
|
||||||
unassignImage: PartialMutationProviderOutput<
|
|
||||||
VariantImageUnassign,
|
|
||||||
VariantImageUnassignVariables
|
|
||||||
>;
|
|
||||||
}) => React.ReactNode;
|
|
||||||
onDelete?: (data: VariantDelete) => void;
|
|
||||||
onImageAssign?: (data: VariantImageAssign) => void;
|
|
||||||
onImageUnassign?: (data: VariantImageUnassign) => void;
|
|
||||||
onUpdate?: (data: VariantUpdate) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const VariantUpdateOperations: React.FC<VariantDeleteOperationsProps> = ({
|
|
||||||
children,
|
|
||||||
onDelete,
|
|
||||||
onUpdate,
|
|
||||||
onImageAssign,
|
|
||||||
onImageUnassign
|
|
||||||
}) => (
|
|
||||||
<TypedVariantImageAssignMutation onCompleted={onImageAssign}>
|
|
||||||
{(...assignImage) => (
|
|
||||||
<TypedVariantImageUnassignMutation onCompleted={onImageUnassign}>
|
|
||||||
{(...unassignImage) => (
|
|
||||||
<TypedVariantUpdateMutation onCompleted={onUpdate}>
|
|
||||||
{(...updateVariant) => (
|
|
||||||
<TypedVariantDeleteMutation onCompleted={onDelete}>
|
|
||||||
{(...deleteVariant) =>
|
|
||||||
children({
|
|
||||||
assignImage: getMutationProviderData(...assignImage),
|
|
||||||
deleteVariant: getMutationProviderData(...deleteVariant),
|
|
||||||
unassignImage: getMutationProviderData(...unassignImage),
|
|
||||||
updateVariant: getMutationProviderData(...updateVariant)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TypedVariantDeleteMutation>
|
|
||||||
)}
|
|
||||||
</TypedVariantUpdateMutation>
|
|
||||||
)}
|
|
||||||
</TypedVariantImageUnassignMutation>
|
|
||||||
)}
|
|
||||||
</TypedVariantImageAssignMutation>
|
|
||||||
);
|
|
||||||
export default VariantUpdateOperations;
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
import makeMutation from "@saleor/hooks/makeMutation";
|
import makeMutation from "@saleor/hooks/makeMutation";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { TypedMutation } from "../mutations";
|
|
||||||
import {
|
import {
|
||||||
productBulkDelete,
|
productBulkDelete,
|
||||||
productBulkDeleteVariables
|
productBulkDeleteVariables
|
||||||
|
@ -80,7 +79,7 @@ export const productImageCreateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductImageCreateMutation = TypedMutation<
|
export const useProductImageCreateMutation = makeMutation<
|
||||||
ProductImageCreate,
|
ProductImageCreate,
|
||||||
ProductImageCreateVariables
|
ProductImageCreateVariables
|
||||||
>(productImageCreateMutation);
|
>(productImageCreateMutation);
|
||||||
|
@ -98,7 +97,7 @@ export const productDeleteMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductDeleteMutation = TypedMutation<
|
export const useProductDeleteMutation = makeMutation<
|
||||||
ProductDelete,
|
ProductDelete,
|
||||||
ProductDeleteVariables
|
ProductDeleteVariables
|
||||||
>(productDeleteMutation);
|
>(productDeleteMutation);
|
||||||
|
@ -122,7 +121,7 @@ export const productImagesReorder = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductImagesReorder = TypedMutation<
|
export const useProductImagesReorder = makeMutation<
|
||||||
ProductImageReorder,
|
ProductImageReorder,
|
||||||
ProductImageReorderVariables
|
ProductImageReorderVariables
|
||||||
>(productImagesReorder);
|
>(productImagesReorder);
|
||||||
|
@ -167,7 +166,7 @@ export const productUpdateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductUpdateMutation = TypedMutation<
|
export const useProductUpdateMutation = makeMutation<
|
||||||
ProductUpdate,
|
ProductUpdate,
|
||||||
ProductUpdateVariables
|
ProductUpdateVariables
|
||||||
>(productUpdateMutation);
|
>(productUpdateMutation);
|
||||||
|
@ -263,7 +262,7 @@ export const simpleProductUpdateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedSimpleProductUpdateMutation = TypedMutation<
|
export const useSimpleProductUpdateMutation = makeMutation<
|
||||||
SimpleProductUpdate,
|
SimpleProductUpdate,
|
||||||
SimpleProductUpdateVariables
|
SimpleProductUpdateVariables
|
||||||
>(simpleProductUpdateMutation);
|
>(simpleProductUpdateMutation);
|
||||||
|
@ -316,7 +315,7 @@ export const productCreateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductCreateMutation = TypedMutation<
|
export const useProductCreateMutation = makeMutation<
|
||||||
ProductCreate,
|
ProductCreate,
|
||||||
ProductCreateVariables
|
ProductCreateVariables
|
||||||
>(productCreateMutation);
|
>(productCreateMutation);
|
||||||
|
@ -334,7 +333,7 @@ export const variantDeleteMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedVariantDeleteMutation = TypedMutation<
|
export const useVariantDeleteMutation = makeMutation<
|
||||||
VariantDelete,
|
VariantDelete,
|
||||||
VariantDeleteVariables
|
VariantDeleteVariables
|
||||||
>(variantDeleteMutation);
|
>(variantDeleteMutation);
|
||||||
|
@ -406,7 +405,7 @@ export const variantUpdateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedVariantUpdateMutation = TypedMutation<
|
export const useVariantUpdateMutation = makeMutation<
|
||||||
VariantUpdate,
|
VariantUpdate,
|
||||||
VariantUpdateVariables
|
VariantUpdateVariables
|
||||||
>(variantUpdateMutation);
|
>(variantUpdateMutation);
|
||||||
|
@ -425,7 +424,7 @@ export const variantCreateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedVariantCreateMutation = TypedMutation<
|
export const useVariantCreateMutation = makeMutation<
|
||||||
VariantCreate,
|
VariantCreate,
|
||||||
VariantCreateVariables
|
VariantCreateVariables
|
||||||
>(variantCreateMutation);
|
>(variantCreateMutation);
|
||||||
|
@ -446,7 +445,7 @@ export const productImageDeleteMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductImageDeleteMutation = TypedMutation<
|
export const useProductImageDeleteMutation = makeMutation<
|
||||||
ProductImageDelete,
|
ProductImageDelete,
|
||||||
ProductImageDeleteVariables
|
ProductImageDeleteVariables
|
||||||
>(productImageDeleteMutation);
|
>(productImageDeleteMutation);
|
||||||
|
@ -465,7 +464,7 @@ export const productImageUpdateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductImageUpdateMutation = TypedMutation<
|
export const useProductImageUpdateMutation = makeMutation<
|
||||||
ProductImageUpdate,
|
ProductImageUpdate,
|
||||||
ProductImageUpdateVariables
|
ProductImageUpdateVariables
|
||||||
>(productImageUpdateMutation);
|
>(productImageUpdateMutation);
|
||||||
|
@ -484,7 +483,7 @@ export const variantImageAssignMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedVariantImageAssignMutation = TypedMutation<
|
export const useVariantImageAssignMutation = makeMutation<
|
||||||
VariantImageAssign,
|
VariantImageAssign,
|
||||||
VariantImageAssignVariables
|
VariantImageAssignVariables
|
||||||
>(variantImageAssignMutation);
|
>(variantImageAssignMutation);
|
||||||
|
@ -503,7 +502,7 @@ export const variantImageUnassignMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedVariantImageUnassignMutation = TypedMutation<
|
export const useVariantImageUnassignMutation = makeMutation<
|
||||||
VariantImageUnassign,
|
VariantImageUnassign,
|
||||||
VariantImageUnassignVariables
|
VariantImageUnassignVariables
|
||||||
>(variantImageUnassignMutation);
|
>(variantImageUnassignMutation);
|
||||||
|
@ -518,7 +517,7 @@ export const productBulkDeleteMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductBulkDeleteMutation = TypedMutation<
|
export const useProductBulkDeleteMutation = makeMutation<
|
||||||
productBulkDelete,
|
productBulkDelete,
|
||||||
productBulkDeleteVariables
|
productBulkDeleteVariables
|
||||||
>(productBulkDeleteMutation);
|
>(productBulkDeleteMutation);
|
||||||
|
@ -533,7 +532,7 @@ export const productBulkPublishMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductBulkPublishMutation = TypedMutation<
|
export const useProductBulkPublishMutation = makeMutation<
|
||||||
productBulkPublish,
|
productBulkPublish,
|
||||||
productBulkPublishVariables
|
productBulkPublishVariables
|
||||||
>(productBulkPublishMutation);
|
>(productBulkPublishMutation);
|
||||||
|
@ -566,7 +565,7 @@ export const ProductVariantBulkDeleteMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductVariantBulkDeleteMutation = TypedMutation<
|
export const useProductVariantBulkDeleteMutation = makeMutation<
|
||||||
ProductVariantBulkDelete,
|
ProductVariantBulkDelete,
|
||||||
ProductVariantBulkDeleteVariables
|
ProductVariantBulkDeleteVariables
|
||||||
>(ProductVariantBulkDeleteMutation);
|
>(ProductVariantBulkDeleteMutation);
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { warehouseFragment } from "@saleor/fragments/warehouses";
|
||||||
import makeQuery from "@saleor/hooks/makeQuery";
|
import makeQuery from "@saleor/hooks/makeQuery";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { TypedQuery } from "../queries";
|
|
||||||
import { CountAllProducts } from "./types/CountAllProducts";
|
import { CountAllProducts } from "./types/CountAllProducts";
|
||||||
import {
|
import {
|
||||||
CreateMultipleVariantsData,
|
CreateMultipleVariantsData,
|
||||||
|
@ -150,10 +149,9 @@ const productListQuery = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductListQuery = TypedQuery<
|
export const useProductListQuery = makeQuery<ProductList, ProductListVariables>(
|
||||||
ProductList,
|
productListQuery
|
||||||
ProductListVariables
|
);
|
||||||
>(productListQuery);
|
|
||||||
|
|
||||||
const countAllProductsQuery = gql`
|
const countAllProductsQuery = gql`
|
||||||
query CountAllProducts {
|
query CountAllProducts {
|
||||||
|
@ -174,7 +172,7 @@ const productDetailsQuery = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductDetailsQuery = TypedQuery<
|
export const useProductDetails = makeQuery<
|
||||||
ProductDetails,
|
ProductDetails,
|
||||||
ProductDetailsVariables
|
ProductDetailsVariables
|
||||||
>(productDetailsQuery);
|
>(productDetailsQuery);
|
||||||
|
@ -187,7 +185,7 @@ const productVariantQuery = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductVariantQuery = TypedQuery<
|
export const useProductVariantQuery = makeQuery<
|
||||||
ProductVariantDetails,
|
ProductVariantDetails,
|
||||||
ProductVariantDetailsVariables
|
ProductVariantDetailsVariables
|
||||||
>(productVariantQuery);
|
>(productVariantQuery);
|
||||||
|
@ -231,7 +229,7 @@ const productVariantCreateQuery = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductVariantCreateQuery = TypedQuery<
|
export const useProductVariantCreateQuery = makeQuery<
|
||||||
ProductVariantCreateData,
|
ProductVariantCreateData,
|
||||||
ProductVariantCreateDataVariables
|
ProductVariantCreateDataVariables
|
||||||
>(productVariantCreateQuery);
|
>(productVariantCreateQuery);
|
||||||
|
@ -253,7 +251,7 @@ const productImageQuery = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const TypedProductImageQuery = TypedQuery<
|
export const useProductImageQuery = makeQuery<
|
||||||
ProductImageById,
|
ProductImageById,
|
||||||
ProductImageByIdVariables
|
ProductImageByIdVariables
|
||||||
>(productImageQuery);
|
>(productImageQuery);
|
||||||
|
@ -288,7 +286,7 @@ const availableInGridAttributes = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AvailableInGridAttributesQuery = TypedQuery<
|
export const useAvailableInGridAttributesQuery = makeQuery<
|
||||||
GridAttributes,
|
GridAttributes,
|
||||||
GridAttributesVariables
|
GridAttributesVariables
|
||||||
>(availableInGridAttributes);
|
>(availableInGridAttributes);
|
||||||
|
|
|
@ -10,12 +10,11 @@ import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { decimal, maybe, weight } from "../../misc";
|
import { decimal, weight } from "../../misc";
|
||||||
import ProductCreatePage, {
|
import ProductCreatePage, {
|
||||||
ProductCreatePageSubmitData
|
ProductCreatePageSubmitData
|
||||||
} from "../components/ProductCreatePage";
|
} from "../components/ProductCreatePage";
|
||||||
import { TypedProductCreateMutation } from "../mutations";
|
import { useProductCreateMutation } from "../mutations";
|
||||||
import { ProductCreate } from "../types/ProductCreate";
|
|
||||||
import { productListUrl, productUrl } from "../urls";
|
import { productListUrl, productUrl } from "../urls";
|
||||||
|
|
||||||
export const ProductCreateView: React.FC = () => {
|
export const ProductCreateView: React.FC = () => {
|
||||||
|
@ -53,118 +52,104 @@ export const ProductCreateView: React.FC = () => {
|
||||||
|
|
||||||
const handleBack = () => navigate(productListUrl());
|
const handleBack = () => navigate(productListUrl());
|
||||||
|
|
||||||
const handleSuccess = (data: ProductCreate) => {
|
const [productCreate, productCreateOpts] = useProductCreateMutation({
|
||||||
if (data.productCreate.errors.length === 0) {
|
onCompleted: data => {
|
||||||
notify({
|
if (data.productCreate.errors.length === 0) {
|
||||||
status: "success",
|
notify({
|
||||||
text: intl.formatMessage({
|
status: "success",
|
||||||
defaultMessage: "Product created"
|
text: intl.formatMessage({
|
||||||
})
|
defaultMessage: "Product created"
|
||||||
});
|
})
|
||||||
navigate(productUrl(data.productCreate.product.id));
|
});
|
||||||
|
navigate(productUrl(data.productCreate.product.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (formData: ProductCreatePageSubmitData) => {
|
||||||
|
productCreate({
|
||||||
|
variables: {
|
||||||
|
attributes: formData.attributes.map(attribute => ({
|
||||||
|
id: attribute.id,
|
||||||
|
values: attribute.value
|
||||||
|
})),
|
||||||
|
basePrice: decimal(formData.basePrice),
|
||||||
|
category: formData.category,
|
||||||
|
chargeTaxes: formData.chargeTaxes,
|
||||||
|
collections: formData.collections,
|
||||||
|
descriptionJson: JSON.stringify(formData.description),
|
||||||
|
isPublished: formData.isPublished,
|
||||||
|
name: formData.name,
|
||||||
|
productType: formData.productType,
|
||||||
|
publicationDate:
|
||||||
|
formData.publicationDate !== "" ? formData.publicationDate : null,
|
||||||
|
seo: {
|
||||||
|
description: formData.seoDescription,
|
||||||
|
title: formData.seoTitle
|
||||||
|
},
|
||||||
|
sku: formData.sku,
|
||||||
|
stocks: formData.stocks.map(stock => ({
|
||||||
|
quantity: parseInt(stock.value, 0),
|
||||||
|
warehouse: stock.id
|
||||||
|
})),
|
||||||
|
trackInventory: formData.trackInventory,
|
||||||
|
weight: weight(formData.weight)
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductCreateMutation onCompleted={handleSuccess}>
|
<>
|
||||||
{(productCreate, productCreateOpts) => {
|
<WindowTitle
|
||||||
const handleSubmit = (formData: ProductCreatePageSubmitData) => {
|
title={intl.formatMessage({
|
||||||
productCreate({
|
defaultMessage: "Create Product",
|
||||||
variables: {
|
description: "window title"
|
||||||
attributes: formData.attributes.map(attribute => ({
|
})}
|
||||||
id: attribute.id,
|
/>
|
||||||
values: attribute.value
|
<ProductCreatePage
|
||||||
})),
|
currency={shop?.defaultCurrency}
|
||||||
basePrice: decimal(formData.basePrice),
|
categories={(searchCategoryOpts.data?.search.edges || []).map(
|
||||||
category: formData.category,
|
edge => edge.node
|
||||||
chargeTaxes: formData.chargeTaxes,
|
)}
|
||||||
collections: formData.collections,
|
collections={(searchCollectionOpts.data?.search.edges || []).map(
|
||||||
descriptionJson: JSON.stringify(formData.description),
|
edge => edge.node
|
||||||
isPublished: formData.isPublished,
|
)}
|
||||||
name: formData.name,
|
disabled={productCreateOpts.loading}
|
||||||
productType: formData.productType,
|
errors={productCreateOpts.data?.productCreate.errors || []}
|
||||||
publicationDate:
|
fetchCategories={searchCategory}
|
||||||
formData.publicationDate !== ""
|
fetchCollections={searchCollection}
|
||||||
? formData.publicationDate
|
fetchProductTypes={searchProductTypes}
|
||||||
: null,
|
header={intl.formatMessage({
|
||||||
seo: {
|
defaultMessage: "New Product",
|
||||||
description: formData.seoDescription,
|
description: "page header"
|
||||||
title: formData.seoTitle
|
})}
|
||||||
},
|
productTypes={searchProductTypesOpts.data?.search.edges.map(
|
||||||
sku: formData.sku,
|
edge => edge.node
|
||||||
stocks: formData.stocks.map(stock => ({
|
)}
|
||||||
quantity: parseInt(stock.value, 0),
|
onBack={handleBack}
|
||||||
warehouse: stock.id
|
onSubmit={handleSubmit}
|
||||||
})),
|
saveButtonBarState={productCreateOpts.status}
|
||||||
trackInventory: formData.trackInventory,
|
fetchMoreCategories={{
|
||||||
weight: weight(formData.weight)
|
hasMore: searchCategoryOpts.data?.search.pageInfo.hasNextPage,
|
||||||
}
|
loading: searchCategoryOpts.loading,
|
||||||
});
|
onFetchMore: loadMoreCategories
|
||||||
};
|
}}
|
||||||
|
fetchMoreCollections={{
|
||||||
return (
|
hasMore: searchCollectionOpts.data?.search.pageInfo.hasNextPage,
|
||||||
<>
|
loading: searchCollectionOpts.loading,
|
||||||
<WindowTitle
|
onFetchMore: loadMoreCollections
|
||||||
title={intl.formatMessage({
|
}}
|
||||||
defaultMessage: "Create Product",
|
fetchMoreProductTypes={{
|
||||||
description: "window title"
|
hasMore: searchProductTypesOpts.data?.search.pageInfo.hasNextPage,
|
||||||
})}
|
loading: searchProductTypesOpts.loading,
|
||||||
/>
|
onFetchMore: loadMoreProductTypes
|
||||||
<ProductCreatePage
|
}}
|
||||||
currency={maybe(() => shop.defaultCurrency)}
|
warehouses={
|
||||||
categories={maybe(
|
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||||
() => searchCategoryOpts.data.search.edges,
|
}
|
||||||
[]
|
weightUnit={shop?.defaultWeightUnit}
|
||||||
).map(edge => edge.node)}
|
/>
|
||||||
collections={maybe(
|
</>
|
||||||
() => searchCollectionOpts.data.search.edges,
|
|
||||||
[]
|
|
||||||
).map(edge => edge.node)}
|
|
||||||
disabled={productCreateOpts.loading}
|
|
||||||
errors={productCreateOpts.data?.productCreate.errors || []}
|
|
||||||
fetchCategories={searchCategory}
|
|
||||||
fetchCollections={searchCollection}
|
|
||||||
fetchProductTypes={searchProductTypes}
|
|
||||||
header={intl.formatMessage({
|
|
||||||
defaultMessage: "New Product",
|
|
||||||
description: "page header"
|
|
||||||
})}
|
|
||||||
productTypes={maybe(() =>
|
|
||||||
searchProductTypesOpts.data.search.edges.map(edge => edge.node)
|
|
||||||
)}
|
|
||||||
onBack={handleBack}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
saveButtonBarState={productCreateOpts.status}
|
|
||||||
fetchMoreCategories={{
|
|
||||||
hasMore: maybe(
|
|
||||||
() => searchCategoryOpts.data.search.pageInfo.hasNextPage
|
|
||||||
),
|
|
||||||
loading: searchCategoryOpts.loading,
|
|
||||||
onFetchMore: loadMoreCategories
|
|
||||||
}}
|
|
||||||
fetchMoreCollections={{
|
|
||||||
hasMore: maybe(
|
|
||||||
() => searchCollectionOpts.data.search.pageInfo.hasNextPage
|
|
||||||
),
|
|
||||||
loading: searchCollectionOpts.loading,
|
|
||||||
onFetchMore: loadMoreCollections
|
|
||||||
}}
|
|
||||||
fetchMoreProductTypes={{
|
|
||||||
hasMore: maybe(
|
|
||||||
() => searchProductTypesOpts.data.search.pageInfo.hasNextPage
|
|
||||||
),
|
|
||||||
loading: searchProductTypesOpts.loading,
|
|
||||||
onFetchMore: loadMoreProductTypes
|
|
||||||
}}
|
|
||||||
warehouses={
|
|
||||||
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
|
||||||
}
|
|
||||||
weightUnit={shop?.defaultWeightUnit}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductCreateMutation>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductCreateView;
|
export default ProductCreateView;
|
||||||
|
|
|
@ -7,14 +7,12 @@ import { commonMessages } from "@saleor/intl";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { maybe } from "../../misc";
|
|
||||||
import ProductImagePage from "../components/ProductImagePage";
|
import ProductImagePage from "../components/ProductImagePage";
|
||||||
import {
|
import {
|
||||||
TypedProductImageDeleteMutation,
|
useProductImageDeleteMutation,
|
||||||
TypedProductImageUpdateMutation
|
useProductImageUpdateMutation
|
||||||
} from "../mutations";
|
} from "../mutations";
|
||||||
import { TypedProductImageQuery } from "../queries";
|
import { useProductImageQuery } from "../queries";
|
||||||
import { ProductImageUpdate } from "../types/ProductImageUpdate";
|
|
||||||
import {
|
import {
|
||||||
productImageUrl,
|
productImageUrl,
|
||||||
ProductImageUrlQueryParams,
|
ProductImageUrlQueryParams,
|
||||||
|
@ -38,93 +36,84 @@ export const ProductImage: React.FC<ProductImageProps> = ({
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleBack = () => navigate(productUrl(productId));
|
const handleBack = () => navigate(productUrl(productId));
|
||||||
const handleUpdateSuccess = (data: ProductImageUpdate) => {
|
|
||||||
if (data.productImageUpdate.errors.length === 0) {
|
const { data, loading } = useProductImageQuery({
|
||||||
notify({
|
displayLoader: true,
|
||||||
status: "success",
|
variables: {
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
imageId,
|
||||||
});
|
productId
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [updateImage, updateResult] = useProductImageUpdateMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.productImageUpdate.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [deleteImage, deleteResult] = useProductImageDeleteMutation({
|
||||||
|
onCompleted: handleBack
|
||||||
|
});
|
||||||
|
|
||||||
|
const product = data?.product;
|
||||||
|
|
||||||
|
if (product === null) {
|
||||||
|
return <NotFoundPage onBack={() => navigate(productListUrl())} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = () => deleteImage({ variables: { id: imageId } });
|
||||||
|
const handleImageClick = (id: string) => () =>
|
||||||
|
navigate(productImageUrl(productId, id));
|
||||||
|
const handleUpdate = (formData: { description: string }) => {
|
||||||
|
updateImage({
|
||||||
|
variables: {
|
||||||
|
alt: formData.description,
|
||||||
|
id: imageId
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
const image = data?.product?.mainImage;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductImageQuery
|
<>
|
||||||
displayLoader
|
<ProductImagePage
|
||||||
variables={{
|
disabled={loading}
|
||||||
imageId,
|
product={data?.product?.name}
|
||||||
productId
|
image={image || null}
|
||||||
}}
|
images={data?.product?.images}
|
||||||
>
|
onBack={handleBack}
|
||||||
{({ data, loading }) => {
|
onDelete={() =>
|
||||||
const product = data?.product;
|
navigate(
|
||||||
|
productImageUrl(productId, imageId, {
|
||||||
if (product === null) {
|
action: "remove"
|
||||||
return <NotFoundPage onBack={() => navigate(productListUrl())} />;
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
onRowClick={handleImageClick}
|
||||||
return (
|
onSubmit={handleUpdate}
|
||||||
<TypedProductImageUpdateMutation onCompleted={handleUpdateSuccess}>
|
saveButtonBarState={updateResult.status}
|
||||||
{(updateImage, updateResult) => (
|
/>
|
||||||
<TypedProductImageDeleteMutation onCompleted={handleBack}>
|
<ActionDialog
|
||||||
{(deleteImage, deleteResult) => {
|
onClose={() => navigate(productImageUrl(productId, imageId), true)}
|
||||||
const handleDelete = () =>
|
onConfirm={handleDelete}
|
||||||
deleteImage({ variables: { id: imageId } });
|
open={params.action === "remove"}
|
||||||
const handleImageClick = (id: string) => () =>
|
title={intl.formatMessage({
|
||||||
navigate(productImageUrl(productId, id));
|
defaultMessage: "Delete Image",
|
||||||
const handleUpdate = (formData: { description: string }) => {
|
description: "dialog header"
|
||||||
updateImage({
|
})}
|
||||||
variables: {
|
variant="delete"
|
||||||
alt: formData.description,
|
confirmButtonState={deleteResult.status}
|
||||||
id: imageId
|
>
|
||||||
}
|
<DialogContentText>
|
||||||
});
|
<FormattedMessage defaultMessage="Are you sure you want to delete this image?" />
|
||||||
};
|
</DialogContentText>
|
||||||
const image = data && data.product && data.product.mainImage;
|
</ActionDialog>
|
||||||
|
</>
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ProductImagePage
|
|
||||||
disabled={loading}
|
|
||||||
product={maybe(() => data.product.name)}
|
|
||||||
image={image || null}
|
|
||||||
images={maybe(() => data.product.images)}
|
|
||||||
onBack={handleBack}
|
|
||||||
onDelete={() =>
|
|
||||||
navigate(
|
|
||||||
productImageUrl(productId, imageId, {
|
|
||||||
action: "remove"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onRowClick={handleImageClick}
|
|
||||||
onSubmit={handleUpdate}
|
|
||||||
saveButtonBarState={updateResult.status}
|
|
||||||
/>
|
|
||||||
<ActionDialog
|
|
||||||
onClose={() =>
|
|
||||||
navigate(productImageUrl(productId, imageId), true)
|
|
||||||
}
|
|
||||||
onConfirm={handleDelete}
|
|
||||||
open={params.action === "remove"}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete Image",
|
|
||||||
description: "dialog header"
|
|
||||||
})}
|
|
||||||
variant="delete"
|
|
||||||
confirmButtonState={deleteResult.status}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage defaultMessage="Are you sure you want to delete this image?" />
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductImageDeleteMutation>
|
|
||||||
)}
|
|
||||||
</TypedProductImageUpdateMutation>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductImageQuery>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductImage;
|
export default ProductImage;
|
||||||
|
|
|
@ -45,18 +45,16 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import ProductListPage from "../../components/ProductListPage";
|
import ProductListPage from "../../components/ProductListPage";
|
||||||
import {
|
import {
|
||||||
TypedProductBulkDeleteMutation,
|
useProductBulkDeleteMutation,
|
||||||
TypedProductBulkPublishMutation,
|
useProductBulkPublishMutation,
|
||||||
useProductExport
|
useProductExport
|
||||||
} from "../../mutations";
|
} from "../../mutations";
|
||||||
import {
|
import {
|
||||||
AvailableInGridAttributesQuery,
|
useAvailableInGridAttributesQuery,
|
||||||
TypedProductListQuery,
|
|
||||||
useCountAllProducts,
|
useCountAllProducts,
|
||||||
useInitialProductFilterDataQuery
|
useInitialProductFilterDataQuery,
|
||||||
|
useProductListQuery
|
||||||
} from "../../queries";
|
} from "../../queries";
|
||||||
import { productBulkDelete } from "../../types/productBulkDelete";
|
|
||||||
import { productBulkPublish } from "../../types/productBulkPublish";
|
|
||||||
import {
|
import {
|
||||||
productAddUrl,
|
productAddUrl,
|
||||||
productListUrl,
|
productListUrl,
|
||||||
|
@ -235,6 +233,53 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
}),
|
}),
|
||||||
[params, settings.rowNumber]
|
[params, settings.rowNumber]
|
||||||
);
|
);
|
||||||
|
const { data, loading, refetch } = useProductListQuery({
|
||||||
|
displayLoader: true,
|
||||||
|
variables: queryVariables
|
||||||
|
});
|
||||||
|
|
||||||
|
function filterColumnIds(columns: ProductListColumns[]) {
|
||||||
|
return columns
|
||||||
|
.filter(isAttributeColumnValue)
|
||||||
|
.map(getAttributeIdFromColumnValue);
|
||||||
|
}
|
||||||
|
const attributes = useAvailableInGridAttributesQuery({
|
||||||
|
variables: { first: 6, ids: filterColumnIds(settings.columns) }
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
productBulkDelete,
|
||||||
|
productBulkDeleteOpts
|
||||||
|
] = useProductBulkDeleteMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.productBulkDelete.errors.length === 0) {
|
||||||
|
closeModal();
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
reset();
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
productBulkPublish,
|
||||||
|
productBulkPublishOpts
|
||||||
|
] = useProductBulkPublishMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.productBulkPublish.errors.length === 0) {
|
||||||
|
closeModal();
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
reset();
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const filterOpts = getFilterOpts(
|
const filterOpts = getFilterOpts(
|
||||||
params,
|
params,
|
||||||
|
@ -262,345 +307,259 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function filterColumnIds(columns: ProductListColumns[]) {
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
return columns
|
maybe(() => data.products.pageInfo),
|
||||||
.filter(isAttributeColumnValue)
|
paginationState,
|
||||||
.map(getAttributeIdFromColumnValue);
|
params
|
||||||
}
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AvailableInGridAttributesQuery
|
<>
|
||||||
variables={{ first: 6, ids: filterColumnIds(settings.columns) }}
|
<ProductListPage
|
||||||
>
|
activeAttributeSortId={params.attributeId}
|
||||||
{attributes => (
|
sort={{
|
||||||
<TypedProductListQuery displayLoader variables={queryVariables}>
|
asc: params.asc,
|
||||||
{({ data, loading, refetch }) => {
|
sort: params.sort
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
}}
|
||||||
maybe(() => data.products.pageInfo),
|
onSort={handleSort}
|
||||||
paginationState,
|
availableInGridAttributes={maybe(
|
||||||
params
|
() => attributes.data.availableInGrid.edges.map(edge => edge.node),
|
||||||
);
|
[]
|
||||||
|
)}
|
||||||
const handleBulkDelete = (data: productBulkDelete) => {
|
currencySymbol={currencySymbol}
|
||||||
if (data.productBulkDelete.errors.length === 0) {
|
currentTab={currentTab}
|
||||||
closeModal();
|
defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]}
|
||||||
notify({
|
filterOpts={filterOpts}
|
||||||
status: "success",
|
gridAttributes={maybe(
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
() => attributes.data.grid.edges.map(edge => edge.node),
|
||||||
});
|
[]
|
||||||
reset();
|
)}
|
||||||
refetch();
|
totalGridAttributes={maybe(
|
||||||
|
() => attributes.data.availableInGrid.totalCount,
|
||||||
|
0
|
||||||
|
)}
|
||||||
|
settings={settings}
|
||||||
|
loading={attributes.loading}
|
||||||
|
hasMore={maybe(
|
||||||
|
() => attributes.data.availableInGrid.pageInfo.hasNextPage,
|
||||||
|
false
|
||||||
|
)}
|
||||||
|
onAdd={() => navigate(productAddUrl)}
|
||||||
|
disabled={loading}
|
||||||
|
products={maybe(() => data.products.edges.map(edge => edge.node))}
|
||||||
|
onFetchMore={() =>
|
||||||
|
attributes.loadMore(
|
||||||
|
(prev, next) => {
|
||||||
|
if (
|
||||||
|
prev.availableInGrid.pageInfo.endCursor ===
|
||||||
|
next.availableInGrid.pageInfo.endCursor
|
||||||
|
) {
|
||||||
|
return prev;
|
||||||
}
|
}
|
||||||
};
|
return {
|
||||||
|
...prev,
|
||||||
const handleBulkPublish = (data: productBulkPublish) => {
|
availableInGrid: {
|
||||||
if (data.productBulkPublish.errors.length === 0) {
|
...prev.availableInGrid,
|
||||||
closeModal();
|
edges: [
|
||||||
notify({
|
...prev.availableInGrid.edges,
|
||||||
status: "success",
|
...next.availableInGrid.edges
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
],
|
||||||
});
|
pageInfo: next.availableInGrid.pageInfo
|
||||||
reset();
|
}
|
||||||
refetch();
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
after: attributes.data.availableInGrid.pageInfo.endCursor
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onNextPage={loadNextPage}
|
||||||
|
onPreviousPage={loadPreviousPage}
|
||||||
|
onUpdateListSettings={updateListSettings}
|
||||||
|
pageInfo={pageInfo}
|
||||||
|
onRowClick={id => () => navigate(productUrl(id))}
|
||||||
|
onAll={resetFilters}
|
||||||
|
toolbar={
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() =>
|
||||||
|
openModal("unpublish", {
|
||||||
|
ids: listElements
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
>
|
||||||
|
<FormattedMessage
|
||||||
return (
|
defaultMessage="Unpublish"
|
||||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
description="unpublish product, button"
|
||||||
{(productBulkDelete, productBulkDeleteOpts) => (
|
/>
|
||||||
<TypedProductBulkPublishMutation
|
</Button>
|
||||||
onCompleted={handleBulkPublish}
|
<Button
|
||||||
>
|
color="primary"
|
||||||
{(productBulkPublish, productBulkPublishOpts) => (
|
onClick={() =>
|
||||||
<>
|
openModal("publish", {
|
||||||
<ProductListPage
|
ids: listElements
|
||||||
activeAttributeSortId={params.attributeId}
|
})
|
||||||
sort={{
|
}
|
||||||
asc: params.asc,
|
>
|
||||||
sort: params.sort
|
<FormattedMessage
|
||||||
}}
|
defaultMessage="Publish"
|
||||||
onSort={handleSort}
|
description="publish product, button"
|
||||||
availableInGridAttributes={maybe(
|
/>
|
||||||
() =>
|
</Button>
|
||||||
attributes.data.availableInGrid.edges.map(
|
<IconButton
|
||||||
edge => edge.node
|
color="primary"
|
||||||
),
|
onClick={() =>
|
||||||
[]
|
openModal("delete", {
|
||||||
)}
|
ids: listElements
|
||||||
currencySymbol={currencySymbol}
|
})
|
||||||
currentTab={currentTab}
|
}
|
||||||
defaultSettings={
|
>
|
||||||
defaultListSettings[ListViews.PRODUCT_LIST]
|
<DeleteIcon />
|
||||||
}
|
</IconButton>
|
||||||
filterOpts={filterOpts}
|
</>
|
||||||
gridAttributes={maybe(
|
}
|
||||||
() =>
|
isChecked={isSelected}
|
||||||
attributes.data.grid.edges.map(edge => edge.node),
|
selected={listElements.length}
|
||||||
[]
|
toggle={toggle}
|
||||||
)}
|
toggleAll={toggleAll}
|
||||||
totalGridAttributes={maybe(
|
onSearchChange={handleSearchChange}
|
||||||
() => attributes.data.availableInGrid.totalCount,
|
onFilterChange={changeFilters}
|
||||||
0
|
onTabSave={() => openModal("save-search")}
|
||||||
)}
|
onTabDelete={() => openModal("delete-search")}
|
||||||
settings={settings}
|
onTabChange={handleTabChange}
|
||||||
loading={attributes.loading}
|
initialSearch={params.query || ""}
|
||||||
hasMore={maybe(
|
tabs={getFilterTabs().map(tab => tab.name)}
|
||||||
() =>
|
onExport={() => openModal("export")}
|
||||||
attributes.data.availableInGrid.pageInfo
|
/>
|
||||||
.hasNextPage,
|
<ActionDialog
|
||||||
false
|
open={params.action === "delete"}
|
||||||
)}
|
confirmButtonState={productBulkDeleteOpts.status}
|
||||||
onAdd={() => navigate(productAddUrl)}
|
onClose={closeModal}
|
||||||
disabled={loading}
|
onConfirm={() =>
|
||||||
products={maybe(() =>
|
productBulkDelete({
|
||||||
data.products.edges.map(edge => edge.node)
|
variables: { ids: params.ids }
|
||||||
)}
|
})
|
||||||
onFetchMore={() =>
|
}
|
||||||
attributes.loadMore(
|
title={intl.formatMessage({
|
||||||
(prev, next) => {
|
defaultMessage: "Delete Products",
|
||||||
if (
|
description: "dialog header"
|
||||||
prev.availableInGrid.pageInfo.endCursor ===
|
})}
|
||||||
next.availableInGrid.pageInfo.endCursor
|
variant="delete"
|
||||||
) {
|
>
|
||||||
return prev;
|
<DialogContentText>
|
||||||
}
|
<FormattedMessage
|
||||||
return {
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this product?} other{Are you sure you want to delete {displayQuantity} products?}}"
|
||||||
...prev,
|
description="dialog content"
|
||||||
availableInGrid: {
|
values={{
|
||||||
...prev.availableInGrid,
|
counter: maybe(() => params.ids.length),
|
||||||
edges: [
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
...prev.availableInGrid.edges,
|
}}
|
||||||
...next.availableInGrid.edges
|
/>
|
||||||
],
|
</DialogContentText>
|
||||||
pageInfo: next.availableInGrid.pageInfo
|
</ActionDialog>
|
||||||
}
|
<ActionDialog
|
||||||
};
|
open={params.action === "publish"}
|
||||||
},
|
confirmButtonState={productBulkPublishOpts.status}
|
||||||
{
|
onClose={closeModal}
|
||||||
after:
|
onConfirm={() =>
|
||||||
attributes.data.availableInGrid.pageInfo
|
productBulkPublish({
|
||||||
.endCursor
|
variables: {
|
||||||
}
|
ids: params.ids,
|
||||||
)
|
isPublished: true
|
||||||
}
|
}
|
||||||
onNextPage={loadNextPage}
|
})
|
||||||
onPreviousPage={loadPreviousPage}
|
}
|
||||||
onUpdateListSettings={updateListSettings}
|
title={intl.formatMessage({
|
||||||
pageInfo={pageInfo}
|
defaultMessage: "Publish Products",
|
||||||
onRowClick={id => () => navigate(productUrl(id))}
|
description: "dialog header"
|
||||||
onAll={resetFilters}
|
})}
|
||||||
toolbar={
|
>
|
||||||
<>
|
<DialogContentText>
|
||||||
<Button
|
<FormattedMessage
|
||||||
color="primary"
|
defaultMessage="{counter,plural,one{Are you sure you want to publish this product?} other{Are you sure you want to publish {displayQuantity} products?}}"
|
||||||
onClick={() =>
|
description="dialog content"
|
||||||
openModal("unpublish", {
|
values={{
|
||||||
ids: listElements
|
counter: maybe(() => params.ids.length),
|
||||||
})
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
}
|
}}
|
||||||
>
|
/>
|
||||||
<FormattedMessage
|
</DialogContentText>
|
||||||
defaultMessage="Unpublish"
|
</ActionDialog>
|
||||||
description="unpublish product, button"
|
<ActionDialog
|
||||||
/>
|
open={params.action === "unpublish"}
|
||||||
</Button>
|
confirmButtonState={productBulkPublishOpts.status}
|
||||||
<Button
|
onClose={closeModal}
|
||||||
color="primary"
|
onConfirm={() =>
|
||||||
onClick={() =>
|
productBulkPublish({
|
||||||
openModal("publish", {
|
variables: {
|
||||||
ids: listElements
|
ids: params.ids,
|
||||||
})
|
isPublished: false
|
||||||
}
|
}
|
||||||
>
|
})
|
||||||
<FormattedMessage
|
}
|
||||||
defaultMessage="Publish"
|
title={intl.formatMessage({
|
||||||
description="publish product, button"
|
defaultMessage: "Unpublish Products",
|
||||||
/>
|
description: "dialog header"
|
||||||
</Button>
|
})}
|
||||||
<IconButton
|
>
|
||||||
color="primary"
|
<DialogContentText>
|
||||||
onClick={() =>
|
<FormattedMessage
|
||||||
openModal("delete", {
|
defaultMessage="{counter,plural,one{Are you sure you want to unpublish this product?} other{Are you sure you want to unpublish {displayQuantity} products?}}"
|
||||||
ids: listElements
|
description="dialog content"
|
||||||
})
|
values={{
|
||||||
}
|
counter: maybe(() => params.ids.length),
|
||||||
>
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
<DeleteIcon />
|
}}
|
||||||
</IconButton>
|
/>
|
||||||
</>
|
</DialogContentText>
|
||||||
}
|
</ActionDialog>
|
||||||
isChecked={isSelected}
|
<ProductExportDialog
|
||||||
selected={listElements.length}
|
attributes={(searchAttributes.result.data?.search.edges || []).map(
|
||||||
toggle={toggle}
|
edge => edge.node
|
||||||
toggleAll={toggleAll}
|
)}
|
||||||
onSearchChange={handleSearchChange}
|
hasMore={searchAttributes.result.data?.search.pageInfo.hasNextPage}
|
||||||
onFilterChange={changeFilters}
|
loading={searchAttributes.result.loading}
|
||||||
onTabSave={() => openModal("save-search")}
|
onFetch={searchAttributes.search}
|
||||||
onTabDelete={() => openModal("delete-search")}
|
onFetchMore={searchAttributes.loadMore}
|
||||||
onTabChange={handleTabChange}
|
open={params.action === "export"}
|
||||||
initialSearch={params.query || ""}
|
confirmButtonState={exportProductsOpts.status}
|
||||||
tabs={getFilterTabs().map(tab => tab.name)}
|
errors={exportProductsOpts.data?.exportProducts.errors || []}
|
||||||
onExport={() => openModal("export")}
|
productQuantity={{
|
||||||
/>
|
all: countAllProducts.data?.products.totalCount,
|
||||||
<ActionDialog
|
filter: data?.products.totalCount
|
||||||
open={params.action === "delete"}
|
}}
|
||||||
confirmButtonState={productBulkDeleteOpts.status}
|
selectedProducts={listElements.length}
|
||||||
onClose={closeModal}
|
warehouses={
|
||||||
onConfirm={() =>
|
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||||
productBulkDelete({
|
}
|
||||||
variables: { ids: params.ids }
|
onClose={closeModal}
|
||||||
})
|
onSubmit={data =>
|
||||||
}
|
exportProducts({
|
||||||
title={intl.formatMessage({
|
variables: {
|
||||||
defaultMessage: "Delete Products",
|
input: {
|
||||||
description: "dialog header"
|
...data,
|
||||||
})}
|
filter,
|
||||||
variant="delete"
|
ids: listElements
|
||||||
>
|
}
|
||||||
<DialogContentText>
|
}
|
||||||
<FormattedMessage
|
})
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this product?} other{Are you sure you want to delete {displayQuantity} products?}}"
|
}
|
||||||
description="dialog content"
|
/>
|
||||||
values={{
|
<SaveFilterTabDialog
|
||||||
counter: maybe(() => params.ids.length),
|
open={params.action === "save-search"}
|
||||||
displayQuantity: (
|
confirmButtonState="default"
|
||||||
<strong>
|
onClose={closeModal}
|
||||||
{maybe(() => params.ids.length)}
|
onSubmit={handleFilterTabSave}
|
||||||
</strong>
|
/>
|
||||||
)
|
<DeleteFilterTabDialog
|
||||||
}}
|
open={params.action === "delete-search"}
|
||||||
/>
|
confirmButtonState="default"
|
||||||
</DialogContentText>
|
onClose={closeModal}
|
||||||
</ActionDialog>
|
onSubmit={handleFilterTabDelete}
|
||||||
<ActionDialog
|
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||||
open={params.action === "publish"}
|
/>
|
||||||
confirmButtonState={productBulkPublishOpts.status}
|
</>
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
productBulkPublish({
|
|
||||||
variables: {
|
|
||||||
ids: params.ids,
|
|
||||||
isPublished: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Publish Products",
|
|
||||||
description: "dialog header"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to publish this product?} other{Are you sure you want to publish {displayQuantity} products?}}"
|
|
||||||
description="dialog content"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>
|
|
||||||
{maybe(() => params.ids.length)}
|
|
||||||
</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
open={params.action === "unpublish"}
|
|
||||||
confirmButtonState={productBulkPublishOpts.status}
|
|
||||||
onClose={closeModal}
|
|
||||||
onConfirm={() =>
|
|
||||||
productBulkPublish({
|
|
||||||
variables: {
|
|
||||||
ids: params.ids,
|
|
||||||
isPublished: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Unpublish Products",
|
|
||||||
description: "dialog header"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to unpublish this product?} other{Are you sure you want to unpublish {displayQuantity} products?}}"
|
|
||||||
description="dialog content"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>
|
|
||||||
{maybe(() => params.ids.length)}
|
|
||||||
</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ProductExportDialog
|
|
||||||
attributes={(
|
|
||||||
searchAttributes.result.data?.search.edges || []
|
|
||||||
).map(edge => edge.node)}
|
|
||||||
hasMore={
|
|
||||||
searchAttributes.result.data?.search.pageInfo
|
|
||||||
.hasNextPage
|
|
||||||
}
|
|
||||||
loading={searchAttributes.result.loading}
|
|
||||||
onFetch={searchAttributes.search}
|
|
||||||
onFetchMore={searchAttributes.loadMore}
|
|
||||||
open={params.action === "export"}
|
|
||||||
confirmButtonState={exportProductsOpts.status}
|
|
||||||
errors={
|
|
||||||
exportProductsOpts.data?.exportProducts.errors || []
|
|
||||||
}
|
|
||||||
productQuantity={{
|
|
||||||
all: countAllProducts.data?.products.totalCount,
|
|
||||||
filter: data?.products.totalCount
|
|
||||||
}}
|
|
||||||
selectedProducts={listElements.length}
|
|
||||||
warehouses={
|
|
||||||
warehouses.data?.warehouses.edges.map(
|
|
||||||
edge => edge.node
|
|
||||||
) || []
|
|
||||||
}
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={data =>
|
|
||||||
exportProducts({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
...data,
|
|
||||||
filter,
|
|
||||||
ids: listElements
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<SaveFilterTabDialog
|
|
||||||
open={params.action === "save-search"}
|
|
||||||
confirmButtonState="default"
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={handleFilterTabSave}
|
|
||||||
/>
|
|
||||||
<DeleteFilterTabDialog
|
|
||||||
open={params.action === "delete-search"}
|
|
||||||
confirmButtonState="default"
|
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={handleFilterTabDelete}
|
|
||||||
tabName={maybe(
|
|
||||||
() => tabs[currentTab - 1].name,
|
|
||||||
"..."
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</TypedProductBulkPublishMutation>
|
|
||||||
)}
|
|
||||||
</TypedProductBulkDeleteMutation>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductListQuery>
|
|
||||||
)}
|
|
||||||
</AvailableInGridAttributesQuery>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductList;
|
export default ProductList;
|
||||||
|
|
|
@ -11,6 +11,15 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import {
|
||||||
|
useProductDeleteMutation,
|
||||||
|
useProductImageCreateMutation,
|
||||||
|
useProductImageDeleteMutation,
|
||||||
|
useProductImagesReorder,
|
||||||
|
useProductUpdateMutation,
|
||||||
|
useProductVariantBulkDeleteMutation,
|
||||||
|
useSimpleProductUpdateMutation
|
||||||
|
} from "@saleor/products/mutations";
|
||||||
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
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";
|
||||||
|
@ -20,14 +29,9 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
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 { useProductDetails } from "../../queries";
|
||||||
import { TypedProductDetailsQuery } from "../../queries";
|
import { ProductImageCreateVariables } from "../../types/ProductImageCreate";
|
||||||
import {
|
|
||||||
ProductImageCreate,
|
|
||||||
ProductImageCreateVariables
|
|
||||||
} from "../../types/ProductImageCreate";
|
|
||||||
import { ProductUpdate as ProductUpdateMutationResult } from "../../types/ProductUpdate";
|
import { ProductUpdate as ProductUpdateMutationResult } from "../../types/ProductUpdate";
|
||||||
import { ProductVariantBulkDelete } from "../../types/ProductVariantBulkDelete";
|
|
||||||
import {
|
import {
|
||||||
productImageUrl,
|
productImageUrl,
|
||||||
productListUrl,
|
productListUrl,
|
||||||
|
@ -78,6 +82,86 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
});
|
});
|
||||||
const shop = useShop();
|
const shop = useShop();
|
||||||
|
|
||||||
|
const { data, loading, refetch } = useProductDetails({
|
||||||
|
displayLoader: true,
|
||||||
|
variables: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUpdate = (data: ProductUpdateMutationResult) => {
|
||||||
|
if (data.productUpdate.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [updateProduct, updateProductOpts] = useProductUpdateMutation({
|
||||||
|
onCompleted: handleUpdate
|
||||||
|
});
|
||||||
|
const [
|
||||||
|
updateSimpleProduct,
|
||||||
|
updateSimpleProductOpts
|
||||||
|
] = useSimpleProductUpdateMutation({
|
||||||
|
onCompleted: handleUpdate
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
reorderProductImages,
|
||||||
|
reorderProductImagesOpts
|
||||||
|
] = useProductImagesReorder({});
|
||||||
|
|
||||||
|
const [deleteProduct, deleteProductOpts] = useProductDeleteMutation({
|
||||||
|
onCompleted: () => {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Product removed"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
navigate(productListUrl());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
createProductImage,
|
||||||
|
createProductImageOpts
|
||||||
|
] = useProductImageCreateMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
const imageError = data.productImageCreate.errors.find(
|
||||||
|
error => error.field === ("image" as keyof ProductImageCreateVariables)
|
||||||
|
);
|
||||||
|
if (imageError) {
|
||||||
|
notify({
|
||||||
|
status: "error",
|
||||||
|
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [deleteProductImage] = useProductImageDeleteMutation({
|
||||||
|
onCompleted: () =>
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
bulkProductVariantDelete,
|
||||||
|
bulkProductVariantDeleteOpts
|
||||||
|
] = useProductVariantBulkDeleteMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.productVariantBulkDelete.errors.length === 0) {
|
||||||
|
closeModal();
|
||||||
|
reset();
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const [openModal, closeModal] = createDialogActionHandlers<
|
const [openModal, closeModal] = createDialogActionHandlers<
|
||||||
ProductUrlDialog,
|
ProductUrlDialog,
|
||||||
ProductUrlQueryParams
|
ProductUrlQueryParams
|
||||||
|
@ -85,256 +169,169 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
||||||
|
|
||||||
const handleBack = () => navigate(productListUrl());
|
const handleBack = () => navigate(productListUrl());
|
||||||
|
|
||||||
|
const product = data?.product;
|
||||||
|
|
||||||
|
if (product === null) {
|
||||||
|
return <NotFoundPage onBack={handleBack} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVariantAdd = () => navigate(productVariantAddUrl(id));
|
||||||
|
|
||||||
|
const handleImageDelete = (id: string) => () =>
|
||||||
|
deleteProductImage({ variables: { id } });
|
||||||
|
const handleImageEdit = (imageId: string) => () =>
|
||||||
|
navigate(productImageUrl(id, imageId));
|
||||||
|
const handleSubmit = createUpdateHandler(
|
||||||
|
product,
|
||||||
|
variables => updateProduct({ variables }),
|
||||||
|
variables => updateSimpleProduct({ variables })
|
||||||
|
);
|
||||||
|
const handleImageUpload = createImageUploadHandler(id, variables =>
|
||||||
|
createProductImage({ variables })
|
||||||
|
);
|
||||||
|
const handleImageReorder = createImageReorderHandler(product, variables =>
|
||||||
|
reorderProductImages({ variables })
|
||||||
|
);
|
||||||
|
|
||||||
|
const disableFormSave =
|
||||||
|
createProductImageOpts.loading ||
|
||||||
|
deleteProductOpts.loading ||
|
||||||
|
reorderProductImagesOpts.loading ||
|
||||||
|
updateProductOpts.loading ||
|
||||||
|
loading;
|
||||||
|
const formTransitionState = getMutationState(
|
||||||
|
updateProductOpts.called || updateSimpleProductOpts.called,
|
||||||
|
updateProductOpts.loading || updateSimpleProductOpts.loading,
|
||||||
|
maybe(() => updateProductOpts.data.productUpdate.errors),
|
||||||
|
maybe(() => updateSimpleProductOpts.data.productUpdate.errors),
|
||||||
|
maybe(() => updateSimpleProductOpts.data.productVariantUpdate.errors)
|
||||||
|
);
|
||||||
|
|
||||||
|
const categories = maybe(
|
||||||
|
() => searchCategoriesOpts.data.search.edges,
|
||||||
|
[]
|
||||||
|
).map(edge => edge.node);
|
||||||
|
const collections = maybe(
|
||||||
|
() => searchCollectionsOpts.data.search.edges,
|
||||||
|
[]
|
||||||
|
).map(edge => edge.node);
|
||||||
|
const errors = [
|
||||||
|
...maybe(() => updateProductOpts.data.productUpdate.errors, []),
|
||||||
|
...maybe(() => updateSimpleProductOpts.data.productUpdate.errors, [])
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductDetailsQuery displayLoader variables={{ id }}>
|
<>
|
||||||
{({ data, loading, refetch }) => {
|
<WindowTitle title={maybe(() => data.product.name)} />
|
||||||
const product = data?.product;
|
<ProductUpdatePage
|
||||||
|
categories={categories}
|
||||||
if (product === null) {
|
collections={collections}
|
||||||
return <NotFoundPage onBack={handleBack} />;
|
defaultWeightUnit={shop?.defaultWeightUnit}
|
||||||
|
disabled={disableFormSave}
|
||||||
|
errors={errors}
|
||||||
|
fetchCategories={searchCategories}
|
||||||
|
fetchCollections={searchCollections}
|
||||||
|
saveButtonBarState={formTransitionState}
|
||||||
|
images={maybe(() => data.product.images)}
|
||||||
|
header={maybe(() => product.name)}
|
||||||
|
placeholderImage={placeholderImg}
|
||||||
|
product={product}
|
||||||
|
warehouses={
|
||||||
|
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||||
}
|
}
|
||||||
|
variants={maybe(() => product.variants)}
|
||||||
const handleDelete = () => {
|
onBack={handleBack}
|
||||||
notify({
|
onDelete={() => openModal("remove")}
|
||||||
status: "success",
|
onImageReorder={handleImageReorder}
|
||||||
text: intl.formatMessage({
|
onSubmit={handleSubmit}
|
||||||
defaultMessage: "Product removed"
|
onVariantAdd={handleVariantAdd}
|
||||||
})
|
onVariantsAdd={() => navigate(productVariantCreatorUrl(id))}
|
||||||
});
|
onVariantShow={variantId => () =>
|
||||||
navigate(productListUrl());
|
navigate(productVariantEditUrl(product.id, variantId))}
|
||||||
};
|
onImageUpload={handleImageUpload}
|
||||||
const handleUpdate = (data: ProductUpdateMutationResult) => {
|
onImageEdit={handleImageEdit}
|
||||||
if (data.productUpdate.errors.length === 0) {
|
onImageDelete={handleImageDelete}
|
||||||
notify({
|
toolbar={
|
||||||
status: "success",
|
<IconButton
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
color="primary"
|
||||||
});
|
onClick={() =>
|
||||||
}
|
openModal("remove-variants", {
|
||||||
};
|
ids: listElements
|
||||||
|
})
|
||||||
const handleImageCreate = (data: ProductImageCreate) => {
|
}
|
||||||
const imageError = data.productImageCreate.errors.find(
|
|
||||||
error =>
|
|
||||||
error.field === ("image" as keyof ProductImageCreateVariables)
|
|
||||||
);
|
|
||||||
if (imageError) {
|
|
||||||
notify({
|
|
||||||
status: "error",
|
|
||||||
text: intl.formatMessage(commonMessages.somethingWentWrong)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleImageDeleteSuccess = () =>
|
|
||||||
notify({
|
|
||||||
status: "success",
|
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
|
||||||
});
|
|
||||||
const handleVariantAdd = () => navigate(productVariantAddUrl(id));
|
|
||||||
|
|
||||||
const handleBulkProductVariantDelete = (
|
|
||||||
data: ProductVariantBulkDelete
|
|
||||||
) => {
|
|
||||||
if (data.productVariantBulkDelete.errors.length === 0) {
|
|
||||||
closeModal();
|
|
||||||
reset();
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ProductUpdateOperations
|
|
||||||
product={product}
|
|
||||||
onBulkProductVariantDelete={handleBulkProductVariantDelete}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
onImageCreate={handleImageCreate}
|
|
||||||
onImageDelete={handleImageDeleteSuccess}
|
|
||||||
onUpdate={handleUpdate}
|
|
||||||
>
|
>
|
||||||
{({
|
<DeleteIcon />
|
||||||
bulkProductVariantDelete,
|
</IconButton>
|
||||||
createProductImage,
|
}
|
||||||
deleteProduct,
|
isChecked={isSelected}
|
||||||
deleteProductImage,
|
selected={listElements.length}
|
||||||
reorderProductImages,
|
toggle={toggle}
|
||||||
updateProduct,
|
toggleAll={toggleAll}
|
||||||
updateSimpleProduct
|
fetchMoreCategories={{
|
||||||
}) => {
|
hasMore: maybe(
|
||||||
const handleImageDelete = (id: string) => () =>
|
() => searchCategoriesOpts.data.search.pageInfo.hasNextPage
|
||||||
deleteProductImage.mutate({ id });
|
),
|
||||||
const handleImageEdit = (imageId: string) => () =>
|
loading: searchCategoriesOpts.loading,
|
||||||
navigate(productImageUrl(id, imageId));
|
onFetchMore: loadMoreCategories
|
||||||
const handleSubmit = createUpdateHandler(
|
}}
|
||||||
product,
|
fetchMoreCollections={{
|
||||||
updateProduct.mutate,
|
hasMore: maybe(
|
||||||
updateSimpleProduct.mutate
|
() => searchCollectionsOpts.data.search.pageInfo.hasNextPage
|
||||||
);
|
),
|
||||||
const handleImageUpload = createImageUploadHandler(
|
loading: searchCollectionsOpts.loading,
|
||||||
id,
|
onFetchMore: loadMoreCollections
|
||||||
createProductImage.mutate
|
}}
|
||||||
);
|
/>
|
||||||
const handleImageReorder = createImageReorderHandler(
|
<ActionDialog
|
||||||
product,
|
open={params.action === "remove"}
|
||||||
reorderProductImages.mutate
|
onClose={closeModal}
|
||||||
);
|
confirmButtonState={deleteProductOpts.status}
|
||||||
|
onConfirm={() => deleteProduct({ variables: { id } })}
|
||||||
const disableFormSave =
|
variant="delete"
|
||||||
createProductImage.opts.loading ||
|
title={intl.formatMessage({
|
||||||
deleteProduct.opts.loading ||
|
defaultMessage: "Delete Product",
|
||||||
reorderProductImages.opts.loading ||
|
description: "dialog header"
|
||||||
updateProduct.opts.loading ||
|
})}
|
||||||
loading;
|
>
|
||||||
const formTransitionState = getMutationState(
|
<DialogContentText>
|
||||||
updateProduct.opts.called || updateSimpleProduct.opts.called,
|
<FormattedMessage
|
||||||
updateProduct.opts.loading || updateSimpleProduct.opts.loading,
|
defaultMessage="Are you sure you want to delete {name}?"
|
||||||
maybe(() => updateProduct.opts.data.productUpdate.errors),
|
description="delete product"
|
||||||
maybe(() => updateSimpleProduct.opts.data.productUpdate.errors),
|
values={{
|
||||||
maybe(
|
name: product ? product.name : undefined
|
||||||
() =>
|
|
||||||
updateSimpleProduct.opts.data.productVariantUpdate.errors
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const categories = maybe(
|
|
||||||
() => searchCategoriesOpts.data.search.edges,
|
|
||||||
[]
|
|
||||||
).map(edge => edge.node);
|
|
||||||
const collections = maybe(
|
|
||||||
() => searchCollectionsOpts.data.search.edges,
|
|
||||||
[]
|
|
||||||
).map(edge => edge.node);
|
|
||||||
const errors = [
|
|
||||||
...maybe(
|
|
||||||
() => updateProduct.opts.data.productUpdate.errors,
|
|
||||||
[]
|
|
||||||
),
|
|
||||||
...maybe(
|
|
||||||
() => updateSimpleProduct.opts.data.productUpdate.errors,
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WindowTitle title={maybe(() => data.product.name)} />
|
|
||||||
<ProductUpdatePage
|
|
||||||
categories={categories}
|
|
||||||
collections={collections}
|
|
||||||
defaultWeightUnit={shop?.defaultWeightUnit}
|
|
||||||
disabled={disableFormSave}
|
|
||||||
errors={errors}
|
|
||||||
fetchCategories={searchCategories}
|
|
||||||
fetchCollections={searchCollections}
|
|
||||||
saveButtonBarState={formTransitionState}
|
|
||||||
images={maybe(() => data.product.images)}
|
|
||||||
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")}
|
|
||||||
onImageReorder={handleImageReorder}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
onVariantAdd={handleVariantAdd}
|
|
||||||
onVariantsAdd={() => navigate(productVariantCreatorUrl(id))}
|
|
||||||
onVariantShow={variantId => () =>
|
|
||||||
navigate(productVariantEditUrl(product.id, variantId))}
|
|
||||||
onImageUpload={handleImageUpload}
|
|
||||||
onImageEdit={handleImageEdit}
|
|
||||||
onImageDelete={handleImageDelete}
|
|
||||||
toolbar={
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
onClick={() =>
|
|
||||||
openModal("remove-variants", {
|
|
||||||
ids: listElements
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
}
|
|
||||||
isChecked={isSelected}
|
|
||||||
selected={listElements.length}
|
|
||||||
toggle={toggle}
|
|
||||||
toggleAll={toggleAll}
|
|
||||||
fetchMoreCategories={{
|
|
||||||
hasMore: maybe(
|
|
||||||
() =>
|
|
||||||
searchCategoriesOpts.data.search.pageInfo.hasNextPage
|
|
||||||
),
|
|
||||||
loading: searchCategoriesOpts.loading,
|
|
||||||
onFetchMore: loadMoreCategories
|
|
||||||
}}
|
|
||||||
fetchMoreCollections={{
|
|
||||||
hasMore: maybe(
|
|
||||||
() =>
|
|
||||||
searchCollectionsOpts.data.search.pageInfo.hasNextPage
|
|
||||||
),
|
|
||||||
loading: searchCollectionsOpts.loading,
|
|
||||||
onFetchMore: loadMoreCollections
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ActionDialog
|
|
||||||
open={params.action === "remove"}
|
|
||||||
onClose={closeModal}
|
|
||||||
confirmButtonState={deleteProduct.opts.status}
|
|
||||||
onConfirm={() => deleteProduct.mutate({ id })}
|
|
||||||
variant="delete"
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete Product",
|
|
||||||
description: "dialog header"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Are you sure you want to delete {name}?"
|
|
||||||
description="delete product"
|
|
||||||
values={{
|
|
||||||
name: product ? product.name : undefined
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
<ActionDialog
|
|
||||||
open={params.action === "remove-variants"}
|
|
||||||
onClose={closeModal}
|
|
||||||
confirmButtonState={bulkProductVariantDelete.opts.status}
|
|
||||||
onConfirm={() =>
|
|
||||||
bulkProductVariantDelete.mutate({
|
|
||||||
ids: params.ids
|
|
||||||
})
|
|
||||||
}
|
|
||||||
variant="delete"
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Delete Product Variants",
|
|
||||||
description: "dialog header"
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<DialogContentText>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this variant?} other{Are you sure you want to delete {displayQuantity} variants?}}"
|
|
||||||
description="dialog content"
|
|
||||||
values={{
|
|
||||||
counter: maybe(() => params.ids.length),
|
|
||||||
displayQuantity: (
|
|
||||||
<strong>{maybe(() => params.ids.length)}</strong>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContentText>
|
|
||||||
</ActionDialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
</ProductUpdateOperations>
|
/>
|
||||||
);
|
</DialogContentText>
|
||||||
}}
|
</ActionDialog>
|
||||||
</TypedProductDetailsQuery>
|
<ActionDialog
|
||||||
|
open={params.action === "remove-variants"}
|
||||||
|
onClose={closeModal}
|
||||||
|
confirmButtonState={bulkProductVariantDeleteOpts.status}
|
||||||
|
onConfirm={() =>
|
||||||
|
bulkProductVariantDelete({
|
||||||
|
variables: {
|
||||||
|
ids: params.ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
variant="delete"
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Delete Product Variants",
|
||||||
|
description: "dialog header"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<DialogContentText>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="{counter,plural,one{Are you sure you want to delete this variant?} other{Are you sure you want to delete {displayQuantity} variants?}}"
|
||||||
|
description="dialog content"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContentText>
|
||||||
|
</ActionDialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductUpdate;
|
export default ProductUpdate;
|
||||||
|
|
|
@ -15,12 +15,14 @@ import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog
|
||||||
import ProductVariantPage, {
|
import ProductVariantPage, {
|
||||||
ProductVariantPageSubmitData
|
ProductVariantPageSubmitData
|
||||||
} from "../components/ProductVariantPage";
|
} from "../components/ProductVariantPage";
|
||||||
import ProductVariantOperations from "../containers/ProductVariantOperations";
|
|
||||||
import { TypedProductVariantQuery } from "../queries";
|
|
||||||
import {
|
import {
|
||||||
VariantUpdate,
|
useVariantDeleteMutation,
|
||||||
VariantUpdate_productVariantUpdate_errors
|
useVariantImageAssignMutation,
|
||||||
} from "../types/VariantUpdate";
|
useVariantImageUnassignMutation,
|
||||||
|
useVariantUpdateMutation
|
||||||
|
} from "../mutations";
|
||||||
|
import { useProductVariantQuery } from "../queries";
|
||||||
|
import { VariantUpdate_productVariantUpdate_errors } from "../types/VariantUpdate";
|
||||||
import {
|
import {
|
||||||
productUrl,
|
productUrl,
|
||||||
productVariantAddUrl,
|
productVariantAddUrl,
|
||||||
|
@ -59,6 +61,13 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data, loading } = useProductVariantQuery({
|
||||||
|
displayLoader: true,
|
||||||
|
variables: {
|
||||||
|
id: variantId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const [openModal] = createDialogActionHandlers<
|
const [openModal] = createDialogActionHandlers<
|
||||||
ProductVariantEditUrlDialog,
|
ProductVariantEditUrlDialog,
|
||||||
ProductVariantEditUrlQueryParams
|
ProductVariantEditUrlQueryParams
|
||||||
|
@ -70,132 +79,122 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
||||||
|
|
||||||
const handleBack = () => navigate(productUrl(productId));
|
const handleBack = () => navigate(productUrl(productId));
|
||||||
|
|
||||||
return (
|
const [assignImage, assignImageOpts] = useVariantImageAssignMutation({});
|
||||||
<TypedProductVariantQuery displayLoader variables={{ id: variantId }}>
|
const [unassignImage, unassignImageOpts] = useVariantImageUnassignMutation(
|
||||||
{({ data, loading }) => {
|
{}
|
||||||
const variant = data?.productVariant;
|
);
|
||||||
|
const [deleteVariant, deleteVariantOpts] = useVariantDeleteMutation({
|
||||||
|
onCompleted: () => {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage({
|
||||||
|
defaultMessage: "Variant removed"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
navigate(productUrl(productId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const [updateVariant, updateVariantOpts] = useVariantUpdateMutation({
|
||||||
|
onCompleted: data => {
|
||||||
|
if (data.productVariantUpdate.errors.length === 0) {
|
||||||
|
notify({
|
||||||
|
status: "success",
|
||||||
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setErrors(data.productVariantUpdate.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (variant === null) {
|
const variant = data?.productVariant;
|
||||||
return <NotFoundPage onBack={handleBack} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
if (variant === null) {
|
||||||
notify({
|
return <NotFoundPage onBack={handleBack} />;
|
||||||
status: "success",
|
}
|
||||||
text: intl.formatMessage({
|
|
||||||
defaultMessage: "Variant removed"
|
const disableFormSave =
|
||||||
})
|
loading ||
|
||||||
});
|
deleteVariantOpts.loading ||
|
||||||
navigate(productUrl(productId));
|
updateVariantOpts.loading ||
|
||||||
};
|
assignImageOpts.loading ||
|
||||||
const handleUpdate = (data: VariantUpdate) => {
|
unassignImageOpts.loading;
|
||||||
if (data.productVariantUpdate.errors.length === 0) {
|
|
||||||
notify({
|
const handleImageSelect = (id: string) => () => {
|
||||||
status: "success",
|
if (variant) {
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
if (variant?.images?.map(image => image.id).indexOf(id) !== -1) {
|
||||||
});
|
unassignImage({
|
||||||
} else {
|
variables: {
|
||||||
setErrors(data.productVariantUpdate.errors);
|
imageId: id,
|
||||||
|
variantId: variant.id
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
} else {
|
||||||
|
assignImage({
|
||||||
|
variables: {
|
||||||
|
imageId: id,
|
||||||
|
variantId: variant.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProductVariantOperations
|
<>
|
||||||
onDelete={handleDelete}
|
<WindowTitle title={data?.productVariant?.name} />
|
||||||
onUpdate={handleUpdate}
|
<ProductVariantPage
|
||||||
>
|
defaultWeightUnit={shop?.defaultWeightUnit}
|
||||||
{({ assignImage, deleteVariant, updateVariant, unassignImage }) => {
|
errors={errors}
|
||||||
const disableFormSave =
|
saveButtonBarState={updateVariantOpts.status}
|
||||||
loading ||
|
loading={disableFormSave}
|
||||||
deleteVariant.opts.loading ||
|
placeholderImage={placeholderImg}
|
||||||
updateVariant.opts.loading ||
|
variant={variant}
|
||||||
assignImage.opts.loading ||
|
header={variant?.name || variant?.sku}
|
||||||
unassignImage.opts.loading;
|
warehouses={
|
||||||
|
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||||
const handleImageSelect = (id: string) => () => {
|
}
|
||||||
if (variant) {
|
onAdd={() => navigate(productVariantAddUrl(productId))}
|
||||||
if (
|
onBack={handleBack}
|
||||||
variant.images &&
|
onDelete={() => openModal("remove")}
|
||||||
variant.images.map(image => image.id).indexOf(id) !== -1
|
onImageSelect={handleImageSelect}
|
||||||
) {
|
onSubmit={(data: ProductVariantPageSubmitData) =>
|
||||||
unassignImage.mutate({
|
updateVariant({
|
||||||
imageId: id,
|
variables: {
|
||||||
variantId: variant.id
|
addStocks: data.addStocks.map(mapFormsetStockToStockInput),
|
||||||
});
|
attributes: data.attributes.map(attribute => ({
|
||||||
} else {
|
id: attribute.id,
|
||||||
assignImage.mutate({
|
values: [attribute.value]
|
||||||
imageId: id,
|
})),
|
||||||
variantId: variant.id
|
costPrice: decimal(data.costPrice),
|
||||||
});
|
id: variantId,
|
||||||
}
|
price: decimal(data.price),
|
||||||
}
|
removeStocks: data.removeStocks,
|
||||||
};
|
sku: data.sku,
|
||||||
|
stocks: data.updateStocks.map(mapFormsetStockToStockInput),
|
||||||
return (
|
trackInventory: data.trackInventory,
|
||||||
<>
|
weight: weight(data.weight)
|
||||||
<WindowTitle title={data?.productVariant?.name} />
|
}
|
||||||
<ProductVariantPage
|
})
|
||||||
defaultWeightUnit={shop?.defaultWeightUnit}
|
}
|
||||||
errors={errors}
|
onVariantClick={variantId => {
|
||||||
saveButtonBarState={updateVariant.opts.status}
|
navigate(productVariantEditUrl(productId, variantId));
|
||||||
loading={disableFormSave}
|
}}
|
||||||
placeholderImage={placeholderImg}
|
/>
|
||||||
variant={variant}
|
<ProductVariantDeleteDialog
|
||||||
header={variant?.name || variant?.sku}
|
confirmButtonState={deleteVariantOpts.status}
|
||||||
warehouses={
|
onClose={() => navigate(productVariantEditUrl(productId, variantId))}
|
||||||
warehouses.data?.warehouses.edges.map(
|
onConfirm={() =>
|
||||||
edge => edge.node
|
deleteVariant({
|
||||||
) || []
|
variables: {
|
||||||
}
|
id: variantId
|
||||||
onAdd={() => navigate(productVariantAddUrl(productId))}
|
}
|
||||||
onBack={handleBack}
|
})
|
||||||
onDelete={() => openModal("remove")}
|
}
|
||||||
onImageSelect={handleImageSelect}
|
open={params.action === "remove"}
|
||||||
onSubmit={(data: ProductVariantPageSubmitData) =>
|
name={data?.productVariant?.name}
|
||||||
updateVariant.mutate({
|
/>
|
||||||
addStocks: data.addStocks.map(
|
</>
|
||||||
mapFormsetStockToStockInput
|
|
||||||
),
|
|
||||||
attributes: data.attributes.map(attribute => ({
|
|
||||||
id: attribute.id,
|
|
||||||
values: [attribute.value]
|
|
||||||
})),
|
|
||||||
costPrice: decimal(data.costPrice),
|
|
||||||
id: variantId,
|
|
||||||
price: decimal(data.price),
|
|
||||||
removeStocks: data.removeStocks,
|
|
||||||
sku: data.sku,
|
|
||||||
stocks: data.updateStocks.map(
|
|
||||||
mapFormsetStockToStockInput
|
|
||||||
),
|
|
||||||
trackInventory: data.trackInventory,
|
|
||||||
weight: weight(data.weight)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onVariantClick={variantId => {
|
|
||||||
navigate(productVariantEditUrl(productId, variantId));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ProductVariantDeleteDialog
|
|
||||||
confirmButtonState={deleteVariant.opts.status}
|
|
||||||
onClose={() =>
|
|
||||||
navigate(productVariantEditUrl(productId, variantId))
|
|
||||||
}
|
|
||||||
onConfirm={() =>
|
|
||||||
deleteVariant.mutate({
|
|
||||||
id: variantId
|
|
||||||
})
|
|
||||||
}
|
|
||||||
open={params.action === "remove"}
|
|
||||||
name={data?.productVariant?.name}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</ProductVariantOperations>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductVariantQuery>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductVariant;
|
export default ProductVariant;
|
||||||
|
|
|
@ -12,9 +12,8 @@ import { decimal, weight } from "../../misc";
|
||||||
import ProductVariantCreatePage, {
|
import ProductVariantCreatePage, {
|
||||||
ProductVariantCreatePageSubmitData
|
ProductVariantCreatePageSubmitData
|
||||||
} from "../components/ProductVariantCreatePage";
|
} from "../components/ProductVariantCreatePage";
|
||||||
import { TypedVariantCreateMutation } from "../mutations";
|
import { useVariantCreateMutation } from "../mutations";
|
||||||
import { TypedProductVariantCreateQuery } from "../queries";
|
import { useProductVariantCreateQuery } from "../queries";
|
||||||
import { VariantCreate } from "../types/VariantCreate";
|
|
||||||
import { productListUrl, productUrl, productVariantEditUrl } from "../urls";
|
import { productListUrl, productUrl, productVariantEditUrl } from "../urls";
|
||||||
|
|
||||||
interface ProductVariantCreateProps {
|
interface ProductVariantCreateProps {
|
||||||
|
@ -35,102 +34,90 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
const { data, loading: productLoading } = useProductVariantCreateQuery({
|
||||||
<TypedProductVariantCreateQuery displayLoader variables={{ id: productId }}>
|
displayLoader: true,
|
||||||
{({ data, loading: productLoading }) => {
|
variables: { id: productId }
|
||||||
const product = data?.product;
|
});
|
||||||
|
|
||||||
if (product === null) {
|
const [variantCreate, variantCreateResult] = useVariantCreateMutation({
|
||||||
return <NotFoundPage onBack={() => navigate(productListUrl())} />;
|
onCompleted: data => {
|
||||||
}
|
if (data.productVariantCreate.errors.length === 0) {
|
||||||
|
notify({
|
||||||
const handleCreateSuccess = (data: VariantCreate) => {
|
status: "success",
|
||||||
if (data.productVariantCreate.errors.length === 0) {
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
notify({
|
});
|
||||||
status: "success",
|
navigate(
|
||||||
text: intl.formatMessage(commonMessages.savedChanges)
|
productVariantEditUrl(
|
||||||
});
|
productId,
|
||||||
navigate(
|
data.productVariantCreate.productVariant.id
|
||||||
productVariantEditUrl(
|
)
|
||||||
productId,
|
|
||||||
data.productVariantCreate.productVariant.id
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TypedVariantCreateMutation onCompleted={handleCreateSuccess}>
|
|
||||||
{(variantCreate, variantCreateResult) => {
|
|
||||||
const handleBack = () => navigate(productUrl(productId));
|
|
||||||
const handleSubmit = (
|
|
||||||
formData: ProductVariantCreatePageSubmitData
|
|
||||||
) =>
|
|
||||||
variantCreate({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
attributes: formData.attributes
|
|
||||||
.filter(attribute => attribute.value !== "")
|
|
||||||
.map(attribute => ({
|
|
||||||
id: attribute.id,
|
|
||||||
values: [attribute.value]
|
|
||||||
})),
|
|
||||||
costPrice: decimal(formData.costPrice),
|
|
||||||
price: decimal(formData.price),
|
|
||||||
product: productId,
|
|
||||||
sku: formData.sku,
|
|
||||||
stocks: formData.stocks.map(stock => ({
|
|
||||||
quantity: parseInt(stock.value, 0),
|
|
||||||
warehouse: stock.id
|
|
||||||
})),
|
|
||||||
trackInventory: true,
|
|
||||||
weight: weight(formData.weight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const handleVariantClick = (id: string) =>
|
|
||||||
navigate(productVariantEditUrl(productId, id));
|
|
||||||
|
|
||||||
const disableForm = productLoading || variantCreateResult.loading;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<WindowTitle
|
|
||||||
title={intl.formatMessage({
|
|
||||||
defaultMessage: "Create variant",
|
|
||||||
description: "window title"
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<ProductVariantCreatePage
|
|
||||||
currencySymbol={shop?.defaultCurrency}
|
|
||||||
disabled={disableForm}
|
|
||||||
errors={
|
|
||||||
variantCreateResult.data?.productVariantCreate.errors ||
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
header={intl.formatMessage({
|
|
||||||
defaultMessage: "Create Variant",
|
|
||||||
description: "header"
|
|
||||||
})}
|
|
||||||
product={data?.product}
|
|
||||||
onBack={handleBack}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
onVariantClick={handleVariantClick}
|
|
||||||
saveButtonBarState={variantCreateResult.status}
|
|
||||||
warehouses={
|
|
||||||
warehouses.data?.warehouses.edges.map(
|
|
||||||
edge => edge.node
|
|
||||||
) || []
|
|
||||||
}
|
|
||||||
weightUnit={shop?.defaultWeightUnit}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedVariantCreateMutation>
|
|
||||||
);
|
);
|
||||||
}}
|
}
|
||||||
</TypedProductVariantCreateQuery>
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const product = data?.product;
|
||||||
|
|
||||||
|
if (product === null) {
|
||||||
|
return <NotFoundPage onBack={() => navigate(productListUrl())} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBack = () => navigate(productUrl(productId));
|
||||||
|
const handleSubmit = (formData: ProductVariantCreatePageSubmitData) =>
|
||||||
|
variantCreate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
attributes: formData.attributes
|
||||||
|
.filter(attribute => attribute.value !== "")
|
||||||
|
.map(attribute => ({
|
||||||
|
id: attribute.id,
|
||||||
|
values: [attribute.value]
|
||||||
|
})),
|
||||||
|
costPrice: decimal(formData.costPrice),
|
||||||
|
price: decimal(formData.price),
|
||||||
|
product: productId,
|
||||||
|
sku: formData.sku,
|
||||||
|
stocks: formData.stocks.map(stock => ({
|
||||||
|
quantity: parseInt(stock.value, 0),
|
||||||
|
warehouse: stock.id
|
||||||
|
})),
|
||||||
|
trackInventory: true,
|
||||||
|
weight: weight(formData.weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const handleVariantClick = (id: string) =>
|
||||||
|
navigate(productVariantEditUrl(productId, id));
|
||||||
|
|
||||||
|
const disableForm = productLoading || variantCreateResult.loading;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WindowTitle
|
||||||
|
title={intl.formatMessage({
|
||||||
|
defaultMessage: "Create variant",
|
||||||
|
description: "window title"
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<ProductVariantCreatePage
|
||||||
|
currencySymbol={shop?.defaultCurrency}
|
||||||
|
disabled={disableForm}
|
||||||
|
errors={variantCreateResult.data?.productVariantCreate.errors || []}
|
||||||
|
header={intl.formatMessage({
|
||||||
|
defaultMessage: "Create Variant",
|
||||||
|
description: "header"
|
||||||
|
})}
|
||||||
|
product={data?.product}
|
||||||
|
onBack={handleBack}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
onVariantClick={handleVariantClick}
|
||||||
|
saveButtonBarState={variantCreateResult.status}
|
||||||
|
warehouses={
|
||||||
|
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||||
|
}
|
||||||
|
weightUnit={shop?.defaultWeightUnit}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductVariant;
|
export default ProductVariant;
|
||||||
|
|
Loading…
Reference in a new issue