Refacto filters to handle multiple values
This commit is contained in:
parent
6aadc05322
commit
3917e13613
21 changed files with 581 additions and 144 deletions
|
@ -19,9 +19,9 @@ import { FilterContent } from ".";
|
||||||
import { FilterContentSubmitData } from "./FilterContent";
|
import { FilterContentSubmitData } from "./FilterContent";
|
||||||
import { IFilter } from "./types";
|
import { IFilter } from "./types";
|
||||||
|
|
||||||
export interface FilterProps {
|
export interface FilterProps<TFilterKeys = string> {
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
menu: IFilter;
|
menu: IFilter<TFilterKeys>;
|
||||||
filterLabel: string;
|
filterLabel: string;
|
||||||
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,13 @@ import SingleSelectField from "../SingleSelectField";
|
||||||
import FilterElement from "./FilterElement";
|
import FilterElement from "./FilterElement";
|
||||||
import { IFilter } from "./types";
|
import { IFilter } from "./types";
|
||||||
|
|
||||||
export interface FilterContentSubmitData {
|
export interface FilterContentSubmitData<TKeys = string> {
|
||||||
name: string;
|
name: TKeys;
|
||||||
value: string | string[];
|
value: string | string[];
|
||||||
}
|
}
|
||||||
export interface FilterContentProps {
|
export interface FilterContentProps {
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
filters: IFilter;
|
filters: IFilter<string>;
|
||||||
onSubmit: (data: FilterContentSubmitData) => void;
|
onSubmit: (data: FilterContentSubmitData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ function checkFilterValue(value: string | string[]): boolean {
|
||||||
return value.some(v => !!v);
|
return value.some(v => !!v);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFilterChoices(items: IFilter) {
|
function getFilterChoices(items: IFilter<string>) {
|
||||||
return items.map(filterItem => ({
|
return items.map(filterItem => ({
|
||||||
label: filterItem.label,
|
label: filterItem.label,
|
||||||
value: filterItem.value
|
value: filterItem.value.toString()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
||||||
onSubmit
|
onSubmit
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [menuValue, setMenuValue] = React.useState<string>("");
|
const [menuValue, setMenuValue] = React.useState<string>(null);
|
||||||
const [filterValue, setFilterValue] = React.useState<string | string[]>("");
|
const [filterValue, setFilterValue] = React.useState<string | string[]>("");
|
||||||
const classes = useStyles({});
|
const classes = useStyles({});
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
||||||
}}
|
}}
|
||||||
value={
|
value={
|
||||||
filterItemIndex === menus.length - 1
|
filterItemIndex === menus.length - 1
|
||||||
? menuValue
|
? menuValue.toString()
|
||||||
: menus[filterItemIndex - 1].label.toString()
|
: menus[filterItemIndex - 1].label.toString()
|
||||||
}
|
}
|
||||||
placeholder={intl.formatMessage({
|
placeholder={intl.formatMessage({
|
||||||
|
|
|
@ -10,9 +10,9 @@ import PriceField from "../PriceField";
|
||||||
import SingleSelectField from "../SingleSelectField";
|
import SingleSelectField from "../SingleSelectField";
|
||||||
import { FieldType, IFilterItem } from "./types";
|
import { FieldType, IFilterItem } from "./types";
|
||||||
|
|
||||||
export interface FilterElementProps {
|
export interface FilterElementProps<TFilterKeys = string> {
|
||||||
className?: string;
|
className?: string;
|
||||||
filter: IFilterItem;
|
filter: IFilterItem<TFilterKeys>;
|
||||||
value: string | string[];
|
value: string | string[];
|
||||||
onChange: (value: string | string[]) => void;
|
onChange: (value: string | string[]) => void;
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,10 @@ const useStyles = makeStyles({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export interface FilterElementProps {
|
export interface FilterElementProps<TFilterKeys = string> {
|
||||||
className?: string;
|
className?: string;
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
filter: IFilterItem;
|
filter: IFilterItem<TFilterKeys>;
|
||||||
value: string | string[];
|
value: string | string[];
|
||||||
onChange: (value: string | string[]) => void;
|
onChange: (value: string | string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,6 @@ export interface FilterData {
|
||||||
value?: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IFilterItem = IMenuItem<FilterData>;
|
export type IFilterItem<TKeys> = IMenuItem<FilterData, TKeys>;
|
||||||
|
|
||||||
export type IFilter = IMenu<FilterData>;
|
export type IFilter<TKeys> = IMenu<FilterData, TKeys>;
|
||||||
|
|
|
@ -6,9 +6,9 @@ import Debounce from "../Debounce";
|
||||||
import { IFilter } from "../Filter/types";
|
import { IFilter } from "../Filter/types";
|
||||||
import FilterTabs, { FilterChips, FilterTab } from "../TableFilter";
|
import FilterTabs, { FilterChips, FilterTab } from "../TableFilter";
|
||||||
|
|
||||||
export interface FilterBarProps<TUrlFilters = object>
|
export interface FilterBarProps<TUrlFilters = object, TFilterKeys = any>
|
||||||
extends FilterProps<TUrlFilters> {
|
extends FilterProps<TUrlFilters, TFilterKeys> {
|
||||||
filterMenu: IFilter;
|
filterMenu: IFilter<TFilterKeys>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterBar: React.FC<FilterBarProps> = ({
|
const FilterBar: React.FC<FilterBarProps> = ({
|
||||||
|
|
|
@ -99,16 +99,16 @@ const useStyles = makeStyles(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
interface FilterChipProps {
|
interface FilterChipProps<TFilterKeys = string> {
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
menu: IFilter;
|
menu: IFilter<TFilterKeys>;
|
||||||
filtersList: Filter[];
|
filtersList: Filter[];
|
||||||
filterLabel: string;
|
filterLabel: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
search: string;
|
search: string;
|
||||||
isCustomSearch: boolean;
|
isCustomSearch: boolean;
|
||||||
onSearchChange: (event: React.ChangeEvent<any>) => void;
|
onSearchChange: (event: React.ChangeEvent<any>) => void;
|
||||||
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
|
||||||
onFilterDelete: () => void;
|
onFilterDelete: () => void;
|
||||||
onFilterSave: () => void;
|
onFilterSave: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ export const countries = [
|
||||||
{ code: "AS", label: "American Samoa" }
|
{ code: "AS", label: "American Samoa" }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const filterPageProps: FilterPageProps<{}> = {
|
export const filterPageProps: FilterPageProps<{}, unknown> = {
|
||||||
currencySymbol: "USD",
|
currencySymbol: "USD",
|
||||||
currentTab: 0,
|
currentTab: 0,
|
||||||
filterTabs: [
|
filterTabs: [
|
||||||
|
|
|
@ -10,17 +10,16 @@ import { FilterProps } from "../../../types";
|
||||||
import { OrderStatusFilter } from "../../../types/globalTypes";
|
import { OrderStatusFilter } from "../../../types/globalTypes";
|
||||||
import { OrderListUrlFilters } from "../../urls";
|
import { OrderListUrlFilters } from "../../urls";
|
||||||
|
|
||||||
type OrderListFilterProps = FilterProps<OrderListUrlFilters>;
|
type OrderListFilterProps = FilterProps<OrderListUrlFilters, OrderFilterKeys>;
|
||||||
|
|
||||||
export enum OrderFilterKeys {
|
export enum OrderFilterKeys {
|
||||||
date,
|
date = "date",
|
||||||
dateEqual,
|
dateEqual = "dateEqual",
|
||||||
dateRange,
|
dateRange = "dateRange",
|
||||||
dateLastWeek,
|
dateLastWeek = "dateLastWeek",
|
||||||
dateLastMonth,
|
dateLastMonth = "dateLastMonth",
|
||||||
dateLastYear,
|
dateLastYear = "dateLastYear",
|
||||||
email,
|
fulfillment = "fulfillment"
|
||||||
fulfillment
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
|
@ -28,7 +27,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
const tz = React.useContext(TimezoneContext);
|
const tz = React.useContext(TimezoneContext);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const filterMenu: IFilter = [
|
const filterMenu: IFilter<OrderFilterKeys> = [
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -44,7 +43,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Last 7 Days"
|
defaultMessage: "Last 7 Days"
|
||||||
}),
|
}),
|
||||||
value: OrderFilterKeys.dateLastWeek.toString()
|
value: OrderFilterKeys.dateLastWeek
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -59,7 +58,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Last 30 Days"
|
defaultMessage: "Last 30 Days"
|
||||||
}),
|
}),
|
||||||
value: OrderFilterKeys.dateLastMonth.toString()
|
value: OrderFilterKeys.dateLastMonth
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -74,7 +73,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Last Year"
|
defaultMessage: "Last Year"
|
||||||
}),
|
}),
|
||||||
value: OrderFilterKeys.dateLastYear.toString()
|
value: OrderFilterKeys.dateLastYear
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -88,7 +87,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Specific Date"
|
defaultMessage: "Specific Date"
|
||||||
}),
|
}),
|
||||||
value: OrderFilterKeys.dateEqual.toString()
|
value: OrderFilterKeys.dateEqual
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -101,7 +100,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Range"
|
defaultMessage: "Range"
|
||||||
}),
|
}),
|
||||||
value: OrderFilterKeys.dateRange.toString()
|
value: OrderFilterKeys.dateRange
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: {
|
data: {
|
||||||
|
@ -113,7 +112,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Date"
|
defaultMessage: "Date"
|
||||||
}),
|
}),
|
||||||
value: OrderFilterKeys.date.toString()
|
value: OrderFilterKeys.date
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -155,7 +154,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||||
defaultMessage: "Fulfillment Status",
|
defaultMessage: "Fulfillment Status",
|
||||||
description: "order"
|
description: "order"
|
||||||
}),
|
}),
|
||||||
value: OrderFilterKeys.fulfillment.toString()
|
value: OrderFilterKeys.fulfillment
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import Container from "@saleor/components/Container";
|
import Container from "@saleor/components/Container";
|
||||||
import PageHeader from "@saleor/components/PageHeader";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
import { sectionNames } from "@saleor/intl";
|
import { sectionNames } from "@saleor/intl";
|
||||||
|
import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter";
|
||||||
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
|
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
|
||||||
import { OrderList_orders_edges_node } from "../../types/OrderList";
|
import { OrderList_orders_edges_node } from "../../types/OrderList";
|
||||||
import { OrderListUrlFilters } from "../../urls";
|
import { OrderListUrlFilters } from "../../urls";
|
||||||
|
@ -16,7 +17,7 @@ import OrderListFilter from "../OrderListFilter";
|
||||||
export interface OrderListPageProps
|
export interface OrderListPageProps
|
||||||
extends PageListProps,
|
extends PageListProps,
|
||||||
ListActions,
|
ListActions,
|
||||||
FilterPageProps<OrderListUrlFilters> {
|
FilterPageProps<OrderListUrlFilters, OrderFilterKeys> {
|
||||||
orders: OrderList_orders_edges_node[];
|
orders: OrderList_orders_edges_node[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
109
src/orders/views/OrderList/__snapshots__/filters.test.ts.snap
Normal file
109
src/orders/views/OrderList/__snapshots__/filters.test.ts.snap
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Crate filter chips 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": "Date from 2019-09-01",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "Date to 2019-09-10",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "Fulfilled",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "Partially Fulfilled",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with date 1`] = `
|
||||||
|
Object {
|
||||||
|
"dateFrom": "2019-09-01",
|
||||||
|
"dateTo": "2019-09-01",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with date last month 1`] = `
|
||||||
|
Object {
|
||||||
|
"dateFrom": "2019-09-01",
|
||||||
|
"dateTo": undefined,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with date last week 1`] = `
|
||||||
|
Object {
|
||||||
|
"dateFrom": "2019-09-01",
|
||||||
|
"dateTo": undefined,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with date last year 1`] = `
|
||||||
|
Object {
|
||||||
|
"dateFrom": "2019-09-01",
|
||||||
|
"dateTo": undefined,
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with date range 1`] = `
|
||||||
|
Object {
|
||||||
|
"dateFrom": "2019-09-01",
|
||||||
|
"dateTo": "2019-09-10",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with fulfillment status 1`] = `
|
||||||
|
Object {
|
||||||
|
"status": Array [
|
||||||
|
"PARTIALLY_FULFILLED",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with multiple deduped values 1`] = `
|
||||||
|
Object {
|
||||||
|
"status": Array [
|
||||||
|
"FULFILLED",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with multiple values 1`] = `
|
||||||
|
Object {
|
||||||
|
"status": Array [
|
||||||
|
"FULFILLED",
|
||||||
|
"PARTIALLY_FULFILLED",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Get filter variables from multiple status value 1`] = `
|
||||||
|
Object {
|
||||||
|
"created": Object {
|
||||||
|
"gte": "2019-09-01",
|
||||||
|
"lte": "2019-09-10",
|
||||||
|
},
|
||||||
|
"customer": "email@example.com",
|
||||||
|
"status": Array [
|
||||||
|
"FULFILLED",
|
||||||
|
"PARTIALLY_FULFILLED",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Get filter variables from single status value 1`] = `
|
||||||
|
Object {
|
||||||
|
"created": Object {
|
||||||
|
"gte": "2019-09-01",
|
||||||
|
"lte": "2019-09-10",
|
||||||
|
},
|
||||||
|
"customer": "email@example.com",
|
||||||
|
"status": Array [
|
||||||
|
"FULFILLED",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
155
src/orders/views/OrderList/filters.test.ts
Normal file
155
src/orders/views/OrderList/filters.test.ts
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import { createIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter";
|
||||||
|
import { OrderStatus, OrderStatusFilter } from "@saleor/types/globalTypes";
|
||||||
|
import { createFilter, createFilterChips, getFilterVariables } from "./filters";
|
||||||
|
|
||||||
|
const mockIntl = createIntl({
|
||||||
|
locale: "en"
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Create filter object", () => {
|
||||||
|
it("with date", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.dateEqual,
|
||||||
|
value: "2019-09-01"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with date range", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.dateRange,
|
||||||
|
value: ["2019-09-01", "2019-09-10"]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with date last week", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.dateLastWeek,
|
||||||
|
value: "2019-09-01"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with date last month", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.dateLastMonth,
|
||||||
|
value: "2019-09-01"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with date last year", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.dateLastYear,
|
||||||
|
value: "2019-09-01"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with fulfillment status", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.fulfillment,
|
||||||
|
value: OrderStatusFilter.PARTIALLY_FULFILLED
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with multiple values", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{
|
||||||
|
status: [OrderStatusFilter.FULFILLED]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.fulfillment,
|
||||||
|
value: OrderStatusFilter.PARTIALLY_FULFILLED
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with multiple deduped values", () => {
|
||||||
|
const filter = createFilter(
|
||||||
|
{
|
||||||
|
status: [OrderStatusFilter.FULFILLED]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: OrderFilterKeys.fulfillment,
|
||||||
|
value: OrderStatusFilter.FULFILLED
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Crate filter chips", () => {
|
||||||
|
const chips = createFilterChips(
|
||||||
|
{
|
||||||
|
dateFrom: "2019-09-01",
|
||||||
|
dateTo: "2019-09-10",
|
||||||
|
status: [OrderStatus.FULFILLED, OrderStatus.PARTIALLY_FULFILLED]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formatDate: date => date
|
||||||
|
},
|
||||||
|
jest.fn(),
|
||||||
|
mockIntl as any
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(chips).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Get filter variables", () => {
|
||||||
|
it("from single status value", () => {
|
||||||
|
const filter = getFilterVariables({
|
||||||
|
dateFrom: "2019-09-01",
|
||||||
|
dateTo: "2019-09-10",
|
||||||
|
email: "email@example.com",
|
||||||
|
status: OrderStatus.FULFILLED.toString()
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("from multiple status value", () => {
|
||||||
|
const filter = getFilterVariables({
|
||||||
|
dateFrom: "2019-09-01",
|
||||||
|
dateTo: "2019-09-10",
|
||||||
|
email: "email@example.com",
|
||||||
|
status: [
|
||||||
|
OrderStatus.FULFILLED.toString(),
|
||||||
|
OrderStatus.PARTIALLY_FULFILLED.toString()
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -86,15 +86,15 @@ export function getFilterVariables(
|
||||||
|
|
||||||
export function createFilter(
|
export function createFilter(
|
||||||
filter: OrderListUrlFilters,
|
filter: OrderListUrlFilters,
|
||||||
data: FilterContentSubmitData
|
data: FilterContentSubmitData<OrderFilterKeys>
|
||||||
): OrderListUrlFilters {
|
): OrderListUrlFilters {
|
||||||
const { name: filterName, value } = data;
|
const { name: filterName, value } = data;
|
||||||
if (filterName === OrderFilterKeys.dateEqual.toString()) {
|
if (filterName === OrderFilterKeys.dateEqual) {
|
||||||
return {
|
return {
|
||||||
dateFrom: valueOrFirst(value),
|
dateFrom: valueOrFirst(value),
|
||||||
dateTo: valueOrFirst(value)
|
dateTo: valueOrFirst(value)
|
||||||
};
|
};
|
||||||
} else if (filterName === OrderFilterKeys.dateRange.toString()) {
|
} else if (filterName === OrderFilterKeys.dateRange) {
|
||||||
return {
|
return {
|
||||||
dateFrom: value[0],
|
dateFrom: value[0],
|
||||||
dateTo: value[1]
|
dateTo: value[1]
|
||||||
|
@ -104,15 +104,13 @@ export function createFilter(
|
||||||
OrderFilterKeys.dateLastWeek,
|
OrderFilterKeys.dateLastWeek,
|
||||||
OrderFilterKeys.dateLastMonth,
|
OrderFilterKeys.dateLastMonth,
|
||||||
OrderFilterKeys.dateLastYear
|
OrderFilterKeys.dateLastYear
|
||||||
]
|
].includes(filterName)
|
||||||
.map(value => value.toString())
|
|
||||||
.includes(filterName)
|
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
dateFrom: valueOrFirst(value),
|
dateFrom: valueOrFirst(value),
|
||||||
dateTo: undefined
|
dateTo: undefined
|
||||||
};
|
};
|
||||||
} else if (filterName === OrderFilterKeys.fulfillment.toString()) {
|
} else if (filterName === OrderFilterKeys.fulfillment) {
|
||||||
return {
|
return {
|
||||||
status: dedupeFilter(
|
status: dedupeFilter(
|
||||||
filter.status
|
filter.status
|
||||||
|
|
|
@ -7,21 +7,23 @@ import { FilterProps } from "@saleor/types";
|
||||||
import { StockAvailability } from "@saleor/types/globalTypes";
|
import { StockAvailability } from "@saleor/types/globalTypes";
|
||||||
import { ProductListUrlFilters } from "../../urls";
|
import { ProductListUrlFilters } from "../../urls";
|
||||||
|
|
||||||
type ProductListFilterProps = FilterProps<ProductListUrlFilters>;
|
type ProductListFilterProps = FilterProps<
|
||||||
|
ProductListUrlFilters,
|
||||||
|
ProductFilterKeys
|
||||||
|
>;
|
||||||
|
|
||||||
export enum ProductFilterKeys {
|
export enum ProductFilterKeys {
|
||||||
published,
|
published = "published",
|
||||||
price,
|
price = "price",
|
||||||
priceEqual,
|
priceEqual = "priceEqual",
|
||||||
priceRange,
|
priceRange = "priceRange",
|
||||||
stock,
|
stock = "stock"
|
||||||
query
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const filterMenu: IFilter = [
|
const filterMenu: IFilter<ProductFilterKeys> = [
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
data: {
|
data: {
|
||||||
|
@ -55,7 +57,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
||||||
defaultMessage: "Visibility",
|
defaultMessage: "Visibility",
|
||||||
description: "product visibility"
|
description: "product visibility"
|
||||||
}),
|
}),
|
||||||
value: ProductFilterKeys.published.toString()
|
value: ProductFilterKeys.published
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -85,7 +87,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
||||||
defaultMessage: "Stock",
|
defaultMessage: "Stock",
|
||||||
description: "product stock"
|
description: "product stock"
|
||||||
}),
|
}),
|
||||||
value: ProductFilterKeys.stock.toString()
|
value: ProductFilterKeys.stock
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
|
@ -102,7 +104,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Specific Price"
|
defaultMessage: "Specific Price"
|
||||||
}),
|
}),
|
||||||
value: ProductFilterKeys.priceEqual.toString()
|
value: ProductFilterKeys.priceEqual
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
|
@ -115,7 +117,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Range"
|
defaultMessage: "Range"
|
||||||
}),
|
}),
|
||||||
value: ProductFilterKeys.priceRange.toString()
|
value: ProductFilterKeys.priceRange
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: {
|
data: {
|
||||||
|
@ -127,7 +129,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
defaultMessage: "Price"
|
defaultMessage: "Price"
|
||||||
}),
|
}),
|
||||||
value: ProductFilterKeys.price.toString()
|
value: ProductFilterKeys.price
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,12 @@ import {
|
||||||
} from "@saleor/types";
|
} from "@saleor/types";
|
||||||
import { ProductListUrlFilters } from "../../urls";
|
import { ProductListUrlFilters } from "../../urls";
|
||||||
import ProductList from "../ProductList";
|
import ProductList from "../ProductList";
|
||||||
import ProductListFilter from "../ProductListFilter";
|
import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter";
|
||||||
|
|
||||||
export interface ProductListPageProps
|
export interface ProductListPageProps
|
||||||
extends PageListProps<ProductListColumns>,
|
extends PageListProps<ProductListColumns>,
|
||||||
ListActions,
|
ListActions,
|
||||||
FilterPageProps<ProductListUrlFilters>,
|
FilterPageProps<ProductListUrlFilters, ProductFilterKeys>,
|
||||||
FetchMoreProps {
|
FetchMoreProps {
|
||||||
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Crate filter chips 1`] = `
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": "Price from $10.00",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "Price to $20.00",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "Available",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"label": "Published",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with price 1`] = `
|
||||||
|
Object {
|
||||||
|
"priceFrom": "10",
|
||||||
|
"priceTo": "10",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with price range 1`] = `
|
||||||
|
Object {
|
||||||
|
"priceFrom": Array [
|
||||||
|
"10",
|
||||||
|
"20",
|
||||||
|
],
|
||||||
|
"priceTo": Array [
|
||||||
|
"10",
|
||||||
|
"20",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with publication status 1`] = `
|
||||||
|
Object {
|
||||||
|
"isPublished": "false",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Create filter object with stock status 1`] = `
|
||||||
|
Object {
|
||||||
|
"status": "OUT_OF_STOCK",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Get filter variables 1`] = `
|
||||||
|
Object {
|
||||||
|
"isPublished": true,
|
||||||
|
"price": Object {
|
||||||
|
"gte": 10,
|
||||||
|
"lte": 20,
|
||||||
|
},
|
||||||
|
"search": undefined,
|
||||||
|
"stockAvailability": "IN_STOCK",
|
||||||
|
}
|
||||||
|
`;
|
77
src/products/views/ProductList/filters.test.ts
Normal file
77
src/products/views/ProductList/filters.test.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { createIntl } from "react-intl";
|
||||||
|
|
||||||
|
import { ProductFilterKeys } from "@saleor/products/components/ProductListFilter";
|
||||||
|
import { StockAvailability } from "@saleor/types/globalTypes";
|
||||||
|
import { createFilter, createFilterChips, getFilterVariables } from "./filters";
|
||||||
|
|
||||||
|
const mockIntl = createIntl({
|
||||||
|
locale: "en"
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Create filter object", () => {
|
||||||
|
it("with price", () => {
|
||||||
|
const filter = createFilter({
|
||||||
|
name: ProductFilterKeys.priceEqual,
|
||||||
|
value: "10"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with price range", () => {
|
||||||
|
const filter = createFilter({
|
||||||
|
name: ProductFilterKeys.priceEqual,
|
||||||
|
value: ["10", "20"]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with publication status", () => {
|
||||||
|
const filter = createFilter({
|
||||||
|
name: ProductFilterKeys.published,
|
||||||
|
value: "false"
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("with stock status", () => {
|
||||||
|
const filter = createFilter({
|
||||||
|
name: ProductFilterKeys.stock,
|
||||||
|
value: StockAvailability.OUT_OF_STOCK
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Crate filter chips", () => {
|
||||||
|
const chips = createFilterChips(
|
||||||
|
{
|
||||||
|
isPublished: "true",
|
||||||
|
priceFrom: "10",
|
||||||
|
priceTo: "20",
|
||||||
|
status: StockAvailability.IN_STOCK
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currencySymbol: "USD",
|
||||||
|
locale: "en"
|
||||||
|
},
|
||||||
|
jest.fn(),
|
||||||
|
mockIntl as any
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(chips).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Get filter variables", () => {
|
||||||
|
const filter = getFilterVariables({
|
||||||
|
isPublished: "true",
|
||||||
|
priceFrom: "10",
|
||||||
|
priceTo: "20",
|
||||||
|
status: StockAvailability.IN_STOCK
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filter).toMatchSnapshot();
|
||||||
|
});
|
|
@ -34,26 +34,26 @@ export function getFilterVariables(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFilter(
|
export function createFilter(
|
||||||
filter: FilterContentSubmitData
|
filter: FilterContentSubmitData<ProductFilterKeys>
|
||||||
): ProductListUrlFilters {
|
): ProductListUrlFilters {
|
||||||
const filterName = filter.name;
|
const filterName = filter.name;
|
||||||
if (filterName === ProductFilterKeys.priceEqual.toString()) {
|
if (filterName === ProductFilterKeys.priceEqual) {
|
||||||
const value = filter.value as string;
|
const value = filter.value as string;
|
||||||
return {
|
return {
|
||||||
priceFrom: value,
|
priceFrom: value,
|
||||||
priceTo: value
|
priceTo: value
|
||||||
};
|
};
|
||||||
} else if (filterName === ProductFilterKeys.priceRange.toString()) {
|
} else if (filterName === ProductFilterKeys.priceRange) {
|
||||||
const { value } = filter;
|
const { value } = filter;
|
||||||
return {
|
return {
|
||||||
priceFrom: value[0],
|
priceFrom: value[0],
|
||||||
priceTo: value[1]
|
priceTo: value[1]
|
||||||
};
|
};
|
||||||
} else if (filterName === ProductFilterKeys.published.toString()) {
|
} else if (filterName === ProductFilterKeys.published) {
|
||||||
return {
|
return {
|
||||||
isPublished: filter.value as string
|
isPublished: filter.value as string
|
||||||
};
|
};
|
||||||
} else if (filterName === ProductFilterKeys.stock.toString()) {
|
} else if (filterName === ProductFilterKeys.stock) {
|
||||||
const value = filter.value as string;
|
const value = filter.value as string;
|
||||||
return {
|
return {
|
||||||
status: StockAvailability[value]
|
status: StockAvailability[value]
|
||||||
|
|
|
@ -65,7 +65,7 @@ export interface PageListProps<TColumns extends string = string>
|
||||||
defaultSettings?: ListSettings<TColumns>;
|
defaultSettings?: ListSettings<TColumns>;
|
||||||
onAdd: () => void;
|
onAdd: () => void;
|
||||||
}
|
}
|
||||||
export interface FilterPageProps<TUrlFilters> {
|
export interface FilterPageProps<TUrlFilters, TFilterKeys> {
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
currentTab: number;
|
currentTab: number;
|
||||||
filterTabs: GetFilterTabsOutput<TUrlFilters>;
|
filterTabs: GetFilterTabsOutput<TUrlFilters>;
|
||||||
|
@ -73,12 +73,13 @@ export interface FilterPageProps<TUrlFilters> {
|
||||||
initialSearch: string;
|
initialSearch: string;
|
||||||
onAll: () => void;
|
onAll: () => void;
|
||||||
onSearchChange: (value: string) => void;
|
onSearchChange: (value: string) => void;
|
||||||
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
|
||||||
onFilterDelete: () => void;
|
onFilterDelete: () => void;
|
||||||
onFilterSave: () => void;
|
onFilterSave: () => void;
|
||||||
onTabChange: (tab: number) => void;
|
onTabChange: (tab: number) => void;
|
||||||
}
|
}
|
||||||
export interface FilterProps<TUrlFilters> extends FilterPageProps<TUrlFilters> {
|
export interface FilterProps<TUrlFilters, TFilterKeys>
|
||||||
|
extends FilterPageProps<TUrlFilters, TFilterKeys> {
|
||||||
allTabLabel: string;
|
allTabLabel: string;
|
||||||
filterLabel: string;
|
filterLabel: string;
|
||||||
searchPlaceholder: string;
|
searchPlaceholder: string;
|
||||||
|
|
|
@ -8,7 +8,7 @@ Array [
|
||||||
"label": "1",
|
"label": "1",
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"value": "1",
|
"value": 0,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"data": null,
|
"data": null,
|
||||||
|
@ -16,7 +16,7 @@ Array [
|
||||||
"label": "2",
|
"label": "2",
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"sort": 1,
|
"sort": 1,
|
||||||
"value": "2",
|
"value": 1,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"data": null,
|
"data": null,
|
||||||
|
@ -24,7 +24,7 @@ Array [
|
||||||
"label": "3",
|
"label": "3",
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"sort": 2,
|
"sort": 2,
|
||||||
"value": "3",
|
"value": 2,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"data": null,
|
"data": null,
|
||||||
|
@ -32,7 +32,7 @@ Array [
|
||||||
"label": "4",
|
"label": "4",
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"sort": 3,
|
"sort": 3,
|
||||||
"value": "4",
|
"value": 3,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"data": null,
|
"data": null,
|
||||||
|
@ -40,7 +40,7 @@ Array [
|
||||||
"label": "4.1",
|
"label": "4.1",
|
||||||
"parent": "3",
|
"parent": "3",
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"value": "4.1",
|
"value": 4,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"data": null,
|
"data": null,
|
||||||
|
@ -48,7 +48,7 @@ Array [
|
||||||
"label": "4.2",
|
"label": "4.2",
|
||||||
"parent": "3",
|
"parent": "3",
|
||||||
"sort": 1,
|
"sort": 1,
|
||||||
"value": "4.2",
|
"value": 5,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -61,24 +61,24 @@ Array [
|
||||||
"children": Array [],
|
"children": Array [],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4.1",
|
"label": "4.1",
|
||||||
"value": "4.1",
|
"value": 4,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"children": Array [],
|
"children": Array [],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4.2",
|
"label": "4.2",
|
||||||
"value": "4.2",
|
"value": 5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4",
|
"label": "4",
|
||||||
"value": "4",
|
"value": 3,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"children": Array [],
|
"children": Array [],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4.1",
|
"label": "4.1",
|
||||||
"value": "4.1",
|
"value": 4,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -89,7 +89,7 @@ Array [
|
||||||
"children": Array [],
|
"children": Array [],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4.1",
|
"label": "4.1",
|
||||||
"value": "4.1",
|
"value": 4,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"children": Array [
|
"children": Array [
|
||||||
|
@ -97,18 +97,18 @@ Array [
|
||||||
"children": Array [],
|
"children": Array [],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4.1",
|
"label": "4.1",
|
||||||
"value": "4.1",
|
"value": 4,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"children": Array [],
|
"children": Array [],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4.2",
|
"label": "4.2",
|
||||||
"value": "4.2",
|
"value": 5,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"data": null,
|
"data": null,
|
||||||
"label": "4",
|
"label": "4",
|
||||||
"value": "4",
|
"value": 3,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -9,24 +9,33 @@ import {
|
||||||
walkToRoot
|
walkToRoot
|
||||||
} from "./menu";
|
} from "./menu";
|
||||||
|
|
||||||
const validMenu: IMenu = [
|
enum MenuKey {
|
||||||
|
one,
|
||||||
|
two,
|
||||||
|
three,
|
||||||
|
four,
|
||||||
|
fourOne,
|
||||||
|
foutTwo
|
||||||
|
}
|
||||||
|
|
||||||
|
const validMenu: IMenu<null, MenuKey> = [
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
data: null,
|
data: null,
|
||||||
label: "1",
|
label: "1",
|
||||||
value: "1"
|
value: MenuKey.one
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
data: null,
|
data: null,
|
||||||
label: "2",
|
label: "2",
|
||||||
value: "2"
|
value: MenuKey.two
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
data: null,
|
data: null,
|
||||||
label: "3",
|
label: "3",
|
||||||
value: "3"
|
value: MenuKey.three
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
|
@ -34,18 +43,18 @@ const validMenu: IMenu = [
|
||||||
children: [],
|
children: [],
|
||||||
data: null,
|
data: null,
|
||||||
label: "4.1",
|
label: "4.1",
|
||||||
value: "4.1"
|
value: MenuKey.fourOne
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
children: [],
|
||||||
data: null,
|
data: null,
|
||||||
label: "4.2",
|
label: "4.2",
|
||||||
value: "4.2"
|
value: MenuKey.foutTwo
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: null,
|
data: null,
|
||||||
label: "4",
|
label: "4",
|
||||||
value: "4"
|
value: MenuKey.four
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,62 +1,76 @@
|
||||||
interface IBaseMenuItem<TMenuData = {}> {
|
interface IBaseMenuItem<TMenuData = {}, TValue = string> {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
value?: string;
|
value?: TValue;
|
||||||
data: TMenuData | null;
|
data: TMenuData | null;
|
||||||
}
|
}
|
||||||
export type IFlatMenuItem<TMenuData = {}> = IBaseMenuItem<TMenuData> & {
|
export type IFlatMenuItem<TMenuData = {}, TValue = string> = IBaseMenuItem<
|
||||||
|
TMenuData,
|
||||||
|
TValue
|
||||||
|
> & {
|
||||||
id: string;
|
id: string;
|
||||||
parent: string | null;
|
parent: string | null;
|
||||||
sort: number;
|
sort: number;
|
||||||
};
|
};
|
||||||
export type IMenuItem<TMenuData = {}> = IBaseMenuItem<TMenuData> & {
|
export type IMenuItem<TMenuData = {}, TValue = string> = IBaseMenuItem<
|
||||||
children: Array<IMenuItem<TMenuData>>;
|
TMenuData,
|
||||||
|
TValue
|
||||||
|
> & {
|
||||||
|
children: Array<IMenuItem<TMenuData, TValue>>;
|
||||||
};
|
};
|
||||||
export type IMenu<TMenuData = {}> = Array<IMenuItem<TMenuData>>;
|
export type IMenu<TMenuData = {}, TValue = string> = Array<
|
||||||
export type IFlatMenu<TMenuData = {}> = Array<IFlatMenuItem<TMenuData>>;
|
IMenuItem<TMenuData, TValue>
|
||||||
|
>;
|
||||||
|
export type IFlatMenu<TMenuData = {}, TValue = string> = Array<
|
||||||
|
IFlatMenuItem<TMenuData, TValue>
|
||||||
|
>;
|
||||||
|
|
||||||
export function validateMenuOptions<TMenuData = {}>(
|
export function validateMenuOptions<TMenuData = {}, TValue = string>(
|
||||||
menu: IMenu<TMenuData>
|
menu: IMenu<TMenuData, TValue>
|
||||||
): boolean {
|
): boolean {
|
||||||
const values: string[] = toFlat(menu)
|
const values: TValue[] = toFlat(menu)
|
||||||
.map(menuItem => menuItem.value)
|
.map(menuItem => menuItem.value)
|
||||||
.filter(value => value !== undefined);
|
.filter(value => value !== undefined);
|
||||||
const uniqueValues = Array.from(new Set(values));
|
const uniqueValues = Array.from(new Set(values));
|
||||||
return uniqueValues.length === values.length;
|
return uniqueValues.length === values.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getMenuItemByPath<TMenuData = {}>(
|
function _getMenuItemByPath<TMenuData = {}, TValue = string>(
|
||||||
menuItem: IMenuItem<TMenuData>,
|
menuItem: IMenuItem<TMenuData, TValue>,
|
||||||
path: number[]
|
path: number[]
|
||||||
): IMenuItem<TMenuData> {
|
): IMenuItem<TMenuData, TValue> {
|
||||||
if (path.length === 0) {
|
if (path.length === 0) {
|
||||||
return menuItem;
|
return menuItem;
|
||||||
}
|
}
|
||||||
return _getMenuItemByPath(menuItem.children[path[0]], path.slice(1));
|
return _getMenuItemByPath(menuItem.children[path[0]], path.slice(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMenuItemByPath<TMenuData = {}>(
|
export function getMenuItemByPath<TMenuData = {}, TValue = string>(
|
||||||
menu: IMenu<TMenuData>,
|
menu: IMenu<TMenuData, TValue>,
|
||||||
path: number[]
|
path: number[]
|
||||||
): IMenuItem<TMenuData> {
|
): IMenuItem<TMenuData, TValue> {
|
||||||
return _getMenuItemByPath(menu[path[0]], path.slice(1));
|
return _getMenuItemByPath(menu[path[0]], path.slice(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMenuItemByValue<TMenuData = {}>(
|
export function getMenuItemByValue<TMenuData = {}, TValue = string>(
|
||||||
menu: IMenu<TMenuData>,
|
menu: IMenu<TMenuData, TValue>,
|
||||||
value: string
|
value: TValue
|
||||||
): IMenuItem<TMenuData> {
|
): IMenuItem<TMenuData, TValue> {
|
||||||
const flatMenu = toFlat(menu);
|
const flatMenu = toFlat(menu);
|
||||||
const flatMenuItem: IFlatMenuItem<TMenuData> = flatMenu.find(
|
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = flatMenu.find(
|
||||||
menuItem => menuItem.value === value
|
menuItem => menuItem.value === value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (flatMenuItem === undefined) {
|
||||||
|
throw new Error(`Value ${value} does not exist in menu`);
|
||||||
|
}
|
||||||
|
|
||||||
return _fromFlat(flatMenu, flatMenuItem);
|
return _fromFlat(flatMenu, flatMenuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _walkToMenuItem<TMenuData = {}>(
|
function _walkToMenuItem<TMenuData = {}, TValue = string>(
|
||||||
menuItem: IMenuItem<TMenuData>,
|
menuItem: IMenuItem<TMenuData, TValue>,
|
||||||
path: number[]
|
path: number[]
|
||||||
): IMenu<TMenuData> {
|
): IMenu<TMenuData, TValue> {
|
||||||
const node = menuItem.children[path[0]];
|
const node = menuItem.children[path[0]];
|
||||||
|
|
||||||
if (path.length === 1) {
|
if (path.length === 1) {
|
||||||
|
@ -66,18 +80,18 @@ function _walkToMenuItem<TMenuData = {}>(
|
||||||
return [node, ..._walkToMenuItem(node, path.slice(1))];
|
return [node, ..._walkToMenuItem(node, path.slice(1))];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function walkToMenuItem<TMenuData = {}>(
|
export function walkToMenuItem<TMenuData = {}, TValue = string>(
|
||||||
menu: IMenu<TMenuData>,
|
menu: IMenu<TMenuData, TValue>,
|
||||||
path: number[]
|
path: number[]
|
||||||
): IMenu<TMenuData> {
|
): IMenu<TMenuData, TValue> {
|
||||||
const walkByNode = menu[path[0]];
|
const walkByNode = menu[path[0]];
|
||||||
return [walkByNode, ..._walkToMenuItem(walkByNode, path.slice(1))];
|
return [walkByNode, ..._walkToMenuItem(walkByNode, path.slice(1))];
|
||||||
}
|
}
|
||||||
|
|
||||||
function _walkToRoot<TMenuData = {}>(
|
function _walkToRoot<TMenuData = {}, TValue = string>(
|
||||||
flatMenu: IFlatMenu<TMenuData>,
|
flatMenu: IFlatMenu<TMenuData, TValue>,
|
||||||
parent: string
|
parent: string
|
||||||
): IFlatMenu<TMenuData> {
|
): IFlatMenu<TMenuData, TValue> {
|
||||||
const menuItem = flatMenu.find(menuItem => menuItem.id === parent);
|
const menuItem = flatMenu.find(menuItem => menuItem.id === parent);
|
||||||
|
|
||||||
if (menuItem.parent === null) {
|
if (menuItem.parent === null) {
|
||||||
|
@ -86,10 +100,10 @@ function _walkToRoot<TMenuData = {}>(
|
||||||
|
|
||||||
return [menuItem, ..._walkToRoot(flatMenu, menuItem.parent)];
|
return [menuItem, ..._walkToRoot(flatMenu, menuItem.parent)];
|
||||||
}
|
}
|
||||||
export function walkToRoot<TMenuData = {}>(
|
export function walkToRoot<TMenuData = {}, TValue = string>(
|
||||||
menu: IMenu<TMenuData>,
|
menu: IMenu<TMenuData, TValue>,
|
||||||
value: string
|
value: TValue
|
||||||
): IMenu<TMenuData> {
|
): IMenu<TMenuData, TValue> {
|
||||||
const flatMenu = toFlat(menu);
|
const flatMenu = toFlat(menu);
|
||||||
const menuItem = flatMenu.find(menuItem => menuItem.value === value);
|
const menuItem = flatMenu.find(menuItem => menuItem.value === value);
|
||||||
|
|
||||||
|
@ -99,13 +113,13 @@ export function walkToRoot<TMenuData = {}>(
|
||||||
).map(flatMenuItem => _fromFlat(flatMenu, flatMenuItem));
|
).map(flatMenuItem => _fromFlat(flatMenu, flatMenuItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
function _toFlat<TMenuData = {}>(
|
function _toFlat<TMenuData = {}, TValue = string>(
|
||||||
menuItem: IMenuItem<TMenuData>,
|
menuItem: IMenuItem<TMenuData, TValue>,
|
||||||
sort: number,
|
sort: number,
|
||||||
parent: string
|
parent: string
|
||||||
): IFlatMenu<TMenuData> {
|
): IFlatMenu<TMenuData, TValue> {
|
||||||
const id = parent ? [parent, sort].join(":") : sort.toString();
|
const id = parent ? [parent, sort].join(":") : sort.toString();
|
||||||
const flatMenuItem: IFlatMenuItem<TMenuData> = {
|
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = {
|
||||||
data: menuItem.data,
|
data: menuItem.data,
|
||||||
id,
|
id,
|
||||||
label: menuItem.label,
|
label: menuItem.label,
|
||||||
|
@ -117,22 +131,28 @@ function _toFlat<TMenuData = {}>(
|
||||||
flatMenuItem,
|
flatMenuItem,
|
||||||
...menuItem.children
|
...menuItem.children
|
||||||
.map((child, childIndex) => _toFlat(child, childIndex, id))
|
.map((child, childIndex) => _toFlat(child, childIndex, id))
|
||||||
.reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu<TMenuData>)
|
.reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu<
|
||||||
|
TMenuData,
|
||||||
|
TValue
|
||||||
|
>)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
export function toFlat<TMenuData = {}>(
|
export function toFlat<TMenuData = {}, TValue = string>(
|
||||||
menu: IMenu<TMenuData>
|
menu: IMenu<TMenuData, TValue>
|
||||||
): IFlatMenu<TMenuData> {
|
): IFlatMenu<TMenuData, TValue> {
|
||||||
return menu
|
return menu
|
||||||
.map((menuItem, menuItemIndex) => _toFlat(menuItem, menuItemIndex, null))
|
.map((menuItem, menuItemIndex) => _toFlat(menuItem, menuItemIndex, null))
|
||||||
.reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu<TMenuData>);
|
.reduce((acc, curr) => [...acc, ...curr], [] as IFlatMenu<
|
||||||
|
TMenuData,
|
||||||
|
TValue
|
||||||
|
>);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _fromFlat<TMenuData = {}>(
|
function _fromFlat<TMenuData = {}, TValue = string>(
|
||||||
menu: IFlatMenu<TMenuData>,
|
menu: IFlatMenu<TMenuData, TValue>,
|
||||||
flatMenuItem: IFlatMenuItem<TMenuData>
|
flatMenuItem: IFlatMenuItem<TMenuData, TValue>
|
||||||
): IMenuItem<TMenuData> {
|
): IMenuItem<TMenuData, TValue> {
|
||||||
const children: Array<IMenuItem<TMenuData>> = menu
|
const children: Array<IMenuItem<TMenuData, TValue>> = menu
|
||||||
.filter(menuItem => menuItem.parent === flatMenuItem.id)
|
.filter(menuItem => menuItem.parent === flatMenuItem.id)
|
||||||
.map(menuItem => _fromFlat(menu, menuItem));
|
.map(menuItem => _fromFlat(menu, menuItem));
|
||||||
|
|
||||||
|
@ -143,16 +163,16 @@ function _fromFlat<TMenuData = {}>(
|
||||||
value: flatMenuItem.value
|
value: flatMenuItem.value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function fromFlat<TMenuData = {}>(
|
export function fromFlat<TMenuData = {}, TValue = string>(
|
||||||
menu: IFlatMenu<TMenuData>
|
menu: IFlatMenu<TMenuData, TValue>
|
||||||
): IMenu<TMenuData> {
|
): IMenu<TMenuData, TValue> {
|
||||||
return menu
|
return menu
|
||||||
.filter(menuItem => menuItem.parent === null)
|
.filter(menuItem => menuItem.parent === null)
|
||||||
.map(menuItem => _fromFlat(menu, menuItem));
|
.map(menuItem => _fromFlat(menu, menuItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLeaf<TMenuData = {}>(
|
export function isLeaf<TMenuData = {}, TValue = string>(
|
||||||
menuItem: IMenuItem<TMenuData>
|
menuItem: IMenuItem<TMenuData, TValue>
|
||||||
): boolean {
|
): boolean {
|
||||||
return menuItem.children.length === 0;
|
return menuItem.children.length === 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue