change(ui) - search optimization and autocomplete improvements
This commit is contained in:
parent
de8eefdffc
commit
e3ead3ebb1
5 changed files with 464 additions and 472 deletions
|
|
@ -1,81 +1,61 @@
|
|||
.wrapper {
|
||||
border: solid thin $gray-light !important;
|
||||
border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
& input {
|
||||
height: 24px;
|
||||
font-size: 13px !important;
|
||||
padding: 0 5px !important;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border: solid thin transparent !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
& .right {
|
||||
height: 24px;
|
||||
border: solid thin $gray-light !important;
|
||||
border-radius: 3px;
|
||||
background-color: white !important;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
background-color: $gray-lightest;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
align-items: center;
|
||||
height: 26px;
|
||||
width: 100%;
|
||||
|
||||
& div {
|
||||
/* background-color: red; */
|
||||
border-left: solid thin $gray-light !important;
|
||||
width: 28px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:last-child {
|
||||
& .right {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
background-color: $gray-lightest;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
margin-left: auto;
|
||||
|
||||
& div {
|
||||
/* background-color: red; */
|
||||
border-left: solid thin $gray-light !important;
|
||||
width: 28px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:last-child {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
border-radius: 0 0 3px 3px;
|
||||
border: solid thin $gray-light !important;
|
||||
box-shadow: 0 2px 2px 0 $gray-light;
|
||||
min-height: 50px;
|
||||
background-color: white;
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 0;
|
||||
width: 400px;
|
||||
z-index: 99;
|
||||
.operatorDropdown {
|
||||
font-weight: 400;
|
||||
/* height: 30px; */
|
||||
min-width: 60px;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8px !important;
|
||||
font-size: 13px;
|
||||
height: 26px;
|
||||
/* background-color: rgba(255, 255, 255, 0.8) !important; */
|
||||
/* background-color: $gray-lightest !important; */
|
||||
/* border: solid thin rgba(34, 36, 38, 0.15) !important; */
|
||||
/* border-radius: 4px !important; */
|
||||
color: $gray-darkest !important;
|
||||
font-size: 14px !important;
|
||||
&.ui.basic.button {
|
||||
box-shadow: 0 0 0 1px rgba(62, 170, 175, 36, 38, 0.35) inset, 0 0 0 0 rgba(62, 170, 175, 0.15) inset !important;
|
||||
}
|
||||
/*
|
||||
& input {
|
||||
padding: 0 8px !important;
|
||||
} */
|
||||
}
|
||||
|
||||
.filterItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
/* transition: all 0.4s; */
|
||||
margin-bottom: 5px;
|
||||
max-width: 100%;
|
||||
& .label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $gray-lightest;
|
||||
/* transition: all 0.2s; */
|
||||
}
|
||||
}
|
||||
|
|
@ -1,155 +1,178 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Icon, Loader } from 'UI';
|
||||
import { Icon } from 'UI';
|
||||
import APIClient from 'App/api_client';
|
||||
import { debounce } from 'App/utils';
|
||||
import stl from './FilterAutoComplete.module.css';
|
||||
import cn from 'classnames';
|
||||
import { components, DropdownIndicatorProps } from 'react-select';
|
||||
import AsyncCreatableSelect from 'react-select/async-creatable';
|
||||
|
||||
const hiddenStyle = {
|
||||
whiteSpace: 'pre-wrap',
|
||||
opacity: 0, position: 'fixed', left: '-3000px'
|
||||
const dropdownStyles = {
|
||||
control: (provided: any) => {
|
||||
const obj = {
|
||||
...provided,
|
||||
border: 'solid thin transparent !important',
|
||||
backgroundColor: 'transparent',
|
||||
cursor: 'pointer',
|
||||
height: '26px',
|
||||
minHeight: '26px',
|
||||
borderRadius: '3px',
|
||||
boxShadow: 'none !important',
|
||||
};
|
||||
return obj;
|
||||
},
|
||||
valueContainer: (provided: any) => ({
|
||||
...provided,
|
||||
// paddingRight: '0px',
|
||||
width: 'fit-content',
|
||||
alignItems: 'center',
|
||||
height: '26px',
|
||||
padding: '0 3px',
|
||||
}),
|
||||
// placeholder: (provided: any) => ({
|
||||
// ...provided,
|
||||
// }),
|
||||
indicatorsContainer: (provided: any) => ({
|
||||
...provided,
|
||||
padding: '0px',
|
||||
height: '26px',
|
||||
}),
|
||||
option: (provided: any, state: any) => ({
|
||||
...provided,
|
||||
whiteSpace: 'nowrap',
|
||||
}),
|
||||
menu: (provided: any, state: any) => ({
|
||||
...provided,
|
||||
top: 20,
|
||||
left: 0,
|
||||
minWidth: 'fit-content',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
container: (provided: any) => ({
|
||||
...provided,
|
||||
width: '100%',
|
||||
}),
|
||||
input: (provided: any) => ({
|
||||
...provided,
|
||||
height: '22px',
|
||||
'& input:focus': {
|
||||
border: 'none !important',
|
||||
},
|
||||
}),
|
||||
singleValue: (provided: any, state: { isDisabled: any }) => {
|
||||
const opacity = state.isDisabled ? 0.5 : 1;
|
||||
const transition = 'opacity 300ms';
|
||||
|
||||
return {
|
||||
...provided,
|
||||
opacity,
|
||||
transition,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
height: '20px',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
interface Props {
|
||||
showOrButton?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
onRemoveValue?: () => void;
|
||||
onAddValue?: () => void;
|
||||
endpoint?: string;
|
||||
method?: string;
|
||||
params?: any;
|
||||
headerText?: string;
|
||||
placeholder?: string;
|
||||
onSelect: (e: any, item: any) => void;
|
||||
value: any;
|
||||
icon?: string;
|
||||
showOrButton?: boolean;
|
||||
showCloseButton?: boolean;
|
||||
onRemoveValue?: () => void;
|
||||
onAddValue?: () => void;
|
||||
endpoint?: string;
|
||||
method?: string;
|
||||
params?: any;
|
||||
headerText?: string;
|
||||
placeholder?: string;
|
||||
onSelect: (e: any, item: any) => void;
|
||||
value: any;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
function FilterAutoComplete(props: Props) {
|
||||
const {
|
||||
showCloseButton = false,
|
||||
placeholder = 'Type to search',
|
||||
method = 'GET',
|
||||
showOrButton = false,
|
||||
onRemoveValue = () => null,
|
||||
onAddValue = () => null,
|
||||
endpoint = '',
|
||||
params = {},
|
||||
headerText = '',
|
||||
value = '',
|
||||
icon = null,
|
||||
} = props;
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [options, setOptions] = useState<any>([]);
|
||||
const [query, setQuery] = useState(value);
|
||||
const {
|
||||
showCloseButton = false,
|
||||
placeholder = 'Type to search',
|
||||
method = 'GET',
|
||||
showOrButton = false,
|
||||
onRemoveValue = () => null,
|
||||
onAddValue = () => null,
|
||||
endpoint = '',
|
||||
params = {},
|
||||
value = '',
|
||||
} = props;
|
||||
const [options, setOptions] = useState<any>(value ? [{ label: value, value }] : []);
|
||||
const [query, setQuery] = useState(value);
|
||||
|
||||
const requestValues = (q: any) => {
|
||||
setLoading(true);
|
||||
useEffect(() => {
|
||||
setQuery(value);
|
||||
}, [value, options])
|
||||
|
||||
return new APIClient()[method?.toLocaleLowerCase()](endpoint, { ...params, q })
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error(response.statusText);
|
||||
})
|
||||
.then(({ data }: any) => {
|
||||
setOptions(data);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
|
||||
const debouncedRequestValues = React.useCallback(debounce(requestValues, 1000), [params]);
|
||||
|
||||
const onInputChange = ({ target: { value } }: any) => {
|
||||
setQuery(value);
|
||||
if (!showModal) {
|
||||
setShowModal(true);
|
||||
}
|
||||
|
||||
if (value === '' || value === ' ') {
|
||||
return
|
||||
}
|
||||
debouncedRequestValues(value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setQuery(value);
|
||||
}, [value])
|
||||
|
||||
const onBlur = (e) => {
|
||||
setTimeout(() => { setShowModal(false) }, 200)
|
||||
if (query !== value) {
|
||||
props.onSelect(e, { value: query })
|
||||
}
|
||||
}
|
||||
|
||||
const onItemClick = (e: any, item: any) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (query !== item.value) {
|
||||
setQuery(item.value);
|
||||
}
|
||||
|
||||
props.onSelect(e, item);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center">
|
||||
<div className={stl.wrapper}>
|
||||
<input
|
||||
name="query"
|
||||
onChange={ onInputChange }
|
||||
onBlur={ onBlur }
|
||||
value={ query }
|
||||
autoFocus={ true }
|
||||
type="text"
|
||||
placeholder={ placeholder }
|
||||
autoComplete="do-not-autofill-bad-chrome"
|
||||
// onPaste={(e) => {
|
||||
// const text = e.clipboardData.getData('Text');
|
||||
// // this.hiddenInput.value = text;
|
||||
// // pasted = true; // to use only the hidden input
|
||||
// } }
|
||||
/>
|
||||
<div
|
||||
className={stl.right}
|
||||
>
|
||||
{ showCloseButton && <div onClick={onRemoveValue}><Icon name="close" size="12" /></div> }
|
||||
{ showOrButton && <div onClick={onAddValue} className="color-teal"><span className="px-1">or</span></div> }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ !showOrButton && <div className="ml-3">or</div> }
|
||||
|
||||
{ showModal && (
|
||||
<div className={ stl.menu }>
|
||||
<Loader loading={loading} size="small">
|
||||
{ options.length === 0 ? (
|
||||
<div className="p-4 w-full">No results found!</div>
|
||||
) : (
|
||||
<div>
|
||||
{
|
||||
options.map((item: any, i: any) => (
|
||||
<div
|
||||
key={item.value + '_' + i}
|
||||
className={ cn(stl.filterItem) }
|
||||
id="filter-item" onClick={ (e) => onItemClick(e, item) }
|
||||
>
|
||||
{ icon && <Icon name={ icon } size="16" marginRight="8" /> }
|
||||
<span className={ stl.label }>{ item.value }</span>
|
||||
</div>
|
||||
))
|
||||
const loadOptions = (inputValue: string, callback: (options: []) => void) => {
|
||||
new APIClient()
|
||||
[method?.toLocaleLowerCase()](endpoint, { ...params, q: inputValue })
|
||||
.then((response: any) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</Loader>
|
||||
throw new Error(response.statusText);
|
||||
})
|
||||
.then(({ data }: any) => {
|
||||
const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || [];
|
||||
setOptions(_options);
|
||||
callback(_options);
|
||||
})
|
||||
};
|
||||
|
||||
const debouncedLoadOptions = React.useCallback(debounce(loadOptions, 1000), [params]);
|
||||
|
||||
const handleInputChange = (newValue: string) => {
|
||||
const inputValue = newValue.replace(/\W/g, '');
|
||||
setQuery(inputValue);
|
||||
return inputValue;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center">
|
||||
<div className={stl.wrapper}>
|
||||
<AsyncCreatableSelect
|
||||
cacheOptions
|
||||
defaultOptions={options}
|
||||
loadOptions={debouncedLoadOptions}
|
||||
onInputChange={handleInputChange}
|
||||
onChange={(obj: any) => props.onSelect(null, obj)}
|
||||
styles={dropdownStyles}
|
||||
placeholder={placeholder}
|
||||
value={value ? options.find((i: any) => i.value === query) : null}
|
||||
components={{
|
||||
IndicatorSeparator: () => null,
|
||||
DropdownIndicator,
|
||||
}}
|
||||
/>
|
||||
<div className={stl.right}>
|
||||
{showCloseButton && (
|
||||
<div onClick={props.onRemoveValue}>
|
||||
<Icon name="close" size="12" />
|
||||
</div>
|
||||
)}
|
||||
{showOrButton && (
|
||||
<div onClick={props.onAddValue} className="color-teal">
|
||||
<span className="px-1">or</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!showOrButton && <div className="ml-3">or</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterAutoComplete;
|
||||
|
||||
const DropdownIndicator = (props: DropdownIndicatorProps<true>) => {
|
||||
return (
|
||||
<components.DropdownIndicator {...props}>
|
||||
<Icon name="chevron-down" size="16" />
|
||||
</components.DropdownIndicator>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,107 +8,107 @@ import { FilterKey, FilterType } from 'App/types/filter/filterType';
|
|||
import SubFilterItem from '../SubFilterItem';
|
||||
|
||||
interface Props {
|
||||
filterIndex: number;
|
||||
filter: any; // event/filter
|
||||
onUpdate: (filter) => void;
|
||||
onRemoveFilter: () => void;
|
||||
isFilter?: boolean;
|
||||
saveRequestPayloads?: boolean;
|
||||
filterIndex: number;
|
||||
filter: any; // event/filter
|
||||
onUpdate: (filter) => void;
|
||||
onRemoveFilter: () => void;
|
||||
isFilter?: boolean;
|
||||
saveRequestPayloads?: boolean;
|
||||
}
|
||||
function FilterItem(props: Props) {
|
||||
const { isFilter = false, filterIndex, filter, saveRequestPayloads } = props;
|
||||
const canShowValues = !(filter.operator === "isAny" || filter.operator === "onAny" || filter.operator === "isUndefined");
|
||||
const isSubFilter = filter.type === FilterType.SUB_FILTERS;
|
||||
const { isFilter = false, filterIndex, filter, saveRequestPayloads } = props;
|
||||
const canShowValues = !(filter.operator === 'isAny' || filter.operator === 'onAny' || filter.operator === 'isUndefined');
|
||||
const isSubFilter = filter.type === FilterType.SUB_FILTERS;
|
||||
|
||||
const replaceFilter = (filter) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
value: [""],
|
||||
filters: filter.filters ? filter.filters.map(i => ({ ...i, value: [""] })) : []
|
||||
});
|
||||
};
|
||||
const replaceFilter = (filter) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
value: [''],
|
||||
filters: filter.filters ? filter.filters.map((i) => ({ ...i, value: [''] })) : [],
|
||||
});
|
||||
};
|
||||
|
||||
const onOperatorChange = (e, { name, value }) => {
|
||||
props.onUpdate({ ...filter, operator: value.value })
|
||||
}
|
||||
|
||||
const onSourceOperatorChange = (e, { name, value }) => {
|
||||
props.onUpdate({ ...filter, sourceOperator: value.value })
|
||||
}
|
||||
const onOperatorChange = (e, { name, value }) => {
|
||||
props.onUpdate({ ...filter, operator: value.value });
|
||||
};
|
||||
|
||||
const onUpdateSubFilter = (subFilter, subFilterIndex) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
filters: filter.filters.map((i, index) => {
|
||||
if (index === subFilterIndex) {
|
||||
return subFilter;
|
||||
}
|
||||
return i;
|
||||
})
|
||||
});
|
||||
};
|
||||
const onSourceOperatorChange = (e, { name, value }) => {
|
||||
props.onUpdate({ ...filter, sourceOperator: value.value });
|
||||
};
|
||||
|
||||
const onUpdateSubFilter = (subFilter, subFilterIndex) => {
|
||||
props.onUpdate({
|
||||
...filter,
|
||||
filters: filter.filters.map((i, index) => {
|
||||
if (index === subFilterIndex) {
|
||||
return subFilter;
|
||||
}
|
||||
return i;
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5 py-2">
|
||||
<div className="flex items-start w-full">
|
||||
{ !isFilter && <div className="mt-1 flex-shrink-0 border w-6 h-6 text-xs flex items-center justify-center rounded-full bg-gray-light-shade mr-2">
|
||||
<span>{filterIndex+1}</span>
|
||||
</div> }
|
||||
<FilterSelection filter={filter} onFilterClick={replaceFilter} />
|
||||
|
||||
{/* Filter with Source */}
|
||||
{ filter.hasSource && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.sourceOperatorOptions}
|
||||
onChange={onSourceOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.sourceOperator}
|
||||
isDisabled={filter.operatorDisabled}
|
||||
/>
|
||||
<FilterSource filter={filter} onUpdate={props.onUpdate} />
|
||||
</>
|
||||
)}
|
||||
return (
|
||||
<div className="flex items-center hover:bg-active-blue -mx-5 px-5 py-2">
|
||||
<div className="flex items-start w-full">
|
||||
{!isFilter && (
|
||||
<div className="mt-1 flex-shrink-0 border w-6 h-6 text-xs flex items-center justify-center rounded-full bg-gray-light-shade mr-2">
|
||||
<span>{filterIndex + 1}</span>
|
||||
</div>
|
||||
)}
|
||||
<FilterSelection filter={filter} onFilterClick={replaceFilter} />
|
||||
|
||||
{/* Filter values */}
|
||||
{ !isSubFilter && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.operator}
|
||||
isDisabled={filter.operatorDisabled}
|
||||
/>
|
||||
{ canShowValues && (<FilterValue filter={filter} onUpdate={props.onUpdate} />) }
|
||||
</>
|
||||
)}
|
||||
{/* Filter with Source */}
|
||||
{filter.hasSource && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.sourceOperatorOptions}
|
||||
onChange={onSourceOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.sourceOperator}
|
||||
isDisabled={filter.operatorDisabled}
|
||||
/>
|
||||
<FilterSource filter={filter} onUpdate={props.onUpdate} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* filters */}
|
||||
{isSubFilter && (
|
||||
<div className="grid grid-col ml-3 w-full">
|
||||
{filter.filters.filter(i => (i.key !== FilterKey.FETCH_REQUEST_BODY && i.key !== FilterKey.FETCH_RESPONSE_BODY) || saveRequestPayloads).map((subFilter, subFilterIndex) => (
|
||||
<SubFilterItem
|
||||
filterIndex={subFilterIndex}
|
||||
filter={subFilter}
|
||||
onUpdate={(f) => onUpdateSubFilter(f, subFilterIndex)}
|
||||
onRemoveFilter={props.onRemoveFilter}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 self-start mt-1 ml-auto px-2">
|
||||
<div
|
||||
className="cursor-pointer p-1"
|
||||
onClick={props.onRemoveFilter}
|
||||
>
|
||||
<Icon name="trash" size="14" />
|
||||
{/* Filter values */}
|
||||
{!isSubFilter && (
|
||||
<>
|
||||
<FilterOperator
|
||||
options={filter.operatorOptions}
|
||||
onChange={onOperatorChange}
|
||||
className="mx-2 flex-shrink-0"
|
||||
value={filter.operator}
|
||||
isDisabled={filter.operatorDisabled}
|
||||
/>
|
||||
{canShowValues && <FilterValue filter={filter} onUpdate={props.onUpdate} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* filters */}
|
||||
{isSubFilter && (
|
||||
<div className="grid grid-col ml-3 w-full">
|
||||
{filter.filters
|
||||
.filter((i) => (i.key !== FilterKey.FETCH_REQUEST_BODY && i.key !== FilterKey.FETCH_RESPONSE_BODY) || saveRequestPayloads)
|
||||
.map((subFilter, subFilterIndex) => (
|
||||
<SubFilterItem
|
||||
filterIndex={subFilterIndex}
|
||||
filter={subFilter}
|
||||
onUpdate={(f) => onUpdateSubFilter(f, subFilterIndex)}
|
||||
onRemoveFilter={props.onRemoveFilter}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 self-start mt-1 ml-auto px-2">
|
||||
<div className="cursor-pointer p-1" onClick={props.onRemoveFilter}>
|
||||
<Icon name="trash" size="14" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterItem;
|
||||
export default FilterItem;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ function FilterList(props: Props) {
|
|||
</div>
|
||||
{filters.map((filter: any, filterIndex: any) => filter.isEvent ? (
|
||||
<FilterItem
|
||||
key={filterIndex}
|
||||
key={`${filter.key}-${filterIndex}`}
|
||||
filterIndex={rowIndex++}
|
||||
filter={filter}
|
||||
onUpdate={(filter) => props.onUpdateFilter(filterIndex, filter)}
|
||||
|
|
|
|||
|
|
@ -5,191 +5,180 @@ import { FilterKey, FilterCategory, FilterType } from 'Types/filter/filterType';
|
|||
import FilterValueDropdown from '../FilterValueDropdown';
|
||||
import FilterDuration from '../FilterDuration';
|
||||
import { debounce } from 'App/utils';
|
||||
import { assist as assistRoute, isRoute } from "App/routes";
|
||||
import { assist as assistRoute, isRoute } from 'App/routes';
|
||||
|
||||
const ASSIST_ROUTE = assistRoute();
|
||||
|
||||
interface Props {
|
||||
filter: any;
|
||||
onUpdate: (filter: any) => void;
|
||||
filter: any;
|
||||
onUpdate: (filter: any) => void;
|
||||
}
|
||||
function FilterValue(props: Props) {
|
||||
const { filter } = props;
|
||||
const [durationValues, setDurationValues] = useState({ minDuration: filter.value[0], maxDuration: filter.value.length > 1 ? filter.value[1] : filter.value[0] });
|
||||
const showCloseButton = filter.value.length > 1;
|
||||
const lastIndex = filter.value.length - 1;
|
||||
const { filter } = props;
|
||||
const [durationValues, setDurationValues] = useState({
|
||||
minDuration: filter.value[0],
|
||||
maxDuration: filter.value.length > 1 ? filter.value[1] : filter.value[0],
|
||||
});
|
||||
const showCloseButton = filter.value.length > 1;
|
||||
const lastIndex = filter.value.length - 1;
|
||||
|
||||
const onAddValue = () => {
|
||||
const newValue = filter.value.concat('');
|
||||
props.onUpdate({ ...filter, value: newValue });
|
||||
}
|
||||
const onAddValue = () => {
|
||||
const newValue = filter.value.concat('');
|
||||
props.onUpdate({ ...filter, value: newValue });
|
||||
};
|
||||
|
||||
const onRemoveValue = (valueIndex: any) => {
|
||||
const newValue = filter.value.filter((_: any, index: any) => index !== valueIndex);
|
||||
props.onUpdate({ ...filter, value: newValue });
|
||||
}
|
||||
const onRemoveValue = (valueIndex: any) => {
|
||||
const newValue = filter.value.filter((_: any, index: any) => index !== valueIndex);
|
||||
props.onUpdate({ ...filter, value: newValue });
|
||||
};
|
||||
|
||||
const onChange = (e: any, item: any, valueIndex: any) => {
|
||||
const newValues = filter.value.map((_: any, _index: any) => {
|
||||
if (_index === valueIndex) {
|
||||
return item.value;
|
||||
}
|
||||
return _;
|
||||
})
|
||||
props.onUpdate({ ...filter, value: newValues })
|
||||
}
|
||||
const onChange = (e: any, item: any, valueIndex: any) => {
|
||||
const newValues = filter.value.map((_: any, _index: any) => {
|
||||
if (_index === valueIndex) {
|
||||
return item.value;
|
||||
}
|
||||
return _;
|
||||
});
|
||||
props.onUpdate({ ...filter, value: newValues });
|
||||
};
|
||||
|
||||
const debounceOnSelect = React.useCallback(debounce(onChange, 500), [onChange]);
|
||||
const debounceOnSelect = React.useCallback(debounce(onChange, 500), [onChange]);
|
||||
|
||||
const onDurationChange = (newValues: any) => {
|
||||
setDurationValues({ ...durationValues, ...newValues });
|
||||
}
|
||||
const onDurationChange = (newValues: any) => {
|
||||
setDurationValues({ ...durationValues, ...newValues });
|
||||
};
|
||||
|
||||
const handleBlur = (e: any) => {
|
||||
if (filter.type === FilterType.DURATION) {
|
||||
const { maxDuration, minDuration, key } = filter;
|
||||
if (maxDuration || minDuration) return;
|
||||
if (maxDuration !== durationValues.maxDuration ||
|
||||
minDuration !== durationValues.minDuration) {
|
||||
props.onUpdate({ ...filter, value: [durationValues.minDuration, durationValues.maxDuration] });
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleBlur = (e: any) => {
|
||||
if (filter.type === FilterType.DURATION) {
|
||||
const { maxDuration, minDuration, key } = filter;
|
||||
if (maxDuration || minDuration) return;
|
||||
if (maxDuration !== durationValues.maxDuration || minDuration !== durationValues.minDuration) {
|
||||
props.onUpdate({ ...filter, value: [durationValues.minDuration, durationValues.maxDuration] });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getParms = (key: any) => {
|
||||
let params: any = { type: filter.key };
|
||||
switch (filter.category) {
|
||||
case FilterCategory.METADATA:
|
||||
params = { type: FilterKey.METADATA, key: key };
|
||||
}
|
||||
const getParms = (key: any) => {
|
||||
let params: any = { type: filter.key };
|
||||
switch (filter.category) {
|
||||
case FilterCategory.METADATA:
|
||||
params = { type: FilterKey.METADATA, key: key };
|
||||
}
|
||||
|
||||
if (isRoute(ASSIST_ROUTE, window.location.pathname)) {
|
||||
params = { ...params, live: true };
|
||||
}
|
||||
if (isRoute(ASSIST_ROUTE, window.location.pathname)) {
|
||||
params = { ...params, live: true };
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
const renderValueFiled = (value: any, valueIndex: any) => {
|
||||
const showOrButton = valueIndex === lastIndex && filter.type !== FilterType.NUMBER;
|
||||
switch(filter.type) {
|
||||
case FilterType.STRING:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
/>
|
||||
)
|
||||
case FilterType.DROPDOWN:
|
||||
return (
|
||||
<FilterValueDropdown
|
||||
// search={true}
|
||||
value={value}
|
||||
filter={filter}
|
||||
options={filter.options}
|
||||
onChange={({ value }) => onChange(null, { value }, valueIndex)}
|
||||
/>
|
||||
)
|
||||
case FilterType.ISSUE:
|
||||
case FilterType.MULTIPLE_DROPDOWN:
|
||||
return (
|
||||
<FilterValueDropdown
|
||||
search={true}
|
||||
// multiple={true}
|
||||
value={value}
|
||||
// filter={filter}
|
||||
options={filter.options}
|
||||
onChange={({ value }) => onChange(null, { value }, valueIndex)}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
/>
|
||||
)
|
||||
case FilterType.DURATION:
|
||||
return (
|
||||
<FilterDuration
|
||||
onChange={ onDurationChange }
|
||||
// onEnterPress={ this.handleClose }
|
||||
onBlur={handleBlur}
|
||||
minDuration={ durationValues.minDuration }
|
||||
maxDuration={ durationValues.maxDuration }
|
||||
/>
|
||||
)
|
||||
case FilterType.NUMBER_MULTIPLE:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
type="number"
|
||||
/>
|
||||
)
|
||||
case FilterType.NUMBER:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
type="number"
|
||||
allowDecimals={false}
|
||||
isMultilple={false}
|
||||
/>
|
||||
// <input
|
||||
// className="w-full px-2 py-1 text-sm leading-tight text-gray-700 rounded bg-white border"
|
||||
// type="number"
|
||||
// name={`${filter.key}-${valueIndex}`}
|
||||
// value={value}
|
||||
// placeholder="Enter"
|
||||
// onChange={(e) => onChange(e, { value: e.target.value }, valueIndex)}
|
||||
// />
|
||||
)
|
||||
case FilterType.MULTIPLE:
|
||||
return (
|
||||
<FilterAutoComplete
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
method={'GET'}
|
||||
endpoint='/events/search'
|
||||
params={getParms(filter.key)}
|
||||
headerText={''}
|
||||
// placeholder={''}
|
||||
onSelect={(e, item) => onChange(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
const renderValueFiled = (value: any, valueIndex: any) => {
|
||||
const showOrButton = valueIndex === lastIndex && filter.type !== FilterType.NUMBER;
|
||||
switch (filter.type) {
|
||||
case FilterType.STRING:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
/>
|
||||
);
|
||||
case FilterType.DROPDOWN:
|
||||
return (
|
||||
<FilterValueDropdown
|
||||
// search={true}
|
||||
value={value}
|
||||
filter={filter}
|
||||
options={filter.options}
|
||||
onChange={({ value }) => onChange(null, { value }, valueIndex)}
|
||||
/>
|
||||
);
|
||||
case FilterType.ISSUE:
|
||||
case FilterType.MULTIPLE_DROPDOWN:
|
||||
return (
|
||||
<FilterValueDropdown
|
||||
search={true}
|
||||
// multiple={true}
|
||||
value={value}
|
||||
// filter={filter}
|
||||
options={filter.options}
|
||||
onChange={({ value }) => onChange(null, { value }, valueIndex)}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
/>
|
||||
);
|
||||
case FilterType.DURATION:
|
||||
return (
|
||||
<FilterDuration
|
||||
onChange={onDurationChange}
|
||||
// onEnterPress={ this.handleClose }
|
||||
onBlur={handleBlur}
|
||||
minDuration={durationValues.minDuration}
|
||||
maxDuration={durationValues.maxDuration}
|
||||
/>
|
||||
);
|
||||
case FilterType.NUMBER_MULTIPLE:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
type="number"
|
||||
/>
|
||||
);
|
||||
case FilterType.NUMBER:
|
||||
return (
|
||||
<FilterAutoCompleteLocal
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
onSelect={(e, item) => debounceOnSelect(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
type="number"
|
||||
allowDecimals={false}
|
||||
isMultilple={false}
|
||||
/>
|
||||
);
|
||||
case FilterType.MULTIPLE:
|
||||
return (
|
||||
<FilterAutoComplete
|
||||
value={value}
|
||||
showCloseButton={showCloseButton}
|
||||
showOrButton={showOrButton}
|
||||
onAddValue={onAddValue}
|
||||
onRemoveValue={() => onRemoveValue(valueIndex)}
|
||||
method={'GET'}
|
||||
endpoint="/events/search"
|
||||
params={getParms(filter.key)}
|
||||
headerText={''}
|
||||
// placeholder={''}
|
||||
onSelect={(e, item) => onChange(e, item, valueIndex)}
|
||||
icon={filter.icon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-3 w-full">
|
||||
{ filter.type === FilterType.DURATION ? (
|
||||
renderValueFiled(filter.value, 0)
|
||||
) : (
|
||||
filter.value && filter.value.map((value: any, valueIndex: any) => (
|
||||
<div key={valueIndex}>
|
||||
{renderValueFiled(value, valueIndex)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-3 w-full">
|
||||
{filter.type === FilterType.DURATION
|
||||
? renderValueFiled(filter.value, 0)
|
||||
: filter.value &&
|
||||
filter.value.map((value: any, valueIndex: any) => <div key={valueIndex}>{renderValueFiled(value, valueIndex)}</div>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterValue;
|
||||
export default FilterValue;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue