Add customer search

This commit is contained in:
dominik-zeglen 2019-11-25 12:29:07 +01:00
parent ebf386dfb5
commit 42b55e860b
13 changed files with 135 additions and 10 deletions

View file

@ -4,7 +4,14 @@ import hotkeys from "hotkeys-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { getActions, getViews, hasActions, hasViews } from "./modes/utils"; import {
getActions,
getCustomers,
getViews,
hasActions,
hasCustomers,
hasViews
} from "./modes/utils";
import NavigatorInput from "./NavigatorInput"; import NavigatorInput from "./NavigatorInput";
import NavigatorSection from "./NavigatorSection"; import NavigatorSection from "./NavigatorSection";
import { QuickSearchAction } from "./types"; import { QuickSearchAction } from "./types";
@ -12,6 +19,13 @@ import useQuickSearch from "./useQuickSearch";
const navigatorHotkey = "ctrl+m, command+m"; const navigatorHotkey = "ctrl+m, command+m";
function getItemOffset(
actions: QuickSearchAction[],
cbs: Array<typeof getViews>
): number {
return cbs.reduce((acc, cb) => cb(actions).length + acc, 0);
}
const Navigator: React.FC = () => { const Navigator: React.FC = () => {
const [visible, setVisible] = React.useState(false); const [visible, setVisible] = React.useState(false);
const input = React.useRef(null); const input = React.useRef(null);
@ -78,7 +92,19 @@ const Navigator: React.FC = () => {
getItemProps={getItemProps} getItemProps={getItemProps}
highlightedIndex={highlightedIndex} highlightedIndex={highlightedIndex}
items={getActions(actions)} items={getActions(actions)}
offset={getViews(actions).length} offset={getItemOffset(actions, [getViews])}
/>
)}
{hasCustomers(actions) && (
<NavigatorSection
label={intl.formatMessage({
defaultMessage: "Search in Customers",
description: "navigator section header"
})}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
items={getCustomers(actions)}
offset={getItemOffset(actions, [getViews, getActions])}
/> />
)} )}
</div> </div>

View file

@ -52,7 +52,7 @@ const NavigatorInput = React.forwardRef<HTMLInputElement, NavigatorInputProps>(
<div className={classes.root}> <div className={classes.root}>
{mode !== "default" && ( {mode !== "default" && (
<span className={classes.adornment}> <span className={classes.adornment}>
{mode === "orders" ? "#" : ">"} {mode === "orders" ? "#" : mode === "customers" ? "@" : ">"}
</span> </span>
)} )}
<input <input

View file

@ -0,0 +1,36 @@
import { IntlShape } from "react-intl";
import { customerUrl } from "@saleor/customers/urls";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { QuickSearchAction } from "../types";
import messages from "./messages";
export function searchInCustomers(
intl: IntlShape,
navigate: UseNavigatorResult,
customers: SearchCustomers_search_edges_node[]
): QuickSearchAction[] {
return customers.map(customer => ({
label:
customer.firstName && customer.lastName
? intl.formatMessage(messages.customerWithName, {
firstName: customer.firstName,
lastName: customer.lastName
})
: customer.email,
onClick: () => navigate(customerUrl(customer.id)),
score: 1,
type: "customer"
}));
}
function getCustomersModeActions(
intl: IntlShape,
navigate: UseNavigatorResult,
customers: SearchCustomers_search_edges_node[]
): QuickSearchAction[] {
return searchInCustomers(intl, navigate, customers);
}
export default getCustomersModeActions;

View file

@ -3,8 +3,10 @@ import { IntlShape } from "react-intl";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator"; import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate"; import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate";
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { QuickSearchAction } from "../../types"; import { QuickSearchAction } from "../../types";
import { searchInCommands } from "../commands"; import { searchInCommands } from "../commands";
import { searchInCustomers } from "../customers";
import searchInViews from "./views"; import searchInViews from "./views";
const threshold = 0.05; const threshold = 0.05;
@ -14,15 +16,22 @@ function getDefaultModeActions(
query: string, query: string,
intl: IntlShape, intl: IntlShape,
navigate: UseNavigatorResult, navigate: UseNavigatorResult,
customers: SearchCustomers_search_edges_node[],
createOrder: MutationFunction<OrderDraftCreate, {}> createOrder: MutationFunction<OrderDraftCreate, {}>
): QuickSearchAction[] { ): QuickSearchAction[] {
return [ const actions = [
...searchInViews(query, intl, navigate), ...searchInViews(query, intl, navigate),
...searchInCommands(query, intl, navigate, createOrder) ...searchInCommands(query, intl, navigate, createOrder)
] ]
.filter(action => action.score >= threshold) .filter(action => action.score >= threshold)
.sort((a, b) => (a.score <= b.score ? 1 : -1)) .sort((a, b) => (a.score <= b.score ? 1 : -1))
.slice(0, maxActions); .slice(0, maxActions);
if (query !== "") {
return [...actions, ...searchInCustomers(intl, navigate, customers)];
}
return actions;
} }
export default getDefaultModeActions; export default getDefaultModeActions;

View file

@ -5,6 +5,7 @@ import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate";
import { MutationFunction } from "react-apollo"; import { MutationFunction } from "react-apollo";
import { QuickSearchAction, QuickSearchMode } from "../types"; import { QuickSearchAction, QuickSearchMode } from "../types";
import getCommandModeActions from "./commands"; import getCommandModeActions from "./commands";
import getCustomersModeActions from "./customers";
import getDefaultModeActions from "./default"; import getDefaultModeActions from "./default";
import getOrdersModeActions from "./orders"; import getOrdersModeActions from "./orders";
import { ActionQueries } from "./types"; import { ActionQueries } from "./types";
@ -22,10 +23,18 @@ function getModeActions(
switch (mode) { switch (mode) {
case "commands": case "commands":
return getCommandModeActions(query, intl, cbs.navigate, cbs.createOrder); return getCommandModeActions(query, intl, cbs.navigate, cbs.createOrder);
case "customers":
return getCustomersModeActions(intl, cbs.navigate, queries.customers);
case "orders": case "orders":
return getOrdersModeActions(query, intl, cbs.navigate, queries.order); return getOrdersModeActions(query, intl, cbs.navigate, queries.order);
default: default:
return getDefaultModeActions(query, intl, cbs.navigate, cbs.createOrder); return getDefaultModeActions(
query,
intl,
cbs.navigate,
queries.customers,
cbs.createOrder
);
} }
} }

View file

@ -25,6 +25,9 @@ const messages = defineMessages({
defaultMessage: "Create Order", defaultMessage: "Create Order",
description: "button" description: "button"
}, },
customerWithName: {
defaultMessage: "{firstName} {lastName}"
},
goToOrder: { goToOrder: {
defaultMessage: "Go to order #{orderNumber}", defaultMessage: "Go to order #{orderNumber}",
description: "navigator action" description: "navigator action"

View file

@ -1,5 +1,7 @@
import { SearchCustomers_search_edges_node } from "@saleor/searches/types/SearchCustomers";
import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists"; import { CheckIfOrderExists_order } from "../queries/types/CheckIfOrderExists";
export interface ActionQueries { export interface ActionQueries {
customers: SearchCustomers_search_edges_node[];
order: CheckIfOrderExists_order; order: CheckIfOrderExists_order;
} }

View file

@ -13,3 +13,12 @@ export function getViews(actions: QuickSearchAction[]): QuickSearchAction[] {
export function hasViews(actions: QuickSearchAction[]): boolean { export function hasViews(actions: QuickSearchAction[]): boolean {
return getViews(actions).length > 0; return getViews(actions).length > 0;
} }
export function getCustomers(
actions: QuickSearchAction[]
): QuickSearchAction[] {
return actions.filter(action => action.type === "customer");
}
export function hasCustomers(actions: QuickSearchAction[]): boolean {
return getCustomers(actions).length > 0;
}

View file

@ -1,4 +1,4 @@
export type QuickSearchActionType = "action" | "view"; export type QuickSearchActionType = "action" | "customer" | "view";
export interface QuickSearchAction { export interface QuickSearchAction {
label: string; label: string;

View file

@ -1,12 +1,14 @@
import { RefObject, useEffect, useState } from "react"; import { RefObject, useEffect, useState } from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { DEFAULT_INITIAL_SEARCH_DATA } from "@saleor/config";
import { ChangeEvent, FormChange } from "@saleor/hooks/useForm"; import { ChangeEvent, FormChange } from "@saleor/hooks/useForm";
import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen"; import useModalDialogOpen from "@saleor/hooks/useModalDialogOpen";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { useOrderDraftCreateMutation } from "@saleor/orders/mutations"; import { useOrderDraftCreateMutation } from "@saleor/orders/mutations";
import { orderUrl } from "@saleor/orders/urls"; import { orderUrl } from "@saleor/orders/urls";
import useCustomerSearch from "@saleor/searches/useCustomerSearch";
import getModeActions from "./modes"; import getModeActions from "./modes";
import { getGqlOrderId, isQueryValidOrderNumber } from "./modes/orders"; import { getGqlOrderId, isQueryValidOrderNumber } from "./modes/orders";
import useCheckIfOrderExists from "./queries/useCheckIfOrderExists"; import useCheckIfOrderExists from "./queries/useCheckIfOrderExists";
@ -27,6 +29,12 @@ function useQuickSearch(
const intl = useIntl(); const intl = useIntl();
const navigate = useNavigator(); const navigate = useNavigator();
const [{ data: orderData }, getOrderData] = useCheckIfOrderExists(); const [{ data: orderData }, getOrderData] = useCheckIfOrderExists();
const { result: customers, search: searchCustomers } = useCustomerSearch({
variables: {
...DEFAULT_INITIAL_SEARCH_DATA,
first: 5
}
});
const [createOrder] = useOrderDraftCreateMutation({ const [createOrder] = useOrderDraftCreateMutation({
onCompleted: result => { onCompleted: result => {
if (result.draftOrderCreate.errors.length === 0) { if (result.draftOrderCreate.errors.length === 0) {
@ -72,6 +80,9 @@ function useQuickSearch(
case "> ": case "> ":
setMode("commands"); setMode("commands");
break; break;
case "@ ":
setMode("customers");
break;
case "# ": case "# ":
setMode("orders"); setMode("orders");
break; break;
@ -84,6 +95,10 @@ function useQuickSearch(
} }
setQuery(value); setQuery(value);
} }
if ((["customers", "default"] as QuickSearchMode[]).includes(mode)) {
searchCustomers(value);
}
}; };
return [ return [
@ -95,6 +110,10 @@ function useQuickSearch(
query, query,
intl, intl,
{ {
customers: maybe(
() => customers.data.search.edges.map(edge => edge.node),
[]
),
order: maybe(() => orderData.order) order: maybe(() => orderData.order)
}, },
{ {

View file

@ -15,22 +15,30 @@ export const clients: SearchCustomers_search_edges_node[] = [
{ {
__typename: "User" as "User", __typename: "User" as "User",
email: "test.client1@example.com", email: "test.client1@example.com",
id: "c1" firstName: "John",
id: "c1",
lastName: "Doe"
}, },
{ {
__typename: "User" as "User", __typename: "User" as "User",
email: "test.client2@example.com", email: "test.client2@example.com",
id: "c2" firstName: "Dough",
id: "c2",
lastName: "Jones"
}, },
{ {
__typename: "User" as "User", __typename: "User" as "User",
email: "test.client3@example.com", email: "test.client3@example.com",
id: "c3" firstName: "Jonas",
id: "c3",
lastName: "Dough"
}, },
{ {
__typename: "User" as "User", __typename: "User" as "User",
email: "test.client4@example.com", email: "test.client4@example.com",
id: "c4" firstName: "Bill",
id: "c4",
lastName: "Jonas"
} }
]; ];
export const orders: OrderList_orders_edges_node[] = [ export const orders: OrderList_orders_edges_node[] = [

View file

@ -10,6 +10,8 @@ export interface SearchCustomers_search_edges_node {
__typename: "User"; __typename: "User";
id: string; id: string;
email: string; email: string;
firstName: string;
lastName: string;
} }
export interface SearchCustomers_search_edges { export interface SearchCustomers_search_edges {

View file

@ -15,6 +15,8 @@ export const searchCustomers = gql`
node { node {
id id
email email
firstName
lastName
} }
} }
pageInfo { pageInfo {