Merge pull request #136 from mirumee/add/attribute-columns
Add attributes to column picker
This commit is contained in:
commit
94eeca32f2
35 changed files with 4404 additions and 2983 deletions
|
@ -12,3 +12,4 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Use react-intl - #105 by @dominik-zeglen
|
||||
- Add dynamic dashboard settings - #135 by @benekex2
|
||||
- Fix plugins page translations - #141 by @benekex2
|
||||
- Add attributes to column picker - #136 by @dominik-zeglen
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Money from "@saleor/components/Money";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import React from "react";
|
||||
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colPrice: {
|
||||
width: 200
|
||||
},
|
||||
colPublished: {
|
||||
width: 200
|
||||
},
|
||||
colType: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
colFill: {
|
||||
padding: 0,
|
||||
width: "100%"
|
||||
},
|
||||
colName: {},
|
||||
colNameHeader: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPrice: {
|
||||
textAlign: "right"
|
||||
},
|
||||
colPublished: {},
|
||||
colType: {},
|
||||
link: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
tableContainer: {
|
||||
overflowX: "scroll"
|
||||
},
|
||||
textLeft: {
|
||||
textAlign: "left"
|
||||
},
|
||||
textRight: {
|
||||
textAlign: "right"
|
||||
}
|
||||
});
|
||||
|
||||
interface CategoryProductListProps
|
||||
extends ListProps,
|
||||
ListActions,
|
||||
WithStyles<typeof styles> {
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
}
|
||||
|
||||
export const CategoryProductList = withStyles(styles, {
|
||||
name: "CategoryProductList"
|
||||
})(
|
||||
({
|
||||
classes,
|
||||
disabled,
|
||||
isChecked,
|
||||
pageInfo,
|
||||
products,
|
||||
selected,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick
|
||||
}: CategoryProductListProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const numberOfColumns = 5;
|
||||
|
||||
return (
|
||||
<div className={classes.tableContainer}>
|
||||
<Table className={classes.table}>
|
||||
<colgroup>
|
||||
<col />
|
||||
<col className={classes.colName} />
|
||||
<col className={classes.colType} />
|
||||
<col className={classes.colPublished} />
|
||||
<col className={classes.colPrice} />
|
||||
</colgroup>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={products}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<span className={classes.colNameHeader}>
|
||||
<FormattedMessage defaultMessage="Name" description="product" />
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colType}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Type"
|
||||
description="product type"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Published"
|
||||
description="product status"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="product price"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
hasNextPage={
|
||||
pageInfo && !disabled ? pageInfo.hasNextPage : false
|
||||
}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
products,
|
||||
product => {
|
||||
const isSelected = product ? isChecked(product.id) : false;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
selected={isSelected}
|
||||
hover={!!product}
|
||||
key={product ? product.id : "skeleton"}
|
||||
onClick={product && onRowClick(product.id)}
|
||||
className={classes.link}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
>
|
||||
{product ? product.name : <Skeleton />}
|
||||
</TableCellAvatar>
|
||||
<TableCell className={classes.colType}>
|
||||
{product && product.productType ? (
|
||||
product.productType.name
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPublished}>
|
||||
{product &&
|
||||
maybe(() => product.isAvailable !== undefined) ? (
|
||||
<StatusLabel
|
||||
label={
|
||||
product.isAvailable
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "product",
|
||||
id: "productStatusLabel"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "product"
|
||||
})
|
||||
}
|
||||
status={product.isAvailable ? "success" : "error"}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className={classes.colPrice}>
|
||||
{maybe(() => product.basePrice) &&
|
||||
maybe(() => product.basePrice.amount) !== undefined &&
|
||||
maybe(() => product.basePrice.currency) !== undefined ? (
|
||||
<Money money={product.basePrice} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
<FormattedMessage defaultMessage="No products found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
CategoryProductList.displayName = "CategoryProductList";
|
||||
export default CategoryProductList;
|
2
src/categories/components/CategoryProductList/index.ts
Normal file
2
src/categories/components/CategoryProductList/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export { default } from "./CategoryProductList";
|
||||
export * from "./CategoryProductList";
|
|
@ -1,156 +1,72 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
|
||||
import CategoryProductList from "../CategoryProductList";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
link: {
|
||||
color: theme.palette.primary.main,
|
||||
cursor: "pointer"
|
||||
},
|
||||
textLeft: {
|
||||
textAlign: "left"
|
||||
}
|
||||
});
|
||||
|
||||
interface ProductListProps extends WithStyles<typeof styles> {
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
products?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
productType: {
|
||||
name: string;
|
||||
};
|
||||
thumbnail: {
|
||||
url: string;
|
||||
};
|
||||
}>;
|
||||
onAddProduct?();
|
||||
onNextPage?();
|
||||
onPreviousPage?();
|
||||
onRowClick?(id: string): () => void;
|
||||
interface CategoryProductsProps extends PageListProps, ListActions {
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
categoryName: string;
|
||||
}
|
||||
|
||||
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||
({
|
||||
classes,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
products,
|
||||
onAddProduct,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick
|
||||
}: ProductListProps) => {
|
||||
const intl = useIntl();
|
||||
export const CategoryProducts: React.StatelessComponent<
|
||||
CategoryProductsProps
|
||||
> = ({
|
||||
products,
|
||||
disabled,
|
||||
pageInfo,
|
||||
onAdd,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick,
|
||||
categoryName,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Products",
|
||||
description: "section header"
|
||||
})}
|
||||
toolbar={
|
||||
<Button variant="text" color="primary" onClick={onAddProduct}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add product"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{(products === undefined || products.length > 0) && <TableCell />}
|
||||
<TableCell className={classes.textLeft}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Name"
|
||||
description="product name"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage
|
||||
defaultMessage="Type"
|
||||
description="product type"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={3}
|
||||
hasNextPage={hasNextPage}
|
||||
onNextPage={onNextPage}
|
||||
hasPreviousPage={hasPreviousPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
products,
|
||||
product => (
|
||||
<TableRow key={product ? product.id : "skeleton"}>
|
||||
<TableCellAvatar
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
/>
|
||||
<TableCell className={classes.textLeft}>
|
||||
{product ? (
|
||||
<span
|
||||
onClick={onRowClick && onRowClick(product.id)}
|
||||
className={classes.link}
|
||||
>
|
||||
{product.name}
|
||||
</span>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{product && product.productType ? (
|
||||
product.productType.name
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
),
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3}>
|
||||
<FormattedMessage defaultMessage="No products found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
);
|
||||
ProductList.displayName = "CategoryProductList";
|
||||
export default ProductList;
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Products in {categoryName}",
|
||||
description: "header"
|
||||
},
|
||||
{ categoryName }
|
||||
)}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add product"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<CategoryProductList
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
pageInfo={pageInfo}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onRowClick}
|
||||
selected={selected}
|
||||
isChecked={isChecked}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
CategoryProducts.displayName = "CategoryProducts";
|
||||
export default CategoryProducts;
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import CardTitle from "@saleor/components/CardTitle";
|
||||
import ProductList from "@saleor/components/ProductList";
|
||||
import { ListActions, PageListProps } from "../../../types";
|
||||
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
|
||||
|
||||
interface CategoryProductsCardProps extends PageListProps, ListActions {
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
categoryName: string;
|
||||
}
|
||||
|
||||
export const CategoryProductsCard: React.StatelessComponent<
|
||||
CategoryProductsCardProps
|
||||
> = ({
|
||||
products,
|
||||
disabled,
|
||||
pageInfo,
|
||||
onAdd,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onRowClick,
|
||||
categoryName,
|
||||
isChecked,
|
||||
selected,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardTitle
|
||||
title={intl.formatMessage(
|
||||
{
|
||||
defaultMessage: "Products in {categoryName}",
|
||||
description: "section header"
|
||||
},
|
||||
{
|
||||
categoryName
|
||||
}
|
||||
)}
|
||||
toolbar={
|
||||
<Button color="primary" variant="text" onClick={onAdd}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Add product"
|
||||
description="button"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ProductList
|
||||
settings={{
|
||||
columns: ["isPublished", "price", "productType"],
|
||||
rowNumber: undefined
|
||||
}}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
pageInfo={pageInfo}
|
||||
onNextPage={onNextPage}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onRowClick={onRowClick}
|
||||
selected={selected}
|
||||
isChecked={isChecked}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
CategoryProductsCard.displayName = "CategoryProductsCard";
|
||||
export default CategoryProductsCard;
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from "./CategoryProductsCard";
|
||||
export * from "./CategoryProductsCard";
|
|
@ -22,7 +22,7 @@ import {
|
|||
CategoryDetails_category_products_edges_node
|
||||
} from "../../types/CategoryDetails";
|
||||
import CategoryBackground from "../CategoryBackground";
|
||||
import CategoryProductsCard from "../CategoryProductsCard";
|
||||
import CategoryProducts from "../CategoryProducts";
|
||||
|
||||
export interface FormData {
|
||||
backgroundImageAlt: string;
|
||||
|
@ -195,7 +195,7 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
|||
/>
|
||||
)}
|
||||
{currentTab === CategoryPageTab.products && (
|
||||
<CategoryProductsCard
|
||||
<CategoryProducts
|
||||
categoryName={maybe(() => category.name)}
|
||||
products={products}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -204,6 +204,7 @@ const styles = (theme: Theme) =>
|
|||
menuSmall: {
|
||||
background: theme.palette.background.paper,
|
||||
height: "100vh",
|
||||
overflow: "hidden",
|
||||
padding: 25
|
||||
},
|
||||
popover: {
|
||||
|
|
|
@ -6,14 +6,23 @@ import { fade } from "@material-ui/core/styles/colorManipulator";
|
|||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import React from "react";
|
||||
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { toggle } from "@saleor/utils/lists";
|
||||
import ColumnPickerButton from "./ColumnPickerButton";
|
||||
import ColumnPickerContent, {
|
||||
ColumnPickerContentProps
|
||||
} from "./ColumnPickerContent";
|
||||
|
||||
export interface ColumnPickerProps extends ColumnPickerContentProps {
|
||||
export interface ColumnPickerProps
|
||||
extends Omit<
|
||||
ColumnPickerContentProps,
|
||||
"selectedColumns" | "onCancel" | "onColumnToggle" | "onReset" | "onSave"
|
||||
> {
|
||||
className?: string;
|
||||
initial?: boolean;
|
||||
defaultColumns: string[];
|
||||
initialColumns: string[];
|
||||
initialOpen?: boolean;
|
||||
onSave: (columns: string[]) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -33,29 +42,39 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
|||
const {
|
||||
className,
|
||||
columns,
|
||||
initial = false,
|
||||
selectedColumns,
|
||||
onCancel,
|
||||
onColumnToggle,
|
||||
onReset,
|
||||
defaultColumns,
|
||||
hasMore,
|
||||
initialColumns,
|
||||
initialOpen = false,
|
||||
loading,
|
||||
total,
|
||||
onFetchMore,
|
||||
onSave
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
const [isExpanded, setExpansionState] = React.useState(false);
|
||||
const [selectedColumns, setSelectedColumns] = useStateFromProps(
|
||||
initialColumns
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => setExpansionState(initial), 100);
|
||||
setTimeout(() => setExpansionState(initialOpen), 100);
|
||||
}, []);
|
||||
|
||||
const handleCancel = React.useCallback(() => {
|
||||
setExpansionState(false);
|
||||
onCancel();
|
||||
}, []);
|
||||
setSelectedColumns(columns.map(column => column.value));
|
||||
}, [columns]);
|
||||
|
||||
const handleColumnToggle = (column: string) =>
|
||||
setSelectedColumns(toggle(column, selectedColumns, (a, b) => a === b));
|
||||
|
||||
const handleReset = () => setSelectedColumns(defaultColumns);
|
||||
|
||||
const handleSave = () => {
|
||||
setExpansionState(false);
|
||||
onSave();
|
||||
onSave(selectedColumns);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -86,10 +105,14 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
|||
>
|
||||
<ColumnPickerContent
|
||||
columns={columns}
|
||||
hasMore={hasMore}
|
||||
loading={loading}
|
||||
selectedColumns={selectedColumns}
|
||||
total={total}
|
||||
onCancel={handleCancel}
|
||||
onColumnToggle={onColumnToggle}
|
||||
onReset={onReset}
|
||||
onColumnToggle={handleColumnToggle}
|
||||
onFetchMore={onFetchMore}
|
||||
onReset={handleReset}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</ClickAwayListener>
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import Button from "@material-ui/core/Button";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||
import { Theme } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import makeStyles from "@material-ui/styles/makeStyles";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import useElementScroll from "@saleor/hooks/useElementScroll";
|
||||
import { buttonMessages } from "@saleor/intl";
|
||||
import { FetchMoreProps } from "@saleor/types";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
import ControlledCheckbox from "../ControlledCheckbox";
|
||||
import Hr from "../Hr";
|
||||
|
@ -18,9 +21,10 @@ export interface ColumnPickerChoice {
|
|||
label: string;
|
||||
value: string;
|
||||
}
|
||||
export interface ColumnPickerContentProps {
|
||||
export interface ColumnPickerContentProps extends Partial<FetchMoreProps> {
|
||||
columns: ColumnPickerChoice[];
|
||||
selectedColumns: string[];
|
||||
total?: number;
|
||||
onCancel: () => void;
|
||||
onColumnToggle: (column: string) => void;
|
||||
onReset: () => void;
|
||||
|
@ -50,15 +54,29 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
},
|
||||
dropShadow: {
|
||||
boxShadow: `0px -5px 10px 0px ${theme.overrides.MuiCard.root.borderColor}`
|
||||
},
|
||||
loadMoreLoaderContainer: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
gridColumnEnd: "span 3",
|
||||
height: theme.spacing.unit * 3,
|
||||
justifyContent: "center"
|
||||
},
|
||||
root: {
|
||||
boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.25)"
|
||||
}
|
||||
}));
|
||||
|
||||
const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
||||
const {
|
||||
columns,
|
||||
hasMore,
|
||||
loading,
|
||||
selectedColumns,
|
||||
total,
|
||||
onCancel,
|
||||
onColumnToggle,
|
||||
onFetchMore,
|
||||
onReset,
|
||||
onSave
|
||||
} = props;
|
||||
|
@ -72,7 +90,7 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
|||
: false;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card className={classes.root}>
|
||||
<CardContent>
|
||||
<Typography color="textSecondary">
|
||||
<FormattedMessage
|
||||
|
@ -80,28 +98,61 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
|||
description="pick columns to display"
|
||||
values={{
|
||||
numberOfSelected: selectedColumns.length,
|
||||
numberOfTotal: columns.length
|
||||
numberOfTotal: total || columns.length
|
||||
}}
|
||||
/>
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<Hr />
|
||||
<CardContent className={classes.contentContainer}>
|
||||
<div className={classes.content} ref={anchor}>
|
||||
{columns.map(column => (
|
||||
<ControlledCheckbox
|
||||
checked={isSelected(
|
||||
column.value,
|
||||
selectedColumns,
|
||||
(a, b) => a === b
|
||||
{hasMore && onFetchMore ? (
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={onFetchMore}
|
||||
hasMore={hasMore}
|
||||
useWindow={false}
|
||||
threshold={100}
|
||||
key="infinite-scroll"
|
||||
>
|
||||
<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)}
|
||||
/>
|
||||
))}
|
||||
{loading && (
|
||||
<div className={classes.loadMoreLoaderContainer}>
|
||||
<CircularProgress size={16} />
|
||||
</div>
|
||||
)}
|
||||
name={column.value}
|
||||
label={column.label}
|
||||
onChange={() => onColumnToggle(column.value)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
</CardContent>
|
||||
</InfiniteScroll>
|
||||
) : (
|
||||
<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)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
)}
|
||||
<Hr />
|
||||
<CardContent
|
||||
className={classNames(classes.actionBarContainer, {
|
||||
|
|
|
@ -9,6 +9,7 @@ import TableBody from "@material-ui/core/TableBody";
|
|||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
|
@ -17,9 +18,14 @@ import TableCellAvatar, {
|
|||
} from "@saleor/components/TableCellAvatar";
|
||||
import { ProductListColumns } from "@saleor/config";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import {
|
||||
getAttributeIdFromColumnValue,
|
||||
isAttributeColumnValue
|
||||
} from "@saleor/products/components/ProductListPage/utils";
|
||||
import { AvailableInGridAttributes_grid_edges_node } from "@saleor/products/types/AvailableInGridAttributes";
|
||||
import { ProductList_products_edges_node } from "@saleor/products/types/ProductList";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import { isSelected } from "@saleor/utils/lists";
|
||||
import { CategoryDetails_category_products_edges_node } from "../../categories/types/CategoryDetails";
|
||||
import TDisplayColumn from "@saleor/utils/columns/DisplayColumn";
|
||||
import Checkbox from "../Checkbox";
|
||||
import Money from "../Money";
|
||||
import Skeleton from "../Skeleton";
|
||||
|
@ -43,11 +49,19 @@ const styles = (theme: Theme) =>
|
|||
width: 200
|
||||
}
|
||||
},
|
||||
colAttribute: {
|
||||
width: 150
|
||||
},
|
||||
colFill: {
|
||||
padding: 0,
|
||||
width: "100%"
|
||||
},
|
||||
colName: {},
|
||||
colName: {
|
||||
"&$colNameFixed": {
|
||||
width: 250
|
||||
}
|
||||
},
|
||||
colNameFixed: {},
|
||||
colNameHeader: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
|
@ -77,7 +91,8 @@ interface ProductListProps
|
|||
extends ListProps<ProductListColumns>,
|
||||
ListActions,
|
||||
WithStyles<typeof styles> {
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
||||
products: ProductList_products_edges_node[];
|
||||
}
|
||||
|
||||
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||
|
@ -86,6 +101,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
settings,
|
||||
disabled,
|
||||
isChecked,
|
||||
gridAttributes,
|
||||
pageInfo,
|
||||
products,
|
||||
selected,
|
||||
|
@ -98,23 +114,35 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
onRowClick
|
||||
}: ProductListProps) => {
|
||||
const intl = useIntl();
|
||||
const displayColumn = React.useCallback(
|
||||
(column: ProductListColumns) =>
|
||||
isSelected(column, settings.columns, (a, b) => a === b),
|
||||
[settings.columns]
|
||||
|
||||
const DisplayColumn: React.FC<{ column: ProductListColumns }> = props => (
|
||||
<TDisplayColumn displayColumns={settings.columns} {...props} />
|
||||
);
|
||||
|
||||
const gridAttributesFromSettings = settings.columns.filter(
|
||||
isAttributeColumnValue
|
||||
);
|
||||
const numberOfColumns = 2 + settings.columns.length;
|
||||
|
||||
return (
|
||||
<div className={classes.tableContainer}>
|
||||
<Table className={classes.table}>
|
||||
<col />
|
||||
<col className={classes.colName} />
|
||||
{displayColumn("productType") && <col className={classes.colType} />}
|
||||
{displayColumn("isPublished") && (
|
||||
<col className={classes.colPublished} />
|
||||
)}
|
||||
{displayColumn("price") && <col className={classes.colPrice} />}
|
||||
<colgroup>
|
||||
<col />
|
||||
<col className={classes.colName} />
|
||||
<DisplayColumn column="productType">
|
||||
<col className={classes.colType} />
|
||||
</DisplayColumn>
|
||||
<DisplayColumn column="isPublished">
|
||||
<col className={classes.colPublished} />
|
||||
</DisplayColumn>
|
||||
{gridAttributesFromSettings.map(gridAttribute => (
|
||||
<col className={classes.colAttribute} key={gridAttribute} />
|
||||
))}
|
||||
<DisplayColumn column="price">
|
||||
<col className={classes.colPrice} />
|
||||
</DisplayColumn>
|
||||
</colgroup>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
|
@ -123,32 +151,53 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell className={classes.colName}>
|
||||
<TableCell
|
||||
className={classNames(classes.colName, {
|
||||
[classes.colNameFixed]: settings.columns.length > 4
|
||||
})}
|
||||
>
|
||||
<span className={classes.colNameHeader}>
|
||||
<FormattedMessage defaultMessage="Name" description="product" />
|
||||
</span>
|
||||
</TableCell>
|
||||
{displayColumn("productType") && (
|
||||
<DisplayColumn column="productType">
|
||||
<TableCell className={classes.colType}>
|
||||
<FormattedMessage defaultMessage="Type" description="product" />
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("isPublished") && (
|
||||
</DisplayColumn>
|
||||
<DisplayColumn column="isPublished">
|
||||
<TableCell className={classes.colPublished}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Published"
|
||||
description="product status"
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("price") && (
|
||||
</DisplayColumn>
|
||||
{gridAttributesFromSettings.map(gridAttributeFromSettings => (
|
||||
<TableCell
|
||||
className={classes.colAttribute}
|
||||
key={gridAttributeFromSettings}
|
||||
>
|
||||
{maybe<React.ReactNode>(
|
||||
() =>
|
||||
gridAttributes.find(
|
||||
gridAttribute =>
|
||||
getAttributeIdFromColumnValue(
|
||||
gridAttributeFromSettings
|
||||
) === gridAttribute.id
|
||||
).name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
<DisplayColumn column="price">
|
||||
<TableCell className={classes.colPrice}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="product"
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
</DisplayColumn>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
|
@ -194,9 +243,9 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
>
|
||||
{product ? product.name : <Skeleton />}
|
||||
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||
</TableCellAvatar>
|
||||
{displayColumn("productType") && (
|
||||
<DisplayColumn column="productType">
|
||||
<TableCell className={classes.colType}>
|
||||
{product && product.productType ? (
|
||||
product.productType.name
|
||||
|
@ -204,8 +253,8 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("isPublished") && (
|
||||
</DisplayColumn>
|
||||
<DisplayColumn column="isPublished">
|
||||
<TableCell className={classes.colPublished}>
|
||||
{product &&
|
||||
maybe(() => product.isAvailable !== undefined) ? (
|
||||
|
@ -227,8 +276,28 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
{displayColumn("price") && (
|
||||
</DisplayColumn>
|
||||
{gridAttributesFromSettings.map(gridAttribute => (
|
||||
<TableCell
|
||||
className={classes.colAttribute}
|
||||
key={gridAttribute}
|
||||
>
|
||||
{maybe<React.ReactNode>(() => {
|
||||
const attribute = product.attributes.find(
|
||||
attribute =>
|
||||
attribute.attribute.id ===
|
||||
getAttributeIdFromColumnValue(gridAttribute)
|
||||
);
|
||||
if (attribute) {
|
||||
return attribute.values
|
||||
.map(value => value.name)
|
||||
.join(", ");
|
||||
}
|
||||
return "-";
|
||||
}, <Skeleton />)}
|
||||
</TableCell>
|
||||
))}
|
||||
<DisplayColumn column="price">
|
||||
<TableCell className={classes.colPrice}>
|
||||
{maybe(() => product.basePrice) &&
|
||||
maybe(() => product.basePrice.amount) !== undefined &&
|
||||
|
@ -239,7 +308,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
|||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</DisplayColumn>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -26,7 +26,8 @@ const styles = (theme: Theme) =>
|
|||
},
|
||||
children: {
|
||||
alignSelf: "center",
|
||||
marginLeft: theme.spacing.unit * 2
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
width: "100%"
|
||||
},
|
||||
content: {
|
||||
alignItems: "center",
|
||||
|
@ -69,7 +70,7 @@ const TableCellAvatar = withStyles(styles, { name: "TableCellAvatar" })(
|
|||
src={thumbnail}
|
||||
/>
|
||||
)}
|
||||
<span className={classes.children}>{children}</span>
|
||||
<div className={classes.children}>{children}</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
)
|
||||
|
|
|
@ -101,26 +101,25 @@ const TableHead = withStyles(styles, {
|
|||
})}
|
||||
/>
|
||||
)}
|
||||
{(items === undefined || items.length > 0) &&
|
||||
(selected && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
{(items === undefined || items.length > 0) && (
|
||||
<TableCell
|
||||
padding="checkbox"
|
||||
className={classNames({
|
||||
[classes.checkboxSelected]: selected,
|
||||
[classes.dragRows]: dragRows
|
||||
})}
|
||||
>
|
||||
<Checkbox
|
||||
className={classNames({
|
||||
[classes.checkboxSelected]: selected,
|
||||
[classes.dragRows]: dragRows
|
||||
[classes.checkboxPartialSelect]:
|
||||
items && items.length > selected && selected > 0
|
||||
})}
|
||||
>
|
||||
<Checkbox
|
||||
className={classNames({
|
||||
[classes.checkboxPartialSelect]:
|
||||
items && items.length > selected && selected > 0
|
||||
})}
|
||||
checked={selected === 0 ? false : true}
|
||||
disabled={disabled}
|
||||
onChange={() => toggleAll(items, selected)}
|
||||
/>
|
||||
</TableCell>
|
||||
))}
|
||||
checked={selected === 0 ? false : true}
|
||||
disabled={disabled}
|
||||
onChange={() => toggleAll(items, selected)}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{selected ? (
|
||||
<>
|
||||
<TableCell
|
||||
|
|
|
@ -134,6 +134,5 @@ export const filters: Filter[] = [
|
|||
export const fetchMoreProps: FetchMoreProps = {
|
||||
hasMore: true,
|
||||
loading: false,
|
||||
onFetch: () => undefined,
|
||||
onFetchMore: () => undefined
|
||||
};
|
||||
|
|
|
@ -5,8 +5,8 @@ export const Plugins = createSvgIcon(
|
|||
<>
|
||||
<g>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.12891 6.10352e-05H12.9999V7.80006H18.7418V6.10352e-05H24.6128V7.80006H31.9999V14.6751H29.0967V29.7876H20.7418V33.6876H16.8708V40.0001H14.8708V33.6876H10.9999V29.7876H2.9031V14.6751H-0.00012207V7.80006H7.12891V6.10352e-05ZM9.12891 7.80006H10.9999V2.00006H9.12891V7.80006ZM4.9031 14.6751V27.7876H27.0967V14.6751H4.9031ZM12.9999 29.7876V31.6876H18.7418V29.7876H12.9999ZM22.6128 7.80006V2.00006H20.7418V7.80006H22.6128ZM1.99988 9.80006V12.6751H29.9999V9.80006H1.99988Z"
|
||||
fill="#06847B"
|
||||
/>
|
||||
|
|
|
@ -81,6 +81,7 @@ interface OrderProductAddDialogProps extends FetchMoreProps {
|
|||
open: boolean;
|
||||
products: SearchOrderVariant_products_edges_node[];
|
||||
onClose: () => void;
|
||||
onFetch: (query: string) => void;
|
||||
onSubmit: (data: SearchOrderVariant_products_edges_node_variants[]) => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
|
|||
import useNavigator from "@saleor/hooks/useNavigator";
|
||||
import useNotifier from "@saleor/hooks/useNotifier";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { useIntl } from "react-intl";
|
||||
|
||||
import { getMutationState, maybe } from "../../misc";
|
||||
import PluginsDetailsPage from "../components/PluginsDetailsPage";
|
||||
|
|
|
@ -54,6 +54,7 @@ export interface AssignAttributeDialogProps extends FetchMoreProps {
|
|||
attributes: SearchAttributes_productType_availableAttributes_edges_node[];
|
||||
selected: string[];
|
||||
onClose: () => void;
|
||||
onFetch: (query: string) => void;
|
||||
onOpen: () => void;
|
||||
onSubmit: () => void;
|
||||
onToggle: (id: string) => void;
|
||||
|
|
332
src/products/components/ProductList/ProductList.tsx
Normal file
332
src/products/components/ProductList/ProductList.tsx
Normal file
|
@ -0,0 +1,332 @@
|
|||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableFooter from "@material-ui/core/TableFooter";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import Checkbox from "@saleor/components/Checkbox";
|
||||
import Money from "@saleor/components/Money";
|
||||
import Skeleton from "@saleor/components/Skeleton";
|
||||
import StatusLabel from "@saleor/components/StatusLabel";
|
||||
import TableCellAvatar, {
|
||||
AVATAR_MARGIN
|
||||
} from "@saleor/components/TableCellAvatar";
|
||||
import TableHead from "@saleor/components/TableHead";
|
||||
import TablePagination from "@saleor/components/TablePagination";
|
||||
import { ProductListColumns } from "@saleor/config";
|
||||
import { maybe, renderCollection } from "@saleor/misc";
|
||||
import {
|
||||
getAttributeIdFromColumnValue,
|
||||
isAttributeColumnValue
|
||||
} from "@saleor/products/components/ProductListPage/utils";
|
||||
import { AvailableInGridAttributes_grid_edges_node } from "@saleor/products/types/AvailableInGridAttributes";
|
||||
import { ProductList_products_edges_node } from "@saleor/products/types/ProductList";
|
||||
import { ListActions, ListProps } from "@saleor/types";
|
||||
import TDisplayColumn from "@saleor/utils/columns/DisplayColumn";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
[theme.breakpoints.up("lg")]: {
|
||||
colName: {
|
||||
width: "auto"
|
||||
},
|
||||
colPrice: {
|
||||
width: 200
|
||||
},
|
||||
colPublished: {
|
||||
width: 200
|
||||
},
|
||||
colType: {
|
||||
width: 200
|
||||
}
|
||||
},
|
||||
colAttribute: {
|
||||
width: 150
|
||||
},
|
||||
colFill: {
|
||||
padding: 0,
|
||||
width: "100%"
|
||||
},
|
||||
colName: {
|
||||
"&$colNameFixed": {
|
||||
width: 250
|
||||
}
|
||||
},
|
||||
colNameFixed: {},
|
||||
colNameHeader: {
|
||||
marginLeft: AVATAR_MARGIN
|
||||
},
|
||||
colPrice: {
|
||||
textAlign: "right"
|
||||
},
|
||||
colPublished: {},
|
||||
colType: {},
|
||||
link: {
|
||||
cursor: "pointer"
|
||||
},
|
||||
table: {
|
||||
tableLayout: "fixed"
|
||||
},
|
||||
tableContainer: {
|
||||
overflowX: "scroll"
|
||||
},
|
||||
textLeft: {
|
||||
textAlign: "left"
|
||||
},
|
||||
textRight: {
|
||||
textAlign: "right"
|
||||
}
|
||||
});
|
||||
|
||||
interface ProductListProps
|
||||
extends ListProps<ProductListColumns>,
|
||||
ListActions,
|
||||
WithStyles<typeof styles> {
|
||||
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
||||
products: ProductList_products_edges_node[];
|
||||
}
|
||||
|
||||
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||
({
|
||||
classes,
|
||||
settings,
|
||||
disabled,
|
||||
isChecked,
|
||||
gridAttributes,
|
||||
pageInfo,
|
||||
products,
|
||||
selected,
|
||||
toggle,
|
||||
toggleAll,
|
||||
toolbar,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
onUpdateListSettings,
|
||||
onRowClick
|
||||
}: ProductListProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const DisplayColumn: React.FC<{ column: ProductListColumns }> = props => (
|
||||
<TDisplayColumn displayColumns={settings.columns} {...props} />
|
||||
);
|
||||
const gridAttributesFromSettings = settings.columns.filter(
|
||||
isAttributeColumnValue
|
||||
);
|
||||
const numberOfColumns = 2 + settings.columns.length;
|
||||
|
||||
return (
|
||||
<div className={classes.tableContainer}>
|
||||
<Table className={classes.table}>
|
||||
<colgroup>
|
||||
<col />
|
||||
<col className={classes.colName} />
|
||||
<DisplayColumn column="productType">
|
||||
<col className={classes.colType} />
|
||||
</DisplayColumn>
|
||||
<DisplayColumn column="isPublished">
|
||||
<col className={classes.colPublished} />
|
||||
</DisplayColumn>
|
||||
{gridAttributesFromSettings.map(gridAttribute => (
|
||||
<col className={classes.colAttribute} key={gridAttribute} />
|
||||
))}
|
||||
<DisplayColumn column="price">
|
||||
<col className={classes.colPrice} />
|
||||
</DisplayColumn>
|
||||
</colgroup>
|
||||
<TableHead
|
||||
colSpan={numberOfColumns}
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
items={products}
|
||||
toggleAll={toggleAll}
|
||||
toolbar={toolbar}
|
||||
>
|
||||
<TableCell
|
||||
className={classNames(classes.colName, {
|
||||
[classes.colNameFixed]: settings.columns.length > 4
|
||||
})}
|
||||
>
|
||||
<span className={classes.colNameHeader}>
|
||||
<FormattedMessage defaultMessage="Name" description="product" />
|
||||
</span>
|
||||
</TableCell>
|
||||
<DisplayColumn column="productType">
|
||||
<TableCell className={classes.colType}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Type"
|
||||
description="product type"
|
||||
/>
|
||||
</TableCell>
|
||||
</DisplayColumn>
|
||||
<DisplayColumn column="isPublished">
|
||||
<TableCell className={classes.colPublished}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Published"
|
||||
description="product status"
|
||||
/>
|
||||
</TableCell>
|
||||
</DisplayColumn>
|
||||
{gridAttributesFromSettings.map(gridAttributeFromSettings => (
|
||||
<TableCell
|
||||
className={classes.colAttribute}
|
||||
key={gridAttributeFromSettings}
|
||||
>
|
||||
{maybe<React.ReactNode>(
|
||||
() =>
|
||||
gridAttributes.find(
|
||||
gridAttribute =>
|
||||
getAttributeIdFromColumnValue(
|
||||
gridAttributeFromSettings
|
||||
) === gridAttribute.id
|
||||
).name,
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
<DisplayColumn column="price">
|
||||
<TableCell className={classes.colPrice}>
|
||||
<FormattedMessage
|
||||
defaultMessage="Price"
|
||||
description="product price"
|
||||
/>
|
||||
</TableCell>
|
||||
</DisplayColumn>
|
||||
</TableHead>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
colSpan={numberOfColumns}
|
||||
settings={settings}
|
||||
hasNextPage={
|
||||
pageInfo && !disabled ? pageInfo.hasNextPage : false
|
||||
}
|
||||
onNextPage={onNextPage}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
hasPreviousPage={
|
||||
pageInfo && !disabled ? pageInfo.hasPreviousPage : false
|
||||
}
|
||||
onPreviousPage={onPreviousPage}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
<TableBody>
|
||||
{renderCollection(
|
||||
products,
|
||||
product => {
|
||||
const isSelected = product ? isChecked(product.id) : false;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
selected={isSelected}
|
||||
hover={!!product}
|
||||
key={product ? product.id : "skeleton"}
|
||||
onClick={product && onRowClick(product.id)}
|
||||
className={classes.link}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={disabled}
|
||||
disableClickPropagation
|
||||
onChange={() => toggle(product.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCellAvatar
|
||||
className={classes.colName}
|
||||
thumbnail={maybe(() => product.thumbnail.url)}
|
||||
>
|
||||
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||
</TableCellAvatar>
|
||||
<DisplayColumn column="productType">
|
||||
<TableCell className={classes.colType}>
|
||||
{product && product.productType ? (
|
||||
product.productType.name
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</DisplayColumn>
|
||||
<DisplayColumn column="isPublished">
|
||||
<TableCell className={classes.colPublished}>
|
||||
{product &&
|
||||
maybe(() => product.isAvailable !== undefined) ? (
|
||||
<StatusLabel
|
||||
label={
|
||||
product.isAvailable
|
||||
? intl.formatMessage({
|
||||
defaultMessage: "Published",
|
||||
description: "product",
|
||||
id: "productStatusLabel"
|
||||
})
|
||||
: intl.formatMessage({
|
||||
defaultMessage: "Not published",
|
||||
description: "product"
|
||||
})
|
||||
}
|
||||
status={product.isAvailable ? "success" : "error"}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</DisplayColumn>
|
||||
{gridAttributesFromSettings.map(gridAttribute => (
|
||||
<TableCell
|
||||
className={classes.colAttribute}
|
||||
key={gridAttribute}
|
||||
>
|
||||
{maybe<React.ReactNode>(() => {
|
||||
const attribute = product.attributes.find(
|
||||
attribute =>
|
||||
attribute.attribute.id ===
|
||||
getAttributeIdFromColumnValue(gridAttribute)
|
||||
);
|
||||
if (attribute) {
|
||||
return attribute.values
|
||||
.map(value => value.name)
|
||||
.join(", ");
|
||||
}
|
||||
return "-";
|
||||
}, <Skeleton />)}
|
||||
</TableCell>
|
||||
))}
|
||||
<DisplayColumn column="price">
|
||||
<TableCell className={classes.colPrice}>
|
||||
{maybe(() => product.basePrice) &&
|
||||
maybe(() => product.basePrice.amount) !== undefined &&
|
||||
maybe(() => product.basePrice.currency) !==
|
||||
undefined ? (
|
||||
<Money money={product.basePrice} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)}
|
||||
</TableCell>
|
||||
</DisplayColumn>
|
||||
</TableRow>
|
||||
);
|
||||
},
|
||||
() => (
|
||||
<TableRow>
|
||||
<TableCell colSpan={numberOfColumns}>
|
||||
<FormattedMessage defaultMessage="No products found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
ProductList.displayName = "ProductList";
|
||||
export default ProductList;
|
1
src/products/components/ProductList/index.ts
Normal file
1
src/products/components/ProductList/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from "./ProductList";
|
|
@ -6,27 +6,38 @@ import makeStyles from "@material-ui/styles/makeStyles";
|
|||
import React from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import { CategoryDetails_category_products_edges_node } from "@saleor/categories/types/CategoryDetails";
|
||||
import ColumnPicker, {
|
||||
ColumnPickerChoice
|
||||
} from "@saleor/components/ColumnPicker";
|
||||
import Container from "@saleor/components/Container";
|
||||
import PageHeader from "@saleor/components/PageHeader";
|
||||
import ProductList from "@saleor/components/ProductList";
|
||||
import { ProductListColumns } from "@saleor/config";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
|
||||
import { toggle } from "@saleor/utils/lists";
|
||||
import {
|
||||
AvailableInGridAttributes_availableInGrid_edges_node,
|
||||
AvailableInGridAttributes_grid_edges_node
|
||||
} from "@saleor/products/types/AvailableInGridAttributes";
|
||||
import { ProductList_products_edges_node } from "@saleor/products/types/ProductList";
|
||||
import {
|
||||
FetchMoreProps,
|
||||
FilterPageProps,
|
||||
ListActions,
|
||||
PageListProps
|
||||
} from "@saleor/types";
|
||||
import { ProductListUrlFilters } from "../../urls";
|
||||
import ProductList from "../ProductList";
|
||||
import ProductListFilter from "../ProductListFilter";
|
||||
|
||||
export interface ProductListPageProps
|
||||
extends PageListProps<ProductListColumns>,
|
||||
ListActions,
|
||||
FilterPageProps<ProductListUrlFilters> {
|
||||
FilterPageProps<ProductListUrlFilters>,
|
||||
FetchMoreProps {
|
||||
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
||||
currencySymbol: string;
|
||||
products: CategoryDetails_category_products_edges_node[];
|
||||
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
||||
totalGridAttributes: number;
|
||||
products: ProductList_products_edges_node[];
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
|
@ -42,10 +53,16 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
defaultSettings,
|
||||
filtersList,
|
||||
filterTabs,
|
||||
gridAttributes,
|
||||
availableInGridAttributes,
|
||||
hasMore,
|
||||
initialSearch,
|
||||
loading,
|
||||
settings,
|
||||
totalGridAttributes,
|
||||
onAdd,
|
||||
onAll,
|
||||
onFetchMore,
|
||||
onSearchChange,
|
||||
onFilterAdd,
|
||||
onFilterSave,
|
||||
|
@ -56,23 +73,9 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
} = props;
|
||||
const intl = useIntl();
|
||||
const classes = useStyles(props);
|
||||
const [selectedColumns, setSelectedColumns] = useStateFromProps(
|
||||
settings.columns
|
||||
);
|
||||
|
||||
const handleCancel = React.useCallback(
|
||||
() => setSelectedColumns(settings.columns),
|
||||
[settings.columns]
|
||||
);
|
||||
|
||||
const handleColumnToggle = (column: ProductListColumns) =>
|
||||
setSelectedColumns(prevSelectedColumns =>
|
||||
toggle(column, prevSelectedColumns, (a, b) => a === b)
|
||||
);
|
||||
|
||||
const handleReset = () => setSelectedColumns(defaultSettings.columns);
|
||||
|
||||
const handleSave = () => onUpdateListSettings("columns", selectedColumns);
|
||||
const handleSave = (columns: ProductListColumns[]) =>
|
||||
onUpdateListSettings("columns", columns);
|
||||
|
||||
const columns: ColumnPickerChoice[] = [
|
||||
{
|
||||
|
@ -95,7 +98,11 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
description: "product type"
|
||||
}),
|
||||
value: "productType" as ProductListColumns
|
||||
}
|
||||
},
|
||||
...availableInGridAttributes.map(attribute => ({
|
||||
label: attribute.name,
|
||||
value: `attribute:${attribute.id}`
|
||||
}))
|
||||
];
|
||||
|
||||
return (
|
||||
|
@ -104,10 +111,16 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
<ColumnPicker
|
||||
className={classes.columnPicker}
|
||||
columns={columns}
|
||||
selectedColumns={selectedColumns}
|
||||
onColumnToggle={handleColumnToggle}
|
||||
onCancel={handleCancel}
|
||||
onReset={handleReset}
|
||||
defaultColumns={defaultSettings.columns}
|
||||
hasMore={hasMore}
|
||||
loading={loading}
|
||||
initialColumns={settings.columns}
|
||||
total={
|
||||
columns.length -
|
||||
availableInGridAttributes.length +
|
||||
totalGridAttributes
|
||||
}
|
||||
onFetchMore={onFetchMore}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
<Button
|
||||
|
@ -146,7 +159,8 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
/>
|
||||
<ProductList
|
||||
{...listProps}
|
||||
settings={{ ...settings, columns: selectedColumns }}
|
||||
gridAttributes={gridAttributes}
|
||||
settings={settings}
|
||||
onUpdateListSettings={onUpdateListSettings}
|
||||
/>
|
||||
</Card>
|
||||
|
|
13
src/products/components/ProductListPage/utils.ts
Normal file
13
src/products/components/ProductListPage/utils.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
const prefix = "attribute";
|
||||
|
||||
export function getAttributeColumnValue(id: string) {
|
||||
return `${prefix}:${id}`;
|
||||
}
|
||||
|
||||
export function isAttributeColumnValue(value: string) {
|
||||
return value.includes(`${prefix}:`);
|
||||
}
|
||||
|
||||
export function getAttributeIdFromColumnValue(value: string) {
|
||||
return value.substr(prefix.length + 1);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,10 @@
|
|||
import gql from "graphql-tag";
|
||||
|
||||
import { TypedQuery } from "../queries";
|
||||
import { pageInfoFragment, TypedQuery } from "../queries";
|
||||
import {
|
||||
AvailableInGridAttributes,
|
||||
AvailableInGridAttributesVariables
|
||||
} from "./types/AvailableInGridAttributes";
|
||||
import { ProductCreateData } from "./types/ProductCreateData";
|
||||
import {
|
||||
ProductDetails,
|
||||
|
@ -225,6 +229,15 @@ const productListQuery = gql`
|
|||
edges {
|
||||
node {
|
||||
...ProductFragment
|
||||
attributes {
|
||||
attribute {
|
||||
id
|
||||
}
|
||||
values {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
|
@ -361,3 +374,38 @@ export const TypedProductImageQuery = TypedQuery<
|
|||
ProductImageById,
|
||||
ProductImageByIdVariables
|
||||
>(productImageQuery);
|
||||
|
||||
const availableInGridAttributes = gql`
|
||||
${pageInfoFragment}
|
||||
query GridAttributes($first: Int!, $after: String, $ids: [ID!]!) {
|
||||
availableInGrid: attributes(
|
||||
first: $first
|
||||
after: $after
|
||||
filter: { availableInGrid: true, isVariantOnly: false }
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
...PageInfoFragment
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
|
||||
grid: attributes(first: 25, filter: { ids: $ids }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const AvailableInGridAttributesQuery = TypedQuery<
|
||||
AvailableInGridAttributes,
|
||||
AvailableInGridAttributesVariables
|
||||
>(availableInGridAttributes);
|
||||
|
|
60
src/products/types/AvailableInGridAttributes.ts
Normal file
60
src/products/types/AvailableInGridAttributes.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// 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_grid_edges_node {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface AvailableInGridAttributes_grid_edges {
|
||||
__typename: "AttributeCountableEdge";
|
||||
node: AvailableInGridAttributes_grid_edges_node;
|
||||
}
|
||||
|
||||
export interface AvailableInGridAttributes_grid {
|
||||
__typename: "AttributeCountableConnection";
|
||||
edges: AvailableInGridAttributes_grid_edges[];
|
||||
}
|
||||
|
||||
export interface AvailableInGridAttributes {
|
||||
availableInGrid: AvailableInGridAttributes_availableInGrid | null;
|
||||
grid: AvailableInGridAttributes_grid | null;
|
||||
}
|
||||
|
||||
export interface AvailableInGridAttributesVariables {
|
||||
first: number;
|
||||
after?: string | null;
|
||||
ids: string[];
|
||||
}
|
60
src/products/types/GridAttributes.ts
Normal file
60
src/products/types/GridAttributes.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
// ====================================================
|
||||
// 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 {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface GridAttributes_grid_edges {
|
||||
__typename: "AttributeCountableEdge";
|
||||
node: GridAttributes_grid_edges_node;
|
||||
}
|
||||
|
||||
export interface GridAttributes_grid {
|
||||
__typename: "AttributeCountableConnection";
|
||||
edges: GridAttributes_grid_edges[];
|
||||
}
|
||||
|
||||
export interface GridAttributes {
|
||||
availableInGrid: GridAttributes_availableInGrid | null;
|
||||
grid: GridAttributes_grid | null;
|
||||
}
|
||||
|
||||
export interface GridAttributesVariables {
|
||||
first: number;
|
||||
after?: string | null;
|
||||
ids: string[];
|
||||
}
|
|
@ -25,6 +25,23 @@ export interface ProductList_products_edges_node_productType {
|
|||
name: string;
|
||||
}
|
||||
|
||||
export interface ProductList_products_edges_node_attributes_attribute {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface ProductList_products_edges_node_attributes_values {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface ProductList_products_edges_node_attributes {
|
||||
__typename: "SelectedAttribute";
|
||||
attribute: ProductList_products_edges_node_attributes_attribute;
|
||||
values: (ProductList_products_edges_node_attributes_values | null)[];
|
||||
}
|
||||
|
||||
export interface ProductList_products_edges_node {
|
||||
__typename: "Product";
|
||||
id: string;
|
||||
|
@ -33,6 +50,7 @@ export interface ProductList_products_edges_node {
|
|||
isAvailable: boolean | null;
|
||||
basePrice: ProductList_products_edges_node_basePrice | null;
|
||||
productType: ProductList_products_edges_node_productType;
|
||||
attributes: ProductList_products_edges_node_attributes[];
|
||||
}
|
||||
|
||||
export interface ProductList_products_edges {
|
||||
|
|
|
@ -20,6 +20,7 @@ import usePaginator, {
|
|||
createPaginationState
|
||||
} from "@saleor/hooks/usePaginator";
|
||||
import useShop from "@saleor/hooks/useShop";
|
||||
import { commonMessages } from "@saleor/intl";
|
||||
import { getMutationState, maybe } from "@saleor/misc";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import ProductListPage from "../../components/ProductListPage";
|
||||
|
@ -27,7 +28,10 @@ import {
|
|||
TypedProductBulkDeleteMutation,
|
||||
TypedProductBulkPublishMutation
|
||||
} from "../../mutations";
|
||||
import { TypedProductListQuery } from "../../queries";
|
||||
import {
|
||||
AvailableInGridAttributesQuery,
|
||||
TypedProductListQuery
|
||||
} from "../../queries";
|
||||
import { productBulkDelete } from "../../types/productBulkDelete";
|
||||
import { productBulkPublish } from "../../types/productBulkPublish";
|
||||
import {
|
||||
|
@ -69,6 +73,18 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
|||
);
|
||||
const intl = useIntl();
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
navigate(
|
||||
productListUrl({
|
||||
...params,
|
||||
after: undefined,
|
||||
before: undefined
|
||||
})
|
||||
),
|
||||
[settings.rowNumber]
|
||||
);
|
||||
|
||||
const tabs = getFilterTabs();
|
||||
|
||||
const currentTab =
|
||||
|
@ -145,265 +161,332 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<TypedProductListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.products.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
<AvailableInGridAttributesQuery
|
||||
variables={{ first: 6, ids: settings.columns }}
|
||||
>
|
||||
{attributes => (
|
||||
<TypedProductListQuery displayLoader variables={queryVariables}>
|
||||
{({ data, loading, refetch }) => {
|
||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||
maybe(() => data.products.pageInfo),
|
||||
paginationState,
|
||||
params
|
||||
);
|
||||
|
||||
const handleBulkDelete = (data: productBulkDelete) => {
|
||||
if (data.productBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Products removed"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
const handleBulkDelete = (data: productBulkDelete) => {
|
||||
if (data.productBulkDelete.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkPublish = (data: productBulkPublish) => {
|
||||
if (data.productBulkPublish.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: intl.formatMessage({
|
||||
defaultMessage: "Changed publication status",
|
||||
description: "product status update notification"
|
||||
})
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
const handleBulkPublish = (data: productBulkPublish) => {
|
||||
if (data.productBulkPublish.errors.length === 0) {
|
||||
closeModal();
|
||||
notify({
|
||||
text: intl.formatMessage(commonMessages.savedChanges)
|
||||
});
|
||||
reset();
|
||||
refetch();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
||||
{(productBulkDelete, productBulkDeleteOpts) => (
|
||||
<TypedProductBulkPublishMutation onCompleted={handleBulkPublish}>
|
||||
{(productBulkPublish, productBulkPublishOpts) => {
|
||||
const bulkDeleteMutationState = getMutationState(
|
||||
productBulkDeleteOpts.called,
|
||||
productBulkDeleteOpts.loading,
|
||||
maybe(
|
||||
() => productBulkDeleteOpts.data.productBulkDelete.errors
|
||||
)
|
||||
);
|
||||
return (
|
||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
||||
{(productBulkDelete, productBulkDeleteOpts) => (
|
||||
<TypedProductBulkPublishMutation
|
||||
onCompleted={handleBulkPublish}
|
||||
>
|
||||
{(productBulkPublish, productBulkPublishOpts) => {
|
||||
const bulkDeleteMutationState = getMutationState(
|
||||
productBulkDeleteOpts.called,
|
||||
productBulkDeleteOpts.loading,
|
||||
maybe(
|
||||
() =>
|
||||
productBulkDeleteOpts.data.productBulkDelete.errors
|
||||
)
|
||||
);
|
||||
|
||||
const bulkPublishMutationState = getMutationState(
|
||||
productBulkPublishOpts.called,
|
||||
productBulkPublishOpts.loading,
|
||||
maybe(
|
||||
() =>
|
||||
productBulkPublishOpts.data.productBulkPublish.errors
|
||||
)
|
||||
);
|
||||
const bulkPublishMutationState = getMutationState(
|
||||
productBulkPublishOpts.called,
|
||||
productBulkPublishOpts.loading,
|
||||
maybe(
|
||||
() =>
|
||||
productBulkPublishOpts.data.productBulkPublish
|
||||
.errors
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProductListPage
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
defaultSettings={
|
||||
defaultListSettings[ListViews.PRODUCT_LIST]
|
||||
}
|
||||
settings={settings}
|
||||
filtersList={createFilterChips(
|
||||
params,
|
||||
{
|
||||
currencySymbol,
|
||||
locale
|
||||
},
|
||||
changeFilterField,
|
||||
intl
|
||||
)}
|
||||
onAdd={() => navigate(productAddUrl)}
|
||||
disabled={loading}
|
||||
products={maybe(() =>
|
||||
data.products.edges.map(edge => edge.node)
|
||||
)}
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
pageInfo={pageInfo}
|
||||
onRowClick={id => () => navigate(productUrl(id))}
|
||||
onAll={() =>
|
||||
changeFilters({
|
||||
status: undefined
|
||||
})
|
||||
}
|
||||
toolbar={
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("unpublish", listElements)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unpublish"
|
||||
description="unpublish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => openModal("publish", listElements)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Publish"
|
||||
description="publish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => openModal("delete", listElements)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
onSearchChange={query => changeFilterField({ query })}
|
||||
onFilterAdd={filter =>
|
||||
changeFilterField(createFilter(filter))
|
||||
}
|
||||
onFilterSave={() => openModal("save-search")}
|
||||
onFilterDelete={() => openModal("delete-search")}
|
||||
onTabChange={handleTabChange}
|
||||
initialSearch={params.query || ""}
|
||||
filterTabs={getFilterTabs()}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "delete"}
|
||||
confirmButtonState={bulkDeleteMutationState}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkDelete({
|
||||
variables: { ids: params.ids }
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||
one {this product}
|
||||
other {{displayQuantity} products}
|
||||
}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "publish"}
|
||||
confirmButtonState={bulkPublishMutationState}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkPublish({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
isPublished: true
|
||||
return (
|
||||
<>
|
||||
<ProductListPage
|
||||
availableInGridAttributes={maybe(
|
||||
() =>
|
||||
attributes.data.availableInGrid.edges.map(
|
||||
edge => edge.node
|
||||
),
|
||||
[]
|
||||
)}
|
||||
currencySymbol={currencySymbol}
|
||||
currentTab={currentTab}
|
||||
defaultSettings={
|
||||
defaultListSettings[ListViews.PRODUCT_LIST]
|
||||
}
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Publish Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to publish {counter, plural,
|
||||
one {this product}
|
||||
other {{displayQuantity} products}
|
||||
}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
gridAttributes={maybe(
|
||||
() =>
|
||||
attributes.data.grid.edges.map(
|
||||
edge => edge.node
|
||||
),
|
||||
[]
|
||||
)}
|
||||
totalGridAttributes={maybe(
|
||||
() => attributes.data.availableInGrid.totalCount,
|
||||
0
|
||||
)}
|
||||
settings={settings}
|
||||
loading={attributes.loading}
|
||||
hasMore={maybe(
|
||||
() =>
|
||||
attributes.data.availableInGrid.pageInfo
|
||||
.hasNextPage,
|
||||
false
|
||||
)}
|
||||
filtersList={createFilterChips(
|
||||
params,
|
||||
{
|
||||
currencySymbol,
|
||||
locale
|
||||
},
|
||||
changeFilterField,
|
||||
intl
|
||||
)}
|
||||
onAdd={() => navigate(productAddUrl)}
|
||||
disabled={loading}
|
||||
products={maybe(() =>
|
||||
data.products.edges.map(edge => edge.node)
|
||||
)}
|
||||
onFetchMore={() =>
|
||||
attributes.loadMore(
|
||||
(prev, next) => {
|
||||
if (
|
||||
prev.availableInGrid.pageInfo.endCursor ===
|
||||
next.availableInGrid.pageInfo.endCursor
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
availableInGrid: {
|
||||
...prev.availableInGrid,
|
||||
edges: [
|
||||
...prev.availableInGrid.edges,
|
||||
...next.availableInGrid.edges
|
||||
],
|
||||
pageInfo: next.availableInGrid.pageInfo
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
after:
|
||||
attributes.data.availableInGrid.pageInfo
|
||||
.endCursor
|
||||
}
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "unpublish"}
|
||||
confirmButtonState={bulkPublishMutationState}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkPublish({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
isPublished: false
|
||||
}
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unpublish Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to unpublish {counter, plural,
|
||||
onNextPage={loadNextPage}
|
||||
onPreviousPage={loadPreviousPage}
|
||||
onUpdateListSettings={updateListSettings}
|
||||
pageInfo={pageInfo}
|
||||
onRowClick={id => () => navigate(productUrl(id))}
|
||||
onAll={() =>
|
||||
changeFilters({
|
||||
status: undefined
|
||||
})
|
||||
}
|
||||
toolbar={
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("unpublish", listElements)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unpublish"
|
||||
description="unpublish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("publish", listElements)
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Publish"
|
||||
description="publish product, button"
|
||||
/>
|
||||
</Button>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() =>
|
||||
openModal("delete", listElements)
|
||||
}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
isChecked={isSelected}
|
||||
selected={listElements.length}
|
||||
toggle={toggle}
|
||||
toggleAll={toggleAll}
|
||||
onSearchChange={query =>
|
||||
changeFilterField({ query })
|
||||
}
|
||||
onFilterAdd={filter =>
|
||||
changeFilterField(createFilter(filter))
|
||||
}
|
||||
onFilterSave={() => openModal("save-search")}
|
||||
onFilterDelete={() => openModal("delete-search")}
|
||||
onTabChange={handleTabChange}
|
||||
initialSearch={params.query || ""}
|
||||
filterTabs={getFilterTabs()}
|
||||
/>
|
||||
<ActionDialog
|
||||
open={params.action === "delete"}
|
||||
confirmButtonState={bulkDeleteMutationState}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkDelete({
|
||||
variables: { ids: params.ids }
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Delete Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
variant="delete"
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||
one {this product}
|
||||
other {{displayQuantity} products}
|
||||
}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "publish"}
|
||||
confirmButtonState={bulkPublishMutationState}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkPublish({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
isPublished: true
|
||||
}
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Publish Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to publish {counter, plural,
|
||||
one {this product}
|
||||
other {{displayQuantity} products}
|
||||
}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<ActionDialog
|
||||
open={params.action === "unpublish"}
|
||||
confirmButtonState={bulkPublishMutationState}
|
||||
onClose={closeModal}
|
||||
onConfirm={() =>
|
||||
productBulkPublish({
|
||||
variables: {
|
||||
ids: params.ids,
|
||||
isPublished: false
|
||||
}
|
||||
})
|
||||
}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Unpublish Products",
|
||||
description: "dialog header"
|
||||
})}
|
||||
>
|
||||
<DialogContentText>
|
||||
<FormattedMessage
|
||||
defaultMessage="Are you sure you want to unpublish {counter, plural,
|
||||
one {this product}
|
||||
other {{displayQuantity} products}
|
||||
}?"
|
||||
description="dialog content"
|
||||
values={{
|
||||
counter: maybe(() => params.ids.length),
|
||||
displayQuantity: (
|
||||
<strong>
|
||||
{maybe(() => params.ids.length)}
|
||||
</strong>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabSave}
|
||||
/>
|
||||
</DialogContentText>
|
||||
</ActionDialog>
|
||||
<SaveFilterTabDialog
|
||||
open={params.action === "save-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabSave}
|
||||
/>
|
||||
<DeleteFilterTabDialog
|
||||
open={params.action === "delete-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabDelete}
|
||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedProductBulkPublishMutation>
|
||||
)}
|
||||
</TypedProductBulkDeleteMutation>
|
||||
);
|
||||
}}
|
||||
</TypedProductListQuery>
|
||||
<DeleteFilterTabDialog
|
||||
open={params.action === "delete-search"}
|
||||
confirmButtonState="default"
|
||||
onClose={closeModal}
|
||||
onSubmit={handleFilterTabDelete}
|
||||
tabName={maybe(
|
||||
() => tabs[currentTab - 1].name,
|
||||
"..."
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TypedProductBulkPublishMutation>
|
||||
)}
|
||||
</TypedProductBulkDeleteMutation>
|
||||
);
|
||||
}}
|
||||
</TypedProductListQuery>
|
||||
)}
|
||||
</AvailableInGridAttributesQuery>
|
||||
);
|
||||
};
|
||||
export default ProductList;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,96 +0,0 @@
|
|||
import { storiesOf } from "@storybook/react";
|
||||
import React from "react";
|
||||
|
||||
import placeholder from "@assets/images/placeholder60x60.png";
|
||||
import CategoryProducts from "../../../categories/components/CategoryProducts";
|
||||
import Decorator from "../../Decorator";
|
||||
|
||||
const products = [
|
||||
{
|
||||
id: "UHJvZHVjdDox",
|
||||
name: "Gardner, Graham and King",
|
||||
productType: {
|
||||
id: "1",
|
||||
name: "T-Shirt"
|
||||
},
|
||||
thumbnail: {
|
||||
url: placeholder
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "UHJvZHVjdDoy",
|
||||
name: "Gardner, Graham and King",
|
||||
productType: {
|
||||
id: "1",
|
||||
name: "T-Shirt"
|
||||
},
|
||||
thumbnail: {
|
||||
url: placeholder
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "UHJvZHVjdDoz",
|
||||
name: "Gardner, Graham and King",
|
||||
productType: {
|
||||
id: "1",
|
||||
name: "T-Shirt"
|
||||
},
|
||||
thumbnail: {
|
||||
url: placeholder
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "UHJvZHVjdDoa",
|
||||
name: "Gardner, Graham and King",
|
||||
productType: {
|
||||
id: "1",
|
||||
name: "T-Shirt"
|
||||
},
|
||||
thumbnail: {
|
||||
url: placeholder
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
storiesOf("Categories / CategoryProducts", module)
|
||||
.addDecorator(Decorator)
|
||||
.add("without initial data", () => (
|
||||
<CategoryProducts
|
||||
hasNextPage={true}
|
||||
hasPreviousPage={false}
|
||||
products={[]}
|
||||
onAddProduct={undefined}
|
||||
onNextPage={undefined}
|
||||
onPreviousPage={undefined}
|
||||
/>
|
||||
))
|
||||
.add("with initial data", () => (
|
||||
<CategoryProducts
|
||||
hasNextPage={true}
|
||||
hasPreviousPage={false}
|
||||
products={products}
|
||||
onAddProduct={undefined}
|
||||
onNextPage={undefined}
|
||||
onPreviousPage={undefined}
|
||||
/>
|
||||
))
|
||||
.add("with clickable rows", () => (
|
||||
<CategoryProducts
|
||||
hasNextPage={true}
|
||||
hasPreviousPage={false}
|
||||
products={products}
|
||||
onAddProduct={undefined}
|
||||
onNextPage={undefined}
|
||||
onPreviousPage={undefined}
|
||||
onRowClick={() => undefined}
|
||||
/>
|
||||
))
|
||||
.add("when loading data", () => (
|
||||
<CategoryProducts
|
||||
hasNextPage={true}
|
||||
hasPreviousPage={false}
|
||||
onAddProduct={undefined}
|
||||
onNextPage={undefined}
|
||||
onPreviousPage={undefined}
|
||||
/>
|
||||
));
|
|
@ -15,6 +15,7 @@ const props: AssignAttributeDialogProps = {
|
|||
confirmButtonState: "default",
|
||||
errors: [],
|
||||
onClose: () => undefined,
|
||||
onFetch: () => undefined,
|
||||
onOpen: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
onToggle: () => undefined,
|
||||
|
|
|
@ -26,12 +26,10 @@ const columns: ColumnPickerChoice[] = [
|
|||
|
||||
const props: ColumnPickerProps = {
|
||||
columns,
|
||||
initial: true,
|
||||
onCancel: () => undefined,
|
||||
onColumnToggle: () => undefined,
|
||||
onReset: () => undefined,
|
||||
onSave: () => undefined,
|
||||
selectedColumns: [1, 3, 4, 6].map(index => columns[index].value)
|
||||
defaultColumns: [1, 3].map(index => columns[index].value),
|
||||
initialColumns: [1, 3, 4, 6].map(index => columns[index].value),
|
||||
initialOpen: true,
|
||||
onSave: () => undefined
|
||||
};
|
||||
|
||||
storiesOf("Generics / Column picker", module)
|
||||
|
@ -42,4 +40,12 @@ storiesOf("Generics / Column picker", module)
|
|||
))
|
||||
.addDecorator(CardDecorator)
|
||||
.addDecorator(Decorator)
|
||||
.add("default", () => <ColumnPicker {...props} />);
|
||||
.add("default", () => <ColumnPicker {...props} />)
|
||||
.add("loading", () => (
|
||||
<ColumnPicker
|
||||
{...props}
|
||||
loading={true}
|
||||
hasMore={true}
|
||||
onFetchMore={() => undefined}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -3,9 +3,11 @@ import React from "react";
|
|||
|
||||
import placeholderImage from "@assets/images/placeholder255x255.png";
|
||||
import { defaultListSettings } from "@saleor/config";
|
||||
import { products as productListFixture } from "@saleor/products/fixtures";
|
||||
import { attributes } from "@saleor/productTypes/fixtures";
|
||||
import { ListViews } from "@saleor/types";
|
||||
import { category as categoryFixture } from "../../../categories/fixtures";
|
||||
import {
|
||||
fetchMoreProps,
|
||||
filterPageProps,
|
||||
filters,
|
||||
listActionsProps,
|
||||
|
@ -16,20 +18,22 @@ import ProductListPage, {
|
|||
} from "../../../products/components/ProductListPage";
|
||||
import Decorator from "../../Decorator";
|
||||
|
||||
const products = categoryFixture(placeholderImage).products.edges.map(
|
||||
edge => edge.node
|
||||
);
|
||||
const products = productListFixture(placeholderImage);
|
||||
|
||||
const props: ProductListPageProps = {
|
||||
...listActionsProps,
|
||||
...pageListProps.default,
|
||||
...filterPageProps,
|
||||
...fetchMoreProps,
|
||||
availableInGridAttributes: attributes,
|
||||
defaultSettings: defaultListSettings[ListViews.PRODUCT_LIST],
|
||||
gridAttributes: attributes,
|
||||
products,
|
||||
settings: {
|
||||
...pageListProps.default.settings,
|
||||
columns: ["isPublished", "productType", "price"]
|
||||
}
|
||||
},
|
||||
totalGridAttributes: attributes.length
|
||||
};
|
||||
|
||||
storiesOf("Views / Products / Product list", module)
|
||||
|
|
|
@ -125,9 +125,8 @@ export interface ReorderEvent {
|
|||
}
|
||||
export type ReorderAction = (event: ReorderEvent) => void;
|
||||
|
||||
export interface FetchMoreProps<TData = string> {
|
||||
export interface FetchMoreProps {
|
||||
loading: boolean;
|
||||
hasMore: boolean;
|
||||
onFetch: (value: TData) => void;
|
||||
onFetchMore: () => void;
|
||||
}
|
||||
|
|
24
src/utils/columns/DisplayColumn.tsx
Normal file
24
src/utils/columns/DisplayColumn.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from "react";
|
||||
|
||||
import { isSelected } from "../lists";
|
||||
|
||||
export interface DisplayColumnProps<TColumn extends string = string> {
|
||||
displayColumns: TColumn[];
|
||||
column: TColumn;
|
||||
}
|
||||
|
||||
const DisplayColumn: React.FC<DisplayColumnProps> = ({
|
||||
displayColumns,
|
||||
children,
|
||||
column
|
||||
}) => {
|
||||
const displayColumn = React.useCallback(
|
||||
(column: string) => isSelected(column, displayColumns, (a, b) => a === b),
|
||||
[displayColumns]
|
||||
);
|
||||
|
||||
return <>{displayColumn(column) && children}</>;
|
||||
};
|
||||
|
||||
DisplayColumn.displayName = "DisplayColumn";
|
||||
export default DisplayColumn;
|
Loading…
Reference in a new issue