Saleor 3087 Paginate attribute values in filters (#1152)
* Dynamic fetch attribute values in filter list * Update filter attributes fixtures * Change attribute values filter to autocomplete field * Fix unchecking attribute value filter failure * Update test snapshots * Update changelog * Fix cypress tests * Add slug node mapping util
This commit is contained in:
parent
99aa6365be
commit
c3e720a47e
20 changed files with 2145 additions and 941 deletions
|
@ -48,6 +48,7 @@ All notable, unreleased changes to this project will be documented in this file.
|
|||
- Choosing user shipping and billing addresses for draft order - #1082 by @orzechdev
|
||||
- Fix EditorJS inline formatting - #1096 by @orzechdev
|
||||
- Add pagination on attribute values - #1112 by @orzechdev
|
||||
- Paginate attribute values in filters - #1152 by @orzechdev
|
||||
- Fix attribute values input display - #1156 by @orzechdev
|
||||
|
||||
# 2.11.1
|
||||
|
|
|
@ -21,10 +21,11 @@ export const PRODUCTS_LIST = {
|
|||
filterOption: '[data-test-id="filterOption"]',
|
||||
productsOutOfStockOption: '[data-test-id="OUT_OF_STOCK"]',
|
||||
filterBy: {
|
||||
category: '[data-test-id="categories"]',
|
||||
collection: '[data-test-id="collections"]',
|
||||
productType: '[data-test-id="productType"]',
|
||||
stock: '[data-test-id="stock"]'
|
||||
category: '[data-test="filterGroupActive"][data-test-id="categories"]',
|
||||
collection: '[data-test="filterGroupActive"][data-test-id="collections"]',
|
||||
productType:
|
||||
'[data-test="filterGroupActive"][data-test-id="productType"]',
|
||||
stock: '[data-test="filterGroupActive"][data-test-id="stock"]'
|
||||
},
|
||||
filterBySearchInput: '[data-test*="filterField"][data-test*="Input"]'
|
||||
},
|
||||
|
|
|
@ -1666,6 +1666,7 @@ input CustomerFilterInput {
|
|||
numberOfOrders: IntRangeInput
|
||||
placedOrders: DateRangeInput
|
||||
search: String
|
||||
metadata: [MetadataInput]
|
||||
}
|
||||
|
||||
input CustomerInput {
|
||||
|
@ -6132,10 +6133,11 @@ enum WebhookSampleEventTypeEnum {
|
|||
PAGE_DELETED
|
||||
PAYMENT_AUTHORIZE
|
||||
PAYMENT_CAPTURE
|
||||
PAYMENT_CONFIRM
|
||||
PAYMENT_LIST_GATEWAYS
|
||||
PAYMENT_PROCESS
|
||||
PAYMENT_REFUND
|
||||
PAYMENT_VOID
|
||||
PAYMENT_CONFIRM
|
||||
PAYMENT_PROCESS
|
||||
}
|
||||
|
||||
type WebhookUpdate {
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface FilterProps<TFilterKeys extends string = string> {
|
|||
errorMessages?: FilterErrorMessages<TFilterKeys>;
|
||||
menu: IFilter<TFilterKeys>;
|
||||
onFilterAdd: (filter: Array<IFilterElement<string>>) => void;
|
||||
onFilterAttributeFocus?: (id?: string) => void;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
|
@ -91,7 +92,13 @@ const useStyles = makeStyles(
|
|||
{ name: "Filter" }
|
||||
);
|
||||
const Filter: React.FC<FilterProps> = props => {
|
||||
const { currencySymbol, menu, onFilterAdd, errorMessages } = props;
|
||||
const {
|
||||
currencySymbol,
|
||||
menu,
|
||||
onFilterAdd,
|
||||
onFilterAttributeFocus,
|
||||
errorMessages
|
||||
} = props;
|
||||
const classes = useStyles(props);
|
||||
|
||||
const anchor = React.useRef<HTMLDivElement>();
|
||||
|
@ -190,6 +197,7 @@ const Filter: React.FC<FilterProps> = props => {
|
|||
filters={data}
|
||||
onClear={reset}
|
||||
onFilterPropertyChange={dispatch}
|
||||
onFilterAttributeFocus={onFilterAttributeFocus}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
</Grow>
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import { Paper, Typography } from "@material-ui/core";
|
||||
import {
|
||||
ExpansionPanel,
|
||||
ExpansionPanelSummary,
|
||||
makeStyles,
|
||||
Paper,
|
||||
Typography
|
||||
} from "@material-ui/core";
|
||||
import CollectionWithDividers from "@saleor/components/CollectionWithDividers";
|
||||
import Hr from "@saleor/components/Hr";
|
||||
import useStateFromProps from "@saleor/hooks/useStateFromProps";
|
||||
import React from "react";
|
||||
import IconChevronDown from "@saleor/icons/ChevronDown";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { FilterAutocompleteDisplayValues } from "../FilterAutocompleteField";
|
||||
import { FilterReducerAction } from "../reducer";
|
||||
|
@ -18,9 +25,57 @@ import FilterContentBodyNameField from "./FilterContentBodyNameField";
|
|||
import FilterContentHeader from "./FilterContentHeader";
|
||||
import FilterErrorsList from "./FilterErrorsList";
|
||||
|
||||
const useExpanderStyles = makeStyles(
|
||||
() => ({
|
||||
expanded: {},
|
||||
root: {
|
||||
boxShadow: "none",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
||||
"&:before": {
|
||||
content: "none"
|
||||
},
|
||||
|
||||
"&$expanded": {
|
||||
margin: 0,
|
||||
border: "none"
|
||||
}
|
||||
}
|
||||
}),
|
||||
{ name: "FilterContentExpander" }
|
||||
);
|
||||
|
||||
const useSummaryStyles = makeStyles(
|
||||
theme => ({
|
||||
expanded: {},
|
||||
root: {
|
||||
width: "100%",
|
||||
border: "none",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
minHeight: 0,
|
||||
paddingRight: theme.spacing(2),
|
||||
|
||||
"&$expanded": {
|
||||
minHeight: 0
|
||||
}
|
||||
},
|
||||
content: {
|
||||
margin: 0,
|
||||
|
||||
"&$expanded": {
|
||||
margin: 0
|
||||
}
|
||||
}
|
||||
}),
|
||||
{ name: "FilterContentExpanderSummary" }
|
||||
);
|
||||
|
||||
export interface FilterContentProps<T extends string = string> {
|
||||
filters: IFilter<T>;
|
||||
onFilterPropertyChange: React.Dispatch<FilterReducerAction<T>>;
|
||||
onFilterAttributeFocus?: (id?: string) => void;
|
||||
onClear: () => void;
|
||||
onSubmit: () => void;
|
||||
currencySymbol?: string;
|
||||
|
@ -36,9 +91,15 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
filters,
|
||||
onClear,
|
||||
onFilterPropertyChange,
|
||||
onFilterAttributeFocus,
|
||||
onSubmit,
|
||||
dataStructure
|
||||
}) => {
|
||||
const expanderClasses = useExpanderStyles({});
|
||||
const summaryClasses = useSummaryStyles({});
|
||||
|
||||
const [openedFilter, setOpenedFilter] = useState<IFilterElement<string>>();
|
||||
|
||||
const getAutocompleteValuesWithNewValues = (
|
||||
autocompleteDisplayValues: FilterAutocompleteDisplayValues,
|
||||
filterField: IFilterElement<string>
|
||||
|
@ -84,6 +145,36 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
initialAutocompleteDisplayValues
|
||||
};
|
||||
|
||||
const handleFilterAttributeFocus = (filter?: IFilterElement<string>) => {
|
||||
setOpenedFilter(filter);
|
||||
if (onFilterAttributeFocus) {
|
||||
onFilterAttributeFocus(filter?.id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFilterOpen = (filter: IFilterElement<string>) => {
|
||||
if (filter.name !== openedFilter?.name) {
|
||||
handleFilterAttributeFocus(filter);
|
||||
} else {
|
||||
handleFilterAttributeFocus(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFilterPropertyGroupChange = function<T extends string>(
|
||||
action: FilterReducerAction<T>,
|
||||
filter: IFilterElement<string>
|
||||
) {
|
||||
const switchToActive = action.payload.update.active;
|
||||
|
||||
if (switchToActive && filter.name !== openedFilter?.name) {
|
||||
handleFilterAttributeFocus(filter);
|
||||
} else if (!switchToActive && filter.name === openedFilter?.name) {
|
||||
handleFilterAttributeFocus(undefined);
|
||||
}
|
||||
|
||||
onFilterPropertyChange(action);
|
||||
};
|
||||
|
||||
const handleMultipleFieldPropertyChange = function<T extends string>(
|
||||
action: FilterReducerAction<T>
|
||||
) {
|
||||
|
@ -114,11 +205,24 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
{dataStructure
|
||||
.sort((a, b) => (a.name > b.name ? 1 : -1))
|
||||
.map(filter => (
|
||||
<React.Fragment key={filter.name}>
|
||||
<ExpansionPanel
|
||||
key={filter.name}
|
||||
classes={expanderClasses}
|
||||
data-test="channel-availability-item"
|
||||
expanded={filter.name === openedFilter?.name}
|
||||
>
|
||||
<ExpansionPanelSummary
|
||||
expandIcon={<IconChevronDown />}
|
||||
classes={summaryClasses}
|
||||
onClick={() => handleFilterOpen(filter)}
|
||||
>
|
||||
<FilterContentBodyNameField
|
||||
filter={getFilterFromCurrentData(filter)}
|
||||
onFilterPropertyChange={onFilterPropertyChange}
|
||||
onFilterPropertyChange={action =>
|
||||
handleFilterPropertyGroupChange(action, filter)
|
||||
}
|
||||
/>
|
||||
</ExpansionPanelSummary>
|
||||
<FilterErrorsList
|
||||
errors={errors}
|
||||
errorMessages={errorMessages}
|
||||
|
@ -147,7 +251,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
filter={getFilterFromCurrentData(filter)}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</ExpansionPanel>
|
||||
))}
|
||||
</form>
|
||||
</Paper>
|
||||
|
|
|
@ -74,10 +74,6 @@ const FilterContentBody: React.FC<FilterContentBodyProps> = ({
|
|||
const intl = useIntl();
|
||||
const classes = useStyles({});
|
||||
|
||||
if (!filter?.active) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.filterSettings}>
|
||||
{children}
|
||||
|
|
|
@ -43,6 +43,7 @@ const FilterContentBodyNameField: React.FC<FilterContentBodyNameFieldProps> = ({
|
|||
/>
|
||||
}
|
||||
label={filter.label}
|
||||
onClick={event => event.stopPropagation()}
|
||||
onChange={() =>
|
||||
onFilterPropertyChange({
|
||||
payload: {
|
||||
|
|
|
@ -32,6 +32,7 @@ export interface IFilterElement<T extends string = string>
|
|||
type?: FieldType;
|
||||
required?: boolean;
|
||||
multipleFields?: IFilterElement[];
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface FilterBaseFieldProps<T extends string = string> {
|
||||
|
|
|
@ -48,6 +48,7 @@ const FilterBar: React.FC<FilterBarProps> = props => {
|
|||
onAll,
|
||||
onSearchChange,
|
||||
onFilterChange,
|
||||
onFilterAttributeFocus,
|
||||
onTabChange,
|
||||
onTabDelete,
|
||||
onTabSave,
|
||||
|
@ -90,6 +91,7 @@ const FilterBar: React.FC<FilterBarProps> = props => {
|
|||
menu={filterStructure}
|
||||
currencySymbol={currencySymbol}
|
||||
onFilterAdd={onFilterChange}
|
||||
onFilterAttributeFocus={onFilterAttributeFocus}
|
||||
/>
|
||||
<SearchInput
|
||||
initialSearch={initialSearch}
|
||||
|
|
|
@ -87,6 +87,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
onExport,
|
||||
onFetchMore,
|
||||
onFilterChange,
|
||||
onFilterAttributeFocus,
|
||||
onSearchChange,
|
||||
onTabChange,
|
||||
onTabDelete,
|
||||
|
@ -196,6 +197,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
|
|||
initialSearch={initialSearch}
|
||||
onAll={onAll}
|
||||
onFilterChange={onFilterChange}
|
||||
onFilterAttributeFocus={onFilterAttributeFocus}
|
||||
onSearchChange={onSearchChange}
|
||||
onTabChange={onTabChange}
|
||||
onTabDelete={onTabDelete}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { IFilter } from "@saleor/components/Filter";
|
||||
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { AutocompleteFilterOpts, FilterOpts, MinMax } from "@saleor/types";
|
||||
import { StockAvailability } from "@saleor/types/globalTypes";
|
||||
|
@ -22,11 +21,12 @@ export enum ProductFilterKeys {
|
|||
export interface ProductListFilterOpts {
|
||||
attributes: Array<
|
||||
FilterOpts<string[]> & {
|
||||
choices: MultiAutocompleteChoiceType[];
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
>;
|
||||
attributeChoices: FilterOpts<string[]> & AutocompleteFilterOpts;
|
||||
categories: FilterOpts<string[]> & AutocompleteFilterOpts;
|
||||
collections: FilterOpts<string[]> & AutocompleteFilterOpts;
|
||||
price: FilterOpts<MinMax>;
|
||||
|
@ -151,12 +151,21 @@ export function createFilterStructure(
|
|||
active: opts.productType.active
|
||||
},
|
||||
...opts.attributes.map(attr => ({
|
||||
...createOptionsField(
|
||||
...createAutocompleteField(
|
||||
attr.slug as any,
|
||||
attr.name,
|
||||
attr.value,
|
||||
opts.attributeChoices.displayValues,
|
||||
true,
|
||||
attr.choices
|
||||
opts.attributeChoices.choices,
|
||||
{
|
||||
hasMore: opts.attributeChoices.hasMore,
|
||||
initialSearch: "",
|
||||
loading: opts.attributeChoices.loading,
|
||||
onFetchMore: opts.attributeChoices.onFetchMore,
|
||||
onSearchChange: opts.attributeChoices.onSearchChange
|
||||
},
|
||||
attr.id
|
||||
),
|
||||
active: attr.active,
|
||||
group: ProductFilterKeys.attributes
|
||||
|
|
|
@ -27,10 +27,7 @@ import {
|
|||
GridAttributes,
|
||||
GridAttributesVariables
|
||||
} from "./types/GridAttributes";
|
||||
import {
|
||||
InitialProductFilterAttributes,
|
||||
InitialProductFilterAttributesVariables
|
||||
} from "./types/InitialProductFilterAttributes";
|
||||
import { InitialProductFilterAttributes } from "./types/InitialProductFilterAttributes";
|
||||
import {
|
||||
InitialProductFilterCategories,
|
||||
InitialProductFilterCategoriesVariables
|
||||
|
@ -60,13 +57,7 @@ import {
|
|||
} from "./types/ProductVariantDetails";
|
||||
|
||||
const initialProductFilterAttributesQuery = gql`
|
||||
${pageInfoFragment}
|
||||
query InitialProductFilterAttributes(
|
||||
$firstValues: Int
|
||||
$afterValues: String
|
||||
$lastValues: Int
|
||||
$beforeValues: String
|
||||
) {
|
||||
query InitialProductFilterAttributes {
|
||||
attributes(
|
||||
first: 100
|
||||
filter: { filterableInDashboard: true, type: PRODUCT_TYPE }
|
||||
|
@ -76,24 +67,6 @@ const initialProductFilterAttributesQuery = gql`
|
|||
id
|
||||
name
|
||||
slug
|
||||
choices(
|
||||
first: $firstValues
|
||||
after: $afterValues
|
||||
last: $lastValues
|
||||
before: $beforeValues
|
||||
) {
|
||||
pageInfo {
|
||||
...PageInfoFragment
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +74,7 @@ const initialProductFilterAttributesQuery = gql`
|
|||
`;
|
||||
export const useInitialProductFilterAttributesQuery = makeQuery<
|
||||
InitialProductFilterAttributes,
|
||||
InitialProductFilterAttributesVariables
|
||||
never
|
||||
>(initialProductFilterAttributesQuery);
|
||||
|
||||
const initialProductFilterCategoriesQuery = gql`
|
||||
|
|
|
@ -7,39 +7,11 @@
|
|||
// GraphQL query operation: InitialProductFilterAttributes
|
||||
// ====================================================
|
||||
|
||||
export interface InitialProductFilterAttributes_attributes_edges_node_choices_pageInfo {
|
||||
__typename: "PageInfo";
|
||||
endCursor: string | null;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
startCursor: string | null;
|
||||
}
|
||||
|
||||
export interface InitialProductFilterAttributes_attributes_edges_node_choices_edges_node {
|
||||
__typename: "AttributeValue";
|
||||
id: string;
|
||||
name: string | null;
|
||||
slug: string | null;
|
||||
}
|
||||
|
||||
export interface InitialProductFilterAttributes_attributes_edges_node_choices_edges {
|
||||
__typename: "AttributeValueCountableEdge";
|
||||
cursor: string;
|
||||
node: InitialProductFilterAttributes_attributes_edges_node_choices_edges_node;
|
||||
}
|
||||
|
||||
export interface InitialProductFilterAttributes_attributes_edges_node_choices {
|
||||
__typename: "AttributeValueCountableConnection";
|
||||
pageInfo: InitialProductFilterAttributes_attributes_edges_node_choices_pageInfo;
|
||||
edges: InitialProductFilterAttributes_attributes_edges_node_choices_edges[];
|
||||
}
|
||||
|
||||
export interface InitialProductFilterAttributes_attributes_edges_node {
|
||||
__typename: "Attribute";
|
||||
id: string;
|
||||
name: string | null;
|
||||
slug: string | null;
|
||||
choices: InitialProductFilterAttributes_attributes_edges_node_choices | null;
|
||||
}
|
||||
|
||||
export interface InitialProductFilterAttributes_attributes_edges {
|
||||
|
@ -55,10 +27,3 @@ export interface InitialProductFilterAttributes_attributes {
|
|||
export interface InitialProductFilterAttributes {
|
||||
attributes: InitialProductFilterAttributes_attributes | null;
|
||||
}
|
||||
|
||||
export interface InitialProductFilterAttributesVariables {
|
||||
firstValues?: number | null;
|
||||
afterValues?: string | null;
|
||||
lastValues?: number | null;
|
||||
beforeValues?: string | null;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
productUrl
|
||||
} from "@saleor/products/urls";
|
||||
import useAttributeSearch from "@saleor/searches/useAttributeSearch";
|
||||
import useAttributeValueSearch from "@saleor/searches/useAttributeValueSearch";
|
||||
import useCategorySearch from "@saleor/searches/useCategorySearch";
|
||||
import useCollectionSearch from "@saleor/searches/useCollectionSearch";
|
||||
import useProductTypeSearch from "@saleor/searches/useProductTypeSearch";
|
||||
|
@ -57,7 +58,7 @@ import createFilterHandlers from "@saleor/utils/handlers/filterHandlers";
|
|||
import { mapEdgesToItems } from "@saleor/utils/maps";
|
||||
import { getSortUrlVariables } from "@saleor/utils/sort";
|
||||
import { useWarehouseList } from "@saleor/warehouses/queries";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
|
||||
import ProductListPage from "../../components/ProductListPage";
|
||||
|
@ -95,11 +96,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
const intl = useIntl();
|
||||
const {
|
||||
data: initialFilterAttributes
|
||||
} = useInitialProductFilterAttributesQuery({
|
||||
variables: {
|
||||
firstValues: 100
|
||||
}
|
||||
});
|
||||
} = useInitialProductFilterAttributesQuery({});
|
||||
const {
|
||||
data: initialFilterCategories
|
||||
} = useInitialProductFilterCategoriesQuery({
|
||||
|
@ -148,6 +145,15 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
first: 10
|
||||
}
|
||||
});
|
||||
const [focusedAttribute, setFocusedAttribute] = useState<string>();
|
||||
const searchAttributeValues = useAttributeValueSearch({
|
||||
variables: {
|
||||
id: focusedAttribute,
|
||||
...DEFAULT_INITIAL_SEARCH_DATA,
|
||||
first: 10
|
||||
},
|
||||
skip: !focusedAttribute
|
||||
});
|
||||
const warehouses = useWarehouseList({
|
||||
variables: {
|
||||
first: 100
|
||||
|
@ -321,6 +327,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
const filterOpts = getFilterOpts(
|
||||
params,
|
||||
mapEdgesToItems(initialFilterAttributes?.attributes),
|
||||
searchAttributeValues,
|
||||
{
|
||||
initial: mapEdgesToItems(initialFilterCategories?.categories),
|
||||
search: searchCategories
|
||||
|
@ -422,6 +429,7 @@ export const ProductList: React.FC<ProductListProps> = ({ params }) => {
|
|||
toggleAll={toggleAll}
|
||||
onSearchChange={handleSearchChange}
|
||||
onFilterChange={changeFilters}
|
||||
onFilterAttributeFocus={setFocusedAttribute}
|
||||
onTabSave={() => openModal("save-search")}
|
||||
onTabDelete={() => openModal("delete-search")}
|
||||
onTabChange={handleTabChange}
|
||||
|
|
|
@ -8,6 +8,10 @@ import { InitialProductFilterAttributes_attributes_edges_node } from "@saleor/pr
|
|||
import { InitialProductFilterCategories_categories_edges_node } from "@saleor/products/types/InitialProductFilterCategories";
|
||||
import { InitialProductFilterCollections_collections_edges_node } from "@saleor/products/types/InitialProductFilterCollections";
|
||||
import { InitialProductFilterProductTypes_productTypes_edges_node } from "@saleor/products/types/InitialProductFilterProductTypes";
|
||||
import {
|
||||
SearchAttributeValues,
|
||||
SearchAttributeValuesVariables
|
||||
} from "@saleor/searches/types/SearchAttributeValues";
|
||||
import {
|
||||
SearchCategories,
|
||||
SearchCategoriesVariables
|
||||
|
@ -20,7 +24,11 @@ import {
|
|||
SearchProductTypes,
|
||||
SearchProductTypesVariables
|
||||
} from "@saleor/searches/types/SearchProductTypes";
|
||||
import { mapEdgesToItems, mapNodeToChoice } from "@saleor/utils/maps";
|
||||
import {
|
||||
mapEdgesToItems,
|
||||
mapNodeToChoice,
|
||||
mapSlugNodeToChoice
|
||||
} from "@saleor/utils/maps";
|
||||
import isArray from "lodash/isArray";
|
||||
|
||||
import { IFilterElement } from "../../../components/Filter";
|
||||
|
@ -50,6 +58,10 @@ export const PRODUCT_FILTERS_KEY = "productFilters";
|
|||
export function getFilterOpts(
|
||||
params: ProductListUrlFilters,
|
||||
attributes: InitialProductFilterAttributes_attributes_edges_node[],
|
||||
focusedAttributeChoices: UseSearchResult<
|
||||
SearchAttributeValues,
|
||||
SearchAttributeValuesVariables
|
||||
>,
|
||||
categories: {
|
||||
initial: InitialProductFilterCategories_categories_edges_node[];
|
||||
search: UseSearchResult<SearchCategories, SearchCategoriesVariables>;
|
||||
|
@ -68,17 +80,31 @@ export function getFilterOpts(
|
|||
.sort((a, b) => (a.name > b.name ? 1 : -1))
|
||||
.map(attr => ({
|
||||
active: maybe(() => params.attributes[attr.slug].length > 0, false),
|
||||
choices: attr.choices?.edges?.map(val => ({
|
||||
label: val.node.name,
|
||||
value: val.node.slug
|
||||
})),
|
||||
id: attr.id,
|
||||
name: attr.name,
|
||||
slug: attr.slug,
|
||||
value:
|
||||
!!params.attributes && params.attributes[attr.slug]
|
||||
? params.attributes[attr.slug]
|
||||
? dedupeFilter(params.attributes[attr.slug])
|
||||
: []
|
||||
})),
|
||||
attributeChoices: {
|
||||
active: true,
|
||||
choices: mapSlugNodeToChoice(
|
||||
mapEdgesToItems(focusedAttributeChoices.result.data?.attribute?.choices)
|
||||
),
|
||||
displayValues: mapNodeToChoice(
|
||||
mapEdgesToItems(focusedAttributeChoices.result.data?.attribute?.choices)
|
||||
),
|
||||
hasMore:
|
||||
focusedAttributeChoices.result.data?.attribute?.choices?.pageInfo
|
||||
?.hasNextPage || false,
|
||||
initialSearch: "",
|
||||
loading: focusedAttributeChoices.result.loading,
|
||||
onFetchMore: focusedAttributeChoices.loadMore,
|
||||
onSearchChange: focusedAttributeChoices.search,
|
||||
value: null
|
||||
},
|
||||
categories: {
|
||||
active: !!params.categories,
|
||||
choices: mapNodeToChoice(
|
||||
|
|
|
@ -5,14 +5,12 @@ import { fetchMoreProps, searchPageProps } from "@saleor/fixtures";
|
|||
import { ProductListFilterOpts } from "@saleor/products/components/ProductListPage";
|
||||
import { productTypes } from "@saleor/productTypes/fixtures";
|
||||
import { StockAvailability } from "@saleor/types/globalTypes";
|
||||
import { mapEdgesToItems, mapSlugNodeToChoice } from "@saleor/utils/maps";
|
||||
|
||||
export const productListFilterOpts: ProductListFilterOpts = {
|
||||
attributes: attributes.map(attr => ({
|
||||
id: attr.id,
|
||||
active: false,
|
||||
choices: attr.choices.edges.map(val => ({
|
||||
label: val.node.name,
|
||||
value: val.node.slug
|
||||
})),
|
||||
name: attr.name,
|
||||
slug: attr.slug,
|
||||
value: [
|
||||
|
@ -20,6 +18,14 @@ export const productListFilterOpts: ProductListFilterOpts = {
|
|||
attr.choices.edges.length > 2 && attr.choices.edges[2].node.slug
|
||||
]
|
||||
})),
|
||||
attributeChoices: {
|
||||
...fetchMoreProps,
|
||||
...searchPageProps,
|
||||
active: false,
|
||||
value: null,
|
||||
choices: mapSlugNodeToChoice(mapEdgesToItems(attributes[0].choices)),
|
||||
displayValues: mapSlugNodeToChoice(mapEdgesToItems(attributes[0].choices))
|
||||
},
|
||||
categories: {
|
||||
...fetchMoreProps,
|
||||
...searchPageProps,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -107,6 +107,7 @@ export interface FilterPageProps<TKeys extends string, TOpts extends {}>
|
|||
export interface FilterProps<TKeys extends string> {
|
||||
currencySymbol?: string;
|
||||
onFilterChange: (filter: IFilter<TKeys>) => void;
|
||||
onFilterAttributeFocus?: (id?: string) => void;
|
||||
}
|
||||
|
||||
export interface TabPageProps {
|
||||
|
@ -133,6 +134,9 @@ export interface PartialMutationProviderOutput<
|
|||
export interface Node {
|
||||
id: string;
|
||||
}
|
||||
export interface SlugNode {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export type Pagination = Partial<{
|
||||
after: string;
|
||||
|
|
|
@ -72,7 +72,8 @@ export function createAutocompleteField<T extends string>(
|
|||
displayValues: MultiAutocompleteChoiceType[],
|
||||
multiple: boolean,
|
||||
options: MultiAutocompleteChoiceType[],
|
||||
opts: FetchMoreProps & SearchPageProps
|
||||
opts: FetchMoreProps & SearchPageProps,
|
||||
id?: string
|
||||
): IFilterElement<T> {
|
||||
return {
|
||||
...opts,
|
||||
|
@ -83,7 +84,8 @@ export function createAutocompleteField<T extends string>(
|
|||
name,
|
||||
options,
|
||||
type: FieldType.autocomplete,
|
||||
value: defaultValue
|
||||
value: defaultValue,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ShopInfo_shop_countries } from "@saleor/components/Shop/types/ShopInfo"
|
|||
import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField";
|
||||
import { MetadataItem } from "@saleor/fragments/types/MetadataItem";
|
||||
import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages";
|
||||
import { Node } from "@saleor/types";
|
||||
import { Node, SlugNode } from "@saleor/types";
|
||||
import { MetadataInput } from "@saleor/types/globalTypes";
|
||||
|
||||
interface EdgesType<T> {
|
||||
|
@ -49,6 +49,19 @@ export function mapNodeToChoice(
|
|||
}));
|
||||
}
|
||||
|
||||
export function mapSlugNodeToChoice(
|
||||
nodes: Array<SlugNode & Record<"name", string>>
|
||||
): Array<SingleAutocompleteChoiceType | MultiAutocompleteChoiceType> {
|
||||
if (!nodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return nodes.map(node => ({
|
||||
label: node.name,
|
||||
value: node.slug
|
||||
}));
|
||||
}
|
||||
|
||||
export function mapMetadataItemToInput(item: MetadataItem): MetadataInput {
|
||||
return {
|
||||
key: item.key,
|
||||
|
|
Loading…
Reference in a new issue