import { FormHelperText, Stack } from '@mui/material'; import { type FormikValues, useFormikContext } from 'formik'; import { equals, isNil, map, not, path, prop, type } from 'ramda'; import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import type { SelectEntry } from '../../InputField/Select'; import SingleAutocompleteField from '../../InputField/Select/Autocomplete'; import MultiAutocompleteField from '../../InputField/Select/Autocomplete/Multi'; import { useMemoComponent } from '../../utils'; import { labelPressEnterToAccept } from '../translatedLabels'; import { type InputPropsWithoutGroup, InputType } from './models'; interface NormalizeNewValuesProps { isCreatable?: boolean; isMultiple: boolean; newValues: SelectEntry | Array | null; } const normalizeNewValues = ({ newValues, isMultiple, isCreatable }: NormalizeNewValuesProps): SelectEntry | Array => { const isSingle = not(isMultiple); if (isSingle) { return newValues as SelectEntry; } return map( (newValue: SelectEntry | string) => { const isManualValue = equals(type(newValue), 'String'); if (isCreatable && isManualValue) { return newValue; } if (isCreatable) { return prop('name', newValue as SelectEntry); } return newValue; }, newValues as Array ); }; const Autocomplete = ({ fieldName, label, required, getDisabled, getRequired, change, additionalMemoProps, autocomplete, type: inputType }: InputPropsWithoutGroup): JSX.Element => { const { t } = useTranslation(); const [inputText, setInputText] = useState(''); const { values, setFieldValue, setFieldTouched, errors, touched, setValues, setTouched } = useFormikContext(); const isMultiple = equals(inputType, InputType.MultiAutocomplete); const changeValues = ( _: React.SyntheticEvent, newValues: SelectEntry | Array | null ): void => { const normalizedNewValues = normalizeNewValues({ isCreatable, isMultiple, newValues }); setInputText(''); if (change) { setFieldTouched(fieldName, true, false); change({ setFieldTouched, setFieldValue, setTouched, setValues, value: normalizedNewValues, values }); return; } setFieldTouched(fieldName, true, false); setFieldValue(fieldName, normalizedNewValues); }; const isCreatable = autocomplete?.creatable; const selectedValues = path | SelectEntry>( [...fieldName.split('.')], values ); const getError = useCallback((): Array | undefined => { if (!path([...fieldName.split('.')], touched)) { return undefined; } const error = path([...fieldName.split('.')], errors) as | Array | string | undefined; const isStringError = equals(type(error), 'String'); if (isMultiple && !isStringError) { const formattedErrors = (error as Array | undefined)?.map( (errorText, index) => { if (isNil(errorText)) { return undefined; } return `${(selectedValues as Array | undefined)?.[index]}: ${errorText}`; } ); const filteredErrors = formattedErrors?.filter(Boolean); return (filteredErrors as Array) || undefined; } const formattedError = [error]; const filteredError = formattedError?.filter(Boolean); return (filteredError as Array) || undefined; }, [errors, fieldName, isMultiple, selectedValues, touched]); const textChange = useCallback( (event: React.ChangeEvent): void => setInputText(event.target.value), [] ); const getValues = useCallback((): | SelectEntry | Array | undefined => { if (isMultiple && isCreatable) { return equals(type(selectedValues), 'Array') ? (selectedValues as Array | undefined)?.map((value) => ({ id: value, name: value })) : selectedValues; } return selectedValues; }, [isMultiple, isCreatable, selectedValues]); const inputErrors = getError(); const disabled = getDisabled?.(values) || false; const isRequired = required || getRequired?.(values) || false; const additionalLabel = inputText && isCreatable ? ` (${labelPressEnterToAccept})` : ''; const AutocompleteField = useMemo( () => (isMultiple ? MultiAutocompleteField : SingleAutocompleteField), [isMultiple] ); return useMemoComponent({ Component: (
equals(option, selectedValue) } label={`${t(label)}${additionalLabel}`} onChange={changeValues as never} onTextChange={textChange} open={isCreatable ? false : undefined} options={autocomplete?.options || []} popupIcon={isCreatable ? null : undefined} required={isRequired} style={{ width: (autocomplete?.fullWidth ?? true) ? 'auto' : '180px' }} value={getValues() ?? null} /> {inputErrors && ( {inputErrors.map((error) => ( {error} ))} )}
), memoProps: [ values, getValues(), inputErrors, additionalLabel, disabled, additionalMemoProps, isMultiple, autocomplete?.options, isCreatable ] }); }; export default Autocomplete;