import { Popper, PopperProps, autocompleteClasses } from '@mui/material'; import Autocomplete, { AutocompleteProps } from '@mui/material/Autocomplete'; import CircularProgress from '@mui/material/CircularProgress'; import TextField, { TextFieldProps } from '@mui/material/TextField'; import { ApiClientUtils, NestedKeys } from 'fwork-jsts-common'; import * as React from 'react'; import useSnackbarExt from '../snackbarExt'; // function sleep(duration: number): Promise { // return new Promise((resolve) => { // setTimeout(() => { // resolve(); // }, duration); // }); // } function getNestedProperty(obj: T, path: string): any { return path.split('.').reduce((acc, key) => acc && (acc as any)[key], obj); } export interface IAutocompleteClientComponentProps extends Partial> { inputValueKeyName: NestedKeys, onGetData: (filter: string, keyValue: boolean) => T[] | undefined | Promise /** * faz uma consulta para pegar a opcao necessaria pela chave, so usar caso nao tenha uma opcao ja disponivel pra passar em initOption */ initKeyValue?: any, initOptions?: readonly T[] | undefined, initOption?: T | undefined, onChangeItem?: (data?: T | null) => void, textFieldProps?: TextFieldProps, getOnInit?: boolean, getAllOnOpen?: boolean, fitDropDownWidth?: boolean, updateOptionsRef?: React.MutableRefObject<((filter: string, keyValue: boolean) => void) | null | undefined> updateOptionsWithValueRef?: React.MutableRefObject<((value: T) => void) | null | undefined> } const CustomPopper = (props: PopperProps) => { return ; }; export const AutocompleteClientComponent = (props: IAutocompleteClientComponentProps) => { const { inputValueKeyName, initKeyValue, initOptions, initOption, onChangeItem, onGetData, textFieldProps, getOnInit, getAllOnOpen, fitDropDownWidth, updateOptionsRef, updateOptionsWithValueRef, ...rest } = props const [inputValue, setInputValue] = React.useState(''); const [open, setOpen] = React.useState(false); const [options, setOptions] = React.useState([]); const [loading, setLoading] = React.useState(false); const { enqueueSnackbarPersistedError } = useSnackbarExt() // EXPORTED METHOD... React.useEffect(() => { if (updateOptionsRef) updateOptionsRef.current = doUpdateDataRef if (updateOptionsWithValueRef) updateOptionsWithValueRef.current = doUpdateDataWithValueRef }, []) const doUpdateDataRef = async (filter: string, keyValue: boolean) => { let dataResponse = await getData(filter, keyValue) setOptions(dataResponse ?? []) } const doUpdateDataWithValueRef = (value: T) => { setOptions(old => ([...old, value])) } // ...EXPORTED METHOD const getData = async (filter: string, keyValue: boolean) => { // if (loading) return setLoading(true) // await sleep(1e3); try { return await onGetData(filter, keyValue) } catch (error) { enqueueSnackbarPersistedError(ApiClientUtils.getErrorMessage(error)) } finally { setLoading(false) } return undefined } const handleOpen = async () => { setOpen(true); // Chama a API ao abrir if (getAllOnOpen) { const dataResponse = await getData('', false); setOptions(dataResponse ?? []); } }; React.useEffect(() => { if (getOnInit) (async () => { const dataResponse = await getData('', false) setOptions(dataResponse ?? []) })(); }, []) React.useEffect(() => { if (initKeyValue) (async () => { const dataResponse = await getData(initKeyValue, true) setOptions(dataResponse ?? []) if (dataResponse?.length) { setInputValue(getNestedProperty(dataResponse[0], inputValueKeyName)) } })(); }, [initKeyValue, inputValueKeyName]) const handleClose = () => { setOpen(false); }; React.useEffect(() => { if (initOption) { setInputValue(getNestedProperty(initOption, inputValueKeyName)) // console.log(`SETINPUTVALUE->useEffect: [initOption, inputValueKeyName]: if (initOption)`) } else { setInputValue("") // console.log(`SETINPUTVALUE->useEffect: [initOption, inputValueKeyName]: if (initOption)...else...`) } }, [initOption, inputValueKeyName]) React.useEffect(() => { const foundOption = options.find(o => props.getOptionKey?.(o) == (typeof props.value == 'string' ? props.value : props.getOptionKey?.(props.value as any))) if (foundOption) { setInputValue(getNestedProperty(foundOption, inputValueKeyName)) // console.log(`SETINPUTVALUE->useEffect: [props.value, options, props.getOptionKey, inputValueKeyName]`) } }, [props.value, options, props.getOptionKey, inputValueKeyName]) const typingTimeout = React.useRef | null>(null); const handleInputChange = (value: string) => { setInputValue(value); // console.log(`SETINPUTVALUE->handleInputChange`) if (typingTimeout.current) clearTimeout(typingTimeout.current); typingTimeout.current = setTimeout(async () => { const dataResponse = await getData(value, false); setOptions(dataResponse ?? []); }, 200); // debounce }; return ( <> {...(rest as any)} // renderOption example // renderOption={(props, option, state, ownerState) => { // const { key, ...optionProps } = props; // const { selected } = state; // return ( // // {ownerState.getOptionLabel(option)} // // ); // }} // PopperComponent={CustomPopper} // new... filterOptions={rest.filterOptions ?? ((options) => options)} // ...new slots={{ popper: fitDropDownWidth ? CustomPopper : undefined, ...rest.slots }} sx={{ [`& .${autocompleteClasses.paper}`]: { minWidth: fitDropDownWidth ? "fit-content" : undefined, }, ...rest.sx }} noOptionsText={rest.noOptionsText ?? (inputValue && !initOption ? 'Nenhum item!' : 'Digite para buscar...')} open={rest.open ?? open} onOpen={rest.onOpen ?? handleOpen} onClose={rest.onClose ?? handleClose} isOptionEqualToValue={rest.isOptionEqualToValue ?? ((option, value) => { return getNestedProperty(option, inputValueKeyName) === getNestedProperty(value, inputValueKeyName) })} getOptionLabel={rest.getOptionLabel ?? ((option) => { const result = getNestedProperty(option, inputValueKeyName) ?? '' return result })} options={rest.options ?? options ?? initOptions} loading={rest.loading ?? loading} inputValue={rest.inputValue ?? inputValue ?? ''} onChange={rest.onChange ?? ((_: any, newValue: T | null) => { setInputValue(newValue ? getNestedProperty(newValue, inputValueKeyName) : '') // console.log(`SETINPUTVALUE->onChange`) if (onChangeItem) onChangeItem(newValue) })} renderInput={rest.renderInput ?? ((params) => ( {loading ? : null} {params.slotProps.input?.endAdornment} ), }, }} onChange={(e) => handleInputChange(e.target.value)} /> ))} onKeyDown={rest.onKeyDown ?? (async (event) => { if (event.key === 'Enter') { let dataResponse = await getData(inputValue, false) setOptions(dataResponse ?? []) } })} /> ); }