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
|
- Use react-intl - #105 by @dominik-zeglen
|
||||||
- Add dynamic dashboard settings - #135 by @benekex2
|
- Add dynamic dashboard settings - #135 by @benekex2
|
||||||
- Fix plugins page translations - #141 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 Button from "@material-ui/core/Button";
|
||||||
import Card from "@material-ui/core/Card";
|
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 React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import CardTitle from "@saleor/components/CardTitle";
|
import CardTitle from "@saleor/components/CardTitle";
|
||||||
import Skeleton from "@saleor/components/Skeleton";
|
import { ListActions, PageListProps } from "../../../types";
|
||||||
import TableCellAvatar from "@saleor/components/TableCellAvatar";
|
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
|
||||||
import TablePagination from "@saleor/components/TablePagination";
|
import CategoryProductList from "../CategoryProductList";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
interface CategoryProductsProps extends PageListProps, ListActions {
|
||||||
createStyles({
|
products: CategoryDetails_category_products_edges_node[];
|
||||||
link: {
|
categoryName: string;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
export const CategoryProducts: React.StatelessComponent<
|
||||||
({
|
CategoryProductsProps
|
||||||
classes,
|
> = ({
|
||||||
hasNextPage,
|
products,
|
||||||
hasPreviousPage,
|
disabled,
|
||||||
products,
|
pageInfo,
|
||||||
onAddProduct,
|
onAdd,
|
||||||
onNextPage,
|
onNextPage,
|
||||||
onPreviousPage,
|
onPreviousPage,
|
||||||
onRowClick
|
onRowClick,
|
||||||
}: ProductListProps) => {
|
categoryName,
|
||||||
const intl = useIntl();
|
isChecked,
|
||||||
|
selected,
|
||||||
|
toggle,
|
||||||
|
toggleAll,
|
||||||
|
toolbar
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardTitle
|
<CardTitle
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage(
|
||||||
defaultMessage: "Products",
|
{
|
||||||
description: "section header"
|
defaultMessage: "Products in {categoryName}",
|
||||||
})}
|
description: "header"
|
||||||
toolbar={
|
},
|
||||||
<Button variant="text" color="primary" onClick={onAddProduct}>
|
{ categoryName }
|
||||||
<FormattedMessage
|
)}
|
||||||
defaultMessage="Add product"
|
toolbar={
|
||||||
description="button"
|
<Button color="primary" variant="text" onClick={onAdd}>
|
||||||
/>
|
<FormattedMessage
|
||||||
</Button>
|
defaultMessage="Add product"
|
||||||
}
|
description="button"
|
||||||
/>
|
/>
|
||||||
<Table>
|
</Button>
|
||||||
<TableHead>
|
}
|
||||||
<TableRow>
|
/>
|
||||||
{(products === undefined || products.length > 0) && <TableCell />}
|
<CategoryProductList
|
||||||
<TableCell className={classes.textLeft}>
|
products={products}
|
||||||
<FormattedMessage
|
disabled={disabled}
|
||||||
defaultMessage="Name"
|
pageInfo={pageInfo}
|
||||||
description="product name"
|
onNextPage={onNextPage}
|
||||||
/>
|
onPreviousPage={onPreviousPage}
|
||||||
</TableCell>
|
onRowClick={onRowClick}
|
||||||
<TableCell>
|
selected={selected}
|
||||||
<FormattedMessage
|
isChecked={isChecked}
|
||||||
defaultMessage="Type"
|
toggle={toggle}
|
||||||
description="product type"
|
toggleAll={toggleAll}
|
||||||
/>
|
toolbar={toolbar}
|
||||||
</TableCell>
|
/>
|
||||||
</TableRow>
|
</Card>
|
||||||
</TableHead>
|
);
|
||||||
<TableFooter>
|
};
|
||||||
<TableRow>
|
|
||||||
<TablePagination
|
CategoryProducts.displayName = "CategoryProducts";
|
||||||
colSpan={3}
|
export default CategoryProducts;
|
||||||
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;
|
|
||||||
|
|
|
@ -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
|
CategoryDetails_category_products_edges_node
|
||||||
} from "../../types/CategoryDetails";
|
} from "../../types/CategoryDetails";
|
||||||
import CategoryBackground from "../CategoryBackground";
|
import CategoryBackground from "../CategoryBackground";
|
||||||
import CategoryProductsCard from "../CategoryProductsCard";
|
import CategoryProducts from "../CategoryProducts";
|
||||||
|
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
backgroundImageAlt: string;
|
backgroundImageAlt: string;
|
||||||
|
@ -195,7 +195,7 @@ export const CategoryUpdatePage: React.StatelessComponent<
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentTab === CategoryPageTab.products && (
|
{currentTab === CategoryPageTab.products && (
|
||||||
<CategoryProductsCard
|
<CategoryProducts
|
||||||
categoryName={maybe(() => category.name)}
|
categoryName={maybe(() => category.name)}
|
||||||
products={products}
|
products={products}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -204,6 +204,7 @@ const styles = (theme: Theme) =>
|
||||||
menuSmall: {
|
menuSmall: {
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
height: "100vh",
|
height: "100vh",
|
||||||
|
overflow: "hidden",
|
||||||
padding: 25
|
padding: 25
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
|
|
|
@ -6,14 +6,23 @@ import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||||
import makeStyles from "@material-ui/styles/makeStyles";
|
import makeStyles from "@material-ui/styles/makeStyles";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||||
|
import { toggle } from "@saleor/utils/lists";
|
||||||
import ColumnPickerButton from "./ColumnPickerButton";
|
import ColumnPickerButton from "./ColumnPickerButton";
|
||||||
import ColumnPickerContent, {
|
import ColumnPickerContent, {
|
||||||
ColumnPickerContentProps
|
ColumnPickerContentProps
|
||||||
} from "./ColumnPickerContent";
|
} from "./ColumnPickerContent";
|
||||||
|
|
||||||
export interface ColumnPickerProps extends ColumnPickerContentProps {
|
export interface ColumnPickerProps
|
||||||
|
extends Omit<
|
||||||
|
ColumnPickerContentProps,
|
||||||
|
"selectedColumns" | "onCancel" | "onColumnToggle" | "onReset" | "onSave"
|
||||||
|
> {
|
||||||
className?: string;
|
className?: string;
|
||||||
initial?: boolean;
|
defaultColumns: string[];
|
||||||
|
initialColumns: string[];
|
||||||
|
initialOpen?: boolean;
|
||||||
|
onSave: (columns: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
|
@ -33,29 +42,39 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
columns,
|
columns,
|
||||||
initial = false,
|
defaultColumns,
|
||||||
selectedColumns,
|
hasMore,
|
||||||
onCancel,
|
initialColumns,
|
||||||
onColumnToggle,
|
initialOpen = false,
|
||||||
onReset,
|
loading,
|
||||||
|
total,
|
||||||
|
onFetchMore,
|
||||||
onSave
|
onSave
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const anchor = React.useRef<HTMLDivElement>();
|
const anchor = React.useRef<HTMLDivElement>();
|
||||||
const [isExpanded, setExpansionState] = React.useState(false);
|
const [isExpanded, setExpansionState] = React.useState(false);
|
||||||
|
const [selectedColumns, setSelectedColumns] = useStateFromProps(
|
||||||
|
initialColumns
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setTimeout(() => setExpansionState(initial), 100);
|
setTimeout(() => setExpansionState(initialOpen), 100);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCancel = React.useCallback(() => {
|
const handleCancel = React.useCallback(() => {
|
||||||
setExpansionState(false);
|
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 = () => {
|
const handleSave = () => {
|
||||||
setExpansionState(false);
|
setExpansionState(false);
|
||||||
onSave();
|
onSave(selectedColumns);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -86,10 +105,14 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
||||||
>
|
>
|
||||||
<ColumnPickerContent
|
<ColumnPickerContent
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
hasMore={hasMore}
|
||||||
|
loading={loading}
|
||||||
selectedColumns={selectedColumns}
|
selectedColumns={selectedColumns}
|
||||||
|
total={total}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onColumnToggle={onColumnToggle}
|
onColumnToggle={handleColumnToggle}
|
||||||
onReset={onReset}
|
onFetchMore={onFetchMore}
|
||||||
|
onReset={handleReset}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import Card from "@material-ui/core/Card";
|
import Card from "@material-ui/core/Card";
|
||||||
import CardContent from "@material-ui/core/CardContent";
|
import CardContent from "@material-ui/core/CardContent";
|
||||||
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
import { Theme } from "@material-ui/core/styles";
|
import { Theme } from "@material-ui/core/styles";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import makeStyles from "@material-ui/styles/makeStyles";
|
import makeStyles from "@material-ui/styles/makeStyles";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import InfiniteScroll from "react-infinite-scroller";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import useElementScroll from "@saleor/hooks/useElementScroll";
|
import useElementScroll from "@saleor/hooks/useElementScroll";
|
||||||
import { buttonMessages } from "@saleor/intl";
|
import { buttonMessages } from "@saleor/intl";
|
||||||
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
import { isSelected } from "@saleor/utils/lists";
|
import { isSelected } from "@saleor/utils/lists";
|
||||||
import ControlledCheckbox from "../ControlledCheckbox";
|
import ControlledCheckbox from "../ControlledCheckbox";
|
||||||
import Hr from "../Hr";
|
import Hr from "../Hr";
|
||||||
|
@ -18,9 +21,10 @@ export interface ColumnPickerChoice {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
export interface ColumnPickerContentProps {
|
export interface ColumnPickerContentProps extends Partial<FetchMoreProps> {
|
||||||
columns: ColumnPickerChoice[];
|
columns: ColumnPickerChoice[];
|
||||||
selectedColumns: string[];
|
selectedColumns: string[];
|
||||||
|
total?: number;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onColumnToggle: (column: string) => void;
|
onColumnToggle: (column: string) => void;
|
||||||
onReset: () => void;
|
onReset: () => void;
|
||||||
|
@ -50,15 +54,29 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
},
|
},
|
||||||
dropShadow: {
|
dropShadow: {
|
||||||
boxShadow: `0px -5px 10px 0px ${theme.overrides.MuiCard.root.borderColor}`
|
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 ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
|
hasMore,
|
||||||
|
loading,
|
||||||
selectedColumns,
|
selectedColumns,
|
||||||
|
total,
|
||||||
onCancel,
|
onCancel,
|
||||||
onColumnToggle,
|
onColumnToggle,
|
||||||
|
onFetchMore,
|
||||||
onReset,
|
onReset,
|
||||||
onSave
|
onSave
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -72,7 +90,7 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card className={classes.root}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography color="textSecondary">
|
<Typography color="textSecondary">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -80,28 +98,61 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
|
||||||
description="pick columns to display"
|
description="pick columns to display"
|
||||||
values={{
|
values={{
|
||||||
numberOfSelected: selectedColumns.length,
|
numberOfSelected: selectedColumns.length,
|
||||||
numberOfTotal: columns.length
|
numberOfTotal: total || columns.length
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Hr />
|
<Hr />
|
||||||
<CardContent className={classes.contentContainer}>
|
{hasMore && onFetchMore ? (
|
||||||
<div className={classes.content} ref={anchor}>
|
<InfiniteScroll
|
||||||
{columns.map(column => (
|
pageStart={0}
|
||||||
<ControlledCheckbox
|
loadMore={onFetchMore}
|
||||||
checked={isSelected(
|
hasMore={hasMore}
|
||||||
column.value,
|
useWindow={false}
|
||||||
selectedColumns,
|
threshold={100}
|
||||||
(a, b) => a === b
|
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}
|
</div>
|
||||||
label={column.label}
|
</CardContent>
|
||||||
onChange={() => onColumnToggle(column.value)}
|
</InfiniteScroll>
|
||||||
/>
|
) : (
|
||||||
))}
|
<CardContent className={classes.contentContainer}>
|
||||||
</div>
|
<div className={classes.content} ref={anchor}>
|
||||||
</CardContent>
|
{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 />
|
<Hr />
|
||||||
<CardContent
|
<CardContent
|
||||||
className={classNames(classes.actionBarContainer, {
|
className={classNames(classes.actionBarContainer, {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import TableBody from "@material-ui/core/TableBody";
|
||||||
import TableCell from "@material-ui/core/TableCell";
|
import TableCell from "@material-ui/core/TableCell";
|
||||||
import TableFooter from "@material-ui/core/TableFooter";
|
import TableFooter from "@material-ui/core/TableFooter";
|
||||||
import TableRow from "@material-ui/core/TableRow";
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
|
import classNames from "classnames";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
|
@ -17,9 +18,14 @@ import TableCellAvatar, {
|
||||||
} from "@saleor/components/TableCellAvatar";
|
} from "@saleor/components/TableCellAvatar";
|
||||||
import { ProductListColumns } from "@saleor/config";
|
import { ProductListColumns } from "@saleor/config";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
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 { ListActions, ListProps } from "@saleor/types";
|
||||||
import { isSelected } from "@saleor/utils/lists";
|
import TDisplayColumn from "@saleor/utils/columns/DisplayColumn";
|
||||||
import { CategoryDetails_category_products_edges_node } from "../../categories/types/CategoryDetails";
|
|
||||||
import Checkbox from "../Checkbox";
|
import Checkbox from "../Checkbox";
|
||||||
import Money from "../Money";
|
import Money from "../Money";
|
||||||
import Skeleton from "../Skeleton";
|
import Skeleton from "../Skeleton";
|
||||||
|
@ -43,11 +49,19 @@ const styles = (theme: Theme) =>
|
||||||
width: 200
|
width: 200
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
colAttribute: {
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
colFill: {
|
colFill: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
width: "100%"
|
width: "100%"
|
||||||
},
|
},
|
||||||
colName: {},
|
colName: {
|
||||||
|
"&$colNameFixed": {
|
||||||
|
width: 250
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colNameFixed: {},
|
||||||
colNameHeader: {
|
colNameHeader: {
|
||||||
marginLeft: AVATAR_MARGIN
|
marginLeft: AVATAR_MARGIN
|
||||||
},
|
},
|
||||||
|
@ -77,7 +91,8 @@ interface ProductListProps
|
||||||
extends ListProps<ProductListColumns>,
|
extends ListProps<ProductListColumns>,
|
||||||
ListActions,
|
ListActions,
|
||||||
WithStyles<typeof styles> {
|
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" })(
|
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
|
@ -86,6 +101,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
settings,
|
settings,
|
||||||
disabled,
|
disabled,
|
||||||
isChecked,
|
isChecked,
|
||||||
|
gridAttributes,
|
||||||
pageInfo,
|
pageInfo,
|
||||||
products,
|
products,
|
||||||
selected,
|
selected,
|
||||||
|
@ -98,23 +114,35 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
onRowClick
|
onRowClick
|
||||||
}: ProductListProps) => {
|
}: ProductListProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const displayColumn = React.useCallback(
|
|
||||||
(column: ProductListColumns) =>
|
const DisplayColumn: React.FC<{ column: ProductListColumns }> = props => (
|
||||||
isSelected(column, settings.columns, (a, b) => a === b),
|
<TDisplayColumn displayColumns={settings.columns} {...props} />
|
||||||
[settings.columns]
|
);
|
||||||
|
|
||||||
|
const gridAttributesFromSettings = settings.columns.filter(
|
||||||
|
isAttributeColumnValue
|
||||||
);
|
);
|
||||||
const numberOfColumns = 2 + settings.columns.length;
|
const numberOfColumns = 2 + settings.columns.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.tableContainer}>
|
<div className={classes.tableContainer}>
|
||||||
<Table className={classes.table}>
|
<Table className={classes.table}>
|
||||||
<col />
|
<colgroup>
|
||||||
<col className={classes.colName} />
|
<col />
|
||||||
{displayColumn("productType") && <col className={classes.colType} />}
|
<col className={classes.colName} />
|
||||||
{displayColumn("isPublished") && (
|
<DisplayColumn column="productType">
|
||||||
<col className={classes.colPublished} />
|
<col className={classes.colType} />
|
||||||
)}
|
</DisplayColumn>
|
||||||
{displayColumn("price") && <col className={classes.colPrice} />}
|
<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
|
<TableHead
|
||||||
colSpan={numberOfColumns}
|
colSpan={numberOfColumns}
|
||||||
selected={selected}
|
selected={selected}
|
||||||
|
@ -123,32 +151,53 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
toggleAll={toggleAll}
|
toggleAll={toggleAll}
|
||||||
toolbar={toolbar}
|
toolbar={toolbar}
|
||||||
>
|
>
|
||||||
<TableCell className={classes.colName}>
|
<TableCell
|
||||||
|
className={classNames(classes.colName, {
|
||||||
|
[classes.colNameFixed]: settings.columns.length > 4
|
||||||
|
})}
|
||||||
|
>
|
||||||
<span className={classes.colNameHeader}>
|
<span className={classes.colNameHeader}>
|
||||||
<FormattedMessage defaultMessage="Name" description="product" />
|
<FormattedMessage defaultMessage="Name" description="product" />
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
{displayColumn("productType") && (
|
<DisplayColumn column="productType">
|
||||||
<TableCell className={classes.colType}>
|
<TableCell className={classes.colType}>
|
||||||
<FormattedMessage defaultMessage="Type" description="product" />
|
<FormattedMessage defaultMessage="Type" description="product" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
</DisplayColumn>
|
||||||
{displayColumn("isPublished") && (
|
<DisplayColumn column="isPublished">
|
||||||
<TableCell className={classes.colPublished}>
|
<TableCell className={classes.colPublished}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Published"
|
defaultMessage="Published"
|
||||||
description="product status"
|
description="product status"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
</DisplayColumn>
|
||||||
{displayColumn("price") && (
|
{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}>
|
<TableCell className={classes.colPrice}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Price"
|
defaultMessage="Price"
|
||||||
description="product"
|
description="product"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
</DisplayColumn>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -194,9 +243,9 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
className={classes.colName}
|
className={classes.colName}
|
||||||
thumbnail={maybe(() => product.thumbnail.url)}
|
thumbnail={maybe(() => product.thumbnail.url)}
|
||||||
>
|
>
|
||||||
{product ? product.name : <Skeleton />}
|
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||||
</TableCellAvatar>
|
</TableCellAvatar>
|
||||||
{displayColumn("productType") && (
|
<DisplayColumn column="productType">
|
||||||
<TableCell className={classes.colType}>
|
<TableCell className={classes.colType}>
|
||||||
{product && product.productType ? (
|
{product && product.productType ? (
|
||||||
product.productType.name
|
product.productType.name
|
||||||
|
@ -204,8 +253,8 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
</DisplayColumn>
|
||||||
{displayColumn("isPublished") && (
|
<DisplayColumn column="isPublished">
|
||||||
<TableCell className={classes.colPublished}>
|
<TableCell className={classes.colPublished}>
|
||||||
{product &&
|
{product &&
|
||||||
maybe(() => product.isAvailable !== undefined) ? (
|
maybe(() => product.isAvailable !== undefined) ? (
|
||||||
|
@ -227,8 +276,28 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
</DisplayColumn>
|
||||||
{displayColumn("price") && (
|
{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}>
|
<TableCell className={classes.colPrice}>
|
||||||
{maybe(() => product.basePrice) &&
|
{maybe(() => product.basePrice) &&
|
||||||
maybe(() => product.basePrice.amount) !== undefined &&
|
maybe(() => product.basePrice.amount) !== undefined &&
|
||||||
|
@ -239,7 +308,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
</DisplayColumn>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,7 +26,8 @@ const styles = (theme: Theme) =>
|
||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
alignSelf: "center",
|
alignSelf: "center",
|
||||||
marginLeft: theme.spacing.unit * 2
|
marginLeft: theme.spacing.unit * 2,
|
||||||
|
width: "100%"
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
@ -69,7 +70,7 @@ const TableCellAvatar = withStyles(styles, { name: "TableCellAvatar" })(
|
||||||
src={thumbnail}
|
src={thumbnail}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span className={classes.children}>{children}</span>
|
<div className={classes.children}>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
)
|
)
|
||||||
|
|
|
@ -101,26 +101,25 @@ const TableHead = withStyles(styles, {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(items === undefined || items.length > 0) &&
|
{(items === undefined || items.length > 0) && (
|
||||||
(selected && (
|
<TableCell
|
||||||
<TableCell
|
padding="checkbox"
|
||||||
padding="checkbox"
|
className={classNames({
|
||||||
|
[classes.checkboxSelected]: selected,
|
||||||
|
[classes.dragRows]: dragRows
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
className={classNames({
|
className={classNames({
|
||||||
[classes.checkboxSelected]: selected,
|
[classes.checkboxPartialSelect]:
|
||||||
[classes.dragRows]: dragRows
|
items && items.length > selected && selected > 0
|
||||||
})}
|
})}
|
||||||
>
|
checked={selected === 0 ? false : true}
|
||||||
<Checkbox
|
disabled={disabled}
|
||||||
className={classNames({
|
onChange={() => toggleAll(items, selected)}
|
||||||
[classes.checkboxPartialSelect]:
|
/>
|
||||||
items && items.length > selected && selected > 0
|
</TableCell>
|
||||||
})}
|
)}
|
||||||
checked={selected === 0 ? false : true}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={() => toggleAll(items, selected)}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
{selected ? (
|
{selected ? (
|
||||||
<>
|
<>
|
||||||
<TableCell
|
<TableCell
|
||||||
|
|
|
@ -134,6 +134,5 @@ export const filters: Filter[] = [
|
||||||
export const fetchMoreProps: FetchMoreProps = {
|
export const fetchMoreProps: FetchMoreProps = {
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
onFetch: () => undefined,
|
|
||||||
onFetchMore: () => undefined
|
onFetchMore: () => undefined
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,8 @@ export const Plugins = createSvgIcon(
|
||||||
<>
|
<>
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="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"
|
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"
|
fill="#06847B"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -81,6 +81,7 @@ interface OrderProductAddDialogProps extends FetchMoreProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
products: SearchOrderVariant_products_edges_node[];
|
products: SearchOrderVariant_products_edges_node[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onFetch: (query: string) => void;
|
||||||
onSubmit: (data: SearchOrderVariant_products_edges_node_variants[]) => 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 useNavigator from "@saleor/hooks/useNavigator";
|
||||||
import useNotifier from "@saleor/hooks/useNotifier";
|
import useNotifier from "@saleor/hooks/useNotifier";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { getMutationState, maybe } from "../../misc";
|
import { getMutationState, maybe } from "../../misc";
|
||||||
import PluginsDetailsPage from "../components/PluginsDetailsPage";
|
import PluginsDetailsPage from "../components/PluginsDetailsPage";
|
||||||
|
|
|
@ -54,6 +54,7 @@ export interface AssignAttributeDialogProps extends FetchMoreProps {
|
||||||
attributes: SearchAttributes_productType_availableAttributes_edges_node[];
|
attributes: SearchAttributes_productType_availableAttributes_edges_node[];
|
||||||
selected: string[];
|
selected: string[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onFetch: (query: string) => void;
|
||||||
onOpen: () => void;
|
onOpen: () => void;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
onToggle: (id: string) => 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 React from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import { CategoryDetails_category_products_edges_node } from "@saleor/categories/types/CategoryDetails";
|
|
||||||
import ColumnPicker, {
|
import ColumnPicker, {
|
||||||
ColumnPickerChoice
|
ColumnPickerChoice
|
||||||
} from "@saleor/components/ColumnPicker";
|
} from "@saleor/components/ColumnPicker";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import ProductList from "@saleor/components/ProductList";
|
|
||||||
import { ProductListColumns } from "@saleor/config";
|
import { ProductListColumns } from "@saleor/config";
|
||||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
|
import {
|
||||||
import { toggle } from "@saleor/utils/lists";
|
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 { ProductListUrlFilters } from "../../urls";
|
||||||
|
import ProductList from "../ProductList";
|
||||||
import ProductListFilter from "../ProductListFilter";
|
import ProductListFilter from "../ProductListFilter";
|
||||||
|
|
||||||
export interface ProductListPageProps
|
export interface ProductListPageProps
|
||||||
extends PageListProps<ProductListColumns>,
|
extends PageListProps<ProductListColumns>,
|
||||||
ListActions,
|
ListActions,
|
||||||
FilterPageProps<ProductListUrlFilters> {
|
FilterPageProps<ProductListUrlFilters>,
|
||||||
|
FetchMoreProps {
|
||||||
|
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
||||||
currencySymbol: string;
|
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) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
@ -42,10 +53,16 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
defaultSettings,
|
defaultSettings,
|
||||||
filtersList,
|
filtersList,
|
||||||
filterTabs,
|
filterTabs,
|
||||||
|
gridAttributes,
|
||||||
|
availableInGridAttributes,
|
||||||
|
hasMore,
|
||||||
initialSearch,
|
initialSearch,
|
||||||
|
loading,
|
||||||
settings,
|
settings,
|
||||||
|
totalGridAttributes,
|
||||||
onAdd,
|
onAdd,
|
||||||
onAll,
|
onAll,
|
||||||
|
onFetchMore,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onFilterAdd,
|
onFilterAdd,
|
||||||
onFilterSave,
|
onFilterSave,
|
||||||
|
@ -56,23 +73,9 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
} = props;
|
} = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const [selectedColumns, setSelectedColumns] = useStateFromProps(
|
|
||||||
settings.columns
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCancel = React.useCallback(
|
const handleSave = (columns: ProductListColumns[]) =>
|
||||||
() => setSelectedColumns(settings.columns),
|
onUpdateListSettings("columns", 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 columns: ColumnPickerChoice[] = [
|
const columns: ColumnPickerChoice[] = [
|
||||||
{
|
{
|
||||||
|
@ -95,7 +98,11 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
description: "product type"
|
description: "product type"
|
||||||
}),
|
}),
|
||||||
value: "productType" as ProductListColumns
|
value: "productType" as ProductListColumns
|
||||||
}
|
},
|
||||||
|
...availableInGridAttributes.map(attribute => ({
|
||||||
|
label: attribute.name,
|
||||||
|
value: `attribute:${attribute.id}`
|
||||||
|
}))
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -104,10 +111,16 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
<ColumnPicker
|
<ColumnPicker
|
||||||
className={classes.columnPicker}
|
className={classes.columnPicker}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
selectedColumns={selectedColumns}
|
defaultColumns={defaultSettings.columns}
|
||||||
onColumnToggle={handleColumnToggle}
|
hasMore={hasMore}
|
||||||
onCancel={handleCancel}
|
loading={loading}
|
||||||
onReset={handleReset}
|
initialColumns={settings.columns}
|
||||||
|
total={
|
||||||
|
columns.length -
|
||||||
|
availableInGridAttributes.length +
|
||||||
|
totalGridAttributes
|
||||||
|
}
|
||||||
|
onFetchMore={onFetchMore}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
@ -146,7 +159,8 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
/>
|
/>
|
||||||
<ProductList
|
<ProductList
|
||||||
{...listProps}
|
{...listProps}
|
||||||
settings={{ ...settings, columns: selectedColumns }}
|
gridAttributes={gridAttributes}
|
||||||
|
settings={settings}
|
||||||
onUpdateListSettings={onUpdateListSettings}
|
onUpdateListSettings={onUpdateListSettings}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</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 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 { ProductCreateData } from "./types/ProductCreateData";
|
||||||
import {
|
import {
|
||||||
ProductDetails,
|
ProductDetails,
|
||||||
|
@ -225,6 +229,15 @@ const productListQuery = gql`
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
...ProductFragment
|
...ProductFragment
|
||||||
|
attributes {
|
||||||
|
attribute {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
values {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pageInfo {
|
pageInfo {
|
||||||
|
@ -361,3 +374,38 @@ export const TypedProductImageQuery = TypedQuery<
|
||||||
ProductImageById,
|
ProductImageById,
|
||||||
ProductImageByIdVariables
|
ProductImageByIdVariables
|
||||||
>(productImageQuery);
|
>(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;
|
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 {
|
export interface ProductList_products_edges_node {
|
||||||
__typename: "Product";
|
__typename: "Product";
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -33,6 +50,7 @@ export interface ProductList_products_edges_node {
|
||||||
isAvailable: boolean | null;
|
isAvailable: boolean | null;
|
||||||
basePrice: ProductList_products_edges_node_basePrice | null;
|
basePrice: ProductList_products_edges_node_basePrice | null;
|
||||||
productType: ProductList_products_edges_node_productType;
|
productType: ProductList_products_edges_node_productType;
|
||||||
|
attributes: ProductList_products_edges_node_attributes[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductList_products_edges {
|
export interface ProductList_products_edges {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import usePaginator, {
|
||||||
createPaginationState
|
createPaginationState
|
||||||
} from "@saleor/hooks/usePaginator";
|
} from "@saleor/hooks/usePaginator";
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getMutationState, maybe } from "@saleor/misc";
|
import { getMutationState, maybe } from "@saleor/misc";
|
||||||
import { ListViews } from "@saleor/types";
|
import { ListViews } from "@saleor/types";
|
||||||
import ProductListPage from "../../components/ProductListPage";
|
import ProductListPage from "../../components/ProductListPage";
|
||||||
|
@ -27,7 +28,10 @@ import {
|
||||||
TypedProductBulkDeleteMutation,
|
TypedProductBulkDeleteMutation,
|
||||||
TypedProductBulkPublishMutation
|
TypedProductBulkPublishMutation
|
||||||
} from "../../mutations";
|
} from "../../mutations";
|
||||||
import { TypedProductListQuery } from "../../queries";
|
import {
|
||||||
|
AvailableInGridAttributesQuery,
|
||||||
|
TypedProductListQuery
|
||||||
|
} from "../../queries";
|
||||||
import { productBulkDelete } from "../../types/productBulkDelete";
|
import { productBulkDelete } from "../../types/productBulkDelete";
|
||||||
import { productBulkPublish } from "../../types/productBulkPublish";
|
import { productBulkPublish } from "../../types/productBulkPublish";
|
||||||
import {
|
import {
|
||||||
|
@ -69,6 +73,18 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
||||||
);
|
);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
React.useEffect(
|
||||||
|
() =>
|
||||||
|
navigate(
|
||||||
|
productListUrl({
|
||||||
|
...params,
|
||||||
|
after: undefined,
|
||||||
|
before: undefined
|
||||||
|
})
|
||||||
|
),
|
||||||
|
[settings.rowNumber]
|
||||||
|
);
|
||||||
|
|
||||||
const tabs = getFilterTabs();
|
const tabs = getFilterTabs();
|
||||||
|
|
||||||
const currentTab =
|
const currentTab =
|
||||||
|
@ -145,265 +161,332 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductListQuery displayLoader variables={queryVariables}>
|
<AvailableInGridAttributesQuery
|
||||||
{({ data, loading, refetch }) => {
|
variables={{ first: 6, ids: settings.columns }}
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
>
|
||||||
maybe(() => data.products.pageInfo),
|
{attributes => (
|
||||||
paginationState,
|
<TypedProductListQuery displayLoader variables={queryVariables}>
|
||||||
params
|
{({ data, loading, refetch }) => {
|
||||||
);
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
|
maybe(() => data.products.pageInfo),
|
||||||
|
paginationState,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
const handleBulkDelete = (data: productBulkDelete) => {
|
const handleBulkDelete = (data: productBulkDelete) => {
|
||||||
if (data.productBulkDelete.errors.length === 0) {
|
if (data.productBulkDelete.errors.length === 0) {
|
||||||
closeModal();
|
closeModal();
|
||||||
notify({
|
notify({
|
||||||
text: intl.formatMessage({
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
defaultMessage: "Products removed"
|
});
|
||||||
})
|
reset();
|
||||||
});
|
refetch();
|
||||||
reset();
|
}
|
||||||
refetch();
|
};
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBulkPublish = (data: productBulkPublish) => {
|
const handleBulkPublish = (data: productBulkPublish) => {
|
||||||
if (data.productBulkPublish.errors.length === 0) {
|
if (data.productBulkPublish.errors.length === 0) {
|
||||||
closeModal();
|
closeModal();
|
||||||
notify({
|
notify({
|
||||||
text: intl.formatMessage({
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
defaultMessage: "Changed publication status",
|
});
|
||||||
description: "product status update notification"
|
reset();
|
||||||
})
|
refetch();
|
||||||
});
|
}
|
||||||
reset();
|
};
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
||||||
{(productBulkDelete, productBulkDeleteOpts) => (
|
{(productBulkDelete, productBulkDeleteOpts) => (
|
||||||
<TypedProductBulkPublishMutation onCompleted={handleBulkPublish}>
|
<TypedProductBulkPublishMutation
|
||||||
{(productBulkPublish, productBulkPublishOpts) => {
|
onCompleted={handleBulkPublish}
|
||||||
const bulkDeleteMutationState = getMutationState(
|
>
|
||||||
productBulkDeleteOpts.called,
|
{(productBulkPublish, productBulkPublishOpts) => {
|
||||||
productBulkDeleteOpts.loading,
|
const bulkDeleteMutationState = getMutationState(
|
||||||
maybe(
|
productBulkDeleteOpts.called,
|
||||||
() => productBulkDeleteOpts.data.productBulkDelete.errors
|
productBulkDeleteOpts.loading,
|
||||||
)
|
maybe(
|
||||||
);
|
() =>
|
||||||
|
productBulkDeleteOpts.data.productBulkDelete.errors
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
const bulkPublishMutationState = getMutationState(
|
const bulkPublishMutationState = getMutationState(
|
||||||
productBulkPublishOpts.called,
|
productBulkPublishOpts.called,
|
||||||
productBulkPublishOpts.loading,
|
productBulkPublishOpts.loading,
|
||||||
maybe(
|
maybe(
|
||||||
() =>
|
() =>
|
||||||
productBulkPublishOpts.data.productBulkPublish.errors
|
productBulkPublishOpts.data.productBulkPublish
|
||||||
)
|
.errors
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProductListPage
|
<ProductListPage
|
||||||
currencySymbol={currencySymbol}
|
availableInGridAttributes={maybe(
|
||||||
currentTab={currentTab}
|
() =>
|
||||||
defaultSettings={
|
attributes.data.availableInGrid.edges.map(
|
||||||
defaultListSettings[ListViews.PRODUCT_LIST]
|
edge => edge.node
|
||||||
}
|
),
|
||||||
settings={settings}
|
[]
|
||||||
filtersList={createFilterChips(
|
)}
|
||||||
params,
|
currencySymbol={currencySymbol}
|
||||||
{
|
currentTab={currentTab}
|
||||||
currencySymbol,
|
defaultSettings={
|
||||||
locale
|
defaultListSettings[ListViews.PRODUCT_LIST]
|
||||||
},
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
})
|
gridAttributes={maybe(
|
||||||
}
|
() =>
|
||||||
title={intl.formatMessage({
|
attributes.data.grid.edges.map(
|
||||||
defaultMessage: "Publish Products",
|
edge => edge.node
|
||||||
description: "dialog header"
|
),
|
||||||
})}
|
[]
|
||||||
>
|
)}
|
||||||
<DialogContentText>
|
totalGridAttributes={maybe(
|
||||||
<FormattedMessage
|
() => attributes.data.availableInGrid.totalCount,
|
||||||
defaultMessage="Are you sure you want to publish {counter, plural,
|
0
|
||||||
one {this product}
|
)}
|
||||||
other {{displayQuantity} products}
|
settings={settings}
|
||||||
}?"
|
loading={attributes.loading}
|
||||||
description="dialog content"
|
hasMore={maybe(
|
||||||
values={{
|
() =>
|
||||||
counter: maybe(() => params.ids.length),
|
attributes.data.availableInGrid.pageInfo
|
||||||
displayQuantity: (
|
.hasNextPage,
|
||||||
<strong>
|
false
|
||||||
{maybe(() => params.ids.length)}
|
)}
|
||||||
</strong>
|
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
|
|
||||||
}
|
}
|
||||||
})
|
onNextPage={loadNextPage}
|
||||||
}
|
onPreviousPage={loadPreviousPage}
|
||||||
title={intl.formatMessage({
|
onUpdateListSettings={updateListSettings}
|
||||||
defaultMessage: "Unpublish Products",
|
pageInfo={pageInfo}
|
||||||
description: "dialog header"
|
onRowClick={id => () => navigate(productUrl(id))}
|
||||||
})}
|
onAll={() =>
|
||||||
>
|
changeFilters({
|
||||||
<DialogContentText>
|
status: undefined
|
||||||
<FormattedMessage
|
})
|
||||||
defaultMessage="Are you sure you want to unpublish {counter, plural,
|
}
|
||||||
|
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}
|
one {this product}
|
||||||
other {{displayQuantity} products}
|
other {{displayQuantity} products}
|
||||||
}?"
|
}?"
|
||||||
description="dialog content"
|
description="dialog content"
|
||||||
values={{
|
values={{
|
||||||
counter: maybe(() => params.ids.length),
|
counter: maybe(() => params.ids.length),
|
||||||
displayQuantity: (
|
displayQuantity: (
|
||||||
<strong>
|
<strong>
|
||||||
{maybe(() => params.ids.length)}
|
{maybe(() => params.ids.length)}
|
||||||
</strong>
|
</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>
|
<DeleteFilterTabDialog
|
||||||
</ActionDialog>
|
open={params.action === "delete-search"}
|
||||||
<SaveFilterTabDialog
|
confirmButtonState="default"
|
||||||
open={params.action === "save-search"}
|
onClose={closeModal}
|
||||||
confirmButtonState="default"
|
onSubmit={handleFilterTabDelete}
|
||||||
onClose={closeModal}
|
tabName={maybe(
|
||||||
onSubmit={handleFilterTabSave}
|
() => tabs[currentTab - 1].name,
|
||||||
/>
|
"..."
|
||||||
<DeleteFilterTabDialog
|
)}
|
||||||
open={params.action === "delete-search"}
|
/>
|
||||||
confirmButtonState="default"
|
</>
|
||||||
onClose={closeModal}
|
);
|
||||||
onSubmit={handleFilterTabDelete}
|
}}
|
||||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
</TypedProductBulkPublishMutation>
|
||||||
/>
|
)}
|
||||||
</>
|
</TypedProductBulkDeleteMutation>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</TypedProductBulkPublishMutation>
|
</TypedProductListQuery>
|
||||||
)}
|
)}
|
||||||
</TypedProductBulkDeleteMutation>
|
</AvailableInGridAttributesQuery>
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductListQuery>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductList;
|
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",
|
confirmButtonState: "default",
|
||||||
errors: [],
|
errors: [],
|
||||||
onClose: () => undefined,
|
onClose: () => undefined,
|
||||||
|
onFetch: () => undefined,
|
||||||
onOpen: () => undefined,
|
onOpen: () => undefined,
|
||||||
onSubmit: () => undefined,
|
onSubmit: () => undefined,
|
||||||
onToggle: () => undefined,
|
onToggle: () => undefined,
|
||||||
|
|
|
@ -26,12 +26,10 @@ const columns: ColumnPickerChoice[] = [
|
||||||
|
|
||||||
const props: ColumnPickerProps = {
|
const props: ColumnPickerProps = {
|
||||||
columns,
|
columns,
|
||||||
initial: true,
|
defaultColumns: [1, 3].map(index => columns[index].value),
|
||||||
onCancel: () => undefined,
|
initialColumns: [1, 3, 4, 6].map(index => columns[index].value),
|
||||||
onColumnToggle: () => undefined,
|
initialOpen: true,
|
||||||
onReset: () => undefined,
|
onSave: () => undefined
|
||||||
onSave: () => undefined,
|
|
||||||
selectedColumns: [1, 3, 4, 6].map(index => columns[index].value)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Generics / Column picker", module)
|
storiesOf("Generics / Column picker", module)
|
||||||
|
@ -42,4 +40,12 @@ storiesOf("Generics / Column picker", module)
|
||||||
))
|
))
|
||||||
.addDecorator(CardDecorator)
|
.addDecorator(CardDecorator)
|
||||||
.addDecorator(Decorator)
|
.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 placeholderImage from "@assets/images/placeholder255x255.png";
|
||||||
import { defaultListSettings } from "@saleor/config";
|
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 { ListViews } from "@saleor/types";
|
||||||
import { category as categoryFixture } from "../../../categories/fixtures";
|
|
||||||
import {
|
import {
|
||||||
|
fetchMoreProps,
|
||||||
filterPageProps,
|
filterPageProps,
|
||||||
filters,
|
filters,
|
||||||
listActionsProps,
|
listActionsProps,
|
||||||
|
@ -16,20 +18,22 @@ import ProductListPage, {
|
||||||
} from "../../../products/components/ProductListPage";
|
} from "../../../products/components/ProductListPage";
|
||||||
import Decorator from "../../Decorator";
|
import Decorator from "../../Decorator";
|
||||||
|
|
||||||
const products = categoryFixture(placeholderImage).products.edges.map(
|
const products = productListFixture(placeholderImage);
|
||||||
edge => edge.node
|
|
||||||
);
|
|
||||||
|
|
||||||
const props: ProductListPageProps = {
|
const props: ProductListPageProps = {
|
||||||
...listActionsProps,
|
...listActionsProps,
|
||||||
...pageListProps.default,
|
...pageListProps.default,
|
||||||
...filterPageProps,
|
...filterPageProps,
|
||||||
|
...fetchMoreProps,
|
||||||
|
availableInGridAttributes: attributes,
|
||||||
defaultSettings: defaultListSettings[ListViews.PRODUCT_LIST],
|
defaultSettings: defaultListSettings[ListViews.PRODUCT_LIST],
|
||||||
|
gridAttributes: attributes,
|
||||||
products,
|
products,
|
||||||
settings: {
|
settings: {
|
||||||
...pageListProps.default.settings,
|
...pageListProps.default.settings,
|
||||||
columns: ["isPublished", "productType", "price"]
|
columns: ["isPublished", "productType", "price"]
|
||||||
}
|
},
|
||||||
|
totalGridAttributes: attributes.length
|
||||||
};
|
};
|
||||||
|
|
||||||
storiesOf("Views / Products / Product list", module)
|
storiesOf("Views / Products / Product list", module)
|
||||||
|
|
|
@ -125,9 +125,8 @@ export interface ReorderEvent {
|
||||||
}
|
}
|
||||||
export type ReorderAction = (event: ReorderEvent) => void;
|
export type ReorderAction = (event: ReorderEvent) => void;
|
||||||
|
|
||||||
export interface FetchMoreProps<TData = string> {
|
export interface FetchMoreProps {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
onFetch: (value: TData) => void;
|
|
||||||
onFetchMore: () => 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