import { DialogContentText } from "@material-ui/core"; import { createCollectionChannels, createCollectionChannelsData } from "@saleor/channels/utils"; import ActionDialog from "@saleor/components/ActionDialog"; import useAppChannel from "@saleor/components/AppLayout/AppChannelContext"; import AssignProductDialog from "@saleor/components/AssignProductDialog"; import { Button } from "@saleor/components/Button"; import ChannelsAvailabilityDialog from "@saleor/components/ChannelsAvailabilityDialog"; import NotFoundPage from "@saleor/components/NotFoundPage"; import { WindowTitle } from "@saleor/components/WindowTitle"; import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "@saleor/config"; import { CollectionInput, CollectionUpdateMutation, useCollectionAssignProductMutation, useCollectionChannelListingUpdateMutation, useCollectionDetailsQuery, useCollectionUpdateMutation, useRemoveCollectionMutation, useUnassignCollectionProductMutation, useUpdateMetadataMutation, useUpdatePrivateMetadataMutation } from "@saleor/graphql"; import useBulkActions from "@saleor/hooks/useBulkActions"; import useChannels from "@saleor/hooks/useChannels"; import useLocalPaginator, { useLocalPaginationState } from "@saleor/hooks/useLocalPaginator"; import useLocalStorage from "@saleor/hooks/useLocalStorage"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages, errorMessages } from "@saleor/intl"; import useProductSearch from "@saleor/searches/useProductSearch"; import { arrayDiff } from "@saleor/utils/arrays"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createMetadataUpdateHandler from "@saleor/utils/handlers/metadataUpdateHandler"; import { mapEdgesToItems } from "@saleor/utils/maps"; import { getParsedDataForJsonStringField } from "@saleor/utils/richText/misc"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { getMutationErrors, getMutationState, maybe } from "../../misc"; import CollectionDetailsPage from "../components/CollectionDetailsPage/CollectionDetailsPage"; import { CollectionUpdateData } from "../components/CollectionDetailsPage/form"; import { collectionListUrl, collectionUrl, CollectionUrlDialog, CollectionUrlQueryParams } from "../urls"; import { COLLECTION_DETAILS_FORM_ID } from "./consts"; interface CollectionDetailsProps { id: string; params: CollectionUrlQueryParams; } export const CollectionDetails: React.FC = ({ id, params }) => { const navigate = useNavigator(); const notify = useNotifier(); const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( params.ids ); const intl = useIntl(); const { search, loadMore, result } = useProductSearch({ variables: DEFAULT_INITIAL_SEARCH_DATA }); const [openModal, closeModal] = createDialogActionHandlers< CollectionUrlDialog, CollectionUrlQueryParams >(navigate, params => collectionUrl(id, params), params); const [updateMetadata] = useUpdateMetadataMutation({}); const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({}); const [ updateChannels, updateChannelsOpts ] = useCollectionChannelListingUpdateMutation({}); const { availableChannels } = useAppChannel(false); const handleCollectionUpdate = (data: CollectionUpdateMutation) => { if (data.collectionUpdate.errors.length === 0) { notify({ status: "success", text: intl.formatMessage(commonMessages.savedChanges) }); navigate(collectionUrl(id)); } else { const backgroundImageError = data.collectionUpdate.errors.find( error => error.field === ("backgroundImage" as keyof CollectionInput) ); if (backgroundImageError) { notify({ status: "error", title: intl.formatMessage(errorMessages.imgageUploadErrorTitle), text: intl.formatMessage(errorMessages.imageUploadErrorText) }); } } }; const [updateCollection, updateCollectionOpts] = useCollectionUpdateMutation({ onCompleted: handleCollectionUpdate }); const [assignProduct, assignProductOpts] = useCollectionAssignProductMutation( { onCompleted: data => { if (data.collectionAddProducts.errors.length === 0) { notify({ status: "success", text: intl.formatMessage({ id: "56vUeQ", defaultMessage: "Added product to collection" }) }); navigate(collectionUrl(id), { replace: true }); } } } ); const [ unassignProduct, unassignProductOpts ] = useUnassignCollectionProductMutation({ onCompleted: data => { if (data.collectionRemoveProducts.errors.length === 0) { notify({ status: "success", text: intl.formatMessage({ id: "WW+Ruy", defaultMessage: "Deleted product from collection" }) }); reset(); closeModal(); } } }); const [removeCollection, removeCollectionOpts] = useRemoveCollectionMutation({ onCompleted: data => { if (data.collectionDelete.errors.length === 0) { notify({ status: "success", text: intl.formatMessage({ id: "Q8wHwJ", defaultMessage: "Deleted collection" }) }); navigate(collectionListUrl()); } } }); const [paginationState, setPaginationState] = useLocalPaginationState( PAGINATE_BY ); const paginate = useLocalPaginator(setPaginationState); const [selectedChannel] = useLocalStorage("collectionListChannel", ""); const { data, loading } = useCollectionDetailsQuery({ displayLoader: true, variables: { id, ...paginationState } }); const collection = data?.collection; if (collection === null) { return ; } const allChannels = createCollectionChannels( availableChannels )?.sort((channel, nextChannel) => channel.name.localeCompare(nextChannel.name) ); const collectionChannelsChoices = createCollectionChannelsData(collection); const { channelListElements, channelsToggle, currentChannels, handleChannelsConfirm, handleChannelsModalClose, handleChannelsModalOpen, isChannelSelected, isChannelsModalOpen, setCurrentChannels, toggleAllChannels } = useChannels( collectionChannelsChoices, params?.action, { closeModal, openModal }, { formId: COLLECTION_DETAILS_FORM_ID } ); const handleUpdate = async (formData: CollectionUpdateData) => { const input: CollectionInput = { backgroundImageAlt: formData.backgroundImageAlt, description: getParsedDataForJsonStringField(formData.description), name: formData.name, seo: { description: formData.seoDescription, title: formData.seoTitle }, slug: formData.slug }; const result = await updateCollection({ variables: { id, input } }); const initialIds = collectionChannelsChoices.map(channel => channel.id); const modifiedIds = formData.channelListings.map(channel => channel.id); const idsDiff = arrayDiff(initialIds, modifiedIds); updateChannels({ variables: { id: collection.id, input: { addChannels: formData.channelListings.map(channel => ({ channelId: channel.id, isPublished: channel.isPublished, publicationDate: channel.publicationDate })), removeChannels: idsDiff.removed } } }); return getMutationErrors(result); }; const handleSubmit = createMetadataUpdateHandler( data?.collection, handleUpdate, variables => updateMetadata({ variables }), variables => updatePrivateMetadata({ variables }) ); const formTransitionState = getMutationState( updateCollectionOpts.called, updateCollectionOpts.loading, updateCollectionOpts.data?.collectionUpdate.errors ); const { loadNextPage, loadPreviousPage, pageInfo } = paginate( data?.collection?.products?.pageInfo, paginationState ); return ( <> {!!allChannels?.length && ( )} openModal("assign")} disabled={loading || updateChannelsOpts.loading} collection={data?.collection} channelsErrors={ updateChannelsOpts?.data?.collectionChannelListingUpdate.errors || [] } errors={updateCollectionOpts?.data?.collectionUpdate.errors || []} onCollectionRemove={() => openModal("remove")} onImageDelete={() => openModal("removeImage")} onImageUpload={file => updateCollection({ variables: { id, input: { backgroundImage: file } } }) } onSubmit={handleSubmit} onNextPage={loadNextPage} onPreviousPage={loadPreviousPage} pageInfo={pageInfo} onProductUnassign={(productId, event) => { event.stopPropagation(); unassignProduct({ variables: { collectionId: id, productIds: [productId], ...paginationState } }); }} saveButtonBarState={formTransitionState} toolbar={ } isChecked={isSelected} selected={listElements.length} toggle={toggle} toggleAll={toggleAll} currentChannels={currentChannels} channelsCount={availableChannels.length} selectedChannelId={selectedChannel} openChannelsModal={handleChannelsModalOpen} onChannelsChange={setCurrentChannels} /> assignProduct({ variables: { ...paginationState, collectionId: id, productIds: products } }) } products={mapEdgesToItems(result?.data?.search)?.filter( suggestedProduct => suggestedProduct.id )} /> removeCollection({ variables: { id } }) } open={params.action === "remove"} title={intl.formatMessage({ id: "+wpvnk", defaultMessage: "Delete Collection", description: "dialog title" })} variant="delete" > {maybe(() => data.collection.name, "...")} ) }} /> unassignProduct({ variables: { ...paginationState, collectionId: id, productIds: params.ids } }) } open={params.action === "unassign"} title={intl.formatMessage({ id: "5OtU+V", defaultMessage: "Unassign products from collection", description: "dialog title" })} > params.ids.length), displayQuantity: {maybe(() => params.ids.length)} }} /> updateCollection({ variables: { id, input: { backgroundImage: null } } }) } open={params.action === "removeImage"} title={intl.formatMessage({ id: "fzk04H", defaultMessage: "Delete image", description: "dialog title" })} variant="delete" > ); }; export default CollectionDetails;