Add basic catalog search

This commit is contained in:
dominik-zeglen 2019-11-25 15:32:10 +01:00
parent 649f35e0d1
commit 4fe0b6da65
11 changed files with 272 additions and 3 deletions

View file

@ -6,9 +6,11 @@ import { useIntl } from "react-intl";
import { import {
getActions, getActions,
getCatalog,
getCustomers, getCustomers,
getViews, getViews,
hasActions, hasActions,
hasCatalog,
hasCustomers, hasCustomers,
hasViews hasViews
} from "./modes/utils"; } from "./modes/utils";
@ -107,6 +109,18 @@ const Navigator: React.FC = () => {
offset={getItemOffset(actions, [getViews, getActions])} offset={getItemOffset(actions, [getViews, getActions])}
/> />
)} )}
{hasCatalog(actions) && (
<NavigatorSection
label={intl.formatMessage({
defaultMessage: "Search in Catalog",
description: "navigator section header"
})}
getItemProps={getItemProps}
highlightedIndex={highlightedIndex}
items={getCatalog(actions)}
offset={0}
/>
)}
</div> </div>
)} )}
</Downshift> </Downshift>

View file

@ -52,7 +52,13 @@ 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 === "customers" ? "@" : ">"} {mode === "orders"
? "#"
: mode === "customers"
? "@"
: mode === "catalog"
? "$"
: ">"}
</span> </span>
)} )}
<input <input

View file

@ -0,0 +1,70 @@
import { score } from "fuzzaldrin";
import { IntlShape } from "react-intl";
import { categoryUrl } from "@saleor/categories/urls";
import { collectionUrl } from "@saleor/collections/urls";
import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { maybe } from "@saleor/misc";
import { productUrl } from "@saleor/products/urls";
import { SearchCatalog } from "../queries/types/SearchCatalog";
import { QuickSearchAction, QuickSearchActionInput } from "../types";
import messages from "./messages";
const threshold = 0.05;
const maxActions = 5;
export function searchInCatalog(
search: string,
intl: IntlShape,
navigate: UseNavigatorResult,
catalog: SearchCatalog
): QuickSearchAction[] {
const categories: QuickSearchActionInput[] = maybe(
() => catalog.categories.edges.map(edge => edge.node),
[]
).map(category => ({
caption: intl.formatMessage(messages.category),
label: category.name,
onClick: () => navigate(categoryUrl(category.id)),
score: score(category.name, search),
text: category.name,
type: "catalog"
}));
const collections: QuickSearchActionInput[] = maybe(
() => catalog.collections.edges.map(edge => edge.node),
[]
).map(collection => ({
caption: intl.formatMessage(messages.collection),
label: collection.name,
onClick: () => navigate(collectionUrl(collection.id)),
score: score(collection.name, search),
text: collection.name,
type: "catalog"
}));
const products: QuickSearchActionInput[] = maybe(
() => catalog.products.edges.map(edge => edge.node),
[]
).map(product => ({
caption: intl.formatMessage(messages.product),
label: product.name,
onClick: () => navigate(productUrl(product.id)),
score: score(product.name, search),
text: product.name,
type: "catalog"
}));
return [...categories, ...collections, ...products];
}
function getCatalogModeActions(
query: string,
intl: IntlShape,
navigate: UseNavigatorResult,
catalog: SearchCatalog
): QuickSearchAction[] {
return searchInCatalog(query, intl, navigate, catalog);
}
export default getCatalogModeActions;

View file

@ -4,6 +4,7 @@ import { UseNavigatorResult } from "@saleor/hooks/useNavigator";
import { OrderDraftCreate } from "@saleor/orders/types/OrderDraftCreate"; 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 getCatalogModeActions from "./catalog";
import getCommandModeActions from "./commands"; import getCommandModeActions from "./commands";
import getCustomersModeActions from "./customers"; import getCustomersModeActions from "./customers";
import getDefaultModeActions from "./default"; import getDefaultModeActions from "./default";
@ -21,6 +22,8 @@ function getModeActions(
} }
): QuickSearchAction[] { ): QuickSearchAction[] {
switch (mode) { switch (mode) {
case "catalog":
return getCatalogModeActions(query, intl, cbs.navigate, queries.catalog);
case "commands": case "commands":
return getCommandModeActions(query, intl, cbs.navigate, cbs.createOrder); return getCommandModeActions(query, intl, cbs.navigate, cbs.createOrder);
case "customers": case "customers":

View file

@ -21,6 +21,14 @@ const messages = defineMessages({
defaultMessage: "Add Voucher", defaultMessage: "Add Voucher",
description: "button" description: "button"
}, },
category: {
defaultMessage: "Category",
description: "catalog item type"
},
collection: {
defaultMessage: "Collection",
description: "catalog item type"
},
createOrder: { createOrder: {
defaultMessage: "Create Order", defaultMessage: "Create Order",
description: "button" description: "button"
@ -31,6 +39,10 @@ const messages = defineMessages({
goToOrder: { goToOrder: {
defaultMessage: "Go to order #{orderNumber}", defaultMessage: "Go to order #{orderNumber}",
description: "navigator action" description: "navigator action"
},
product: {
defaultMessage: "Product",
description: "catalog item type"
} }
}); });

View file

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

View file

@ -22,3 +22,10 @@ export function getCustomers(
export function hasCustomers(actions: QuickSearchAction[]): boolean { export function hasCustomers(actions: QuickSearchAction[]): boolean {
return getCustomers(actions).length > 0; return getCustomers(actions).length > 0;
} }
export function getCatalog(actions: QuickSearchAction[]): QuickSearchAction[] {
return actions.filter(action => action.type === "catalog");
}
export function hasCatalog(actions: QuickSearchAction[]): boolean {
return getCatalog(actions).length > 0;
}

View file

@ -0,0 +1,75 @@
/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.
// ====================================================
// GraphQL query operation: SearchCatalog
// ====================================================
export interface SearchCatalog_categories_edges_node {
__typename: "Category";
id: string;
name: string;
}
export interface SearchCatalog_categories_edges {
__typename: "CategoryCountableEdge";
node: SearchCatalog_categories_edges_node;
}
export interface SearchCatalog_categories {
__typename: "CategoryCountableConnection";
edges: SearchCatalog_categories_edges[];
}
export interface SearchCatalog_collections_edges_node {
__typename: "Collection";
id: string;
name: string;
isPublished: boolean;
publicationDate: any | null;
}
export interface SearchCatalog_collections_edges {
__typename: "CollectionCountableEdge";
node: SearchCatalog_collections_edges_node;
}
export interface SearchCatalog_collections {
__typename: "CollectionCountableConnection";
edges: SearchCatalog_collections_edges[];
}
export interface SearchCatalog_products_edges_node_category {
__typename: "Category";
id: string;
name: string;
}
export interface SearchCatalog_products_edges_node {
__typename: "Product";
id: string;
category: SearchCatalog_products_edges_node_category;
name: string;
}
export interface SearchCatalog_products_edges {
__typename: "ProductCountableEdge";
node: SearchCatalog_products_edges_node;
}
export interface SearchCatalog_products {
__typename: "ProductCountableConnection";
edges: SearchCatalog_products_edges[];
}
export interface SearchCatalog {
categories: SearchCatalog_categories | null;
collections: SearchCatalog_collections | null;
products: SearchCatalog_products | null;
}
export interface SearchCatalogVariables {
first: number;
query: string;
}

View file

@ -0,0 +1,66 @@
import gql from "graphql-tag";
import { useState } from "react";
import makeQuery, { UseQueryResult } from "@saleor/hooks/makeQuery";
import useDebounce from "@saleor/hooks/useDebounce";
import { SearchCatalog, SearchCatalogVariables } from "./types/SearchCatalog";
const searchCatalog = gql`
query SearchCatalog($first: Int!, $query: String!) {
categories(first: $first, filter: { search: $query }) {
edges {
node {
id
name
}
}
}
collections(first: $first, filter: { search: $query }) {
edges {
node {
id
name
isPublished
publicationDate
}
}
}
products(first: $first, filter: { search: $query }) {
edges {
node {
id
category {
id
name
}
name
}
}
}
}
`;
const useSearchCatalogQuery = makeQuery<SearchCatalog, SearchCatalogVariables>(
searchCatalog
);
type UseSearchCatalog = [
UseQueryResult<SearchCatalog, SearchCatalogVariables>,
(query: string) => void
];
function useSearchCatalog(first: number): UseSearchCatalog {
const [query, setQuery] = useState("");
const setQueryDebounced = useDebounce(setQuery);
const result = useSearchCatalogQuery({
skip: query === "",
variables: {
first,
query
}
});
return [result, setQueryDebounced];
}
export default useSearchCatalog;

View file

@ -1,4 +1,4 @@
export type QuickSearchActionType = "action" | "customer" | "view"; export type QuickSearchActionType = "action" | "catalog" | "customer" | "view";
export interface QuickSearchAction { export interface QuickSearchAction {
caption?: string; caption?: string;
@ -14,4 +14,9 @@ export interface QuickSearchActionInput extends QuickSearchAction {
text: string; text: string;
} }
export type QuickSearchMode = "default" | "commands" | "orders" | "customers"; export type QuickSearchMode =
| "default"
| "catalog"
| "commands"
| "orders"
| "customers";

View file

@ -11,6 +11,7 @@ import { orderUrl } from "@saleor/orders/urls";
import useCustomerSearch from "@saleor/searches/useCustomerSearch"; 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 useSearchCatalog from "./queries/useCatalogSearch";
import useCheckIfOrderExists from "./queries/useCheckIfOrderExists"; import useCheckIfOrderExists from "./queries/useCheckIfOrderExists";
import { QuickSearchAction, QuickSearchMode } from "./types"; import { QuickSearchAction, QuickSearchMode } from "./types";
@ -35,6 +36,7 @@ function useQuickSearch(
first: 5 first: 5
} }
}); });
const [{ data: catalog }, searchCatalog] = useSearchCatalog(5);
const [createOrder] = useOrderDraftCreateMutation({ const [createOrder] = useOrderDraftCreateMutation({
onCompleted: result => { onCompleted: result => {
if (result.draftOrderCreate.errors.length === 0) { if (result.draftOrderCreate.errors.length === 0) {
@ -86,6 +88,9 @@ function useQuickSearch(
case "# ": case "# ":
setMode("orders"); setMode("orders");
break; break;
case "$ ":
setMode("catalog");
break;
default: default:
setQuery(value); setQuery(value);
} }
@ -93,6 +98,9 @@ function useQuickSearch(
if (mode === "orders" && isQueryValidOrderNumber(value)) { if (mode === "orders" && isQueryValidOrderNumber(value)) {
getOrderData(getGqlOrderId(value)); getOrderData(getGqlOrderId(value));
} }
if (mode === "catalog") {
searchCatalog(value);
}
setQuery(value); setQuery(value);
} }
@ -110,6 +118,7 @@ function useQuickSearch(
query, query,
intl, intl,
{ {
catalog,
customers: maybe( customers: maybe(
() => customers.data.search.edges.map(edge => edge.node), () => customers.data.search.edges.map(edge => edge.node),
[] []