From 96d7358668da11d543afe3c564f2e4742a0b2b51 Mon Sep 17 00:00:00 2001 From: dominik-zeglen Date: Wed, 11 Sep 2019 16:39:52 +0200 Subject: [PATCH] Add search to customers --- .../components/CustomerList/CustomerList.tsx | 159 +++++++++--------- .../CustomerListPage/CustomerListPage.tsx | 49 +++++- src/customers/queries.ts | 9 +- src/customers/types/ListCustomers.ts | 3 + src/customers/urls.ts | 20 ++- .../views/{ => CustomerList}/CustomerList.tsx | 107 +++++++++++- src/customers/views/CustomerList/filter.ts | 31 ++++ src/customers/views/CustomerList/index.ts | 2 + .../OrderDraftListPage/OrderDraftListPage.tsx | 2 +- src/types/globalTypes.ts | 13 ++ 10 files changed, 293 insertions(+), 102 deletions(-) rename src/customers/views/{ => CustomerList}/CustomerList.tsx (64%) create mode 100644 src/customers/views/CustomerList/filter.ts create mode 100644 src/customers/views/CustomerList/index.ts diff --git a/src/customers/components/CustomerList/CustomerList.tsx b/src/customers/components/CustomerList/CustomerList.tsx index 67b3622dd..c3c886440 100644 --- a/src/customers/components/CustomerList/CustomerList.tsx +++ b/src/customers/components/CustomerList/CustomerList.tsx @@ -1,4 +1,3 @@ -import Card from "@material-ui/core/Card"; import { createStyles, Theme, @@ -68,89 +67,87 @@ const CustomerList = withStyles(styles, { name: "CustomerList" })( selected, isChecked }: CustomerListProps) => ( - - - - - - - - - - - - - - - - - - - - {renderCollection( - customers, - customer => { - const isSelected = customer ? isChecked(customer.id) : false; +
+ + + + + + + + + + + + + + + + + + {renderCollection( + customers, + customer => { + const isSelected = customer ? isChecked(customer.id) : false; - return ( - - - toggle(customer.id)} - /> - - - {getUserName(customer)} - - - {maybe(() => customer.email, )} - - - {maybe( - () => customer.orders.totalCount, - - )} - - - ); - }, - () => ( - - - + return ( + + + toggle(customer.id)} + /> + + + {getUserName(customer)} + + + {maybe(() => customer.email, )} + + + {maybe( + () => customer.orders.totalCount, + + )} - ) - )} - -
-
+ ); + }, + () => ( + + + + + + ) + )} + + ) ); CustomerList.displayName = "CustomerList"; diff --git a/src/customers/components/CustomerListPage/CustomerListPage.tsx b/src/customers/components/CustomerListPage/CustomerListPage.tsx index d9fa44acb..62e2d4a84 100644 --- a/src/customers/components/CustomerListPage/CustomerListPage.tsx +++ b/src/customers/components/CustomerListPage/CustomerListPage.tsx @@ -1,23 +1,41 @@ 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 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 } from "@saleor/types"; +import { + ListActions, + PageListProps, + SearchPageProps, + TabPageProps +} from "@saleor/types"; import { ListCustomers_customers_edges_node } from "../../types/ListCustomers"; import CustomerList from "../CustomerList/CustomerList"; -export interface CustomerListPageProps extends PageListProps, ListActions { +export interface CustomerListPageProps + extends PageListProps, + ListActions, + SearchPageProps, + TabPageProps { customers: ListCustomers_customers_edges_node[]; } const CustomerListPage: React.StatelessComponent = ({ + currentTab, customers, disabled, + initialSearch, onAdd, + onAll, + onSearchChange, + onTabChange, + onTabDelete, + onTabSave, + tabs, ...customerListProps }) => { const intl = useIntl(); @@ -37,11 +55,26 @@ const CustomerListPage: React.StatelessComponent = ({ /> - + + + + ); }; diff --git a/src/customers/queries.ts b/src/customers/queries.ts index 9030ecbdf..60a00e56e 100644 --- a/src/customers/queries.ts +++ b/src/customers/queries.ts @@ -64,8 +64,15 @@ const customerList = gql` $before: String $first: Int $last: Int + $filter: CustomerFilterInput ) { - customers(after: $after, before: $before, first: $first, last: $last) { + customers( + after: $after + before: $before + first: $first + last: $last + filter: $filter + ) { edges { node { ...CustomerFragment diff --git a/src/customers/types/ListCustomers.ts b/src/customers/types/ListCustomers.ts index aeaf81931..06a65a999 100644 --- a/src/customers/types/ListCustomers.ts +++ b/src/customers/types/ListCustomers.ts @@ -2,6 +2,8 @@ /* eslint-disable */ // This file was automatically generated and should not be edited. +import { CustomerFilterInput } from "./../../types/globalTypes"; + // ==================================================== // GraphQL query operation: ListCustomers // ==================================================== @@ -48,4 +50,5 @@ export interface ListCustomersVariables { before?: string | null; first?: number | null; last?: number | null; + filter?: CustomerFilterInput | null; } diff --git a/src/customers/urls.ts b/src/customers/urls.ts index baab66be7..afb55862b 100644 --- a/src/customers/urls.ts +++ b/src/customers/urls.ts @@ -1,13 +1,27 @@ import { stringify as stringifyQs } from "qs"; 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 customerListPath = customerSection; -export type CustomerListUrlDialog = "remove"; -export type CustomerListUrlQueryParams = BulkAction & +export enum CustomerListUrlFiltersEnum { + query = "query" +} +export type CustomerListUrlFilters = Filters; +export type CustomerListUrlDialog = "remove" | TabActionDialog; +export type CustomerListUrlQueryParams = ActiveTab & + BulkAction & + CustomerListUrlFilters & Dialog & Pagination; export const customerListUrl = (params?: CustomerListUrlQueryParams) => diff --git a/src/customers/views/CustomerList.tsx b/src/customers/views/CustomerList/CustomerList.tsx similarity index 64% rename from src/customers/views/CustomerList.tsx rename to src/customers/views/CustomerList/CustomerList.tsx index 0ee6d578b..d939c285a 100644 --- a/src/customers/views/CustomerList.tsx +++ b/src/customers/views/CustomerList/CustomerList.tsx @@ -5,6 +5,10 @@ import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; 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 useListSettings from "@saleor/hooks/useListSettings"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -15,16 +19,26 @@ import usePaginator, { import { commonMessages } from "@saleor/intl"; import { getMutationState, maybe } from "@saleor/misc"; import { ListViews } from "@saleor/types"; -import CustomerListPage from "../components/CustomerListPage"; -import { TypedBulkRemoveCustomers } from "../mutations"; -import { TypedCustomerListQuery } from "../queries"; -import { BulkRemoveCustomers } from "../types/BulkRemoveCustomers"; +import CustomerListPage from "../../components/CustomerListPage"; +import { TypedBulkRemoveCustomers } from "../../mutations"; +import { TypedCustomerListQuery } from "../../queries"; +import { BulkRemoveCustomers } from "../../types/BulkRemoveCustomers"; import { customerAddUrl, customerListUrl, + CustomerListUrlDialog, + CustomerListUrlFilters, CustomerListUrlQueryParams, customerUrl -} from "../urls"; +} from "../../urls"; +import { + areFiltersApplied, + deleteFilterTab, + getActiveFilters, + getFilterTabs, + getFilterVariables, + saveFilterTab +} from "./filter"; interface CustomerListProps { params: CustomerListUrlQueryParams; @@ -44,20 +58,76 @@ export const CustomerList: React.StatelessComponent = ({ ); 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 = () => navigate( customerListUrl({ ...params, action: 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 queryVariables = React.useMemo( + () => ({ + ...paginationState, + filter: getFilterVariables(params) + }), + [params] + ); return ( - + {({ data, loading, refetch }) => { const { loadNextPage, loadPreviousPage, pageInfo } = paginate( maybe(() => data.customers.pageInfo), @@ -90,6 +160,14 @@ export const CustomerList: React.StatelessComponent = ({ return ( <> changeFilterField({ query })} + onAll={() => navigate(customerListUrl())} + onTabChange={handleTabChange} + onTabDelete={() => openModal("delete-search")} + onTabSave={() => openModal("save-search")} + tabs={tabs.map(tab => tab.name)} customers={maybe(() => data.customers.edges.map(edge => edge.node) )} @@ -156,6 +234,19 @@ export const CustomerList: React.StatelessComponent = ({ /> + + tabs[currentTab - 1].name, "...")} + /> ); }} diff --git a/src/customers/views/CustomerList/filter.ts b/src/customers/views/CustomerList/filter.ts new file mode 100644 index 000000000..98e29cf77 --- /dev/null +++ b/src/customers/views/CustomerList/filter.ts @@ -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(CUSTOMER_FILTERS_KEY); + +export const { areFiltersApplied, getActiveFilters } = createFilterUtils< + CustomerListUrlQueryParams, + CustomerListUrlFilters +>(CustomerListUrlFiltersEnum); diff --git a/src/customers/views/CustomerList/index.ts b/src/customers/views/CustomerList/index.ts new file mode 100644 index 000000000..c5517efd0 --- /dev/null +++ b/src/customers/views/CustomerList/index.ts @@ -0,0 +1,2 @@ +export { default } from "./CustomerList"; +export * from "./CustomerList"; diff --git a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx index 389cb275a..835b9e80b 100644 --- a/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx +++ b/src/orders/components/OrderDraftListPage/OrderDraftListPage.tsx @@ -60,7 +60,7 @@ const OrderDraftListPage: React.StatelessComponent = ({ currentTab={currentTab} initialSearch={initialSearch} searchPlaceholder={intl.formatMessage({ - defaultMessage: "Search Collection" + defaultMessage: "Search Draft" })} tabs={tabs} onAll={onAll} diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index d085099f7..ab30edcc5 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -370,6 +370,14 @@ export interface ConfigurationItemInput { value: string; } +export interface CustomerFilterInput { + dateJoined?: DateRangeInput | null; + moneySpent?: PriceRangeInput | null; + numberOfOrders?: IntRangeInput | null; + placedOrders?: DateRangeInput | null; + search?: string | null; +} + export interface CustomerInput { defaultBillingAddress?: AddressInput | null; defaultShippingAddress?: AddressInput | null; @@ -415,6 +423,11 @@ export interface FulfillmentUpdateTrackingInput { notifyCustomer?: boolean | null; } +export interface IntRangeInput { + gte?: number | null; + lte?: number | null; +} + export interface MenuCreateInput { name: string; items?: (MenuItemInput | null)[] | null;