Refactor filter components
This commit is contained in:
parent
d58b1046ff
commit
87b94f47e1
16 changed files with 626 additions and 286 deletions
22
src/components/Filter/Arrow.tsx
Normal file
22
src/components/Filter/Arrow.tsx
Normal 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;
|
|
@ -1,6 +1,5 @@
|
||||||
import ButtonBase from "@material-ui/core/ButtonBase";
|
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||||
import Grow from "@material-ui/core/Grow";
|
import Grow from "@material-ui/core/Grow";
|
||||||
import Paper from "@material-ui/core/Paper";
|
|
||||||
import Popper from "@material-ui/core/Popper";
|
import Popper from "@material-ui/core/Popper";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import { fade } from "@material-ui/core/styles/colorManipulator";
|
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||||
|
@ -17,7 +16,6 @@ import { FilterContent } from ".";
|
||||||
export interface FilterProps<TFilterKeys = string> {
|
export interface FilterProps<TFilterKeys = string> {
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
menu: IFilter<TFilterKeys>;
|
menu: IFilter<TFilterKeys>;
|
||||||
filterLabel: string;
|
|
||||||
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
onFilterAdd: (filter: FilterContentSubmitData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +80,7 @@ const useStyles = makeStyles(
|
||||||
{ name: "Filter" }
|
{ name: "Filter" }
|
||||||
);
|
);
|
||||||
const Filter: React.FC<FilterProps> = props => {
|
const Filter: React.FC<FilterProps> = props => {
|
||||||
const { currencySymbol, filterLabel, menu, onFilterAdd } = props;
|
const { currencySymbol, menu, onFilterAdd } = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
const anchor = React.useRef<HTMLDivElement>();
|
const anchor = React.useRef<HTMLDivElement>();
|
||||||
|
@ -122,8 +120,6 @@ const Filter: React.FC<FilterProps> = props => {
|
||||||
placement === "bottom" ? "right top" : "right bottom"
|
placement === "bottom" ? "right top" : "right bottom"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paper className={classes.paper}>
|
|
||||||
<Typography>{filterLabel}</Typography>
|
|
||||||
<FilterContent
|
<FilterContent
|
||||||
currencySymbol={currencySymbol}
|
currencySymbol={currencySymbol}
|
||||||
filters={menu}
|
filters={menu}
|
||||||
|
@ -132,7 +128,6 @@ const Filter: React.FC<FilterProps> = props => {
|
||||||
setFilterMenuOpened(false);
|
setFilterMenuOpened(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
|
||||||
</Grow>
|
</Grow>
|
||||||
)}
|
)}
|
||||||
</Popper>
|
</Popper>
|
||||||
|
|
|
@ -56,13 +56,10 @@ export interface FilterActionsPropsSearch {
|
||||||
export interface FilterActionsPropsFilters<TKeys = string> {
|
export interface FilterActionsPropsFilters<TKeys = string> {
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
menu: IFilter<TKeys>;
|
menu: IFilter<TKeys>;
|
||||||
filterLabel: string;
|
|
||||||
onFilterAdd: (filter: FilterContentSubmitData<TKeys>) => void;
|
onFilterAdd: (filter: FilterContentSubmitData<TKeys>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FilterActionsOnlySearch: React.FC<
|
export const FilterActionsOnlySearch: React.FC<FilterActionsPropsSearch> = props => {
|
||||||
FilterActionsPropsSearch
|
|
||||||
> = props => {
|
|
||||||
const { onSearchChange, placeholder, search } = props;
|
const { onSearchChange, placeholder, search } = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
@ -83,7 +80,6 @@ export type FilterActionsProps = FilterActionsPropsSearch &
|
||||||
const FilterActions: React.FC<FilterActionsProps> = props => {
|
const FilterActions: React.FC<FilterActionsProps> = props => {
|
||||||
const {
|
const {
|
||||||
currencySymbol,
|
currencySymbol,
|
||||||
filterLabel,
|
|
||||||
menu,
|
menu,
|
||||||
onFilterAdd,
|
onFilterAdd,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
|
@ -97,7 +93,6 @@ const FilterActions: React.FC<FilterActionsProps> = props => {
|
||||||
<Filter
|
<Filter
|
||||||
currencySymbol={currencySymbol}
|
currencySymbol={currencySymbol}
|
||||||
menu={menu}
|
menu={menu}
|
||||||
filterLabel={filterLabel}
|
|
||||||
onFilterAdd={onFilterAdd}
|
onFilterAdd={onFilterAdd}
|
||||||
/>
|
/>
|
||||||
<Search
|
<Search
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
|
import Paper from "@material-ui/core/Paper";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import FormControlLabel from "@material-ui/core/FormControlLabel";
|
||||||
import React from "react";
|
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 makeStyles from "@material-ui/core/styles/makeStyles";
|
||||||
import { getMenuItemByValue, isLeaf, walkToRoot } from "../../utils/menu";
|
import { fade } from "@material-ui/core/styles/colorManipulator";
|
||||||
import FormSpacer from "../FormSpacer";
|
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 SingleSelectField from "../SingleSelectField";
|
||||||
import FilterElement from "./FilterElement";
|
import { SingleAutocompleteChoiceType } from "../SingleAutocompleteSelectField";
|
||||||
import { IFilter } from "./types";
|
import FormSpacer from "../FormSpacer";
|
||||||
|
import { IFilter, FieldType, FilterType } from "./types";
|
||||||
|
import Arrow from "./Arrow";
|
||||||
|
import { FilterReducerAction } from "./reducer";
|
||||||
|
|
||||||
export interface FilterContentSubmitData<TKeys = string> {
|
export interface FilterContentSubmitData<TKeys = string> {
|
||||||
name: TKeys;
|
name: TKeys;
|
||||||
value: string | string[];
|
value: string[];
|
||||||
}
|
}
|
||||||
export interface FilterContentProps {
|
export interface FilterContentProps<T extends string = string> {
|
||||||
currencySymbol: string;
|
currencySymbol: string;
|
||||||
filters: IFilter<string>;
|
filters: IFilter<T>;
|
||||||
onSubmit: (data: FilterContentSubmitData) => void;
|
onFilterPropertyChange: React.Dispatch<FilterReducerAction<T>>;
|
||||||
|
onClear: () => void;
|
||||||
|
onSubmit: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFilterValue(value: string | string[]): boolean {
|
function checkFilterValue(value: string[]): boolean {
|
||||||
if (typeof value === "string") {
|
|
||||||
return !!value;
|
|
||||||
}
|
|
||||||
return value.some(v => !!v);
|
return value.some(v => !!v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,113 +43,333 @@ function getFilterChoices(items: IFilter<string>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
{
|
theme => ({
|
||||||
input: {
|
actionBar: {
|
||||||
padding: "20px 12px 17px"
|
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: "12px 0 9px 12px"
|
||||||
|
},
|
||||||
|
inputRange: {
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex"
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontWeight: 600
|
||||||
|
},
|
||||||
|
option: {
|
||||||
|
left: -theme.spacing(0.5),
|
||||||
|
position: "relative"
|
||||||
|
}
|
||||||
|
}),
|
||||||
{ name: "FilterContent" }
|
{ 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> = ({
|
const FilterContent: React.FC<FilterContentProps> = ({
|
||||||
currencySymbol,
|
currencySymbol,
|
||||||
filters,
|
filters,
|
||||||
|
onClear,
|
||||||
|
onFilterPropertyChange,
|
||||||
onSubmit
|
onSubmit
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [menuValue, setMenuValue] = React.useState<string>(null);
|
|
||||||
const [filterValue, setFilterValue] = React.useState<string | string[]>("");
|
|
||||||
const classes = useStyles({});
|
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 (
|
return (
|
||||||
<>
|
<Paper>
|
||||||
<SingleSelectField
|
<form
|
||||||
choices={getFilterChoices(filters)}
|
onSubmit={event => {
|
||||||
onChange={onMenuChange}
|
event.preventDefault();
|
||||||
selectProps={{
|
onSubmit();
|
||||||
classes: {
|
|
||||||
selectMenu: classes.input
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
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 />
|
<div className={classes.actionBar}>
|
||||||
<SingleSelectField
|
<Typography className={classes.label}>
|
||||||
choices={getFilterChoices(filterItem.children)}
|
<FormattedMessage defaultMessage="Filters" />
|
||||||
onChange={onMenuChange}
|
</Typography>
|
||||||
selectProps={{
|
<div>
|
||||||
classes: {
|
<Button className={classes.clear} onClick={onClear}>
|
||||||
selectMenu: classes.input
|
<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
|
||||||
}
|
}
|
||||||
}}
|
},
|
||||||
value={
|
type: "set-property"
|
||||||
filterItemIndex === menus.length - 1
|
|
||||||
? menuValue.toString()
|
|
||||||
: menus[filterItemIndex - 1].label.toString()
|
|
||||||
}
|
|
||||||
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";
|
FilterContent.displayName = "FilterContent";
|
||||||
|
|
0
src/components/Filter/reducer.test.ts
Normal file
0
src/components/Filter/reducer.test.ts
Normal file
43
src/components/Filter/reducer.ts
Normal file
43
src/components/Filter/reducer.ts
Normal 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;
|
|
@ -1,30 +1,33 @@
|
||||||
import { IMenu, IMenuItem } from "../../utils/menu";
|
import { FetchMoreProps } from "@saleor/types";
|
||||||
|
import { MultiAutocompleteChoiceType } from "../MultiAutocompleteSelectField";
|
||||||
|
|
||||||
export enum FieldType {
|
export enum FieldType {
|
||||||
date,
|
date,
|
||||||
hidden,
|
|
||||||
number,
|
number,
|
||||||
price,
|
price,
|
||||||
range,
|
options,
|
||||||
rangeDate,
|
|
||||||
rangePrice,
|
|
||||||
select,
|
|
||||||
text
|
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;
|
label: string;
|
||||||
value: string | boolean;
|
name: T;
|
||||||
}
|
|
||||||
|
|
||||||
export interface FilterData {
|
|
||||||
additionalText?: string;
|
|
||||||
fieldLabel: string;
|
|
||||||
options?: FilterChoice[];
|
|
||||||
type: FieldType;
|
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"
|
||||||
|
}
|
||||||
|
|
37
src/components/Filter/useFilter.ts
Normal file
37
src/components/Filter/useFilter.ts
Normal 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;
|
|
@ -2,8 +2,8 @@ import React from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { SearchPageProps, TabPageProps } from "@saleor/types";
|
import { SearchPageProps, TabPageProps } from "@saleor/types";
|
||||||
import FilterSearch from "../Filter/FilterSearch";
|
|
||||||
import FilterTabs, { FilterTab } from "../TableFilter";
|
import FilterTabs, { FilterTab } from "../TableFilter";
|
||||||
|
import SearchInput from "./SearchInput";
|
||||||
|
|
||||||
export interface SearchBarProps extends SearchPageProps, TabPageProps {
|
export interface SearchBarProps extends SearchPageProps, TabPageProps {
|
||||||
allTabLabel: string;
|
allTabLabel: string;
|
||||||
|
@ -47,7 +47,7 @@ const SearchBar: React.FC<SearchBarProps> = props => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</FilterTabs>
|
</FilterTabs>
|
||||||
<FilterSearch
|
<SearchInput
|
||||||
displaySearchAction={
|
displaySearchAction={
|
||||||
!!initialSearch ? (isCustom ? "save" : "delete") : null
|
!!initialSearch ? (isCustom ? "save" : "delete") : null
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { FilterActionsOnlySearch } from "../Filter/FilterActions";
|
||||||
import Hr from "../Hr";
|
import Hr from "../Hr";
|
||||||
import Link from "../Link";
|
import Link from "../Link";
|
||||||
|
|
||||||
export interface FilterSearchProps extends SearchPageProps {
|
export interface SearchInputProps extends SearchPageProps {
|
||||||
displaySearchAction: "save" | "delete" | null;
|
displaySearchAction: "save" | "delete" | null;
|
||||||
searchPlaceholder: string;
|
searchPlaceholder: string;
|
||||||
onSearchDelete?: () => void;
|
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 {
|
const {
|
||||||
displaySearchAction,
|
displaySearchAction,
|
||||||
initialSearch,
|
initialSearch,
|
||||||
|
@ -93,5 +93,5 @@ const FilterSearch: React.FC<FilterSearchProps> = props => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
FilterSearch.displayName = "FilterSearch";
|
SearchInput.displayName = "SearchInput";
|
||||||
export default FilterSearch;
|
export default SearchInput;
|
|
@ -8,6 +8,7 @@ import { makeStyles } from "@material-ui/core/styles";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { InputProps } from "@material-ui/core/Input";
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
theme => ({
|
theme => ({
|
||||||
|
@ -38,13 +39,13 @@ interface SingleSelectFieldProps {
|
||||||
selectProps?: SelectProps;
|
selectProps?: SelectProps;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
InputProps?: InputProps;
|
||||||
onChange(event: any);
|
onChange(event: any);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SingleSelectField: React.FC<SingleSelectFieldProps> = props => {
|
export const SingleSelectField: React.FC<SingleSelectFieldProps> = props => {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
|
|
||||||
disabled,
|
disabled,
|
||||||
error,
|
error,
|
||||||
label,
|
label,
|
||||||
|
@ -54,7 +55,8 @@ export const SingleSelectField: React.FC<SingleSelectFieldProps> = props => {
|
||||||
name,
|
name,
|
||||||
hint,
|
hint,
|
||||||
selectProps,
|
selectProps,
|
||||||
placeholder
|
placeholder,
|
||||||
|
InputProps
|
||||||
} = props;
|
} = props;
|
||||||
const classes = useStyles(props);
|
const classes = useStyles(props);
|
||||||
|
|
||||||
|
@ -90,6 +92,7 @@ export const SingleSelectField: React.FC<SingleSelectFieldProps> = props => {
|
||||||
}}
|
}}
|
||||||
name={name}
|
name={name}
|
||||||
labelWidth={180}
|
labelWidth={180}
|
||||||
|
{...InputProps}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{...selectProps}
|
{...selectProps}
|
||||||
|
|
|
@ -1,124 +1,92 @@
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { FilterContent, FilterContentProps } from "@saleor/components/Filter";
|
||||||
import {
|
import {
|
||||||
FieldType,
|
createPriceField,
|
||||||
FilterContent,
|
createDateField,
|
||||||
FilterContentProps
|
createOptionsField
|
||||||
} from "@saleor/components/Filter";
|
} from "@saleor/utils/filters/fields";
|
||||||
import CardDecorator from "../../CardDecorator";
|
import useFilter from "@saleor/components/Filter/useFilter";
|
||||||
import Decorator from "../../Decorator";
|
import Decorator from "../../Decorator";
|
||||||
|
|
||||||
const props: FilterContentProps = {
|
const props: FilterContentProps = {
|
||||||
currencySymbol: "USD",
|
currencySymbol: "USD",
|
||||||
filters: [
|
filters: [
|
||||||
|
createPriceField("price", "Price", "USD", {
|
||||||
|
max: "100.00",
|
||||||
|
min: "20.00"
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
children: [],
|
...createDateField("createdAt", "Created At", {
|
||||||
data: {
|
max: "2019-10-23",
|
||||||
fieldLabel: "Category Name",
|
min: "2019-09-09"
|
||||||
type: FieldType.text
|
}),
|
||||||
},
|
active: true
|
||||||
label: "Category",
|
|
||||||
value: "category"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
...createOptionsField("status", "Status", ["val1"], false, [
|
||||||
data: {
|
{
|
||||||
fieldLabel: "Product Type Name",
|
label: "Value 1",
|
||||||
type: FieldType.text
|
value: "val1"
|
||||||
},
|
|
||||||
label: "Product Type",
|
|
||||||
value: "product-type"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: [],
|
label: "Value 2",
|
||||||
data: {
|
value: "val2"
|
||||||
fieldLabel: "Status",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: "Published",
|
|
||||||
value: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Hidden",
|
label: "Value 3",
|
||||||
value: false
|
value: "val3"
|
||||||
}
|
}
|
||||||
],
|
]),
|
||||||
type: FieldType.select
|
active: true
|
||||||
},
|
},
|
||||||
label: "Published",
|
{
|
||||||
value: "published"
|
...createOptionsField(
|
||||||
},
|
"multiplOptions",
|
||||||
{
|
"Multiple Options",
|
||||||
children: [],
|
["val1", "val2"],
|
||||||
data: {
|
true,
|
||||||
fieldLabel: "Stock",
|
[
|
||||||
type: FieldType.range
|
{
|
||||||
},
|
label: "Value 1",
|
||||||
label: "Stock",
|
value: "val1"
|
||||||
value: "stock"
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Value 2",
|
||||||
children: [
|
value: "val2"
|
||||||
{
|
},
|
||||||
children: [],
|
{
|
||||||
data: {
|
label: "Value 3",
|
||||||
fieldLabel: "Equal to",
|
value: "val3"
|
||||||
type: FieldType.date
|
}
|
||||||
},
|
]
|
||||||
label: "Equal to",
|
),
|
||||||
value: "date-equal"
|
active: false
|
||||||
},
|
|
||||||
{
|
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
onClear: () => undefined,
|
||||||
|
onFilterPropertyChange: () => undefined,
|
||||||
onSubmit: () => 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)
|
storiesOf("Generics / Filter", module)
|
||||||
.addDecorator(CardDecorator)
|
.addDecorator(storyFn => (
|
||||||
|
<div style={{ margin: "auto", width: 400 }}>{storyFn()}</div>
|
||||||
|
))
|
||||||
.addDecorator(Decorator)
|
.addDecorator(Decorator)
|
||||||
.add("default", () => <FilterContent {...props} />);
|
.add("default", () => <FilterContent {...props} />)
|
||||||
|
.add("interactive", () => <InteractiveStory />);
|
||||||
|
|
|
@ -330,6 +330,7 @@ export default (colors: IThemeColors): Theme =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
backgroundColor: colors.background.paper,
|
||||||
borderColor: colors.input.border,
|
borderColor: colors.input.border,
|
||||||
top: 0
|
top: 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,8 @@ import { IntlShape, useIntl } from "react-intl";
|
||||||
|
|
||||||
import AppHeader from "@saleor/components/AppHeader";
|
import AppHeader from "@saleor/components/AppHeader";
|
||||||
import Container from "@saleor/components/Container";
|
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";
|
import PageHeader from "@saleor/components/PageHeader";
|
||||||
// tslint:disable no-submodule-imports
|
|
||||||
import { ShopInfo_shop_languages } from "@saleor/components/Shop/types/ShopInfo";
|
import { ShopInfo_shop_languages } from "@saleor/components/Shop/types/ShopInfo";
|
||||||
import FilterTabs, { FilterTab } from "@saleor/components/TableFilter";
|
import FilterTabs, { FilterTab } from "@saleor/components/TableFilter";
|
||||||
import { maybe } from "@saleor/misc";
|
import { maybe } from "@saleor/misc";
|
||||||
|
@ -88,9 +87,13 @@ const tabs: TranslationsEntitiesListFilterTab[] = [
|
||||||
"productTypes"
|
"productTypes"
|
||||||
];
|
];
|
||||||
|
|
||||||
const TranslationsEntitiesListPage: React.FC<
|
const TranslationsEntitiesListPage: React.FC<TranslationsEntitiesListPageProps> = ({
|
||||||
TranslationsEntitiesListPageProps
|
filters,
|
||||||
> = ({ filters, language, onBack, children, ...searchProps }) => {
|
language,
|
||||||
|
onBack,
|
||||||
|
children,
|
||||||
|
...searchProps
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const currentTab = tabs.indexOf(filters.current);
|
const currentTab = tabs.indexOf(filters.current);
|
||||||
|
|
||||||
|
@ -157,7 +160,7 @@ const TranslationsEntitiesListPage: React.FC<
|
||||||
onClick={filters.onProductTypesTabClick}
|
onClick={filters.onProductTypesTabClick}
|
||||||
/>
|
/>
|
||||||
</FilterTabs>
|
</FilterTabs>
|
||||||
<FilterSearch
|
<SearchInput
|
||||||
displaySearchAction={null}
|
displaySearchAction={null}
|
||||||
searchPlaceholder={getSearchPlaceholder(filters.current, intl)}
|
searchPlaceholder={getSearchPlaceholder(filters.current, intl)}
|
||||||
{...searchProps}
|
{...searchProps}
|
||||||
|
|
69
src/utils/filters/fields.ts
Normal file
69
src/utils/filters/fields.ts
Normal 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
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,13 +5,10 @@ function createFilterUtils<
|
||||||
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))
|
||||||
.reduce(
|
.reduce((acc, key) => {
|
||||||
(acc, key) => {
|
|
||||||
acc[key] = params[key];
|
acc[key] = params[key];
|
||||||
return acc;
|
return acc;
|
||||||
},
|
}, {} as any);
|
||||||
{} as any
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function areFiltersApplied(params: TQueryParams): boolean {
|
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[] {
|
export function dedupeFilter<T>(array: T[]): T[] {
|
||||||
return Array.from(new Set(array));
|
return Array.from(new Set(array));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue