Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sharp-kangaroos-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---

In quick search you can now search for product variants with SKU. This means product variants are a new item added to the search catalogue. Catalogue items now show their media, if available. Category item display message if they don't have parents.
8 changes: 8 additions & 0 deletions locale/defaultMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@
"context": "expiry date section header",
"string": "Expiry date"
},
"1JSu4L": {
"context": "Navigation search no parent category message",
"string": "This is root category"
},
"1LBYpE": {
"context": "dialog header",
"string": "Delete Menus"
Expand Down Expand Up @@ -3930,6 +3934,10 @@
"context": "customer details, header",
"string": "{fullName} Details"
},
"MtkXb6": {
"context": "Navigation search variant item type name",
"string": "Variant"
},
"MwfSVA": {
"context": "input helper text",
"string": "Customers can not add quantities to a single cart above the limit when value is provided."
Expand Down
8 changes: 6 additions & 2 deletions src/components/NavigatorSearch/NavigatorSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,14 @@ const NavigatorSearch: React.FC = () => {

return (
<DashboardModal open={isNavigatorVisible} onChange={setNavigatorVisibility}>
<DashboardModal.Content size="sm" backgroundColor="default1" padding={0}>
<DashboardModal.Content size="sm" backgroundColor="default1" padding={0} paddingBottom={4}>
<Box __height="500px" width="100%">
<Downshift
itemToString={(item: QuickSearchAction) => (item ? item.label : "")}
itemToString={(item: QuickSearchAction) =>
// 'label' can be string for non-search results (actions)
// and ReactNode for search results
item && typeof item.label === "string" ? item.label : ""
}
onSelect={(item: QuickSearchAction) => {
const shouldRemainVisible = item?.onClick();

Expand Down
44 changes: 29 additions & 15 deletions src/components/NavigatorSearch/NavigatorSearchSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { GetItemPropsOptions } from "downshift";
import React from "react";

import { NavigatorThumbnail } from "./NavigatorThumbnail";
import { QuickSearchAction } from "./types";

interface NavigatorSearchSectionProps {
Expand All @@ -28,6 +29,9 @@
item,
});

// 'thumbnail' is 'null' when the item is from Saleor API, 'undefined' if it's nav item
const shouldRenderThumbnail = typeof item.thumbnail !== "undefined";

Check warning on line 33 in src/components/NavigatorSearch/NavigatorSearchSection.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorSearchSection.tsx#L33

Added line #L33 was not covered by tests

return (
<Box
{...itemProps}
Expand All @@ -43,28 +47,38 @@
active: "accent1Pressed",
}}
selected={highlightedIndex === index}
key={[item.label, item.type].join(":")}
key={[typeof item.label === "string" ? item.label : item.searchValue, item.type].join(
":",
)}
cursor="pointer"
display="flex"
flexDirection="row"
>
{shouldRenderThumbnail && (
<NavigatorThumbnail src={item.thumbnail?.url} alt={item.thumbnail?.alt} />

Check warning on line 58 in src/components/NavigatorSearch/NavigatorSearchSection.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorSearchSection.tsx#L58

Added line #L58 was not covered by tests
)}

<Box as="span" display="inline-block">
{item.symbol && (
<Box as="span" display="inline-block" fontWeight="bold" width={6}>
{item.symbol}
</Box>
)}
<Box as="span" display="inline-block">
{item.symbol && (
<Box as="span" display="inline-block" fontWeight="bold" width={6}>

Check warning on line 64 in src/components/NavigatorSearch/NavigatorSearchSection.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorSearchSection.tsx#L64

Added line #L64 was not covered by tests
{item.symbol}
</Box>
)}

<Text size={3}>{item.label}</Text>
<Text size={3}>{item.label}</Text>

{item.caption && (
<Text size={2} marginLeft={2} color="default2">
{item.caption}
</Text>
)}
</Box>
{item.caption && (
<Text size={2} marginLeft={2} color="default2">

Check warning on line 72 in src/components/NavigatorSearch/NavigatorSearchSection.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorSearchSection.tsx#L72

Added line #L72 was not covered by tests
{item.caption}
</Text>
)}
</Box>

<Box __flex={1} />
<Box __flex={1} />

{item.extraInfo}
{item.extraInfo}
</Box>
</Box>
);
})}
Expand Down
70 changes: 70 additions & 0 deletions src/components/NavigatorSearch/NavigatorThumbnail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Box, Skeleton } from "@saleor/macaw-ui-next";
import React, { useState } from "react";

const defaultProps = {
__height: "40px",
__width: "40px",
__minWidth: "40px",
};

export const NavigatorThumbnail = ({
src,
alt,
}: {
src: string | undefined;
alt: string | undefined;
}) => {
const [loading, setLoading] = useState(true);

Check warning on line 17 in src/components/NavigatorSearch/NavigatorThumbnail.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorThumbnail.tsx#L16-L17

Added lines #L16 - L17 were not covered by tests
const [error, setError] = useState(false || !src);

if (error) {
return (
<Box
{...defaultProps}
backgroundColor="default2"
borderRadius={4}
borderStyle="solid"
borderColor="default1"
borderWidth={1}
marginRight={2}
/>
);
}

return (
<Box
display="flex"
alignItems="center"
justifyContent="center"
padding={0.5}
borderRadius={4}
borderStyle="solid"
borderWidth={1}
borderColor="default1"
overflow="hidden"
marginRight={2}
position="relative"
{...defaultProps}
>
{loading && (
<Skeleton {...defaultProps} position="absolute" left={0} right={0} top={0} bottom={0} />

Check warning on line 50 in src/components/NavigatorSearch/NavigatorThumbnail.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorThumbnail.tsx#L50

Added line #L50 was not covered by tests
)}
<Box
as="img"
width="100%"
height="100%"
objectFit="contain"
src={src}
alt={alt || ""}
borderRadius={2}
backgroundColor="default2"
onError={() => {
setError(true);

Check warning on line 62 in src/components/NavigatorSearch/NavigatorThumbnail.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorThumbnail.tsx#L61-L62

Added lines #L61 - L62 were not covered by tests
}}
onLoad={() => {
setLoading(false);

Check warning on line 65 in src/components/NavigatorSearch/NavigatorThumbnail.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/NavigatorThumbnail.tsx#L64-L65

Added lines #L64 - L65 were not covered by tests
}}
/>
</Box>
);
};
88 changes: 88 additions & 0 deletions src/components/NavigatorSearch/modes/catalog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { SearchCatalogQuery } from "@dashboard/graphql";
import { intlMock } from "@test/intl";

import { searchInCatalog } from "./catalog";

describe("NavigatorSearch / modes / searchInCatalog", () => {
const intl = intlMock;
const navigate = jest.fn();

it("should return variants if search is a sku", () => {
// Arrange
const mockCatalog: SearchCatalogQuery = {
productVariants: {
edges: [
{
node: {
id: "1",
name: "Small",
sku: "PROD-SMALL",
product: {
id: "prod1",
name: "T-Shirt",
thumbnail: null,
category: {
name: "Apparel",
},
},
},
},
],
},
} as SearchCatalogQuery;

// Act
const results = searchInCatalog("PROD-SMALL", intl, navigate, mockCatalog);

// Assert
expect(results).toHaveLength(1);
expect(results[0].searchValue).toContain("PROD-SMALL");
});

it("should return both product and its variant", () => {
// Arrange
const mockCatalog: SearchCatalogQuery = {
products: {
edges: [
{
node: {
id: "prod1",
name: "T-Shirt",
thumbnail: null,
category: {
name: "Apparel",
},
},
},
],
},
productVariants: {
edges: [
{
node: {
id: "1",
name: "Small",
sku: "PROD-SMALL",
product: {
id: "prod1",
name: "T-Shirt",
thumbnail: null,
category: {
name: "Apparel",
},
},
},
},
],
},
} as SearchCatalogQuery;

// Act
const results = searchInCatalog("T-Shirt", intl, navigate, mockCatalog);

// Assert
expect(results).toHaveLength(2);
expect(results[0].label).toBe("T-Shirt");
expect(results[1].searchValue).toContain("PROD-SMALL");
});
});
31 changes: 28 additions & 3 deletions src/components/NavigatorSearch/modes/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import { SearchCatalogQuery } from "@dashboard/graphql";
import { UseNavigatorResult } from "@dashboard/hooks/useNavigator";
import { fuzzySearch } from "@dashboard/misc";
import { productUrl } from "@dashboard/products/urls";
import { productUrl, productVariantEditUrl } from "@dashboard/products/urls";
import { mapEdgesToItems } from "@dashboard/utils/maps";
import { IntlShape } from "react-intl";

import { QuickSearchAction, QuickSearchActionInput } from "../types";
import { getProductVariantLabel } from "./labels";
import messages from "./messages";

export function searchInCatalog(
Expand All @@ -22,45 +23,69 @@
).map<QuickSearchActionInput>(category => ({
caption: intl.formatMessage(messages.category),
label: category.name,
searchValue: category.name,
onClick: () => {
navigate(categoryUrl(category.id));

return false;
},
text: category.name,
type: "catalog",
thumbnail: category.backgroundImage,
extraInfo: category.level === 0 ? intl.formatMessage(messages.root) : undefined,
}));
const collections: QuickSearchActionInput[] = (
mapEdgesToItems(catalog?.collections) || []
).map<QuickSearchActionInput>(collection => ({
caption: intl.formatMessage(messages.collection),
label: collection.name,
searchValue: collection.name,
onClick: () => {
navigate(collectionUrl(collection.id));

return false;
},
text: collection.name,
type: "catalog",
thumbnail: collection.backgroundImage,
}));
const products: QuickSearchActionInput[] = (
mapEdgesToItems(catalog?.products) || []
).map<QuickSearchActionInput>(product => ({
caption: intl.formatMessage(messages.product),
extraInfo: product.category.name,
label: product.name,
searchValue: product.name,
onClick: () => {
navigate(productUrl(product.id));

return false;
},
text: product.name,
type: "catalog",
thumbnail: product.thumbnail,
}));
const variants: QuickSearchActionInput[] = (
mapEdgesToItems(catalog?.productVariants) || []
).map<QuickSearchActionInput>(variant => ({
caption: intl.formatMessage(messages.variant),
extraInfo: variant.product.category.name,
label: getProductVariantLabel(variant),
searchValue: `${variant.product.name} ${variant.name} ${variant.sku}`,
onClick: () => {
navigate(productVariantEditUrl(variant.product.id, variant.id));

Check warning on line 76 in src/components/NavigatorSearch/modes/catalog.ts

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/modes/catalog.ts#L75-L76

Added lines #L75 - L76 were not covered by tests

return false;

Check warning on line 78 in src/components/NavigatorSearch/modes/catalog.ts

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/modes/catalog.ts#L78

Added line #L78 was not covered by tests
},
text: variant.name,
type: "catalog",
thumbnail: variant.product.thumbnail,
}));

const searchableItems = [...categories, ...collections, ...products];
const searchableItems = [...categories, ...collections, ...products, ...variants];
const searchResults = fuzzySearch(searchableItems, search, ["searchValue"], 0.8);

return fuzzySearch(searchableItems, search, ["label"]);
return searchResults;
}

function getCatalogModeActions(
Expand Down
1 change: 1 addition & 0 deletions src/components/NavigatorSearch/modes/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function searchInCommands(

return fuzzySearch(actions, search, ["label"]).map(action => ({
label: action.label,
searchValue: action.label,
onClick: action.onClick,
text: action.label,
type: "action",
Expand Down
4 changes: 4 additions & 0 deletions src/components/NavigatorSearch/modes/customers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
lastName: customer.lastName,
})
: customer.email,
searchValue:
customer.firstName && customer.lastName
? `${customer.firstName} ${customer.lastName}`
: customer.email,

Check warning on line 28 in src/components/NavigatorSearch/modes/customers.ts

View check run for this annotation

Codecov / codecov/patch

src/components/NavigatorSearch/modes/customers.ts#L27-L28

Added lines #L27 - L28 were not covered by tests
onClick: () => {
navigate(customerUrl(customer.id));

Expand Down
1 change: 1 addition & 0 deletions src/components/NavigatorSearch/modes/default/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ function searchInViews(

return fuzzySearch(views, search, ["label"]).map(view => ({
label: view.label,
searchValue: view.label,
onClick: () => {
navigate(view.url);

Expand Down
Loading
Loading