diff --git a/apps/design-system/src/subjects/views/delegates/delegate-selector.tsx b/apps/design-system/src/subjects/views/delegates/delegate-selector.tsx index c2c6145a64..8ad5a5cafb 100644 --- a/apps/design-system/src/subjects/views/delegates/delegate-selector.tsx +++ b/apps/design-system/src/subjects/views/delegates/delegate-selector.tsx @@ -59,7 +59,7 @@ const DelegateSelectorDrawer = ({ open, setOpen, preSelectedTags, onSubmit, disa
Haven't installed a delegate yet? - + Install delegate
@@ -68,6 +68,8 @@ const DelegateSelectorDrawer = ({ open, setOpen, preSelectedTags, onSubmit, disa @@ -116,7 +120,7 @@ export const DelegateSelector = () => { onEdit={() => setOpenA(true)} onClear={() => setTagsA([])} renderValue={tag => tag} - className="max-w-xs mb-8" + className="mb-8 max-w-xs" /> @@ -130,7 +134,7 @@ export const DelegateSelector = () => { onEdit={() => setOpenB(true)} onClear={() => setTagsB([])} renderValue={tag => tag} - className="max-w-xs mb-8" + className="mb-8 max-w-xs" /> = T & { id: string | number label: string @@ -30,6 +32,7 @@ export interface MultiSelectProps { searchValue?: string handleChangeSearchValue?: (data: string) => void customOptionElem?: (data: MultiSelectOptionType) => ReactNode + loading?: boolean error?: string label?: string } @@ -44,14 +47,60 @@ export const MultiSelect = ({ searchValue = '', handleChangeSearchValue, customOptionElem, + loading, error, label }: MultiSelectProps) => { + const { showSelectedFirstOnOpen, optionsToDisplay, setOptionsToDisplay } = useSelectedFirstOptions( + options, + selectedItems + ) + const { search, handleSearchChange } = useDebounceSearch({ handleChangeSearchValue, searchValue }) + useEffect(() => { + setOptionsToDisplay(options) + }, [search, options, setOptionsToDisplay]) + + const renderEmptyStateWithText = (stateText: string) => { + return ( +
+ {stateText} +
+ ) + } + + const renderOptions = () => { + return optionsToDisplay.length ? ( + + {optionsToDisplay.map(option => { + const isSelected = selectedItems.findIndex(it => it.id === option.id) > -1 + + return ( + { + e.preventDefault() + handleChange(option) + }} + > +
+ {isSelected && } + {customOptionElem ? customOptionElem(option) : {option.label}} +
+
+ ) + })} +
+ ) : ( + renderEmptyStateWithText(t('views:noData.noResults', 'No search results')) + ) + } + return ( {!!label && ( @@ -59,8 +108,8 @@ export const MultiSelect = ({ {label} )} - - + + {placeholder} @@ -80,39 +129,7 @@ export const MultiSelect = ({ )} - {options.length ? ( - - {options.map(option => { - const isSelected = selectedItems.findIndex(it => it.id === option.id) > -1 - - return ( - { - e.preventDefault() - handleChange(option) - }} - > -
- {isSelected && } - {customOptionElem ? ( - customOptionElem(option) - ) : ( - {option.label} - )} -
-
- ) - })} -
- ) : ( -
- - {t('views:noData.noResults', 'No search results')} - -
- )} + {loading ? renderEmptyStateWithText('Loading...') : renderOptions()}
{!!selectedItems.length && ( diff --git a/packages/ui/src/components/multi-select/use-selected-first-options.ts b/packages/ui/src/components/multi-select/use-selected-first-options.ts new file mode 100644 index 0000000000..a580bf7e40 --- /dev/null +++ b/packages/ui/src/components/multi-select/use-selected-first-options.ts @@ -0,0 +1,44 @@ +import { Dispatch, SetStateAction, useCallback, useMemo, useState } from 'react' + +import { MultiSelectOptionType } from '@/components' + +type UseSelectedFirstOptionsReturnType = { + optionsToDisplay: MultiSelectOptionType[] + setOptionsToDisplay: Dispatch[]>> + showSelectedFirstOnOpen: (open: boolean) => void +} + +export const useSelectedFirstOptions = ( + options: MultiSelectOptionType[], + selectedItems: MultiSelectOptionType>[] +): UseSelectedFirstOptionsReturnType => { + const [isOpen, setIsOpen] = useState(false) + const [optionsToDisplay, setOptionsToDisplay] = useState[]>(options) + const selectedIds = useMemo(() => new Set(selectedItems.map(item => item.id)), [selectedItems]) + + const showSelectedFirstOnOpen = useCallback( + open => { + if (open) { + const selectedFirst = [...options].sort((a, b) => { + const aSelected = selectedIds.has(a.id) + const bSelected = selectedIds.has(b.id) + + if (aSelected === bSelected) return 0 + + return aSelected ? -1 : 1 + }) + + setOptionsToDisplay(selectedFirst) + } + + setIsOpen(open) + }, + [selectedIds, options] + ) + + return { + optionsToDisplay: isOpen ? optionsToDisplay : options, + setOptionsToDisplay, + showSelectedFirstOnOpen + } +} diff --git a/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx b/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx index 4db668dbc4..17705f21ee 100644 --- a/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx +++ b/packages/ui/src/views/delegates/delegate-selector/delegate-selector-form.tsx @@ -17,6 +17,7 @@ import { import { SandboxLayout, TranslationStore } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' import { RadioOption, RadioSelect } from '@views/components/RadioSelect' +import { useBackendTagsSearch, useClientTagsSearch } from '@views/delegates/hooks' import { z } from 'zod' import { DelegateConnectivityList } from '../components/delegate-connectivity-list' @@ -78,7 +79,12 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem disableAnyDelegate } = props const { t } = useTranslationStore() - const [searchTag, setSearchTag] = useState('') + + // TODO: Uncomment to check the client tags search + // const { searchTag, handleSearchChange, filteredTags } = useClientTagsSearch(tagsList, preSelectedTags) + + const { searchTag, handleSearchChange, filteredTags, loadingTags } = useBackendTagsSearch(tagsList, preSelectedTags) + const [matchedDelegates, setMatchedDelegates] = useState(0) const { register, @@ -182,11 +188,12 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem label="Tags" placeholder="Enter tags" handleChange={handleTagChange} - options={tagsList.map(tag => { + options={filteredTags.map(tag => { return { id: tag, label: tag } })} searchValue={searchTag} - handleChangeSearchValue={setSearchTag} + handleChangeSearchValue={handleSearchChange} + loading={loadingTags} error={errors.tags?.message?.toString()} /> @@ -202,7 +209,7 @@ export const DelegateSelectorForm = (props: DelegateSelectorFormProps): JSX.Elem )} -
+