From 96d8aa101cea7f22d5b4fa634919c70c0eb1e8fd Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 26 Oct 2020 11:29:41 +0100 Subject: [PATCH 1/7] Add infinite scroll to product selection --- src/collections/views/CollectionDetails.tsx | 9 +- .../AssignProductDialog.tsx | 103 +++++++++++------- src/discounts/views/SaleDetails.tsx | 8 ++ src/discounts/views/VoucherDetails.tsx | 6 + 4 files changed, 85 insertions(+), 41 deletions(-) diff --git a/src/collections/views/CollectionDetails.tsx b/src/collections/views/CollectionDetails.tsx index 9ca6d0798..1ff2e1862 100644 --- a/src/collections/views/CollectionDetails.tsx +++ b/src/collections/views/CollectionDetails.tsx @@ -60,8 +60,11 @@ export const CollectionDetails: React.FC = ({ ); const paginate = usePaginator(); const intl = useIntl(); - const { search, result } = useProductSearch({ - variables: DEFAULT_INITIAL_SEARCH_DATA + const { search, loadMore, result } = useProductSearch({ + variables: { + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 100 + } }); const [updateMetadata] = useMetadataUpdate({}); const [updatePrivateMetadata] = usePrivateMetadataUpdate({}); @@ -296,8 +299,10 @@ export const CollectionDetails: React.FC = ({ /> diff --git a/src/components/AssignProductDialog/AssignProductDialog.tsx b/src/components/AssignProductDialog/AssignProductDialog.tsx index 92a2a9a0c..0f0409c25 100644 --- a/src/components/AssignProductDialog/AssignProductDialog.tsx +++ b/src/components/AssignProductDialog/AssignProductDialog.tsx @@ -19,7 +19,9 @@ import useSearchQuery from "@saleor/hooks/useSearchQuery"; import { buttonMessages } from "@saleor/intl"; import { maybe } from "@saleor/misc"; import { SearchProducts_search_edges_node } from "@saleor/searches/types/SearchProducts"; +import { FetchMoreProps } from "@saleor/types"; import React from "react"; +import InfiniteScroll from "react-infinite-scroller"; import { FormattedMessage, useIntl } from "react-intl"; import Checkbox from "../Checkbox"; @@ -30,7 +32,7 @@ export interface FormData { } const useStyles = makeStyles( - { + theme => ({ avatar: { "&&:first-child": { paddingLeft: 0 @@ -44,17 +46,25 @@ const useStyles = makeStyles( colName: { paddingLeft: 0 }, + loadMoreLoaderContainer: { + alignItems: "center", + display: "flex", + height: theme.spacing(3), + justifyContent: "center", + marginTop: theme.spacing(3) + }, overflow: { overflowY: "visible" }, scrollArea: { + height: 500, overflowY: "scroll" } - }, + }), { name: "AssignProductDialog" } ); -export interface AssignProductDialogProps { +export interface AssignProductDialogProps extends FetchMoreProps { confirmButtonState: ConfirmButtonTransitionState; open: boolean; products: SearchProducts_search_edges_node[]; @@ -84,11 +94,13 @@ function handleProductAssign( const AssignProductDialog: React.FC = props => { const { confirmButtonState, + hasMore, open, loading, products, onClose, onFetch, + onFetchMore, onSubmit } = props; const classes = useStyles(props); @@ -135,44 +147,57 @@ const AssignProductDialog: React.FC = props => { />
- - - {products && - products.map(product => { - const isSelected = selectedProducts.some( - selectedProduct => selectedProduct.id === product.id - ); + + +
+ } + threshold={10} + > + + + {products && + products.map(product => { + const isSelected = selectedProducts.some( + selectedProduct => selectedProduct.id === product.id + ); - return ( - - product.thumbnail.url)} - /> - - {product.name} - - - - handleProductAssign( - product, - isSelected, - selectedProducts, - setSelectedProducts - ) - } + return ( + + product.thumbnail.url)} /> - - - ); - })} - - + + {product.name} + + + + handleProductAssign( + product, + isSelected, + selectedProducts, + setSelectedProducts + ) + } + /> + + + ); + })} + + + diff --git a/src/discounts/views/SaleDetails.tsx b/src/discounts/views/SaleDetails.tsx index b7b5bbde6..a63c33418 100644 --- a/src/discounts/views/SaleDetails.tsx +++ b/src/discounts/views/SaleDetails.tsx @@ -68,18 +68,21 @@ export const SaleDetails: React.FC = ({ id, params }) => { ); const intl = useIntl(); const { + // loadMore: loadMoreCategories, search: searchCategories, result: searchCategoriesOpts } = useCategorySearch({ variables: DEFAULT_INITIAL_SEARCH_DATA }); const { + // loadMore: loadMoreCollections, search: searchCollections, result: searchCollectionsOpts } = useCollectionSearch({ variables: DEFAULT_INITIAL_SEARCH_DATA }); const { + loadMore: loadMoreProducts, search: searchProducts, result: searchProductsOpts } = useProductSearch({ @@ -325,8 +328,13 @@ export const SaleDetails: React.FC = ({ id, params }) => { /> diff --git a/src/discounts/views/VoucherDetails.tsx b/src/discounts/views/VoucherDetails.tsx index 866073fde..4c714b29b 100644 --- a/src/discounts/views/VoucherDetails.tsx +++ b/src/discounts/views/VoucherDetails.tsx @@ -82,6 +82,7 @@ export const VoucherDetails: React.FC = ({ variables: DEFAULT_INITIAL_SEARCH_DATA }); const { + loadMore: loadMoreProducts, search: searchProducts, result: searchProductsOpts } = useProductSearch({ @@ -484,8 +485,13 @@ export const VoucherDetails: React.FC = ({ confirmButtonState={ voucherCataloguesAddOpts.status } + hasMore={ + searchProductsOpts.data?.search.pageInfo + .hasNextPage + } open={params.action === "assign-product"} onFetch={searchProducts} + onFetchMore={loadMoreProducts} loading={searchProductsOpts.loading} onClose={closeModal} onSubmit={products => From fb0a503b9189c368c7d3ef75d8339badb2a88325 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Mon, 26 Oct 2020 12:20:52 +0100 Subject: [PATCH 2/7] Add load more to category and collection selection --- .../AssignCategoryDialog.tsx | 106 +++++++++++------- .../AssignCollectionDialog.tsx | 103 ++++++++++------- src/discounts/views/SaleDetails.tsx | 14 ++- src/discounts/views/VoucherDetails.tsx | 12 ++ src/styles/useScrollableDialogStyle.ts | 28 +++++ 5 files changed, 183 insertions(+), 80 deletions(-) create mode 100644 src/styles/useScrollableDialogStyle.ts diff --git a/src/components/AssignCategoryDialog/AssignCategoryDialog.tsx b/src/components/AssignCategoryDialog/AssignCategoryDialog.tsx index 7b3bc1ed5..4008e4472 100644 --- a/src/components/AssignCategoryDialog/AssignCategoryDialog.tsx +++ b/src/components/AssignCategoryDialog/AssignCategoryDialog.tsx @@ -17,7 +17,10 @@ import ResponsiveTable from "@saleor/components/ResponsiveTable"; import useSearchQuery from "@saleor/hooks/useSearchQuery"; import { buttonMessages } from "@saleor/intl"; import { SearchCategories_search_edges_node } from "@saleor/searches/types/SearchCategories"; +import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle"; +import { FetchMoreProps } from "@saleor/types"; import React from "react"; +import InfiniteScroll from "react-infinite-scroller"; import { FormattedMessage, useIntl } from "react-intl"; import Checkbox from "../Checkbox"; @@ -37,9 +40,6 @@ const useStyles = makeStyles( checkboxCell: { paddingLeft: 0 }, - overflow: { - overflowY: "visible" - }, wideCell: { width: "100%" } @@ -47,7 +47,7 @@ const useStyles = makeStyles( { name: "AssignCategoryDialog" } ); -interface AssignCategoriesDialogProps { +interface AssignCategoriesDialogProps extends FetchMoreProps { categories: SearchCategories_search_edges_node[]; confirmButtonState: ConfirmButtonTransitionState; open: boolean; @@ -80,25 +80,31 @@ const AssignCategoriesDialog: React.FC = props => { open, loading, categories: categories, + hasMore, onClose, onFetch, + onFetchMore, onSubmit } = props; const classes = useStyles(props); + const scrollableDialogClasses = useScrollableDialogStyle({}); const intl = useIntl(); const [query, onQueryChange] = useSearchQuery(onFetch); const [selectedCategories, setSelectedCategories] = React.useState< SearchCategories_search_edges_node[] >([]); + const container = React.useRef(); const handleSubmit = () => onSubmit(selectedCategories); + const containerHeight = container.current?.scrollHeight - 130; + return ( @@ -108,7 +114,10 @@ const AssignCategoriesDialog: React.FC = props => { description="dialog header" /> - + = props => { }} /> - - - {categories && - categories.map(category => { - const isSelected = !!selectedCategories.find( - selectedCategories => selectedCategories.id === category.id - ); +
+ + +
+ } + threshold={10} + > + + + {categories && + categories.map(category => { + const isSelected = !!selectedCategories.find( + selectedCategories => + selectedCategories.id === category.id + ); - return ( - - - - handleCategoryAssign( - category, - isSelected, - selectedCategories, - setSelectedCategories - ) - } - /> - - - {category.name} - - - ); - })} - - + return ( + + + + handleCategoryAssign( + category, + isSelected, + selectedCategories, + setSelectedCategories + ) + } + /> + + + {category.name} + + + ); + })} +
+
+ +
- - - - -
+ ); }; -AssignCategoriesDialog.displayName = "AssignCategoriesDialog"; -export default AssignCategoriesDialog; + +AssignCategoryDialog.displayName = "AssignCategoryDialog"; +export default AssignCategoryDialog; diff --git a/src/components/AssignCollectionDialog/AssignCollectionDialog.tsx b/src/components/AssignCollectionDialog/AssignCollectionDialog.tsx index 99266f934..0ee4d7146 100644 --- a/src/components/AssignCollectionDialog/AssignCollectionDialog.tsx +++ b/src/components/AssignCollectionDialog/AssignCollectionDialog.tsx @@ -1,213 +1,41 @@ -import Button from "@material-ui/core/Button"; -import CircularProgress from "@material-ui/core/CircularProgress"; -import Dialog from "@material-ui/core/Dialog"; -import DialogActions from "@material-ui/core/DialogActions"; -import DialogContent from "@material-ui/core/DialogContent"; -import DialogTitle from "@material-ui/core/DialogTitle"; -import { makeStyles } from "@material-ui/core/styles"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableRow from "@material-ui/core/TableRow"; -import TextField from "@material-ui/core/TextField"; -import ResponsiveTable from "@saleor/components/ResponsiveTable"; -import useSearchQuery from "@saleor/hooks/useSearchQuery"; -import { buttonMessages } from "@saleor/intl"; import { SearchCollections_search_edges_node } from "@saleor/searches/types/SearchCollections"; -import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle"; -import { FetchMoreProps } from "@saleor/types"; import React from "react"; -import InfiniteScroll from "react-infinite-scroller"; -import { FormattedMessage, useIntl } from "react-intl"; +import { useIntl } from "react-intl"; -import Checkbox from "../Checkbox"; -import ConfirmButton, { - ConfirmButtonTransitionState -} from "../ConfirmButton/ConfirmButton"; -import FormSpacer from "../FormSpacer"; +import AssignContainerDialog, { + AssignContainerDialogProps +} from "../AssignContainerDialog"; -export interface FormData { +interface AssignCollectionDialogProps + extends Omit { collections: SearchCollections_search_edges_node[]; - query: string; } -const useStyles = makeStyles( - { - avatar: { - "&:first-child": { - paddingLeft: 0 - } - }, - checkboxCell: { - paddingLeft: 0 - }, - wideCell: { - width: "100%" - } - }, - { name: "AssignCollectionDialog" } -); - -interface AssignCollectionDialogProps extends FetchMoreProps { - collections: SearchCollections_search_edges_node[]; - confirmButtonState: ConfirmButtonTransitionState; - open: boolean; - loading: boolean; - onClose: () => void; - onFetch: (value: string) => void; - onSubmit: (data: SearchCollections_search_edges_node[]) => void; -} - -function handleCollectionAssign( - product: SearchCollections_search_edges_node, - isSelected: boolean, - selectedCollections: SearchCollections_search_edges_node[], - setSelectedCollections: (data: SearchCollections_search_edges_node[]) => void -) { - if (isSelected) { - setSelectedCollections( - selectedCollections.filter( - selectedProduct => selectedProduct.id !== product.id - ) - ); - } else { - setSelectedCollections([...selectedCollections, product]); - } -} - -const AssignCollectionDialog: React.FC = props => { - const { - confirmButtonState, - hasMore, - open, - loading, - collections, - onClose, - onFetch, - onFetchMore, - onSubmit - } = props; - const classes = useStyles(props); - const scrollableDialogClasses = useScrollableDialogStyle({}); - +const AssignCollectionDialog: React.FC = ({ + collections, + ...rest +}) => { const intl = useIntl(); - const [query, onQueryChange] = useSearchQuery(onFetch); - const [selectedCollections, setSelectedCollections] = React.useState< - SearchCollections_search_edges_node[] - >([]); - const container = React.useRef(); - - const handleSubmit = () => onSubmit(selectedCollections); - - const containerHeight = container.current?.scrollHeight - 130; return ( - - - - - - - }} - /> - -
- - -
- } - threshold={10} - > - - - {collections && - collections.map(collection => { - const isSelected = !!selectedCollections.find( - selectedCollection => - selectedCollection.id === collection.id - ); - - return ( - - - - handleCollectionAssign( - collection, - isSelected, - selectedCollections, - setSelectedCollections - ) - } - /> - - - {collection.name} - - - ); - })} - - - - -
- - - - - - -
+ ); }; + AssignCollectionDialog.displayName = "AssignCollectionDialog"; export default AssignCollectionDialog; diff --git a/src/components/AssignContainerDialog/AssignContainerDialog.tsx b/src/components/AssignContainerDialog/AssignContainerDialog.tsx new file mode 100644 index 000000000..23b3e29b4 --- /dev/null +++ b/src/components/AssignContainerDialog/AssignContainerDialog.tsx @@ -0,0 +1,204 @@ +import Button from "@material-ui/core/Button"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import { makeStyles } from "@material-ui/core/styles"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableRow from "@material-ui/core/TableRow"; +import TextField from "@material-ui/core/TextField"; +import ResponsiveTable from "@saleor/components/ResponsiveTable"; +import useSearchQuery from "@saleor/hooks/useSearchQuery"; +import { buttonMessages } from "@saleor/intl"; +import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle"; +import { FetchMoreProps, Node } from "@saleor/types"; +import React from "react"; +import InfiniteScroll from "react-infinite-scroller"; +import { FormattedMessage } from "react-intl"; + +import Checkbox from "../Checkbox"; +import ConfirmButton, { + ConfirmButtonTransitionState +} from "../ConfirmButton/ConfirmButton"; +import FormSpacer from "../FormSpacer"; + +export interface FormData { + containers: string[]; + query: string; +} + +const useStyles = makeStyles( + { + avatar: { + "&:first-child": { + paddingLeft: 0 + } + }, + checkboxCell: { + paddingLeft: 0 + }, + wideCell: { + width: "100%" + } + }, + { name: "AssignContainerDialog" } +); + +interface Container extends Node { + name: string; +} +export interface AssignContainerDialogProps extends FetchMoreProps { + confirmButtonState: ConfirmButtonTransitionState; + containers: Container[]; + loading: boolean; + open: boolean; + search: Record<"label" | "placeholder", string>; + title: string; + onClose: () => void; + onFetch: (value: string) => void; + onSubmit: (data: string[]) => void; +} + +function handleContainerAssign( + containerId: string, + isSelected: boolean, + selectedContainers: string[], + setSelectedContainers: (data: string[]) => void +) { + if (isSelected) { + setSelectedContainers( + selectedContainers.filter( + selectedContainer => selectedContainer !== containerId + ) + ); + } else { + setSelectedContainers([...selectedContainers, containerId]); + } +} + +const AssignContainerDialog: React.FC = props => { + const { + confirmButtonState, + containers, + hasMore, + loading, + open, + search, + title, + onClose, + onFetch, + onFetchMore, + onSubmit + } = props; + const classes = useStyles(props); + const scrollableDialogClasses = useScrollableDialogStyle({}); + + const [query, onQueryChange] = useSearchQuery(onFetch); + const [selectedContainers, setSelectedContainers] = React.useState( + [] + ); + const container = React.useRef(); + + const handleSubmit = () => onSubmit(selectedContainers); + + const containerHeight = container.current?.scrollHeight - 130; + + return ( + + {title} + + + }} + /> + +
+ + +
+ } + threshold={10} + > + + + {containers?.map(container => { + const isSelected = !!selectedContainers.find( + selectedContainer => selectedContainer === container.id + ); + + return ( + + + + handleContainerAssign( + container.id, + isSelected, + selectedContainers, + setSelectedContainers + ) + } + /> + + + {container.name} + + + ); + })} + + + + +
+ + + + + + +
+ ); +}; +AssignContainerDialog.displayName = "AssignContainerDialog"; +export default AssignContainerDialog; diff --git a/src/components/AssignContainerDialog/index.ts b/src/components/AssignContainerDialog/index.ts new file mode 100644 index 000000000..523a573e8 --- /dev/null +++ b/src/components/AssignContainerDialog/index.ts @@ -0,0 +1,2 @@ +export * from "./AssignContainerDialog"; +export { default } from "./AssignContainerDialog"; diff --git a/src/discounts/views/SaleDetails.tsx b/src/discounts/views/SaleDetails.tsx index e24a32de0..8660a2878 100644 --- a/src/discounts/views/SaleDetails.tsx +++ b/src/discounts/views/SaleDetails.tsx @@ -382,9 +382,7 @@ export const SaleDetails: React.FC = ({ id, params }) => { ...paginationState, id, input: { - categories: categories.map( - product => product.id - ) + categories } } }) @@ -414,9 +412,7 @@ export const SaleDetails: React.FC = ({ id, params }) => { ...paginationState, id, input: { - collections: collections.map( - product => product.id - ) + collections } } }) diff --git a/src/discounts/views/VoucherDetails.tsx b/src/discounts/views/VoucherDetails.tsx index d86aa1844..e286ddd83 100644 --- a/src/discounts/views/VoucherDetails.tsx +++ b/src/discounts/views/VoucherDetails.tsx @@ -428,9 +428,7 @@ export const VoucherDetails: React.FC = ({ ...paginationState, id, input: { - categories: categories.map( - product => product.id - ) + categories } } }) @@ -462,9 +460,7 @@ export const VoucherDetails: React.FC = ({ ...paginationState, id, input: { - collections: collections.map( - product => product.id - ) + collections } } }) From ec25e521df4f09a728fe4636bec06acf461b4a6b Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Fri, 30 Oct 2020 15:01:29 +0100 Subject: [PATCH 7/7] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e5f49767..36dc8e6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ All notable, unreleased changes to this project will be documented in this file. - Fix order draft back button redirect - #753 by @orzechdev - Add manage product types and attributes permission - #768 by @orzechdev - Fix isPublished and isAvailable behaviour for products, collections and pages - #780 by @mmarkusik +- Add missing infinite scroll to searches - #793 by @dominik-zeglen ## 2.10.1