Merge pull request #160 from mirumee/add/ready-to-fulfill-orders

Add support for multiple values in filters
This commit is contained in:
Marcin Gębala 2019-09-10 13:40:33 +02:00 committed by GitHub
commit 2287411d5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 823 additions and 232 deletions

View file

@ -17,3 +17,4 @@ All notable, unreleased changes to this project will be documented in this file.
- Add fallback locale - #153 by @dominik-zeglen
- Replace checkbox with switch component in "product type has variants" - #152 by @dominik-zeglen
- Add password reset flow - #147 by @dominik-zeglen
- Add support for multiple values in filters - #160 by @dominik-zeglen

View file

@ -1,6 +1,6 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2019-09-06T13:56:36.897Z\n"
"POT-Creation-Date: 2019-09-10T11:00:36.829Z\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"MIME-Version: 1.0\n"
@ -1439,6 +1439,10 @@ msgctxt "description"
msgid "Availability"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.2157131639] - product status
#. defaultMessage is:
#. Available
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.2157131639] - product status
#. defaultMessage is:
@ -3447,14 +3451,6 @@ msgctxt "order history message"
msgid "Fulfilled {quantity} items"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.2943502192] - order
#. defaultMessage is:
#. Fulfillment Status
msgctxt "order"
msgid "Fulfillment Status"
msgstr ""
#: build/locale/src/orders/components/OrderHistory/OrderHistory.json
#. [src.orders.components.OrderHistory.3081292385] - order history message
#. defaultMessage is:
@ -3555,14 +3551,10 @@ msgctxt "subheader"
msgid "Here is some information we gathered about your store"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.77815154]
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.77815154] - product is hidden
#. defaultMessage is:
#. Hidden
msgctxt "description"
msgid "Hidden"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.77815154] - product is hidden
#. defaultMessage is:
@ -3571,6 +3563,14 @@ msgctxt "product is hidden"
msgid "Hidden"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.77815154]
#. defaultMessage is:
#. Hidden
msgctxt "description"
msgid "Hidden"
msgstr ""
#: build/locale/src/products/views/ProductList/filters.json
#. [src.products.views.ProductList.hidden] - filter products by visibility
#. defaultMessage is:
@ -4871,6 +4871,14 @@ msgctxt "description"
msgid "Order History"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.2222765704]
#. defaultMessage is:
#. Order Status
msgctxt "description"
msgid "Order Status"
msgstr ""
#: build/locale/src/orders/components/OrderHistory/OrderHistory.json
#. [src.orders.components.OrderHistory.1230178536] - order history message
#. defaultMessage is:
@ -5027,6 +5035,10 @@ msgctxt "description"
msgid "Original String"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1640493122] - product status
#. defaultMessage is:
#. Out Of Stock
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1640493122] - product status
#. defaultMessage is:
@ -5363,6 +5375,18 @@ msgctxt "order payment"
msgid "Preauthorized amount"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1134347598]
#. defaultMessage is:
#. Price
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1134347598]
#. defaultMessage is:
#. Price
msgctxt "description"
msgid "Price"
msgstr ""
#: build/locale/src/categories/components/CategoryProductList/CategoryProductList.json
#. [src.categories.components.CategoryProductList.1134347598] - product price
#. defaultMessage is:
@ -5411,14 +5435,6 @@ msgctxt "product unit price"
msgid "Price"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1134347598]
#. defaultMessage is:
#. Price
msgctxt "description"
msgid "Price"
msgstr ""
#: build/locale/src/products/components/ProductVariants/ProductVariants.json
#. [src.products.components.ProductVariants.1134347598] - product variant price
#. defaultMessage is:
@ -5903,6 +5919,10 @@ msgctxt "description"
msgid "Quick Pick"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.2545228781]
#. defaultMessage is:
#. Range
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.2545228781]
#. defaultMessage is:
@ -5939,6 +5959,18 @@ msgctxt "shipping method price"
msgid "Rate Price"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.2415661583] - order status
#. defaultMessage is:
#. Ready to Capture
#: build/locale/src/orders/views/OrderList/filters.json
#. [src.orders.views.OrderList.readyToCapture] - order status
#. defaultMessage is:
#. Ready to Capture
msgctxt "order status"
msgid "Ready to Capture"
msgstr ""
#: build/locale/src/customers/components/CustomerOrders/CustomerOrders.json
#. [src.customers.components.CustomerOrders.3878642352] - section header
#. defaultMessage is:
@ -6803,6 +6835,10 @@ msgctxt "description"
msgid "Specific Date"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.2844426531]
#. defaultMessage is:
#. Specific Price
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.2844426531]
#. defaultMessage is:
@ -6883,6 +6919,18 @@ msgctxt "voucher is active from date"
msgid "Starts"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1756106276] - product status
#. defaultMessage is:
#. Status
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1756106276] - product status
#. defaultMessage is:
#. Status
msgctxt "product status"
msgid "Status"
msgstr ""
#: build/locale/src/customers/components/CustomerOrders/CustomerOrders.json
#. [src.customers.components.CustomerOrders.1756106276] - order status
#. defaultMessage is:
@ -6907,14 +6955,6 @@ msgctxt "plugin status"
msgid "Status"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1756106276] - product status
#. defaultMessage is:
#. Status
msgctxt "product status"
msgid "Status"
msgstr ""
#: build/locale/src/products/components/ProductVariants/ProductVariants.json
#. [src.products.components.ProductVariants.1756106276] - product variant status
#. defaultMessage is:
@ -6923,6 +6963,10 @@ msgctxt "product variant status"
msgid "Status"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.3841616483] - product stock
#. defaultMessage is:
#. Stock
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.3841616483] - product stock
#. defaultMessage is:
@ -6939,6 +6983,10 @@ msgctxt "product variant stock, section header"
msgid "Stock"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.3645081351]
#. defaultMessage is:
#. Stock quantity
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.3645081351]
#. defaultMessage is:
@ -8107,6 +8155,18 @@ msgctxt "description"
msgid "View and update your site settings"
msgstr ""
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1459686496] - product visibility
#. defaultMessage is:
#. Visibility
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1459686496] - product visibility
#. defaultMessage is:
#. Visibility
msgctxt "product visibility"
msgid "Visibility"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.1459686496] - section header
#. defaultMessage is:
@ -8123,14 +8183,6 @@ msgctxt "page status"
msgid "Visibility"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1459686496] - product visibility
#. defaultMessage is:
#. Visibility
msgctxt "product visibility"
msgid "Visibility"
msgstr ""
#: build/locale/src/attributes/components/AttributeList/AttributeList.json
#. [src.attributes.components.AttributeList.643174786] - attribute is visible
#. defaultMessage is:
@ -8139,14 +8191,10 @@ msgctxt "attribute is visible"
msgid "Visible"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.643174786]
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.643174786] - product is visible
#. defaultMessage is:
#. Visible
msgctxt "description"
msgid "Visible"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.643174786] - product is visible
#. defaultMessage is:
@ -8155,6 +8203,14 @@ msgctxt "product is visible"
msgid "Visible"
msgstr ""
#: build/locale/src/components/VisibilityCard/VisibilityCard.json
#. [src.components.VisibilityCard.643174786]
#. defaultMessage is:
#. Visible
msgctxt "description"
msgid "Visible"
msgstr ""
#: build/locale/src/attributes/components/AttributeProperties/AttributeProperties.json
#. [src.attributes.components.AttributeProperties.3876764312] - attribute
#. defaultMessage is:
@ -8303,14 +8359,10 @@ msgctxt "order does not require shipping"
msgid "does not apply"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.3477667254]
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.3477667254] - product price
#. defaultMessage is:
#. equals
msgctxt "description"
msgid "equals"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.3477667254] - product price
#. defaultMessage is:
@ -8319,6 +8371,14 @@ msgctxt "product price"
msgid "equals"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.3477667254]
#. defaultMessage is:
#. equals
msgctxt "description"
msgid "equals"
msgstr ""
#: build/locale/src/components/Filter/FilterElement.json
#. [src.components.Filter.2755325844]
#. defaultMessage is:
@ -8343,14 +8403,10 @@ msgctxt "weight"
msgid "from {value} {unit}"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.1438173764] - date is set as
#: build/locale/src/attributes/components/ProductListFilter/ProductListFilter.json
#. [src.attributes.components.ProductListFilter.1438173764] - product status is set as
#. defaultMessage is:
#. is set as
msgctxt "date is set as"
msgid "is set as"
msgstr ""
#: build/locale/src/products/components/ProductListFilter/ProductListFilter.json
#. [src.products.components.ProductListFilter.1438173764] - product status is set as
#. defaultMessage is:
@ -8359,6 +8415,14 @@ msgctxt "product status is set as"
msgid "is set as"
msgstr ""
#: build/locale/src/orders/components/OrderListFilter/OrderListFilter.json
#. [src.orders.components.OrderListFilter.1438173764] - date is set as
#. defaultMessage is:
#. is set as
msgctxt "date is set as"
msgid "is set as"
msgstr ""
#: build/locale/src/staff/views/StaffDetails.json
#. [src.staff.views.2240444792] - dialog header
#. defaultMessage is:

View file

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

View file

@ -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({

View file

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

View file

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

View file

@ -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> = ({

View file

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

View file

@ -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: [

View file

@ -31,14 +31,17 @@ const HomeSection = () => {
onOrdersToCaptureClick={() =>
navigate(
orderListUrl({
status: OrderStatusFilter.READY_TO_CAPTURE
status: [OrderStatusFilter.READY_TO_CAPTURE]
})
)
}
onOrdersToFulfillClick={() =>
navigate(
orderListUrl({
status: OrderStatusFilter.READY_TO_FULFILL
status: [
OrderStatusFilter.UNFULFILLED,
OrderStatusFilter.PARTIALLY_FULFILLED
]
})
)
}

View file

@ -466,3 +466,15 @@ export function generateCode(charNum: number) {
}
return result;
}
export function findInEnum<TEnum extends object>(
needle: string,
haystack: TEnum
) {
const match = Object.keys(haystack).find(key => key === needle);
if (!!match) {
return haystack[needle as keyof TEnum];
}
throw new Error(`Key ${needle} not found in enum`);
}

View file

@ -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",
status = "status"
}
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: [],
@ -147,15 +146,21 @@ const OrderListFilter: React.FC<OrderListFilterProps> = props => {
description: "order fulfillment status"
}),
value: OrderStatusFilter.UNFULFILLED.toString()
},
{
label: intl.formatMessage({
defaultMessage: "Ready to Capture",
description: "order status"
}),
value: OrderStatusFilter.READY_TO_CAPTURE.toString()
}
],
type: FieldType.select
},
label: intl.formatMessage({
defaultMessage: "Fulfillment Status",
description: "order"
defaultMessage: "Order Status"
}),
value: OrderFilterKeys.fulfillment.toString()
value: OrderFilterKeys.status
}
];

View file

@ -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[];
}

View file

@ -6,6 +6,7 @@ import {
BulkAction,
Dialog,
Filters,
FiltersWithMultipleValues,
Pagination,
SingleAction
} from "../types";
@ -16,11 +17,14 @@ export const orderListPath = orderSectionUrl;
export enum OrderListUrlFiltersEnum {
dateFrom = "dateFrom",
dateTo = "dateTo",
status = "status",
email = "email",
payment = "payment"
}
export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum>;
export enum OrderListUrlFiltersWithMultipleValuesEnum {
status = "status"
}
export type OrderListUrlFilters = Filters<OrderListUrlFiltersEnum> &
FiltersWithMultipleValues<OrderListUrlFiltersWithMultipleValuesEnum>;
export type OrderListUrlDialog = "cancel" | "save-search" | "delete-search";
export type OrderListUrlQueryParams = BulkAction &
Dialog<OrderListUrlDialog> &

View file

@ -229,8 +229,8 @@ export const OrderList: React.StatelessComponent<OrderListProps> = ({
</Button>
}
onSearchChange={email => changeFilterField({ email })}
onFilterAdd={filter =>
changeFilterField(createFilter(filter))
onFilterAdd={data =>
changeFilterField(createFilter(params, data))
}
onFilterSave={() => openModal("save-search")}
onFilterDelete={() => openModal("delete-search")}

View 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",
],
}
`;

View 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.status,
value: OrderStatusFilter.PARTIALLY_FULFILLED
}
);
expect(filter).toMatchSnapshot();
});
it("with multiple values", () => {
const filter = createFilter(
{
status: [OrderStatusFilter.FULFILLED]
},
{
name: OrderFilterKeys.status,
value: OrderStatusFilter.PARTIALLY_FULFILLED
}
);
expect(filter).toMatchSnapshot();
});
it("with multiple deduped values", () => {
const filter = createFilter(
{
status: [OrderStatusFilter.FULFILLED]
},
{
name: OrderFilterKeys.status,
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();
});
});

View file

@ -1,5 +1,7 @@
import { defineMessages, IntlShape } from "react-intl";
import { findInEnum } from "@saleor/misc";
import { removeAtIndex } from "@saleor/utils/lists";
import { FilterContentSubmitData } from "../../../components/Filter";
import { Filter } from "../../../components/TableFilter";
import {
@ -7,8 +9,12 @@ import {
OrderStatusFilter
} from "../../../types/globalTypes";
import {
arrayOrUndefined,
arrayOrValue,
createFilterTabUtils,
createFilterUtils
createFilterUtils,
dedupeFilter,
valueOrFirst
} from "../../../utils/filters";
import { OrderFilterKeys } from "../../components/OrderListFilter";
import {
@ -40,6 +46,10 @@ const filterMessages = defineMessages({
defaultMessage: "Partially Fulfilled",
description: "order status"
},
readyToCapture: {
defaultMessage: "Ready to Capture",
description: "order status"
},
unfulfilled: {
defaultMessage: "Unfulfilled",
description: "order status"
@ -56,6 +66,9 @@ function getStatusLabel(status: string, intl: IntlShape): string {
case OrderStatusFilter.UNFULFILLED.toString():
return intl.formatMessage(filterMessages.unfulfilled);
case OrderStatusFilter.READY_TO_CAPTURE.toString():
return intl.formatMessage(filterMessages.readyToCapture);
}
return "";
@ -70,22 +83,25 @@ export function getFilterVariables(
lte: params.dateTo
},
customer: params.email,
status: OrderStatusFilter[params.status]
status: Array.isArray(params.status)
? params.status.map(status => findInEnum(status, OrderStatusFilter))
: params.status
? [findInEnum(params.status, OrderStatusFilter)]
: undefined
};
}
export function createFilter(
filter: FilterContentSubmitData
filter: OrderListUrlFilters,
data: FilterContentSubmitData<OrderFilterKeys>
): OrderListUrlFilters {
const filterName = filter.name;
if (filterName === OrderFilterKeys.dateEqual.toString()) {
const value = filter.value as string;
const { name: filterName, value } = data;
if (filterName === OrderFilterKeys.dateEqual) {
return {
dateFrom: value,
dateTo: value
dateFrom: valueOrFirst(value),
dateTo: valueOrFirst(value)
};
} else if (filterName === OrderFilterKeys.dateRange.toString()) {
const { value } = filter;
} else if (filterName === OrderFilterKeys.dateRange) {
return {
dateFrom: value[0],
dateTo: value[1]
@ -95,19 +111,19 @@ export function createFilter(
OrderFilterKeys.dateLastWeek,
OrderFilterKeys.dateLastMonth,
OrderFilterKeys.dateLastYear
]
.map(value => value.toString())
.includes(filterName)
].includes(filterName)
) {
const { value } = filter;
return {
dateFrom: value as string,
dateFrom: valueOrFirst(value),
dateTo: undefined
};
} else if (filterName === OrderFilterKeys.fulfillment.toString()) {
const { value } = filter;
} else if (filterName === OrderFilterKeys.status) {
return {
status: value as string
status: dedupeFilter(
filter.status
? [...(filter.status as string[]), valueOrFirst(value)]
: arrayOrValue(value)
)
};
}
}
@ -175,17 +191,29 @@ export function createFilterChips(
}
if (!!filters.status) {
filterChips = [
...filterChips,
{
label: getStatusLabel(filters.status, intl),
onClick: () =>
onFilterDelete({
...filters,
status: undefined
})
}
];
const statusFilterChips = Array.isArray(filters.status)
? filters.status.map((status, statusIndex) => ({
label: getStatusLabel(status, intl),
onClick: () =>
onFilterDelete({
...filters,
status: arrayOrUndefined(
removeAtIndex(filters.status as string[], statusIndex)
)
})
}))
: [
{
label: getStatusLabel(filters.status, intl),
onClick: () =>
onFilterDelete({
...filters,
status: undefined
})
}
];
filterChips = [...filterChips, ...statusFilterChips];
}
return filterChips;

View file

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

View file

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

View file

@ -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",
}
`;

View 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();
});

View file

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

View file

@ -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;
@ -112,6 +113,9 @@ export type ActiveTab<TTab extends string = string> = Partial<{
export type Filters<TFilters extends string> = Partial<
Record<TFilters, string>
>;
export type FiltersWithMultipleValues<TFilters extends string> = Partial<
Record<TFilters, string | string[]>
>;
export type SingleAction = Partial<{
id: string;
}>;

View file

@ -1,4 +1,7 @@
function createFilterUtils<TQueryParams, TFilters>(filters: object) {
function createFilterUtils<
TQueryParams extends object,
TFilters extends object
>(filters: object) {
function getActiveFilters(params: TQueryParams): TFilters {
return Object.keys(params)
.filter(key => Object.keys(filters).includes(key))
@ -21,4 +24,32 @@ function createFilterUtils<TQueryParams, TFilters>(filters: object) {
};
}
export function valueOrFirst<T>(value: T | T[]): T {
if (Array.isArray(value)) {
return value[0];
}
return value;
}
export function arrayOrValue<T>(value: T | T[]): T[] {
if (Array.isArray(value)) {
return value;
}
return [value];
}
export function arrayOrUndefined<T>(array: T[]): T[] | undefined {
if (array.length === 0) {
return undefined;
}
return array;
}
export function dedupeFilter<T>(array: T[]): T[] {
return Array.from(new Set(array));
}
export default createFilterUtils;

View file

@ -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,
},
]
`;

View file

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

View file

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