Add attributes as columns in product list view
This commit is contained in:
parent
bc2c6a9c71
commit
85cd2e875e
25 changed files with 1831 additions and 2569 deletions
|
@ -0,0 +1,229 @@
|
||||||
|
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 TableCellAvatar, {
|
||||||
|
AVATAR_MARGIN
|
||||||
|
} from "@saleor/components/TableCellAvatar";
|
||||||
|
import TableHead from "@saleor/components/TableHead";
|
||||||
|
import TablePagination from "@saleor/components/TablePagination";
|
||||||
|
import i18n from "@saleor/i18n";
|
||||||
|
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 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}>
|
||||||
|
{i18n.t("Name", { context: "object" })}
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.colType}>
|
||||||
|
{i18n.t("Type", { context: "object" })}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.colPublished}>
|
||||||
|
{i18n.t("Published", { context: "object" })}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className={classes.colPrice}>
|
||||||
|
{i18n.t("Price", { context: "object" })}
|
||||||
|
</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
|
||||||
|
? i18n.t("Published", {
|
||||||
|
context: "product status"
|
||||||
|
})
|
||||||
|
: i18n.t("Not published", {
|
||||||
|
context: "product status"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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}>
|
||||||
|
{i18n.t("No products found")}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
CategoryProductList.displayName = "CategoryProductList";
|
||||||
|
export default CategoryProductList;
|
0
src/categories/components/CategoryProductList/index.ts
Normal file
0
src/categories/components/CategoryProductList/index.ts
Normal file
|
@ -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}
|
||||||
|
|
|
@ -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,33 +42,39 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
columns,
|
columns,
|
||||||
|
defaultColumns,
|
||||||
hasMore,
|
hasMore,
|
||||||
initial = false,
|
initialColumns,
|
||||||
|
initialOpen = false,
|
||||||
loading,
|
loading,
|
||||||
selectedColumns,
|
|
||||||
total,
|
total,
|
||||||
onCancel,
|
|
||||||
onColumnToggle,
|
|
||||||
onFetchMore,
|
onFetchMore,
|
||||||
onReset,
|
|
||||||
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 (
|
||||||
|
@ -95,9 +110,9 @@ const ColumnPicker: React.FC<ColumnPickerProps> = props => {
|
||||||
selectedColumns={selectedColumns}
|
selectedColumns={selectedColumns}
|
||||||
total={total}
|
total={total}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onColumnToggle={onColumnToggle}
|
onColumnToggle={handleColumnToggle}
|
||||||
onFetchMore={onFetchMore}
|
onFetchMore={onFetchMore}
|
||||||
onReset={onReset}
|
onReset={handleReset}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
|
|
|
@ -61,6 +61,9 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
gridColumnEnd: "span 3",
|
gridColumnEnd: "span 3",
|
||||||
height: theme.spacing.unit * 3,
|
height: theme.spacing.unit * 3,
|
||||||
justifyContent: "center"
|
justifyContent: "center"
|
||||||
|
},
|
||||||
|
root: {
|
||||||
|
boxShadow: "0px 4px 4px rgba(0, 0, 0, 0.25)"
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -87,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
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
318
src/products/components/ProductList/ProductList.tsx
Normal file
318
src/products/components/ProductList/ProductList.tsx
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
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 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 i18n from "@saleor/i18n";
|
||||||
|
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 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}>
|
||||||
|
{i18n.t("Name", { context: "object" })}
|
||||||
|
</span>
|
||||||
|
</TableCell>
|
||||||
|
<DisplayColumn column="productType">
|
||||||
|
<TableCell className={classes.colType}>
|
||||||
|
{i18n.t("Type", { context: "object" })}
|
||||||
|
</TableCell>
|
||||||
|
</DisplayColumn>
|
||||||
|
<DisplayColumn column="isPublished">
|
||||||
|
<TableCell className={classes.colPublished}>
|
||||||
|
{i18n.t("Published", { context: "object" })}
|
||||||
|
</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}>
|
||||||
|
{i18n.t("Price", { context: "object" })}
|
||||||
|
</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
|
||||||
|
? i18n.t("Published", {
|
||||||
|
context: "product status"
|
||||||
|
})
|
||||||
|
: i18n.t("Not published", {
|
||||||
|
context: "product status"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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}>
|
||||||
|
{i18n.t("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,25 +6,25 @@ 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 {
|
||||||
import { sectionNames } from "@saleor/intl";
|
AvailableInGridAttributes_availableInGrid_edges_node,
|
||||||
import { AvailableInGridAttributes_attributes_edges_node } from "@saleor/products/types/AvailableInGridAttributes";
|
AvailableInGridAttributes_grid_edges_node
|
||||||
|
} from "@saleor/products/types/AvailableInGridAttributes";
|
||||||
|
import { ProductList_products_edges_node } from "@saleor/products/types/ProductList";
|
||||||
import {
|
import {
|
||||||
FetchMoreProps,
|
FetchMoreProps,
|
||||||
FilterPageProps,
|
FilterPageProps,
|
||||||
ListActions,
|
ListActions,
|
||||||
PageListProps
|
PageListProps
|
||||||
} from "@saleor/types";
|
} from "@saleor/types";
|
||||||
import { toggle } from "@saleor/utils/lists";
|
|
||||||
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
|
||||||
|
@ -32,10 +32,11 @@ export interface ProductListPageProps
|
||||||
ListActions,
|
ListActions,
|
||||||
FilterPageProps<ProductListUrlFilters>,
|
FilterPageProps<ProductListUrlFilters>,
|
||||||
FetchMoreProps {
|
FetchMoreProps {
|
||||||
|
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
gridAttributes: AvailableInGridAttributes_attributes_edges_node[];
|
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
||||||
totalGridAttributes: number;
|
totalGridAttributes: number;
|
||||||
products: CategoryDetails_category_products_edges_node[];
|
products: ProductList_products_edges_node[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
@ -52,6 +53,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
filtersList,
|
filtersList,
|
||||||
filterTabs,
|
filterTabs,
|
||||||
gridAttributes,
|
gridAttributes,
|
||||||
|
availableInGridAttributes,
|
||||||
hasMore,
|
hasMore,
|
||||||
initialSearch,
|
initialSearch,
|
||||||
loading,
|
loading,
|
||||||
|
@ -70,23 +72,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[] = [
|
||||||
{
|
{
|
||||||
|
@ -110,7 +98,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
}),
|
}),
|
||||||
value: "productType" as ProductListColumns
|
value: "productType" as ProductListColumns
|
||||||
},
|
},
|
||||||
...gridAttributes.map(attribute => ({
|
...availableInGridAttributes.map(attribute => ({
|
||||||
label: attribute.name,
|
label: attribute.name,
|
||||||
value: `attribute:${attribute.id}`
|
value: `attribute:${attribute.id}`
|
||||||
}))
|
}))
|
||||||
|
@ -122,14 +110,16 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
||||||
<ColumnPicker
|
<ColumnPicker
|
||||||
className={classes.columnPicker}
|
className={classes.columnPicker}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
defaultColumns={defaultSettings.columns}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
selectedColumns={selectedColumns}
|
initialColumns={settings.columns}
|
||||||
total={columns.length + totalGridAttributes}
|
total={
|
||||||
onColumnToggle={handleColumnToggle}
|
columns.length -
|
||||||
onCancel={handleCancel}
|
availableInGridAttributes.length +
|
||||||
|
totalGridAttributes
|
||||||
|
}
|
||||||
onFetchMore={onFetchMore}
|
onFetchMore={onFetchMore}
|
||||||
onReset={handleReset}
|
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
@ -168,7 +158,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
|
@ -377,11 +377,11 @@ export const TypedProductImageQuery = TypedQuery<
|
||||||
|
|
||||||
const availableInGridAttributes = gql`
|
const availableInGridAttributes = gql`
|
||||||
${pageInfoFragment}
|
${pageInfoFragment}
|
||||||
query AvailableInGridAttributes($first: Int!, $after: String) {
|
query GridAttributes($first: Int!, $after: String, $ids: [ID!]!) {
|
||||||
attributes(
|
availableInGrid: attributes(
|
||||||
first: $first
|
first: $first
|
||||||
after: $after
|
after: $after
|
||||||
filter: { availableInGrid: true }
|
filter: { availableInGrid: true, isVariantOnly: false }
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
@ -394,6 +394,15 @@ const availableInGridAttributes = gql`
|
||||||
}
|
}
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grid: attributes(first: 25, filter: { ids: $ids }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const AvailableInGridAttributesQuery = TypedQuery<
|
export const AvailableInGridAttributesQuery = TypedQuery<
|
||||||
|
|
|
@ -6,18 +6,18 @@
|
||||||
// GraphQL query operation: AvailableInGridAttributes
|
// GraphQL query operation: AvailableInGridAttributes
|
||||||
// ====================================================
|
// ====================================================
|
||||||
|
|
||||||
export interface AvailableInGridAttributes_attributes_edges_node {
|
export interface AvailableInGridAttributes_availableInGrid_edges_node {
|
||||||
__typename: "Attribute";
|
__typename: "Attribute";
|
||||||
id: string;
|
id: string;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvailableInGridAttributes_attributes_edges {
|
export interface AvailableInGridAttributes_availableInGrid_edges {
|
||||||
__typename: "AttributeCountableEdge";
|
__typename: "AttributeCountableEdge";
|
||||||
node: AvailableInGridAttributes_attributes_edges_node;
|
node: AvailableInGridAttributes_availableInGrid_edges_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvailableInGridAttributes_attributes_pageInfo {
|
export interface AvailableInGridAttributes_availableInGrid_pageInfo {
|
||||||
__typename: "PageInfo";
|
__typename: "PageInfo";
|
||||||
endCursor: string | null;
|
endCursor: string | null;
|
||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
|
@ -25,18 +25,36 @@ export interface AvailableInGridAttributes_attributes_pageInfo {
|
||||||
startCursor: string | null;
|
startCursor: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvailableInGridAttributes_attributes {
|
export interface AvailableInGridAttributes_availableInGrid {
|
||||||
__typename: "AttributeCountableConnection";
|
__typename: "AttributeCountableConnection";
|
||||||
edges: AvailableInGridAttributes_attributes_edges[];
|
edges: AvailableInGridAttributes_availableInGrid_edges[];
|
||||||
pageInfo: AvailableInGridAttributes_attributes_pageInfo;
|
pageInfo: AvailableInGridAttributes_availableInGrid_pageInfo;
|
||||||
totalCount: number | null;
|
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 {
|
export interface AvailableInGridAttributes {
|
||||||
attributes: AvailableInGridAttributes_attributes | null;
|
availableInGrid: AvailableInGridAttributes_availableInGrid | null;
|
||||||
|
grid: AvailableInGridAttributes_grid | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AvailableInGridAttributesVariables {
|
export interface AvailableInGridAttributesVariables {
|
||||||
first: number;
|
first: number;
|
||||||
after?: string | null;
|
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[];
|
||||||
|
}
|
|
@ -73,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 =
|
||||||
|
@ -149,8 +161,10 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AvailableInGridAttributesQuery variables={{ first: 6 }}>
|
<AvailableInGridAttributesQuery
|
||||||
{gridAttributes => (
|
variables={{ first: 6, ids: settings.columns }}
|
||||||
|
>
|
||||||
|
{attributes => (
|
||||||
<TypedProductListQuery displayLoader variables={queryVariables}>
|
<TypedProductListQuery displayLoader variables={queryVariables}>
|
||||||
{({ data, loading, refetch }) => {
|
{({ data, loading, refetch }) => {
|
||||||
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
|
||||||
|
@ -159,27 +173,27 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
||||||
params
|
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(commonMessages.savedChanges)
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
});
|
});
|
||||||
reset();
|
reset();
|
||||||
refetch();
|
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(commonMessages.savedChanges)
|
text: intl.formatMessage(commonMessages.savedChanges)
|
||||||
});
|
});
|
||||||
reset();
|
reset();
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
<TypedProductBulkDeleteMutation onCompleted={handleBulkDelete}>
|
||||||
|
@ -207,254 +221,272 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProductListPage
|
<ProductListPage
|
||||||
currencySymbol={currencySymbol}
|
availableInGridAttributes={maybe(
|
||||||
currentTab={currentTab}
|
() =>
|
||||||
defaultSettings={
|
attributes.data.availableInGrid.edges.map(
|
||||||
defaultListSettings[ListViews.PRODUCT_LIST]
|
edge => edge.node
|
||||||
}
|
),
|
||||||
gridAttributes={maybe(
|
[]
|
||||||
() =>
|
)}
|
||||||
gridAttributes.data.attributes.edges.map(
|
currencySymbol={currencySymbol}
|
||||||
edge => edge.node
|
currentTab={currentTab}
|
||||||
),
|
defaultSettings={
|
||||||
[]
|
defaultListSettings[ListViews.PRODUCT_LIST]
|
||||||
)}
|
}
|
||||||
totalGridAttributes={maybe(
|
gridAttributes={maybe(
|
||||||
() => gridAttributes.data.attributes.totalCount,
|
() =>
|
||||||
0
|
attributes.data.grid.edges.map(
|
||||||
)}
|
edge => edge.node
|
||||||
settings={settings}
|
),
|
||||||
loading={gridAttributes.loading}
|
[]
|
||||||
|
)}
|
||||||
|
totalGridAttributes={maybe(
|
||||||
|
() => attributes.data.availableInGrid.totalCount,
|
||||||
|
0
|
||||||
|
)}
|
||||||
|
settings={settings}
|
||||||
|
loading={attributes.loading}
|
||||||
hasMore={maybe(
|
hasMore={maybe(
|
||||||
() =>
|
() =>
|
||||||
gridAttributes.data.attributes.pageInfo
|
attributes.data.availableInGrid.pageInfo
|
||||||
.hasNextPage,
|
.hasNextPage,
|
||||||
false
|
false
|
||||||
)}
|
)}
|
||||||
filtersList={createFilterChips(
|
filtersList={createFilterChips(
|
||||||
params,
|
params,
|
||||||
{
|
{
|
||||||
currencySymbol,
|
currencySymbol,
|
||||||
locale
|
locale
|
||||||
},
|
},
|
||||||
changeFilterField,
|
changeFilterField,
|
||||||
intl
|
intl
|
||||||
)}
|
)}
|
||||||
onAdd={() => navigate(productAddUrl)}
|
onAdd={() => navigate(productAddUrl)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
products={maybe(() =>
|
products={maybe(() =>
|
||||||
data.products.edges.map(edge => edge.node)
|
data.products.edges.map(edge => edge.node)
|
||||||
)}
|
)}
|
||||||
onFetchMore={() =>
|
onFetchMore={() =>
|
||||||
gridAttributes.loadMore(
|
attributes.loadMore(
|
||||||
(prev, next) => {
|
(prev, next) => {
|
||||||
if (
|
if (
|
||||||
prev.attributes.pageInfo.endCursor ===
|
prev.availableInGrid.pageInfo.endCursor ===
|
||||||
next.attributes.pageInfo.endCursor
|
next.availableInGrid.pageInfo.endCursor
|
||||||
) {
|
) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
attributes: {
|
availableInGrid: {
|
||||||
...prev.attributes,
|
...prev.availableInGrid,
|
||||||
edges: [
|
edges: [
|
||||||
...prev.attributes.edges,
|
...prev.availableInGrid.edges,
|
||||||
...next.attributes.edges
|
...next.availableInGrid.edges
|
||||||
],
|
],
|
||||||
pageInfo: next.attributes.pageInfo
|
pageInfo: next.availableInGrid.pageInfo
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
after:
|
||||||
|
attributes.data.availableInGrid.pageInfo
|
||||||
|
.endCursor
|
||||||
}
|
}
|
||||||
};
|
)
|
||||||
},
|
|
||||||
{
|
|
||||||
after:
|
|
||||||
gridAttributes.data.attributes.pageInfo
|
|
||||||
.endCursor
|
|
||||||
}
|
}
|
||||||
)
|
onNextPage={loadNextPage}
|
||||||
}
|
onPreviousPage={loadPreviousPage}
|
||||||
onNextPage={loadNextPage}
|
onUpdateListSettings={updateListSettings}
|
||||||
onPreviousPage={loadPreviousPage}
|
pageInfo={pageInfo}
|
||||||
onUpdateListSettings={updateListSettings}
|
onRowClick={id => () => navigate(productUrl(id))}
|
||||||
pageInfo={pageInfo}
|
onAll={() =>
|
||||||
onRowClick={id => () => navigate(productUrl(id))}
|
changeFilters({
|
||||||
onAll={() =>
|
status: undefined
|
||||||
changeFilters({
|
})
|
||||||
status: undefined
|
}
|
||||||
})
|
toolbar={
|
||||||
}
|
<>
|
||||||
toolbar={
|
<Button
|
||||||
<>
|
color="primary"
|
||||||
<Button
|
onClick={() =>
|
||||||
color="primary"
|
openModal("unpublish", listElements)
|
||||||
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
|
<FormattedMessage
|
||||||
defaultMessage="Unpublish"
|
defaultMessage="Are you sure you want to delete {counter, plural,
|
||||||
description="unpublish product, button"
|
one {this product}
|
||||||
|
other {{displayQuantity} products}
|
||||||
|
}?"
|
||||||
|
description="dialog content"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: (
|
||||||
|
<strong>
|
||||||
|
{maybe(() => params.ids.length)}
|
||||||
|
</strong>
|
||||||
|
)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</DialogContentText>
|
||||||
<Button
|
</ActionDialog>
|
||||||
color="primary"
|
<ActionDialog
|
||||||
onClick={() => openModal("publish", listElements)}
|
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
|
<FormattedMessage
|
||||||
defaultMessage="Publish"
|
defaultMessage="Are you sure you want to publish {counter, plural,
|
||||||
description="publish product, button"
|
one {this product}
|
||||||
|
other {{displayQuantity} products}
|
||||||
|
}?"
|
||||||
|
description="dialog content"
|
||||||
|
values={{
|
||||||
|
counter: maybe(() => params.ids.length),
|
||||||
|
displayQuantity: (
|
||||||
|
<strong>
|
||||||
|
{maybe(() => params.ids.length)}
|
||||||
|
</strong>
|
||||||
|
)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</DialogContentText>
|
||||||
<IconButton
|
</ActionDialog>
|
||||||
color="primary"
|
<ActionDialog
|
||||||
onClick={() => openModal("delete", listElements)}
|
open={params.action === "unpublish"}
|
||||||
>
|
confirmButtonState={bulkPublishMutationState}
|
||||||
<DeleteIcon />
|
onClose={closeModal}
|
||||||
</IconButton>
|
onConfirm={() =>
|
||||||
</>
|
productBulkPublish({
|
||||||
}
|
variables: {
|
||||||
isChecked={isSelected}
|
ids: params.ids,
|
||||||
selected={listElements.length}
|
isPublished: false
|
||||||
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
|
|
||||||
}
|
}
|
||||||
})
|
title={intl.formatMessage({
|
||||||
}
|
defaultMessage: "Unpublish Products",
|
||||||
title={intl.formatMessage({
|
description: "dialog header"
|
||||||
defaultMessage: "Publish Products",
|
})}
|
||||||
description: "dialog header"
|
>
|
||||||
})}
|
<DialogContentText>
|
||||||
>
|
<FormattedMessage
|
||||||
<DialogContentText>
|
defaultMessage="Are you sure you want to unpublish {counter, plural,
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Are you sure you want to publish {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>
|
||||||
|
<SaveFilterTabDialog
|
||||||
|
open={params.action === "save-search"}
|
||||||
|
confirmButtonState="default"
|
||||||
|
onClose={closeModal}
|
||||||
|
onSubmit={handleFilterTabSave}
|
||||||
/>
|
/>
|
||||||
</DialogContentText>
|
<DeleteFilterTabDialog
|
||||||
</ActionDialog>
|
open={params.action === "delete-search"}
|
||||||
<ActionDialog
|
confirmButtonState="default"
|
||||||
open={params.action === "unpublish"}
|
onClose={closeModal}
|
||||||
confirmButtonState={bulkPublishMutationState}
|
onSubmit={handleFilterTabDelete}
|
||||||
onClose={closeModal}
|
tabName={maybe(
|
||||||
onConfirm={() =>
|
() => tabs[currentTab - 1].name,
|
||||||
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"}
|
</TypedProductBulkPublishMutation>
|
||||||
confirmButtonState="default"
|
)}
|
||||||
onClose={closeModal}
|
</TypedProductBulkDeleteMutation>
|
||||||
onSubmit={handleFilterTabSave}
|
);
|
||||||
/>
|
}}
|
||||||
<DeleteFilterTabDialog
|
</TypedProductListQuery>
|
||||||
open={params.action === "delete-search"}
|
)}
|
||||||
confirmButtonState="default"
|
</AvailableInGridAttributesQuery>
|
||||||
onClose={closeModal}
|
|
||||||
onSubmit={handleFilterTabDelete}
|
|
||||||
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductBulkPublishMutation>
|
|
||||||
)}
|
|
||||||
</TypedProductBulkDeleteMutation>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</TypedProductListQuery>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default ProductList;
|
export default ProductList;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
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