Add sorting ability to product list
This commit is contained in:
parent
5dea135b2e
commit
87fca41f07
13 changed files with 270 additions and 41 deletions
|
@ -24,6 +24,12 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
height: 24
|
height: 24
|
||||||
},
|
},
|
||||||
|
labelContainerCenter: {
|
||||||
|
justifyContent: "center"
|
||||||
|
},
|
||||||
|
labelContainerRight: {
|
||||||
|
justifyContent: "flex-end"
|
||||||
|
},
|
||||||
root: {
|
root: {
|
||||||
cursor: "pointer"
|
cursor: "pointer"
|
||||||
}
|
}
|
||||||
|
@ -31,18 +37,31 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
|
||||||
export type TableCellHeaderArrowDirection = "asc" | "desc";
|
export type TableCellHeaderArrowDirection = "asc" | "desc";
|
||||||
export type TableCellHeaderArrowPosition = "left" | "right";
|
export type TableCellHeaderArrowPosition = "left" | "right";
|
||||||
export interface TableCellHeader extends TableCellProps {
|
export interface TableCellHeaderProps extends TableCellProps {
|
||||||
arrowPosition?: TableCellHeaderArrowPosition;
|
arrowPosition?: TableCellHeaderArrowPosition;
|
||||||
direction?: TableCellHeaderArrowDirection;
|
direction?: TableCellHeaderArrowDirection;
|
||||||
|
textAlign?: "left" | "center" | "right";
|
||||||
}
|
}
|
||||||
|
|
||||||
const TableCellHeader: React.FC<TableCellHeader> = props => {
|
const TableCellHeader: React.FC<TableCellHeaderProps> = props => {
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
const { arrowPosition, children, className, direction, ...rest } = props;
|
const {
|
||||||
|
arrowPosition,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
direction,
|
||||||
|
textAlign,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableCell {...rest} className={classNames(className, classes.root)}>
|
<TableCell {...rest} className={classNames(classes.root, className)}>
|
||||||
<div className={classes.labelContainer}>
|
<div
|
||||||
|
className={classNames(classes.labelContainer, {
|
||||||
|
[classes.labelContainerCenter]: textAlign === "center",
|
||||||
|
[classes.labelContainerRight]: textAlign === "right"
|
||||||
|
})}
|
||||||
|
>
|
||||||
{!!direction && arrowPosition === "left" && (
|
{!!direction && arrowPosition === "left" && (
|
||||||
<ArrowSort
|
<ArrowSort
|
||||||
className={classNames(classes.arrow, classes.arrowLeft, {
|
className={classNames(classes.arrow, classes.arrowLeft, {
|
||||||
|
@ -65,6 +84,7 @@ const TableCellHeader: React.FC<TableCellHeader> = props => {
|
||||||
|
|
||||||
TableCellHeader.displayName = "TableCellHeader";
|
TableCellHeader.displayName = "TableCellHeader";
|
||||||
TableCellHeader.defaultProps = {
|
TableCellHeader.defaultProps = {
|
||||||
arrowPosition: "left"
|
arrowPosition: "left",
|
||||||
|
textAlign: "left"
|
||||||
};
|
};
|
||||||
export default TableCellHeader;
|
export default TableCellHeader;
|
||||||
|
|
16
src/misc.ts
16
src/misc.ts
|
@ -4,11 +4,13 @@ import urlJoin from "url-join";
|
||||||
|
|
||||||
import { defineMessages, IntlShape } from "react-intl";
|
import { defineMessages, IntlShape } from "react-intl";
|
||||||
import { ConfirmButtonTransitionState } from "./components/ConfirmButton/ConfirmButton";
|
import { ConfirmButtonTransitionState } from "./components/ConfirmButton/ConfirmButton";
|
||||||
|
import { TableCellHeaderArrowDirection } from "./components/TableCellHeader";
|
||||||
import { APP_MOUNT_URI } from "./config";
|
import { APP_MOUNT_URI } from "./config";
|
||||||
import { AddressType } from "./customers/types";
|
import { AddressType } from "./customers/types";
|
||||||
import { PartialMutationProviderOutput, UserError } from "./types";
|
import { PartialMutationProviderOutput, UserError } from "./types";
|
||||||
import {
|
import {
|
||||||
AuthorizationKeyType,
|
AuthorizationKeyType,
|
||||||
|
OrderDirection,
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
PaymentChargeStatusEnum,
|
PaymentChargeStatusEnum,
|
||||||
TaxRateType
|
TaxRateType
|
||||||
|
@ -478,3 +480,17 @@ export function findInEnum<TEnum extends object>(
|
||||||
|
|
||||||
throw new Error(`Key ${needle} not found in enum`);
|
throw new Error(`Key ${needle} not found in enum`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getOrderDirection(asc: boolean): OrderDirection {
|
||||||
|
return asc ? OrderDirection.ASC : OrderDirection.DESC;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getArrowDirection(
|
||||||
|
order: OrderDirection
|
||||||
|
): TableCellHeaderArrowDirection {
|
||||||
|
return order === OrderDirection.ASC ? "asc" : "desc";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseBoolean(a: string): boolean {
|
||||||
|
return a === "true";
|
||||||
|
}
|
||||||
|
|
|
@ -20,18 +20,22 @@ import StatusLabel from "@saleor/components/StatusLabel";
|
||||||
import TableCellAvatar, {
|
import TableCellAvatar, {
|
||||||
AVATAR_MARGIN
|
AVATAR_MARGIN
|
||||||
} from "@saleor/components/TableCellAvatar";
|
} from "@saleor/components/TableCellAvatar";
|
||||||
|
import TableCellHeader from "@saleor/components/TableCellHeader";
|
||||||
import TableHead from "@saleor/components/TableHead";
|
import TableHead from "@saleor/components/TableHead";
|
||||||
import TablePagination from "@saleor/components/TablePagination";
|
import TablePagination from "@saleor/components/TablePagination";
|
||||||
import { ProductListColumns } from "@saleor/config";
|
import { ProductListColumns } from "@saleor/config";
|
||||||
import { maybe, renderCollection } from "@saleor/misc";
|
import { getArrowDirection, maybe, renderCollection } from "@saleor/misc";
|
||||||
import {
|
import {
|
||||||
getAttributeIdFromColumnValue,
|
getAttributeIdFromColumnValue,
|
||||||
isAttributeColumnValue
|
isAttributeColumnValue
|
||||||
} from "@saleor/products/components/ProductListPage/utils";
|
} from "@saleor/products/components/ProductListPage/utils";
|
||||||
import { AvailableInGridAttributes_grid_edges_node } from "@saleor/products/types/AvailableInGridAttributes";
|
import { AvailableInGridAttributes_grid_edges_node } from "@saleor/products/types/AvailableInGridAttributes";
|
||||||
import { ProductList_products_edges_node } from "@saleor/products/types/ProductList";
|
import { ProductList_products_edges_node } from "@saleor/products/types/ProductList";
|
||||||
import { ListActions, ListProps } from "@saleor/types";
|
import { ListActions, ListProps, SortPage } from "@saleor/types";
|
||||||
import TDisplayColumn from "@saleor/utils/columns/DisplayColumn";
|
import { ProductOrder, ProductOrderField } from "@saleor/types/globalTypes";
|
||||||
|
import TDisplayColumn, {
|
||||||
|
DisplayColumnProps
|
||||||
|
} from "@saleor/utils/columns/DisplayColumn";
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
|
@ -87,12 +91,18 @@ const styles = (theme: Theme) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const DisplayColumn = TDisplayColumn as React.FunctionComponent<
|
||||||
|
DisplayColumnProps<ProductListColumns>
|
||||||
|
>;
|
||||||
|
|
||||||
interface ProductListProps
|
interface ProductListProps
|
||||||
extends ListProps<ProductListColumns>,
|
extends ListProps<ProductListColumns>,
|
||||||
ListActions,
|
ListActions,
|
||||||
|
SortPage<ProductOrderField>,
|
||||||
WithStyles<typeof styles> {
|
WithStyles<typeof styles> {
|
||||||
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
||||||
products: ProductList_products_edges_node[];
|
products: ProductList_products_edges_node[];
|
||||||
|
sort: ProductOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
|
@ -105,19 +115,18 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
pageInfo,
|
pageInfo,
|
||||||
products,
|
products,
|
||||||
selected,
|
selected,
|
||||||
|
sort,
|
||||||
toggle,
|
toggle,
|
||||||
toggleAll,
|
toggleAll,
|
||||||
toolbar,
|
toolbar,
|
||||||
onNextPage,
|
onNextPage,
|
||||||
onPreviousPage,
|
onPreviousPage,
|
||||||
onUpdateListSettings,
|
onUpdateListSettings,
|
||||||
onRowClick
|
onRowClick,
|
||||||
|
onSort
|
||||||
}: ProductListProps) => {
|
}: ProductListProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const DisplayColumn: React.FC<{ column: ProductListColumns }> = props => (
|
|
||||||
<TDisplayColumn displayColumns={settings.columns} {...props} />
|
|
||||||
);
|
|
||||||
const gridAttributesFromSettings = settings.columns.filter(
|
const gridAttributesFromSettings = settings.columns.filter(
|
||||||
isAttributeColumnValue
|
isAttributeColumnValue
|
||||||
);
|
);
|
||||||
|
@ -129,16 +138,22 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col />
|
<col />
|
||||||
<col className={classes.colName} />
|
<col className={classes.colName} />
|
||||||
<DisplayColumn column="productType">
|
<DisplayColumn
|
||||||
|
column="productType"
|
||||||
|
displayColumns={settings.columns}
|
||||||
|
>
|
||||||
<col className={classes.colType} />
|
<col className={classes.colType} />
|
||||||
</DisplayColumn>
|
</DisplayColumn>
|
||||||
<DisplayColumn column="isPublished">
|
<DisplayColumn
|
||||||
|
column="isPublished"
|
||||||
|
displayColumns={settings.columns}
|
||||||
|
>
|
||||||
<col className={classes.colPublished} />
|
<col className={classes.colPublished} />
|
||||||
</DisplayColumn>
|
</DisplayColumn>
|
||||||
{gridAttributesFromSettings.map(gridAttribute => (
|
{gridAttributesFromSettings.map(gridAttribute => (
|
||||||
<col className={classes.colAttribute} key={gridAttribute} />
|
<col className={classes.colAttribute} key={gridAttribute} />
|
||||||
))}
|
))}
|
||||||
<DisplayColumn column="price">
|
<DisplayColumn column="price" displayColumns={settings.columns}>
|
||||||
<col className={classes.colPrice} />
|
<col className={classes.colPrice} />
|
||||||
</DisplayColumn>
|
</DisplayColumn>
|
||||||
</colgroup>
|
</colgroup>
|
||||||
|
@ -150,30 +165,59 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
toggleAll={toggleAll}
|
toggleAll={toggleAll}
|
||||||
toolbar={toolbar}
|
toolbar={toolbar}
|
||||||
>
|
>
|
||||||
<TableCell
|
<TableCellHeader
|
||||||
|
arrowPosition="right"
|
||||||
className={classNames(classes.colName, {
|
className={classNames(classes.colName, {
|
||||||
[classes.colNameFixed]: settings.columns.length > 4
|
[classes.colNameFixed]: settings.columns.length > 4
|
||||||
})}
|
})}
|
||||||
|
direction={
|
||||||
|
sort.field === ProductOrderField.NAME
|
||||||
|
? getArrowDirection(sort.direction)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={() => onSort(ProductOrderField.NAME)}
|
||||||
>
|
>
|
||||||
<span className={classes.colNameHeader}>
|
<span className={classes.colNameHeader}>
|
||||||
<FormattedMessage defaultMessage="Name" description="product" />
|
<FormattedMessage defaultMessage="Name" description="product" />
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCellHeader>
|
||||||
<DisplayColumn column="productType">
|
<DisplayColumn
|
||||||
<TableCell className={classes.colType}>
|
column="productType"
|
||||||
|
displayColumns={settings.columns}
|
||||||
|
>
|
||||||
|
<TableCellHeader
|
||||||
|
className={classes.colType}
|
||||||
|
direction={
|
||||||
|
sort.field === ProductOrderField.TYPE
|
||||||
|
? getArrowDirection(sort.direction)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={() => onSort(ProductOrderField.TYPE)}
|
||||||
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Type"
|
defaultMessage="Type"
|
||||||
description="product type"
|
description="product type"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCellHeader>
|
||||||
</DisplayColumn>
|
</DisplayColumn>
|
||||||
<DisplayColumn column="isPublished">
|
<DisplayColumn
|
||||||
<TableCell className={classes.colPublished}>
|
column="isPublished"
|
||||||
|
displayColumns={settings.columns}
|
||||||
|
>
|
||||||
|
<TableCellHeader
|
||||||
|
className={classes.colPublished}
|
||||||
|
direction={
|
||||||
|
sort.field === ProductOrderField.PUBLISHED
|
||||||
|
? getArrowDirection(sort.direction)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
onClick={() => onSort(ProductOrderField.PUBLISHED)}
|
||||||
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Published"
|
defaultMessage="Published"
|
||||||
description="product status"
|
description="product status"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCellHeader>
|
||||||
</DisplayColumn>
|
</DisplayColumn>
|
||||||
{gridAttributesFromSettings.map(gridAttributeFromSettings => (
|
{gridAttributesFromSettings.map(gridAttributeFromSettings => (
|
||||||
<TableCell
|
<TableCell
|
||||||
|
@ -192,13 +236,22 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
<DisplayColumn column="price">
|
<DisplayColumn column="price" displayColumns={settings.columns}>
|
||||||
<TableCell className={classes.colPrice}>
|
<TableCellHeader
|
||||||
|
className={classes.colPrice}
|
||||||
|
direction={
|
||||||
|
sort.field === ProductOrderField.PRICE
|
||||||
|
? getArrowDirection(sort.direction)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
textAlign="right"
|
||||||
|
onClick={() => onSort(ProductOrderField.PRICE)}
|
||||||
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Price"
|
defaultMessage="Price"
|
||||||
description="product price"
|
description="product price"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCellHeader>
|
||||||
</DisplayColumn>
|
</DisplayColumn>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
|
@ -246,7 +299,10 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
>
|
>
|
||||||
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
{maybe<React.ReactNode>(() => product.name, <Skeleton />)}
|
||||||
</TableCellAvatar>
|
</TableCellAvatar>
|
||||||
<DisplayColumn column="productType">
|
<DisplayColumn
|
||||||
|
column="productType"
|
||||||
|
displayColumns={settings.columns}
|
||||||
|
>
|
||||||
<TableCell className={classes.colType}>
|
<TableCell className={classes.colType}>
|
||||||
{product && product.productType ? (
|
{product && product.productType ? (
|
||||||
product.productType.name
|
product.productType.name
|
||||||
|
@ -255,7 +311,10 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</DisplayColumn>
|
</DisplayColumn>
|
||||||
<DisplayColumn column="isPublished">
|
<DisplayColumn
|
||||||
|
column="isPublished"
|
||||||
|
displayColumns={settings.columns}
|
||||||
|
>
|
||||||
<TableCell className={classes.colPublished}>
|
<TableCell className={classes.colPublished}>
|
||||||
{product &&
|
{product &&
|
||||||
maybe(() => product.isAvailable !== undefined) ? (
|
maybe(() => product.isAvailable !== undefined) ? (
|
||||||
|
@ -299,7 +358,10 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
|
||||||
}, <Skeleton />)}
|
}, <Skeleton />)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
<DisplayColumn column="price">
|
<DisplayColumn
|
||||||
|
column="price"
|
||||||
|
displayColumns={settings.columns}
|
||||||
|
>
|
||||||
<TableCell className={classes.colPrice}>
|
<TableCell className={classes.colPrice}>
|
||||||
{maybe(() => product.basePrice) &&
|
{maybe(() => product.basePrice) &&
|
||||||
maybe(() => product.basePrice.amount) !== undefined &&
|
maybe(() => product.basePrice.amount) !== undefined &&
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
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 { Theme } from "@material-ui/core/styles";
|
import { Theme } from "@material-ui/core/styles";
|
||||||
|
|
||||||
import makeStyles from "@material-ui/styles/makeStyles";
|
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";
|
||||||
|
@ -22,8 +21,11 @@ import {
|
||||||
FetchMoreProps,
|
FetchMoreProps,
|
||||||
FilterPageProps,
|
FilterPageProps,
|
||||||
ListActions,
|
ListActions,
|
||||||
PageListProps
|
PageListProps,
|
||||||
|
SortPage
|
||||||
} from "@saleor/types";
|
} from "@saleor/types";
|
||||||
|
import { ProductOrder, ProductOrderField } from "@saleor/types/globalTypes";
|
||||||
|
import { ProductListUrlFilters } from "../../urls";
|
||||||
import ProductList from "../ProductList";
|
import ProductList from "../ProductList";
|
||||||
import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter";
|
import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter";
|
||||||
|
|
||||||
|
@ -31,12 +33,14 @@ export interface ProductListPageProps
|
||||||
extends PageListProps<ProductListColumns>,
|
extends PageListProps<ProductListColumns>,
|
||||||
ListActions,
|
ListActions,
|
||||||
FilterPageProps<ProductFilterKeys>,
|
FilterPageProps<ProductFilterKeys>,
|
||||||
FetchMoreProps {
|
FetchMoreProps,
|
||||||
|
SortPage<ProductOrderField> {
|
||||||
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
gridAttributes: AvailableInGridAttributes_grid_edges_node[];
|
||||||
totalGridAttributes: number;
|
totalGridAttributes: number;
|
||||||
products: ProductList_products_edges_node[];
|
products: ProductList_products_edges_node[];
|
||||||
|
sort: ProductOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { useIntl } from "react-intl";
|
||||||
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
import { Route, RouteComponentProps, Switch } from "react-router-dom";
|
||||||
|
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
|
import { parseBoolean } from "@saleor/misc";
|
||||||
import { WindowTitle } from "../components/WindowTitle";
|
import { WindowTitle } from "../components/WindowTitle";
|
||||||
import {
|
import {
|
||||||
productAddPath,
|
productAddPath,
|
||||||
|
@ -28,7 +29,10 @@ const ProductList: React.StatelessComponent<RouteComponentProps<any>> = ({
|
||||||
location
|
location
|
||||||
}) => {
|
}) => {
|
||||||
const qs = parseQs(location.search.substr(1));
|
const qs = parseQs(location.search.substr(1));
|
||||||
const params: ProductListUrlQueryParams = qs;
|
const params: ProductListUrlQueryParams = {
|
||||||
|
...qs,
|
||||||
|
asc: parseBoolean(qs.asc)
|
||||||
|
};
|
||||||
return <ProductListComponent params={params} />;
|
return <ProductListComponent params={params} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,7 @@ const productListQuery = gql`
|
||||||
$last: Int
|
$last: Int
|
||||||
$before: String
|
$before: String
|
||||||
$filter: ProductFilterInput
|
$filter: ProductFilterInput
|
||||||
|
$sort: ProductOrder
|
||||||
) {
|
) {
|
||||||
products(
|
products(
|
||||||
before: $before
|
before: $before
|
||||||
|
@ -225,6 +226,7 @@ const productListQuery = gql`
|
||||||
first: $first
|
first: $first
|
||||||
last: $last
|
last: $last
|
||||||
filter: $filter
|
filter: $filter
|
||||||
|
sortBy: $sort
|
||||||
) {
|
) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
import { ProductFilterInput } from "./../../types/globalTypes";
|
import { ProductFilterInput, ProductOrder } from "./../../types/globalTypes";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL query operation: ProductList
|
// GraphQL query operation: ProductList
|
||||||
|
@ -82,4 +82,5 @@ export interface ProductListVariables {
|
||||||
last?: number | null;
|
last?: number | null;
|
||||||
before?: string | null;
|
before?: string | null;
|
||||||
filter?: ProductFilterInput | null;
|
filter?: ProductFilterInput | null;
|
||||||
|
sort?: ProductOrder | null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
Dialog,
|
Dialog,
|
||||||
Filters,
|
Filters,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
Sort,
|
||||||
TabActionDialog
|
TabActionDialog
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
|
@ -29,9 +30,17 @@ export enum ProductListUrlFiltersEnum {
|
||||||
query = "query"
|
query = "query"
|
||||||
}
|
}
|
||||||
export type ProductListUrlFilters = Filters<ProductListUrlFiltersEnum>;
|
export type ProductListUrlFilters = Filters<ProductListUrlFiltersEnum>;
|
||||||
|
export enum ProductListUrlSortFields {
|
||||||
|
name = "name",
|
||||||
|
productType = "type",
|
||||||
|
status = "status",
|
||||||
|
price = "price"
|
||||||
|
}
|
||||||
|
export type ProductListUrlSort = Sort<ProductListUrlSortFields>;
|
||||||
export type ProductListUrlQueryParams = BulkAction &
|
export type ProductListUrlQueryParams = BulkAction &
|
||||||
Dialog<ProductListUrlDialog> &
|
Dialog<ProductListUrlDialog> &
|
||||||
ProductListUrlFilters &
|
ProductListUrlFilters &
|
||||||
|
ProductListUrlSort &
|
||||||
Pagination &
|
Pagination &
|
||||||
ActiveTab;
|
ActiveTab;
|
||||||
export const productListUrl = (params?: ProductListUrlQueryParams): string =>
|
export const productListUrl = (params?: ProductListUrlQueryParams): string =>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import usePaginator, {
|
||||||
import useShop from "@saleor/hooks/useShop";
|
import useShop from "@saleor/hooks/useShop";
|
||||||
import { commonMessages } from "@saleor/intl";
|
import { commonMessages } from "@saleor/intl";
|
||||||
import { getMutationState, maybe } from "@saleor/misc";
|
import { getMutationState, maybe } from "@saleor/misc";
|
||||||
|
import { ProductListVariables } from "@saleor/products/types/ProductList";
|
||||||
import { ListViews } from "@saleor/types";
|
import { ListViews } from "@saleor/types";
|
||||||
import ProductListPage from "../../components/ProductListPage";
|
import ProductListPage from "../../components/ProductListPage";
|
||||||
import {
|
import {
|
||||||
|
@ -52,6 +53,7 @@ import {
|
||||||
getFilterVariables,
|
getFilterVariables,
|
||||||
saveFilterTab
|
saveFilterTab
|
||||||
} from "./filters";
|
} from "./filters";
|
||||||
|
import { getSortUrlVariables, getSortQueryVariables } from "./sort";
|
||||||
|
|
||||||
interface ProductListProps {
|
interface ProductListProps {
|
||||||
params: ProductListUrlQueryParams;
|
params: ProductListUrlQueryParams;
|
||||||
|
@ -152,10 +154,13 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
||||||
|
|
||||||
const paginationState = createPaginationState(settings.rowNumber, params);
|
const paginationState = createPaginationState(settings.rowNumber, params);
|
||||||
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
|
||||||
const queryVariables = React.useMemo(
|
const filter = getFilterVariables(params);
|
||||||
|
const sort = getSortQueryVariables(params);
|
||||||
|
const queryVariables = React.useMemo<ProductListVariables>(
|
||||||
() => ({
|
() => ({
|
||||||
...paginationState,
|
...paginationState,
|
||||||
filter: getFilterVariables(params)
|
filter,
|
||||||
|
sort
|
||||||
}),
|
}),
|
||||||
[params, settings.rowNumber]
|
[params, settings.rowNumber]
|
||||||
);
|
);
|
||||||
|
@ -224,6 +229,19 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProductListPage
|
<ProductListPage
|
||||||
|
sort={sort}
|
||||||
|
onSort={field =>
|
||||||
|
navigate(
|
||||||
|
productListUrl({
|
||||||
|
...params,
|
||||||
|
...getSortUrlVariables(
|
||||||
|
field,
|
||||||
|
sort.field,
|
||||||
|
params
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
availableInGridAttributes={maybe(
|
availableInGridAttributes={maybe(
|
||||||
() =>
|
() =>
|
||||||
attributes.data.availableInGrid.edges.map(
|
attributes.data.availableInGrid.edges.map(
|
||||||
|
|
66
src/products/views/ProductList/sort.ts
Normal file
66
src/products/views/ProductList/sort.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { getOrderDirection } from "@saleor/misc";
|
||||||
|
import {
|
||||||
|
ProductListUrlQueryParams,
|
||||||
|
ProductListUrlSortFields as ProductListUrlSortField
|
||||||
|
} from "@saleor/products/urls";
|
||||||
|
import { Sort } from "@saleor/types";
|
||||||
|
import { ProductOrderField } from "@saleor/types/globalTypes";
|
||||||
|
|
||||||
|
export function getSortQueryVariables(params: ProductListUrlQueryParams) {
|
||||||
|
return {
|
||||||
|
direction: getOrderDirection(params.asc),
|
||||||
|
field: getSortQueryField(params.sort)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSortQueryField(
|
||||||
|
sort: ProductListUrlSortField
|
||||||
|
): ProductOrderField {
|
||||||
|
switch (sort) {
|
||||||
|
case ProductListUrlSortField.name:
|
||||||
|
return ProductOrderField.NAME;
|
||||||
|
case ProductListUrlSortField.price:
|
||||||
|
return ProductOrderField.PRICE;
|
||||||
|
case ProductListUrlSortField.productType:
|
||||||
|
return ProductOrderField.TYPE;
|
||||||
|
case ProductListUrlSortField.status:
|
||||||
|
return ProductOrderField.PUBLISHED;
|
||||||
|
default:
|
||||||
|
return ProductOrderField.NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSortUrlField(
|
||||||
|
sort: ProductOrderField
|
||||||
|
): ProductListUrlSortField {
|
||||||
|
switch (sort) {
|
||||||
|
case ProductOrderField.NAME:
|
||||||
|
return ProductListUrlSortField.name;
|
||||||
|
case ProductOrderField.PRICE:
|
||||||
|
return ProductListUrlSortField.price;
|
||||||
|
case ProductOrderField.TYPE:
|
||||||
|
return ProductListUrlSortField.productType;
|
||||||
|
case ProductOrderField.PUBLISHED:
|
||||||
|
return ProductListUrlSortField.status;
|
||||||
|
default:
|
||||||
|
return ProductListUrlSortField.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSortUrlVariables(
|
||||||
|
field: ProductOrderField,
|
||||||
|
selectedField: ProductOrderField,
|
||||||
|
params: Sort<ProductListUrlSortField>
|
||||||
|
): Sort<ProductListUrlSortField> {
|
||||||
|
if (field === selectedField) {
|
||||||
|
return {
|
||||||
|
asc: !params.asc,
|
||||||
|
sort: getSortUrlField(field)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
asc: true,
|
||||||
|
sort: getSortUrlField(field)
|
||||||
|
};
|
||||||
|
}
|
|
@ -47,6 +47,10 @@ export interface ListProps<TColumns extends string = string> {
|
||||||
) => void;
|
) => void;
|
||||||
onListSettingsReset?: () => void;
|
onListSettingsReset?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SortPage<TSortKey extends string> {
|
||||||
|
onSort: (field: TSortKey) => void;
|
||||||
|
}
|
||||||
export interface ListActionsWithoutToolbar {
|
export interface ListActionsWithoutToolbar {
|
||||||
toggle: (id: string) => void;
|
toggle: (id: string) => void;
|
||||||
toggleAll: (items: React.ReactNodeArray, selected: number) => void;
|
toggleAll: (items: React.ReactNodeArray, selected: number) => void;
|
||||||
|
@ -131,6 +135,10 @@ export type FiltersWithMultipleValues<TFilters extends string> = Partial<
|
||||||
export type SingleAction = Partial<{
|
export type SingleAction = Partial<{
|
||||||
id: string;
|
id: string;
|
||||||
}>;
|
}>;
|
||||||
|
export type Sort<TSort extends string = string> = Partial<{
|
||||||
|
asc: boolean;
|
||||||
|
sort: TSort;
|
||||||
|
}>;
|
||||||
export type BulkAction = Partial<{
|
export type BulkAction = Partial<{
|
||||||
ids: string[];
|
ids: string[];
|
||||||
}>;
|
}>;
|
||||||
|
|
|
@ -112,6 +112,11 @@ export enum OrderAction {
|
||||||
VOID = "VOID",
|
VOID = "VOID",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrderDirection {
|
||||||
|
ASC = "ASC",
|
||||||
|
DESC = "DESC",
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrderEventsEmailsEnum {
|
export enum OrderEventsEmailsEnum {
|
||||||
DIGITAL_LINKS = "DIGITAL_LINKS",
|
DIGITAL_LINKS = "DIGITAL_LINKS",
|
||||||
FULFILLMENT_CONFIRMATION = "FULFILLMENT_CONFIRMATION",
|
FULFILLMENT_CONFIRMATION = "FULFILLMENT_CONFIRMATION",
|
||||||
|
@ -187,6 +192,15 @@ export enum PermissionEnum {
|
||||||
MANAGE_USERS = "MANAGE_USERS",
|
MANAGE_USERS = "MANAGE_USERS",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProductOrderField {
|
||||||
|
DATE = "DATE",
|
||||||
|
MINIMAL_PRICE = "MINIMAL_PRICE",
|
||||||
|
NAME = "NAME",
|
||||||
|
PRICE = "PRICE",
|
||||||
|
PUBLISHED = "PUBLISHED",
|
||||||
|
TYPE = "TYPE",
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProductTypeConfigurable {
|
export enum ProductTypeConfigurable {
|
||||||
CONFIGURABLE = "CONFIGURABLE",
|
CONFIGURABLE = "CONFIGURABLE",
|
||||||
SIMPLE = "SIMPLE",
|
SIMPLE = "SIMPLE",
|
||||||
|
@ -574,6 +588,11 @@ export interface ProductFilterInput {
|
||||||
minimalPrice?: PriceRangeInput | null;
|
minimalPrice?: PriceRangeInput | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProductOrder {
|
||||||
|
field: ProductOrderField;
|
||||||
|
direction: OrderDirection;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProductTypeFilterInput {
|
export interface ProductTypeFilterInput {
|
||||||
search?: string | null;
|
search?: string | null;
|
||||||
configurable?: ProductTypeConfigurable | null;
|
configurable?: ProductTypeConfigurable | null;
|
||||||
|
|
|
@ -12,12 +12,12 @@ const DisplayColumn: React.FC<DisplayColumnProps> = ({
|
||||||
children,
|
children,
|
||||||
column
|
column
|
||||||
}) => {
|
}) => {
|
||||||
const displayColumn = React.useCallback(
|
const display = React.useMemo(
|
||||||
(column: string) => isSelected(column, displayColumns, (a, b) => a === b),
|
() => isSelected(column, displayColumns, (a, b) => a === b),
|
||||||
[displayColumns]
|
[column, displayColumns]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <>{displayColumn(column) && children}</>;
|
return <>{display && children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
DisplayColumn.displayName = "DisplayColumn";
|
DisplayColumn.displayName = "DisplayColumn";
|
||||||
|
|
Loading…
Reference in a new issue