pref: member/group/org (#4316)

* feat: change group owner api

* pref: member/org/group

* fix: member modal select clb

* fix: search member when change owner
This commit is contained in:
Finley Ge
2025-03-25 21:08:51 +08:00
committed by archer
parent ff64a3c039
commit 1fdf947a13
20 changed files with 496 additions and 350 deletions

View File

@@ -1,3 +1,4 @@
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
import { getTeamMembers } from '@/web/support/user/team/api';
import {
Box,
@@ -38,16 +39,29 @@ export function ChangeOwnerModal({
pageSize: 15
});
const memberList = teamMembers.filter((item) => {
return item.memberName.includes(inputValue);
});
const { data: searchedData } = useRequest2(
async () => {
if (!inputValue) return;
return GetSearchUserGroupOrg(inputValue);
},
{
manual: false,
refreshDeps: [inputValue],
throttleWait: 500,
debounceWait: 200
}
);
const memberList = searchedData ? searchedData.members : teamMembers;
const {
isOpen: isOpenMemberListMenu,
onClose: onCloseMemberListMenu,
onOpen: onOpenMemberListMenu
} = useDisclosure();
const [selectedMember, setSelectedMember] = useState<TeamMemberItemType | null>(null);
const [selectedMember, setSelectedMember] = useState<Omit<
TeamMemberItemType,
'permission' | 'teamId'
> | null>(null);
const { runAsync, loading } = useRequest2(onChangeOwner, {
onSuccess: onClose,

View File

@@ -30,7 +30,7 @@ import {
import Path from '@/components/common/folder/Path';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
import { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
import { useContextSelector } from 'use-context-selector';
import { CollaboratorContext } from './context';
import { getTeamMembers } from '@/web/support/user/team/api';
@@ -39,6 +39,9 @@ import { getOrgList, getOrgMembers } from '@/web/support/user/team/org/api';
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import MemberItemCard from './MemberItemCard';
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
const HoverBoxStyle = {
bgColor: 'myGray.50',
@@ -57,48 +60,21 @@ function MemberModal({
const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList);
const [searchText, setSearchText] = useState<string>('');
const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>();
const [path, setPath] = useState('');
const [orgStack, setOrgStack] = useState<OrgType[]>([]);
const currentOrg = useMemo(() => orgStack[orgStack.length - 1], [orgStack]);
const {
paths,
onClickOrg,
members: orgMembers,
MemberScrollData: OrgMemberScrollData,
onPathClick,
refresh,
updateCurrentOrg,
orgs
} = useOrg({ getPermission: false });
const { data: members, ScrollData: TeamMemberScrollData } = useScrollPagination(getTeamMembers, {
pageSize: 15
});
const [rootOrg, setRootOrg] = useState<OrgType>();
const { data: orgMembers = [], ScrollData: OrgMemberScrollData } = useScrollPagination(
getOrgMembers,
{
pageSize: 20,
params: {
orgId: currentOrg?._id ?? rootOrg?._id
},
refreshDeps: [currentOrg?._id]
}
);
const onClickOrg = (org: OrgType) => {
setOrgStack([...orgStack, org]);
setPath(getOrgChildrenPath(org));
};
const { data: orgs = [] } = useRequest2(
() => {
const splitPath = path.split('/').filter(Boolean);
const orgs = orgStack.filter((o) => splitPath.includes(o.pathId));
setOrgStack(orgs);
return getOrgList(path);
},
{
manual: false,
refreshDeps: [path],
onSuccess: (data) => {
if (!rootOrg) {
setRootOrg(data[0]);
}
}
}
);
const { data: groups = [], loading: loadingGroupsAndOrgs } = useRequest2(
async () => {
if (!userInfo?.team?.teamId) return [];
@@ -117,21 +93,9 @@ function MemberModal({
refreshDeps: [searchText]
});
const paths = useMemo(() => {
return orgStack
.map((org) => {
if (org?.path === '') return;
return {
parentId: getOrgChildrenPath(org),
parentName: org.name
};
})
.filter(Boolean) as ParentTreePathItemType[];
}, [orgStack]);
const [selectedOrgList, setSelectedOrgIdList] = useState<OrgListItemType[]>([]);
const [selectedOrgIdList, setSelectedOrgIdList] = useState<string[]>([]);
const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => {
const filterOrgs: (OrgListItemType & { count?: number })[] = useMemo(() => {
if (searchText && searchedData) {
const orgids = searchedData.orgs.map((item) => item._id);
return orgs.filter((org) => orgids.includes(String(org._id)));
@@ -144,7 +108,9 @@ function MemberModal({
}));
}, [searchText, orgs, searchedData]);
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
const [selectedMemberList, setSelectedMemberList] = useState<
Omit<TeamMemberItemType, 'permission' | 'teamId'>[]
>([]);
const filterMembers = useMemo(() => {
if (searchText) {
return searchedData?.members || [];
@@ -152,9 +118,8 @@ function MemberModal({
return members;
}, [searchText, members, searchedData?.members]);
console.log(filterMembers);
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
const [selectedGroupList, setSelectedGroupList] = useState<MemberGroupListType>([]);
const filterGroups = useMemo(() => {
if (searchText) {
return searchedData?.groups.map((item) => ({
@@ -186,9 +151,9 @@ function MemberModal({
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
() =>
onUpdateCollaborators({
members: selectedMemberIdList,
groups: selectedGroupIdList,
orgs: selectedOrgIdList,
members: selectedMemberList.map((item) => item.tmbId),
groups: selectedGroupList.map((item) => item._id),
orgs: selectedOrgList.map((item) => item._id),
permission: selectedPermission!
}),
{
@@ -206,42 +171,31 @@ function MemberModal({
]);
const selectedList = useMemo(() => {
const selectedOrgs = orgs.filter((org) => selectedOrgIdList.includes(org._id));
const selectedGroups = groups.filter((group) => selectedGroupIdList.includes(group._id));
const selectedMembers = members.filter((member) => selectedMemberIdList.includes(member.tmbId));
return [
...selectedOrgs.map((item) => ({
...selectedOrgList.map((item) => ({
id: `org-${item._id}`,
avatar: item.avatar,
name: item.name,
onDelete: () => setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== item._id)),
onDelete: () => setSelectedOrgIdList(selectedOrgList.filter((v) => v._id !== item._id)),
orgs: undefined
})),
...selectedGroups.map((item) => ({
...selectedGroupList.map((item) => ({
id: `group-${item._id}`,
avatar: item.avatar,
name: item.name === DefaultGroupName ? userInfo?.team.teamName : item.name,
onDelete: () => setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== item._id)),
onDelete: () => setSelectedGroupList(selectedGroupList.filter((v) => v._id !== item._id)),
orgs: undefined
})),
...selectedMembers.map((item) => ({
...selectedMemberList.map((item) => ({
id: `member-${item.tmbId}`,
avatar: item.avatar,
name: item.memberName,
onDelete: () => setSelectedMembers(selectedMemberIdList.filter((v) => v !== item.tmbId)),
onDelete: () =>
setSelectedMemberList(selectedMemberList.filter((v) => v.tmbId !== item.tmbId)),
orgs: item.orgs
}))
];
}, [
orgs,
groups,
members,
selectedOrgIdList,
selectedGroupIdList,
selectedMemberIdList,
userInfo?.team.teamName
]);
}, [selectedOrgList, selectedGroupList, selectedMemberList, userInfo?.team.teamName]);
return (
<MyModal
@@ -324,36 +278,31 @@ function MemberModal({
onClick={(parentId) => {
if (parentId === '') {
setFilterClass(undefined);
setPath('');
onPathClick('');
} else if (
parentId === 'member' ||
parentId === 'org' ||
parentId === 'group'
) {
setFilterClass(parentId);
setPath('');
onPathClick('');
} else {
setPath(parentId);
onPathClick(parentId);
}
}}
rootName={t('common:common.Team')}
/>
</Box>
)}
{(filterClass === 'member' || (searchText && filterMembers.length > 0)) && (
<TeamMemberScrollData
flexDirection={'column'}
gap={1}
userSelect={'none'}
height={'fit-content'}
>
{filterMembers?.map((member) => {
{(filterClass === 'member' || (searchText && filterMembers.length > 0)) &&
(() => {
const members = filterMembers?.map((member) => {
const onChange = () => {
setSelectedMembers((state) => {
if (state.includes(member.tmbId)) {
return state.filter((v) => v !== member.tmbId);
setSelectedMemberList((state) => {
if (state.find((v) => v.tmbId === member.tmbId)) {
return state.filter((v) => v.tmbId !== member.tmbId);
}
return [...state, member.tmbId];
return [...state, member];
});
};
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
@@ -364,23 +313,33 @@ function MemberModal({
name={member.memberName}
permission={collaborator?.permission.value}
onChange={onChange}
isChecked={selectedMemberIdList.includes(member.tmbId)}
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
orgs={member.orgs}
/>
);
})}
</TeamMemberScrollData>
)}
});
return searchText ? (
members
) : (
<TeamMemberScrollData
flexDirection={'column'}
gap={1}
userSelect={'none'}
height={'fit-content'}
>
{members}
</TeamMemberScrollData>
);
})()}
{(filterClass === 'org' || searchText) &&
(() => {
const orgs = filterOrgs?.map((org) => {
const onChange = () => {
setSelectedOrgIdList((state) => {
if (state.includes(org._id)) {
return state.filter((v) => v !== org._id);
if (state.find((v) => v._id === org._id)) {
return state.filter((v) => v._id !== org._id);
}
return [...state, org._id];
return [...state, org];
});
};
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
@@ -396,7 +355,7 @@ function MemberModal({
onClick={onChange}
>
<Checkbox
isChecked={selectedOrgIdList.includes(org._id)}
isChecked={!!selectedOrgList.find((v) => v._id === org._id)}
pointerEvents="none"
/>
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
@@ -442,14 +401,14 @@ function MemberModal({
key={member.tmbId}
name={member.memberName}
onChange={() => {
setSelectedMembers((state) => {
if (state.includes(member.tmbId)) {
return state.filter((v) => v !== member.tmbId);
setSelectedMemberList((state) => {
if (state.find((v) => v.tmbId === member.tmbId)) {
return state.filter((v) => v.tmbId !== member.tmbId);
}
return [...state, member.tmbId];
return [...state, member];
});
}}
isChecked={selectedMemberIdList.includes(member.tmbId)}
isChecked={!!selectedMemberList.find((v) => v.tmbId === member.tmbId)}
orgs={member.orgs}
/>
);
@@ -459,11 +418,11 @@ function MemberModal({
})()}
{filterGroups?.map((group) => {
const onChange = () => {
setSelectedGroupIdList((state) => {
if (state.includes(group._id)) {
return state.filter((v) => v !== group._id);
setSelectedGroupList((state) => {
if (state.find((v) => v._id === group._id)) {
return state.filter((v) => v._id !== group._id);
}
return [...state, group._id];
return [...state, group];
});
};
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
@@ -476,7 +435,7 @@ function MemberModal({
}
permission={collaborator?.permission.value}
onChange={onChange}
isChecked={selectedGroupIdList.includes(group._id)}
isChecked={!!selectedGroupList.find((v) => v._id === group._id)}
/>
);
})}
@@ -486,7 +445,7 @@ function MemberModal({
<Flex h={'100%'} p="4" flexDirection="column">
<Box>
{`${t('user:has_chosen')}: `}
{selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length}
{selectedMemberList.length + selectedGroupList.length + selectedOrgList.length}
</Box>
<Flex flexDirection="column" mt="2" gap={1} overflow={'auto'} flex={'1 0 0'} h={0}>
{selectedList.map((item) => {