diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index 1f8b4d7ea..3ed9beb05 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -3,6 +3,7 @@ import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField"; export enum FieldType { date, + dateTime, number, price, options, diff --git a/src/discounts/components/SaleListPage/SaleListPage.tsx b/src/discounts/components/SaleListPage/SaleListPage.tsx index f7f5754fd..e539cfbdf 100644 --- a/src/discounts/components/SaleListPage/SaleListPage.tsx +++ b/src/discounts/components/SaleListPage/SaleListPage.tsx @@ -5,23 +5,28 @@ import { FormattedMessage, useIntl } from "react-intl"; import Container from "@saleor/components/Container"; import PageHeader from "@saleor/components/PageHeader"; -import SearchBar from "@saleor/components/SearchBar"; +import FilterBar from "@saleor/components/FilterBar"; import { sectionNames } from "@saleor/intl"; import { ListActions, PageListProps, - SearchPageProps, TabPageProps, - SortPage + SortPage, + FilterPageProps } from "@saleor/types"; import { SaleListUrlSortField } from "@saleor/discounts/urls"; +import { + SaleFilterKeys, + createFilterStructure +} from "@saleor/discounts/views/SaleList/filter"; +import { SaleListFilterOpts } from "@saleor/discounts/types"; import { SaleList_sales_edges_node } from "../../types/SaleList"; import SaleList from "../SaleList"; export interface SaleListPageProps extends PageListProps, ListActions, - SearchPageProps, + FilterPageProps, SortPage, TabPageProps { defaultCurrency: string; @@ -29,10 +34,13 @@ export interface SaleListPageProps } const SaleListPage: React.FC = ({ + currencySymbol, currentTab, + filterOpts, initialSearch, onAdd, onAll, + onFilterChange, onSearchChange, onTabChange, onTabDelete, @@ -42,6 +50,8 @@ const SaleListPage: React.FC = ({ }) => { const intl = useIntl(); + const structure = createFilterStructure(intl, filterOpts); + return ( @@ -50,18 +60,21 @@ const SaleListPage: React.FC = ({ - ; + started: FilterOpts; + status: FilterOpts; +} + export enum RequirementsPicker { ORDER = "ORDER", ITEM = "ITEM", diff --git a/src/discounts/urls.ts b/src/discounts/urls.ts index efdd37f9c..7fe025069 100644 --- a/src/discounts/urls.ts +++ b/src/discounts/urls.ts @@ -8,7 +8,8 @@ import { Filters, Pagination, TabActionDialog, - Sort + Sort, + FiltersWithMultipleValues } from "../types"; import { SaleDetailsPageTab } from "./components/SaleDetailsPage"; import { VoucherDetailsPageTab } from "./components/VoucherDetailsPage"; @@ -18,9 +19,16 @@ export const discountSection = "/discounts/"; export const saleSection = urlJoin(discountSection, "sales"); export const saleListPath = saleSection; export enum SaleListUrlFiltersEnum { + type = "type", + startedFrom = "startedFrom", + startedTo = "startedTo", query = "query" } -export type SaleListUrlFilters = Filters; +export enum SaleListUrlFiltersWithMultipleValues { + status = "status" +} +export type SaleListUrlFilters = Filters & + FiltersWithMultipleValues; export type SaleListUrlDialog = "remove" | TabActionDialog; export enum SaleListUrlSortField { name = "name", diff --git a/src/discounts/views/SaleList/SaleList.tsx b/src/discounts/views/SaleList/SaleList.tsx index b068377ec..16d6d33b8 100644 --- a/src/discounts/views/SaleList/SaleList.tsx +++ b/src/discounts/views/SaleList/SaleList.tsx @@ -24,13 +24,14 @@ import { ListViews } from "@saleor/types"; import { getSortParams } from "@saleor/utils/sort"; import createSortHandler from "@saleor/utils/handlers/sortHandler"; import createDialogActionHandlers from "@saleor/utils/handlers/dialogActionHandlers"; +import { IFilter } from "@saleor/components/Filter"; +import { getFilterQueryParams } from "@saleor/utils/filters"; import SaleListPage from "../../components/SaleListPage"; import { TypedSaleBulkDelete } from "../../mutations"; import { useSaleListQuery } from "../../queries"; import { SaleBulkDelete } from "../../types/SaleBulkDelete"; import { saleAddUrl, - SaleListUrlFilters, SaleListUrlQueryParams, saleUrl, saleListUrl, @@ -42,7 +43,10 @@ import { getActiveFilters, getFilterTabs, getFilterVariables, - saveFilterTab + saveFilterTab, + SaleFilterKeys, + getFilterQueryParam, + getFilterOpts } from "./filter"; import { getSortQueryVariables } from "./sort"; @@ -86,17 +90,38 @@ export const SaleList: React.FC = ({ params }) => { : 0 : parseInt(params.activeTab, 0); - const changeFilterField = (filter: SaleListUrlFilters) => { + const changeFilters = (filter: IFilter) => { reset(); navigate( saleListUrl({ - ...getActiveFilters(params), - ...filter, + ...params, + ...getFilterQueryParams(filter, getFilterQueryParam), activeTab: undefined }) ); }; + const resetFilters = () => { + reset(); + navigate( + saleListUrl({ + asc: params.asc, + sort: params.sort + }) + ); + }; + + const handleSearchChange = (query: string) => { + reset(); + navigate( + saleListUrl({ + ...params, + activeTab: undefined, + query + }) + ); + }; + const [openModal, closeModal] = createDialogActionHandlers< SaleListUrlDialog, SaleListUrlQueryParams @@ -143,6 +168,7 @@ export const SaleList: React.FC = ({ params }) => { }; const handleSort = createSortHandler(navigate, saleListUrl, params); + const currencySymbol = maybe(() => shop.defaultCurrency, "USD"); return ( @@ -158,10 +184,13 @@ export const SaleList: React.FC = ({ params }) => { <> changeFilterField({ query })} - onAll={() => navigate(saleListUrl())} + onSearchChange={handleSearchChange} + onFilterChange={filter => changeFilters(filter)} + onAll={resetFilters} onTabChange={handleTabChange} onTabDelete={() => openModal("delete-search")} onTabSave={() => openModal("save-search")} diff --git a/src/discounts/views/SaleList/filter.ts b/src/discounts/views/SaleList/filter.ts index 69362c3d3..c7bae066b 100644 --- a/src/discounts/views/SaleList/filter.ts +++ b/src/discounts/views/SaleList/filter.ts @@ -1,24 +1,194 @@ -import { SaleFilterInput } from "@saleor/types/globalTypes"; +import { IntlShape } from "react-intl"; + +import { + SaleFilterInput, + DiscountStatusEnum, + DiscountValueTypeEnum +} from "@saleor/types/globalTypes"; +import { maybe, findValueInEnum, joinDateTime } from "@saleor/misc"; +import { SaleListFilterOpts } from "@saleor/discounts/types"; +import { IFilter, IFilterElement } from "@saleor/components/Filter"; +import { + createDateField, + createOptionsField +} from "@saleor/utils/filters/fields"; import { createFilterTabUtils, - createFilterUtils + createFilterUtils, + dedupeFilter } from "../../../utils/filters"; import { SaleListUrlFilters, SaleListUrlFiltersEnum, SaleListUrlQueryParams } from "../../urls"; +import messages from "./messages"; export const SALE_FILTERS_KEY = "saleFilters"; +export enum SaleFilterKeys { + saleType = "saleType", + started = "started", + status = "status" +} + +export function getFilterOpts(params: SaleListUrlFilters): SaleListFilterOpts { + return { + saleType: { + active: !!maybe(() => params.type), + value: maybe(() => findValueInEnum(params.type, DiscountValueTypeEnum)) + }, + started: { + active: maybe( + () => + [params.startedFrom, params.startedTo].some( + field => field !== undefined + ), + false + ), + value: { + max: maybe(() => params.startedTo, ""), + min: maybe(() => params.startedFrom, "") + } + }, + status: { + active: !!maybe(() => params.status), + value: maybe( + () => + dedupeFilter( + params.status.map(status => + findValueInEnum(status, DiscountStatusEnum) + ) + ), + [] + ) + } + }; +} + +export function createFilterStructure( + intl: IntlShape, + opts: SaleListFilterOpts +): IFilter { + return [ + { + ...createDateField( + SaleFilterKeys.started, + intl.formatMessage(messages.started), + opts.started.value + ), + active: opts.started.active + }, + { + ...createOptionsField( + SaleFilterKeys.status, + intl.formatMessage(messages.status), + opts.status.value, + true, + [ + { + label: intl.formatMessage(messages.active), + value: DiscountStatusEnum.ACTIVE + }, + { + label: intl.formatMessage(messages.expired), + value: DiscountStatusEnum.EXPIRED + }, + { + label: intl.formatMessage(messages.scheduled), + value: DiscountStatusEnum.SCHEDULED + } + ] + ), + active: opts.status.active + }, + { + ...createOptionsField( + SaleFilterKeys.saleType, + intl.formatMessage(messages.type), + [opts.saleType.value], + false, + [ + { + label: intl.formatMessage(messages.fixed), + value: DiscountValueTypeEnum.FIXED + }, + { + label: intl.formatMessage(messages.percentage), + value: DiscountValueTypeEnum.PERCENTAGE + } + ] + ), + active: opts.saleType.active + } + ]; +} + export function getFilterVariables( params: SaleListUrlFilters ): SaleFilterInput { return { - search: params.query + saleType: + params.type && findValueInEnum(params.type, DiscountValueTypeEnum), + search: params.query, + started: { + gte: joinDateTime(params.startedFrom), + lte: joinDateTime(params.startedTo) + }, + status: + params.status && + params.status.map(status => findValueInEnum(status, DiscountStatusEnum)) }; } +export function getFilterQueryParam( + filter: IFilterElement +): SaleListUrlFilters { + const { active, multiple, name, value } = filter; + + switch (name) { + case SaleFilterKeys.saleType: + if (!active) { + return { + type: undefined + }; + } + + return { + type: findValueInEnum(value[0], DiscountValueTypeEnum) + }; + + case SaleFilterKeys.started: + if (!active) { + return { + startedFrom: undefined, + startedTo: undefined + }; + } + if (multiple) { + return { + startedFrom: value[0], + startedTo: value[1] + }; + } + + return { + startedFrom: value[0], + startedTo: value[0] + }; + + case SaleFilterKeys.status: + if (!active) { + return { + status: undefined + }; + } + return { + status: value.map(val => findValueInEnum(val, DiscountStatusEnum)) + }; + } +} + export const { deleteFilterTab, getFilterTabs, diff --git a/src/discounts/views/SaleList/messages.ts b/src/discounts/views/SaleList/messages.ts new file mode 100644 index 000000000..70cb66cc9 --- /dev/null +++ b/src/discounts/views/SaleList/messages.ts @@ -0,0 +1,37 @@ +import { defineMessages } from "react-intl"; + +const messages = defineMessages({ + active: { + defaultMessage: "Active", + description: "sale status" + }, + expired: { + defaultMessage: "Expired", + description: "sale status" + }, + fixed: { + defaultMessage: "Fixed amount", + description: "discount type" + }, + percentage: { + defaultMessage: "Percentage", + description: "discount type" + }, + scheduled: { + defaultMessage: "Scheduled", + description: "sale status" + }, + started: { + defaultMessage: "Started", + description: "sale start date" + }, + status: { + defaultMessage: "Status", + description: "sale status" + }, + type: { + defaultMessage: "Discount Type" + } +}); + +export default messages; diff --git a/src/storybook/stories/discounts/SaleListPage.tsx b/src/storybook/stories/discounts/SaleListPage.tsx index 07eb24509..7dfd58feb 100644 --- a/src/storybook/stories/discounts/SaleListPage.tsx +++ b/src/storybook/stories/discounts/SaleListPage.tsx @@ -2,6 +2,10 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { SaleListUrlSortField } from "@saleor/discounts/urls"; +import { + DiscountValueTypeEnum, + DiscountStatusEnum +} from "@saleor/types/globalTypes"; import SaleListPage, { SaleListPageProps } from "../../../discounts/components/SaleListPage"; @@ -9,19 +13,36 @@ import { saleList } from "../../../discounts/fixtures"; import { listActionsProps, pageListProps, - searchPageProps, tabPageProps, - sortPageProps + sortPageProps, + filterPageProps } from "../../../fixtures"; import Decorator from "../../Decorator"; const props: SaleListPageProps = { ...listActionsProps, ...pageListProps.default, - ...searchPageProps, + ...filterPageProps, ...sortPageProps, ...tabPageProps, defaultCurrency: "USD", + filterOpts: { + saleType: { + active: false, + value: DiscountValueTypeEnum.FIXED + }, + started: { + active: false, + value: { + max: undefined, + min: undefined + } + }, + status: { + active: false, + value: [DiscountStatusEnum.ACTIVE] + } + }, sales: saleList, sort: { ...sortPageProps.sort,