Add filtering to customer list

This commit is contained in:
dominik-zeglen 2020-01-02 13:22:12 +01:00
parent d3bd6a6c22
commit 0ad80813fe
7 changed files with 285 additions and 14 deletions

View file

@ -5,33 +5,41 @@ import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader";
import SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl";
import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps,
SortPage
SortPage,
FilterPageProps
} from "@saleor/types";
import { CustomerListUrlSortField } from "@saleor/customers/urls";
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
import {
CustomerFilterKeys,
createFilterStructure
} from "@saleor/customers/views/CustomerList/filter";
import { CustomerListFilterOpts } from "@saleor/customers/types";
import FilterBar from "@saleor/components/FilterBar";
import CustomerList from "../CustomerList/CustomerList";
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
export interface CustomerListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
FilterPageProps<CustomerFilterKeys, CustomerListFilterOpts>,
SortPage<CustomerListUrlSortField>,
TabPageProps {
customers: ListCustomers_customers_edges_node[];
}
const CustomerListPage: React.FC<CustomerListPageProps> = ({
currencySymbol,
currentTab,
filterOpts,
initialSearch,
onAdd,
onAll,
onFilterChange,
onSearchChange,
onTabChange,
onTabDelete,
@ -41,6 +49,8 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
}) => {
const intl = useIntl();
const structure = createFilterStructure(intl, filterOpts);
return (
<Container>
<PageHeader title={intl.formatMessage(sectionNames.customers)}>
@ -52,18 +62,21 @@ const CustomerListPage: React.FC<CustomerListPageProps> = ({
</Button>
</PageHeader>
<Card>
<SearchBar
<FilterBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Customers",
description: "tab name"
})}
currencySymbol={currencySymbol}
currentTab={currentTab}
filterStructure={structure}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Customer"
})}
tabs={tabs}
onAll={onAll}
onFilterChange={onFilterChange}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}

View file

@ -1,3 +1,11 @@
import { FilterOpts, MinMax } from "@saleor/types";
export interface CustomerListFilterOpts {
joined: FilterOpts<MinMax>;
moneySpent: FilterOpts<MinMax>;
numberOfOrders: FilterOpts<MinMax>;
}
export interface AddressTypeInput {
city: string;
cityArea?: string;

View file

@ -16,6 +16,12 @@ export const customerSection = "/customers/";
export const customerListPath = customerSection;
export enum CustomerListUrlFiltersEnum {
joinedFrom = "joinedFrom",
joinedTo = "joinedTo",
moneySpentFrom = "moneySpentFrom",
moneySpentTo = "moneySpentTo",
numberOfOrdersFrom = "numberOfOrdersFrom",
numberOfOrdersTo = "numberOfOrdersTo",
query = "query"
}
export type CustomerListUrlFilters = Filters<CustomerListUrlFiltersEnum>;

View file

@ -22,6 +22,9 @@ import { ListViews } from "@saleor/types";
import { getSortParams } from "@saleor/utils/sort";
import createSortHandler from "@saleor/utils/handlers/sortHandler";
import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers";
import { IFilter } from "@saleor/components/Filter";
import { getFilterQueryParams } from "@saleor/utils/filters";
import useShop from "@saleor/hooks/useShop";
import CustomerListPage from "../../components/CustomerListPage";
import { TypedBulkRemoveCustomers } from "../../mutations";
import { useCustomerListQuery } from "../../queries";
@ -29,7 +32,6 @@ import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers";
import {
customerAddUrl,
customerListUrl,
CustomerListUrlFilters,
CustomerListUrlQueryParams,
customerUrl,
CustomerListUrlDialog
@ -40,7 +42,10 @@ import {
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
saveFilterTab,
CustomerFilterKeys,
getFilterQueryParam,
getFilterOpts
} from "./filter";
import { getSortQueryVariables } from "./sort";
@ -59,6 +64,7 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
ListViews.CUSTOMER_LIST
);
const intl = useIntl();
const shop = useShop();
const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
@ -83,17 +89,38 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: CustomerListUrlFilters) => {
const changeFilters = (filter: IFilter<CustomerFilterKeys>) => {
reset();
navigate(
customerListUrl({
...getActiveFilters(params),
...filter,
...params,
...getFilterQueryParams(filter, getFilterQueryParam),
activeTab: undefined
})
);
};
const resetFilters = () => {
reset();
navigate(
customerListUrl({
asc: params.asc,
sort: params.sort
})
);
};
const handleSearchChange = (query: string) => {
reset();
navigate(
customerListUrl({
...params,
activeTab: undefined,
query
})
);
};
const [openModal, closeModal] = createDialogActionHandlers<
CustomerListUrlDialog,
CustomerListUrlQueryParams
@ -138,16 +165,20 @@ export const CustomerList: React.FC<CustomerListProps> = ({ params }) => {
};
const handleSort = createSortHandler(navigate, customerListUrl, params);
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
return (
<TypedBulkRemoveCustomers onCompleted={handleBulkCustomerDelete}>
{(bulkRemoveCustomers, bulkRemoveCustomersOpts) => (
<>
<CustomerListPage
currencySymbol={currencySymbol}
currentTab={currentTab}
filterOpts={getFilterOpts(params)}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(customerListUrl())}
onSearchChange={handleSearchChange}
onFilterChange={changeFilters}
onAll={resetFilters}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}

View file

@ -1,4 +1,12 @@
import { IntlShape } from "react-intl";
import { CustomerFilterInput } from "@saleor/types/globalTypes";
import { maybe } from "@saleor/misc";
import {
createDateField,
createNumberField
} from "@saleor/utils/filters/fields";
import { IFilter, IFilterElement } from "@saleor/components/Filter";
import {
createFilterTabUtils,
createFilterUtils
@ -8,17 +16,180 @@ import {
CustomerListUrlFiltersEnum,
CustomerListUrlQueryParams
} from "../../urls";
import { CustomerListFilterOpts } from "../../types";
import messages from "./messages";
export const CUSTOMER_FILTERS_KEY = "customerFilters";
export enum CustomerFilterKeys {
joined = "joined",
moneySpent = "spent",
numberOfOrders = "orders"
}
export function getFilterOpts(
params: CustomerListUrlFilters
): CustomerListFilterOpts {
return {
joined: {
active: maybe(
() =>
[params.joinedFrom, params.joinedTo].some(
field => field !== undefined
),
false
),
value: {
max: maybe(() => params.joinedTo, ""),
min: maybe(() => params.joinedFrom, "")
}
},
moneySpent: {
active: maybe(
() =>
[params.moneySpentFrom, params.moneySpentTo].some(
field => field !== undefined
),
false
),
value: {
max: maybe(() => params.moneySpentTo, ""),
min: maybe(() => params.moneySpentFrom, "")
}
},
numberOfOrders: {
active: maybe(
() =>
[params.numberOfOrdersFrom, params.numberOfOrdersTo].some(
field => field !== undefined
),
false
),
value: {
max: maybe(() => params.numberOfOrdersTo, ""),
min: maybe(() => params.numberOfOrdersFrom, "")
}
}
};
}
export function createFilterStructure(
intl: IntlShape,
opts: CustomerListFilterOpts
): IFilter<CustomerFilterKeys> {
return [
{
...createDateField(
CustomerFilterKeys.joined,
intl.formatMessage(messages.joinDate),
opts.joined.value
),
active: opts.joined.active
},
{
...createNumberField(
CustomerFilterKeys.moneySpent,
intl.formatMessage(messages.moneySpent),
opts.moneySpent.value
),
active: opts.moneySpent.active
},
{
...createNumberField(
CustomerFilterKeys.numberOfOrders,
intl.formatMessage(messages.numberOfOrders),
opts.numberOfOrders.value
),
active: opts.numberOfOrders.active
}
];
}
export function getFilterVariables(
params: CustomerListUrlFilters
): CustomerFilterInput {
return {
dateJoined: {
gte: params.joinedFrom,
lte: params.joinedTo
},
moneySpent: {
gte: parseInt(params.moneySpentFrom, 0),
lte: parseInt(params.moneySpentTo, 0)
},
numberOfOrders: {
gte: parseInt(params.numberOfOrdersFrom, 0),
lte: parseInt(params.numberOfOrdersTo, 0)
},
search: params.query
};
}
export function getFilterQueryParam(
filter: IFilterElement<CustomerFilterKeys>
): CustomerListUrlFilters {
const { active, multiple, name, value } = filter;
switch (name) {
case CustomerFilterKeys.joined:
if (!active) {
return {
joinedFrom: undefined,
joinedTo: undefined
};
}
if (multiple) {
return {
joinedFrom: value[0],
joinedTo: value[1]
};
}
return {
joinedFrom: value[0],
joinedTo: value[0]
};
case CustomerFilterKeys.moneySpent:
if (!active) {
return {
moneySpentFrom: undefined,
moneySpentTo: undefined
};
}
if (multiple) {
return {
moneySpentFrom: value[0],
moneySpentTo: value[1]
};
}
return {
moneySpentFrom: value[0],
moneySpentTo: value[0]
};
case CustomerFilterKeys.numberOfOrders:
if (!active) {
return {
numberOfOrdersFrom: undefined,
numberOfOrdersTo: undefined
};
}
if (multiple) {
return {
numberOfOrdersFrom: value[0],
numberOfOrdersTo: value[1]
};
}
return {
numberOfOrdersFrom: value[0],
numberOfOrdersTo: value[0]
};
}
}
export const {
deleteFilterTab,
getFilterTabs,

View file

@ -0,0 +1,17 @@
import { defineMessages } from "react-intl";
const messages = defineMessages({
joinDate: {
defaultMessage: "Join Date",
description: "customer"
},
moneySpent: {
defaultMessage: "Money Spent",
description: "customer"
},
numberOfOrders: {
defaultMessage: "Number of Orders"
}
});
export default messages;

View file

@ -11,17 +11,42 @@ import {
pageListProps,
searchPageProps,
tabPageProps,
sortPageProps
sortPageProps,
filterPageProps
} from "../../../fixtures";
import Decorator from "../../Decorator";
const props: CustomerListPageProps = {
...filterPageProps,
...listActionsProps,
...pageListProps.default,
...searchPageProps,
...sortPageProps,
...tabPageProps,
customers: customerList,
filterOpts: {
joined: {
active: false,
value: {
max: undefined,
min: undefined
}
},
moneySpent: {
active: false,
value: {
max: undefined,
min: undefined
}
},
numberOfOrders: {
active: false,
value: {
max: undefined,
min: undefined
}
}
},
sort: {
...sortPageProps.sort,
sort: CustomerListUrlSortField.name