Fix duplicated labels in column picker (#1197)

* Fix duplicated labels in column picker

* Update changelog

* Refactor column picker scroll fetch

* Migrate react-infinite-scroller to react-infinite-scroll-component

* Remove unneeded keys

* Align dialog items to top
This commit is contained in:
Dawid Tarasiuk 2021-07-01 10:21:41 +02:00 committed by GitHub
parent 8e1dc4e12d
commit 909e08f2af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 280 additions and 268 deletions

View file

@ -57,6 +57,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Fix breaking select popups in filters - #1193 by @orzechdev - Fix breaking select popups in filters - #1193 by @orzechdev
- Create channel filters in product, sales and voucher lists - #1187 by @jwm0 - Create channel filters in product, sales and voucher lists - #1187 by @jwm0
- Add generic filter validation - #1187 by @jwm0 - Add generic filter validation - #1187 by @jwm0
- Fix duplicated labels in column picker - #1197 by @orzechdev
# 2.11.1 # 2.11.1

13
package-lock.json generated
View file

@ -22656,12 +22656,12 @@
"prop-types": "^15.6.1" "prop-types": "^15.6.1"
} }
}, },
"react-infinite-scroller": { "react-infinite-scroll-component": {
"version": "1.2.4", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz", "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz",
"integrity": "sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw==", "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==",
"requires": { "requires": {
"prop-types": "^15.5.8" "throttle-debounce": "^2.1.0"
} }
}, },
"react-inlinesvg": { "react-inlinesvg": {
@ -26702,8 +26702,7 @@
"throttle-debounce": { "throttle-debounce": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz",
"integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==", "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ=="
"dev": true
}, },
"throttleit": { "throttleit": {
"version": "1.0.0", "version": "1.0.0",

View file

@ -65,7 +65,7 @@
"react-error-boundary": "^1.2.5", "react-error-boundary": "^1.2.5",
"react-gtm-module": "^2.0.11", "react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-infinite-scroller": "^1.2.4", "react-infinite-scroll-component": "^6.1.0",
"react-inlinesvg": "^2.1.1", "react-inlinesvg": "^2.1.1",
"react-intl": "^5.10.2", "react-intl": "^5.10.2",
"react-jss": "^10.0.0", "react-jss": "^10.0.0",

View file

@ -30,7 +30,7 @@ import { makeStyles } from "@saleor/theme";
import { FetchMoreProps } from "@saleor/types"; import { FetchMoreProps } from "@saleor/types";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import InfiniteScroll from "react-infinite-scroller"; import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -73,6 +73,8 @@ export interface AssignAttributeDialogProps extends FetchMoreProps {
onToggle: (id: string) => void; onToggle: (id: string) => void;
} }
const scrollableTargetId = "assignAttributeScrollableDialog";
const AssignAttributeDialog: React.FC<AssignAttributeDialogProps> = ({ const AssignAttributeDialog: React.FC<AssignAttributeDialogProps> = ({
attributes, attributes,
confirmButtonState, confirmButtonState,
@ -126,19 +128,22 @@ const AssignAttributeDialog: React.FC<AssignAttributeDialogProps> = ({
}} }}
/> />
</DialogContent> </DialogContent>
<DialogContent className={classes.scrollArea} ref={anchor}> <DialogContent
className={classes.scrollArea}
ref={anchor}
id={scrollableTargetId}
>
<InfiniteScroll <InfiniteScroll
pageStart={0} dataLength={attributes?.length}
loadMore={onFetchMore} next={onFetchMore}
hasMore={hasMore} hasMore={hasMore}
useWindow={false} scrollThreshold="100px"
loader={ loader={
<div className={classes.loadMoreLoaderContainer}> <div className={classes.loadMoreLoaderContainer}>
<CircularProgress size={16} /> <CircularProgress size={16} />
</div> </div>
} }
threshold={100} scrollableTarget={scrollableTargetId}
key="infinite-scroll"
> >
<ResponsiveTable key="table"> <ResponsiveTable key="table">
<TableBody> <TableBody>

View file

@ -17,14 +17,13 @@ import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle";
import { makeStyles } from "@saleor/theme"; import { makeStyles } from "@saleor/theme";
import { FetchMoreProps, Node } from "@saleor/types"; import { FetchMoreProps, Node } from "@saleor/types";
import React from "react"; import React from "react";
import InfiniteScroll from "react-infinite-scroller"; import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import Checkbox from "../Checkbox"; import Checkbox from "../Checkbox";
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
} from "../ConfirmButton/ConfirmButton"; } from "../ConfirmButton/ConfirmButton";
import FormSpacer from "../FormSpacer";
export interface FormData { export interface FormData {
containers: string[]; containers: string[];
@ -80,6 +79,8 @@ function handleContainerAssign(
} }
} }
const scrollableTargetId = "assignContainerScrollableDialog";
const AssignContainerDialog: React.FC<AssignContainerDialogProps> = props => { const AssignContainerDialog: React.FC<AssignContainerDialogProps> = props => {
const { const {
confirmButtonState, confirmButtonState,
@ -101,12 +102,9 @@ const AssignContainerDialog: React.FC<AssignContainerDialogProps> = props => {
const [selectedContainers, setSelectedContainers] = React.useState<string[]>( const [selectedContainers, setSelectedContainers] = React.useState<string[]>(
[] []
); );
const container = React.useRef<HTMLDivElement>();
const handleSubmit = () => onSubmit(selectedContainers); const handleSubmit = () => onSubmit(selectedContainers);
const containerHeight = container.current?.scrollHeight - 130;
return ( return (
<Dialog <Dialog
onClose={onClose} onClose={onClose}
@ -116,10 +114,7 @@ const AssignContainerDialog: React.FC<AssignContainerDialogProps> = props => {
maxWidth="sm" maxWidth="sm"
> >
<DialogTitle>{title}</DialogTitle> <DialogTitle>{title}</DialogTitle>
<DialogContent <DialogContent className={scrollableDialogClasses.topArea}>
className={scrollableDialogClasses.content}
ref={container}
>
<TextField <TextField
name="query" name="query"
value={query} value={query}
@ -132,58 +127,57 @@ const AssignContainerDialog: React.FC<AssignContainerDialogProps> = props => {
endAdornment: loading && <CircularProgress size={16} /> endAdornment: loading && <CircularProgress size={16} />
}} }}
/> />
<FormSpacer /> </DialogContent>
<div <DialogContent
className={scrollableDialogClasses.scrollArea} className={scrollableDialogClasses.scrollArea}
style={{ height: containerHeight }} id={scrollableTargetId}
>
<InfiniteScroll
dataLength={containers?.length}
next={onFetchMore}
hasMore={hasMore}
scrollThreshold="100px"
loader={
<div className={scrollableDialogClasses.loadMoreLoaderContainer}>
<CircularProgress size={16} />
</div>
}
scrollableTarget={scrollableTargetId}
> >
<InfiniteScroll <ResponsiveTable>
pageStart={0} <TableBody>
loadMore={onFetchMore} {containers?.map(container => {
hasMore={hasMore} const isSelected = !!selectedContainers.find(
useWindow={false} selectedContainer => selectedContainer === container.id
loader={ );
<div className={scrollableDialogClasses.loadMoreLoaderContainer}>
<CircularProgress size={16} />
</div>
}
threshold={10}
>
<ResponsiveTable>
<TableBody>
{containers?.map(container => {
const isSelected = !!selectedContainers.find(
selectedContainer => selectedContainer === container.id
);
return ( return (
<TableRow key={container.id}> <TableRow key={container.id}>
<TableCell <TableCell
padding="checkbox" padding="checkbox"
className={classes.checkboxCell} className={classes.checkboxCell}
> >
<Checkbox <Checkbox
checked={isSelected} checked={isSelected}
onChange={() => onChange={() =>
handleContainerAssign( handleContainerAssign(
container.id, container.id,
isSelected, isSelected,
selectedContainers, selectedContainers,
setSelectedContainers setSelectedContainers
) )
} }
/> />
</TableCell> </TableCell>
<TableCell className={classes.wideCell}> <TableCell className={classes.wideCell}>
{container.name} {container.name}
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
})} })}
</TableBody> </TableBody>
</ResponsiveTable> </ResponsiveTable>
</InfiniteScroll> </InfiniteScroll>
</div>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose}> <Button onClick={onClose}>

View file

@ -13,7 +13,6 @@ import {
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton"; } from "@saleor/components/ConfirmButton";
import FormSpacer from "@saleor/components/FormSpacer";
import ResponsiveTable from "@saleor/components/ResponsiveTable"; import ResponsiveTable from "@saleor/components/ResponsiveTable";
import TableCellAvatar from "@saleor/components/TableCellAvatar"; import TableCellAvatar from "@saleor/components/TableCellAvatar";
import useSearchQuery from "@saleor/hooks/useSearchQuery"; import useSearchQuery from "@saleor/hooks/useSearchQuery";
@ -24,7 +23,7 @@ import useScrollableDialogStyle from "@saleor/styles/useScrollableDialogStyle";
import { makeStyles } from "@saleor/theme"; import { makeStyles } from "@saleor/theme";
import { FetchMoreProps } from "@saleor/types"; import { FetchMoreProps } from "@saleor/types";
import React from "react"; import React from "react";
import InfiniteScroll from "react-infinite-scroller"; import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import Checkbox from "../Checkbox"; import Checkbox from "../Checkbox";
@ -80,6 +79,8 @@ function handleProductAssign(
} }
} }
const scrollableTargetId = "assignProductScrollableDialog";
const AssignProductDialog: React.FC<AssignProductDialogProps> = props => { const AssignProductDialog: React.FC<AssignProductDialogProps> = props => {
const { const {
confirmButtonState, confirmButtonState,
@ -100,12 +101,9 @@ const AssignProductDialog: React.FC<AssignProductDialogProps> = props => {
const [selectedProducts, setSelectedProducts] = React.useState< const [selectedProducts, setSelectedProducts] = React.useState<
SearchProducts_search_edges_node[] SearchProducts_search_edges_node[]
>([]); >([]);
const container = React.useRef<HTMLDivElement>();
const handleSubmit = () => onSubmit(selectedProducts); const handleSubmit = () => onSubmit(selectedProducts);
const containerHeight = container.current?.scrollHeight - 130;
return ( return (
<Dialog <Dialog
onClose={onClose} onClose={onClose}
@ -120,10 +118,7 @@ const AssignProductDialog: React.FC<AssignProductDialogProps> = props => {
description="dialog header" description="dialog header"
/> />
</DialogTitle> </DialogTitle>
<DialogContent <DialogContent className={scrollableDialogClasses.topArea}>
className={scrollableDialogClasses.content}
ref={container}
>
<TextField <TextField
name="query" name="query"
value={query} value={query}
@ -141,66 +136,65 @@ const AssignProductDialog: React.FC<AssignProductDialogProps> = props => {
endAdornment: loading && <CircularProgress size={16} /> endAdornment: loading && <CircularProgress size={16} />
}} }}
/> />
<FormSpacer /> </DialogContent>
<div <DialogContent
className={scrollableDialogClasses.scrollArea} className={scrollableDialogClasses.scrollArea}
style={{ height: containerHeight }} id={scrollableTargetId}
>
<InfiniteScroll
dataLength={products?.length}
next={onFetchMore}
hasMore={hasMore}
scrollThreshold="100px"
loader={
<div className={scrollableDialogClasses.loadMoreLoaderContainer}>
<CircularProgress size={16} />
</div>
}
scrollableTarget={scrollableTargetId}
> >
<InfiniteScroll <ResponsiveTable key="table">
pageStart={0} <TableBody>
loadMore={onFetchMore} {products &&
hasMore={hasMore} products.map(product => {
useWindow={false} const isSelected = selectedProducts.some(
loader={ selectedProduct => selectedProduct.id === product.id
<div className={scrollableDialogClasses.loadMoreLoaderContainer}> );
<CircularProgress size={16} />
</div>
}
threshold={10}
>
<ResponsiveTable key="table">
<TableBody>
{products &&
products.map(product => {
const isSelected = selectedProducts.some(
selectedProduct => selectedProduct.id === product.id
);
return ( return (
<TableRow <TableRow
key={product.id} key={product.id}
data-test-id="assign-product-table-row" data-test-id="assign-product-table-row"
>
<TableCellAvatar
className={classes.avatar}
thumbnail={maybe(() => product.thumbnail.url)}
/>
<TableCell className={classes.colName}>
{product.name}
</TableCell>
<TableCell
padding="checkbox"
className={classes.checkboxCell}
> >
<TableCellAvatar <Checkbox
className={classes.avatar} checked={isSelected}
thumbnail={maybe(() => product.thumbnail.url)} onChange={() =>
handleProductAssign(
product,
isSelected,
selectedProducts,
setSelectedProducts
)
}
/> />
<TableCell className={classes.colName}> </TableCell>
{product.name} </TableRow>
</TableCell> );
<TableCell })}
padding="checkbox" </TableBody>
className={classes.checkboxCell} </ResponsiveTable>
> </InfiniteScroll>
<Checkbox
checked={isSelected}
onChange={() =>
handleProductAssign(
product,
isSelected,
selectedProducts,
setSelectedProducts
)
}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</ResponsiveTable>
</InfiniteScroll>
</div>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose}> <Button onClick={onClose}>

View file

@ -43,7 +43,6 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
hasMore, hasMore,
initialColumns, initialColumns,
initialOpen = false, initialOpen = false,
loading,
total, total,
onFetchMore, onFetchMore,
onSave onSave
@ -100,7 +99,6 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
<ColumnPickerContent <ColumnPickerContent
columns={columns} columns={columns}
hasMore={hasMore} hasMore={hasMore}
loading={loading}
selectedColumns={selectedColumns} selectedColumns={selectedColumns}
total={total} total={total}
onCancel={handleCancel} onCancel={handleCancel}

View file

@ -12,7 +12,7 @@ import { FetchMoreProps } from "@saleor/types";
import { isSelected } from "@saleor/utils/lists"; import { isSelected } from "@saleor/utils/lists";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import InfiniteScroll from "react-infinite-scroller"; import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import ControlledCheckbox from "../ControlledCheckbox"; import ControlledCheckbox from "../ControlledCheckbox";
@ -52,12 +52,12 @@ const useStyles = makeStyles(
display: "grid", display: "grid",
gridColumnGap: theme.spacing(3), gridColumnGap: theme.spacing(3),
gridTemplateColumns: "repeat(3, 1fr)", gridTemplateColumns: "repeat(3, 1fr)",
maxHeight: 256,
overflowX: "visible",
overflowY: "scroll",
padding: theme.spacing(2, 3) padding: theme.spacing(2, 3)
}, },
contentContainer: { contentContainer: {
maxHeight: 256,
overflowX: "visible",
overflowY: "scroll",
padding: 0 padding: 0
}, },
dropShadow: { dropShadow: {
@ -80,11 +80,12 @@ const useStyles = makeStyles(
{ name: "ColumnPickerContent" } { name: "ColumnPickerContent" }
); );
const scrollableTargetId = "columnPickerScrollableDiv";
const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => { const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
const { const {
columns, columns,
hasMore, hasMore,
loading,
selectedColumns, selectedColumns,
total, total,
onCancel, onCancel,
@ -118,41 +119,24 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
</Typography> </Typography>
</CardContent> </CardContent>
<Hr /> <Hr />
{hasMore && onFetchMore ? ( <CardContent
className={classes.contentContainer}
ref={anchor}
id={scrollableTargetId}
>
<InfiniteScroll <InfiniteScroll
pageStart={0} dataLength={columns.length}
loadMore={onFetchMore} next={onFetchMore}
hasMore={hasMore} hasMore={hasMore}
useWindow={false} scrollThreshold="100px"
threshold={100} loader={
key="infinite-scroll" <div className={classes.loadMoreLoaderContainer}>
> <CircularProgress size={16} />
<CardContent className={classes.contentContainer}>
<div className={classes.content} ref={anchor}>
{columns.map(column => (
<ControlledCheckbox
checked={isSelected(
column.value,
selectedColumns,
(a, b) => a === b
)}
name={column.value}
label={column.label}
onChange={() => onColumnToggle(column.value)}
key={column.value}
/>
))}
{loading && (
<div className={classes.loadMoreLoaderContainer}>
<CircularProgress size={16} />
</div>
)}
</div> </div>
</CardContent> }
</InfiniteScroll> scrollableTarget={scrollableTargetId}
) : ( >
<CardContent className={classes.contentContainer}> <div className={classes.content}>
<div className={classes.content} ref={anchor}>
{columns.map(column => ( {columns.map(column => (
<ControlledCheckbox <ControlledCheckbox
checked={isSelected( checked={isSelected(
@ -167,8 +151,8 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
/> />
))} ))}
</div> </div>
</CardContent> </InfiniteScroll>
)} </CardContent>
<Hr /> <Hr />
<CardContent <CardContent
className={classNames(classes.actionBarContainer, { className={classNames(classes.actionBarContainer, {

View file

@ -29,7 +29,7 @@ import { makeStyles } from "@saleor/theme";
import { ChannelProps, FetchMoreProps } from "@saleor/types"; import { ChannelProps, FetchMoreProps } from "@saleor/types";
import getOrderErrorMessage from "@saleor/utils/errors/order"; import getOrderErrorMessage from "@saleor/utils/errors/order";
import React from "react"; import React from "react";
import InfiniteScroll from "react-infinite-scroller"; import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { import {
@ -164,6 +164,8 @@ const onVariantAdd = (
) )
: setVariants([...variants, variant]); : setVariants([...variants, variant]);
const scrollableTargetId = "orderProductAddScrollableDialog";
const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => { const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
const { const {
confirmButtonState, confirmButtonState,
@ -267,18 +269,18 @@ const OrderProductAddDialog: React.FC<OrderProductAddDialogProps> = props => {
}} }}
/> />
</DialogContent> </DialogContent>
<DialogContent className={classes.content}> <DialogContent className={classes.content} id={scrollableTargetId}>
<InfiniteScroll <InfiniteScroll
pageStart={0} dataLength={productChoicesWithValidVariants?.length}
loadMore={onFetchMore} next={onFetchMore}
hasMore={hasMore} hasMore={hasMore}
useWindow={false} scrollThreshold="100px"
loader={ loader={
<div className={classes.loadMoreLoaderContainer}> <div className={classes.loadMoreLoaderContainer}>
<CircularProgress size={16} /> <CircularProgress size={16} />
</div> </div>
} }
threshold={10} scrollableTarget={scrollableTargetId}
> >
<ResponsiveTable key="table"> <ResponsiveTable key="table">
<TableBody> <TableBody>

View file

@ -27,7 +27,7 @@ import { makeStyles } from "@saleor/theme";
import { DialogProps, FetchMoreProps, SearchPageProps } from "@saleor/types"; import { DialogProps, FetchMoreProps, SearchPageProps } from "@saleor/types";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import InfiniteScroll from "react-infinite-scroller"; import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -47,9 +47,10 @@ const useStyles = makeStyles(
width: 32 width: 32
}, },
avatarDefault: { avatarDefault: {
"& p": { "& div": {
color: "#fff", color: "#fff",
lineHeight: "47px" lineHeight: 2.8,
fontSize: "0.75rem"
}, },
background: theme.palette.primary.main, background: theme.palette.primary.main,
height: 32, height: 32,
@ -93,7 +94,11 @@ const useStyles = makeStyles(
scrollArea: { scrollArea: {
maxHeight: 400, maxHeight: 400,
overflowY: "scroll", overflowY: "scroll",
paddingTop: 0 paddingTop: 0,
paddingBottom: 0
},
table: {
marginBottom: theme.spacing(3)
}, },
statusText: { statusText: {
color: "#9E9D9D" color: "#9E9D9D"
@ -132,6 +137,8 @@ function handleStaffMemberAssign(
} }
} }
const scrollableTargetId = "assignMembersScrollableDialog";
const AssignMembersDialog: React.FC<AssignMembersDialogProps> = ({ const AssignMembersDialog: React.FC<AssignMembersDialogProps> = ({
confirmButtonState, confirmButtonState,
disabled, disabled,
@ -187,16 +194,23 @@ const AssignMembersDialog: React.FC<AssignMembersDialogProps> = ({
disabled={disabled} disabled={disabled}
/> />
</DialogContent> </DialogContent>
<DialogContent className={classes.scrollArea}> <DialogContent className={classes.scrollArea} id={scrollableTargetId}>
<InfiniteScroll <InfiniteScroll
pageStart={0} dataLength={staffMembers?.length}
loadMore={onFetchMore} next={onFetchMore}
hasMore={hasMore} hasMore={hasMore}
useWindow={false} scrollThreshold="100px"
threshold={100} loader={
key="infinite-scroll" <>
{staffMembers?.length > 0 && <CardSpacer />}
<div className={classes.loadMoreLoaderContainer}>
<CircularProgress size={24} />
</div>
</>
}
scrollableTarget={scrollableTargetId}
> >
<ResponsiveTable> <ResponsiveTable className={classes.table}>
<TableBody> <TableBody>
{staffMembers && {staffMembers &&
staffMembers.map(member => { staffMembers.map(member => {
@ -267,14 +281,6 @@ const AssignMembersDialog: React.FC<AssignMembersDialogProps> = ({
})} })}
</TableBody> </TableBody>
</ResponsiveTable> </ResponsiveTable>
{loading && (
<>
{staffMembers?.length > 0 && <CardSpacer />}
<div className={classes.loadMoreLoaderContainer}>
<CircularProgress size={24} />
</div>
</>
)}
</InfiniteScroll> </InfiniteScroll>
</DialogContent> </DialogContent>
<DialogActions <DialogActions

View file

@ -10,10 +10,8 @@ import PageHeader from "@saleor/components/PageHeader";
import { RefreshLimits_shop_limits } from "@saleor/components/Shop/types/RefreshLimits"; import { RefreshLimits_shop_limits } from "@saleor/components/Shop/types/RefreshLimits";
import { ProductListColumns } from "@saleor/config"; import { ProductListColumns } from "@saleor/config";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { import { AvailableInGridAttributes_availableInGrid_edges_node } from "@saleor/products/types/AvailableInGridAttributes";
GridAttributes_availableInGrid_edges_node, import { GridAttributes_grid_edges_node } from "@saleor/products/types/GridAttributes";
GridAttributes_grid_edges_node
} from "@saleor/products/types/GridAttributes";
import { ProductList_products_edges_node } from "@saleor/products/types/ProductList"; import { ProductList_products_edges_node } from "@saleor/products/types/ProductList";
import { makeStyles } from "@saleor/theme"; import { makeStyles } from "@saleor/theme";
import { import {
@ -44,7 +42,7 @@ export interface ProductListPageProps
SortPage<ProductListUrlSortField>, SortPage<ProductListUrlSortField>,
ChannelProps { ChannelProps {
activeAttributeSortId: string; activeAttributeSortId: string;
availableInGridAttributes: GridAttributes_availableInGrid_edges_node[]; availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
channelsCount: number; channelsCount: number;
currencySymbol: string; currencySymbol: string;
gridAttributes: GridAttributes_grid_edges_node[]; gridAttributes: GridAttributes_grid_edges_node[];
@ -163,7 +161,6 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
columns={columns} columns={columns}
defaultColumns={defaultSettings.columns} defaultColumns={defaultSettings.columns}
hasMore={hasMore} hasMore={hasMore}
loading={loading}
initialColumns={settings.columns} initialColumns={settings.columns}
total={ total={
columns.length - columns.length -

View file

@ -19,6 +19,10 @@ import {
} from "@saleor/products/types/ProductMediaById"; } from "@saleor/products/types/ProductMediaById";
import gql from "graphql-tag"; import gql from "graphql-tag";
import {
AvailableInGridAttributes,
AvailableInGridAttributesVariables
} from "./types/AvailableInGridAttributes";
import { import {
CreateMultipleVariantsData, CreateMultipleVariantsData,
CreateMultipleVariantsDataVariables CreateMultipleVariantsDataVariables
@ -362,7 +366,7 @@ export const useProductMediaQuery = makeQuery<
const availableInGridAttributes = gql` const availableInGridAttributes = gql`
${pageInfoFragment} ${pageInfoFragment}
query GridAttributes($first: Int!, $after: String, $ids: [ID!]!) { query AvailableInGridAttributes($first: Int!, $after: String) {
availableInGrid: attributes( availableInGrid: attributes(
first: $first first: $first
after: $after after: $after
@ -383,7 +387,15 @@ const availableInGridAttributes = gql`
} }
totalCount totalCount
} }
}
`;
export const useAvailableInGridAttributesQuery = makeQuery<
AvailableInGridAttributes,
AvailableInGridAttributesVariables
>(availableInGridAttributes);
const gridAttributes = gql`
query GridAttributes($ids: [ID!]!) {
grid: attributes(first: 25, filter: { ids: $ids }) { grid: attributes(first: 25, filter: { ids: $ids }) {
edges { edges {
node { node {
@ -394,10 +406,10 @@ const availableInGridAttributes = gql`
} }
} }
`; `;
export const useAvailableInGridAttributesQuery = makeQuery< export const useGridAttributesQuery = makeQuery<
GridAttributes, GridAttributes,
GridAttributesVariables GridAttributesVariables
>(availableInGridAttributes); >(gridAttributes);
const createMultipleVariantsData = gql` const createMultipleVariantsData = gql`
${productVariantAttributesFragment} ${productVariantAttributesFragment}

View file

@ -0,0 +1,43 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: AvailableInGridAttributes
// ====================================================
export interface AvailableInGridAttributes_availableInGrid_edges_node {
__typename: "Attribute";
id: string;
name: string | null;
}
export interface AvailableInGridAttributes_availableInGrid_edges {
__typename: "AttributeCountableEdge";
node: AvailableInGridAttributes_availableInGrid_edges_node;
}
export interface AvailableInGridAttributes_availableInGrid_pageInfo {
__typename: "PageInfo";
endCursor: string | null;
hasNextPage: boolean;
hasPreviousPage: boolean;
startCursor: string | null;
}
export interface AvailableInGridAttributes_availableInGrid {
__typename: "AttributeCountableConnection";
edges: AvailableInGridAttributes_availableInGrid_edges[];
pageInfo: AvailableInGridAttributes_availableInGrid_pageInfo;
totalCount: number | null;
}
export interface AvailableInGridAttributes {
availableInGrid: AvailableInGridAttributes_availableInGrid | null;
}
export interface AvailableInGridAttributesVariables {
first: number;
after?: string | null;
}

View file

@ -7,32 +7,6 @@
// GraphQL query operation: GridAttributes // GraphQL query operation: GridAttributes
// ==================================================== // ====================================================
export interface GridAttributes_availableInGrid_edges_node {
__typename: "Attribute";
id: string;
name: string | null;
}
export interface GridAttributes_availableInGrid_edges {
__typename: "AttributeCountableEdge";
node: GridAttributes_availableInGrid_edges_node;
}
export interface GridAttributes_availableInGrid_pageInfo {
__typename: "PageInfo";
endCursor: string | null;
hasNextPage: boolean;
hasPreviousPage: boolean;
startCursor: string | null;
}
export interface GridAttributes_availableInGrid {
__typename: "AttributeCountableConnection";
edges: GridAttributes_availableInGrid_edges[];
pageInfo: GridAttributes_availableInGrid_pageInfo;
totalCount: number | null;
}
export interface GridAttributes_grid_edges_node { export interface GridAttributes_grid_edges_node {
__typename: "Attribute"; __typename: "Attribute";
id: string; id: string;
@ -50,12 +24,9 @@ export interface GridAttributes_grid {
} }
export interface GridAttributes { export interface GridAttributes {
availableInGrid: GridAttributes_availableInGrid | null;
grid: GridAttributes_grid | null; grid: GridAttributes_grid | null;
} }
export interface GridAttributesVariables { export interface GridAttributesVariables {
first: number;
after?: string | null;
ids: string[]; ids: string[];
} }

View file

@ -31,6 +31,7 @@ import {
} from "@saleor/products/components/ProductListPage/utils"; } from "@saleor/products/components/ProductListPage/utils";
import { import {
useAvailableInGridAttributesQuery, useAvailableInGridAttributesQuery,
useGridAttributesQuery,
useInitialProductFilterAttributesQuery, useInitialProductFilterAttributesQuery,
useInitialProductFilterCategoriesQuery, useInitialProductFilterCategoriesQuery,
useInitialProductFilterCollectionsQuery, useInitialProductFilterCollectionsQuery,
@ -319,8 +320,11 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
.filter(isAttributeColumnValue) .filter(isAttributeColumnValue)
.map(getAttributeIdFromColumnValue); .map(getAttributeIdFromColumnValue);
} }
const attributes = useAvailableInGridAttributesQuery({ const availableInGridAttributes = useAvailableInGridAttributesQuery({
variables: { first: 6, ids: filterColumnIds(settings.columns) } variables: { first: 24 }
});
const gridAttributes = useGridAttributesQuery({
variables: { ids: filterColumnIds(settings.columns) }
}); });
const [ const [
@ -376,21 +380,22 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
}} }}
onSort={handleSort} onSort={handleSort}
availableInGridAttributes={mapEdgesToItems( availableInGridAttributes={mapEdgesToItems(
attributes?.data?.availableInGrid availableInGridAttributes?.data?.availableInGrid
)} )}
currencySymbol={selectedChannel?.currencyCode || ""} currencySymbol={selectedChannel?.currencyCode || ""}
currentTab={currentTab} currentTab={currentTab}
defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]} defaultSettings={defaultListSettings[ListViews.PRODUCT_LIST]}
filterOpts={filterOpts} filterOpts={filterOpts}
gridAttributes={mapEdgesToItems(attributes?.data?.grid)} gridAttributes={mapEdgesToItems(gridAttributes?.data?.grid)}
totalGridAttributes={maybe( totalGridAttributes={maybe(
() => attributes.data.availableInGrid.totalCount, () => availableInGridAttributes.data.availableInGrid.totalCount,
0 0
)} )}
settings={settings} settings={settings}
loading={attributes.loading} loading={availableInGridAttributes.loading || gridAttributes.loading}
hasMore={maybe( hasMore={maybe(
() => attributes.data.availableInGrid.pageInfo.hasNextPage, () =>
availableInGridAttributes.data.availableInGrid.pageInfo.hasNextPage,
false false
)} )}
onAdd={() => navigate(productAddUrl())} onAdd={() => navigate(productAddUrl())}
@ -398,7 +403,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
limits={limitOpts.data?.shop.limits} limits={limitOpts.data?.shop.limits}
products={mapEdgesToItems(data?.products)} products={mapEdgesToItems(data?.products)}
onFetchMore={() => onFetchMore={() =>
attributes.loadMore( availableInGridAttributes.loadMore(
(prev, next) => { (prev, next) => {
if ( if (
prev.availableInGrid.pageInfo.endCursor === prev.availableInGrid.pageInfo.endCursor ===
@ -419,7 +424,9 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
}; };
}, },
{ {
after: attributes.data.availableInGrid.pageInfo.endCursor after:
availableInGridAttributes.data.availableInGrid.pageInfo
.endCursor
} }
) )
} }

View file

@ -26,7 +26,7 @@ import { makeStyles } from "@saleor/theme";
import { FetchMoreProps } from "@saleor/types"; import { FetchMoreProps } from "@saleor/types";
import React from "react"; import React from "react";
import { MutationFetchResult } from "react-apollo"; import { MutationFetchResult } from "react-apollo";
import InfiniteScroll from "react-infinite-scroller"; import InfiniteScroll from "react-infinite-scroll-component";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -89,6 +89,8 @@ const handleProductAssign = (
} }
}; };
const scrollableTargetId = "shippingMethodProductsAddScrollableDialog";
const ShippingMethodProductsAddDialog: React.FC<ShippingMethodProductsAddDialogProps> = props => { const ShippingMethodProductsAddDialog: React.FC<ShippingMethodProductsAddDialogProps> = props => {
const { const {
confirmButtonState, confirmButtonState,
@ -154,18 +156,18 @@ const ShippingMethodProductsAddDialog: React.FC<ShippingMethodProductsAddDialogP
}} }}
/> />
</DialogContent> </DialogContent>
<DialogContent className={classes.content}> <DialogContent className={classes.content} id={scrollableTargetId}>
<InfiniteScroll <InfiniteScroll
pageStart={0} dataLength={products?.length}
loadMore={onFetchMore} next={onFetchMore}
hasMore={hasMore} hasMore={hasMore}
useWindow={false} scrollThreshold="100px"
loader={ loader={
<div key="loader" className={classes.loadMoreLoaderContainer}> <div key="loader" className={classes.loadMoreLoaderContainer}>
<CircularProgress size={16} /> <CircularProgress size={16} />
</div> </div>
} }
threshold={10} scrollableTarget={scrollableTargetId}
> >
<ResponsiveTable key="table"> <ResponsiveTable key="table">
<TableBody> <TableBody>

View file

@ -42,10 +42,5 @@ storiesOf("Generics / Column picker", module)
.addDecorator(Decorator) .addDecorator(Decorator)
.add("default", () => <ColumnPicker {...props} />) .add("default", () => <ColumnPicker {...props} />)
.add("loading", () => ( .add("loading", () => (
<ColumnPicker <ColumnPicker {...props} hasMore={true} onFetchMore={() => undefined} />
{...props}
loading={true}
hasMore={true}
onFetchMore={() => undefined}
/>
)); ));

View file

@ -2,9 +2,6 @@ import { makeStyles } from "@saleor/theme";
const useScrollableDialogStyle = makeStyles( const useScrollableDialogStyle = makeStyles(
theme => ({ theme => ({
content: {
overflowY: "hidden"
},
dialog: { dialog: {
height: "calc(100% - 64px)", height: "calc(100% - 64px)",
maxHeight: 700 maxHeight: 700
@ -16,8 +13,13 @@ const useScrollableDialogStyle = makeStyles(
justifyContent: "center", justifyContent: "center",
marginTop: theme.spacing(3) marginTop: theme.spacing(3)
}, },
topArea: {
overflowY: "visible"
},
scrollArea: { scrollArea: {
overflowY: "scroll" overflowY: "scroll",
paddingTop: 0,
height: "inherit"
} }
}), }),
{ {