Add search to customers

This commit is contained in:
dominik-zeglen 2019-09-11 16:39:52 +02:00
parent 38ca4b2444
commit 96d7358668
10 changed files with 293 additions and 102 deletions

View file

@ -1,4 +1,3 @@
import Card from "@material-ui/core/Card";
import { import {
createStyles, createStyles,
Theme, Theme,
@ -68,7 +67,6 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })(
selected, selected,
isChecked isChecked
}: CustomerListProps) => ( }: CustomerListProps) => (
<Card>
<Table> <Table>
<TableHead <TableHead
colSpan={numberOfColumns} colSpan={numberOfColumns}
@ -150,7 +148,6 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })(
)} )}
</TableBody> </TableBody>
</Table> </Table>
</Card>
) )
); );
CustomerList.displayName = "CustomerList"; CustomerList.displayName = "CustomerList";

View file

@ -1,23 +1,41 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
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 SearchBar from "@saleor/components/SearchBar";
import { sectionNames } from "@saleor/intl"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import {
ListActions,
PageListProps,
SearchPageProps,
TabPageProps
} from "@saleor/types";
import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; import { ListCustomers_customers_edges_node } from "../../types/ListCustomers";
import CustomerList from "../CustomerList/CustomerList"; import CustomerList from "../CustomerList/CustomerList";
export interface CustomerListPageProps extends PageListProps, ListActions { export interface CustomerListPageProps
extends PageListProps,
ListActions,
SearchPageProps,
TabPageProps {
customers: ListCustomers_customers_edges_node[]; customers: ListCustomers_customers_edges_node[];
} }
const CustomerListPage: React.StatelessComponent<CustomerListPageProps> = ({ const CustomerListPage: React.StatelessComponent<CustomerListPageProps> = ({
currentTab,
customers, customers,
disabled, disabled,
initialSearch,
onAdd, onAdd,
onAll,
onSearchChange,
onTabChange,
onTabDelete,
onTabSave,
tabs,
...customerListProps ...customerListProps
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@ -37,11 +55,26 @@ const CustomerListPage: React.StatelessComponent<CustomerListPageProps> = ({
/> />
</Button> </Button>
</PageHeader> </PageHeader>
<Card>
<SearchBar
currentTab={currentTab}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Customer"
})}
tabs={tabs}
onAll={onAll}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}
onTabSave={onTabSave}
/>
<CustomerList <CustomerList
customers={customers} customers={customers}
disabled={disabled} disabled={disabled}
{...customerListProps} {...customerListProps}
/> />
</Card>
</Container> </Container>
); );
}; };

View file

@ -64,8 +64,15 @@ const customerList = gql`
$before: String $before: String
$first: Int $first: Int
$last: Int $last: Int
$filter: CustomerFilterInput
) {
customers(
after: $after
before: $before
first: $first
last: $last
filter: $filter
) { ) {
customers(after: $after, before: $before, first: $first, last: $last) {
edges { edges {
node { node {
...CustomerFragment ...CustomerFragment

View file

@ -2,6 +2,8 @@
/* 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 { CustomerFilterInput } from "./../../types/globalTypes";
// ==================================================== // ====================================================
// GraphQL query operation: ListCustomers // GraphQL query operation: ListCustomers
// ==================================================== // ====================================================
@ -48,4 +50,5 @@ export interface ListCustomersVariables {
before?: string | null; before?: string | null;
first?: number | null; first?: number | null;
last?: number | null; last?: number | null;
filter?: CustomerFilterInput | null;
} }

View file

@ -1,13 +1,27 @@
import { stringify as stringifyQs } from "qs"; import { stringify as stringifyQs } from "qs";
import urlJoin from "url-join"; import urlJoin from "url-join";
import { BulkAction, Dialog, Pagination, SingleAction } from "../types"; import {
ActiveTab,
BulkAction,
Dialog,
Filters,
Pagination,
SingleAction,
TabActionDialog
} from "../types";
export const customerSection = "/customers/"; export const customerSection = "/customers/";
export const customerListPath = customerSection; export const customerListPath = customerSection;
export type CustomerListUrlDialog = "remove"; export enum CustomerListUrlFiltersEnum {
export type CustomerListUrlQueryParams = BulkAction & query = "query"
}
export type CustomerListUrlFilters = Filters<CustomerListUrlFiltersEnum>;
export type CustomerListUrlDialog = "remove" | TabActionDialog;
export type CustomerListUrlQueryParams = ActiveTab &
BulkAction &
CustomerListUrlFilters &
Dialog<CustomerListUrlDialog> & Dialog<CustomerListUrlDialog> &
Pagination; Pagination;
export const customerListUrl = (params?: CustomerListUrlQueryParams) => export const customerListUrl = (params?: CustomerListUrlQueryParams) =>

View file

@ -5,6 +5,10 @@ import React from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
import SaveFilterTabDialog, {
SaveFilterTabDialogFormData
} from "@saleor/components/SaveFilterTabDialog";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useListSettings from "@saleor/hooks/useListSettings"; import useListSettings from "@saleor/hooks/useListSettings";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -15,16 +19,26 @@ import usePaginator, {
import { commonMessages } from "@saleor/intl"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import CustomerListPage from "../components/CustomerListPage"; import CustomerListPage from "../../components/CustomerListPage";
import { TypedBulkRemoveCustomers } from "../mutations"; import { TypedBulkRemoveCustomers } from "../../mutations";
import { TypedCustomerListQuery } from "../queries"; import { TypedCustomerListQuery } from "../../queries";
import { BulkRemoveCustomers } from "../types/BulkRemoveCustomers"; import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers";
import { import {
customerAddUrl, customerAddUrl,
customerListUrl, customerListUrl,
CustomerListUrlDialog,
CustomerListUrlFilters,
CustomerListUrlQueryParams, CustomerListUrlQueryParams,
customerUrl customerUrl
} from "../urls"; } from "../../urls";
import {
areFiltersApplied,
deleteFilterTab,
getActiveFilters,
getFilterTabs,
getFilterVariables,
saveFilterTab
} from "./filter";
interface CustomerListProps { interface CustomerListProps {
params: CustomerListUrlQueryParams; params: CustomerListUrlQueryParams;
@ -44,20 +58,76 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
); );
const intl = useIntl(); const intl = useIntl();
const tabs = getFilterTabs();
const currentTab =
params.activeTab === undefined
? areFiltersApplied(params)
? tabs.length + 1
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: CustomerListUrlFilters) => {
reset();
navigate(
customerListUrl({
...getActiveFilters(params),
...filter,
activeTab: undefined
})
);
};
const closeModal = () => const closeModal = () =>
navigate( navigate(
customerListUrl({ customerListUrl({
...params, ...params,
action: undefined, action: undefined,
ids: undefined ids: undefined
}), })
true
); );
const openModal = (action: CustomerListUrlDialog, ids?: string[]) =>
navigate(
customerListUrl({
...params,
action,
ids
})
);
const handleTabChange = (tab: number) => {
reset();
navigate(
customerListUrl({
activeTab: tab.toString(),
...getFilterTabs()[tab - 1].data
})
);
};
const handleTabDelete = () => {
deleteFilterTab(currentTab);
reset();
navigate(customerListUrl());
};
const handleTabSave = (data: SaveFilterTabDialogFormData) => {
saveFilterTab(data.name, getActiveFilters(params));
handleTabChange(tabs.length + 1);
};
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
const queryVariables = React.useMemo(
() => ({
...paginationState,
filter: getFilterVariables(params)
}),
[params]
);
return ( return (
<TypedCustomerListQuery displayLoader variables={paginationState}> <TypedCustomerListQuery displayLoader variables={queryVariables}>
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const { loadNextPage, loadPreviousPage, pageInfo } = paginate( const { loadNextPage, loadPreviousPage, pageInfo } = paginate(
maybe(() => data.customers.pageInfo), maybe(() => data.customers.pageInfo),
@ -90,6 +160,14 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
return ( return (
<> <>
<CustomerListPage <CustomerListPage
currentTab={currentTab}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(customerListUrl())}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}
tabs={tabs.map(tab => tab.name)}
customers={maybe(() => customers={maybe(() =>
data.customers.edges.map(edge => edge.node) data.customers.edges.map(edge => edge.node)
)} )}
@ -156,6 +234,19 @@ export const CustomerList: React.StatelessComponent<CustomerListProps> = ({
/> />
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog
open={params.action === "save-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabSave}
/>
<DeleteFilterTabDialog
open={params.action === "delete-search"}
confirmButtonState="default"
onClose={closeModal}
onSubmit={handleTabDelete}
tabName={maybe(() => tabs[currentTab - 1].name, "...")}
/>
</> </>
); );
}} }}

View file

@ -0,0 +1,31 @@
import { CustomerFilterInput } from "@saleor/types/globalTypes";
import {
createFilterTabUtils,
createFilterUtils
} from "../../../utils/filters";
import {
CustomerListUrlFilters,
CustomerListUrlFiltersEnum,
CustomerListUrlQueryParams
} from "../../urls";
export const CUSTOMER_FILTERS_KEY = "customerFilters";
export function getFilterVariables(
params: CustomerListUrlFilters
): CustomerFilterInput {
return {
search: params.query
};
}
export const {
deleteFilterTab,
getFilterTabs,
saveFilterTab
} = createFilterTabUtils<CustomerListUrlFilters>(CUSTOMER_FILTERS_KEY);
export const { areFiltersApplied, getActiveFilters } = createFilterUtils<
CustomerListUrlQueryParams,
CustomerListUrlFilters
>(CustomerListUrlFiltersEnum);

View file

@ -0,0 +1,2 @@
export { default } from "./CustomerList";
export * from "./CustomerList";

View file

@ -60,7 +60,7 @@ const OrderDraftListPage: React.StatelessComponent<OrderDraftListPageProps> = ({
currentTab={currentTab} currentTab={currentTab}
initialSearch={initialSearch} initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({ searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Collection" defaultMessage: "Search Draft"
})} })}
tabs={tabs} tabs={tabs}
onAll={onAll} onAll={onAll}

View file

@ -370,6 +370,14 @@ export interface ConfigurationItemInput {
value: string; value: string;
} }
export interface CustomerFilterInput {
dateJoined?: DateRangeInput | null;
moneySpent?: PriceRangeInput | null;
numberOfOrders?: IntRangeInput | null;
placedOrders?: DateRangeInput | null;
search?: string | null;
}
export interface CustomerInput { export interface CustomerInput {
defaultBillingAddress?: AddressInput | null; defaultBillingAddress?: AddressInput | null;
defaultShippingAddress?: AddressInput | null; defaultShippingAddress?: AddressInput | null;
@ -415,6 +423,11 @@ export interface FulfillmentUpdateTrackingInput {
notifyCustomer?: boolean | null; notifyCustomer?: boolean | null;
} }
export interface IntRangeInput {
gte?: number | null;
lte?: number | null;
}
export interface MenuCreateInput { export interface MenuCreateInput {
name: string; name: string;
items?: (MenuItemInput | null)[] | null; items?: (MenuItemInput | null)[] | null;