Commit 5a47af6f by Finley Ge Committed by archer

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
parent 6ea57e46
...@@ -19,9 +19,23 @@ type GroupMemberSchemaType = { ...@@ -19,9 +19,23 @@ type GroupMemberSchemaType = {
type MemberGroupType = MemberGroupSchemaType & { type MemberGroupType = MemberGroupSchemaType & {
members: { members: {
tmbId: string; tmbId: string;
role: `${GroupMemberRole}`; name: string;
}[]; // we can get tmb's info from other api. there is no need but only need to get tmb's id avatar: string;
permission: TeamPermission; }[];
count: number;
owner: {
tmbId: string;
name: string;
avatar: string;
};
canEdit: boolean;
}; };
type MemberGroupListType = MemberGroupType[]; type MemberGroupListType = MemberGroupType[];
type GroupMemberItemType = {
tmbId: string;
name: string;
avatar: string;
role: `${GroupMemberRole}`;
};
import type { TeamPermission } from 'support/permission/user/controller'; import type { TeamPermission } from 'support/permission/user/controller';
import { ResourcePermissionType } from '../type'; import { ResourcePermissionType } from '../type';
import { SourceMemberType } from 'support/user/type';
type OrgSchemaType = { type OrgSchemaType = {
_id: string; _id: string;
...@@ -23,4 +24,5 @@ type OrgType = Omit<OrgSchemaType, 'avatar'> & { ...@@ -23,4 +24,5 @@ type OrgType = Omit<OrgSchemaType, 'avatar'> & {
avatar: string; avatar: string;
permission: TeamPermission; permission: TeamPermission;
members: OrgMemberSchemaType[]; members: OrgMemberSchemaType[];
total: number; // members + children orgs
}; };
...@@ -82,6 +82,7 @@ export type TeamMemberItemType = { ...@@ -82,6 +82,7 @@ export type TeamMemberItemType = {
contact?: string; contact?: string;
createTime: Date; createTime: Date;
updateTime?: Date; updateTime?: Date;
orgs?: string[]; // full path name, pattern: /teamName/orgname1/orgname2
}; };
export type TeamTagItemType = { export type TeamTagItemType = {
......
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, getMongoModel } from '../../../common/mongo'; import { connectionMongo, getMongoModel } from '../../../common/mongo';
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
import { GroupMemberCollectionName } from './groupMemberSchema';
const { Schema } = connectionMongo; const { Schema } = connectionMongo;
export const MemberGroupCollectionName = 'team_member_groups'; export const MemberGroupCollectionName = 'team_member_groups';
......
...@@ -90,6 +90,6 @@ export async function createRootOrg({ ...@@ -90,6 +90,6 @@ export async function createRootOrg({
path: '' path: ''
} }
], ],
{ session } { session, ordered: true }
); );
} }
...@@ -10,7 +10,16 @@ import { Box, Flex } from '@chakra-ui/react'; ...@@ -10,7 +10,16 @@ import { Box, Flex } from '@chakra-ui/react';
* @param [groupId] - group id to make the key unique * @param [groupId] - group id to make the key unique
* @returns * @returns
*/ */
function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] }) { function AvatarGroup({
avatars,
max = 3,
total
}: {
max?: number;
avatars: string[];
total?: number;
}) {
const remain = total ?? avatars.length - max;
return ( return (
<Flex position="relative"> <Flex position="relative">
{avatars.slice(0, max).map((avatar, index) => ( {avatars.slice(0, max).map((avatar, index) => (
...@@ -24,10 +33,10 @@ function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] }) ...@@ -24,10 +33,10 @@ function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] })
borderRadius={'50%'} borderRadius={'50%'}
/> />
))} ))}
{avatars.length > max && ( {remain > 0 && (
<Box <Box
position="relative" position="relative"
left={`${(max - 1) * 15}px`} left={`${(max - 1) * 15 + 15}px`}
w={'24px'} w={'24px'}
h={'24px'} h={'24px'}
borderRadius="50%" borderRadius="50%"
...@@ -37,7 +46,7 @@ function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] }) ...@@ -37,7 +46,7 @@ function AvatarGroup({ avatars, max = 3 }: { max?: number; avatars: string[] })
fontSize="sm" fontSize="sm"
color="myGray.500" color="myGray.500"
> >
+{avatars.length - max} +{String(remain)}
</Box> </Box>
)} )}
</Flex> </Flex>
......
...@@ -4,8 +4,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; ...@@ -4,8 +4,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import Tag from '@fastgpt/web/components/common/Tag'; import Tag from '@fastgpt/web/components/common/Tag';
import React from 'react'; import React from 'react';
function OrgTags({ orgs, type = 'simple' }: { orgs: string[]; type?: 'simple' | 'tag' }) { function OrgTags({ orgs, type = 'simple' }: { orgs?: string[]; type?: 'simple' | 'tag' }) {
return ( return orgs?.length ? (
<MyTooltip <MyTooltip
label={ label={
<VStack gap="1" alignItems={'start'}> <VStack gap="1" alignItems={'start'}>
...@@ -39,6 +39,10 @@ function OrgTags({ orgs, type = 'simple' }: { orgs: string[]; type?: 'simple' | ...@@ -39,6 +39,10 @@ function OrgTags({ orgs, type = 'simple' }: { orgs: string[]; type?: 'simple' |
</Flex> </Flex>
)} )}
</MyTooltip> </MyTooltip>
) : (
<Box fontSize="xs" fontWeight={400} w="full" color="myGray.400" whiteSpace={'nowrap'}>
-
</Box>
); );
} }
......
...@@ -18,7 +18,7 @@ import React, { useMemo, useRef, useState } from 'react'; ...@@ -18,7 +18,7 @@ import React, { useMemo, useRef, useState } from 'react';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { TeamContext } from '../context'; import { TeamContext } from '../context';
import { putUpdateGroup } from '@/web/support/user/team/group/api'; import { getGroupMembers, putUpdateGroup } from '@/web/support/user/team/group/api';
import { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant'; import { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
...@@ -46,13 +46,26 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ...@@ -46,13 +46,26 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
return groups.find((item) => item._id === editGroupId); return groups.find((item) => item._id === editGroupId);
}, [editGroupId, groups]); }, [editGroupId, groups]);
const { data: groupMembers } = useRequest2(
() => {
if (editGroupId) return getGroupMembers(editGroupId);
return Promise.resolve(undefined);
},
{
manual: false,
onSuccess: (data) => {
setMembers(data ?? []);
}
}
);
const allMembers = useContextSelector(TeamContext, (v) => v.members); const allMembers = useContextSelector(TeamContext, (v) => v.members);
const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers); const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers);
const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData); const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData);
const [hoveredMemberId, setHoveredMemberId] = useState<string>(); const [hoveredMemberId, setHoveredMemberId] = useState<string>();
const selectedMembersRef = useRef<HTMLDivElement>(null); const selectedMembersRef = useRef<HTMLDivElement>(null);
const [members, setMembers] = useState(group?.members || []); const [members, setMembers] = useState(groupMembers || []);
const [searchKey, setSearchKey] = useState(''); const [searchKey, setSearchKey] = useState('');
const filtered = useMemo(() => { const filtered = useMemo(() => {
...@@ -67,6 +80,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ...@@ -67,6 +80,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2( const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
async () => { async () => {
if (!editGroupId || !members.length) return; if (!editGroupId || !members.length) return;
console.log(members);
return putUpdateGroup({ return putUpdateGroup({
groupId: editGroupId, groupId: editGroupId,
memberList: members memberList: members
...@@ -89,10 +103,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ...@@ -89,10 +103,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
}, [members, userInfo]); }, [members, userInfo]);
const handleToggleSelect = (memberId: string) => { const handleToggleSelect = (memberId: string) => {
if ( if (myRole === 'owner' && memberId === members.find((item) => item.role === 'owner')?.tmbId) {
myRole === 'owner' &&
memberId === group?.members.find((item) => item.role === 'owner')?.tmbId
) {
toast({ toast({
title: t('user:team.group.toast.can_not_delete_owner'), title: t('user:team.group.toast.can_not_delete_owner'),
status: 'error' status: 'error'
...@@ -102,7 +113,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ...@@ -102,7 +113,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
if ( if (
myRole === 'admin' && myRole === 'admin' &&
group?.members.find((item) => String(item.tmbId) === memberId)?.role !== 'member' members.find((item) => String(item.tmbId) === memberId)?.role !== 'member'
) { ) {
return; return;
} }
...@@ -110,7 +121,17 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ...@@ -110,7 +121,17 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
if (isSelected(memberId)) { if (isSelected(memberId)) {
setMembers(members.filter((item) => item.tmbId !== memberId)); setMembers(members.filter((item) => item.tmbId !== memberId));
} else { } else {
setMembers([...members, { tmbId: memberId, role: 'member' }]); const member = allMembers.find((m) => m.tmbId === memberId);
if (!member) return;
setMembers([
...members,
{
name: member.memberName,
avatar: member.avatar,
tmbId: member.tmbId,
role: 'member'
}
]);
} }
}; };
...@@ -188,7 +209,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ...@@ -188,7 +209,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}> <Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
<Box mt={2}>{t('common:chosen') + ': ' + members.length}</Box> <Box mt={2}>{t('common:chosen') + ': ' + members.length}</Box>
<MemberScrollData ScrollContainerRef={selectedMembersRef} mt={3} flex={'1 0 0'} h={0}> <MemberScrollData ScrollContainerRef={selectedMembersRef} mt={3} flex={'1 0 0'} h={0}>
{members.map((member) => { {members?.map((member) => {
return ( return (
<HStack <HStack
onMouseEnter={() => setHoveredMemberId(member.tmbId)} onMouseEnter={() => setHoveredMemberId(member.tmbId)}
...@@ -202,14 +223,8 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ...@@ -202,14 +223,8 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
_notLast={{ mb: 2 }} _notLast={{ mb: 2 }}
> >
<HStack> <HStack>
<Avatar <Avatar src={member.avatar} w="1.5rem" borderRadius={'md'} />
src={allMembers.find((item) => item.tmbId === member.tmbId)?.avatar} <Box>{member.name}</Box>
w="1.5rem"
borderRadius={'md'}
/>
<Box>
{allMembers.find((item) => item.tmbId === member.tmbId)?.memberName}
</Box>
</HStack> </HStack>
<Box mr="auto"> <Box mr="auto">
{(() => { {(() => {
......
import { putUpdateGroup } from '@/web/support/user/team/group/api'; import { putGroupChangeOwner, putUpdateGroup } from '@/web/support/user/team/group/api';
import { import {
Box, Box,
Flex, Flex,
...@@ -38,10 +38,6 @@ export function ChangeOwnerModal({ ...@@ -38,10 +38,6 @@ export function ChangeOwnerModal({
return item.memberName.toLowerCase().includes(inputValue.toLowerCase()); return item.memberName.toLowerCase().includes(inputValue.toLowerCase());
}); });
const OldOwnerId = useMemo(() => {
return group?.members.find((item) => item.role === 'owner')?.tmbId;
}, [group]);
const [keepAdmin, setKeepAdmin] = useState(true); const [keepAdmin, setKeepAdmin] = useState(true);
const { const {
...@@ -52,36 +48,14 @@ export function ChangeOwnerModal({ ...@@ -52,36 +48,14 @@ export function ChangeOwnerModal({
const [selectedMember, setSelectedMember] = useState<TeamMemberItemType | null>(null); const [selectedMember, setSelectedMember] = useState<TeamMemberItemType | null>(null);
const onChangeOwner = async (tmbId: string) => { const { runAsync, loading } = useRequest2(
if (!group) { (tmbId: string) => putGroupChangeOwner(groupId, tmbId),
return; {
}
const newMemberList = group.members
.map((item) => {
if (item.tmbId === OldOwnerId) {
if (keepAdmin) {
return { tmbId: OldOwnerId, role: 'admin' };
}
return { tmbId: OldOwnerId, role: 'member' };
}
return item;
})
.filter((item) => item.tmbId !== tmbId) as any;
newMemberList.push({ tmbId, role: 'owner' });
return putUpdateGroup({
groupId,
memberList: newMemberList
});
};
const { runAsync, loading } = useRequest2(onChangeOwner, {
onSuccess: () => Promise.all([onClose(), refetchGroups()]), onSuccess: () => Promise.all([onClose(), refetchGroups()]),
successToast: t('common:permission.change_owner_success'), successToast: t('common:permission.change_owner_success'),
errorToast: t('common:permission.change_owner_failed') errorToast: t('common:permission.change_owner_failed')
}); }
);
const onConfirm = async () => { const onConfirm = async () => {
if (!selectedMember) { if (!selectedMember) {
......
...@@ -22,7 +22,7 @@ import MyMenu, { MenuItemType } from '@fastgpt/web/components/common/MyMenu'; ...@@ -22,7 +22,7 @@ import MyMenu, { MenuItemType } from '@fastgpt/web/components/common/MyMenu';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { deleteGroup } from '@/web/support/user/team/group/api'; import { deleteGroup, getGroupMembers } from '@/web/support/user/team/group/api';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import MemberTag from '../../../../components/support/user/team/Info/MemberTag'; import MemberTag from '../../../../components/support/user/team/Info/MemberTag';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
...@@ -39,10 +39,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -39,10 +39,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { groups, refetchGroups, members, refetchMembers } = useContextSelector( const { groups, refetchGroups, members, teamSize } = useContextSelector(TeamContext, (v) => v);
TeamContext,
(v) => v
);
const [editGroup, setEditGroup] = useState<MemberGroupType>(); const [editGroup, setEditGroup] = useState<MemberGroupType>();
const { const {
...@@ -64,7 +61,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -64,7 +61,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, { const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, {
onSuccess: () => { onSuccess: () => {
refetchGroups(); refetchGroups();
refetchMembers();
} }
}); });
...@@ -78,14 +74,9 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -78,14 +74,9 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
onOpenManageGroupMember(); onOpenManageGroupMember();
}; };
const hasGroupManagePer = (group: (typeof groups)[0]) => const hasGroupManagePer = (group: (typeof groups)[0]) => userInfo?.team.permission.hasManagePer;
userInfo?.team.permission.hasManagePer ||
['admin', 'owner'].includes( const isGroupOwner = (group: (typeof groups)[0]) => userInfo?.team.permission.hasManagePer;
group.members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? ''
);
const isGroupOwner = (group: (typeof groups)[0]) =>
userInfo?.team.permission.hasManagePer ||
group.members.find((item) => item.role === 'owner')?.tmbId === userInfo?.team.tmbId;
const { const {
isOpen: isOpenChangeOwner, isOpen: isOpenChangeOwner,
...@@ -143,9 +134,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -143,9 +134,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
} }
avatar={group.avatar} avatar={group.avatar}
/> />
<Box> <Box>({group.name === DefaultGroupName ? teamSize : group.count})</Box>
({group.name === DefaultGroupName ? members.length : group.members.length})
</Box>
</HStack> </HStack>
</Td> </Td>
<Td> <Td>
...@@ -153,26 +142,18 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -153,26 +142,18 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
name={ name={
group.name === DefaultGroupName group.name === DefaultGroupName
? members.find((item) => item.role === 'owner')?.memberName ?? '' ? members.find((item) => item.role === 'owner')?.memberName ?? ''
: members.find( : group.owner.name
(item) =>
item.tmbId ===
group.members.find((item) => item.role === 'owner')?.tmbId
)?.memberName ?? ''
} }
avatar={ avatar={
group.name === DefaultGroupName group.name === DefaultGroupName
? members.find((item) => item.role === 'owner')?.avatar ?? '' ? members.find((item) => item.role === 'owner')?.avatar ?? ''
: members.find( : group.owner.avatar
(i) =>
i.tmbId ===
group.members.find((item) => item.role === 'owner')?.tmbId
)?.avatar ?? ''
} }
/> />
</Td> </Td>
<Td> <Td>
{group.name === DefaultGroupName ? ( {group.name === DefaultGroupName ? (
<AvatarGroup avatars={members.map((v) => v.avatar)} /> <AvatarGroup avatars={members.map((v) => v.avatar)} total={teamSize} />
) : hasGroupManagePer(group) ? ( ) : hasGroupManagePer(group) ? (
<MyTooltip label={t('account_team:manage_member')}> <MyTooltip label={t('account_team:manage_member')}>
<Box cursor="pointer" onClick={() => onManageMember(group)}> <Box cursor="pointer" onClick={() => onManageMember(group)}>
...@@ -180,6 +161,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -180,6 +161,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
avatars={group.members.map( avatars={group.members.map(
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' (v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
)} )}
total={group.count}
/> />
</Box> </Box>
</MyTooltip> </MyTooltip>
...@@ -188,6 +170,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -188,6 +170,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
avatars={group.members.map( avatars={group.members.map(
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' (v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
)} )}
total={group.count}
/> />
)} )}
</Td> </Td>
......
...@@ -30,8 +30,6 @@ import { TeamContext } from './context'; ...@@ -30,8 +30,6 @@ import { TeamContext } from './context';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { delLeaveTeam } from '@/web/support/user/team/api'; import { delLeaveTeam } from '@/web/support/user/team/api';
import { GetSearchUserGroupOrg, postSyncMembers } from '@/web/support/user/api'; import { GetSearchUserGroupOrg, postSyncMembers } from '@/web/support/user/api';
...@@ -52,9 +50,8 @@ const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTa ...@@ -52,9 +50,8 @@ const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTa
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { userInfo } = useUserStore();
const { userInfo, teamPlanStatus } = useUserStore(); const { feConfigs } = useSystemStore();
const { feConfigs, setNotSufficientModalType } = useSystemStore();
const { const {
refetchGroups, refetchGroups,
...@@ -63,8 +60,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -63,8 +60,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
members, members,
refetchMembers, refetchMembers,
onSwitchTeam, onSwitchTeam,
MemberScrollData, MemberScrollData
orgs
} = useContextSelector(TeamContext, (v) => v); } = useContextSelector(TeamContext, (v) => v);
const { const {
...@@ -93,6 +89,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -93,6 +89,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
{ {
manual: false, manual: false,
throttleWait: 500, throttleWait: 500,
debounceWait: 200,
refreshDeps: [searchText] refreshDeps: [searchText]
} }
); );
...@@ -281,16 +278,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { ...@@ -281,16 +278,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
<Td maxW={'300px'}>{member.contact || '-'}</Td> <Td maxW={'300px'}>{member.contact || '-'}</Td>
<Td maxWidth="300px"> <Td maxWidth="300px">
{(() => { {(() => {
const memberOrgs = orgs.filter((org) => return <OrgTags orgs={member.orgs || undefined} type="tag" />;
org.members.find((v) => String(v.tmbId) === String(member.tmbId))
);
const memberPathIds = memberOrgs.map((org) =>
(org.path + '/' + org.pathId).split('/').slice(0)
);
const memberOrgNames = memberPathIds.map((pathIds) =>
pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/')
);
return <OrgTags orgs={memberOrgNames} type="tag" />;
})()} })()}
</Td> </Td>
<Td maxW={'300px'}> <Td maxW={'300px'}>
......
import { putUpdateOrgMembers } from '@/web/support/user/team/org/api'; import { getOrgMembers, putUpdateOrgMembers } from '@/web/support/user/team/org/api';
import { import {
Box, Box,
Button, Button,
...@@ -18,10 +18,11 @@ import MyModal from '@fastgpt/web/components/common/MyModal'; ...@@ -18,10 +18,11 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import type React from 'react'; import type React from 'react';
import { useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { TeamContext } from '../context'; import { TeamContext } from '../context';
import { OrgType } from '@fastgpt/global/support/user/team/org/type'; import { OrgType } from '@fastgpt/global/support/user/team/org/type';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
export type GroupFormType = { export type GroupFormType = {
members: { members: {
...@@ -51,11 +52,21 @@ function OrgMemberManageModal({ ...@@ -51,11 +52,21 @@ function OrgMemberManageModal({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { members: allMembers, MemberScrollData } = useContextSelector(TeamContext, (v) => v); const { members: allMembers, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
const { data: orgMembers, ScrollData: OrgMemberScrollData } = useScrollPagination(getOrgMembers, {
pageSize: 20,
params: {
orgId: currentOrg?._id ?? ''
}
});
const [selectedMembers, setSelectedMembers] = useState<string[]>( const [selectedMembers, setSelectedMembers] = useState<string[]>(
currentOrg.members.map((item) => item.tmbId) orgMembers.map((item) => item.tmbId)
); );
useEffect(() => {
setSelectedMembers(orgMembers.map((item) => item.tmbId));
}, [orgMembers]);
const [searchKey, setSearchKey] = useState(''); const [searchKey, setSearchKey] = useState('');
const filterMembers = useMemo(() => { const filterMembers = useMemo(() => {
if (!searchKey) return allMembers; if (!searchKey) return allMembers;
...@@ -150,9 +161,10 @@ function OrgMemberManageModal({ ...@@ -150,9 +161,10 @@ function OrgMemberManageModal({
})} })}
</MemberScrollData> </MemberScrollData>
</Flex> </Flex>
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}> {/* <Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'100%'}> */}
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
<OrgMemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
<Box mt={2}>{`${t('common:chosen')}:${selectedMembers.length}`}</Box> <Box mt={2}>{`${t('common:chosen')}:${selectedMembers.length}`}</Box>
<Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'400px'}>
{selectedMembers.map((tmbId) => { {selectedMembers.map((tmbId) => {
const member = allMembers.find((item) => item.tmbId === tmbId)!; const member = allMembers.find((item) => item.tmbId === tmbId)!;
return ( return (
...@@ -179,7 +191,7 @@ function OrgMemberManageModal({ ...@@ -179,7 +191,7 @@ function OrgMemberManageModal({
</HStack> </HStack>
); );
})} })}
</Flex> </OrgMemberScrollData>
</Flex> </Flex>
</Grid> </Grid>
</ModalBody> </ModalBody>
......
...@@ -75,6 +75,7 @@ function PermissionManage({ ...@@ -75,6 +75,7 @@ function PermissionManage({
const { data: searchResult } = useRequest2(() => GetSearchUserGroupOrg(searchKey), { const { data: searchResult } = useRequest2(() => GetSearchUserGroupOrg(searchKey), {
manual: false, manual: false,
throttleWait: 500, throttleWait: 500,
debounceWait: 200,
refreshDeps: [searchKey] refreshDeps: [searchKey]
}); });
......
...@@ -104,7 +104,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) ...@@ -104,7 +104,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
refreshList: refetchMemberList, refreshList: refetchMemberList,
ScrollData: MemberScrollData ScrollData: MemberScrollData
} = useScrollPagination(getTeamMembers, { } = useScrollPagination(getTeamMembers, {
pageSize: 1000, pageSize: 20,
params: { params: {
withLeaved: true withLeaved: true
} }
......
...@@ -48,7 +48,7 @@ const Team = () => { ...@@ -48,7 +48,7 @@ const Team = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { setEditTeamData, isLoading, teamSize } = useContextSelector(TeamContext, (v) => v); const { setEditTeamData, teamSize } = useContextSelector(TeamContext, (v) => v);
const Tabs = useMemo( const Tabs = useMemo(
() => ( () => (
...@@ -75,7 +75,7 @@ const Team = () => { ...@@ -75,7 +75,7 @@ const Team = () => {
); );
return ( return (
<AccountContainer isLoading={isLoading}> <AccountContainer>
<Flex h={'100%'} flexDirection={'column'}> <Flex h={'100%'} flexDirection={'column'}>
{/* header */} {/* header */}
<Flex <Flex
......
...@@ -105,6 +105,7 @@ export const GetSearchUserGroupOrg = ( ...@@ -105,6 +105,7 @@ export const GetSearchUserGroupOrg = (
orgs?: boolean; orgs?: boolean;
groups?: boolean; groups?: boolean;
} }
) => GET<SearchResult>('/proApi/support/user/search', { searchKey, ...options }); ) =>
GET<SearchResult>('/proApi/support/user/search', { searchKey, ...options }, { maxQuantity: 1 });
export const ExportMembers = () => GET<{ csv: string }>('/proApi/support/user/team/member/export'); export const ExportMembers = () => GET<{ csv: string }>('/proApi/support/user/team/member/export');
import { DELETE, GET, POST, PUT } from '@/web/common/api/request'; import { DELETE, GET, POST, PUT } from '@/web/common/api/request';
import type { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type'; import type {
GroupMemberItemType,
MemberGroupListType
} from '@fastgpt/global/support/permission/memberGroup/type';
import type { import type {
postCreateGroupData, postCreateGroupData,
putUpdateGroupData putUpdateGroupData
...@@ -15,3 +18,9 @@ export const deleteGroup = (groupId: string) => ...@@ -15,3 +18,9 @@ export const deleteGroup = (groupId: string) =>
export const putUpdateGroup = (data: putUpdateGroupData) => export const putUpdateGroup = (data: putUpdateGroupData) =>
PUT('/proApi/support/user/team/group/update', data); PUT('/proApi/support/user/team/group/update', data);
export const getGroupMembers = (groupId: string) =>
GET<GroupMemberItemType[]>(`/proApi/support/user/team/group/members`, { groupId });
export const putGroupChangeOwner = (groupId: string, tmbId: string) =>
PUT(`/proApi/support/user/team/group/changeOwner`, { groupId, tmbId });
...@@ -6,8 +6,11 @@ import type { ...@@ -6,8 +6,11 @@ import type {
} from '@fastgpt/global/support/user/team/org/api'; } from '@fastgpt/global/support/user/team/org/api';
import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
import type { putMoveOrgType } from '@fastgpt/global/support/user/team/org/api'; import type { putMoveOrgType } from '@fastgpt/global/support/user/team/org/api';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
export const getOrgList = () => GET<OrgType[]>('/proApi/support/user/team/org/list'); export const getOrgList = (path: string) =>
GET<OrgType[]>(`/proApi/support/user/team/org/list`, { orgPath: path });
export const postCreateOrg = (data: postCreateOrgData) => export const postCreateOrg = (data: postCreateOrgData) =>
POST('/proApi/support/user/team/org/create', data); POST('/proApi/support/user/team/org/create', data);
...@@ -28,3 +31,6 @@ export const putUpdateOrgMembers = (data: putUpdateOrgMembersData) => ...@@ -28,3 +31,6 @@ export const putUpdateOrgMembers = (data: putUpdateOrgMembersData) =>
// export const putChnageOrgOwner = (data: putChnageOrgOwnerData) => // export const putChnageOrgOwner = (data: putChnageOrgOwnerData) =>
// PUT('/proApi/support/user/team/org/changeOwner', data); // PUT('/proApi/support/user/team/org/changeOwner', data);
export const getOrgMembers = (data: PaginationProps<{ orgId: string }>) =>
GET<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/org/members`, data);
...@@ -32,8 +32,6 @@ type State = { ...@@ -32,8 +32,6 @@ type State = {
loadAndGetGroups: (init?: boolean) => Promise<MemberGroupListType>; loadAndGetGroups: (init?: boolean) => Promise<MemberGroupListType>;
teamOrgs: OrgType[]; teamOrgs: OrgType[];
myOrgs: OrgType[];
loadAndGetOrgs: (init?: boolean) => Promise<OrgType[]>;
}; };
export const useUserStore = create<State>()( export const useUserStore = create<State>()(
...@@ -123,23 +121,6 @@ export const useUserStore = create<State>()( ...@@ -123,23 +121,6 @@ export const useUserStore = create<State>()(
}); });
return res; return res;
},
myOrgs: [],
loadAndGetOrgs: async (init = false) => {
if (!useSystemStore.getState()?.feConfigs?.isPlus) return [];
const randomRefresh = Math.random() > 0.7;
if (!randomRefresh && !init && get().myOrgs.length) return Promise.resolve(get().myOrgs);
const res = await getOrgList();
set((state) => {
state.teamOrgs = res;
state.myOrgs = res.filter((item) =>
item.members.map((i) => String(i.tmbId)).includes(String(state.userInfo?.team?.tmbId))
);
});
return res;
} }
})), })),
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment