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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -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;
@ -112,6 +113,9 @@ export type ActiveTab<TTab extends string = string> = Partial<{
export type Filters<TFilters extends string> = Partial< export type Filters<TFilters extends string> = Partial<
Record<TFilters, string> Record<TFilters, string>
>; >;
export type FiltersWithMultipleValues<TFilters extends string> = Partial<
Record<TFilters, string | string[]>
>;
export type SingleAction = Partial<{ export type SingleAction = Partial<{
id: string; 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 { function getActiveFilters(params: TQueryParams): TFilters {
return Object.keys(params) return Object.keys(params)
.filter(key => Object.keys(filters).includes(key)) .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; export default createFilterUtils;

View file

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

View file

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

View file

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