From d1de872d86bf3b5619a2e7b2f9950710a1a8020c Mon Sep 17 00:00:00 2001 From: NFish Date: Wed, 9 Apr 2025 14:56:43 +0800 Subject: [PATCH] feat: support search group or members --- .../add-member-or-group-pop.tsx | 87 ++++++++++++++----- web/i18n/en-US/app.ts | 1 + web/i18n/zh-Hans/app.ts | 1 + web/models/access-control.ts | 29 +++++++ web/service/access-control.ts | 35 ++++++++ 5 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 web/models/access-control.ts create mode 100644 web/service/access-control.ts diff --git a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx index 950519e02b..28e668a5c9 100644 --- a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx +++ b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx @@ -1,17 +1,44 @@ 'use client' import { RiAddCircleFill, RiArrowRightSLine, RiOrganizationChart } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import { useState } from 'react' +import { useEffect, useRef, useState } from 'react' +import { useDebounce } from 'ahooks' import Avatar from '../../base/avatar' import Button from '../../base/button' import Checkbox from '../../base/checkbox' import Input from '../../base/input' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '../../base/portal-to-follow-elem' +import Loading from '../../base/loading' import classNames from '@/utils/classnames' +import { useSearchForWhiteListCandidates } from '@/service/access-control' +import type { AccessControlAccount, AccessControlGroup, Subject, SubjectAccount, SubjectGroup } from '@/models/access-control' +import { SubjectType } from '@/models/access-control' export default function AddMemberOrGroupDialog() { const { t } = useTranslation() const [open, setOpen] = useState(false) + const [keyword, setKeyword] = useState('') + const [pageNumber, setPageNumber] = useState(1) + const debouncedKeyword = useDebounce(keyword, { wait: 500 }) + + const { isPending, data } = useSearchForWhiteListCandidates({ keyword: debouncedKeyword, pageNumber, resultsPerPage: 10 }, open) + const handleKeywordChange = (e: React.ChangeEvent) => { + setKeyword(e.target.value) + } + + const anchorRef = useRef(null) + useEffect(() => { + const hasMore = data?.has_more ?? true + let observer: IntersectionObserver | undefined + if (anchorRef.current) { + observer = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting && !isPending && hasMore) + setPageNumber((size: number) => size + 1) + }, { rootMargin: '20px' }) + observer.observe(anchorRef.current) + } + return () => observer?.disconnect() + }, [isPending, setPageNumber, anchorRef, data]) return @@ -21,34 +48,51 @@ export default function AddMemberOrGroupDialog() { -
-
- +
+
+
-
- {t('app.accessControlDialog.operateGroupAndMember.allMembers')} -
- + { + (data?.subjects?.length ?? 0) > 0 + ? <> +
+ {t('app.accessControlDialog.operateGroupAndMember.allMembers')} +
+ +
+ + : isPending + ? null + :
+ {t('app.accessControlDialog.operateGroupAndMember.noResult')} +
+ } + { + isPending &&
+ }
} type RenderGroupOrMemberProps = { - data: any[] + data: Subject[] } function RenderGroupOrMember({ data }: RenderGroupOrMemberProps) { return
{data.map((item, index) => { - if (item.type === 'group') - return - return + if (item.subjectType === SubjectType.Group) + return + return })}
} -function GroupItem() { +type GroupItemProps = { + group: AccessControlGroup +} +function GroupItem({ group }: GroupItemProps) { const { t } = useTranslation() return @@ -58,8 +102,8 @@ function GroupItem() {
-

Name

-

5

+

{group.name}

+

{group.groupSize}