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 {
|
input PageFilterInput {
|
||||||
|
pageTypes: [ID!]
|
||||||
search: String
|
search: String
|
||||||
metadata: [MetadataInput]
|
metadata: [MetadataInput]
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export const ChannelsAvailabilityWrapper: React.FC<ChannelsAvailabilityWrapperPr
|
||||||
toolbar={
|
toolbar={
|
||||||
<RequirePermissions
|
<RequirePermissions
|
||||||
userPermissions={user?.userPermissions || []}
|
userPermissions={user?.userPermissions || []}
|
||||||
requiredPermissions={[PermissionEnum.MANAGE_CHANNELS]}
|
requiredPermissions={[PermissionEnum.MANAGE_PRODUCTS]}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
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 Typography from "@material-ui/core/Typography";
|
||||||
import CloseIcon from "@material-ui/icons/Close";
|
import CloseIcon from "@material-ui/icons/Close";
|
||||||
|
import CardSpacer from "@saleor/components/CardSpacer";
|
||||||
import { makeStyles } from "@saleor/theme";
|
import { makeStyles } from "@saleor/theme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
@ -19,16 +21,29 @@ const useStyles = makeStyles(
|
||||||
interface ModalTitleProps {
|
interface ModalTitleProps {
|
||||||
title: string;
|
title: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
withBorder?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModalTitle: React.FC<ModalTitleProps> = ({ title, onClose }) => {
|
const ModalTitle: React.FC<ModalTitleProps> = ({
|
||||||
|
title,
|
||||||
|
onClose,
|
||||||
|
withBorder = false
|
||||||
|
}) => {
|
||||||
const classes = useStyles({});
|
const classes = useStyles({});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.container}>
|
<>
|
||||||
<Typography variant="h5">{title}</Typography>
|
<div className={classes.container}>
|
||||||
<CloseIcon onClick={onClose} />
|
<Typography variant="h5">{title}</Typography>
|
||||||
</div>
|
<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 AttributeUnassignDialog from "@saleor/components/AttributeUnassignDialog";
|
||||||
import BulkAttributeUnassignDialog from "@saleor/components/BulkAttributeUnassignDialog";
|
import BulkAttributeUnassignDialog from "@saleor/components/BulkAttributeUnassignDialog";
|
||||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||||
|
import TypeDeleteWarningDialog from "@saleor/components/TypeDeleteWarningDialog";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||||
|
@ -11,7 +12,6 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
import { getStringOrPlaceholder } from "@saleor/misc";
|
||||||
import PageTypeDeleteDialog from "@saleor/pageTypes/components/PageTypeDeleteDialog";
|
|
||||||
import {
|
import {
|
||||||
useAssignPageAttributeMutation,
|
useAssignPageAttributeMutation,
|
||||||
usePageTypeAttributeReorderMutation,
|
usePageTypeAttributeReorderMutation,
|
||||||
|
@ -33,6 +33,7 @@ import PageTypeDetailsPage, {
|
||||||
PageTypeForm
|
PageTypeForm
|
||||||
} from "../components/PageTypeDetailsPage";
|
} from "../components/PageTypeDetailsPage";
|
||||||
import useAvailablePageAttributeSearch from "../hooks/useAvailablePageAttributeSearch";
|
import useAvailablePageAttributeSearch from "../hooks/useAvailablePageAttributeSearch";
|
||||||
|
import usePageTypeDelete from "../hooks/usePageTypeDelete";
|
||||||
import { usePageTypeDetailsQuery } from "../queries";
|
import { usePageTypeDetailsQuery } from "../queries";
|
||||||
import { pageTypeListUrl, pageTypeUrl, PageTypeUrlQueryParams } from "../urls";
|
import { pageTypeListUrl, pageTypeUrl, PageTypeUrlQueryParams } from "../urls";
|
||||||
|
|
||||||
|
@ -184,6 +185,11 @@ export const PageTypeDetails: React.FC<PageTypeDetailsProps> = ({
|
||||||
|
|
||||||
const loading = updatePageTypeOpts.loading || dataLoading;
|
const loading = updatePageTypeOpts.loading || dataLoading;
|
||||||
|
|
||||||
|
const pageTypeDeleteData = usePageTypeDelete({
|
||||||
|
singleId: id,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<WindowTitle title={data?.pageType.name} />
|
<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 && (
|
{!dataLoading && (
|
||||||
<AssignAttributeDialog
|
<>
|
||||||
attributes={result.data?.pageType.availableAttributes.edges.map(
|
<TypeDeleteWarningDialog
|
||||||
edge => edge.node
|
{...pageTypeDeleteData}
|
||||||
)}
|
typesData={[pageType]}
|
||||||
confirmButtonState={assignAttributeOpts.status}
|
typesToDelete={[id]}
|
||||||
errors={
|
onClose={closeModal}
|
||||||
assignAttributeOpts.data?.pageAttributeAssign.errors
|
onDelete={handlePageTypeDelete}
|
||||||
? assignAttributeOpts.data.pageAttributeAssign.errors.map(err =>
|
deleteButtonState={deletePageTypeOpts.status}
|
||||||
getPageErrorMessage(err, intl)
|
/>
|
||||||
)
|
<AssignAttributeDialog
|
||||||
: []
|
attributes={result.data?.pageType.availableAttributes.edges.map(
|
||||||
}
|
edge => edge.node
|
||||||
loading={result.loading}
|
)}
|
||||||
onClose={closeModal}
|
confirmButtonState={assignAttributeOpts.status}
|
||||||
onSubmit={handleAssignAttribute}
|
errors={
|
||||||
onFetch={search}
|
assignAttributeOpts.data?.pageAttributeAssign.errors
|
||||||
onFetchMore={loadMore}
|
? assignAttributeOpts.data.pageAttributeAssign.errors.map(err =>
|
||||||
onOpen={result.refetch}
|
getPageErrorMessage(err, intl)
|
||||||
hasMore={
|
)
|
||||||
!!result.data?.pageType.availableAttributes.pageInfo.hasNextPage
|
: []
|
||||||
}
|
}
|
||||||
open={params.action === "assign-attribute"}
|
loading={result.loading}
|
||||||
selected={params.ids || []}
|
onClose={closeModal}
|
||||||
onToggle={attributeId => {
|
onSubmit={handleAssignAttribute}
|
||||||
const ids = params.ids || [];
|
onFetch={search}
|
||||||
navigate(
|
onFetchMore={loadMore}
|
||||||
pageTypeUrl(id, {
|
onOpen={result.refetch}
|
||||||
...params,
|
hasMore={
|
||||||
ids: ids.includes(attributeId)
|
!!result.data?.pageType.availableAttributes.pageInfo.hasNextPage
|
||||||
? params.ids.filter(selectedId => selectedId !== attributeId)
|
}
|
||||||
: [...ids, attributeId]
|
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
|
<BulkAttributeUnassignDialog
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
|
|
|
@ -4,6 +4,7 @@ import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||||
import SaveFilterTabDialog, {
|
import SaveFilterTabDialog, {
|
||||||
SaveFilterTabDialogFormData
|
SaveFilterTabDialogFormData
|
||||||
} from "@saleor/components/SaveFilterTabDialog";
|
} from "@saleor/components/SaveFilterTabDialog";
|
||||||
|
import TypeDeleteWarningDialog from "@saleor/components/TypeDeleteWarningDialog";
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||||
import useListSettings from "@saleor/hooks/useListSettings";
|
import useListSettings from "@saleor/hooks/useListSettings";
|
||||||
import useNavigator from "@saleor/hooks/useNavigator";
|
import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
|
@ -13,7 +14,7 @@ import usePaginator, {
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getStringOrPlaceholder } from "@saleor/misc";
|
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 { usePageTypeBulkDeleteMutation } from "@saleor/pageTypes/mutations";
|
||||||
import { ListViews } from "@saleor/types";
|
import { ListViews } from "@saleor/types";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
|
@ -51,9 +52,13 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const paginate = usePaginator();
|
const paginate = usePaginator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
const {
|
||||||
params.ids
|
isSelected,
|
||||||
);
|
listElements: selectedPageTypes,
|
||||||
|
reset,
|
||||||
|
toggle,
|
||||||
|
toggleAll
|
||||||
|
} = useBulkActions(params.ids);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { settings } = useListSettings(ListViews.PAGES_LIST);
|
const { settings } = useListSettings(ListViews.PAGES_LIST);
|
||||||
|
|
||||||
|
@ -155,10 +160,12 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedPageTypesHasPages = data?.pageTypes.edges.some(
|
const pageTypeDeleteData = usePageTypeDelete({
|
||||||
pageType =>
|
selectedTypes: selectedPageTypes,
|
||||||
pageType.node.hasPages && params.ids?.some(id => id === pageType.node.id)
|
params
|
||||||
);
|
});
|
||||||
|
|
||||||
|
const pageTypesData = data?.pageTypes?.edges.map(edge => edge.node) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -181,7 +188,7 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
||||||
onRowClick={id => () => navigate(pageTypeUrl(id))}
|
onRowClick={id => () => navigate(pageTypeUrl(id))}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
isChecked={isSelected}
|
isChecked={isSelected}
|
||||||
selected={listElements.length}
|
selected={selectedPageTypes.length}
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
toggleAll={toggleAll}
|
toggleAll={toggleAll}
|
||||||
|
@ -190,7 +197,7 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openModal("remove", {
|
openModal("remove", {
|
||||||
ids: listElements
|
ids: selectedPageTypes
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -198,13 +205,14 @@ export const PageTypeList: React.FC<PageTypeListProps> = ({ params }) => {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<PageTypeBulkDeleteDialog
|
<TypeDeleteWarningDialog
|
||||||
confirmButtonState={pageTypeBulkDeleteOpts.status}
|
{...pageTypeDeleteData}
|
||||||
quantity={params.ids?.length}
|
typesData={pageTypesData}
|
||||||
hasPages={selectedPageTypesHasPages}
|
typesToDelete={selectedPageTypes}
|
||||||
open={params.action === "remove"}
|
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onConfirm={hanldePageTypeBulkDelete}
|
onDelete={hanldePageTypeBulkDelete}
|
||||||
|
deleteButtonState={pageTypeBulkDeleteOpts.status}
|
||||||
|
showViewAssignedItemsButton={false}
|
||||||
/>
|
/>
|
||||||
<SaveFilterTabDialog
|
<SaveFilterTabDialog
|
||||||
open={params.action === "save-search"}
|
open={params.action === "save-search"}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { pageDetailsFragment, pageFragment } from "@saleor/fragments/pages";
|
||||||
import makeQuery from "@saleor/hooks/makeQuery";
|
import makeQuery from "@saleor/hooks/makeQuery";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
|
import { PageCount, PageCountVariables } from "./types/PageCount";
|
||||||
import { PageDetails, PageDetailsVariables } from "./types/PageDetails";
|
import { PageDetails, PageDetailsVariables } from "./types/PageDetails";
|
||||||
import { PageList, PageListVariables } from "./types/PageList";
|
import { PageList, PageListVariables } from "./types/PageList";
|
||||||
|
|
||||||
|
@ -50,3 +51,15 @@ const pageDetails = gql`
|
||||||
export const usePageDetailsQuery = makeQuery<PageDetails, PageDetailsVariables>(
|
export const usePageDetailsQuery = makeQuery<PageDetails, PageDetailsVariables>(
|
||||||
pageDetails
|
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 { stringify as stringifyQs } from "qs";
|
||||||
import urlJoin from "url-join";
|
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/";
|
export const pagesSection = "/pages/";
|
||||||
|
|
||||||
|
@ -12,8 +19,17 @@ export enum PageListUrlSortField {
|
||||||
slug = "slug",
|
slug = "slug",
|
||||||
visible = "visible"
|
visible = "visible"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PageListUrlFiltersWithMultipleValues {
|
||||||
|
pageTypes = "pageTypes"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PageListUrlFilters = FiltersWithMultipleValues<
|
||||||
|
PageListUrlFiltersWithMultipleValues
|
||||||
|
>;
|
||||||
export type PageListUrlSort = Sort<PageListUrlSortField>;
|
export type PageListUrlSort = Sort<PageListUrlSortField>;
|
||||||
export type PageListUrlQueryParams = BulkAction &
|
export type PageListUrlQueryParams = BulkAction &
|
||||||
|
PageListUrlFilters &
|
||||||
Dialog<PageListUrlDialog> &
|
Dialog<PageListUrlDialog> &
|
||||||
PageListUrlSort &
|
PageListUrlSort &
|
||||||
Pagination;
|
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 IconButton from "@material-ui/core/IconButton";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
import ActionDialog from "@saleor/components/ActionDialog";
|
|
||||||
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
|
||||||
import SaveFilterTabDialog, {
|
import SaveFilterTabDialog, {
|
||||||
SaveFilterTabDialogFormData
|
SaveFilterTabDialogFormData
|
||||||
|
@ -14,14 +12,16 @@ import usePaginator, {
|
||||||
createPaginationState
|
createPaginationState
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
|
import useProductTypeDelete from "@saleor/productTypes/hooks/useProductTypeDelete";
|
||||||
import { ListViews } from "@saleor/types";
|
import { ListViews } from "@saleor/types";
|
||||||
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
|
||||||
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
||||||
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
import createSortHandler from "@saleor/utils/handlers/sortHandler";
|
||||||
import { getSortParams } from "@saleor/utils/sort";
|
import { getSortParams } from "@saleor/utils/sort";
|
||||||
import React from "react";
|
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 { configurationMenuUrl } from "../../../configuration";
|
||||||
import { maybe } from "../../../misc";
|
import { maybe } from "../../../misc";
|
||||||
import ProductTypeListPage from "../../components/ProductTypeListPage";
|
import ProductTypeListPage from "../../components/ProductTypeListPage";
|
||||||
|
@ -55,9 +55,14 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
||||||
const navigate = useNavigator();
|
const navigate = useNavigator();
|
||||||
const notify = useNotifier();
|
const notify = useNotifier();
|
||||||
const paginate = usePaginator();
|
const paginate = usePaginator();
|
||||||
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
|
const {
|
||||||
params.ids
|
isSelected,
|
||||||
);
|
listElements: selectedProductTypes,
|
||||||
|
reset,
|
||||||
|
toggle,
|
||||||
|
toggleAll
|
||||||
|
} = useBulkActions(params.ids);
|
||||||
|
|
||||||
const { settings } = useListSettings(ListViews.PRODUCT_LIST);
|
const { settings } = useListSettings(ListViews.PRODUCT_LIST);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
@ -148,6 +153,14 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
||||||
|
|
||||||
const handleSort = createSortHandler(navigate, productTypeListUrl, params);
|
const handleSort = createSortHandler(navigate, productTypeListUrl, params);
|
||||||
|
|
||||||
|
const productTypeDeleteData = useProductTypeDelete({
|
||||||
|
selectedTypes: selectedProductTypes,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
|
||||||
|
const productTypesData =
|
||||||
|
data?.productTypes?.edges.map(edge => edge.node) || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductTypeBulkDeleteMutation
|
<TypedProductTypeBulkDeleteMutation
|
||||||
onCompleted={handleProductTypeBulkDelete}
|
onCompleted={handleProductTypeBulkDelete}
|
||||||
|
@ -174,9 +187,7 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
||||||
onTabSave={() => openModal("save-search")}
|
onTabSave={() => openModal("save-search")}
|
||||||
tabs={tabs.map(tab => tab.name)}
|
tabs={tabs.map(tab => tab.name)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
productTypes={maybe(() =>
|
productTypes={productTypesData}
|
||||||
data.productTypes.edges.map(edge => edge.node)
|
|
||||||
)}
|
|
||||||
pageInfo={pageInfo}
|
pageInfo={pageInfo}
|
||||||
onAdd={() => navigate(productTypeAddUrl)}
|
onAdd={() => navigate(productTypeAddUrl)}
|
||||||
onBack={() => navigate(configurationMenuUrl)}
|
onBack={() => navigate(configurationMenuUrl)}
|
||||||
|
@ -185,7 +196,7 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
||||||
onRowClick={id => () => navigate(productTypeUrl(id))}
|
onRowClick={id => () => navigate(productTypeUrl(id))}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
isChecked={isSelected}
|
isChecked={isSelected}
|
||||||
selected={listElements.length}
|
selected={selectedProductTypes.length}
|
||||||
sort={getSortParams(params)}
|
sort={getSortParams(params)}
|
||||||
toggle={toggle}
|
toggle={toggle}
|
||||||
toggleAll={toggleAll}
|
toggleAll={toggleAll}
|
||||||
|
@ -194,7 +205,7 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openModal("remove", {
|
openModal("remove", {
|
||||||
ids: listElements
|
ids: selectedProductTypes
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -202,30 +213,14 @@ export const ProductTypeList: React.FC<ProductTypeListProps> = ({ params }) => {
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ActionDialog
|
<TypeDeleteWarningDialog
|
||||||
confirmButtonState={productTypeBulkDeleteOpts.status}
|
{...productTypeDeleteData}
|
||||||
|
typesData={productTypesData}
|
||||||
|
typesToDelete={selectedProductTypes}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
onConfirm={onProductTypeBulkDelete}
|
onDelete={onProductTypeBulkDelete}
|
||||||
open={params.action === "remove"}
|
deleteButtonState={productTypeBulkDeleteOpts.status}
|
||||||
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>
|
|
||||||
<SaveFilterTabDialog
|
<SaveFilterTabDialog
|
||||||
open={params.action === "save-search"}
|
open={params.action === "save-search"}
|
||||||
confirmButtonState="default"
|
confirmButtonState="default"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import AssignAttributeDialog from "@saleor/components/AssignAttributeDialog";
|
||||||
import AttributeUnassignDialog from "@saleor/components/AttributeUnassignDialog";
|
import AttributeUnassignDialog from "@saleor/components/AttributeUnassignDialog";
|
||||||
import BulkAttributeUnassignDialog from "@saleor/components/BulkAttributeUnassignDialog";
|
import BulkAttributeUnassignDialog from "@saleor/components/BulkAttributeUnassignDialog";
|
||||||
import NotFoundPage from "@saleor/components/NotFoundPage";
|
import NotFoundPage from "@saleor/components/NotFoundPage";
|
||||||
|
import TypeDeleteWarningDialog from "@saleor/components/TypeDeleteWarningDialog";
|
||||||
import { WindowTitle } from "@saleor/components/WindowTitle";
|
import { WindowTitle } from "@saleor/components/WindowTitle";
|
||||||
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
|
||||||
import useBulkActions from "@saleor/hooks/useBulkActions";
|
import useBulkActions from "@saleor/hooks/useBulkActions";
|
||||||
|
@ -11,6 +12,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getStringOrPlaceholder, maybe } from "@saleor/misc";
|
import { getStringOrPlaceholder, maybe } from "@saleor/misc";
|
||||||
|
import useProductTypeDelete from "@saleor/productTypes/hooks/useProductTypeDelete";
|
||||||
import { useProductTypeUpdateMutation } from "@saleor/productTypes/mutations";
|
import { useProductTypeUpdateMutation } from "@saleor/productTypes/mutations";
|
||||||
import { ReorderEvent } from "@saleor/types";
|
import { ReorderEvent } from "@saleor/types";
|
||||||
import { ProductAttributeType } from "@saleor/types/globalTypes";
|
import { ProductAttributeType } from "@saleor/types/globalTypes";
|
||||||
|
@ -22,7 +24,6 @@ import {
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import ProductTypeDeleteDialog from "../../components/ProductTypeDeleteDialog";
|
|
||||||
import ProductTypeDetailsPage, {
|
import ProductTypeDetailsPage, {
|
||||||
ProductTypeForm
|
ProductTypeForm
|
||||||
} from "../../components/ProductTypeDetailsPage";
|
} from "../../components/ProductTypeDetailsPage";
|
||||||
|
@ -117,6 +118,11 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
||||||
return result.data.productTypeUpdate.errors;
|
return result.data.productTypeUpdate.errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const productTypeDeleteData = useProductTypeDelete({
|
||||||
|
singleId: id,
|
||||||
|
params
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductTypeDetailsQuery displayLoader variables={{ id }}>
|
<TypedProductTypeDetailsQuery displayLoader variables={{ id }}>
|
||||||
{({ data, loading: dataLoading }) => {
|
{({ data, loading: dataLoading }) => {
|
||||||
|
@ -335,62 +341,67 @@ export const ProductTypeUpdate: React.FC<ProductTypeUpdateProps> = ({
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!dataLoading &&
|
{!dataLoading && (
|
||||||
Object.keys(ProductAttributeType).map(key => (
|
<>
|
||||||
<AssignAttributeDialog
|
{Object.keys(ProductAttributeType).map(key => (
|
||||||
attributes={maybe(() =>
|
<AssignAttributeDialog
|
||||||
result.data.productType.availableAttributes.edges.map(
|
attributes={maybe(() =>
|
||||||
edge => edge.node
|
result.data.productType.availableAttributes.edges.map(
|
||||||
)
|
edge => edge.node
|
||||||
)}
|
)
|
||||||
confirmButtonState={assignAttribute.opts.status}
|
)}
|
||||||
errors={maybe(
|
confirmButtonState={assignAttribute.opts.status}
|
||||||
() =>
|
errors={maybe(
|
||||||
assignAttribute.opts.data.productAttributeAssign.errors.map(
|
() =>
|
||||||
err => err.message
|
assignAttribute.opts.data.productAttributeAssign.errors.map(
|
||||||
),
|
err => err.message
|
||||||
[]
|
),
|
||||||
)}
|
[]
|
||||||
loading={result.loading}
|
)}
|
||||||
|
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}
|
onClose={closeModal}
|
||||||
onSubmit={handleAssignAttribute}
|
onDelete={handleProductTypeDelete}
|
||||||
onFetch={search}
|
deleteButtonState={deleteProductType.opts.status}
|
||||||
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}
|
|
||||||
/>
|
/>
|
||||||
))}
|
</>
|
||||||
<ProductTypeDeleteDialog
|
)}
|
||||||
confirmButtonState={deleteProductType.opts.status}
|
|
||||||
name={maybe(() => data.productType.name, "...")}
|
|
||||||
open={params.action === "remove"}
|
|
||||||
onClose={() => navigate(productTypeUrl(id))}
|
|
||||||
onConfirm={handleProductTypeDelete}
|
|
||||||
/>
|
|
||||||
<BulkAttributeUnassignDialog
|
<BulkAttributeUnassignDialog
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
defaultMessage: "Unassign Attribute from Product Type",
|
defaultMessage: "Unassign Attribute from Product Type",
|
||||||
|
|
|
@ -16,7 +16,6 @@ import {
|
||||||
} from "@saleor/products/types/ProductMediaById";
|
} from "@saleor/products/types/ProductMediaById";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
import { CountAllProducts } from "./types/CountAllProducts";
|
|
||||||
import {
|
import {
|
||||||
CreateMultipleVariantsData,
|
CreateMultipleVariantsData,
|
||||||
CreateMultipleVariantsDataVariables
|
CreateMultipleVariantsDataVariables
|
||||||
|
@ -38,6 +37,7 @@ import {
|
||||||
InitialProductFilterProductTypes,
|
InitialProductFilterProductTypes,
|
||||||
InitialProductFilterProductTypesVariables
|
InitialProductFilterProductTypesVariables
|
||||||
} from "./types/InitialProductFilterProductTypes";
|
} from "./types/InitialProductFilterProductTypes";
|
||||||
|
import { ProductCount, ProductCountVariables } from "./types/ProductCount";
|
||||||
import {
|
import {
|
||||||
ProductDetails,
|
ProductDetails,
|
||||||
ProductDetailsVariables
|
ProductDetailsVariables
|
||||||
|
@ -176,16 +176,18 @@ export const useProductListQuery = makeQuery<ProductList, ProductListVariables>(
|
||||||
productListQuery
|
productListQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
const countAllProductsQuery = gql`
|
const productCountQuery = gql`
|
||||||
query CountAllProducts {
|
query ProductCount($filter: ProductFilterInput) {
|
||||||
products {
|
products(filter: $filter) {
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const useCountAllProducts = makeQuery<CountAllProducts, null>(
|
|
||||||
countAllProductsQuery
|
export const useProductCountQuery = makeQuery<
|
||||||
);
|
ProductCount,
|
||||||
|
ProductCountVariables
|
||||||
|
>(productCountQuery);
|
||||||
|
|
||||||
const productDetailsQuery = gql`
|
const productDetailsQuery = gql`
|
||||||
${productFragmentDetails}
|
${productFragmentDetails}
|
||||||
|
|
|
@ -3,15 +3,21 @@
|
||||||
// @generated
|
// @generated
|
||||||
// This file was automatically generated and should not be edited.
|
// 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";
|
__typename: "ProductCountableConnection";
|
||||||
totalCount: number | null;
|
totalCount: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CountAllProducts {
|
export interface ProductCount {
|
||||||
products: CountAllProducts_products | null;
|
products: ProductCount_products | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductCountVariables {
|
||||||
|
filter?: ProductFilterInput | null;
|
||||||
}
|
}
|
|
@ -32,11 +32,11 @@ import {
|
||||||
} from "@saleor/products/components/ProductListPage/utils";
|
} from "@saleor/products/components/ProductListPage/utils";
|
||||||
import {
|
import {
|
||||||
useAvailableInGridAttributesQuery,
|
useAvailableInGridAttributesQuery,
|
||||||
useCountAllProducts,
|
|
||||||
useInitialProductFilterAttributesQuery,
|
useInitialProductFilterAttributesQuery,
|
||||||
useInitialProductFilterCategoriesQuery,
|
useInitialProductFilterCategoriesQuery,
|
||||||
useInitialProductFilterCollectionsQuery,
|
useInitialProductFilterCollectionsQuery,
|
||||||
useInitialProductFilterProductTypesQuery,
|
useInitialProductFilterProductTypesQuery,
|
||||||
|
useProductCountQuery,
|
||||||
useProductListQuery
|
useProductListQuery
|
||||||
} from "@saleor/products/queries";
|
} from "@saleor/products/queries";
|
||||||
import { ProductListVariables } from "@saleor/products/types/ProductList";
|
import { ProductListVariables } from "@saleor/products/types/ProductList";
|
||||||
|
@ -186,7 +186,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
||||||
: 0
|
: 0
|
||||||
: parseInt(params.activeTab, 0);
|
: parseInt(params.activeTab, 0);
|
||||||
|
|
||||||
const countAllProducts = useCountAllProducts({
|
const countAllProducts = useProductCountQuery({
|
||||||
skip: params.action !== "export"
|
skip: params.action !== "export"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,6 @@ function loadStories() {
|
||||||
|
|
||||||
// Product types
|
// Product types
|
||||||
require("./stories/productTypes/ProductTypeCreatePage");
|
require("./stories/productTypes/ProductTypeCreatePage");
|
||||||
require("./stories/productTypes/ProductTypeDeleteDialog");
|
|
||||||
require("./stories/productTypes/ProductTypeDetailsPage");
|
require("./stories/productTypes/ProductTypeDetailsPage");
|
||||||
require("./stories/productTypes/ProductTypeListPage");
|
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[];
|
choices: MultiAutocompleteChoiceType[];
|
||||||
displayValues: MultiAutocompleteChoiceType[];
|
displayValues: MultiAutocompleteChoiceType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Ids = string[];
|
||||||
|
|
|
@ -1528,6 +1528,12 @@ export interface PageCreateInput {
|
||||||
pageType: string;
|
pageType: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PageFilterInput {
|
||||||
|
pageTypes?: string[] | null;
|
||||||
|
search?: string | null;
|
||||||
|
metadata?: (MetadataInput | null)[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PageInput {
|
export interface PageInput {
|
||||||
slug?: string | null;
|
slug?: string | null;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
|
|
Loading…
Reference in a new issue