Add filtering to sale list

This commit is contained in:
dominik-zeglen 2020-01-03 14:21:58 +01:00
parent 890a27ae68
commit f9d0c22531
8 changed files with 311 additions and 20 deletions

View file

@ -3,6 +3,7 @@ import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField";
export enum FieldType {
date,
dateTime,
number,
price,
options,

View file

@ -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<SaleFilterKeys, SaleListFilterOpts>,
SortPage<SaleListUrlSortField>,
TabPageProps {
defaultCurrency: string;
@ -29,10 +34,13 @@ export interface SaleListPageProps
}
const SaleListPage: React.FC<SaleListPageProps> = ({
currencySymbol,
currentTab,
filterOpts,
initialSearch,
onAdd,
onAll,
onFilterChange,
onSearchChange,
onTabChange,
onTabDelete,
@ -42,6 +50,8 @@ const SaleListPage: React.FC<SaleListPageProps> = ({
}) => {
const intl = useIntl();
const structure = createFilterStructure(intl, filterOpts);
return (
<Container>
<PageHeader title={intl.formatMessage(sectionNames.sales)}>
@ -50,18 +60,21 @@ const SaleListPage: React.FC<SaleListPageProps> = ({
</Button>
</PageHeader>
<Card>
<SearchBar
<FilterBar
allTabLabel={intl.formatMessage({
defaultMessage: "All Sales",
description: "tab name"
})}
currencySymbol={currencySymbol}
currentTab={currentTab}
filterStructure={structure}
initialSearch={initialSearch}
searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Sale"
})}
tabs={tabs}
onAll={onAll}
onFilterChange={onFilterChange}
onSearchChange={onSearchChange}
onTabChange={onTabChange}
onTabDelete={onTabDelete}

View file

@ -1,3 +1,15 @@
import { FilterOpts, MinMax } from "@saleor/types";
import {
DiscountStatusEnum,
DiscountValueTypeEnum
} from "@saleor/types/globalTypes";
export interface SaleListFilterOpts {
saleType: FilterOpts<DiscountValueTypeEnum>;
started: FilterOpts<MinMax>;
status: FilterOpts<DiscountStatusEnum[]>;
}
export enum RequirementsPicker {
ORDER = "ORDER",
ITEM = "ITEM",

View file

@ -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<SaleListUrlFiltersEnum>;
export enum SaleListUrlFiltersWithMultipleValues {
status = "status"
}
export type SaleListUrlFilters = Filters<SaleListUrlFiltersEnum> &
FiltersWithMultipleValues<SaleListUrlFiltersWithMultipleValues>;
export type SaleListUrlDialog = "remove" | TabActionDialog;
export enum SaleListUrlSortField {
name = "name",

View file

@ -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<SaleListProps> = ({ params }) => {
: 0
: parseInt(params.activeTab, 0);
const changeFilterField = (filter: SaleListUrlFilters) => {
const changeFilters = (filter: IFilter<SaleFilterKeys>) => {
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<SaleListProps> = ({ params }) => {
};
const handleSort = createSortHandler(navigate, saleListUrl, params);
const currencySymbol = maybe(() => shop.defaultCurrency, "USD");
return (
<TypedSaleBulkDelete onCompleted={handleSaleBulkDelete}>
@ -158,10 +184,13 @@ export const SaleList: React.FC<SaleListProps> = ({ params }) => {
<>
<WindowTitle title={intl.formatMessage(sectionNames.sales)} />
<SaleListPage
currencySymbol={currencySymbol}
currentTab={currentTab}
filterOpts={getFilterOpts(params)}
initialSearch={params.query || ""}
onSearchChange={query => changeFilterField({ query })}
onAll={() => navigate(saleListUrl())}
onSearchChange={handleSearchChange}
onFilterChange={filter => changeFilters(filter)}
onAll={resetFilters}
onTabChange={handleTabChange}
onTabDelete={() => openModal("delete-search")}
onTabSave={() => openModal("save-search")}

View file

@ -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<SaleFilterKeys> {
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<SaleFilterKeys>
): 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,

View file

@ -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;

View file

@ -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,