Add product / page delete warning (#1095)
* Add Delete button component * Add product / page type delete warning dialog * Replace old product types delete dialog with new one, add products total count query * Update schema, types and queries for pages, add use page count query and add warning delete dialog to page types * Move type delete warning dialog data to proper hooks, refactor * Remove unused components and stories * Add plural forms to messages for product / page type delete warning, refactor * Add type delete warning dialog stories * Move type delete hooks to proper directiories, fix imports * Fix imports * Remove countallproducts query and instead use useproductcountquery * Remove unnecessary types and imports
This commit is contained in:
parent
4bf40ed410
commit
bb441ea11a
37 changed files with 900 additions and 302 deletions
|
@ -3579,6 +3579,7 @@ enum PageErrorCode {
|
|||
}
|
||||
|
||||
input PageFilterInput {
|
||||
pageTypes: [ID!]
|
||||
search: String
|
||||
metadata: [MetadataInput]
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperPr
|
|||
toolbar={
|
||||
<RequirePermissions
|
||||
userPermissions={user?.userPermissions || []}
|
||||
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
||||
requiredPermissions={[PermissionEnum.MANAGE_PRODUCTS]}
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
|
|
47
src/components/DeleteButton/DeleteButton.tsx
Normal file
47
src/components/DeleteButton/DeleteButton.tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { makeStyles } from "@saleor/theme";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
button: {
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.error.dark
|
||||
},
|
||||
backgroundColor: theme.palette.error.main,
|
||||
color: theme.palette.error.contrastText
|
||||
}
|
||||
}),
|
||||
{ name: "DeleteButton" }
|
||||
);
|
||||
|
||||
interface DeleteButtonProps {
|
||||
onClick: () => void;
|
||||
label?: string | React.ReactNode;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const DeleteButton: React.FC<DeleteButtonProps> = ({
|
||||
onClick,
|
||||
label,
|
||||
disabled = false
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={onClick}
|
||||
className={classes.button}
|
||||
data-test="button-bar-delete"
|
||||
disabled={disabled}
|
||||
>
|
||||
{label || intl.formatMessage(buttonMessages.delete)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteButton;
|
2
src/components/DeleteButton/index.tsx
Normal file
2
src/components/DeleteButton/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./DeleteButton";
|
||||
export { default } from "./DeleteButton";
|
|
@ -0,0 +1,40 @@
|
|||
import CentralPlacementDecorator from "@saleor/storybook/CentralPlacementDecorator";
|
||||
import CommonDecorator from "@saleor/storybook/Decorator";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import * as messages from "../../pageTypes/hooks/usePageTypeDelete/messages";
|
||||
import TypeDeleteWarningDialog, {
|
||||
TypeBaseData,
|
||||
TypeDeleteWarningDialogProps
|
||||
} from "./TypeDeleteWarningDialog";
|
||||
|
||||
const props: TypeDeleteWarningDialogProps<TypeBaseData> = {
|
||||
...messages,
|
||||
isOpen: true,
|
||||
onClose: () => undefined,
|
||||
onDelete: () => undefined,
|
||||
typesData: [{ id: "id-1", name: "Interesting Pages" }],
|
||||
isLoading: false,
|
||||
assignedItemsCount: 4,
|
||||
typesToDelete: ["id-1"],
|
||||
viewAssignedItemsUrl: "some-url",
|
||||
deleteButtonState: "default"
|
||||
};
|
||||
|
||||
storiesOf("TypeDeleteWarningDialog.stories", module)
|
||||
.addDecorator(CommonDecorator)
|
||||
.addDecorator(CentralPlacementDecorator)
|
||||
.add("loading", () => <TypeDeleteWarningDialog {...props} isLoading={true} />)
|
||||
.add("single type no assigned items", () => (
|
||||
<TypeDeleteWarningDialog {...props} assignedItemsCount={0} />
|
||||
))
|
||||
.add("single type some assigned items", () => (
|
||||
<TypeDeleteWarningDialog {...props} />
|
||||
))
|
||||
.add("multiple type no assigned items", () => (
|
||||
<TypeDeleteWarningDialog {...props} assignedItemsCount={0} />
|
||||
))
|
||||
.add("multiple types some assigned items", () => (
|
||||
<TypeDeleteWarningDialog {...props} typesToDelete={["id-1", "id-2"]} />
|
||||
));
|
|
@ -0,0 +1,132 @@
|
|||
import { CardContent } from "@material-ui/core";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import Modal from "@material-ui/core/Modal";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import ModalTitle from "@saleor/orders/components/OrderDiscountCommonModal/ModalTitle";
|
||||
import { getById } from "@saleor/orders/components/OrderReturnPage/utils";
|
||||
import React from "react";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { useTypeDeleteWarningDialogStyles as useStyles } from "./styles";
|
||||
import ProductTypeDeleteWarningDialogContent from "./TypeDeleteWarningDialogContent";
|
||||
import {
|
||||
CommonTypeDeleteWarningMessages,
|
||||
TypeDeleteWarningMessages
|
||||
} from "./types";
|
||||
|
||||
export interface TypeBaseData {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface TypeDeleteMessages {
|
||||
baseMessages: CommonTypeDeleteWarningMessages;
|
||||
singleWithItemsMessages: TypeDeleteWarningMessages;
|
||||
singleWithoutItemsMessages: TypeDeleteWarningMessages;
|
||||
multipleWithItemsMessages: TypeDeleteWarningMessages;
|
||||
multipleWithoutItemsMessages: TypeDeleteWarningMessages;
|
||||
}
|
||||
|
||||
export interface TypeDeleteWarningDialogProps<T extends TypeBaseData>
|
||||
extends TypeDeleteMessages {
|
||||
isOpen: boolean;
|
||||
deleteButtonState: ConfirmButtonTransitionState;
|
||||
onClose: () => void;
|
||||
onDelete: () => void;
|
||||
viewAssignedItemsUrl: string;
|
||||
typesToDelete: string[];
|
||||
assignedItemsCount: number | undefined;
|
||||
isLoading?: boolean;
|
||||
typesData: T[];
|
||||
// temporary, until we add filters to pages list - SALEOR-3279
|
||||
showViewAssignedItemsButton?: boolean;
|
||||
}
|
||||
|
||||
function TypeDeleteWarningDialog<T extends TypeBaseData>({
|
||||
isLoading = false,
|
||||
isOpen,
|
||||
baseMessages,
|
||||
singleWithItemsMessages,
|
||||
singleWithoutItemsMessages,
|
||||
multipleWithItemsMessages,
|
||||
multipleWithoutItemsMessages,
|
||||
onClose,
|
||||
onDelete,
|
||||
assignedItemsCount,
|
||||
viewAssignedItemsUrl,
|
||||
typesToDelete,
|
||||
typesData,
|
||||
showViewAssignedItemsButton = true
|
||||
}: TypeDeleteWarningDialogProps<T>) {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
const showMultiple = typesToDelete.length > 1;
|
||||
|
||||
const hasAssignedItems = !!assignedItemsCount;
|
||||
|
||||
const selectMessages = () => {
|
||||
if (showMultiple) {
|
||||
const multipleMessages = hasAssignedItems
|
||||
? multipleWithItemsMessages
|
||||
: multipleWithoutItemsMessages;
|
||||
|
||||
return {
|
||||
...multipleMessages
|
||||
};
|
||||
}
|
||||
|
||||
const singleMessages = hasAssignedItems
|
||||
? singleWithItemsMessages
|
||||
: singleWithoutItemsMessages;
|
||||
|
||||
return {
|
||||
...singleMessages
|
||||
};
|
||||
};
|
||||
|
||||
const { description, consentLabel } = selectMessages();
|
||||
|
||||
const singleItemSelectedId = typesToDelete[0];
|
||||
|
||||
const singleItemSelectedName = typesData.find(getById(singleItemSelectedId))
|
||||
?.name;
|
||||
|
||||
return (
|
||||
<Modal open={isOpen}>
|
||||
<div className={classes.centerContainer}>
|
||||
<Card className={classes.content}>
|
||||
<ModalTitle
|
||||
title={intl.formatMessage(baseMessages.title, {
|
||||
selectedTypesCount: typesToDelete.length
|
||||
})}
|
||||
withBorder
|
||||
onClose={onClose}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<CardContent className={classes.centerContainer}>
|
||||
<CircularProgress size={16} />
|
||||
</CardContent>
|
||||
) : (
|
||||
<ProductTypeDeleteWarningDialogContent
|
||||
showViewAssignedItemsButton={showViewAssignedItemsButton}
|
||||
assignedItemsCount={assignedItemsCount}
|
||||
hasAssignedItems={hasAssignedItems}
|
||||
singleItemSelectedName={singleItemSelectedName}
|
||||
viewAssignedItemsUrl={viewAssignedItemsUrl}
|
||||
onDelete={onDelete}
|
||||
description={description}
|
||||
consentLabel={consentLabel}
|
||||
viewAssignedItemsButtonLabel={
|
||||
baseMessages.viewAssignedItemsButtonLabel
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default TypeDeleteWarningDialog;
|
|
@ -0,0 +1,95 @@
|
|||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import HorizontalSpacer from "@saleor/apps/components/HorizontalSpacer";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import ConfirmButton from "@saleor/components/ConfirmButton";
|
||||
import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
|
||||
import DeleteButton from "@saleor/components/DeleteButton";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import React, { ChangeEvent, useState } from "react";
|
||||
import { MessageDescriptor, useIntl } from "react-intl";
|
||||
|
||||
import { useTypeDeleteWarningDialogStyles as useStyles } from "./styles";
|
||||
|
||||
interface TypeDeleteWarningDialogContentProps {
|
||||
singleItemSelectedName?: string;
|
||||
viewAssignedItemsButtonLabel: MessageDescriptor;
|
||||
description: MessageDescriptor;
|
||||
consentLabel: MessageDescriptor;
|
||||
viewAssignedItemsUrl: string;
|
||||
hasAssignedItems: boolean;
|
||||
assignedItemsCount: number | undefined;
|
||||
onDelete: () => void;
|
||||
// temporary, until we add filters to pages list - SALEOR-3279
|
||||
showViewAssignedItemsButton?: boolean;
|
||||
}
|
||||
|
||||
const TypeDeleteWarningDialogContent: React.FC<TypeDeleteWarningDialogContentProps> = ({
|
||||
description,
|
||||
consentLabel,
|
||||
viewAssignedItemsUrl,
|
||||
viewAssignedItemsButtonLabel,
|
||||
singleItemSelectedName,
|
||||
hasAssignedItems,
|
||||
assignedItemsCount,
|
||||
onDelete,
|
||||
showViewAssignedItemsButton
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigator();
|
||||
|
||||
const [isConsentChecked, setIsConsentChecked] = useState(false);
|
||||
|
||||
const handleConsentChange = ({ target }: ChangeEvent<any>) =>
|
||||
setIsConsentChecked(target.value);
|
||||
|
||||
const handleViewAssignedItems = () => navigate(viewAssignedItemsUrl);
|
||||
|
||||
const isDisbled = hasAssignedItems ? !isConsentChecked : false;
|
||||
|
||||
const shouldShowViewAssignedItemsButton =
|
||||
showViewAssignedItemsButton && hasAssignedItems;
|
||||
|
||||
return (
|
||||
<CardContent>
|
||||
<Typography>
|
||||
{intl.formatMessage(description, {
|
||||
typeName: singleItemSelectedName,
|
||||
assignedItemsCount,
|
||||
b: (...chunks) => <b>{chunks}</b>
|
||||
})}
|
||||
</Typography>
|
||||
<CardSpacer />
|
||||
{consentLabel && (
|
||||
<ControlledCheckbox
|
||||
name="delete-assigned-items-consent"
|
||||
checked={isConsentChecked}
|
||||
onChange={handleConsentChange}
|
||||
label={
|
||||
<Typography className={classes.consentLabel}>
|
||||
{intl.formatMessage(consentLabel)}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<CardSpacer />
|
||||
<div className={classes.buttonsSection}>
|
||||
{shouldShowViewAssignedItemsButton && (
|
||||
<>
|
||||
<ConfirmButton
|
||||
onClick={handleViewAssignedItems}
|
||||
transitionState="default"
|
||||
>
|
||||
{intl.formatMessage(viewAssignedItemsButtonLabel)}
|
||||
</ConfirmButton>
|
||||
<HorizontalSpacer spacing={3} />
|
||||
</>
|
||||
)}
|
||||
<DeleteButton onClick={onDelete} disabled={isDisbled} />
|
||||
</div>
|
||||
</CardContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default TypeDeleteWarningDialogContent;
|
2
src/components/TypeDeleteWarningDialog/index.tsx
Normal file
2
src/components/TypeDeleteWarningDialog/index.tsx
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./TypeDeleteWarningDialog";
|
||||
export { default } from "./TypeDeleteWarningDialog";
|
23
src/components/TypeDeleteWarningDialog/styles.ts
Normal file
23
src/components/TypeDeleteWarningDialog/styles.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { makeStyles } from "@saleor/theme";
|
||||
|
||||
export const useTypeDeleteWarningDialogStyles = makeStyles(
|
||||
theme => ({
|
||||
centerContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100%"
|
||||
},
|
||||
content: {
|
||||
width: 600
|
||||
},
|
||||
consentLabel: {
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
buttonsSection: {
|
||||
display: "flex",
|
||||
justifyContent: "flex-end"
|
||||
}
|
||||
}),
|
||||
{ name: "ProductTypeDeleteWarningDialog" }
|
||||
);
|
10
src/components/TypeDeleteWarningDialog/types.ts
Normal file
10
src/components/TypeDeleteWarningDialog/types.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { MessageDescriptor } from "react-intl";
|
||||
|
||||
export type CommonTypeDeleteWarningMessages = Record<
|
||||
"title" | "viewAssignedItemsButtonLabel",
|
||||
MessageDescriptor
|
||||
>;
|
||||
|
||||
export type TypeDeleteWarningMessages = Partial<
|
||||
Record<"description" | "consentLabel", MessageDescriptor>
|
||||
>;
|
|
@ -1,5 +1,7 @@
|
|||
import Divider from "@material-ui/core/Divider";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import CardSpacer from "@saleor/components/CardSpacer";
|
||||
import { makeStyles } from "@saleor/theme";
|
||||
import React from "react";
|
||||
|
||||
|
@ -19,16 +21,29 @@ const useStyles = makeStyles(
|
|||
interface ModalTitleProps {
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
withBorder?: boolean;
|
||||
}
|
||||
|
||||
const ModalTitle: React.FC<ModalTitleProps> = ({ title, onClose }) => {
|
||||
const ModalTitle: React.FC<ModalTitleProps> = ({
|
||||
title,
|
||||
onClose,
|
||||
withBorder = false
|
||||
}) => {
|
||||
const classes = useStyles({});
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<Typography variant="h5">{title}</Typography>
|
||||
<CloseIcon onClick={onClose} />
|
||||
</div>
|
||||
<>
|
||||
<div className={classes.container}>
|
||||
<Typography variant="h5">{title}</Typography>
|
||||
<CloseIcon onClick={onClose} />
|
||||
</div>
|
||||
{withBorder && (
|
||||
<>
|
||||
<CardSpacer />
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
export interface PageTypeDeleteDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
open: boolean;
|
||||
name: string;
|
||||
hasPages: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
const PageTypeDeleteDialog: React.FC<PageTypeDeleteDialogProps> = ({
|
||||
confirmButtonState,
|
||||
open,
|
||||
name,
|
||||
hasPages,
|
||||
onClose,
|
||||
onConfirm
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Page Type",
|
||||
description: "dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
{hasPages ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Page Type you want to delete is used by some pages. Deleting this page type will also delete those pages. Are you sure you want to delete {name}? After doing so you won’t be able to revert changes."
|
||||
description="delete page type with its pages"
|
||||
values={{
|
||||
name: <strong>{name}</strong>
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {name}? After doing so you won’t be able to revert changes."
|
||||
description="delete page type"
|
||||
values={{
|
||||
name: <strong>{name}</strong>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
PageTypeDeleteDialog.displayName = "PageTypeDeleteDialog";
|
||||
export default PageTypeDeleteDialog;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./PageTypeDeleteDialog";
|
||||
export * from "./PageTypeDeleteDialog";
|
2
src/pageTypes/hooks/usePageTypeDelete/index.ts
Normal file
2
src/pageTypes/hooks/usePageTypeDelete/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./usePageTypeDelete";
|
||||
export { default } from "./usePageTypeDelete";
|
59
src/pageTypes/hooks/usePageTypeDelete/messages.ts
Normal file
59
src/pageTypes/hooks/usePageTypeDelete/messages.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const baseMessages = defineMessages({
|
||||
title: {
|
||||
defaultMessage:
|
||||
"Delete page {selectedTypesCount,plural,one{type} other{types}}",
|
||||
description: "ProductTypeDeleteWarningDialog title"
|
||||
},
|
||||
viewAssignedItemsButtonLabel: {
|
||||
defaultMessage: "View pages",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items button label"
|
||||
}
|
||||
});
|
||||
|
||||
export const singleWithItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"You are about to delete page type <b>{typeName}</b>. It is assigned to {assignedItemsCount} {assignedItemsCount,plural,one{page} other{pages}}. Deleting this page type will also delete those pages. Are you sure you want to do this?",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items description"
|
||||
},
|
||||
consentLabel: {
|
||||
defaultMessage: "Yes, I want to delete this page type and assigned pages",
|
||||
description: "ProductTypeDeleteWarningDialog single consent label"
|
||||
}
|
||||
});
|
||||
|
||||
export const multipleWithItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"You are about to delete multiple page types. Some of them are assigned to pages. Deleting those page types will also delete those pages",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog with items multiple description"
|
||||
},
|
||||
consentLabel: {
|
||||
defaultMessage:
|
||||
"Yes, I want to delete those pages types and assigned pages",
|
||||
description: "ProductTypeDeleteWarningDialog multiple consent label"
|
||||
}
|
||||
});
|
||||
|
||||
export const singleWithoutItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"Are you sure you want to delete <b>{typeName}</b>? If you remove it you won’t be able to assign it to created pages.",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items description"
|
||||
}
|
||||
});
|
||||
|
||||
export const multipleWithoutItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"Are you sure you want to delete selected page types? If you remove them you won’t be able to assign them to created pages.",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items description"
|
||||
}
|
||||
});
|
16
src/pageTypes/hooks/usePageTypeDelete/types.ts
Normal file
16
src/pageTypes/hooks/usePageTypeDelete/types.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { TypeDeleteMessages } from "@saleor/components/TypeDeleteWarningDialog";
|
||||
import { Ids } from "@saleor/types";
|
||||
|
||||
export interface UseTypeDeleteData extends TypeDeleteMessages {
|
||||
isOpen: boolean;
|
||||
assignedItemsCount: number | undefined;
|
||||
viewAssignedItemsUrl: string;
|
||||
isLoading: boolean | undefined;
|
||||
typesToDelete: Ids;
|
||||
}
|
||||
|
||||
export interface UseTypeDeleteProps<T> {
|
||||
params: T;
|
||||
selectedTypes?: Ids;
|
||||
singleId?: string;
|
||||
}
|
64
src/pageTypes/hooks/usePageTypeDelete/usePageTypeDelete.tsx
Normal file
64
src/pageTypes/hooks/usePageTypeDelete/usePageTypeDelete.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { usePageCountQuery } from "@saleor/pages/queries";
|
||||
import { PageCountVariables } from "@saleor/pages/types/PageCount";
|
||||
import { pageListUrl } from "@saleor/pages/urls";
|
||||
import {
|
||||
PageTypeListUrlQueryParams,
|
||||
PageTypeUrlQueryParams
|
||||
} from "@saleor/pageTypes/urls";
|
||||
import React from "react";
|
||||
|
||||
import * as messages from "./messages";
|
||||
import { UseTypeDeleteData, UseTypeDeleteProps } from "./types";
|
||||
|
||||
type UsePageTypeDeleteProps<
|
||||
T = PageTypeListUrlQueryParams | PageTypeUrlQueryParams
|
||||
> = UseTypeDeleteProps<T>;
|
||||
|
||||
function usePageTypeDelete({
|
||||
singleId,
|
||||
params,
|
||||
selectedTypes
|
||||
}: UsePageTypeDeleteProps): UseTypeDeleteData {
|
||||
const pageTypes = selectedTypes || [singleId];
|
||||
|
||||
const isDeleteDialogOpen = params.action === "remove";
|
||||
|
||||
const pagesAssignedToSelectedTypesQueryVars = React.useMemo<
|
||||
PageCountVariables
|
||||
>(
|
||||
() => ({
|
||||
filter: {
|
||||
pageTypes
|
||||
}
|
||||
}),
|
||||
[pageTypes]
|
||||
);
|
||||
|
||||
const shouldSkipPageListQuery = !pageTypes.length || !isDeleteDialogOpen;
|
||||
|
||||
const {
|
||||
data: pagesAssignedToSelectedTypesData,
|
||||
loading: loadingPagesAssignedToSelectedTypes
|
||||
} = usePageCountQuery({
|
||||
variables: pagesAssignedToSelectedTypesQueryVars,
|
||||
skip: shouldSkipPageListQuery
|
||||
});
|
||||
|
||||
const selectedPagesAssignedToDeleteUrl = pageListUrl({
|
||||
pageTypes
|
||||
});
|
||||
|
||||
const assignedItemsCount =
|
||||
pagesAssignedToSelectedTypesData?.pages?.totalCount;
|
||||
|
||||
return {
|
||||
...messages,
|
||||
isOpen: isDeleteDialogOpen,
|
||||
assignedItemsCount,
|
||||
viewAssignedItemsUrl: selectedPagesAssignedToDeleteUrl,
|
||||
isLoading: loadingPagesAssignedToSelectedTypes,
|
||||
typesToDelete: pageTypes
|
||||
};
|
||||
}
|
||||
|
||||
export default usePageTypeDelete;
|
|
@ -4,6 +4,7 @@ import AssignAttributeDialog from "@saleor/components/AssignAttributeDialog";
|
|||
import AttributeUnassignDialog from "@saleor/components/AttributeUnassignDialog";
|
||||
import BulkAttributeUnassignDialog from "@saleor/components/BulkAttributeUnassignDialog";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import TypeDeleteWarningDialog from "@saleor/components/TypeDeleteWarningDialog";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
|
@ -11,7 +12,6 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
|||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
||||
import PageTypeDeleteDialog from "@saleor/pageTypes/components/PageTypeDeleteDialog";
|
||||
import {
|
||||
useAssignPageAttributeMutation,
|
||||
usePageTypeAttributeReorderMutation,
|
||||
|
@ -33,6 +33,7 @@ import PageTypeDetailsPage, {
|
|||
PageTypeForm
|
||||
} from "../components/PageTypeDetailsPage";
|
||||
import useAvailablePageAttributeSearch from "../hooks/useAvailablePageAttributeSearch";
|
||||
import usePageTypeDelete from "../hooks/usePageTypeDelete";
|
||||
import { usePageTypeDetailsQuery } from "../queries";
|
||||
import { pageTypeListUrl, pageTypeUrl, PageTypeUrlQueryParams } from "../urls";
|
||||
|
||||
|
@ -184,6 +185,11 @@ export const PageTypeDetails: React.FC<PageTypeDetailsProps> = ({
|
|||
|
||||
const loading = updatePageTypeOpts.loading || dataLoading;
|
||||
|
||||
const pageTypeDeleteData = usePageTypeDelete({
|
||||
singleId: id,
|
||||
params
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowTitle title={data?.pageType.name} />
|
||||
|
@ -245,50 +251,55 @@ export const PageTypeDetails: React.FC<PageTypeDetailsProps> = ({
|
|||
)
|
||||
}}
|
||||
/>
|
||||
<PageTypeDeleteDialog
|
||||
confirmButtonState={deletePageTypeOpts.status}
|
||||
name={getStringOrPlaceholder(data?.pageType.name)}
|
||||
hasPages={data?.pageType.hasPages}
|
||||
open={params.action === "remove"}
|
||||
onClose={() => navigate(pageTypeUrl(id))}
|
||||
onConfirm={handlePageTypeDelete}
|
||||
/>
|
||||
|
||||
{!dataLoading && (
|
||||
<AssignAttributeDialog
|
||||
attributes={result.data?.pageType.availableAttributes.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
confirmButtonState={assignAttributeOpts.status}
|
||||
errors={
|
||||
assignAttributeOpts.data?.pageAttributeAssign.errors
|
||||
? assignAttributeOpts.data.pageAttributeAssign.errors.map(err =>
|
||||
getPageErrorMessage(err, intl)
|
||||
)
|
||||
: []
|
||||
}
|
||||
loading={result.loading}
|
||||
onClose={closeModal}
|
||||
onSubmit={handleAssignAttribute}
|
||||
onFetch={search}
|
||||
onFetchMore={loadMore}
|
||||
onOpen={result.refetch}
|
||||
hasMore={
|
||||
!!result.data?.pageType.availableAttributes.pageInfo.hasNextPage
|
||||
}
|
||||
open={params.action === "assign-attribute"}
|
||||
selected={params.ids || []}
|
||||
onToggle={attributeId => {
|
||||
const ids = params.ids || [];
|
||||
navigate(
|
||||
pageTypeUrl(id, {
|
||||
...params,
|
||||
ids: ids.includes(attributeId)
|
||||
? params.ids.filter(selectedId => selectedId !== attributeId)
|
||||
: [...ids, attributeId]
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<TypeDeleteWarningDialog
|
||||
{...pageTypeDeleteData}
|
||||
typesData={[pageType]}
|
||||
typesToDelete={[id]}
|
||||
onClose={closeModal}
|
||||
onDelete={handlePageTypeDelete}
|
||||
deleteButtonState={deletePageTypeOpts.status}
|
||||
/>
|
||||
<AssignAttributeDialog
|
||||
attributes={result.data?.pageType.availableAttributes.edges.map(
|
||||
edge => edge.node
|
||||
)}
|
||||
confirmButtonState={assignAttributeOpts.status}
|
||||
errors={
|
||||
assignAttributeOpts.data?.pageAttributeAssign.errors
|
||||
? assignAttributeOpts.data.pageAttributeAssign.errors.map(err =>
|
||||
getPageErrorMessage(err, intl)
|
||||
)
|
||||
: []
|
||||
}
|
||||
loading={result.loading}
|
||||
onClose={closeModal}
|
||||
onSubmit={handleAssignAttribute}
|
||||
onFetch={search}
|
||||
onFetchMore={loadMore}
|
||||
onOpen={result.refetch}
|
||||
hasMore={
|
||||
!!result.data?.pageType.availableAttributes.pageInfo.hasNextPage
|
||||
}
|
||||
open={params.action === "assign-attribute"}
|
||||
selected={params.ids || []}
|
||||
onToggle={attributeId => {
|
||||
const ids = params.ids || [];
|
||||
navigate(
|
||||
pageTypeUrl(id, {
|
||||
...params,
|
||||
ids: ids.includes(attributeId)
|
||||
? params.ids.filter(
|
||||
selectedId => selectedId !== attributeId
|
||||
)
|
||||
: [...ids, attributeId]
|
||||
})
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<BulkAttributeUnassignDialog
|
||||
title={intl.formatMessage({
|
||||
|
|
|
@ -4,6 +4,7 @@ import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
|||
import SaveFilterTabDialog, {
|
||||
SaveFilterTabDialogFormData
|
||||
} from "@saleor/components/SaveFilterTabDialog";
|
||||
import TypeDeleteWarningDialog from "@saleor/components/TypeDeleteWarningDialog";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
import useListSettings from "@saleor/hooks/useListSettings";
|
||||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
|
@ -13,7 +14,7 @@ import usePaginator, {
|
|||
} from "@saleor/hooks/usePaginator";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
||||
import PageTypeBulkDeleteDialog from "@saleor/pageTypes/components/PageTypeBulkDeleteDialog";
|
||||
import usePageTypeDelete from "@saleor/pageTypes/hooks/usePageTypeDelete";
|
||||
import { usePageTypeBulkDeleteMutation } from "@saleor/pageTypes/mutations";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
|
@ -51,9 +52,13 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
|||
const navigate = useNavigator();
|
||||
const paginate = usePaginator();
|
||||
const notify = useNotifier();
|
||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
const {
|
||||
isSelected,
|
||||
listElements: selectedPageTypes,
|
||||
reset,
|
||||
toggle,
|
||||
toggleAll
|
||||
} = useBulkActions(params.ids);
|
||||
const intl = useIntl();
|
||||
const { settings } = useListSettings(ListViews.PAGES_LIST);
|
||||
|
||||
|
@ -155,10 +160,12 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
|||
}
|
||||
});
|
||||
|
||||
const selectedPageTypesHasPages = data?.pageTypes.edges.some(
|
||||
pageType =>
|
||||
pageType.node.hasPages && params.ids?.some(id => id === pageType.node.id)
|
||||
);
|
||||
const pageTypeDeleteData = usePageTypeDelete({
|
||||
selectedTypes: selectedPageTypes,
|
||||
params
|
||||
});
|
||||
|
||||
const pageTypesData = data?.pageTypes?.edges.map(edge => edge.node) || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -181,7 +188,7 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
|||
onRowClick={id => () => navigate(pageTypeUrl(id))}
|
||||
onSort={handleSort}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
selected={selectedPageTypes.length}
|
||||
sort={getSortParams(params)}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
|
@ -190,7 +197,7 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
|||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("remove", {
|
||||
ids: listElements
|
||||
ids: selectedPageTypes
|
||||
})
|
||||
}
|
||||
>
|
||||
|
@ -198,13 +205,14 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
|||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<PageTypeBulkDeleteDialog
|
||||
confirmButtonState={pageTypeBulkDeleteOpts.status}
|
||||
quantity={params.ids?.length}
|
||||
hasPages={selectedPageTypesHasPages}
|
||||
open={params.action === "remove"}
|
||||
<TypeDeleteWarningDialog
|
||||
{...pageTypeDeleteData}
|
||||
typesData={pageTypesData}
|
||||
typesToDelete={selectedPageTypes}
|
||||
onClose={closeModal}
|
||||
onConfirm={hanldePageTypeBulkDelete}
|
||||
onDelete={hanldePageTypeBulkDelete}
|
||||
deleteButtonState={pageTypeBulkDeleteOpts.status}
|
||||
showViewAssignedItemsButton={false}
|
||||
/>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { pageDetailsFragment, pageFragment } from "@saleor/fragments/pages";
|
|||
import makeQuery from "@saleor/hooks/makeQuery";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { PageCount, PageCountVariables } from "./types/PageCount";
|
||||
import { PageDetails, PageDetailsVariables } from "./types/PageDetails";
|
||||
import { PageList, PageListVariables } from "./types/PageList";
|
||||
|
||||
|
@ -50,3 +51,15 @@ const pageDetails = gql`
|
|||
export const usePageDetailsQuery = makeQuery<PageDetails, PageDetailsVariables>(
|
||||
pageDetails
|
||||
);
|
||||
|
||||
const pageCountQuery = gql`
|
||||
query PageCount($filter: PageFilterInput) {
|
||||
pages(filter: $filter) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const usePageCountQuery = makeQuery<PageCount, PageCountVariables>(
|
||||
pageCountQuery
|
||||
);
|
||||
|
|
23
src/pages/types/PageCount.ts
Normal file
23
src/pages/types/PageCount.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { PageFilterInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: PageCount
|
||||
// ====================================================
|
||||
|
||||
export interface PageCount_pages {
|
||||
__typename: "PageCountableConnection";
|
||||
totalCount: number | null;
|
||||
}
|
||||
|
||||
export interface PageCount {
|
||||
pages: PageCount_pages | null;
|
||||
}
|
||||
|
||||
export interface PageCountVariables {
|
||||
filter?: PageFilterInput | null;
|
||||
}
|
|
@ -1,7 +1,14 @@
|
|||
import { stringify as stringifyQs } from "qs";
|
||||
import urlJoin from "url-join";
|
||||
|
||||
import { BulkAction, Dialog, Pagination, SingleAction, Sort } from "../types";
|
||||
import {
|
||||
BulkAction,
|
||||
Dialog,
|
||||
FiltersWithMultipleValues,
|
||||
Pagination,
|
||||
SingleAction,
|
||||
Sort
|
||||
} from "../types";
|
||||
|
||||
export const pagesSection = "/pages/";
|
||||
|
||||
|
@ -12,8 +19,17 @@ export enum PageListUrlSortField {
|
|||
slug = "slug",
|
||||
visible = "visible"
|
||||
}
|
||||
|
||||
export enum PageListUrlFiltersWithMultipleValues {
|
||||
pageTypes = "pageTypes"
|
||||
}
|
||||
|
||||
export type PageListUrlFilters = FiltersWithMultipleValues<
|
||||
PageListUrlFiltersWithMultipleValues
|
||||
>;
|
||||
export type PageListUrlSort = Sort<PageListUrlSortField>;
|
||||
export type PageListUrlQueryParams = BulkAction &
|
||||
PageListUrlFilters &
|
||||
Dialog<PageListUrlDialog> &
|
||||
PageListUrlSort &
|
||||
Pagination;
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
export interface ProductTypeDeleteDialogProps {
|
||||
confirmButtonState: ConfirmButtonTransitionState;
|
||||
open: boolean;
|
||||
name: string;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
const ProductTypeDeleteDialog: React.FC<ProductTypeDeleteDialogProps> = ({
|
||||
confirmButtonState,
|
||||
open,
|
||||
name,
|
||||
onClose,
|
||||
onConfirm
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ActionDialog
|
||||
confirmButtonState={confirmButtonState}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onConfirm={onConfirm}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Product Type",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {name}?"
|
||||
description="delete product type"
|
||||
values={{
|
||||
name: <strong>{name}</strong>
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
);
|
||||
};
|
||||
ProductTypeDeleteDialog.displayName = "ProductTypeDeleteDialog";
|
||||
export default ProductTypeDeleteDialog;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./ProductTypeDeleteDialog";
|
||||
export * from "./ProductTypeDeleteDialog";
|
2
src/productTypes/hooks/useProductTypeDelete/index.ts
Normal file
2
src/productTypes/hooks/useProductTypeDelete/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from "./useProductTypeDelete";
|
||||
export { default } from "./useProductTypeDelete";
|
60
src/productTypes/hooks/useProductTypeDelete/messages.ts
Normal file
60
src/productTypes/hooks/useProductTypeDelete/messages.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { defineMessages } from "react-intl";
|
||||
|
||||
export const baseMessages = defineMessages({
|
||||
title: {
|
||||
defaultMessage:
|
||||
"Delete product {selectedTypesCount,plural,one{type} other{types}}",
|
||||
description: "ProductTypeDeleteWarningDialog title"
|
||||
},
|
||||
viewAssignedItemsButtonLabel: {
|
||||
defaultMessage: "View products",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items button label"
|
||||
}
|
||||
});
|
||||
|
||||
export const singleWithItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"You are about to delete product type <b>{typeName}</b>. It is assigned to {assignedItemsCount} {assignedItemsCount,plural,one{product} other{products}}. Deleting this product type will also delete those products. Are you sure you want to do this?",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items description"
|
||||
},
|
||||
consentLabel: {
|
||||
defaultMessage:
|
||||
"Yes, I want to delete this product type and assigned products",
|
||||
description: "ProductTypeDeleteWarningDialog single consent label"
|
||||
}
|
||||
});
|
||||
|
||||
export const multipleWithItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"You are about to delete multiple product types. Some of them are assigned to products. Deleting those product types will also delete those products",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog with items multiple description"
|
||||
},
|
||||
consentLabel: {
|
||||
defaultMessage:
|
||||
"Yes, I want to delete those products types and assigned products",
|
||||
description: "ProductTypeDeleteWarningDialog multiple consent label"
|
||||
}
|
||||
});
|
||||
|
||||
export const singleWithoutItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"Are you sure you want to delete <b>{typeName}</b>? If you remove it you won’t be able to assign it to created products.",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items description"
|
||||
}
|
||||
});
|
||||
|
||||
export const multipleWithoutItemsMessages = defineMessages({
|
||||
description: {
|
||||
defaultMessage:
|
||||
"Are you sure you want to delete selected product types? If you remove them you won’t be able to assign them to created products.",
|
||||
description:
|
||||
"ProductTypeDeleteWarningDialog single assigned items description"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
import {
|
||||
UseTypeDeleteData,
|
||||
UseTypeDeleteProps
|
||||
} from "@saleor/pageTypes/hooks/usePageTypeDelete/types";
|
||||
import { useProductCountQuery } from "@saleor/products/queries";
|
||||
import { ProductCountVariables } from "@saleor/products/types/ProductCount";
|
||||
import { productListUrl } from "@saleor/products/urls";
|
||||
import {
|
||||
ProductTypeListUrlQueryParams,
|
||||
ProductTypeUrlQueryParams
|
||||
} from "@saleor/productTypes/urls";
|
||||
import React from "react";
|
||||
|
||||
import * as messages from "./messages";
|
||||
|
||||
type UseProductTypeDeleteProps<
|
||||
T = ProductTypeListUrlQueryParams | ProductTypeUrlQueryParams
|
||||
> = UseTypeDeleteProps<T>;
|
||||
|
||||
function useProductTypeDelete({
|
||||
params,
|
||||
singleId,
|
||||
selectedTypes
|
||||
}: UseProductTypeDeleteProps): UseTypeDeleteData {
|
||||
const productTypes = selectedTypes || [singleId];
|
||||
|
||||
const isDeleteDialogOpen = params.action === "remove";
|
||||
|
||||
const productsAssignedToSelectedTypesQueryVars = React.useMemo<
|
||||
ProductCountVariables
|
||||
>(
|
||||
() => ({
|
||||
filter: {
|
||||
productTypes
|
||||
}
|
||||
}),
|
||||
[productTypes]
|
||||
);
|
||||
|
||||
const shouldSkipProductListQuery =
|
||||
!productTypes.length || !isDeleteDialogOpen;
|
||||
|
||||
const {
|
||||
data: productsAssignedToSelectedTypesData,
|
||||
loading: loadingProductsAssignedToSelectedTypes
|
||||
} = useProductCountQuery({
|
||||
variables: productsAssignedToSelectedTypesQueryVars,
|
||||
skip: shouldSkipProductListQuery
|
||||
});
|
||||
|
||||
const selectedProductsAssignedToDeleteUrl = productListUrl({
|
||||
productTypes
|
||||
});
|
||||
|
||||
const assignedItemsCount =
|
||||
productsAssignedToSelectedTypesData?.products?.totalCount;
|
||||
|
||||
return {
|
||||
...messages,
|
||||
isOpen: isDeleteDialogOpen,
|
||||
assignedItemsCount,
|
||||
viewAssignedItemsUrl: selectedProductsAssignedToDeleteUrl,
|
||||
isLoading: loadingProductsAssignedToSelectedTypes,
|
||||
typesToDelete: productTypes
|
||||
};
|
||||
}
|
||||
|
||||
export default useProductTypeDelete;
|
|
@ -1,7 +1,5 @@
|
|||
import DialogContentText from "@material-ui/core/DialogContentText";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import ActionDialog from "@saleor/components/ActionDialog";
|
||||
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||
import SaveFilterTabDialog, {
|
||||
SaveFilterTabDialogFormData
|
||||
|
@ -14,14 +12,16 @@ import usePaginator, {
|
|||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import useProductTypeDelete from "@saleor/productTypes/hooks/useProductTypeDelete";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||
import { getSortParams } from "@saleor/utils/sort";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import TypeDeleteWarningDialog from "../../../components/TypeDeleteWarningDialog/TypeDeleteWarningDialog";
|
||||
import { configurationMenuUrl } from "../../../configuration";
|
||||
import { maybe } from "../../../misc";
|
||||
import ProductTypeListPage from "../../components/ProductTypeListPage";
|
||||
|
@ -55,9 +55,14 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
|||
const navigate = useNavigator();
|
||||
const notify = useNotifier();
|
||||
const paginate = usePaginator();
|
||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
||||
params.ids
|
||||
);
|
||||
const {
|
||||
isSelected,
|
||||
listElements: selectedProductTypes,
|
||||
reset,
|
||||
toggle,
|
||||
toggleAll
|
||||
} = useBulkActions(params.ids);
|
||||
|
||||
const { settings } = useListSettings(ListViews.PRODUCT_LIST);
|
||||
const intl = useIntl();
|
||||
|
||||
|
@ -148,6 +153,14 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
|||
|
||||
const handleSort = createSortHandler(navigate, productTypeListUrl, params);
|
||||
|
||||
const productTypeDeleteData = useProductTypeDelete({
|
||||
selectedTypes: selectedProductTypes,
|
||||
params
|
||||
});
|
||||
|
||||
const productTypesData =
|
||||
data?.productTypes?.edges.map(edge => edge.node) || [];
|
||||
|
||||
return (
|
||||
<TypedProductTypeBulkDeleteMutation
|
||||
onCompleted={handleProductTypeBulkDelete}
|
||||
|
@ -174,9 +187,7 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
|||
onTabSave={() => openModal("save-search")}
|
||||
tabs={tabs.map(tab => tab.name)}
|
||||
disabled={loading}
|
||||
productTypes={maybe(() =>
|
||||
data.productTypes.edges.map(edge => edge.node)
|
||||
)}
|
||||
productTypes={productTypesData}
|
||||
pageInfo={pageInfo}
|
||||
onAdd={() => navigate(productTypeAddUrl)}
|
||||
onBack={() => navigate(configurationMenuUrl)}
|
||||
|
@ -185,7 +196,7 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
|||
onRowClick={id => () => navigate(productTypeUrl(id))}
|
||||
onSort={handleSort}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
selected={selectedProductTypes.length}
|
||||
sort={getSortParams(params)}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
|
@ -194,7 +205,7 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
|||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("remove", {
|
||||
ids: listElements
|
||||
ids: selectedProductTypes
|
||||
})
|
||||
}
|
||||
>
|
||||
|
@ -202,30 +213,14 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
|||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<ActionDialog
|
||||
confirmButtonState={productTypeBulkDeleteOpts.status}
|
||||
<TypeDeleteWarningDialog
|
||||
{...productTypeDeleteData}
|
||||
typesData={productTypesData}
|
||||
typesToDelete={selectedProductTypes}
|
||||
onClose={closeModal}
|
||||
onConfirm={onProductTypeBulkDelete}
|
||||
open={params.action === "remove"}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Product Types",
|
||||
description: "dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="{counter,plural,one{Are you sure you want to delete this product type?} other{Are you sure you want to delete {displayQuantity} product types?}}"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>{maybe(() => params.ids.length)}</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
onDelete={onProductTypeBulkDelete}
|
||||
deleteButtonState={productTypeBulkDeleteOpts.status}
|
||||
/>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
|
|
|
@ -4,6 +4,7 @@ import AssignAttributeDialog from "@saleor/components/AssignAttributeDialog";
|
|||
import AttributeUnassignDialog from "@saleor/components/AttributeUnassignDialog";
|
||||
import BulkAttributeUnassignDialog from "@saleor/components/BulkAttributeUnassignDialog";
|
||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||
import TypeDeleteWarningDialog from "@saleor/components/TypeDeleteWarningDialog";
|
||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||
|
@ -11,6 +12,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
|||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getStringOrPlaceholder, maybe } from "@saleor/misc";
|
||||
import useProductTypeDelete from "@saleor/productTypes/hooks/useProductTypeDelete";
|
||||
import { useProductTypeUpdateMutation } from "@saleor/productTypes/mutations";
|
||||
import { ReorderEvent } from "@saleor/types";
|
||||
import { ProductAttributeType } from "@saleor/types/globalTypes";
|
||||
|
@ -22,7 +24,6 @@ import {
|
|||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ProductTypeDeleteDialog from "../../components/ProductTypeDeleteDialog";
|
||||
import ProductTypeDetailsPage, {
|
||||
ProductTypeForm
|
||||
} from "../../components/ProductTypeDetailsPage";
|
||||
|
@ -117,6 +118,11 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
return result.data.productTypeUpdate.errors;
|
||||
};
|
||||
|
||||
const productTypeDeleteData = useProductTypeDelete({
|
||||
singleId: id,
|
||||
params
|
||||
});
|
||||
|
||||
return (
|
||||
<TypedProductTypeDetailsQuery displayLoader variables={{ id }}>
|
||||
{({ data, loading: dataLoading }) => {
|
||||
|
@ -335,62 +341,67 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
|||
)
|
||||
}}
|
||||
/>
|
||||
{!dataLoading &&
|
||||
Object.keys(ProductAttributeType).map(key => (
|
||||
<AssignAttributeDialog
|
||||
attributes={maybe(() =>
|
||||
result.data.productType.availableAttributes.edges.map(
|
||||
edge => edge.node
|
||||
)
|
||||
)}
|
||||
confirmButtonState={assignAttribute.opts.status}
|
||||
errors={maybe(
|
||||
() =>
|
||||
assignAttribute.opts.data.productAttributeAssign.errors.map(
|
||||
err => err.message
|
||||
),
|
||||
[]
|
||||
)}
|
||||
loading={result.loading}
|
||||
{!dataLoading && (
|
||||
<>
|
||||
{Object.keys(ProductAttributeType).map(key => (
|
||||
<AssignAttributeDialog
|
||||
attributes={maybe(() =>
|
||||
result.data.productType.availableAttributes.edges.map(
|
||||
edge => edge.node
|
||||
)
|
||||
)}
|
||||
confirmButtonState={assignAttribute.opts.status}
|
||||
errors={maybe(
|
||||
() =>
|
||||
assignAttribute.opts.data.productAttributeAssign.errors.map(
|
||||
err => err.message
|
||||
),
|
||||
[]
|
||||
)}
|
||||
loading={result.loading}
|
||||
onClose={closeModal}
|
||||
onSubmit={handleAssignAttribute}
|
||||
onFetch={search}
|
||||
onFetchMore={loadMore}
|
||||
onOpen={result.refetch}
|
||||
hasMore={maybe(
|
||||
() =>
|
||||
result.data.productType.availableAttributes
|
||||
.pageInfo.hasNextPage,
|
||||
false
|
||||
)}
|
||||
open={
|
||||
params.action === "assign-attribute" &&
|
||||
params.type === ProductAttributeType[key]
|
||||
}
|
||||
selected={maybe(() => params.ids, [])}
|
||||
onToggle={attributeId => {
|
||||
const ids = maybe(() => params.ids, []);
|
||||
navigate(
|
||||
productTypeUrl(id, {
|
||||
...params,
|
||||
ids: ids.includes(attributeId)
|
||||
? params.ids.filter(
|
||||
selectedId => selectedId !== attributeId
|
||||
)
|
||||
: [...ids, attributeId]
|
||||
})
|
||||
);
|
||||
}}
|
||||
key={key}
|
||||
/>
|
||||
))}
|
||||
<TypeDeleteWarningDialog
|
||||
{...productTypeDeleteData}
|
||||
typesData={[productType]}
|
||||
typesToDelete={[id]}
|
||||
onClose={closeModal}
|
||||
onSubmit={handleAssignAttribute}
|
||||
onFetch={search}
|
||||
onFetchMore={loadMore}
|
||||
onOpen={result.refetch}
|
||||
hasMore={maybe(
|
||||
() =>
|
||||
result.data.productType.availableAttributes.pageInfo
|
||||
.hasNextPage,
|
||||
false
|
||||
)}
|
||||
open={
|
||||
params.action === "assign-attribute" &&
|
||||
params.type === ProductAttributeType[key]
|
||||
}
|
||||
selected={maybe(() => params.ids, [])}
|
||||
onToggle={attributeId => {
|
||||
const ids = maybe(() => params.ids, []);
|
||||
navigate(
|
||||
productTypeUrl(id, {
|
||||
...params,
|
||||
ids: ids.includes(attributeId)
|
||||
? params.ids.filter(
|
||||
selectedId => selectedId !== attributeId
|
||||
)
|
||||
: [...ids, attributeId]
|
||||
})
|
||||
);
|
||||
}}
|
||||
key={key}
|
||||
onDelete={handleProductTypeDelete}
|
||||
deleteButtonState={deleteProductType.opts.status}
|
||||
/>
|
||||
))}
|
||||
<ProductTypeDeleteDialog
|
||||
confirmButtonState={deleteProductType.opts.status}
|
||||
name={maybe(() => data.productType.name, "...")}
|
||||
open={params.action === "remove"}
|
||||
onClose={() => navigate(productTypeUrl(id))}
|
||||
onConfirm={handleProductTypeDelete}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<BulkAttributeUnassignDialog
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unassign Attribute from Product Type",
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
} from "@saleor/products/types/ProductMediaById";
|
||||
import gql from "graphql-tag";
|
||||
|
||||
import { CountAllProducts } from "./types/CountAllProducts";
|
||||
import {
|
||||
CreateMultipleVariantsData,
|
||||
CreateMultipleVariantsDataVariables
|
||||
|
@ -38,6 +37,7 @@ import {
|
|||
InitialProductFilterProductTypes,
|
||||
InitialProductFilterProductTypesVariables
|
||||
} from "./types/InitialProductFilterProductTypes";
|
||||
import { ProductCount, ProductCountVariables } from "./types/ProductCount";
|
||||
import {
|
||||
ProductDetails,
|
||||
ProductDetailsVariables
|
||||
|
@ -176,16 +176,18 @@ export const useProductListQuery = makeQuery<ProductList, ProductListVariables>(
|
|||
productListQuery
|
||||
);
|
||||
|
||||
const countAllProductsQuery = gql`
|
||||
query CountAllProducts {
|
||||
products {
|
||||
const productCountQuery = gql`
|
||||
query ProductCount($filter: ProductFilterInput) {
|
||||
products(filter: $filter) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const useCountAllProducts = makeQuery<CountAllProducts, null>(
|
||||
countAllProductsQuery
|
||||
);
|
||||
|
||||
export const useProductCountQuery = makeQuery<
|
||||
ProductCount,
|
||||
ProductCountVariables
|
||||
>(productCountQuery);
|
||||
|
||||
const productDetailsQuery = gql`
|
||||
${productFragmentDetails}
|
||||
|
|
|
@ -3,15 +3,21 @@
|
|||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { ProductFilterInput } from "./../../types/globalTypes";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: CountAllProducts
|
||||
// GraphQL query operation: ProductCount
|
||||
// ====================================================
|
||||
|
||||
export interface CountAllProducts_products {
|
||||
export interface ProductCount_products {
|
||||
__typename: "ProductCountableConnection";
|
||||
totalCount: number | null;
|
||||
}
|
||||
|
||||
export interface CountAllProducts {
|
||||
products: CountAllProducts_products | null;
|
||||
export interface ProductCount {
|
||||
products: ProductCount_products | null;
|
||||
}
|
||||
|
||||
export interface ProductCountVariables {
|
||||
filter?: ProductFilterInput | null;
|
||||
}
|
|
@ -32,11 +32,11 @@ import {
|
|||
} from "@saleor/products/components/ProductListPage/utils";
|
||||
import {
|
||||
useAvailableInGridAttributesQuery,
|
||||
useCountAllProducts,
|
||||
useInitialProductFilterAttributesQuery,
|
||||
useInitialProductFilterCategoriesQuery,
|
||||
useInitialProductFilterCollectionsQuery,
|
||||
useInitialProductFilterProductTypesQuery,
|
||||
useProductCountQuery,
|
||||
useProductListQuery
|
||||
} from "@saleor/products/queries";
|
||||
import { ProductListVariables } from "@saleor/products/types/ProductList";
|
||||
|
@ -186,7 +186,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
: 0
|
||||
: parseInt(params.activeTab, 0);
|
||||
|
||||
const countAllProducts = useCountAllProducts({
|
||||
const countAllProducts = useProductCountQuery({
|
||||
skip: params.action !== "export"
|
||||
});
|
||||
|
||||
|
|
|
@ -130,7 +130,6 @@ function loadStories() {
|
|||
|
||||
// Product types
|
||||
require("./stories/productTypes/ProductTypeCreatePage");
|
||||
require("./stories/productTypes/ProductTypeDeleteDialog");
|
||||
require("./stories/productTypes/ProductTypeDetailsPage");
|
||||
require("./stories/productTypes/ProductTypeListPage");
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import ProductTypeDeleteDialog, {
|
||||
ProductTypeDeleteDialogProps
|
||||
} from "../../../productTypes/components/ProductTypeDeleteDialog";
|
||||
import Decorator from "../../Decorator";
|
||||
|
||||
const props: ProductTypeDeleteDialogProps = {
|
||||
confirmButtonState: "default",
|
||||
name: "Shoes",
|
||||
onClose: () => undefined,
|
||||
onConfirm: () => undefined,
|
||||
open: true
|
||||
};
|
||||
|
||||
storiesOf("Product types / ProductTypeDeleteDialog", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ProductTypeDeleteDialog {...props} />);
|
|
@ -203,3 +203,5 @@ export interface AutocompleteFilterOpts
|
|||
choices: MultiAutocompleteChoiceType[];
|
||||
displayValues: MultiAutocompleteChoiceType[];
|
||||
}
|
||||
|
||||
export type Ids = string[];
|
||||
|
|
|
@ -1528,6 +1528,12 @@ export interface PageCreateInput {
|
|||
pageType: string;
|
||||
}
|
||||
|
||||
export interface PageFilterInput {
|
||||
pageTypes?: string[] | null;
|
||||
search?: string | null;
|
||||
metadata?: (MetadataInput | null)[] | null;
|
||||
}
|
||||
|
||||
export interface PageInput {
|
||||
slug?: string | null;
|
||||
title?: string | null;
|
||||
|
|
Loading…
Reference in a new issue