pref: member/org/gourp list (#4295)
* refactor: org api * refactor: org api * pref: member/org/group list * feat: change group owner api * fix: manage org member * pref: member search
This commit is contained in:
@@ -26,7 +26,12 @@ import { useMemo, useState } from 'react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import MemberTag from '@/components/support/user/team/Info/MemberTag';
|
||||
import { TeamContext } from '../context';
|
||||
import { deleteOrg, deleteOrgMember, getOrgList } from '@/web/support/user/team/org/api';
|
||||
import {
|
||||
deleteOrg,
|
||||
deleteOrgMember,
|
||||
getOrgList,
|
||||
getOrgMembers
|
||||
} from '@/web/support/user/team/org/api';
|
||||
|
||||
import IconButton from './IconButton';
|
||||
import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
|
||||
@@ -37,8 +42,9 @@ import Path from '@/components/common/folder/Path';
|
||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { delRemoveMember } from '@/web/support/user/team/api';
|
||||
import { delRemoveMember, getTeamMembers } from '@/web/support/user/team/api';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
|
||||
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
|
||||
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
|
||||
@@ -77,66 +83,63 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, isTeamAdmin } = useUserStore();
|
||||
const [searchOrg, setSearchOrg] = useState('');
|
||||
const [orgStack, setOrgStack] = useState<OrgType[]>([]);
|
||||
const currentOrg = useMemo(() => orgStack[orgStack.length - 1], [orgStack]);
|
||||
|
||||
const [rootOrg, setRootOrg] = useState<OrgType>();
|
||||
|
||||
const { data: members = [], ScrollData: MemberScrollData } = useScrollPagination(getOrgMembers, {
|
||||
pageSize: 20,
|
||||
params: {
|
||||
orgId: currentOrg?._id ?? rootOrg?._id
|
||||
},
|
||||
refreshDeps: [currentOrg?._id, rootOrg?._id]
|
||||
});
|
||||
|
||||
const { members, MemberScrollData, refetchMembers } = useContextSelector(TeamContext, (v) => v);
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const isSyncMember = feConfigs.register_method?.includes('sync');
|
||||
const [parentPath, setParentPath] = useState('');
|
||||
const [path, setPath] = useState('');
|
||||
|
||||
const {
|
||||
data: orgs = [],
|
||||
loading: isLoadingOrgs,
|
||||
refresh: refetchOrgs
|
||||
} = useRequest2(getOrgList, {
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
|
||||
const currentOrgs = useMemo(() => {
|
||||
if (orgs.length === 0) return [];
|
||||
if (parentPath === '') {
|
||||
const rootOrg = orgs.find((org) => org.path === '');
|
||||
if (rootOrg) {
|
||||
setParentPath(getOrgChildrenPath(rootOrg));
|
||||
} = useRequest2(
|
||||
() => {
|
||||
// sync path to orgStack
|
||||
const splitPath = path.split('/').filter(Boolean);
|
||||
const orgs = orgStack.filter((o) => splitPath.includes(o.pathId));
|
||||
setOrgStack(orgs);
|
||||
return getOrgList(path);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId, path],
|
||||
onSuccess: (data) => {
|
||||
if (!rootOrg) {
|
||||
setRootOrg(data[0]);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return orgs
|
||||
.filter((org) => org.path === parentPath)
|
||||
.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
// Member + org
|
||||
count:
|
||||
item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length
|
||||
};
|
||||
});
|
||||
}, [orgs, parentPath]);
|
||||
|
||||
const currentOrg = useMemo(() => {
|
||||
const splitPath = parentPath.split('/');
|
||||
const currentOrgId = splitPath[splitPath.length - 1];
|
||||
if (!currentOrgId) return;
|
||||
|
||||
return orgs.find((org) => org.pathId === currentOrgId);
|
||||
}, [orgs, parentPath]);
|
||||
);
|
||||
|
||||
const paths = useMemo(() => {
|
||||
const splitPath = parentPath.split('/').filter(Boolean);
|
||||
return splitPath
|
||||
.map((id) => {
|
||||
const org = orgs.find((org) => org.pathId === id)!;
|
||||
|
||||
return orgStack
|
||||
.map((org) => {
|
||||
if (org?.path === '') return;
|
||||
|
||||
return {
|
||||
parentId: getOrgChildrenPath(org),
|
||||
parentName: org.name
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as ParentTreePathItemType[];
|
||||
}, [parentPath, orgs]);
|
||||
}, [orgStack]);
|
||||
|
||||
const onClickOrg = (org: OrgType) => {
|
||||
setOrgStack([...orgStack, org]);
|
||||
setPath(getOrgChildrenPath(org));
|
||||
};
|
||||
|
||||
const [editOrg, setEditOrg] = useState<OrgFormType>();
|
||||
const [manageMemberOrg, setManageMemberOrg] = useState<OrgType>();
|
||||
@@ -174,7 +177,6 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const { runAsync: deleteMemberFromTeamReq } = useRequest2(delRemoveMember, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -184,9 +186,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
return orgs
|
||||
.filter((org) => org.name.includes(searchOrg))
|
||||
.map((org) => ({
|
||||
...org,
|
||||
count:
|
||||
org.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(org)).length
|
||||
...org
|
||||
}));
|
||||
}, [orgs, searchOrg]);
|
||||
|
||||
@@ -210,11 +210,10 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
isLoading={isLoadingOrgs}
|
||||
>
|
||||
<Box mb={3}>
|
||||
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
|
||||
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setPath} />
|
||||
</Box>
|
||||
<Flex flex={'1 0 0'} h={0} w={'100%'} gap={'4'}>
|
||||
<MemberScrollData h={'100%'} fontSize={'sm'} flexGrow={1}>
|
||||
{/* Table */}
|
||||
<MemberScrollData flex="1">
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
@@ -229,21 +228,11 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{searchedOrgs.map((org) => (
|
||||
<Tr
|
||||
key={org._id}
|
||||
overflow={'unset'}
|
||||
onClick={() => setParentPath(getOrgChildrenPath(org))}
|
||||
>
|
||||
<Tr key={org._id} overflow={'unset'} onClick={() => onClickOrg(org)}>
|
||||
<Td>
|
||||
<HStack
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setParentPath(getOrgChildrenPath(org));
|
||||
setSearchOrg('');
|
||||
}}
|
||||
>
|
||||
<HStack cursor={'pointer'} onClick={() => onClickOrg(org)}>
|
||||
<MemberTag name={org.name} avatar={org.avatar} />
|
||||
<Tag size="sm">{org.count}</Tag>
|
||||
<Tag size="sm">{org.total}</Tag>
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w={'1rem'}
|
||||
@@ -252,70 +241,93 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
/>
|
||||
</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)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
{!searchOrg &&
|
||||
currentOrgs.map((org) => (
|
||||
<Tr key={org._id} overflow={'unset'}>
|
||||
<Td>
|
||||
<HStack
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setParentPath(getOrgChildrenPath(org));
|
||||
setSearchOrg('');
|
||||
}}
|
||||
>
|
||||
<MemberTag name={org.name} avatar={org.avatar} />
|
||||
<Tag size="sm">{org.count}</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>
|
||||
))}
|
||||
{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 &&
|
||||
currentOrg?.members.map((member) => {
|
||||
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
||||
if (!memberInfo) return null;
|
||||
|
||||
members.map((member) => {
|
||||
return (
|
||||
<Tr key={member.tmbId}>
|
||||
<Td>
|
||||
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
|
||||
<MemberTag name={member.memberName} avatar={member.avatar} />
|
||||
</Td>
|
||||
<Td w={'6rem'}>
|
||||
{isTeamAdmin && (
|
||||
@@ -333,14 +345,14 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
}
|
||||
},
|
||||
label: t('account_team:delete_from_team', {
|
||||
username: memberInfo.memberName
|
||||
username: member.memberName
|
||||
}),
|
||||
onClick: () => {
|
||||
openDeleteMemberFromTeamModal(
|
||||
() => deleteMemberFromTeamReq(member.tmbId),
|
||||
undefined,
|
||||
t('account_team:confirm_delete_from_team', {
|
||||
username: memberInfo.memberName
|
||||
username: member.memberName
|
||||
})
|
||||
)();
|
||||
}
|
||||
@@ -362,7 +374,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
deleteMemberReq(currentOrg._id, member.tmbId),
|
||||
undefined,
|
||||
t('account_team:confirm_delete_from_org', {
|
||||
username: memberInfo.memberName
|
||||
username: member.memberName
|
||||
})
|
||||
)()
|
||||
}
|
||||
@@ -385,22 +397,29 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
{!isSyncMember && (
|
||||
<VStack w={'180px'} alignItems={'start'}>
|
||||
<HStack gap={'6px'}>
|
||||
<Avatar src={currentOrg?.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
|
||||
<Avatar
|
||||
src={currentOrg?.avatar || userInfo?.team.avatar}
|
||||
w={'1rem'}
|
||||
h={'1rem'}
|
||||
rounded={'xs'}
|
||||
/>
|
||||
<Box fontWeight={500} color={'myGray.900'}>
|
||||
{currentOrg?.name}
|
||||
{currentOrg?.name || userInfo?.team.teamName}
|
||||
</Box>
|
||||
{currentOrg?.path !== '' && (
|
||||
{currentOrg && currentOrg?.path !== '' && (
|
||||
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
|
||||
)}
|
||||
</HStack>
|
||||
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
|
||||
{currentOrg && (
|
||||
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
|
||||
)}
|
||||
|
||||
<Divider my={'20px'} />
|
||||
|
||||
<Box fontWeight={500} fontSize="sm" color="myGray.900">
|
||||
{t('common:common.Action')}
|
||||
</Box>
|
||||
{currentOrg && isTeamAdmin && (
|
||||
{isTeamAdmin && (
|
||||
<VStack gap="13px" w="100%">
|
||||
<ActionButton
|
||||
icon="common/add2"
|
||||
@@ -408,16 +427,16 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
onClick={() => {
|
||||
setEditOrg({
|
||||
...defaultOrgForm,
|
||||
parentId: currentOrg?._id
|
||||
parentId: currentOrg?._id ?? rootOrg?._id
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
icon="common/administrator"
|
||||
text={t('account_team:manage_member')}
|
||||
onClick={() => setManageMemberOrg(currentOrg)}
|
||||
onClick={() => setManageMemberOrg(currentOrg ?? rootOrg)}
|
||||
/>
|
||||
{currentOrg?.path !== '' && (
|
||||
{currentOrg && currentOrg?.path !== '' && (
|
||||
<>
|
||||
<ActionButton
|
||||
icon="common/file/move"
|
||||
|
||||
Reference in New Issue
Block a user