fix(ui): top values

This commit is contained in:
Shekar Siri 2024-08-06 14:15:34 +02:00
parent ec67590472
commit 8bf640d753
3 changed files with 98 additions and 93 deletions

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback, useRef, ChangeEvent, KeyboardEvent } from 'react';
import { Icon } from 'UI';
import APIClient from 'App/api_client';
import { debounce } from 'App/utils';
@ -135,168 +135,177 @@ interface Props {
hideOrText?: boolean;
}
function FilterAutoComplete(props: Props) {
const {
showCloseButton = false,
placeholder = 'Type to search',
method = 'GET',
showOrButton = false,
onRemoveValue = () => null,
onAddValue = () => null,
endpoint = '',
params = {},
value = '',
hideOrText = false
} = props;
const FilterAutoComplete: React.FC<Props> = ({
showCloseButton = false,
placeholder = 'Type to search',
method = 'GET',
showOrButton = false,
endpoint = '',
params = {},
value = '',
hideOrText = false,
onSelect,
onRemoveValue,
onAddValue
}: Props) => {
const [loading, setLoading] = useState(false);
const [options, setOptions] = useState<any>([]);
const [options, setOptions] = useState<{ value: string; label: string }[]>([]);
const [query, setQuery] = useState(value);
const [menuIsOpen, setMenuIsOpen] = useState(false);
const [initialFocus, setInitialFocus] = useState(false);
let selectRef: any = null;
let inputRef: any = null;
const [previousQuery, setPreviousQuery] = useState(value);
const selectRef = useRef<any>(null);
const inputRef = useRef<any>(null);
const { filterStore } = useStore();
const _params = processKey(params);
const [topValues, setTopValues] = useState<any>([]);
const filterKey = `${_params.type}${_params.key || ''}`;
const topValues = filterStore.topValues[filterKey] || [];
const [topValuesLoading, setTopValuesLoading] = useState(false);
useEffect(() => {
const fetchValues = async () => {
setTopValuesLoading(true);
const values = await filterStore.getTopValues(_params.type);
setTopValues(values);
const loadTopValues = () => {
setTopValuesLoading(true);
filterStore.fetchTopValues(_params.type, _params.key).finally(() => {
setTopValuesLoading(false);
setLoading(false);
};
fetchValues().then(r => {
});
}, []);
};
useEffect(() => {
if (topValues.length > 0) {
const mappedValues = topValues.map((i) => ({ value: i.value, label: i.value }));
setOptions(mappedValues);
if (!query.length && initialFocus) {
setMenuIsOpen(true);
}
}
}, [topValues, initialFocus, query.length]);
useEffect(loadTopValues, [_params.type]);
useEffect(() => {
setQuery(value);
}, [value]);
const loadOptions = (inputValue: string, callback: (options: []) => void) => {
const loadOptions = async (inputValue: string, callback: (options: { value: string; label: string }[]) => void) => {
if (!inputValue.length) {
setOptions(topValues.map((i: any) => ({ value: i.value, label: i.value })));
callback(topValues.map((i: any) => ({ value: i.value, label: i.value })));
const mappedValues = topValues.map((i) => ({ value: i.value, label: i.value }));
setOptions(mappedValues);
callback(mappedValues);
setLoading(false);
return;
}
// @ts-ignore
new APIClient()
[method?.toLocaleLowerCase()](endpoint, { ..._params, q: inputValue })
.then((response: any) => {
return response.json();
})
.then(({ data }: any) => {
const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || [];
setOptions(_options);
callback(_options);
setLoading(false);
})
.catch((e) => {
throw new Error(e);
});
try {
const response = await new APIClient()[method.toLowerCase()](endpoint, { ..._params, q: inputValue });
const data = await response.json();
const _options = data.map((i: any) => ({ value: i.value, label: i.value })) || [];
setOptions(_options);
callback(_options);
} catch (e) {
throw new Error(e);
} finally {
setLoading(false);
}
};
const debouncedLoadOptions = React.useCallback(debounce(loadOptions, 1000), [params, topValues]);
const debouncedLoadOptions = useCallback(debounce(loadOptions, 1000), [params, topValues]);
const handleInputChange = (newValue: string) => {
setLoading(true);
setInitialFocus(true);
setQuery(newValue);
debouncedLoadOptions(newValue, (opt: any) => {
selectRef?.focus();
debouncedLoadOptions(newValue, () => {
selectRef.current?.focus();
});
};
const onChange = (item: any) => {
const handleChange = (item: { value: string }) => {
setMenuIsOpen(false);
setQuery(item);
props.onSelect(null, item);
setQuery(item.value);
onSelect(null, item.value);
};
const onFocus = () => {
const handleFocus = () => {
setInitialFocus(true);
if (!query.length) {
setLoading(topValuesLoading);
setMenuIsOpen(!topValuesLoading && topValues.length > 0);
setOptions(topValues.map((i: any) => ({ value: i.value, label: i.value })));
setOptions(topValues.map((i) => ({ value: i.value, label: i.value })));
} else {
setMenuIsOpen(true);
}
};
const onBlur = () => {
const handleBlur = () => {
setMenuIsOpen(false);
props.onSelect(null, query);
setInitialFocus(false);
if (query !== previousQuery) {
onSelect(null, query);
}
setPreviousQuery(query);
};
const selected = value ? options.find((i: any) => i.value === query) : null;
const uniqueOptions = options.filter((i: Record<string, string>) => i.value !== query);
const selected = value ? options.find((i) => i.value === query) : null;
const uniqueOptions = options.filter((i) => i.value !== query);
const selectOptionsArr = query.length ? [{ value: query, label: query }, ...uniqueOptions] : options;
return (
<div className="relative flex items-center">
<div className={cn(stl.wrapper, 'relative')}>
<input
ref={(ref: any) => (inputRef = ref)}
ref={inputRef}
className="w-full rounded px-2 no-focus"
value={query}
onChange={({ target: { value } }: any) => handleInputChange(value)}
onClick={onFocus}
onFocus={onFocus}
onBlur={onBlur}
onChange={(e: ChangeEvent<HTMLInputElement>) => handleInputChange(e.target.value)}
onClick={handleFocus}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
onKeyDown={(e: any) => {
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
inputRef?.blur();
inputRef.current.blur();
}
}}
/>
{loading && (
<div className="absolute top-0 right-0" style={{
marginTop: '5px',
marginRight: !showCloseButton || (showCloseButton && !showOrButton) ? '34px' : '62px'
}}>
<div
className="absolute top-0 right-0"
style={{
marginTop: '5px',
marginRight: !showCloseButton || (showCloseButton && !showOrButton) ? '34px' : '62px'
}}
>
<Icon name="spinner" className="animate-spin" size="14" />
</div>
)}
<Select
ref={(ref: any) => {
selectRef = ref;
}}
ref={selectRef}
options={selectOptionsArr}
value={selected}
onChange={(e: any) => onChange(e.value)}
onChange={(e) => handleChange(e as { value: string })}
menuIsOpen={initialFocus && menuIsOpen}
menuPlacement="auto"
styles={dropdownStyles}
components={{
Control: ({ children, ...props }: any) => <></>
Control: () => null
}}
/>
<div className={stl.right}>
{showCloseButton && (
<div onClick={props.onRemoveValue}>
<div onClick={onRemoveValue}>
<Icon name="close" size="12" />
</div>
)}
{showOrButton && (
<div onClick={props.onAddValue} className="color-teal">
<div onClick={onAddValue} className="color-teal">
<span className="px-1">or</span>
</div>
)}
</div>
</div>
{!showOrButton && !hideOrText && <div className="ml-3">or</div>}
</div>
);
}
};
export default observer(FilterAutoComplete);

View file

@ -17,26 +17,18 @@ export default class FilterStore {
constructor() {
makeAutoObservable(this);
filters.forEach((filter) => {
this.topValues[filter.key] = [];
});
}
setTopValues = (key: string, values: TopValue[]) => {
this.topValues[key] = values.filter((value) => value !== null && value.value !== '');
};
getTopValues = async (key: string) => {
if (!this.topValues[key] || this.topValues[key].length === 0) {
await this.fetchTopValues(key);
fetchTopValues = async (key: string, source?: string) => {
if (this.topValues.hasOwnProperty(key)) {
return Promise.resolve(this.topValues[key]);
}
return Promise.resolve(this.topValues[key]);
};
fetchTopValues = async (key: string) => {
return filterService.fetchTopValues(key).then((response: TopValue[]) => {
this.setTopValues(key, response);
return filterService.fetchTopValues(key, source).then((response: TopValue[]) => {
this.setTopValues(`${key}${source || ''}`, response);
});
};
}

View file

@ -11,8 +11,12 @@ export default class FilterService {
this.client = client || new APIClient();
}
fetchTopValues = async (key: string) => {
const response = await this.client.get(`/PROJECT_ID/events/search?type=${key}`);
fetchTopValues = async (key: string, source?: string) => {
let path = `/PROJECT_ID/events/search?type=${key}`;
if (source) {
path += `&source=${source}`;
}
const response = await this.client.get(path);
return await response.json();
};
}