import { MultiSelectFiltered } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; import { useEndpoint } from '@rocket.chat/ui-contexts'; import { keepPreviousData, useQuery } from '@tanstack/react-query'; import type { ReactElement, AllHTMLAttributes } from 'react'; import { memo, useState, useCallback, useMemo } from 'react'; import AutocompleteOptions, { OptionsContext } from './UserAutoCompleteMultipleOptions'; import UserAvatarChip from './UserAvatarChip'; import { usersQueryKeys } from '../../lib/queryKeys'; type UserAutoCompleteMultipleProps = { onChange: (value: Array) => void; value: Array | undefined; placeholder?: string; federated?: boolean; error?: string; } & Omit, 'is' | 'onChange' | 'value'>; type UserAutoCompleteOptionType = { name: string; username: string; _federated?: boolean; }; type UserAutoCompleteOptions = { [k: string]: UserAutoCompleteOptionType; }; const matrixRegex = new RegExp('@(.*:.*)'); const UserAutoCompleteMultiple = ({ onChange, value, placeholder, federated, ...props }: UserAutoCompleteMultipleProps): ReactElement => { const [filter, setFilter] = useState(''); const [selectedCache, setSelectedCache] = useState({}); const debouncedFilter = useDebouncedValue(filter, 500); const getUsers = useEndpoint('GET', '/v1/users.autocomplete'); const { data } = useQuery({ queryKey: usersQueryKeys.userAutoComplete(debouncedFilter, federated ?? false), queryFn: async () => { const users = await getUsers({ selector: JSON.stringify({ term: debouncedFilter }) }); const options = users.items.map((item): [string, UserAutoCompleteOptionType] => [item.username, item]); // Add extra option if filter text matches `username:server` // Used to add federated users that do not exist yet if (federated && matrixRegex.test(debouncedFilter)) { options.unshift([debouncedFilter, { name: debouncedFilter, username: debouncedFilter, _federated: true }]); } return options; }, placeholderData: keepPreviousData, }); const options = useMemo(() => data || [], [data]); const onAddUser = useCallback( (username: string): void => { const user = options?.find(([val]) => val === username)?.[1]; if (!user) { throw new Error('UserAutoCompleteMultiple - onAddSelected - failed to cache option'); } setSelectedCache((selectedCache) => ({ ...selectedCache, [username]: user })); }, [setSelectedCache, options], ); const onRemoveUser = useCallback( (username: string): void => setSelectedCache((selectedCache) => { const users = { ...selectedCache }; delete users[username]; return users; }), [setSelectedCache], ); const handleOnChange = useCallback( (usernames: string[]) => { onChange(usernames); const newAddedUsername = usernames.filter((username) => !value?.includes(username))[0]; const removedUsername = value?.filter((username) => !usernames.includes(username))[0]; setFilter(''); newAddedUsername && onAddUser(newAddedUsername); removedUsername && onRemoveUser(removedUsername); }, [onChange, setFilter, onAddUser, onRemoveUser, value], ); return ( void }) => { const currentCachedOption = selectedCache[username] || {}; return ( ); }} renderOptions={AutocompleteOptions} options={options.concat(Object.entries(selectedCache)).map(([, item]) => [item.username, item.name || item.username])} /> ); }; export default memo(UserAutoCompleteMultiple);