diff --git a/src/components/Filter/FilterContent.tsx b/src/components/Filter/FilterContent.tsx index d0eefc5a6..584336963 100644 --- a/src/components/Filter/FilterContent.tsx +++ b/src/components/Filter/FilterContent.tsx @@ -12,11 +12,16 @@ import { fade } from "@material-ui/core/styles/colorManipulator"; import { buttonMessages } from "@saleor/intl"; import { TextField } from "@material-ui/core"; import { toggle } from "@saleor/utils/lists"; +import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; +import useStateFromProps from "@saleor/hooks/useStateFromProps"; import Hr from "../Hr"; import Checkbox from "../Checkbox"; import SingleSelectField from "../SingleSelectField"; import { SingleAutocompleteChoiceType } from "../SingleAutocompleteSelectField"; import FormSpacer from "../FormSpacer"; +import MultiAutocompleteSelectField, { + MultiAutocompleteChoiceType +} from "../MultiAutocompleteSelectField"; import { IFilter, FieldType, FilterType } from "./types"; import Arrow from "./Arrow"; import { FilterReducerAction } from "./reducer"; @@ -107,6 +112,18 @@ const FilterContent: React.FC = ({ }) => { const intl = useIntl(); const classes = useStyles({}); + const [ + autocompleteDisplayValues, + setAutocompleteDisplayValues + ] = useStateFromProps>( + filters.reduce((acc, filterField) => { + if (filterField.type === FieldType.autocomplete) { + acc[filterField.name] = filterField.displayValues; + } + + return acc; + }, {}) + ); return ( @@ -409,6 +426,54 @@ const FilterContent: React.FC = ({ /> ))} + {filterField.type === FieldType.autocomplete && + filterField.multiple && ( + + onFilterPropertyChange({ + payload: { + name: filterField.name, + update: { + value: toggle( + event.target.value, + filterField.value, + (a, b) => a === b + ) + } + }, + type: "set-property" + }), + value => + setAutocompleteDisplayValues({ + ...autocompleteDisplayValues, + [filterField.name]: toggle( + value[0], + autocompleteDisplayValues[filterField.name], + (a, b) => a.value === b.value + ) + }), + [], + filterField.options + )} + fetchChoices={filterField.onSearchChange} + loading={filterField.loading} + data-tc={filterField.name} + key={filterField.name} + /> + )} )} diff --git a/src/components/Filter/reducer.ts b/src/components/Filter/reducer.ts index a36a09050..33eb850db 100644 --- a/src/components/Filter/reducer.ts +++ b/src/components/Filter/reducer.ts @@ -1,16 +1,39 @@ import { update } from "@saleor/utils/lists"; import { IFilter, IFilterElementMutableData } from "./types"; -export type FilterReducerActionType = "clear" | "reset" | "set-property"; +export type FilterReducerActionType = + | "clear" + | "merge" + | "reset" + | "set-property"; export interface FilterReducerAction { type: FilterReducerActionType; payload: Partial<{ name: T; update: Partial; - reset: IFilter; + new: IFilter; }>; } +function merge( + prevState: IFilter, + newState: IFilter +): IFilter { + return newState.map(newFilter => { + const prevFilter = prevState.find( + prevFilter => prevFilter.name === newFilter.name + ); + if (!!prevFilter) { + return { + ...newFilter, + active: prevFilter.active + }; + } + + return newFilter; + }); +} + function setProperty( prevState: IFilter, filter: T, @@ -32,8 +55,10 @@ function reduceFilter( switch (action.type) { case "set-property": return setProperty(prevState, action.payload.name, action.payload.update); + case "merge": + return merge(prevState, action.payload.new); case "reset": - return action.payload.reset; + return action.payload.new; default: return prevState; diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index eea2f96d5..ea257c8f7 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -1,7 +1,8 @@ -import { FetchMoreProps } from "@saleor/types"; +import { FetchMoreProps, SearchPageProps } from "@saleor/types"; import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; export enum FieldType { + autocomplete, boolean, date, dateTime, @@ -18,9 +19,10 @@ export interface IFilterElementMutableData { value: T[]; } export interface IFilterElement - extends Partial, - IFilterElementMutableData { + extends IFilterElementMutableData, + Partial { autocomplete?: boolean; + displayValues?: MultiAutocompleteChoiceType[]; label: string; name: T; type: FieldType; diff --git a/src/components/Filter/useFilter.ts b/src/components/Filter/useFilter.ts index 0a9b5ccb3..e51bebe56 100644 --- a/src/components/Filter/useFilter.ts +++ b/src/components/Filter/useFilter.ts @@ -17,12 +17,20 @@ function useFilter(initialFilter: IFilter): UseFilter { const reset = () => dispatchFilterAction({ payload: { - reset: initialFilter + new: initialFilter }, type: "reset" }); - useEffect(reset, [initialFilter]); + const refresh = () => + dispatchFilterAction({ + payload: { + new: initialFilter + }, + type: "merge" + }); + + useEffect(refresh, [initialFilter]); return [data, dispatchFilterAction, reset]; } diff --git a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx index 902103756..48b6e0692 100644 --- a/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx +++ b/src/components/SingleAutocompleteSelectField/SingleAutocompleteSelectField.tsx @@ -41,17 +41,14 @@ export interface SingleAutocompleteSelectFieldProps onChange: (event: React.ChangeEvent) => void; } -const DebounceAutocomplete: React.ComponentType< - DebounceProps -> = Debounce; +const DebounceAutocomplete: React.ComponentType> = Debounce; -const SingleAutocompleteSelectFieldComponent: React.FC< - SingleAutocompleteSelectFieldProps -> = props => { +const SingleAutocompleteSelectFieldComponent: React.FC = props => { const { - choices, - allowCustomValues, + choices, disabled, displayValue, emptyOption, @@ -169,9 +166,11 @@ const SingleAutocompleteSelectFieldComponent: React.FC< ); }; -const SingleAutocompleteSelectField: React.FC< - SingleAutocompleteSelectFieldProps -> = ({ choices, fetchChoices, ...rest }) => { +const SingleAutocompleteSelectField: React.FC = ({ + choices, + fetchChoices, + ...rest +}) => { const [query, setQuery] = React.useState(""); if (fetchChoices) { return ( diff --git a/src/discounts/components/VoucherListPage/filters.ts b/src/discounts/components/VoucherListPage/filters.ts index 4faa735cd..e1c2c56a5 100644 --- a/src/discounts/components/VoucherListPage/filters.ts +++ b/src/discounts/components/VoucherListPage/filters.ts @@ -7,8 +7,7 @@ import { } from "@saleor/utils/filters/fields"; import { VoucherDiscountType, - DiscountStatusEnum, - DiscountValueTypeEnum + DiscountStatusEnum } from "@saleor/types/globalTypes"; import { MinMax, FilterOpts } from "@saleor/types"; import { IFilter } from "@saleor/components/Filter"; @@ -118,11 +117,15 @@ export function createFilterStructure( [ { label: intl.formatMessage(messages.fixed), - value: DiscountValueTypeEnum.FIXED + value: VoucherDiscountType.FIXED }, { label: intl.formatMessage(messages.percentage), - value: DiscountValueTypeEnum.PERCENTAGE + value: VoucherDiscountType.PERCENTAGE + }, + { + label: intl.formatMessage(messages.percentage), + value: VoucherDiscountType.SHIPPING } ] ), diff --git a/src/products/components/ProductListPage/filters.ts b/src/products/components/ProductListPage/filters.ts index 3f65c2211..70205b500 100644 --- a/src/products/components/ProductListPage/filters.ts +++ b/src/products/components/ProductListPage/filters.ts @@ -1,20 +1,26 @@ import { defineMessages, IntlShape } from "react-intl"; -import { FilterOpts, MinMax } from "@saleor/types"; +import { FilterOpts, MinMax, AutocompleteFilterOpts } from "@saleor/types"; import { StockAvailability } from "@saleor/types/globalTypes"; import { createOptionsField, - createPriceField + createPriceField, + createAutocompleteField } from "@saleor/utils/filters/fields"; import { IFilter } from "@saleor/components/Filter"; +import { sectionNames } from "@saleor/intl"; export enum ProductFilterKeys { + categories = "categories", + collections = "collections", status = "status", price = "price", stock = "stock" } export interface ProductListFilterOpts { + categories: FilterOpts & AutocompleteFilterOpts; + collections: FilterOpts & AutocompleteFilterOpts; price: FilterOpts; status: FilterOpts; stockStatus: FilterOpts; @@ -105,6 +111,42 @@ export function createFilterStructure( opts.price.value ), active: opts.price.active + }, + { + ...createAutocompleteField( + ProductFilterKeys.categories, + intl.formatMessage(sectionNames.categories), + opts.categories.value, + opts.categories.displayValues, + true, + opts.categories.choices, + { + hasMore: opts.categories.hasMore, + initialSearch: "", + loading: opts.categories.loading, + onFetchMore: opts.categories.onFetchMore, + onSearchChange: opts.categories.onSearchChange + } + ), + active: opts.categories.active + }, + { + ...createAutocompleteField( + ProductFilterKeys.collections, + intl.formatMessage(sectionNames.collections), + opts.collections.value, + opts.collections.displayValues, + true, + opts.collections.choices, + { + hasMore: opts.collections.hasMore, + initialSearch: "", + loading: opts.collections.loading, + onFetchMore: opts.collections.onFetchMore, + onSearchChange: opts.collections.onSearchChange + } + ), + active: opts.collections.active } ]; } diff --git a/src/products/queries.ts b/src/products/queries.ts index c8e27af29..513ec26b0 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -1,5 +1,6 @@ import gql from "graphql-tag"; +import makeQuery from "@saleor/hooks/makeQuery"; import { pageInfoFragment, TypedQuery } from "../queries"; import { AvailableInGridAttributes, @@ -22,6 +23,10 @@ import { ProductVariantDetails, ProductVariantDetailsVariables } from "./types/ProductVariantDetails"; +import { + InitialProductFilterData, + InitialProductFilterDataVariables +} from "./types/InitialProductFilterData"; export const fragmentMoney = gql` fragment Money on Money { @@ -209,6 +214,31 @@ export const fragmentVariant = gql` } `; +const initialProductFilterDataQuery = gql` + query InitialProductFilterData($categories: [ID!], $collections: [ID!]) { + categories(first: 20, filter: { ids: $categories }) { + edges { + node { + id + name + } + } + } + collections(first: 20, filter: { ids: $collections }) { + edges { + node { + id + name + } + } + } + } +`; +export const useInitialProductFilterDataQuery = makeQuery< + InitialProductFilterData, + InitialProductFilterDataVariables +>(initialProductFilterDataQuery); + const productListQuery = gql` ${productFragment} query ProductList( diff --git a/src/products/types/InitialProductFilterData.ts b/src/products/types/InitialProductFilterData.ts new file mode 100644 index 000000000..58d6b9e29 --- /dev/null +++ b/src/products/types/InitialProductFilterData.ts @@ -0,0 +1,49 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +// ==================================================== +// GraphQL query operation: InitialProductFilterData +// ==================================================== + +export interface InitialProductFilterData_categories_edges_node { + __typename: "Category"; + id: string; + name: string; +} + +export interface InitialProductFilterData_categories_edges { + __typename: "CategoryCountableEdge"; + node: InitialProductFilterData_categories_edges_node; +} + +export interface InitialProductFilterData_categories { + __typename: "CategoryCountableConnection"; + edges: InitialProductFilterData_categories_edges[]; +} + +export interface InitialProductFilterData_collections_edges_node { + __typename: "Collection"; + id: string; + name: string; +} + +export interface InitialProductFilterData_collections_edges { + __typename: "CollectionCountableEdge"; + node: InitialProductFilterData_collections_edges_node; +} + +export interface InitialProductFilterData_collections { + __typename: "CollectionCountableConnection"; + edges: InitialProductFilterData_collections_edges[]; +} + +export interface InitialProductFilterData { + categories: InitialProductFilterData_categories | null; + collections: InitialProductFilterData_collections | null; +} + +export interface InitialProductFilterDataVariables { + categories?: string[] | null; + collections?: string[] | null; +} diff --git a/src/products/urls.ts b/src/products/urls.ts index 426471fd7..ac5a946a9 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -9,7 +9,8 @@ import { Filters, Pagination, Sort, - TabActionDialog + TabActionDialog, + FiltersWithMultipleValues } from "../types"; const productSection = "/products/"; @@ -30,7 +31,12 @@ export enum ProductListUrlFiltersEnum { stockStatus = "stockStatus", query = "query" } -export type ProductListUrlFilters = Filters; +export enum ProductListUrlFiltersWithMultipleValues { + categories = "categories", + collections = "collections" +} +export type ProductListUrlFilters = Filters & + FiltersWithMultipleValues; export enum ProductListUrlSortField { attribute = "attribute", name = "name", diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 7eb034d10..a84ee4d30 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -10,7 +10,11 @@ import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; import SaveFilterTabDialog, { SaveFilterTabDialogFormData } from "@saleor/components/SaveFilterTabDialog"; -import { defaultListSettings, ProductListColumns } from "@saleor/config"; +import { + defaultListSettings, + ProductListColumns, + DEFAULT_INITIAL_SEARCH_DATA +} from "@saleor/config"; import useBulkActions from "@saleor/hooks/useBulkActions"; import useListSettings from "@saleor/hooks/useListSettings"; import useNavigator from "@saleor/hooks/useNavigator"; @@ -26,6 +30,8 @@ import { ListViews } from "@saleor/types"; import { getSortUrlVariables } from "@saleor/utils/sort"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; +import useCategorySearch from "@saleor/searches/useCategorySearch"; +import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import ProductListPage from "../../components/ProductListPage"; import { TypedProductBulkDeleteMutation, @@ -33,7 +39,8 @@ import { } from "../../mutations"; import { AvailableInGridAttributesQuery, - TypedProductListQuery + TypedProductListQuery, + useInitialProductFilterDataQuery } from "../../queries"; import { productBulkDelete } from "../../types/productBulkDelete"; import { productBulkPublish } from "../../types/productBulkPublish"; @@ -73,6 +80,19 @@ export const ProductList: React.FC = ({ params }) => { ListViews.PRODUCT_LIST ); const intl = useIntl(); + const { data: initialFilterData } = useInitialProductFilterDataQuery({ + skip: !(!!params.categories || !!params.collections), + variables: { + categories: params.categories, + collections: params.collections + } + }); + const searchCategories = useCategorySearch({ + variables: DEFAULT_INITIAL_SEARCH_DATA + }); + const searchCollections = useCollectionSearch({ + variables: DEFAULT_INITIAL_SEARCH_DATA + }); React.useEffect( () => @@ -156,6 +176,24 @@ export const ProductList: React.FC = ({ params }) => { [params, settings.rowNumber] ); + const filterOpts = getFilterOpts( + params, + { + initial: maybe( + () => initialFilterData.categories.edges.map(edge => edge.node), + [] + ), + search: searchCategories + }, + { + initial: maybe( + () => initialFilterData.collections.edges.map(edge => edge.node), + [] + ), + search: searchCollections + } + ); + return ( = ({ params }) => { defaultSettings={ defaultListSettings[ListViews.PRODUCT_LIST] } - filterOpts={getFilterOpts(params)} + filterOpts={filterOpts} gridAttributes={maybe( () => attributes.data.grid.edges.map(edge => edge.node), diff --git a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap index cb6472a86..07f2f3bab 100644 --- a/src/products/views/ProductList/__snapshots__/filters.test.ts.snap +++ b/src/products/views/ProductList/__snapshots__/filters.test.ts.snap @@ -2,6 +2,12 @@ exports[`Filtering URL params should not be empty if active filters are present 1`] = ` Object { + "categories": Array [ + "878752", + ], + "collections": Array [ + "Q29sbGVjdGlvbjoc", + ], "priceFrom": "10", "priceTo": "20", "status": "published", @@ -9,4 +15,4 @@ Object { } `; -exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20"`; +exports[`Filtering URL params should not be empty if active filters are present 2`] = `"status=published&stockStatus=IN_STOCK&priceFrom=10&priceTo=20&categories%5B0%5D=878752&collections%5B0%5D=Q29sbGVjdGlvbjoc"`; diff --git a/src/products/views/ProductList/filters.test.ts b/src/products/views/ProductList/filters.test.ts index 43b4f23ab..300a2ab15 100644 --- a/src/products/views/ProductList/filters.test.ts +++ b/src/products/views/ProductList/filters.test.ts @@ -10,6 +10,9 @@ import { getFilterQueryParams } from "@saleor/utils/filters"; import { getExistingKeys, setFilterOptsStatus } from "@test/filters"; import { config } from "@test/intl"; import { StockAvailability } from "@saleor/types/globalTypes"; +import { categories } from "@saleor/categories/fixtures"; +import { fetchMoreProps, searchPageProps } from "@saleor/fixtures"; +import { collections } from "@saleor/collections/fixtures"; import { getFilterVariables, getFilterQueryParam } from "./filters"; describe("Filtering query params", () => { @@ -37,6 +40,38 @@ describe("Filtering URL params", () => { const intl = createIntl(config); const filters = createFilterStructure(intl, { + categories: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: categories.slice(5).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: categories[5].name, + value: categories[5].id + } + ], + value: [categories[5].id] + }, + collections: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + choices: collections.slice(5).map(category => ({ + label: category.name, + value: category.id + })), + displayValues: [ + { + label: collections[5].name, + value: collections[5].id + } + ], + value: [collections[5].id] + }, price: { active: false, value: { diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index aad3278a4..7860fe536 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -4,6 +4,19 @@ import { ProductListFilterOpts, ProductStatus } from "@saleor/products/components/ProductListPage"; +import { UseSearchResult } from "@saleor/hooks/makeSearch"; +import { + SearchCategories, + SearchCategoriesVariables +} from "@saleor/searches/types/SearchCategories"; +import { + InitialProductFilterData_categories_edges_node, + InitialProductFilterData_collections_edges_node +} from "@saleor/products/types/InitialProductFilterData"; +import { + SearchCollections, + SearchCollectionsVariables +} from "@saleor/searches/types/SearchCollections"; import { IFilterElement } from "../../../components/Filter"; import { ProductFilterInput, @@ -14,20 +27,87 @@ import { createFilterUtils, getGteLteVariables, getMinMaxQueryParam, - getSingleEnumValueQueryParam + getSingleEnumValueQueryParam, + dedupeFilter, + getMultipleValueQueryParam } from "../../../utils/filters"; import { ProductListUrlFilters, ProductListUrlFiltersEnum, - ProductListUrlQueryParams + ProductListUrlQueryParams, + ProductListUrlFiltersWithMultipleValues } from "../../urls"; export const PRODUCT_FILTERS_KEY = "productFilters"; export function getFilterOpts( - params: ProductListUrlFilters + params: ProductListUrlFilters, + categories: { + initial: InitialProductFilterData_categories_edges_node[]; + search: UseSearchResult; + }, + collections: { + initial: InitialProductFilterData_collections_edges_node[]; + search: UseSearchResult; + } ): ProductListFilterOpts { return { + categories: { + active: !!params.categories, + choices: maybe( + () => + categories.search.result.data.search.edges.map(edge => ({ + label: edge.node.name, + value: edge.node.id + })), + [] + ), + displayValues: maybe( + () => + categories.initial.map(category => ({ + label: category.name, + value: category.id + })), + [] + ), + hasMore: maybe( + () => categories.search.result.data.search.pageInfo.hasNextPage, + false + ), + initialSearch: "", + loading: categories.search.result.loading, + onFetchMore: categories.search.loadMore, + onSearchChange: categories.search.search, + value: maybe(() => dedupeFilter(params.categories), []) + }, + collections: { + active: !!params.collections, + choices: maybe( + () => + collections.search.result.data.search.edges.map(edge => ({ + label: edge.node.name, + value: edge.node.id + })), + [] + ), + displayValues: maybe( + () => + collections.initial.map(category => ({ + label: category.name, + value: category.id + })), + [] + ), + hasMore: maybe( + () => collections.search.result.data.search.pageInfo.hasNextPage, + false + ), + initialSearch: "", + loading: collections.search.result.loading, + onFetchMore: collections.search.loadMore, + onSearchChange: collections.search.search, + value: maybe(() => dedupeFilter(params.collections), []) + }, price: { active: maybe( () => @@ -54,6 +134,8 @@ export function getFilterVariables( params: ProductListUrlFilters ): ProductFilterInput { return { + categories: params.categories !== undefined ? params.categories : null, + collections: params.collections !== undefined ? params.collections : null, isPublished: params.status !== undefined ? params.status === ProductStatus.PUBLISHED @@ -76,6 +158,18 @@ export function getFilterQueryParam( const { name } = filter; switch (name) { + case ProductFilterKeys.categories: + return getMultipleValueQueryParam( + filter, + ProductListUrlFiltersWithMultipleValues.categories + ); + + case ProductFilterKeys.collections: + return getMultipleValueQueryParam( + filter, + ProductListUrlFiltersWithMultipleValues.collections + ); + case ProductFilterKeys.price: return getMinMaxQueryParam( filter, diff --git a/src/types.ts b/src/types.ts index dc2011c19..ec663ad21 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import { MutationResult } from "react-apollo"; import { User_permissions } from "./auth/types/User"; import { ConfirmButtonTransitionState } from "./components/ConfirmButton"; import { IFilter } from "./components/Filter"; +import { MultiAutocompleteChoiceType } from "./components/MultiAutocompleteSelectField"; export interface UserError { field: string; @@ -176,3 +177,10 @@ export interface FilterOpts { active: boolean; value: T; } + +export interface AutocompleteFilterOpts + extends FetchMoreProps, + SearchPageProps { + choices: MultiAutocompleteChoiceType[]; + displayValues: MultiAutocompleteChoiceType[]; +} diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index 69dae346b..a46473bbe 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -1,6 +1,6 @@ import { IFilterElement, FieldType } from "@saleor/components/Filter"; import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; -import { MinMax } from "@saleor/types"; +import { MinMax, FetchMoreProps, SearchPageProps } from "@saleor/types"; export function createPriceField( name: T, @@ -65,6 +65,28 @@ export function createOptionsField( }; } +export function createAutocompleteField( + name: T, + label: string, + defaultValue: string[], + displayValues: MultiAutocompleteChoiceType[], + multiple: boolean, + options: MultiAutocompleteChoiceType[], + opts: FetchMoreProps & SearchPageProps +): IFilterElement { + return { + ...opts, + active: false, + displayValues, + label, + multiple, + name, + options, + type: FieldType.autocomplete, + value: defaultValue + }; +} + export function createTextField( name: T, label: string, diff --git a/src/utils/filters/filters.ts b/src/utils/filters/filters.ts index f2e42ae85..c80edc459 100644 --- a/src/utils/filters/filters.ts +++ b/src/utils/filters/filters.ts @@ -1,3 +1,5 @@ +import isArray from "lodash-es/isArray"; + import { IFilterElement, IFilter } from "@saleor/components/Filter"; import { findValueInEnum } from "@saleor/misc"; @@ -25,6 +27,10 @@ function createFilterUtils< } export function dedupeFilter(array: T[]): T[] { + if (!isArray(array)) { + return [array]; + } + return Array.from(new Set(array)); } @@ -110,6 +116,23 @@ export function getMultipleEnumValueQueryParam< }; } +export function getMultipleValueQueryParam< + TKey extends string, + TUrlKey extends string +>(param: IFilterElement, key: TUrlKey) { + const { active, value } = param; + + if (!active) { + return { + [key]: undefined + }; + } + + return { + [key]: value + }; +} + export function getMinMaxQueryParam< TKey extends string, TUrlKey extends string