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
|
||||
- Fix user management modal actions - #637 by @eaglesemanation
|
||||
- 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
|
||||
|
||||
|
|
|
@ -17,8 +17,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
|
||||
import { PAGINATE_BY } from "../../config";
|
||||
import { maybe } from "../../misc";
|
||||
import { TypedProductBulkDeleteMutation } from "../../products/mutations";
|
||||
import { productBulkDelete } from "../../products/types/productBulkDelete";
|
||||
import { useProductBulkDeleteMutation } from "../../products/mutations";
|
||||
import { productAddUrl, productUrl } from "../../products/urls";
|
||||
import { CategoryInput } from "../../types/globalTypes";
|
||||
import {
|
||||
|
@ -129,6 +128,23 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
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) => {
|
||||
reset();
|
||||
navigate(
|
||||
|
@ -143,18 +159,6 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
CategoryUrlQueryParams
|
||||
>(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(
|
||||
params.activeTab === CategoryPageTab.categories
|
||||
? maybe(() => data.category.children.pageInfo)
|
||||
|
@ -166,191 +170,178 @@ export const CategoryDetails: React.FC<CategoryDetailsProps> = ({
|
|||
return (
|
||||
<>
|
||||
<WindowTitle title={maybe(() => data.category.name)} />
|
||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkProductDelete}>
|
||||
{(productBulkDelete, productBulkDeleteOpts) => (
|
||||
<>
|
||||
<CategoryUpdatePage
|
||||
changeTab={changeTab}
|
||||
currentTab={params.activeTab}
|
||||
category={maybe(() => data.category)}
|
||||
disabled={loading}
|
||||
errors={updateResult.data?.categoryUpdate.errors || []}
|
||||
onAddCategory={() => navigate(categoryAddUrl(id))}
|
||||
onAddProduct={() => navigate(productAddUrl)}
|
||||
onBack={() =>
|
||||
navigate(
|
||||
maybe(
|
||||
() => categoryUrl(data.category.parent.id),
|
||||
categoryListUrl()
|
||||
)
|
||||
)
|
||||
<CategoryUpdatePage
|
||||
changeTab={changeTab}
|
||||
currentTab={params.activeTab}
|
||||
category={maybe(() => data.category)}
|
||||
disabled={loading}
|
||||
errors={updateResult.data?.categoryUpdate.errors || []}
|
||||
onAddCategory={() => navigate(categoryAddUrl(id))}
|
||||
onAddProduct={() => navigate(productAddUrl)}
|
||||
onBack={() =>
|
||||
navigate(
|
||||
maybe(() => categoryUrl(data.category.parent.id), categoryListUrl())
|
||||
)
|
||||
}
|
||||
onCategoryClick={id => () => navigate(categoryUrl(id))}
|
||||
onDelete={() => openModal("delete")}
|
||||
onImageDelete={() =>
|
||||
updateCategory({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
backgroundImage: null
|
||||
}
|
||||
onCategoryClick={id => () => navigate(categoryUrl(id))}
|
||||
onDelete={() => openModal("delete")}
|
||||
onImageDelete={() =>
|
||||
updateCategory({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
backgroundImage: null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
onImageUpload={file =>
|
||||
updateCategory({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
backgroundImage: file
|
||||
}
|
||||
onImageUpload={file =>
|
||||
updateCategory({
|
||||
variables: {
|
||||
id,
|
||||
input: {
|
||||
backgroundImage: file
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
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))}
|
||||
onSubmit={formData =>
|
||||
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>
|
||||
</>
|
||||
}
|
||||
})
|
||||
}
|
||||
products={maybe(() =>
|
||||
data.category.products.edges.map(edge => edge.node)
|
||||
)}
|
||||
</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 { TypedMutationInnerProps } from "../../mutations";
|
||||
import { TypedProductImagesReorder } from "../mutations";
|
||||
import { useProductImagesReorder } from "../mutations";
|
||||
import {
|
||||
ProductImageReorder,
|
||||
ProductImageReorderVariables
|
||||
|
@ -24,10 +24,12 @@ const ProductImagesReorderProvider: React.FC<ProductImagesReorderProviderProps>
|
|||
productId,
|
||||
productImages,
|
||||
...mutationProps
|
||||
}) => (
|
||||
<TypedProductImagesReorder {...mutationProps}>
|
||||
{(mutate, mutationResult) =>
|
||||
children(opts => {
|
||||
}) => {
|
||||
const [mutate, mutationResult] = useProductImagesReorder(mutationProps);
|
||||
|
||||
return (
|
||||
<>
|
||||
{children(opts => {
|
||||
const productImagesMap = productImages.reduce((prev, curr) => {
|
||||
prev[curr.id] = curr;
|
||||
return prev;
|
||||
|
@ -52,9 +54,9 @@ const ProductImagesReorderProvider: React.FC<ProductImagesReorderProviderProps>
|
|||
...opts,
|
||||
optimisticResponse
|
||||
});
|
||||
}, mutationResult)
|
||||
}
|
||||
</TypedProductImagesReorder>
|
||||
);
|
||||
}, mutationResult)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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 gql from "graphql-tag";
|
||||
|
||||
import { TypedMutation } from "../mutations";
|
||||
import {
|
||||
productBulkDelete,
|
||||
productBulkDeleteVariables
|
||||
|
@ -80,7 +79,7 @@ export const productImageCreateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductImageCreateMutation = TypedMutation<
|
||||
export const useProductImageCreateMutation = makeMutation<
|
||||
ProductImageCreate,
|
||||
ProductImageCreateVariables
|
||||
>(productImageCreateMutation);
|
||||
|
@ -98,7 +97,7 @@ export const productDeleteMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductDeleteMutation = TypedMutation<
|
||||
export const useProductDeleteMutation = makeMutation<
|
||||
ProductDelete,
|
||||
ProductDeleteVariables
|
||||
>(productDeleteMutation);
|
||||
|
@ -122,7 +121,7 @@ export const productImagesReorder = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductImagesReorder = TypedMutation<
|
||||
export const useProductImagesReorder = makeMutation<
|
||||
ProductImageReorder,
|
||||
ProductImageReorderVariables
|
||||
>(productImagesReorder);
|
||||
|
@ -167,7 +166,7 @@ export const productUpdateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductUpdateMutation = TypedMutation<
|
||||
export const useProductUpdateMutation = makeMutation<
|
||||
ProductUpdate,
|
||||
ProductUpdateVariables
|
||||
>(productUpdateMutation);
|
||||
|
@ -263,7 +262,7 @@ export const simpleProductUpdateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedSimpleProductUpdateMutation = TypedMutation<
|
||||
export const useSimpleProductUpdateMutation = makeMutation<
|
||||
SimpleProductUpdate,
|
||||
SimpleProductUpdateVariables
|
||||
>(simpleProductUpdateMutation);
|
||||
|
@ -316,7 +315,7 @@ export const productCreateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductCreateMutation = TypedMutation<
|
||||
export const useProductCreateMutation = makeMutation<
|
||||
ProductCreate,
|
||||
ProductCreateVariables
|
||||
>(productCreateMutation);
|
||||
|
@ -334,7 +333,7 @@ export const variantDeleteMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedVariantDeleteMutation = TypedMutation<
|
||||
export const useVariantDeleteMutation = makeMutation<
|
||||
VariantDelete,
|
||||
VariantDeleteVariables
|
||||
>(variantDeleteMutation);
|
||||
|
@ -406,7 +405,7 @@ export const variantUpdateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedVariantUpdateMutation = TypedMutation<
|
||||
export const useVariantUpdateMutation = makeMutation<
|
||||
VariantUpdate,
|
||||
VariantUpdateVariables
|
||||
>(variantUpdateMutation);
|
||||
|
@ -425,7 +424,7 @@ export const variantCreateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedVariantCreateMutation = TypedMutation<
|
||||
export const useVariantCreateMutation = makeMutation<
|
||||
VariantCreate,
|
||||
VariantCreateVariables
|
||||
>(variantCreateMutation);
|
||||
|
@ -446,7 +445,7 @@ export const productImageDeleteMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductImageDeleteMutation = TypedMutation<
|
||||
export const useProductImageDeleteMutation = makeMutation<
|
||||
ProductImageDelete,
|
||||
ProductImageDeleteVariables
|
||||
>(productImageDeleteMutation);
|
||||
|
@ -465,7 +464,7 @@ export const productImageUpdateMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductImageUpdateMutation = TypedMutation<
|
||||
export const useProductImageUpdateMutation = makeMutation<
|
||||
ProductImageUpdate,
|
||||
ProductImageUpdateVariables
|
||||
>(productImageUpdateMutation);
|
||||
|
@ -484,7 +483,7 @@ export const variantImageAssignMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedVariantImageAssignMutation = TypedMutation<
|
||||
export const useVariantImageAssignMutation = makeMutation<
|
||||
VariantImageAssign,
|
||||
VariantImageAssignVariables
|
||||
>(variantImageAssignMutation);
|
||||
|
@ -503,7 +502,7 @@ export const variantImageUnassignMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedVariantImageUnassignMutation = TypedMutation<
|
||||
export const useVariantImageUnassignMutation = makeMutation<
|
||||
VariantImageUnassign,
|
||||
VariantImageUnassignVariables
|
||||
>(variantImageUnassignMutation);
|
||||
|
@ -518,7 +517,7 @@ export const productBulkDeleteMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductBulkDeleteMutation = TypedMutation<
|
||||
export const useProductBulkDeleteMutation = makeMutation<
|
||||
productBulkDelete,
|
||||
productBulkDeleteVariables
|
||||
>(productBulkDeleteMutation);
|
||||
|
@ -533,7 +532,7 @@ export const productBulkPublishMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductBulkPublishMutation = TypedMutation<
|
||||
export const useProductBulkPublishMutation = makeMutation<
|
||||
productBulkPublish,
|
||||
productBulkPublishVariables
|
||||
>(productBulkPublishMutation);
|
||||
|
@ -566,7 +565,7 @@ export const ProductVariantBulkDeleteMutation = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductVariantBulkDeleteMutation = TypedMutation<
|
||||
export const useProductVariantBulkDeleteMutation = makeMutation<
|
||||
ProductVariantBulkDelete,
|
||||
ProductVariantBulkDeleteVariables
|
||||
>(ProductVariantBulkDeleteMutation);
|
||||
|
|
|
@ -10,7 +10,6 @@ import { warehouseFragment } from "@saleor/fragments/warehouses";
|
|||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedQuery } from "../queries";
|
||||
import { CountAllProducts } from "./types/CountAllProducts";
|
||||
import {
|
||||
CreateMultipleVariantsData,
|
||||
|
@ -150,10 +149,9 @@ const productListQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductListQuery = TypedQuery<
|
||||
ProductList,
|
||||
ProductListVariables
|
||||
>(productListQuery);
|
||||
export const useProductListQuery = makeQuery<ProductList, ProductListVariables>(
|
||||
productListQuery
|
||||
);
|
||||
|
||||
const countAllProductsQuery = gql`
|
||||
query CountAllProducts {
|
||||
|
@ -174,7 +172,7 @@ const productDetailsQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductDetailsQuery = TypedQuery<
|
||||
export const useProductDetails = makeQuery<
|
||||
ProductDetails,
|
||||
ProductDetailsVariables
|
||||
>(productDetailsQuery);
|
||||
|
@ -187,7 +185,7 @@ const productVariantQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductVariantQuery = TypedQuery<
|
||||
export const useProductVariantQuery = makeQuery<
|
||||
ProductVariantDetails,
|
||||
ProductVariantDetailsVariables
|
||||
>(productVariantQuery);
|
||||
|
@ -231,7 +229,7 @@ const productVariantCreateQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductVariantCreateQuery = TypedQuery<
|
||||
export const useProductVariantCreateQuery = makeQuery<
|
||||
ProductVariantCreateData,
|
||||
ProductVariantCreateDataVariables
|
||||
>(productVariantCreateQuery);
|
||||
|
@ -253,7 +251,7 @@ const productImageQuery = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const TypedProductImageQuery = TypedQuery<
|
||||
export const useProductImageQuery = makeQuery<
|
||||
ProductImageById,
|
||||
ProductImageByIdVariables
|
||||
>(productImageQuery);
|
||||
|
@ -288,7 +286,7 @@ const availableInGridAttributes = gql`
|
|||
}
|
||||
}
|
||||
`;
|
||||
export const AvailableInGridAttributesQuery = TypedQuery<
|
||||
export const useAvailableInGridAttributesQuery = makeQuery<
|
||||
GridAttributes,
|
||||
GridAttributesVariables
|
||||
>(availableInGridAttributes);
|
||||
|
|
|
@ -10,12 +10,11 @@ import { useWarehouseList } from "@saleor/warehouses/queries";
|
|||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { decimal, maybe, weight } from "../../misc";
|
||||
import { decimal, weight } from "../../misc";
|
||||
import ProductCreatePage, {
|
||||
ProductCreatePageSubmitData
|
||||
} from "../components/ProductCreatePage";
|
||||
import { TypedProductCreateMutation } from "../mutations";
|
||||
import { ProductCreate } from "../types/ProductCreate";
|
||||
import { useProductCreateMutation } from "../mutations";
|
||||
import { productListUrl, productUrl } from "../urls";
|
||||
|
||||
export const ProductCreateView: React.FC = () => {
|
||||
|
@ -53,118 +52,104 @@ export const ProductCreateView: React.FC = () => {
|
|||
|
||||
const handleBack = () => navigate(productListUrl());
|
||||
|
||||
const handleSuccess = (data: ProductCreate) => {
|
||||
if (data.productCreate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Product created"
|
||||
})
|
||||
});
|
||||
navigate(productUrl(data.productCreate.product.id));
|
||||
const [productCreate, productCreateOpts] = useProductCreateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.productCreate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Product created"
|
||||
})
|
||||
});
|
||||
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 (
|
||||
<TypedProductCreateMutation onCompleted={handleSuccess}>
|
||||
{(productCreate, productCreateOpts) => {
|
||||
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 (
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create Product",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<ProductCreatePage
|
||||
currency={maybe(() => shop.defaultCurrency)}
|
||||
categories={maybe(
|
||||
() => searchCategoryOpts.data.search.edges,
|
||||
[]
|
||||
).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>
|
||||
<>
|
||||
<WindowTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Create Product",
|
||||
description: "window title"
|
||||
})}
|
||||
/>
|
||||
<ProductCreatePage
|
||||
currency={shop?.defaultCurrency}
|
||||
categories={(searchCategoryOpts.data?.search.edges || []).map(
|
||||
edge => edge.node
|
||||
)}
|
||||
collections={(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={searchProductTypesOpts.data?.search.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmit}
|
||||
saveButtonBarState={productCreateOpts.status}
|
||||
fetchMoreCategories={{
|
||||
hasMore: searchCategoryOpts.data?.search.pageInfo.hasNextPage,
|
||||
loading: searchCategoryOpts.loading,
|
||||
onFetchMore: loadMoreCategories
|
||||
}}
|
||||
fetchMoreCollections={{
|
||||
hasMore: searchCollectionOpts.data?.search.pageInfo.hasNextPage,
|
||||
loading: searchCollectionOpts.loading,
|
||||
onFetchMore: loadMoreCollections
|
||||
}}
|
||||
fetchMoreProductTypes={{
|
||||
hasMore: searchProductTypesOpts.data?.search.pageInfo.hasNextPage,
|
||||
loading: searchProductTypesOpts.loading,
|
||||
onFetchMore: loadMoreProductTypes
|
||||
}}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||
}
|
||||
weightUnit={shop?.defaultWeightUnit}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProductCreateView;
|
||||
|
|
|
@ -7,14 +7,12 @@ import { commonMessages } from "@saleor/intl";
|
|||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { maybe } from "../../misc";
|
||||
import ProductImagePage from "../components/ProductImagePage";
|
||||
import {
|
||||
TypedProductImageDeleteMutation,
|
||||
TypedProductImageUpdateMutation
|
||||
useProductImageDeleteMutation,
|
||||
useProductImageUpdateMutation
|
||||
} from "../mutations";
|
||||
import { TypedProductImageQuery } from "../queries";
|
||||
import { ProductImageUpdate } from "../types/ProductImageUpdate";
|
||||
import { useProductImageQuery } from "../queries";
|
||||
import {
|
||||
productImageUrl,
|
||||
ProductImageUrlQueryParams,
|
||||
|
@ -38,93 +36,84 @@ export const ProductImage: React.FC<ProductImageProps> = ({
|
|||
const intl = useIntl();
|
||||
|
||||
const handleBack = () => navigate(productUrl(productId));
|
||||
const handleUpdateSuccess = (data: ProductImageUpdate) => {
|
||||
if (data.productImageUpdate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
|
||||
const { data, loading } = useProductImageQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
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 (
|
||||
<TypedProductImageQuery
|
||||
displayLoader
|
||||
variables={{
|
||||
imageId,
|
||||
productId
|
||||
}}
|
||||
>
|
||||
{({ data, loading }) => {
|
||||
const product = data?.product;
|
||||
|
||||
if (product === null) {
|
||||
return <NotFoundPage onBack={() => navigate(productListUrl())} />;
|
||||
<>
|
||||
<ProductImagePage
|
||||
disabled={loading}
|
||||
product={data?.product?.name}
|
||||
image={image || null}
|
||||
images={data?.product?.images}
|
||||
onBack={handleBack}
|
||||
onDelete={() =>
|
||||
navigate(
|
||||
productImageUrl(productId, imageId, {
|
||||
action: "remove"
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TypedProductImageUpdateMutation onCompleted={handleUpdateSuccess}>
|
||||
{(updateImage, updateResult) => (
|
||||
<TypedProductImageDeleteMutation onCompleted={handleBack}>
|
||||
{(deleteImage, deleteResult) => {
|
||||
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 && data.product && data.product.mainImage;
|
||||
|
||||
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>
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProductImage;
|
||||
|
|
|
@ -45,18 +45,16 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
|
||||
import ProductListPage from "../../components/ProductListPage";
|
||||
import {
|
||||
TypedProductBulkDeleteMutation,
|
||||
TypedProductBulkPublishMutation,
|
||||
useProductBulkDeleteMutation,
|
||||
useProductBulkPublishMutation,
|
||||
useProductExport
|
||||
} from "../../mutations";
|
||||
import {
|
||||
AvailableInGridAttributesQuery,
|
||||
TypedProductListQuery,
|
||||
useAvailableInGridAttributesQuery,
|
||||
useCountAllProducts,
|
||||
useInitialProductFilterDataQuery
|
||||
useInitialProductFilterDataQuery,
|
||||
useProductListQuery
|
||||
} from "../../queries";
|
||||
import { productBulkDelete } from "../../types/productBulkDelete";
|
||||
import { productBulkPublish } from "../../types/productBulkPublish";
|
||||
import {
|
||||
productAddUrl,
|
||||
productListUrl,
|
||||
|
@ -235,6 +233,53 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
}),
|
||||
[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(
|
||||
params,
|
||||
|
@ -262,345 +307,259 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
}
|
||||
);
|
||||
|
||||
function filterColumnIds(columns: ProductListColumns[]) {
|
||||
return columns
|
||||
.filter(isAttributeColumnValue)
|
||||
.map(getAttributeIdFromColumnValue);
|
||||
}
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.products.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
return (
|
||||
<AvailableInGridAttributesQuery
|
||||
variables={{ first: 6, ids: filterColumnIds(settings.columns) }}
|
||||
>
|
||||
{attributes => (
|
||||
<TypedProductListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.products.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleBulkDelete = (data: productBulkDelete) => {
|
||||
if (data.productBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
<>
|
||||
<ProductListPage
|
||||
activeAttributeSortId={params.attributeId}
|
||||
sort={{
|
||||
asc: params.asc,
|
||||
sort: params.sort
|
||||
}}
|
||||
onSort={handleSort}
|
||||
availableInGridAttributes={maybe(
|
||||
() => attributes.data.availableInGrid.edges.map(edge => edge.node),
|
||||
[]
|
||||
)}
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]}
|
||||
filterOpts={filterOpts}
|
||||
gridAttributes={maybe(
|
||||
() => attributes.data.grid.edges.map(edge => edge.node),
|
||||
[]
|
||||
)}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkPublish = (data: productBulkPublish) => {
|
||||
if (data.productBulkPublish.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
return {
|
||||
...prev,
|
||||
availableInGrid: {
|
||||
...prev.availableInGrid,
|
||||
edges: [
|
||||
...prev.availableInGrid.edges,
|
||||
...next.availableInGrid.edges
|
||||
],
|
||||
pageInfo: next.availableInGrid.pageInfo
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
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
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
||||
{(productBulkDelete, productBulkDeleteOpts) => (
|
||||
<TypedProductBulkPublishMutation
|
||||
onCompleted={handleBulkPublish}
|
||||
>
|
||||
{(productBulkPublish, productBulkPublishOpts) => (
|
||||
<>
|
||||
<ProductListPage
|
||||
activeAttributeSortId={params.attributeId}
|
||||
sort={{
|
||||
asc: params.asc,
|
||||
sort: params.sort
|
||||
}}
|
||||
onSort={handleSort}
|
||||
availableInGridAttributes={maybe(
|
||||
() =>
|
||||
attributes.data.availableInGrid.edges.map(
|
||||
edge => edge.node
|
||||
),
|
||||
[]
|
||||
)}
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
defaultSettings={
|
||||
defaultListSettings[ListViews.PRODUCT_LIST]
|
||||
}
|
||||
filterOpts={filterOpts}
|
||||
gridAttributes={maybe(
|
||||
() =>
|
||||
attributes.data.grid.edges.map(edge => edge.node),
|
||||
[]
|
||||
)}
|
||||
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,
|
||||
availableInGrid: {
|
||||
...prev.availableInGrid,
|
||||
edges: [
|
||||
...prev.availableInGrid.edges,
|
||||
...next.availableInGrid.edges
|
||||
],
|
||||
pageInfo: next.availableInGrid.pageInfo
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
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
|
||||
defaultMessage="Unpublish"
|
||||
description="unpublish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("publish", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Publish"
|
||||
description="publish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("delete", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
onSearchChange={handleSearchChange}
|
||||
onFilterChange={changeFilters}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabChange={handleTabChange}
|
||||
initialSearch={params.query || ""}
|
||||
tabs={getFilterTabs().map(tab => tab.name)}
|
||||
onExport={() => openModal("export")}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "delete"}
|
||||
confirmButtonState={productBulkDeleteOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkDelete({
|
||||
variables: { ids: params.ids }
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
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?}}"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
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>
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unpublish"
|
||||
description="unpublish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("publish", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Publish"
|
||||
description="publish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("delete", {
|
||||
ids: listElements
|
||||
})
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
onSearchChange={handleSearchChange}
|
||||
onFilterChange={changeFilters}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabChange={handleTabChange}
|
||||
initialSearch={params.query || ""}
|
||||
tabs={getFilterTabs().map(tab => tab.name)}
|
||||
onExport={() => openModal("export")}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "delete"}
|
||||
confirmButtonState={productBulkDeleteOpts.status}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkDelete({
|
||||
variables: { ids: params.ids }
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
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?}}"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: <strong>{maybe(() => params.ids.length)}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
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, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProductList;
|
||||
|
|
|
@ -11,6 +11,15 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
|||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import useShop from "@saleor/hooks/useShop";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import {
|
||||
useProductDeleteMutation,
|
||||
useProductImageCreateMutation,
|
||||
useProductImageDeleteMutation,
|
||||
useProductImagesReorder,
|
||||
useProductUpdateMutation,
|
||||
useProductVariantBulkDeleteMutation,
|
||||
useSimpleProductUpdateMutation
|
||||
} from "@saleor/products/mutations";
|
||||
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
||||
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
|
@ -20,14 +29,9 @@ import { FormattedMessage, useIntl } from "react-intl";
|
|||
|
||||
import { getMutationState, maybe } from "../../../misc";
|
||||
import ProductUpdatePage from "../../components/ProductUpdatePage";
|
||||
import ProductUpdateOperations from "../../containers/ProductUpdateOperations";
|
||||
import { TypedProductDetailsQuery } from "../../queries";
|
||||
import {
|
||||
ProductImageCreate,
|
||||
ProductImageCreateVariables
|
||||
} from "../../types/ProductImageCreate";
|
||||
import { useProductDetails } from "../../queries";
|
||||
import { ProductImageCreateVariables } from "../../types/ProductImageCreate";
|
||||
import { ProductUpdate as ProductUpdateMutationResult } from "../../types/ProductUpdate";
|
||||
import { ProductVariantBulkDelete } from "../../types/ProductVariantBulkDelete";
|
||||
import {
|
||||
productImageUrl,
|
||||
productListUrl,
|
||||
|
@ -78,6 +82,86 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
});
|
||||
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<
|
||||
ProductUrlDialog,
|
||||
ProductUrlQueryParams
|
||||
|
@ -85,256 +169,169 @@ export const ProductUpdate: React.FC<ProductUpdateProps> = ({ id, params }) => {
|
|||
|
||||
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 (
|
||||
<TypedProductDetailsQuery displayLoader variables={{ id }}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const product = data?.product;
|
||||
|
||||
if (product === null) {
|
||||
return <NotFoundPage onBack={handleBack} />;
|
||||
<>
|
||||
<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) || []
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Product removed"
|
||||
})
|
||||
});
|
||||
navigate(productListUrl());
|
||||
};
|
||||
const handleUpdate = (data: ProductUpdateMutationResult) => {
|
||||
if (data.productUpdate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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}
|
||||
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
|
||||
})
|
||||
}
|
||||
>
|
||||
{({
|
||||
bulkProductVariantDelete,
|
||||
createProductImage,
|
||||
deleteProduct,
|
||||
deleteProductImage,
|
||||
reorderProductImages,
|
||||
updateProduct,
|
||||
updateSimpleProduct
|
||||
}) => {
|
||||
const handleImageDelete = (id: string) => () =>
|
||||
deleteProductImage.mutate({ id });
|
||||
const handleImageEdit = (imageId: string) => () =>
|
||||
navigate(productImageUrl(id, imageId));
|
||||
const handleSubmit = createUpdateHandler(
|
||||
product,
|
||||
updateProduct.mutate,
|
||||
updateSimpleProduct.mutate
|
||||
);
|
||||
const handleImageUpload = createImageUploadHandler(
|
||||
id,
|
||||
createProductImage.mutate
|
||||
);
|
||||
const handleImageReorder = createImageReorderHandler(
|
||||
product,
|
||||
reorderProductImages.mutate
|
||||
);
|
||||
|
||||
const disableFormSave =
|
||||
createProductImage.opts.loading ||
|
||||
deleteProduct.opts.loading ||
|
||||
reorderProductImages.opts.loading ||
|
||||
updateProduct.opts.loading ||
|
||||
loading;
|
||||
const formTransitionState = getMutationState(
|
||||
updateProduct.opts.called || updateSimpleProduct.opts.called,
|
||||
updateProduct.opts.loading || updateSimpleProduct.opts.loading,
|
||||
maybe(() => updateProduct.opts.data.productUpdate.errors),
|
||||
maybe(() => updateSimpleProduct.opts.data.productUpdate.errors),
|
||||
maybe(
|
||||
() =>
|
||||
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>
|
||||
</>
|
||||
);
|
||||
<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={deleteProductOpts.status}
|
||||
onConfirm={() => deleteProduct({ variables: { 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
|
||||
}}
|
||||
</ProductUpdateOperations>
|
||||
);
|
||||
}}
|
||||
</TypedProductDetailsQuery>
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<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;
|
||||
|
|
|
@ -15,12 +15,14 @@ import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog
|
|||
import ProductVariantPage, {
|
||||
ProductVariantPageSubmitData
|
||||
} from "../components/ProductVariantPage";
|
||||
import ProductVariantOperations from "../containers/ProductVariantOperations";
|
||||
import { TypedProductVariantQuery } from "../queries";
|
||||
import {
|
||||
VariantUpdate,
|
||||
VariantUpdate_productVariantUpdate_errors
|
||||
} from "../types/VariantUpdate";
|
||||
useVariantDeleteMutation,
|
||||
useVariantImageAssignMutation,
|
||||
useVariantImageUnassignMutation,
|
||||
useVariantUpdateMutation
|
||||
} from "../mutations";
|
||||
import { useProductVariantQuery } from "../queries";
|
||||
import { VariantUpdate_productVariantUpdate_errors } from "../types/VariantUpdate";
|
||||
import {
|
||||
productUrl,
|
||||
productVariantAddUrl,
|
||||
|
@ -59,6 +61,13 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
}
|
||||
});
|
||||
|
||||
const { data, loading } = useProductVariantQuery({
|
||||
displayLoader: true,
|
||||
variables: {
|
||||
id: variantId
|
||||
}
|
||||
});
|
||||
|
||||
const [openModal] = createDialogActionHandlers<
|
||||
ProductVariantEditUrlDialog,
|
||||
ProductVariantEditUrlQueryParams
|
||||
|
@ -70,132 +79,122 @@ export const ProductVariant: React.FC<ProductUpdateProps> = ({
|
|||
|
||||
const handleBack = () => navigate(productUrl(productId));
|
||||
|
||||
return (
|
||||
<TypedProductVariantQuery displayLoader variables={{ id: variantId }}>
|
||||
{({ data, loading }) => {
|
||||
const variant = data?.productVariant;
|
||||
const [assignImage, assignImageOpts] = useVariantImageAssignMutation({});
|
||||
const [unassignImage, unassignImageOpts] = useVariantImageUnassignMutation(
|
||||
{}
|
||||
);
|
||||
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) {
|
||||
return <NotFoundPage onBack={handleBack} />;
|
||||
}
|
||||
const variant = data?.productVariant;
|
||||
|
||||
const handleDelete = () => {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Variant removed"
|
||||
})
|
||||
});
|
||||
navigate(productUrl(productId));
|
||||
};
|
||||
const handleUpdate = (data: VariantUpdate) => {
|
||||
if (data.productVariantUpdate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
} else {
|
||||
setErrors(data.productVariantUpdate.errors);
|
||||
if (variant === null) {
|
||||
return <NotFoundPage onBack={handleBack} />;
|
||||
}
|
||||
|
||||
const disableFormSave =
|
||||
loading ||
|
||||
deleteVariantOpts.loading ||
|
||||
updateVariantOpts.loading ||
|
||||
assignImageOpts.loading ||
|
||||
unassignImageOpts.loading;
|
||||
|
||||
const handleImageSelect = (id: string) => () => {
|
||||
if (variant) {
|
||||
if (variant?.images?.map(image => image.id).indexOf(id) !== -1) {
|
||||
unassignImage({
|
||||
variables: {
|
||||
imageId: id,
|
||||
variantId: variant.id
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
assignImage({
|
||||
variables: {
|
||||
imageId: id,
|
||||
variantId: variant.id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductVariantOperations
|
||||
onDelete={handleDelete}
|
||||
onUpdate={handleUpdate}
|
||||
>
|
||||
{({ assignImage, deleteVariant, updateVariant, unassignImage }) => {
|
||||
const disableFormSave =
|
||||
loading ||
|
||||
deleteVariant.opts.loading ||
|
||||
updateVariant.opts.loading ||
|
||||
assignImage.opts.loading ||
|
||||
unassignImage.opts.loading;
|
||||
|
||||
const handleImageSelect = (id: string) => () => {
|
||||
if (variant) {
|
||||
if (
|
||||
variant.images &&
|
||||
variant.images.map(image => image.id).indexOf(id) !== -1
|
||||
) {
|
||||
unassignImage.mutate({
|
||||
imageId: id,
|
||||
variantId: variant.id
|
||||
});
|
||||
} else {
|
||||
assignImage.mutate({
|
||||
imageId: id,
|
||||
variantId: variant.id
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={data?.productVariant?.name} />
|
||||
<ProductVariantPage
|
||||
defaultWeightUnit={shop?.defaultWeightUnit}
|
||||
errors={errors}
|
||||
saveButtonBarState={updateVariant.opts.status}
|
||||
loading={disableFormSave}
|
||||
placeholderImage={placeholderImg}
|
||||
variant={variant}
|
||||
header={variant?.name || variant?.sku}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(
|
||||
edge => edge.node
|
||||
) || []
|
||||
}
|
||||
onAdd={() => navigate(productVariantAddUrl(productId))}
|
||||
onBack={handleBack}
|
||||
onDelete={() => openModal("remove")}
|
||||
onImageSelect={handleImageSelect}
|
||||
onSubmit={(data: ProductVariantPageSubmitData) =>
|
||||
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>
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={data?.productVariant?.name} />
|
||||
<ProductVariantPage
|
||||
defaultWeightUnit={shop?.defaultWeightUnit}
|
||||
errors={errors}
|
||||
saveButtonBarState={updateVariantOpts.status}
|
||||
loading={disableFormSave}
|
||||
placeholderImage={placeholderImg}
|
||||
variant={variant}
|
||||
header={variant?.name || variant?.sku}
|
||||
warehouses={
|
||||
warehouses.data?.warehouses.edges.map(edge => edge.node) || []
|
||||
}
|
||||
onAdd={() => navigate(productVariantAddUrl(productId))}
|
||||
onBack={handleBack}
|
||||
onDelete={() => openModal("remove")}
|
||||
onImageSelect={handleImageSelect}
|
||||
onSubmit={(data: ProductVariantPageSubmitData) =>
|
||||
updateVariant({
|
||||
variables: {
|
||||
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={deleteVariantOpts.status}
|
||||
onClose={() => navigate(productVariantEditUrl(productId, variantId))}
|
||||
onConfirm={() =>
|
||||
deleteVariant({
|
||||
variables: {
|
||||
id: variantId
|
||||
}
|
||||
})
|
||||
}
|
||||
open={params.action === "remove"}
|
||||
name={data?.productVariant?.name}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProductVariant;
|
||||
|
|
|
@ -12,9 +12,8 @@ import { decimal, weight } from "../../misc";
|
|||
import ProductVariantCreatePage, {
|
||||
ProductVariantCreatePageSubmitData
|
||||
} from "../components/ProductVariantCreatePage";
|
||||
import { TypedVariantCreateMutation } from "../mutations";
|
||||
import { TypedProductVariantCreateQuery } from "../queries";
|
||||
import { VariantCreate } from "../types/VariantCreate";
|
||||
import { useVariantCreateMutation } from "../mutations";
|
||||
import { useProductVariantCreateQuery } from "../queries";
|
||||
import { productListUrl, productUrl, productVariantEditUrl } from "../urls";
|
||||
|
||||
interface ProductVariantCreateProps {
|
||||
|
@ -35,102 +34,90 @@ export const ProductVariant: React.FC<ProductVariantCreateProps> = ({
|
|||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedProductVariantCreateQuery displayLoader variables={{ id: productId }}>
|
||||
{({ data, loading: productLoading }) => {
|
||||
const product = data?.product;
|
||||
const { data, loading: productLoading } = useProductVariantCreateQuery({
|
||||
displayLoader: true,
|
||||
variables: { id: productId }
|
||||
});
|
||||
|
||||
if (product === null) {
|
||||
return <NotFoundPage onBack={() => navigate(productListUrl())} />;
|
||||
}
|
||||
|
||||
const handleCreateSuccess = (data: VariantCreate) => {
|
||||
if (data.productVariantCreate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(
|
||||
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>
|
||||
const [variantCreate, variantCreateResult] = useVariantCreateMutation({
|
||||
onCompleted: data => {
|
||||
if (data.productVariantCreate.errors.length === 0) {
|
||||
notify({
|
||||
status: "success",
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
navigate(
|
||||
productVariantEditUrl(
|
||||
productId,
|
||||
data.productVariantCreate.productVariant.id
|
||||
)
|
||||
);
|
||||
}}
|
||||
</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;
|
||||
|
|
Loading…
Reference in a new issue