Refactor filter components

This commit is contained in:
dominik-zeglen 2019-12-17 15:20:54 +01:00
parent d58b1046ff
commit 87b94f47e1
16 changed files with 626 additions and 286 deletions

View file

@ -0,0 +1,22 @@
import React from "react";
const Arrow: React.FC<React.SVGProps<SVGSVGElement>> = props => (
<svg
width="18"
height="21"
viewBox="0 0 18 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.5858 17.1357L-1.37065e-07 17.1357L-1.37065e-07 15L-1.37064e-07 0L2 -8.74228e-08L2 15.1357L13.5858 15.1357L11.8643 13.4142L13.2785 12L17.4142 16.1357L13.2785 20.2714L11.8643 18.8571L13.5858 17.1357Z"
fill="#3D3D3D"
/>
</svg>
);
Arrow.displayName = "Arrow";
export default Arrow;

View file

@ -1,6 +1,5 @@
import ButtonBase from "@material-ui/core/ButtonBase";
import Grow from "@material-ui/core/Grow";
import Paper from "@material-ui/core/Paper";
import Popper from "@material-ui/core/Popper";
import { makeStyles } from "@material-ui/core/styles";
import { fade } from "@material-ui/core/styles/colorManipulator";
@ -17,7 +16,6 @@ import { FilterContent } from ".";
export interface FilterProps<TFilterKeys = string> {
currencySymbol: string;
menu: IFilter<TFilterKeys>;
filterLabel: string;
onFilterAdd: (filter: FilterContentSubmitData) => void;
}
@ -82,7 +80,7 @@ const useStyles = makeStyles(
{ name: "Filter" }
);
const Filter: React.FC<FilterProps> = props => {
const { currencySymbol, filterLabel, menu, onFilterAdd } = props;
const { currencySymbol, menu, onFilterAdd } = props;
const classes = useStyles(props);
const anchor = React.useRef<HTMLDivElement>();
@ -122,17 +120,14 @@ const Filter: React.FC<FilterProps> = props => {
placement === "bottom" ? "right top" : "right bottom"
}}
>
<Paper className={classes.paper}>
<Typography>{filterLabel}</Typography>
<FilterContent
currencySymbol={currencySymbol}
filters={menu}
onSubmit={data => {
onFilterAdd(data);
setFilterMenuOpened(false);
}}
/>
</Paper>
<FilterContent
currencySymbol={currencySymbol}
filters={menu}
onSubmit={data => {
onFilterAdd(data);
setFilterMenuOpened(false);
}}
/>
</Grow>
)}
</Popper>

View file

@ -56,13 +56,10 @@ export interface FilterActionsPropsSearch {
export interface FilterActionsPropsFilters<TKeys = string> {
currencySymbol: string;
menu: IFilter<TKeys>;
filterLabel: string;
onFilterAdd: (filter: FilterContentSubmitData<TKeys>) => void;
}
export const FilterActionsOnlySearch: React.FC<
FilterActionsPropsSearch
> = props => {
export const FilterActionsOnlySearch: React.FC<FilterActionsPropsSearch> = props => {
const { onSearchChange, placeholder, search } = props;
const classes = useStyles(props);
@ -83,7 +80,6 @@ export type FilterActionsProps = FilterActionsPropsSearch &
const FilterActions: React.FC<FilterActionsProps> = props => {
const {
currencySymbol,
filterLabel,
menu,
onFilterAdd,
onSearchChange,
@ -97,7 +93,6 @@ const FilterActions: React.FC<FilterActionsProps> = props => {
<Filter
currencySymbol={currencySymbol}
menu={menu}
filterLabel={filterLabel}
onFilterAdd={onFilterAdd}
/>
<Search

View file

@ -1,29 +1,37 @@
import Button from "@material-ui/core/Button";
import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { FormattedMessage, useIntl, IntlShape } from "react-intl";
import { makeStyles } from "@material-ui/core/styles";
import { getMenuItemByValue, isLeaf, walkToRoot } from "../../utils/menu";
import FormSpacer from "../FormSpacer";
import makeStyles from "@material-ui/core/styles/makeStyles";
import { fade } from "@material-ui/core/styles/colorManipulator";
import { buttonMessages } from "@saleor/intl";
import { TextField } from "@material-ui/core";
import { toggle } from "@saleor/utils/lists";
import Hr from "../Hr";
import Checkbox from "../Checkbox";
import SingleSelectField from "../SingleSelectField";
import FilterElement from "./FilterElement";
import { IFilter } from "./types";
import { SingleAutocompleteChoiceType } from "../SingleAutocompleteSelectField";
import FormSpacer from "../FormSpacer";
import { IFilter, FieldType, FilterType } from "./types";
import Arrow from "./Arrow";
import { FilterReducerAction } from "./reducer";
export interface FilterContentSubmitData<TKeys = string> {
name: TKeys;
value: string | string[];
value: string[];
}
export interface FilterContentProps {
export interface FilterContentProps<T extends string = string> {
currencySymbol: string;
filters: IFilter<string>;
onSubmit: (data: FilterContentSubmitData) => void;
filters: IFilter<T>;
onFilterPropertyChange: React.Dispatch<FilterReducerAction<T>>;
onClear: () => void;
onSubmit: () => void;
}
function checkFilterValue(value: string | string[]): boolean {
if (typeof value === "string") {
return !!value;
}
function checkFilterValue(value: string[]): boolean {
return value.some(v => !!v);
}
@ -35,113 +43,333 @@ function getFilterChoices(items: IFilter<string>) {
}
const useStyles = makeStyles(
{
theme => ({
actionBar: {
alignItems: "center",
display: "flex",
justifyContent: "space-between",
padding: theme.spacing(1, 3)
},
andLabel: {
margin: theme.spacing(0, 2)
},
arrow: {
marginRight: theme.spacing(2)
},
clear: {
marginRight: theme.spacing(1)
},
filterFieldBar: {
"&:not(:last-of-type)": {
borderBottom: `1px solid ${theme.palette.divider}`
},
padding: theme.spacing(1, 2.5)
},
filterSettings: {
background: fade(theme.palette.primary.main, 0.2),
padding: theme.spacing(2, 3)
},
input: {
padding: "20px 12px 17px"
padding: "12px 0 9px 12px"
},
inputRange: {
alignItems: "center",
display: "flex"
},
label: {
fontWeight: 600
},
option: {
left: -theme.spacing(0.5),
position: "relative"
}
},
}),
{ name: "FilterContent" }
);
function getIsFilterMultipleChoices(
intl: IntlShape
): SingleAutocompleteChoiceType[] {
return [
{
label: intl.formatMessage({
defaultMessage: "is equal to",
description: "is filter range or value"
}),
value: FilterType.SINGULAR
},
{
label: intl.formatMessage({
defaultMessage: "is between",
description: "is filter range or value"
}),
value: FilterType.MULTIPLE
}
];
}
const FilterContent: React.FC<FilterContentProps> = ({
currencySymbol,
filters,
onClear,
onFilterPropertyChange,
onSubmit
}) => {
const intl = useIntl();
const [menuValue, setMenuValue] = React.useState<string>(null);
const [filterValue, setFilterValue] = React.useState<string | string[]>("");
const classes = useStyles({});
const activeMenu = menuValue
? getMenuItemByValue(filters, menuValue)
: undefined;
const menus = menuValue
? walkToRoot(filters, menuValue).slice(-1)
: undefined;
const onMenuChange = (event: React.ChangeEvent<any>) => {
setMenuValue(event.target.value);
setFilterValue("");
};
return (
<>
<SingleSelectField
choices={getFilterChoices(filters)}
onChange={onMenuChange}
selectProps={{
classes: {
selectMenu: classes.input
}
<Paper>
<form
onSubmit={event => {
event.preventDefault();
onSubmit();
}}
value={menus ? menus[0].value : menuValue}
placeholder={intl.formatMessage({
defaultMessage: "Select Filter..."
})}
/>
{menus &&
menus.map(
(filterItem, filterItemIndex) =>
!isLeaf(filterItem) && (
<React.Fragment
key={filterItem.label.toString() + ":" + filterItem.value}
>
<FormSpacer />
<SingleSelectField
choices={getFilterChoices(filterItem.children)}
onChange={onMenuChange}
selectProps={{
classes: {
selectMenu: classes.input
}
}}
value={
filterItemIndex === menus.length - 1
? menuValue.toString()
: menus[filterItemIndex - 1].label.toString()
>
<div className={classes.actionBar}>
<Typography className={classes.label}>
<FormattedMessage defaultMessage="Filters" />
</Typography>
<div>
<Button className={classes.clear} onClick={onClear}>
<FormattedMessage {...buttonMessages.clear} />
</Button>
<Button color="primary" variant="contained" type="submit">
<FormattedMessage {...buttonMessages.done} />
</Button>
</div>
</div>
<Hr />
{filters
.sort((a, b) => (a.name > b.name ? 1 : -1))
.map(filterField => (
<React.Fragment key={filterField.name}>
<div className={classes.filterFieldBar}>
<FormControlLabel
control={<Checkbox checked={filterField.active} />}
label={filterField.label}
onChange={() =>
onFilterPropertyChange({
payload: {
name: filterField.name,
update: {
active: !filterField.active
}
},
type: "set-property"
})
}
placeholder={intl.formatMessage({
defaultMessage: "Select Filter..."
})}
/>
</React.Fragment>
)
)}
{activeMenu && isLeaf(activeMenu) && (
<>
<FormSpacer />
{activeMenu.data.additionalText && (
<Typography>{activeMenu.data.additionalText}</Typography>
)}
<FilterElement
currencySymbol={currencySymbol}
filter={activeMenu}
value={filterValue}
onChange={value => setFilterValue(value)}
/>
{checkFilterValue(filterValue) && (
<>
<FormSpacer />
<Button
color="primary"
onClick={() =>
onSubmit({
name: activeMenu.value,
value: filterValue
})
}
>
<FormattedMessage
defaultMessage="Add filter"
description="button"
/>
</Button>
</>
)}
</>
)}
</>
</div>
{filterField.active && (
<div className={classes.filterSettings}>
{[FieldType.date, FieldType.price, FieldType.number].includes(
filterField.type
) && (
<>
<SingleSelectField
choices={getIsFilterMultipleChoices(intl)}
value={
filterField.multiple
? FilterType.MULTIPLE
: FilterType.SINGULAR
}
InputProps={{
classes: {
input: classes.input
}
}}
onChange={event =>
onFilterPropertyChange({
payload: {
name: filterField.name,
update: {
multiple:
event.target.value === FilterType.MULTIPLE
}
},
type: "set-property"
})
}
/>
<FormSpacer />
<div className={classes.inputRange}>
<div>
<Arrow className={classes.arrow} />
</div>
{filterField.multiple ? (
<>
<TextField
fullWidth
name={filterField.name + "_min"}
InputProps={{
classes: {
input: classes.input
},
endAdornment:
filterField.type === FieldType.price &&
currencySymbol,
type:
filterField.type === FieldType.date
? "date"
: "number"
}}
value={filterField.value[0]}
onChange={event =>
onFilterPropertyChange({
payload: {
name: filterField.name,
update: {
value: [
event.target.value,
filterField.value[1]
]
}
},
type: "set-property"
})
}
/>
<span className={classes.andLabel}>
<FormattedMessage
defaultMessage="and"
description="filter range separator"
/>
</span>
<TextField
fullWidth
name={filterField.name + "_max"}
InputProps={{
classes: {
input: classes.input
},
endAdornment:
filterField.type === FieldType.price &&
currencySymbol,
type:
filterField.type === FieldType.date
? "date"
: "number"
}}
value={filterField.value[1]}
onChange={event =>
onFilterPropertyChange({
payload: {
name: filterField.name,
update: {
value: [
filterField.value[0],
event.target.value
]
}
},
type: "set-property"
})
}
/>
</>
) : (
<TextField
fullWidth
name={filterField.name}
InputProps={{
classes: {
input: classes.input
},
endAdornment:
filterField.type === FieldType.price &&
currencySymbol,
type:
filterField.type === FieldType.date
? "date"
: [
FieldType.number,
FieldType.price
].includes(filterField.type)
? "number"
: "text"
}}
value={filterField.value[0]}
onChange={event =>
onFilterPropertyChange({
payload: {
name: filterField.name,
update: {
value: [
event.target.value,
filterField.value[1]
]
}
},
type: "set-property"
})
}
/>
)}
</div>
</>
)}
{filterField.type === FieldType.options &&
(filterField.multiple ? (
filterField.options.map(option => (
<div className={classes.option} key={option.value}>
<FormControlLabel
control={
<Checkbox
checked={filterField.value.includes(
option.value
)}
/>
}
label={option.label}
name={filterField.name}
onChange={() =>
onFilterPropertyChange({
payload: {
name: filterField.name,
update: {
value: toggle(
option.value,
filterField.value,
(a, b) => a === b
)
}
},
type: "set-property"
})
}
/>
</div>
))
) : (
<SingleSelectField
choices={filterField.options}
name={filterField.name}
value={filterField.value[0]}
InputProps={{
classes: {
input: classes.input
}
}}
onChange={event =>
onFilterPropertyChange({
payload: {
name: filterField.name,
update: {
value: [event.target.value]
}
},
type: "set-property"
})
}
/>
))}
</div>
)}
</React.Fragment>
))}
</form>
</Paper>
);
};
FilterContent.displayName = "FilterContent";

View file

View file

@ -0,0 +1,43 @@
import { update } from "@saleor/utils/lists";
import { IFilter, IFilterElementMutableData } from "./types";
export type FilterReducerActionType = "clear" | "reset" | "set-property";
export interface FilterReducerAction<T extends string> {
type: FilterReducerActionType;
payload: Partial<{
name: T;
update: Partial<IFilterElementMutableData>;
reset: IFilter<T>;
}>;
}
function setProperty<T extends string>(
prevState: IFilter<T>,
filter: T,
updateData: Partial<IFilterElementMutableData>
): IFilter<T> {
const field = prevState.find(f => f.name === filter);
const updatedField = {
...field,
...updateData
};
return update(updatedField, prevState, (a, b) => a.name === b.name);
}
function reduceFilter<T extends string>(
prevState: IFilter<T>,
action: FilterReducerAction<T>
): IFilter<T> {
switch (action.type) {
case "clear":
return prevState;
case "set-property":
return setProperty(prevState, action.payload.name, action.payload.update);
case "reset":
return action.payload.reset;
}
return prevState;
}
export default reduceFilter;

View file

@ -1,30 +1,33 @@
import { IMenu, IMenuItem } from "../../utils/menu";
import { FetchMoreProps } from "@saleor/types";
import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField";
export enum FieldType {
date,
hidden,
number,
price,
range,
rangeDate,
rangePrice,
select,
options,
text
}
export interface FilterChoice {
export interface IFilterElementMutableData<T extends string = string> {
active: boolean;
multiple: boolean;
options?: MultiAutocompleteChoiceType[];
value: T[];
}
export interface IFilterElement<T extends string = string>
extends Partial<FetchMoreProps>,
IFilterElementMutableData {
autocomplete?: boolean;
currencySymbol?: string;
label: string;
value: string | boolean;
}
export interface FilterData {
additionalText?: string;
fieldLabel: string;
options?: FilterChoice[];
name: T;
type: FieldType;
value?: string;
}
export type IFilterItem<TKeys> = IMenuItem<FilterData, TKeys>;
export type IFilter<T extends string = string> = Array<IFilterElement<T>>;
export type IFilter<TKeys> = IMenu<FilterData, TKeys>;
export enum FilterType {
MULTIPLE = "MULTIPLE",
SINGULAR = "SINGULAR"
}

View file

@ -0,0 +1,37 @@
import { useReducer, useEffect, Dispatch } from "react";
import reduceFilter, { FilterReducerAction } from "./reducer";
import { IFilter, IFilterElement } from "./types";
function createInitialFilter<T extends string>(
initialFilter: IFilter<T>
): IFilter<T> {
return initialFilter;
}
export type UseFilter<T extends string> = [
Array<IFilterElement<T>>,
Dispatch<FilterReducerAction<T>>,
() => void
];
function useFilter<T extends string>(initialFilter: IFilter<T>): UseFilter<T> {
const [data, dispatchFilterAction] = useReducer(
reduceFilter,
createInitialFilter(initialFilter)
);
const reset = () =>
dispatchFilterAction({
payload: {
reset: initialFilter
},
type: "reset"
});
useEffect(reset, [initialFilter]);
return [data, dispatchFilterAction, reset];
}
export default useFilter;

View file

@ -2,8 +2,8 @@ import React from "react";
import { useIntl } from "react-intl";
import { SearchPageProps, TabPageProps } from "@saleor/types";
import FilterSearch from "../Filter/FilterSearch";
import FilterTabs, { FilterTab } from "../TableFilter";
import SearchInput from "./SearchInput";
export interface SearchBarProps extends SearchPageProps, TabPageProps {
allTabLabel: string;
@ -47,7 +47,7 @@ const SearchBar: React.FC<SearchBarProps> = props => {
/>
)}
</FilterTabs>
<FilterSearch
<SearchInput
displaySearchAction={
!!initialSearch ? (isCustom ? "save" : "delete") : null
}

View file

@ -8,7 +8,7 @@ import { FilterActionsOnlySearch } from "../Filter/FilterActions";
import Hr from "../Hr";
import Link from "../Link";
export interface FilterSearchProps extends SearchPageProps {
export interface SearchInputProps extends SearchPageProps {
displaySearchAction: "save" | "delete" | null;
searchPlaceholder: string;
onSearchDelete?: () => void;
@ -29,11 +29,11 @@ const useStyles = makeStyles(
}
}),
{
name: "FilterSearch"
name: "SearchInput"
}
);
const FilterSearch: React.FC<FilterSearchProps> = props => {
const SearchInput: React.FC<SearchInputProps> = props => {
const {
displaySearchAction,
initialSearch,
@ -93,5 +93,5 @@ const FilterSearch: React.FC<FilterSearchProps> = props => {
);
};
FilterSearch.displayName = "FilterSearch";
export default FilterSearch;
SearchInput.displayName = "SearchInput";
export default SearchInput;

View file

@ -8,6 +8,7 @@ import { makeStyles } from "@material-ui/core/styles";
import classNames from "classnames";
import React from "react";
import { FormattedMessage } from "react-intl";
import { InputProps } from "@material-ui/core/Input";
const useStyles = makeStyles(
theme => ({
@ -38,13 +39,13 @@ interface SingleSelectFieldProps {
selectProps?: SelectProps;
placeholder?: string;
value?: string;
InputProps?: InputProps;
onChange(event: any);
}
export const SingleSelectField: React.FC<SingleSelectFieldProps> = props => {
const {
className,
disabled,
error,
label,
@ -54,7 +55,8 @@ export const SingleSelectField: React.FC<SingleSelectFieldProps> = props => {
name,
hint,
selectProps,
placeholder
placeholder,
InputProps
} = props;
const classes = useStyles(props);
@ -90,6 +92,7 @@ export const SingleSelectField: React.FC<SingleSelectFieldProps> = props => {
}}
name={name}
labelWidth={180}
{...InputProps}
/>
}
{...selectProps}

View file

@ -1,124 +1,92 @@
import { storiesOf } from "@storybook/react";
import React from "react";
import { FilterContent, FilterContentProps } from "@saleor/components/Filter";
import {
FieldType,
FilterContent,
FilterContentProps
} from "@saleor/components/Filter";
import CardDecorator from "../../CardDecorator";
createPriceField,
createDateField,
createOptionsField
} from "@saleor/utils/filters/fields";
import useFilter from "@saleor/components/Filter/useFilter";
import Decorator from "../../Decorator";
const props: FilterContentProps = {
currencySymbol: "USD",
filters: [
createPriceField("price", "Price", "USD", {
max: "100.00",
min: "20.00"
}),
{
children: [],
data: {
fieldLabel: "Category Name",
type: FieldType.text
},
label: "Category",
value: "category"
...createDateField("createdAt", "Created At", {
max: "2019-10-23",
min: "2019-09-09"
}),
active: true
},
{
children: [],
data: {
fieldLabel: "Product Type Name",
type: FieldType.text
},
label: "Product Type",
value: "product-type"
...createOptionsField("status", "Status", ["val1"], false, [
{
label: "Value 1",
value: "val1"
},
{
label: "Value 2",
value: "val2"
},
{
label: "Value 3",
value: "val3"
}
]),
active: true
},
{
children: [],
data: {
fieldLabel: "Status",
options: [
...createOptionsField(
"multiplOptions",
"Multiple Options",
["val1", "val2"],
true,
[
{
label: "Published",
value: true
label: "Value 1",
value: "val1"
},
{
label: "Hidden",
value: false
label: "Value 2",
value: "val2"
},
{
label: "Value 3",
value: "val3"
}
],
type: FieldType.select
},
label: "Published",
value: "published"
},
{
children: [],
data: {
fieldLabel: "Stock",
type: FieldType.range
},
label: "Stock",
value: "stock"
},
{
children: [
{
children: [],
data: {
fieldLabel: "Equal to",
type: FieldType.date
},
label: "Equal to",
value: "date-equal"
},
{
children: [],
data: {
fieldLabel: "Range",
type: FieldType.rangeDate
},
label: "Range",
value: "date-range"
}
],
data: {
fieldLabel: "Date",
type: FieldType.select
},
label: "Date",
value: "date"
},
{
children: [
{
children: [],
data: {
fieldLabel: "Exactly",
type: FieldType.price
},
label: "Exactly",
value: "price-exactly"
},
{
children: [],
data: {
fieldLabel: "Range",
type: FieldType.rangePrice
},
label: "Range",
value: "price-range"
}
],
data: {
fieldLabel: "Price",
type: FieldType.select
},
label: "Price",
value: "price"
]
),
active: false
}
],
onClear: () => undefined,
onFilterPropertyChange: () => undefined,
onSubmit: () => undefined
};
const InteractiveStory: React.FC = () => {
const [data, dispatchFilterActions, clear] = useFilter(props.filters);
return (
<FilterContent
{...props}
filters={data}
onClear={clear}
onFilterPropertyChange={dispatchFilterActions}
/>
);
};
storiesOf("Generics / Filter", module)
.addDecorator(CardDecorator)
.addDecorator(storyFn => (
<div style={{ margin: "auto", width: 400 }}>{storyFn()}</div>
))
.addDecorator(Decorator)
.add("default", () => <FilterContent {...props} />);
.add("default", () => <FilterContent {...props} />)
.add("interactive", () => <InteractiveStory />);

View file

@ -330,6 +330,7 @@ export default (colors: IThemeColors): Theme =>
}
}
},
backgroundColor: colors.background.paper,
borderColor: colors.input.border,
top: 0
}

View file

@ -4,9 +4,8 @@ import { IntlShape, useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader";
import Container from "@saleor/components/Container";
import FilterSearch from "@saleor/components/Filter/FilterSearch";
import SearchInput from "@saleor/components/SearchBar/SearchInput";
import PageHeader from "@saleor/components/PageHeader";
// tslint:disable no-submodule-imports
import { ShopInfo_shop_languages } from "@saleor/components/Shop/types/ShopInfo";
import FilterTabs, { FilterTab } from "@saleor/components/TableFilter";
import { maybe } from "@saleor/misc";
@ -88,9 +87,13 @@ const tabs: TranslationsEntitiesListFilterTab[] = [
"productTypes"
];
const TranslationsEntitiesListPage: React.FC<
TranslationsEntitiesListPageProps
> = ({ filters, language, onBack, children, ...searchProps }) => {
const TranslationsEntitiesListPage: React.FC<TranslationsEntitiesListPageProps> = ({
filters,
language,
onBack,
children,
...searchProps
}) => {
const intl = useIntl();
const currentTab = tabs.indexOf(filters.current);
@ -157,7 +160,7 @@ const TranslationsEntitiesListPage: React.FC<
onClick={filters.onProductTypesTabClick}
/>
</FilterTabs>
<FilterSearch
<SearchInput
displaySearchAction={null}
searchPlaceholder={getSearchPlaceholder(filters.current, intl)}
{...searchProps}

View file

@ -0,0 +1,69 @@
import { IFilterElement, FieldType } from "@saleor/components/Filter";
import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField";
type MinMax = Record<"min" | "max", string>;
export function createPriceField<T extends string>(
name: T,
label: string,
currencySymbol: string,
defaultValue: MinMax
): IFilterElement<T> {
return {
active: false,
currencySymbol,
label,
multiple: true,
name,
type: FieldType.price,
value: [defaultValue.min, defaultValue.max]
};
}
export function createDateField<T extends string>(
name: T,
label: string,
defaultValue: MinMax
): IFilterElement<T> {
return {
active: false,
label,
multiple: true,
name,
type: FieldType.date,
value: [defaultValue.min, defaultValue.max]
};
}
export function createNumberField<T extends string>(
name: T,
label: string,
defaultValue: MinMax
): IFilterElement<T> {
return {
active: false,
label,
multiple: true,
name,
type: FieldType.number,
value: [defaultValue.min, defaultValue.max]
};
}
export function createOptionsField<T extends string>(
name: T,
label: string,
defaultValue: string[],
multiple: boolean,
options: MultiAutocompleteChoiceType[]
): IFilterElement<T> {
return {
active: false,
label,
multiple,
name,
options,
type: FieldType.options,
value: defaultValue
};
}

View file

@ -5,13 +5,10 @@ function createFilterUtils<
function getActiveFilters(params: TQueryParams): TFilters {
return Object.keys(params)
.filter(key => Object.keys(filters).includes(key))
.reduce(
(acc, key) => {
acc[key] = params[key];
return acc;
},
{} as any
);
.reduce((acc, key) => {
acc[key] = params[key];
return acc;
}, {} as any);
}
function areFiltersApplied(params: TQueryParams): boolean {
@ -24,30 +21,6 @@ function createFilterUtils<
};
}
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));
}