Add basic catalog search
This commit is contained in:
parent
649f35e0d1
commit
4fe0b6da65
11 changed files with 272 additions and 3 deletions
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
70
src/components/Navigator/modes/catalog.ts
Normal file
70
src/components/Navigator/modes/catalog.ts
Normal 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;
|
|
@ -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":
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
75
src/components/Navigator/queries/types/SearchCatalog.ts
Normal file
75
src/components/Navigator/queries/types/SearchCatalog.ts
Normal 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;
|
||||||
|
}
|
66
src/components/Navigator/queries/useCatalogSearch.ts
Normal file
66
src/components/Navigator/queries/useCatalogSearch.ts
Normal 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;
|
|
@ -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";
|
||||||
|
|
|
@ -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),
|
||||||
[]
|
[]
|
||||||
|
|
Loading…
Reference in a new issue