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 { IFilter } from "./types";
|
||||
|
||||
export interface FilterProps {
|
||||
export interface FilterProps<TFilterKeys = string> {
|
||||
currencySymbol: string;
|
||||
menu: IFilter;
|
||||
menu: IFilter<TFilterKeys>;
|
||||
filterLabel: string;
|
||||
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ import SingleSelectField from "../SingleSelectField";
|
|||
import FilterElement from "./FilterElement";
|
||||
import { IFilter } from "./types";
|
||||
|
||||
export interface FilterContentSubmitData {
|
||||
name: string;
|
||||
export interface FilterContentSubmitData<TKeys = string> {
|
||||
name: TKeys;
|
||||
value: string | string[];
|
||||
}
|
||||
export interface FilterContentProps {
|
||||
currencySymbol: string;
|
||||
filters: IFilter;
|
||||
filters: IFilter<string>;
|
||||
onSubmit: (data: FilterContentSubmitData) => void;
|
||||
}
|
||||
|
||||
|
@ -27,10 +27,10 @@ function checkFilterValue(value: string | string[]): boolean {
|
|||
return value.some(v => !!v);
|
||||
}
|
||||
|
||||
function getFilterChoices(items: IFilter) {
|
||||
function getFilterChoices(items: IFilter<string>) {
|
||||
return items.map(filterItem => ({
|
||||
label: filterItem.label,
|
||||
value: filterItem.value
|
||||
value: filterItem.value.toString()
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const [menuValue, setMenuValue] = React.useState<string>("");
|
||||
const [menuValue, setMenuValue] = React.useState<string>(null);
|
||||
const [filterValue, setFilterValue] = React.useState<string | string[]>("");
|
||||
const classes = useStyles({});
|
||||
|
||||
|
@ -95,7 +95,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
|
|||
}}
|
||||
value={
|
||||
filterItemIndex === menus.length - 1
|
||||
? menuValue
|
||||
? menuValue.toString()
|
||||
: menus[filterItemIndex - 1].label.toString()
|
||||
}
|
||||
placeholder={intl.formatMessage({
|
||||
|
|
|
@ -10,9 +10,9 @@ import PriceField from "../PriceField";
|
|||
import SingleSelectField from "../SingleSelectField";
|
||||
import { FieldType, IFilterItem } from "./types";
|
||||
|
||||
export interface FilterElementProps {
|
||||
export interface FilterElementProps<TFilterKeys = string> {
|
||||
className?: string;
|
||||
filter: IFilterItem;
|
||||
filter: IFilterItem<TFilterKeys>;
|
||||
value: string | string[];
|
||||
onChange: (value: string | string[]) => void;
|
||||
}
|
||||
|
@ -26,10 +26,10 @@ const useStyles = makeStyles({
|
|||
}
|
||||
});
|
||||
|
||||
export interface FilterElementProps {
|
||||
export interface FilterElementProps<TFilterKeys = string> {
|
||||
className?: string;
|
||||
currencySymbol: string;
|
||||
filter: IFilterItem;
|
||||
filter: IFilterItem<TFilterKeys>;
|
||||
value: string | string[];
|
||||
onChange: (value: string | string[]) => void;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ export interface FilterData {
|
|||
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 FilterTabs, { FilterChips, FilterTab } from "../TableFilter";
|
||||
|
||||
export interface FilterBarProps<TUrlFilters = object>
|
||||
extends FilterProps<TUrlFilters> {
|
||||
filterMenu: IFilter;
|
||||
export interface FilterBarProps<TUrlFilters = object, TFilterKeys = any>
|
||||
extends FilterProps<TUrlFilters, TFilterKeys> {
|
||||
filterMenu: IFilter<TFilterKeys>;
|
||||
}
|
||||
|
||||
const FilterBar: React.FC<FilterBarProps> = ({
|
||||
|
|
|
@ -99,16 +99,16 @@ const useStyles = makeStyles(
|
|||
}
|
||||
);
|
||||
|
||||
interface FilterChipProps {
|
||||
interface FilterChipProps<TFilterKeys = string> {
|
||||
currencySymbol: string;
|
||||
menu: IFilter;
|
||||
menu: IFilter<TFilterKeys>;
|
||||
filtersList: Filter[];
|
||||
filterLabel: string;
|
||||
placeholder: string;
|
||||
search: string;
|
||||
isCustomSearch: boolean;
|
||||
onSearchChange: (event: React.ChangeEvent<any>) => void;
|
||||
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
||||
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
|
||||
onFilterDelete: () => void;
|
||||
onFilterSave: () => void;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ export const countries = [
|
|||
{ code: "AS", label: "American Samoa" }
|
||||
];
|
||||
|
||||
export const filterPageProps: FilterPageProps<{}> = {
|
||||
export const filterPageProps: FilterPageProps<{}, unknown> = {
|
||||
currencySymbol: "USD",
|
||||
currentTab: 0,
|
||||
filterTabs: [
|
||||
|
|
|
@ -10,17 +10,16 @@ import { FilterProps } from "../../../types";
|
|||
import { OrderStatusFilter } from "../../../types/globalTypes";
|
||||
import { OrderListUrlFilters } from "../../urls";
|
||||
|
||||
type OrderListFilterProps = FilterProps<OrderListUrlFilters>;
|
||||
type OrderListFilterProps = FilterProps<OrderListUrlFilters, OrderFilterKeys>;
|
||||
|
||||
export enum OrderFilterKeys {
|
||||
date,
|
||||
dateEqual,
|
||||
dateRange,
|
||||
dateLastWeek,
|
||||
dateLastMonth,
|
||||
dateLastYear,
|
||||
email,
|
||||
fulfillment
|
||||
date = "date",
|
||||
dateEqual = "dateEqual",
|
||||
dateRange = "dateRange",
|
||||
dateLastWeek = "dateLastWeek",
|
||||
dateLastMonth = "dateLastMonth",
|
||||
dateLastYear = "dateLastYear",
|
||||
fulfillment = "fulfillment"
|
||||
}
|
||||
|
||||
const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
||||
|
@ -28,7 +27,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
const tz = React.useContext(TimezoneContext);
|
||||
const intl = useIntl();
|
||||
|
||||
const filterMenu: IFilter = [
|
||||
const filterMenu: IFilter<OrderFilterKeys> = [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
|
@ -44,7 +43,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Last 7 Days"
|
||||
}),
|
||||
value: OrderFilterKeys.dateLastWeek.toString()
|
||||
value: OrderFilterKeys.dateLastWeek
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
|
@ -59,7 +58,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Last 30 Days"
|
||||
}),
|
||||
value: OrderFilterKeys.dateLastMonth.toString()
|
||||
value: OrderFilterKeys.dateLastMonth
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
|
@ -74,7 +73,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Last Year"
|
||||
}),
|
||||
value: OrderFilterKeys.dateLastYear.toString()
|
||||
value: OrderFilterKeys.dateLastYear
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
|
@ -88,7 +87,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Specific Date"
|
||||
}),
|
||||
value: OrderFilterKeys.dateEqual.toString()
|
||||
value: OrderFilterKeys.dateEqual
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
|
@ -101,7 +100,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Range"
|
||||
}),
|
||||
value: OrderFilterKeys.dateRange.toString()
|
||||
value: OrderFilterKeys.dateRange
|
||||
}
|
||||
],
|
||||
data: {
|
||||
|
@ -113,7 +112,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Date"
|
||||
}),
|
||||
value: OrderFilterKeys.date.toString()
|
||||
value: OrderFilterKeys.date
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
|
@ -155,7 +154,7 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
|
|||
defaultMessage: "Fulfillment Status",
|
||||
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 PageHeader from "@saleor/components/PageHeader";
|
||||
import { sectionNames } from "@saleor/intl";
|
||||
import { OrderFilterKeys } from "@saleor/orders/components/OrderListFilter";
|
||||
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
|
||||
import { OrderList_orders_edges_node } from "../../types/OrderList";
|
||||
import { OrderListUrlFilters } from "../../urls";
|
||||
|
@ -16,7 +17,7 @@ import OrderListFilter from "../OrderListFilter";
|
|||
export interface OrderListPageProps
|
||||
extends PageListProps,
|
||||
ListActions,
|
||||
FilterPageProps<OrderListUrlFilters> {
|
||||
FilterPageProps<OrderListUrlFilters, OrderFilterKeys> {
|
||||
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(
|
||||
filter: OrderListUrlFilters,
|
||||
data: FilterContentSubmitData
|
||||
data: FilterContentSubmitData<OrderFilterKeys>
|
||||
): OrderListUrlFilters {
|
||||
const { name: filterName, value } = data;
|
||||
if (filterName === OrderFilterKeys.dateEqual.toString()) {
|
||||
if (filterName === OrderFilterKeys.dateEqual) {
|
||||
return {
|
||||
dateFrom: valueOrFirst(value),
|
||||
dateTo: valueOrFirst(value)
|
||||
};
|
||||
} else if (filterName === OrderFilterKeys.dateRange.toString()) {
|
||||
} else if (filterName === OrderFilterKeys.dateRange) {
|
||||
return {
|
||||
dateFrom: value[0],
|
||||
dateTo: value[1]
|
||||
|
@ -104,15 +104,13 @@ export function createFilter(
|
|||
OrderFilterKeys.dateLastWeek,
|
||||
OrderFilterKeys.dateLastMonth,
|
||||
OrderFilterKeys.dateLastYear
|
||||
]
|
||||
.map(value => value.toString())
|
||||
.includes(filterName)
|
||||
].includes(filterName)
|
||||
) {
|
||||
return {
|
||||
dateFrom: valueOrFirst(value),
|
||||
dateTo: undefined
|
||||
};
|
||||
} else if (filterName === OrderFilterKeys.fulfillment.toString()) {
|
||||
} else if (filterName === OrderFilterKeys.fulfillment) {
|
||||
return {
|
||||
status: dedupeFilter(
|
||||
filter.status
|
||||
|
|
|
@ -7,21 +7,23 @@ import { FilterProps } from "@saleor/types";
|
|||
import { StockAvailability } from "@saleor/types/globalTypes";
|
||||
import { ProductListUrlFilters } from "../../urls";
|
||||
|
||||
type ProductListFilterProps = FilterProps<ProductListUrlFilters>;
|
||||
type ProductListFilterProps = FilterProps<
|
||||
ProductListUrlFilters,
|
||||
ProductFilterKeys
|
||||
>;
|
||||
|
||||
export enum ProductFilterKeys {
|
||||
published,
|
||||
price,
|
||||
priceEqual,
|
||||
priceRange,
|
||||
stock,
|
||||
query
|
||||
published = "published",
|
||||
price = "price",
|
||||
priceEqual = "priceEqual",
|
||||
priceRange = "priceRange",
|
||||
stock = "stock"
|
||||
}
|
||||
|
||||
const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
||||
const intl = useIntl();
|
||||
|
||||
const filterMenu: IFilter = [
|
||||
const filterMenu: IFilter<ProductFilterKeys> = [
|
||||
{
|
||||
children: [],
|
||||
data: {
|
||||
|
@ -55,7 +57,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
|||
defaultMessage: "Visibility",
|
||||
description: "product visibility"
|
||||
}),
|
||||
value: ProductFilterKeys.published.toString()
|
||||
value: ProductFilterKeys.published
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
|
@ -85,7 +87,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
|||
defaultMessage: "Stock",
|
||||
description: "product stock"
|
||||
}),
|
||||
value: ProductFilterKeys.stock.toString()
|
||||
value: ProductFilterKeys.stock
|
||||
},
|
||||
{
|
||||
children: [
|
||||
|
@ -102,7 +104,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Specific Price"
|
||||
}),
|
||||
value: ProductFilterKeys.priceEqual.toString()
|
||||
value: ProductFilterKeys.priceEqual
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
|
@ -115,7 +117,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Range"
|
||||
}),
|
||||
value: ProductFilterKeys.priceRange.toString()
|
||||
value: ProductFilterKeys.priceRange
|
||||
}
|
||||
],
|
||||
data: {
|
||||
|
@ -127,7 +129,7 @@ const ProductListFilter: React.FC<ProductListFilterProps> = props => {
|
|||
label: intl.formatMessage({
|
||||
defaultMessage: "Price"
|
||||
}),
|
||||
value: ProductFilterKeys.price.toString()
|
||||
value: ProductFilterKeys.price
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -26,12 +26,12 @@ import {
|
|||
} from "@saleor/types";
|
||||
import { ProductListUrlFilters } from "../../urls";
|
||||
import ProductList from "../ProductList";
|
||||
import ProductListFilter from "../ProductListFilter";
|
||||
import ProductListFilter, { ProductFilterKeys } from "../ProductListFilter";
|
||||
|
||||
export interface ProductListPageProps
|
||||
extends PageListProps<ProductListColumns>,
|
||||
ListActions,
|
||||
FilterPageProps<ProductListUrlFilters>,
|
||||
FilterPageProps<ProductListUrlFilters, ProductFilterKeys>,
|
||||
FetchMoreProps {
|
||||
availableInGridAttributes: AvailableInGridAttributes_availableInGrid_edges_node[];
|
||||
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(
|
||||
filter: FilterContentSubmitData
|
||||
filter: FilterContentSubmitData<ProductFilterKeys>
|
||||
): ProductListUrlFilters {
|
||||
const filterName = filter.name;
|
||||
if (filterName === ProductFilterKeys.priceEqual.toString()) {
|
||||
if (filterName === ProductFilterKeys.priceEqual) {
|
||||
const value = filter.value as string;
|
||||
return {
|
||||
priceFrom: value,
|
||||
priceTo: value
|
||||
};
|
||||
} else if (filterName === ProductFilterKeys.priceRange.toString()) {
|
||||
} else if (filterName === ProductFilterKeys.priceRange) {
|
||||
const { value } = filter;
|
||||
return {
|
||||
priceFrom: value[0],
|
||||
priceTo: value[1]
|
||||
};
|
||||
} else if (filterName === ProductFilterKeys.published.toString()) {
|
||||
} else if (filterName === ProductFilterKeys.published) {
|
||||
return {
|
||||
isPublished: filter.value as string
|
||||
};
|
||||
} else if (filterName === ProductFilterKeys.stock.toString()) {
|
||||
} else if (filterName === ProductFilterKeys.stock) {
|
||||
const value = filter.value as string;
|
||||
return {
|
||||
status: StockAvailability[value]
|
||||
|
|
|
@ -65,7 +65,7 @@ export interface PageListProps<TColumns extends string = string>
|
|||
defaultSettings?: ListSettings<TColumns>;
|
||||
onAdd: () => void;
|
||||
}
|
||||
export interface FilterPageProps<TUrlFilters> {
|
||||
export interface FilterPageProps<TUrlFilters, TFilterKeys> {
|
||||
currencySymbol: string;
|
||||
currentTab: number;
|
||||
filterTabs: GetFilterTabsOutput<TUrlFilters>;
|
||||
|
@ -73,12 +73,13 @@ export interface FilterPageProps<TUrlFilters> {
|
|||
initialSearch: string;
|
||||
onAll: () => void;
|
||||
onSearchChange: (value: string) => void;
|
||||
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
||||
onFilterAdd: (filter: FilterContentSubmitData<TFilterKeys>) => void;
|
||||
onFilterDelete: () => void;
|
||||
onFilterSave: () => void;
|
||||
onTabChange: (tab: number) => void;
|
||||
}
|
||||
export interface FilterProps<TUrlFilters> extends FilterPageProps<TUrlFilters> {
|
||||
export interface FilterProps<TUrlFilters, TFilterKeys>
|
||||
extends FilterPageProps<TUrlFilters, TFilterKeys> {
|
||||
allTabLabel: string;
|
||||
filterLabel: string;
|
||||
searchPlaceholder: string;
|
||||
|
|
|
@ -8,7 +8,7 @@ Array [
|
|||
"label": "1",
|
||||
"parent": null,
|
||||
"sort": 0,
|
||||
"value": "1",
|
||||
"value": 0,
|
||||
},
|
||||
Object {
|
||||
"data": null,
|
||||
|
@ -16,7 +16,7 @@ Array [
|
|||
"label": "2",
|
||||
"parent": null,
|
||||
"sort": 1,
|
||||
"value": "2",
|
||||
"value": 1,
|
||||
},
|
||||
Object {
|
||||
"data": null,
|
||||
|
@ -24,7 +24,7 @@ Array [
|
|||
"label": "3",
|
||||
"parent": null,
|
||||
"sort": 2,
|
||||
"value": "3",
|
||||
"value": 2,
|
||||
},
|
||||
Object {
|
||||
"data": null,
|
||||
|
@ -32,7 +32,7 @@ Array [
|
|||
"label": "4",
|
||||
"parent": null,
|
||||
"sort": 3,
|
||||
"value": "4",
|
||||
"value": 3,
|
||||
},
|
||||
Object {
|
||||
"data": null,
|
||||
|
@ -40,7 +40,7 @@ Array [
|
|||
"label": "4.1",
|
||||
"parent": "3",
|
||||
"sort": 0,
|
||||
"value": "4.1",
|
||||
"value": 4,
|
||||
},
|
||||
Object {
|
||||
"data": null,
|
||||
|
@ -48,7 +48,7 @@ Array [
|
|||
"label": "4.2",
|
||||
"parent": "3",
|
||||
"sort": 1,
|
||||
"value": "4.2",
|
||||
"value": 5,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
@ -61,24 +61,24 @@ Array [
|
|||
"children": Array [],
|
||||
"data": null,
|
||||
"label": "4.1",
|
||||
"value": "4.1",
|
||||
"value": 4,
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"data": null,
|
||||
"label": "4.2",
|
||||
"value": "4.2",
|
||||
"value": 5,
|
||||
},
|
||||
],
|
||||
"data": null,
|
||||
"label": "4",
|
||||
"value": "4",
|
||||
"value": 3,
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"data": null,
|
||||
"label": "4.1",
|
||||
"value": "4.1",
|
||||
"value": 4,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
@ -89,7 +89,7 @@ Array [
|
|||
"children": Array [],
|
||||
"data": null,
|
||||
"label": "4.1",
|
||||
"value": "4.1",
|
||||
"value": 4,
|
||||
},
|
||||
Object {
|
||||
"children": Array [
|
||||
|
@ -97,18 +97,18 @@ Array [
|
|||
"children": Array [],
|
||||
"data": null,
|
||||
"label": "4.1",
|
||||
"value": "4.1",
|
||||
"value": 4,
|
||||
},
|
||||
Object {
|
||||
"children": Array [],
|
||||
"data": null,
|
||||
"label": "4.2",
|
||||
"value": "4.2",
|
||||
"value": 5,
|
||||
},
|
||||
],
|
||||
"data": null,
|
||||
"label": "4",
|
||||
"value": "4",
|
||||
"value": 3,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
|
@ -9,24 +9,33 @@ import {
|
|||
walkToRoot
|
||||
} from "./menu";
|
||||
|
||||
const validMenu: IMenu = [
|
||||
enum MenuKey {
|
||||
one,
|
||||
two,
|
||||
three,
|
||||
four,
|
||||
fourOne,
|
||||
foutTwo
|
||||
}
|
||||
|
||||
const validMenu: IMenu<null, MenuKey> = [
|
||||
{
|
||||
children: [],
|
||||
data: null,
|
||||
label: "1",
|
||||
value: "1"
|
||||
value: MenuKey.one
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
data: null,
|
||||
label: "2",
|
||||
value: "2"
|
||||
value: MenuKey.two
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
data: null,
|
||||
label: "3",
|
||||
value: "3"
|
||||
value: MenuKey.three
|
||||
},
|
||||
{
|
||||
children: [
|
||||
|
@ -34,18 +43,18 @@ const validMenu: IMenu = [
|
|||
children: [],
|
||||
data: null,
|
||||
label: "4.1",
|
||||
value: "4.1"
|
||||
value: MenuKey.fourOne
|
||||
},
|
||||
{
|
||||
children: [],
|
||||
data: null,
|
||||
label: "4.2",
|
||||
value: "4.2"
|
||||
value: MenuKey.foutTwo
|
||||
}
|
||||
],
|
||||
data: null,
|
||||
label: "4",
|
||||
value: "4"
|
||||
value: MenuKey.four
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1,62 +1,76 @@
|
|||
interface IBaseMenuItem<TMenuData = {}> {
|
||||
interface IBaseMenuItem<TMenuData = {}, TValue = string> {
|
||||
label: React.ReactNode;
|
||||
value?: string;
|
||||
value?: TValue;
|
||||
data: TMenuData | null;
|
||||
}
|
||||
export type IFlatMenuItem<TMenuData = {}> = IBaseMenuItem<TMenuData> & {
|
||||
export type IFlatMenuItem<TMenuData = {}, TValue = string> = IBaseMenuItem<
|
||||
TMenuData,
|
||||
TValue
|
||||
> & {
|
||||
id: string;
|
||||
parent: string | null;
|
||||
sort: number;
|
||||
};
|
||||
export type IMenuItem<TMenuData = {}> = IBaseMenuItem<TMenuData> & {
|
||||
children: Array<IMenuItem<TMenuData>>;
|
||||
export type IMenuItem<TMenuData = {}, TValue = string> = IBaseMenuItem<
|
||||
TMenuData,
|
||||
TValue
|
||||
> & {
|
||||
children: Array<IMenuItem<TMenuData, TValue>>;
|
||||
};
|
||||
export type IMenu<TMenuData = {}> = Array<IMenuItem<TMenuData>>;
|
||||
export type IFlatMenu<TMenuData = {}> = Array<IFlatMenuItem<TMenuData>>;
|
||||
export type IMenu<TMenuData = {}, TValue = string> = Array<
|
||||
IMenuItem<TMenuData, TValue>
|
||||
>;
|
||||
export type IFlatMenu<TMenuData = {}, TValue = string> = Array<
|
||||
IFlatMenuItem<TMenuData, TValue>
|
||||
>;
|
||||
|
||||
export function validateMenuOptions<TMenuData = {}>(
|
||||
menu: IMenu<TMenuData>
|
||||
export function validateMenuOptions<TMenuData = {}, TValue = string>(
|
||||
menu: IMenu<TMenuData, TValue>
|
||||
): boolean {
|
||||
const values: string[] = toFlat(menu)
|
||||
const values: TValue[] = toFlat(menu)
|
||||
.map(menuItem => menuItem.value)
|
||||
.filter(value => value !== undefined);
|
||||
const uniqueValues = Array.from(new Set(values));
|
||||
return uniqueValues.length === values.length;
|
||||
}
|
||||
|
||||
function _getMenuItemByPath<TMenuData = {}>(
|
||||
menuItem: IMenuItem<TMenuData>,
|
||||
function _getMenuItemByPath<TMenuData = {}, TValue = string>(
|
||||
menuItem: IMenuItem<TMenuData, TValue>,
|
||||
path: number[]
|
||||
): IMenuItem<TMenuData> {
|
||||
): IMenuItem<TMenuData, TValue> {
|
||||
if (path.length === 0) {
|
||||
return menuItem;
|
||||
}
|
||||
return _getMenuItemByPath(menuItem.children[path[0]], path.slice(1));
|
||||
}
|
||||
|
||||
export function getMenuItemByPath<TMenuData = {}>(
|
||||
menu: IMenu<TMenuData>,
|
||||
export function getMenuItemByPath<TMenuData = {}, TValue = string>(
|
||||
menu: IMenu<TMenuData, TValue>,
|
||||
path: number[]
|
||||
): IMenuItem<TMenuData> {
|
||||
): IMenuItem<TMenuData, TValue> {
|
||||
return _getMenuItemByPath(menu[path[0]], path.slice(1));
|
||||
}
|
||||
|
||||
export function getMenuItemByValue<TMenuData = {}>(
|
||||
menu: IMenu<TMenuData>,
|
||||
value: string
|
||||
): IMenuItem<TMenuData> {
|
||||
export function getMenuItemByValue<TMenuData = {}, TValue = string>(
|
||||
menu: IMenu<TMenuData, TValue>,
|
||||
value: TValue
|
||||
): IMenuItem<TMenuData, TValue> {
|
||||
const flatMenu = toFlat(menu);
|
||||
const flatMenuItem: IFlatMenuItem<TMenuData> = flatMenu.find(
|
||||
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = flatMenu.find(
|
||||
menuItem => menuItem.value === value
|
||||
);
|
||||
|
||||
if (flatMenuItem === undefined) {
|
||||
throw new Error(`Value ${value} does not exist in menu`);
|
||||
}
|
||||
|
||||
return _fromFlat(flatMenu, flatMenuItem);
|
||||
}
|
||||
|
||||
function _walkToMenuItem<TMenuData = {}>(
|
||||
menuItem: IMenuItem<TMenuData>,
|
||||
function _walkToMenuItem<TMenuData = {}, TValue = string>(
|
||||
menuItem: IMenuItem<TMenuData, TValue>,
|
||||
path: number[]
|
||||
): IMenu<TMenuData> {
|
||||
): IMenu<TMenuData, TValue> {
|
||||
const node = menuItem.children[path[0]];
|
||||
|
||||
if (path.length === 1) {
|
||||
|
@ -66,18 +80,18 @@ function _walkToMenuItem<TMenuData = {}>(
|
|||
return [node, ..._walkToMenuItem(node, path.slice(1))];
|
||||
}
|
||||
|
||||
export function walkToMenuItem<TMenuData = {}>(
|
||||
menu: IMenu<TMenuData>,
|
||||
export function walkToMenuItem<TMenuData = {}, TValue = string>(
|
||||
menu: IMenu<TMenuData, TValue>,
|
||||
path: number[]
|
||||
): IMenu<TMenuData> {
|
||||
): IMenu<TMenuData, TValue> {
|
||||
const walkByNode = menu[path[0]];
|
||||
return [walkByNode, ..._walkToMenuItem(walkByNode, path.slice(1))];
|
||||
}
|
||||
|
||||
function _walkToRoot<TMenuData = {}>(
|
||||
flatMenu: IFlatMenu<TMenuData>,
|
||||
function _walkToRoot<TMenuData = {}, TValue = string>(
|
||||
flatMenu: IFlatMenu<TMenuData, TValue>,
|
||||
parent: string
|
||||
): IFlatMenu<TMenuData> {
|
||||
): IFlatMenu<TMenuData, TValue> {
|
||||
const menuItem = flatMenu.find(menuItem => menuItem.id === parent);
|
||||
|
||||
if (menuItem.parent === null) {
|
||||
|
@ -86,10 +100,10 @@ function _walkToRoot<TMenuData = {}>(
|
|||
|
||||
return [menuItem, ..._walkToRoot(flatMenu, menuItem.parent)];
|
||||
}
|
||||
export function walkToRoot<TMenuData = {}>(
|
||||
menu: IMenu<TMenuData>,
|
||||
value: string
|
||||
): IMenu<TMenuData> {
|
||||
export function walkToRoot<TMenuData = {}, TValue = string>(
|
||||
menu: IMenu<TMenuData, TValue>,
|
||||
value: TValue
|
||||
): IMenu<TMenuData, TValue> {
|
||||
const flatMenu = toFlat(menu);
|
||||
const menuItem = flatMenu.find(menuItem => menuItem.value === value);
|
||||
|
||||
|
@ -99,13 +113,13 @@ export function walkToRoot<TMenuData = {}>(
|
|||
).map(flatMenuItem => _fromFlat(flatMenu, flatMenuItem));
|
||||
}
|
||||
|
||||
function _toFlat<TMenuData = {}>(
|
||||
menuItem: IMenuItem<TMenuData>,
|
||||
function _toFlat<TMenuData = {}, TValue = string>(
|
||||
menuItem: IMenuItem<TMenuData, TValue>,
|
||||
sort: number,
|
||||
parent: string
|
||||
): IFlatMenu<TMenuData> {
|
||||
): IFlatMenu<TMenuData, TValue> {
|
||||
const id = parent ? [parent, sort].join(":") : sort.toString();
|
||||
const flatMenuItem: IFlatMenuItem<TMenuData> = {
|
||||
const flatMenuItem: IFlatMenuItem<TMenuData, TValue> = {
|
||||
data: menuItem.data,
|
||||
id,
|
||||
label: menuItem.label,
|
||||
|
@ -117,22 +131,28 @@ function _toFlat<TMenuData = {}>(
|
|||
flatMenuItem,
|
||||
...menuItem.children
|
||||
.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 = {}>(
|
||||
menu: IMenu<TMenuData>
|
||||
): IFlatMenu<TMenuData> {
|
||||
export function toFlat<TMenuData = {}, TValue = string>(
|
||||
menu: IMenu<TMenuData, TValue>
|
||||
): IFlatMenu<TMenuData, TValue> {
|
||||
return menu
|
||||
.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 = {}>(
|
||||
menu: IFlatMenu<TMenuData>,
|
||||
flatMenuItem: IFlatMenuItem<TMenuData>
|
||||
): IMenuItem<TMenuData> {
|
||||
const children: Array<IMenuItem<TMenuData>> = menu
|
||||
function _fromFlat<TMenuData = {}, TValue = string>(
|
||||
menu: IFlatMenu<TMenuData, TValue>,
|
||||
flatMenuItem: IFlatMenuItem<TMenuData, TValue>
|
||||
): IMenuItem<TMenuData, TValue> {
|
||||
const children: Array<IMenuItem<TMenuData, TValue>> = menu
|
||||
.filter(menuItem => menuItem.parent === flatMenuItem.id)
|
||||
.map(menuItem => _fromFlat(menu, menuItem));
|
||||
|
||||
|
@ -143,16 +163,16 @@ function _fromFlat<TMenuData = {}>(
|
|||
value: flatMenuItem.value
|
||||
};
|
||||
}
|
||||
export function fromFlat<TMenuData = {}>(
|
||||
menu: IFlatMenu<TMenuData>
|
||||
): IMenu<TMenuData> {
|
||||
export function fromFlat<TMenuData = {}, TValue = string>(
|
||||
menu: IFlatMenu<TMenuData, TValue>
|
||||
): IMenu<TMenuData, TValue> {
|
||||
return menu
|
||||
.filter(menuItem => menuItem.parent === null)
|
||||
.map(menuItem => _fromFlat(menu, menuItem));
|
||||
}
|
||||
|
||||
export function isLeaf<TMenuData = {}>(
|
||||
menuItem: IMenuItem<TMenuData>
|
||||
export function isLeaf<TMenuData = {}, TValue = string>(
|
||||
menuItem: IMenuItem<TMenuData, TValue>
|
||||
): boolean {
|
||||
return menuItem.children.length === 0;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue