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:
mmarkusik 2021-05-11 14:26:17 +02:00 committed by GitHub
parent 4bf40ed410
commit bb441ea11a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 900 additions and 302 deletions

View file

@ -3579,6 +3579,7 @@ enum PageErrorCode {
} }
input PageFilterInput { input PageFilterInput {
pageTypes: [ID!]
search: String search: String
metadata: [MetadataInput] metadata: [MetadataInput]
} }

View file

@ -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"

View 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;

View file

@ -0,0 +1,2 @@
export * from "./DeleteButton";
export { default } from "./DeleteButton";

View file

@ -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"]} />
));

View file

@ -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;

View file

@ -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;

View file

@ -0,0 +1,2 @@
export * from "./TypeDeleteWarningDialog";
export { default } from "./TypeDeleteWarningDialog";

View 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" }
);

View 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>
>;

View file

@ -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 />
</>
)}
</>
); );
}; };

View file

@ -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 wont 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 wont be able to revert changes."
description="delete page type"
values={{
name: <strong>{name}</strong>
}}
/>
)}
</DialogContentText>
</ActionDialog>
);
};
PageTypeDeleteDialog.displayName = "PageTypeDeleteDialog";
export default PageTypeDeleteDialog;

View file

@ -1,2 +0,0 @@
export { default } from "./PageTypeDeleteDialog";
export * from "./PageTypeDeleteDialog";

View file

@ -0,0 +1,2 @@
export * from "./usePageTypeDelete";
export { default } from "./usePageTypeDelete";

View 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 wont 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 wont be able to assign them to created pages.",
description:
"ProductTypeDeleteWarningDialog single assigned items description"
}
});

View 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;
}

View 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;

View file

@ -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({

View file

@ -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"}

View file

@ -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
);

View 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;
}

View file

@ -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;

View file

@ -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;

View file

@ -1,2 +0,0 @@
export { default } from "./ProductTypeDeleteDialog";
export * from "./ProductTypeDeleteDialog";

View file

@ -0,0 +1,2 @@
export * from "./useProductTypeDelete";
export { default } from "./useProductTypeDelete";

View 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 wont 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 wont be able to assign them to created products.",
description:
"ProductTypeDeleteWarningDialog single assigned items description"
}
});

View file

@ -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;

View file

@ -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"

View file

@ -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",

View file

@ -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}

View file

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

View file

@ -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"
}); });

View file

@ -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");

View file

@ -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} />);

View file

@ -203,3 +203,5 @@ export interface AutocompleteFilterOpts
choices: MultiAutocompleteChoiceType[]; choices: MultiAutocompleteChoiceType[];
displayValues: MultiAutocompleteChoiceType[]; displayValues: MultiAutocompleteChoiceType[];
} }
export type Ids = string[];

View file

@ -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;