diff --git a/src/misc.ts b/src/misc.ts index 0d5971e8d..5672298d9 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -351,14 +351,14 @@ export function findInEnum( export function findValueInEnum( needle: string, haystack: TEnum -) { +): TEnum[keyof TEnum] { const match = Object.entries(haystack).find(([_, value]) => value === needle); - if (!!match) { - return match[1] as TEnum; + if (!match) { + throw new Error(`Value ${needle} not found in enum`); } - throw new Error(`Value ${needle} not found in enum`); + return (needle as unknown) as TEnum[keyof TEnum]; } export function parseBoolean(a: string, defaultValue: boolean): boolean { diff --git a/src/orders/components/OrderListPage/OrderListPage.tsx b/src/orders/components/OrderListPage/OrderListPage.tsx index 151f2ad41..f4c23b968 100644 --- a/src/orders/components/OrderListPage/OrderListPage.tsx +++ b/src/orders/components/OrderListPage/OrderListPage.tsx @@ -21,11 +21,12 @@ import { import FilterBar from "@saleor/components/FilterBar"; import { OrderList_orders_edges_node } from "../../types/OrderList"; import OrderList from "../OrderList"; +import { OrderListFilterOpts } from "../../types"; export interface OrderListPageProps extends PageListProps, ListActions, - FilterPageProps, + FilterPageProps, SortPage { orders: OrderList_orders_edges_node[]; } @@ -34,6 +35,7 @@ const OrderListPage: React.FC = ({ currencySymbol, currentTab, initialSearch, + filterOpts, tabs, onAdd, onAll, @@ -46,7 +48,7 @@ const OrderListPage: React.FC = ({ }) => { const intl = useIntl(); - const filterStructure = createFilterStructure(intl); + const filterStructure = createFilterStructure(intl, filterOpts); return ( diff --git a/src/orders/types.ts b/src/orders/types.ts new file mode 100644 index 000000000..fd4de956e --- /dev/null +++ b/src/orders/types.ts @@ -0,0 +1,7 @@ +import { FilterOpts, MinMax } from "@saleor/types"; +import { OrderStatusFilter } from "@saleor/types/globalTypes"; + +export interface OrderListFilterOpts { + created: FilterOpts; + status: FilterOpts; +} diff --git a/src/orders/views/OrderList/OrderList.tsx b/src/orders/views/OrderList/OrderList.tsx index 88ea50b18..aef862f0f 100644 --- a/src/orders/views/OrderList/OrderList.tsx +++ b/src/orders/views/OrderList/OrderList.tsx @@ -41,6 +41,7 @@ import { deleteFilterTab, getActiveFilters, getFilterTabs, + getFilterOpts, getFilterVariables, saveFilterTab, OrderFilterKeys, @@ -200,6 +201,7 @@ export const OrderList: React.FC = ({ params }) => { settings={settings} currentTab={currentTab} disabled={loading} + filterOpts={getFilterOpts(params)} orders={maybe(() => data.orders.edges.map(edge => edge.node))} pageInfo={pageInfo} sort={getSortParams(params)} diff --git a/src/orders/views/OrderList/filters.ts b/src/orders/views/OrderList/filters.ts index 614d9432e..d78f55f40 100644 --- a/src/orders/views/OrderList/filters.ts +++ b/src/orders/views/OrderList/filters.ts @@ -1,6 +1,11 @@ import { IntlShape } from "react-intl"; -import { findInEnum, maybe, orderStatusMessages } from "@saleor/misc"; +import { + findInEnum, + maybe, + orderStatusMessages, + findValueInEnum +} from "@saleor/misc"; import { createDateField, createOptionsField @@ -22,6 +27,7 @@ import { OrderListUrlFiltersWithMultipleValuesEnum, OrderListUrlQueryParams } from "../../urls"; +import { OrderListFilterOpts } from "../../types"; import messages from "./messages"; export const ORDER_FILTERS_KEY = "orderFilters"; @@ -31,39 +37,56 @@ export enum OrderFilterKeys { status = "status" } -export function createFilterStructure( - intl: IntlShape, +export function getFilterOpts( params: OrderListUrlFilters -): IFilter { - return [ - { - ...createDateField( - OrderFilterKeys.created, - intl.formatMessage(messages.placed), - { - max: maybe(() => params.createdTo, ""), - min: maybe(() => params.createdFrom, "") - } - ), +): OrderListFilterOpts { + return { + created: { active: maybe( () => [params.createdFrom, params.createdTo].some( field => field !== undefined ), false + ), + value: { + max: maybe(() => params.createdTo, ""), + min: maybe(() => params.createdFrom, "") + } + }, + status: { + active: maybe(() => params.status !== undefined, false), + value: maybe( + () => + dedupeFilter( + params.status.map(status => + findValueInEnum(status, OrderStatusFilter) + ) + ), + [] ) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: OrderListFilterOpts +): IFilter { + return [ + { + ...createDateField( + OrderFilterKeys.created, + intl.formatMessage(messages.placed), + opts.created.value + ), + active: opts.created.active }, { ...createOptionsField( OrderFilterKeys.status, intl.formatMessage(messages.status), - maybe( - () => - dedupeFilter( - params.status.map(status => findInEnum(status, OrderStatusFilter)) - ), - [] - ), + opts.status.value, true, [ { @@ -84,7 +107,7 @@ export function createFilterStructure( } ] ), - active: maybe(() => params.status !== undefined, false) + active: opts.status.active } ]; } @@ -108,14 +131,21 @@ export function getFilterVariables( export function getFilterQueryParam( filter: IFilterElement ): OrderListUrlFilters { - const { active, name, value } = filter; + const { active, multiple, name, value } = filter; if (active) { switch (name) { case OrderFilterKeys.created: + if (multiple) { + return { + createdFrom: value[0], + createdTo: value[1] + }; + } + return { createdFrom: value[0], - createdTo: value[1] + createdTo: value[0] }; case OrderFilterKeys.status: diff --git a/src/products/components/ProductListPage/ProductListPage.tsx b/src/products/components/ProductListPage/ProductListPage.tsx index eb50c0704..074952648 100644 --- a/src/products/components/ProductListPage/ProductListPage.tsx +++ b/src/products/components/ProductListPage/ProductListPage.tsx @@ -24,17 +24,18 @@ import { SortPage } from "@saleor/types"; import FilterBar from "@saleor/components/FilterBar"; +import { ProductListFilterOpts } from "@saleor/products/types"; +import { ProductListUrlSortField } from "../../urls"; import { createFilterStructure, ProductFilterKeys -} from "@saleor/products/views/ProductList/filters"; -import { ProductListUrlSortField } from "../../urls"; +} from "../../views/ProductList/filters"; import ProductList from "../ProductList"; export interface ProductListPageProps extends PageListProps, ListActions, - FilterPageProps, + FilterPageProps, FetchMoreProps, SortPage { activeAttributeSortId: string; @@ -61,6 +62,7 @@ export const ProductListPage: React.FC = props => { defaultSettings, gridAttributes, availableInGridAttributes, + filterOpts, hasMore, initialSearch, loading, @@ -84,7 +86,7 @@ export const ProductListPage: React.FC = props => { const handleSave = (columns: ProductListColumns[]) => onUpdateListSettings("columns", columns); - const filterStructure = createFilterStructure(intl); + const filterStructure = createFilterStructure(intl, filterOpts); const columns: ColumnPickerChoice[] = [ { diff --git a/src/products/types.ts b/src/products/types.ts new file mode 100644 index 000000000..f6dfd9396 --- /dev/null +++ b/src/products/types.ts @@ -0,0 +1,13 @@ +import { FilterOpts, MinMax } from "@saleor/types"; +import { StockAvailability } from "@saleor/types/globalTypes"; + +export enum ProductStatus { + PUBLISHED = "published", + HIDDEN = "hidden" +} + +export interface ProductListFilterOpts { + price: FilterOpts; + status: FilterOpts; + stockStatus: FilterOpts; +} diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 1a0aafb88..45b2586c2 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -54,7 +54,8 @@ import { getFilterVariables, saveFilterTab, ProductFilterKeys, - createFilterQueryParams + createFilterQueryParams, + getFilterOpts } from "./filters"; import { getSortQueryVariables } from "./sort"; @@ -234,6 +235,7 @@ export const ProductList: React.FC = ({ params }) => { defaultSettings={ defaultListSettings[ListViews.PRODUCT_LIST] } + filterOpts={getFilterOpts(params)} gridAttributes={maybe( () => attributes.data.grid.edges.map(edge => edge.node), diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 21cc8ff50..5d2d0b21e 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -1,10 +1,11 @@ import { IntlShape } from "react-intl"; -import { findInEnum, maybe } from "@saleor/misc"; +import { maybe, findValueInEnum } from "@saleor/misc"; import { createOptionsField, createPriceField } from "@saleor/utils/filters/fields"; +import { ProductStatus, ProductListFilterOpts } from "@saleor/products/types"; import { IFilterElement, IFilter } from "../../../components/Filter"; import { ProductFilterInput, @@ -29,21 +30,42 @@ export enum ProductFilterKeys { stock = "stock" } -export enum ProductStatus { - PUBLISHED = "published", - HIDDEN = "hidden" +export function getFilterOpts( + params: ProductListUrlFilters +): ProductListFilterOpts { + return { + price: { + active: maybe( + () => + [params.priceFrom, params.priceTo].some(field => field !== undefined), + false + ), + value: { + max: maybe(() => params.priceTo, "0"), + min: maybe(() => params.priceFrom, "0") + } + }, + status: { + active: maybe(() => params.status !== undefined, false), + value: maybe(() => findValueInEnum(params.status, ProductStatus)) + }, + stockStatus: { + active: maybe(() => params.stockStatus !== undefined, false), + value: maybe(() => findValueInEnum(params.stockStatus, StockAvailability)) + } + }; } export function createFilterStructure( intl: IntlShape, - params: ProductListUrlFilters + opts: ProductListFilterOpts ): IFilter { return [ { ...createOptionsField( ProductFilterKeys.status, intl.formatMessage(messages.visibility), - [ProductStatus.PUBLISHED], + [opts.status.value], false, [ { @@ -56,13 +78,13 @@ export function createFilterStructure( } ] ), - active: maybe(() => params.status !== undefined, false) + active: opts.status.active }, { ...createOptionsField( ProductFilterKeys.stock, intl.formatMessage(messages.quantity), - [StockAvailability.IN_STOCK], + [opts.stockStatus.value], false, [ { @@ -75,22 +97,15 @@ export function createFilterStructure( } ] ), - active: maybe(() => params.stockStatus !== undefined, false) + active: opts.stockStatus.active }, { ...createPriceField( ProductFilterKeys.price, intl.formatMessage(messages.price), - { - max: maybe(() => params.priceTo, "0"), - min: maybe(() => params.priceFrom, "0") - } + opts.price.value ), - active: maybe( - () => - [params.priceFrom, params.priceTo].some(field => field !== undefined), - false - ) + active: opts.price.active } ]; } @@ -108,33 +123,41 @@ export function getFilterVariables( lte: parseFloat(params.priceTo) }, search: params.query, - stockAvailability: StockAvailability[params.stockStatus] + stockAvailability: + params.stockStatus !== undefined + ? findValueInEnum(params.stockStatus, StockAvailability) + : null }; } export function getFilterQueryParam( filter: IFilterElement ): ProductListUrlFilters { - const { active, name, value } = filter; + const { active, multiple, name, value } = filter; if (active) { switch (name) { case ProductFilterKeys.price: + if (multiple) { + return { + priceFrom: value[0], + priceTo: value[1] + }; + } + return { priceFrom: value[0], - priceTo: value[1] + priceTo: value[0] }; case ProductFilterKeys.status: return { - status: ( - findInEnum(value[0], ProductStatus) === ProductStatus.PUBLISHED - ).toString() + status: findValueInEnum(value[0], ProductStatus) }; case ProductFilterKeys.stock: return { - status: findInEnum(value[0], StockAvailability) + stockStatus: findValueInEnum(value[0], StockAvailability) }; } } diff --git a/src/types.ts b/src/types.ts index d7204757c..977cd1bf2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -82,20 +82,16 @@ export interface SearchPageProps { initialSearch: string; onSearchChange: (value: string) => void; } -export interface FilterPageProps - extends SearchPageProps, +export interface FilterPageProps + extends FilterProps, + SearchPageProps, TabPageProps { - currencySymbol: string; - onFilterChange: (filter: IFilter) => void; + filterOpts: TOpts; } -export interface SearchProps { - searchPlaceholder: string; -} -export interface FilterProps - extends FilterPageProps, - SearchProps { +export interface FilterProps { currencySymbol: string; + onFilterChange: (filter: IFilter) => void; } export interface TabPageProps { @@ -170,3 +166,10 @@ export interface UserPermissionProps { export interface MutationResultAdditionalProps { status: ConfirmButtonTransitionState; } + +export type MinMax = Record<"min" | "max", string>; + +export interface FilterOpts { + active: boolean; + value: T; +} diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index a56dfeb8b..3d5d4cd91 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -1,7 +1,6 @@ import { IFilterElement, FieldType } from "@saleor/components/Filter"; import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; - -type MinMax = Record<"min" | "max", string>; +import { MinMax } from "@saleor/types"; export function createPriceField( name: T, @@ -26,7 +25,7 @@ export function createDateField( return { active: false, label, - multiple: true, + multiple: defaultValue.min !== defaultValue.max, name, type: FieldType.date, value: [defaultValue.min, defaultValue.max]