Add customer search
This commit is contained in:
parent
ebf386dfb5
commit
42b55e860b
13 changed files with 135 additions and 10 deletions
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
36
src/components/Navigator/modes/customers.ts
Normal file
36
src/components/Navigator/modes/customers.ts
Normal 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;
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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[] = [
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -15,6 +15,8 @@ export const searchCustomers = gql`
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
email
|
email
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pageInfo {
|
pageInfo {
|
||||||
|
|
Loading…
Reference in a new issue