pref: member list (#4344)

* chore: search member new api

* chore: permission

* fix: ts error

* fix: member modal
This commit is contained in:
Finley Ge
2025-03-26 22:10:03 +08:00
committed by archer
parent 484b87478c
commit 2ebb2ccc9c
15 changed files with 508 additions and 548 deletions

View File

@@ -29,12 +29,14 @@ function OrgInfoModal({
editOrg,
onClose,
onSuccess,
updateCurrentOrg
updateCurrentOrg,
parentId
}: {
editOrg: OrgFormType;
onClose: () => void;
onSuccess: () => void;
updateCurrentOrg: (data: { name?: string; avatar?: string; description?: string }) => void;
parentId?: string;
}) {
const { t } = useTranslation();
@@ -51,10 +53,11 @@ function OrgInfoModal({
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
async (data: OrgFormType) => {
if (parentId === undefined) return;
return postCreateOrg({
name: data.name,
avatar: data.avatar,
path: editOrg.path,
orgId: parentId,
description: data.description
});
},

View File

@@ -1,14 +1,5 @@
import { getOrgMembers, putUpdateOrgMembers } from '@/web/support/user/team/org/api';
import {
Box,
Button,
Checkbox,
Flex,
Grid,
HStack,
ModalBody,
ModalFooter
} from '@chakra-ui/react';
import { putUpdateOrgMembers } from '@/web/support/user/team/org/api';
import { Box, Button, Flex, Grid, HStack, ModalBody, ModalFooter } from '@chakra-ui/react';
import type { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -17,11 +8,11 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
import { getTeamMembers } from '@/web/support/user/team/api';
import MemberItemCard from '@/components/support/permission/MemberManager/MemberItemCard';
export type GroupFormType = {
members: {
@@ -30,16 +21,6 @@ export type GroupFormType = {
}[];
};
function CheckboxIcon({
name
}: {
isChecked?: boolean;
isIndeterminate?: boolean;
name: IconNameType;
}) {
return <MyIcon name={name} w="12px" />;
}
function OrgMemberManageModal({
currentOrg,
refetchOrgs,
@@ -56,17 +37,24 @@ function OrgMemberManageModal({
ScrollData: MemberScrollData,
isLoading: isLoadingMembers
} = useScrollPagination(getTeamMembers, {
pageSize: 20
pageSize: 20,
params: {
withOrgs: true,
withPermission: false,
status: 'active'
}
});
const {
data: orgMembers,
ScrollData: OrgMemberScrollData,
isLoading: isLoadingOrgMembers
} = useScrollPagination(getOrgMembers, {
pageSize: 20,
} = useScrollPagination(getTeamMembers, {
pageSize: 100000,
params: {
orgPath: getOrgChildrenPath(currentOrg)
orgId: currentOrg._id,
withOrgs: false,
withPermission: false
}
});
@@ -83,11 +71,6 @@ function OrgMemberManageModal({
}, [orgMembers]);
const [searchKey, setSearchKey] = useState('');
const filterMembers = useMemo(() => {
if (!searchKey) return allMembers;
const regx = new RegExp(searchKey, 'i');
return allMembers.filter((member) => regx.test(member.memberName));
}, [searchKey, allMembers]);
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
() => {
@@ -165,35 +148,20 @@ function OrgMemberManageModal({
}}
/>
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} isLoading={isLoadingMembers}>
{filterMembers.map((member) => {
{allMembers.map((member) => {
return (
<HStack
py="2"
px={3}
borderRadius={'md'}
alignItems="center"
<MemberItemCard
avatar={member.avatar}
key={member.tmbId}
cursor={'pointer'}
_hover={{
bg: 'myGray.50',
...(!isSelected(member.tmbId) ? { svg: { color: 'myGray.50' } } : {})
}}
_notLast={{ mb: 2 }}
onClick={() => handleToggleSelect(member.tmbId)}
>
<Checkbox
isChecked={!!isSelected(member.tmbId)}
icon={<CheckboxIcon name={'common/check'} />}
pointerEvents="none"
/>
<Avatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
<Box>{member.memberName}</Box>
</HStack>
name={member.memberName}
onChange={() => handleToggleSelect(member.tmbId)}
isChecked={!!isSelected(member.tmbId)}
orgs={member.orgs}
/>
);
})}
</MemberScrollData>
</Flex>
{/* <Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'100%'}> */}
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
<OrgMemberScrollData
mt={3}

View File

@@ -20,8 +20,6 @@ function OrgMoveModal({
}) {
const { t } = useTranslation();
const [selectedOrg, setSelectedOrg] = useState<OrgListItemType>();
const { userInfo } = useUserStore();
const team = userInfo?.team!;
const { runAsync: onMoveOrg, loading } = useRequest2(putMoveOrg, {
onSuccess: () => {
@@ -39,7 +37,7 @@ function OrgMoveModal({
iconColor="primary.600"
>
<ModalBody>
<OrgTree selectedOrg={selectedOrg} setSelectedOrg={setSelectedOrg} />
<OrgTree selectedOrg={selectedOrg} setSelectedOrg={setSelectedOrg} movingOrg={movingOrg} />
</ModalBody>
<ModalFooter>
<Button

View File

@@ -14,17 +14,19 @@ function OrgTreeNode({
org,
selectedOrg,
setSelectedOrg,
index = 0
index = 0,
movingOrg
}: {
org: OrgListItemType;
selectedOrg?: OrgListItemType;
setSelectedOrg: (org?: OrgListItemType) => void;
index?: number;
movingOrg: OrgListItemType;
}) {
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
const [canBeExpanded, setCanBeExpanded] = useState(true);
const { data: orgs = [], runAsync: getOrgs } = useRequest2(() =>
getOrgList({ orgPath: getOrgChildrenPath(org) })
getOrgList({ orgId: org._id, withPermission: false })
);
const onClickExpand = async () => {
const data = await getOrgs();
@@ -34,6 +36,9 @@ function OrgTreeNode({
toggleIsExpanded.toggle();
};
if (org._id === movingOrg._id) {
return <></>;
}
return (
<Box userSelect={'none'}>
<HStack
@@ -78,6 +83,7 @@ function OrgTreeNode({
orgs.map((child) => (
<Box key={child._id} mt={0.5}>
<OrgTreeNode
movingOrg={movingOrg}
org={child}
index={index + 1}
selectedOrg={selectedOrg}
@@ -91,10 +97,12 @@ function OrgTreeNode({
function OrgTree({
selectedOrg,
setSelectedOrg
setSelectedOrg,
movingOrg
}: {
selectedOrg?: OrgListItemType;
setSelectedOrg: (org?: OrgListItemType) => void;
movingOrg: OrgListItemType;
}) {
const { userInfo } = useUserStore();
const root: OrgListItemType = {
@@ -107,6 +115,7 @@ function OrgTree({
return (
<OrgTreeNode
movingOrg={movingOrg}
key={'root'}
org={root}
selectedOrg={selectedOrg}

View File

@@ -78,9 +78,6 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
const [editOrg, setEditOrg] = useState<OrgFormType>();
const [manageMemberOrg, setManageMemberOrg] = useState<OrgListItemType>();
const [movingOrg, setMovingOrg] = useState<OrgListItemType>();
const [searchOrg, setSearchOrg] = useState('');
const {
currentOrg,
orgs,
@@ -91,7 +88,9 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
MemberScrollData,
onPathClick,
refresh,
updateCurrentOrg
updateCurrentOrg,
setSearchKey,
searchKey
} = useOrg();
// Delete org
@@ -123,12 +122,6 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
onSuccess: refresh
});
const searchedOrgs = useMemo(() => {
if (!searchOrg) return [];
return orgs.filter((org) => org.name.includes(searchOrg));
}, [orgs, searchOrg]);
return (
<>
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
@@ -136,8 +129,8 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
<Box w="200px">
<SearchInput
placeholder={t('account_team:search_org')}
value={searchOrg}
onChange={(e) => setSearchOrg(e.target.value)}
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
/>
</Box>
</Flex>
@@ -166,102 +159,55 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
</Tr>
</Thead>
<Tbody>
{searchedOrgs.map((org) => (
<Tr key={org._id} overflow={'unset'} onClick={() => onClickOrg(org)}>
<Td>
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
<MemberTag name={org.name} avatar={org.avatar!} />
<Tag size="sm">{org.total}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
{orgs
.filter((org) => org.path !== '')
.map((org) => (
<Tr key={org._id} overflow={'unset'}>
<Td>
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.total}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
</Td>
)}
</Tr>
))}
{!searchOrg &&
orgs
.filter((org) => org.path !== '')
.map((org) => (
<Tr key={org._id} overflow={'unset'}>
<Td>
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
<MemberTag name={org.name} avatar={org.avatar} />
<Tag size="sm">{org.total}</Tag>
<MyIcon
name="core/chat/chevronRight"
w={'1rem'}
h={'1rem'}
color={'myGray.500'}
/>
</HStack>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
</Td>
{isTeamAdmin && !isSyncMember && (
<Td w={'6rem'}>
<MyMenu
trigger="hover"
Button={<IconButton name="more" />}
menuList={[
{
children: [
{
icon: 'edit',
label: t('account_team:edit_info'),
onClick: () => setEditOrg(org)
},
{
icon: 'common/file/move',
label: t('common:Move'),
onClick: () => setMovingOrg(org)
},
{
icon: 'delete',
label: t('account_team:delete'),
type: 'danger',
onClick: () => deleteOrgHandler(org._id)
}
]
}
]}
/>
</Td>
)}
</Tr>
))}
{!searchOrg &&
)}
</Tr>
))}
{!searchKey &&
members.map((member) => {
return (
<Tr key={member.tmbId}>
@@ -403,6 +349,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
onClose={() => setEditOrg(undefined)}
onSuccess={refresh}
updateCurrentOrg={updateCurrentOrg}
parentId={currentOrg._id}
/>
)}
{!!movingOrg && (