fix: replace useQuery with useInfiniteQuery in search members

This commit is contained in:
NFish 2025-04-10 15:47:33 +08:00
parent eddf4eeac6
commit bfdcd78942
2 changed files with 38 additions and 35 deletions

View File

@ -19,27 +19,26 @@ export default function AddMemberOrGroupDialog() {
const { t } = useTranslation() const { t } = useTranslation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [keyword, setKeyword] = useState('') const [keyword, setKeyword] = useState('')
const [pageNumber, setPageNumber] = useState(1)
const debouncedKeyword = useDebounce(keyword, { wait: 500 }) const debouncedKeyword = useDebounce(keyword, { wait: 500 })
const { isPending, data } = useSearchForWhiteListCandidates({ keyword: debouncedKeyword, pageNumber, resultsPerPage: 10 }, open) const { isPending, isFetchingNextPage, fetchNextPage, data } = useSearchForWhiteListCandidates({ keyword: debouncedKeyword, resultsPerPage: 10 }, open)
const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(e.target.value) setKeyword(e.target.value)
} }
const anchorRef = useRef<HTMLDivElement>(null) const anchorRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
const hasMore = data?.has_more ?? true const hasMore = data?.pages?.[0].hasMore ?? false
let observer: IntersectionObserver | undefined let observer: IntersectionObserver | undefined
if (anchorRef.current) { if (anchorRef.current) {
observer = new IntersectionObserver((entries) => { observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && !isPending && hasMore) if (entries[0].isIntersecting && !isPending && hasMore)
setPageNumber((size: number) => size + 1) fetchNextPage()
}, { rootMargin: '20px' }) }, { rootMargin: '20px' })
observer.observe(anchorRef.current) observer.observe(anchorRef.current)
} }
return () => observer?.disconnect() return () => observer?.disconnect()
}, [isPending, setPageNumber, anchorRef, data]) }, [isPending, fetchNextPage, anchorRef, data])
return <PortalToFollowElem open={open} onOpenChange={setOpen} offset={{ crossAxis: 300 }} placement='bottom-end'> return <PortalToFollowElem open={open} onOpenChange={setOpen} offset={{ crossAxis: 300 }} placement='bottom-end'>
<PortalToFollowElemTrigger asChild> <PortalToFollowElemTrigger asChild>
@ -54,40 +53,37 @@ export default function AddMemberOrGroupDialog() {
<Input value={keyword} onChange={handleKeywordChange} showLeftIcon placeholder={t('app.accessControlDialog.operateGroupAndMember.searchPlaceholder') as string} /> <Input value={keyword} onChange={handleKeywordChange} showLeftIcon placeholder={t('app.accessControlDialog.operateGroupAndMember.searchPlaceholder') as string} />
</div> </div>
{ {
(data?.subjects?.length ?? 0) > 0 isPending
? <div className='p-1'><Loading /></div>
: (data?.pages?.length ?? 0) > 0
? <> ? <>
<div className='flex items-center h-7 px-2 py-0.5'> <div className='flex items-center h-7 px-2 py-0.5'>
<span className='system-xs-regular text-text-tertiary'>{t('app.accessControlDialog.operateGroupAndMember.allMembers')}</span> <span className='system-xs-regular text-text-tertiary'>{t('app.accessControlDialog.operateGroupAndMember.allMembers')}</span>
</div> </div>
<RenderGroupOrMember data={data?.subjects ?? []} /> {renderGroupOrMember(data?.pages ?? [])}
{isFetchingNextPage && <div className='p-1'><Loading /></div>}
<div ref={anchorRef} className='h-0'> </div> <div ref={anchorRef} className='h-0'> </div>
</> </>
: isPending
? null
: <div className='flex items-center justify-center h-7 px-2 py-0.5'> : <div className='flex items-center justify-center h-7 px-2 py-0.5'>
<span className='system-xs-regular text-text-tertiary'>{t('app.accessControlDialog.operateGroupAndMember.noResult')}</span> <span className='system-xs-regular text-text-tertiary'>{t('app.accessControlDialog.operateGroupAndMember.noResult')}</span>
</div> </div>
} }
{
isPending && <div className='p-1'><Loading /></div>
}
</div> </div>
</PortalToFollowElemContent> </PortalToFollowElemContent>
</PortalToFollowElem> </PortalToFollowElem>
} }
type RenderGroupOrMemberProps = { type GroupOrMemberData = { subjects: Subject[]; currPage: number }[]
data: Subject[] function renderGroupOrMember(data: GroupOrMemberData) {
} return data?.map((page) => {
return <div key={`search_group_member_page_${page.currPage}`} className='p-1'>
function RenderGroupOrMember({ data }: RenderGroupOrMemberProps) { {page.subjects?.map((item, index) => {
return <div className='p-1'>
{data.map((item, index) => {
if (item.subjectType === SubjectType.Group) if (item.subjectType === SubjectType.Group)
return <GroupItem key={index} group={(item as SubjectGroup).groupData} /> return <GroupItem key={index} group={(item as SubjectGroup).groupData} />
return <MemberItem key={index} member={(item as SubjectAccount).accountData} /> return <MemberItem key={index} member={(item as SubjectAccount).accountData} />
})} })}
</div> </div>
}) ?? null
} }
type GroupItemProps = { type GroupItemProps = {

View File

@ -1,4 +1,4 @@
import { useMutation, useQuery } from '@tanstack/react-query' import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query'
import { get, post } from './base' import { get, post } from './base'
import type { AccessControlAccount, AccessControlGroup, Subject } from '@/models/access-control' import type { AccessControlAccount, AccessControlGroup, Subject } from '@/models/access-control'
import type { App } from '@/types/app' import type { App } from '@/types/app'
@ -17,21 +17,28 @@ type SearchResults = {
currPage: number currPage: number
totalPages: number totalPages: number
subjects: Subject[] subjects: Subject[]
has_more: boolean hasMore: boolean
} }
export const useSearchForWhiteListCandidates = (query: { appId?: string; keyword?: string; pageNumber?: number; resultsPerPage?: number }, enabled: boolean) => { export const useSearchForWhiteListCandidates = (query: { keyword?: string; resultsPerPage?: number }, enabled: boolean) => {
return useQuery({ return useInfiniteQuery({
queryKey: [NAME_SPACE, 'app-whitelist-candidates', query], queryKey: [NAME_SPACE, 'app-whitelist-candidates', query],
queryFn: () => { queryFn: ({ pageParam }) => {
const params = new URLSearchParams() const params = new URLSearchParams()
Object.keys(query).forEach((key) => { Object.keys(query).forEach((key) => {
const typedKey = key as keyof typeof query const typedKey = key as keyof typeof query
if (query[typedKey]) if (query[typedKey])
params.append(key, `${query[typedKey]}`) params.append(key, `${query[typedKey]}`)
}) })
params.append('pageNumber', `${pageParam}`)
return get<SearchResults>(`/enterprise/webapp/app/subject/search?${new URLSearchParams(params).toString()}`) return get<SearchResults>(`/enterprise/webapp/app/subject/search?${new URLSearchParams(params).toString()}`)
}, },
initialPageParam: 1,
getNextPageParam: (lastPage) => {
if (lastPage.hasMore)
return lastPage.currPage + 1
return undefined
},
enabled, enabled,
}) })
} }