From 1fdf947a13e9f285f91d14700593079d4920b189 Mon Sep 17 00:00:00 2001 From: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Date: Tue, 25 Mar 2025 21:08:51 +0800 Subject: [PATCH] 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 --- .../global/support/user/team/org/api.d.ts | 9 +- .../global/support/user/team/org/constant.ts | 5 +- .../global/support/user/team/org/type.d.ts | 2 +- .../service/support/permission/auth/org.ts | 4 +- .../components/common/Avatar/AvatarGroup.tsx | 2 +- .../permission/ChangeOwnerModal/index.tsx | 22 ++- .../permission/MemberManager/MemberModal.tsx | 183 +++++++----------- .../team/GroupManage/GroupInfoModal.tsx | 15 +- .../team/GroupManage/GroupManageMember.tsx | 19 +- .../GroupManage/GroupTransferOwnerModal.tsx | 33 +++- .../account/team/GroupManage/index.tsx | 27 ++- .../account/team/MemberTable.tsx | 47 +++-- .../account/team/OrgManage/OrgInfoModal.tsx | 16 +- .../team/OrgManage/OrgMemberManageModal.tsx | 95 ++++++--- .../account/team/OrgManage/OrgMoveModal.tsx | 17 +- .../account/team/OrgManage/OrgTree.tsx | 86 ++++---- .../account/team/OrgManage/index.tsx | 121 ++++-------- .../pageComponents/account/team/context.tsx | 19 +- .../app/src/web/support/user/team/org/api.ts | 16 +- .../support/user/team/org/hooks/useOrg.tsx | 108 +++++++++++ 20 files changed, 496 insertions(+), 350 deletions(-) create mode 100644 projects/app/src/web/support/user/team/org/hooks/useOrg.tsx diff --git a/packages/global/support/user/team/org/api.d.ts b/packages/global/support/user/team/org/api.d.ts index 69ca09796..feaded6f1 100644 --- a/packages/global/support/user/team/org/api.d.ts +++ b/packages/global/support/user/team/org/api.d.ts @@ -1,12 +1,13 @@ +// orgId, pathid, path === null ===> root org export type postCreateOrgData = { name: string; - parentId: string; description?: string; avatar?: string; + path?: string; }; export type putUpdateOrgMembersData = { - orgId: string; + orgId?: string; members: { tmbId: string; // role: `${OrgMemberRole}`; @@ -14,7 +15,7 @@ export type putUpdateOrgMembersData = { }; export type putUpdateOrgData = { - orgId: string; + orgId: string; // can not be undefined because can not uppdate root org name?: string; avatar?: string; description?: string; @@ -22,7 +23,7 @@ export type putUpdateOrgData = { export type putMoveOrgType = { orgId: string; - targetOrgId: string; + targetOrgId?: string; // '' ===> move to root org }; // type putChnageOrgOwnerData = { diff --git a/packages/global/support/user/team/org/constant.ts b/packages/global/support/user/team/org/constant.ts index c5aea21ab..cc335d930 100644 --- a/packages/global/support/user/team/org/constant.ts +++ b/packages/global/support/user/team/org/constant.ts @@ -3,7 +3,10 @@ import { OrgSchemaType } from './type'; export const OrgCollectionName = 'team_orgs'; export const OrgMemberCollectionName = 'team_org_members'; -export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`; +export const getOrgChildrenPath = (org: OrgSchemaType) => { + if (org.path === '' && org.pathId === '') return ''; + return `${org.path ?? ''}/${org.pathId}`; +}; export enum SyncOrgSourceEnum { wecom = 'wecom' diff --git a/packages/global/support/user/team/org/type.d.ts b/packages/global/support/user/team/org/type.d.ts index f2afac2d5..1f0f45608 100644 --- a/packages/global/support/user/team/org/type.d.ts +++ b/packages/global/support/user/team/org/type.d.ts @@ -21,7 +21,7 @@ type OrgMemberSchemaType = { }; export type OrgListItemType = OrgSchemaType & { - permission: TeamPermission; + permission?: TeamPermission; total: number; // members + children orgs }; diff --git a/packages/service/support/permission/auth/org.ts b/packages/service/support/permission/auth/org.ts index d864b1cb5..a569a18ae 100644 --- a/packages/service/support/permission/auth/org.ts +++ b/packages/service/support/permission/auth/org.ts @@ -4,14 +4,14 @@ import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { authUserPer } from '../user/auth'; import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant'; -/* +/* Team manager can control org */ export const authOrgMember = async ({ orgIds, ...props }: { - orgIds: string | string[]; + orgIds?: string | string[]; } & AuthModeType): Promise => { const result = await authUserPer({ ...props, diff --git a/packages/web/components/common/Avatar/AvatarGroup.tsx b/packages/web/components/common/Avatar/AvatarGroup.tsx index 947e488bf..f57ba7fc8 100644 --- a/packages/web/components/common/Avatar/AvatarGroup.tsx +++ b/packages/web/components/common/Avatar/AvatarGroup.tsx @@ -19,7 +19,7 @@ function AvatarGroup({ avatars: string[]; total?: number; }) { - const remain = total ?? avatars.length - max; + const remain = (total ?? avatars.length) - max; return ( {avatars.slice(0, max).map((avatar, index) => ( diff --git a/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx b/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx index 5818a39d7..a379447fa 100644 --- a/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx +++ b/projects/app/src/components/support/permission/ChangeOwnerModal/index.tsx @@ -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(null); + const [selectedMember, setSelectedMember] = useState | null>(null); const { runAsync, loading } = useRequest2(onChangeOwner, { onSuccess: onClose, diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx index da5468d8f..22e56d470 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -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(''); const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>(); - const [path, setPath] = useState(''); - const [orgStack, setOrgStack] = useState([]); - 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(); - 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([]); - const [selectedOrgIdList, setSelectedOrgIdList] = useState([]); - - 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([]); + const [selectedMemberList, setSelectedMemberList] = useState< + Omit[] + >([]); 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([]); + const [selectedGroupList, setSelectedGroupList] = useState([]); 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 ( { 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')} /> )} - {(filterClass === 'member' || (searchText && filterMembers.length > 0)) && ( - - {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} /> ); - })} - - )} - + }); + return searchText ? ( + members + ) : ( + + {members} + + ); + })()} {(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} > v._id === org._id)} pointerEvents="none" /> @@ -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({ {`${t('user:has_chosen')}: `} - {selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length} + {selectedMemberList.length + selectedGroupList.length + selectedOrgList.length} {selectedList.map((item) => { diff --git a/projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx index 9fd81a310..c86134bfe 100644 --- a/projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx @@ -12,14 +12,25 @@ import { useContextSelector } from 'use-context-selector'; import { TeamContext } from '../context'; import { postCreateGroup, putUpdateGroup } from '@/web/support/user/team/group/api'; import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; +import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type'; export type GroupFormType = { avatar: string; name: string; }; -function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) { - const { refetchGroups, groups, refetchMembers } = useContextSelector(TeamContext, (v) => v); +function GroupInfoModal({ + onClose, + editGroupId, + groups, + refetchGroups +}: { + onClose: () => void; + editGroupId?: string; + groups: MemberGroupListType; + refetchGroups: () => void; +}) { + const { refetchMembers } = useContextSelector(TeamContext, (v) => v); const { t } = useTranslation(); const { File: AvatarSelect, diff --git a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx index e0d1b05f4..7b13fc301 100644 --- a/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx @@ -24,7 +24,10 @@ import { useUserStore } from '@/web/support/user/useUserStore'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; -import { GroupMemberItemType } from '@fastgpt/global/support/permission/memberGroup/type'; +import { + GroupMemberItemType, + MemberGroupListType +} from '@fastgpt/global/support/permission/memberGroup/type'; import { useMount } from 'ahooks'; export type GroupFormType = { @@ -37,13 +40,21 @@ export type GroupFormType = { // 1. Owner can not be deleted, toast // 2. Owner/Admin can manage members // 3. Owner can add/remove admins -function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) { +function GroupEditModal({ + onClose, + editGroupId, + groups, + refetchGroups +}: { + onClose: () => void; + editGroupId?: string; + groups: MemberGroupListType; + refetchGroups: () => void; +}) { const { t } = useTranslation(); const { userInfo } = useUserStore(); const { toast } = useToast(); - const groups = useContextSelector(TeamContext, (v) => v.groups); - const refetchGroups = useContextSelector(TeamContext, (v) => v.refetchGroups); const group = useMemo(() => { return groups.find((item) => item._id === editGroupId); }, [editGroupId, groups]); diff --git a/projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx index aab5696b1..93b0bc7cc 100644 --- a/projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx @@ -18,25 +18,43 @@ import { useTranslation } from 'next-i18next'; import React, { useMemo, useState } from 'react'; import { TeamContext } from '../context'; import { useContextSelector } from 'use-context-selector'; +import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type'; +import { GetSearchUserGroupOrg } from '@/web/support/user/api'; +import { Omit } from '@fastgpt/web/components/common/DndDrag'; export type ChangeOwnerModalProps = { groupId: string; + groups: MemberGroupListType; + refetchGroups: () => void; }; export function ChangeOwnerModal({ onClose, - groupId + groupId, + groups, + refetchGroups }: ChangeOwnerModalProps & { onClose: () => void }) { const { t } = useTranslation(); const [inputValue, setInputValue] = React.useState(''); - const { members: allMembers, groups, refetchGroups } = useContextSelector(TeamContext, (v) => v); + const { data: searchedData } = useRequest2( + async () => { + if (!inputValue) return; + return GetSearchUserGroupOrg(inputValue); + }, + { + manual: false, + refreshDeps: [inputValue], + throttleWait: 500, + debounceWait: 200 + } + ); + + const { members: allMembers } = useContextSelector(TeamContext, (v) => v); const group = useMemo(() => { return groups.find((item) => item._id === groupId); }, [groupId, groups]); - const memberList = allMembers.filter((item) => { - return item.memberName.toLowerCase().includes(inputValue.toLowerCase()); - }); + const memberList = searchedData ? searchedData.members : allMembers; const [keepAdmin, setKeepAdmin] = useState(true); @@ -46,7 +64,10 @@ export function ChangeOwnerModal({ onOpen: onOpenMemberListMenu } = useDisclosure(); - const [selectedMember, setSelectedMember] = useState(null); + const [selectedMember, setSelectedMember] = useState | null>(null); const { runAsync, loading } = useRequest2( (tmbId: string) => putGroupChangeOwner(groupId, tmbId), diff --git a/projects/app/src/pageComponents/account/team/GroupManage/index.tsx b/projects/app/src/pageComponents/account/team/GroupManage/index.tsx index f411bc3f5..574e52117 100644 --- a/projects/app/src/pageComponents/account/team/GroupManage/index.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/index.tsx @@ -22,7 +22,7 @@ import MyMenu, { MenuItemType } from '@fastgpt/web/components/common/MyMenu'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { deleteGroup, getGroupMembers } from '@/web/support/user/team/group/api'; +import { deleteGroup, getGroupList, getGroupMembers } from '@/web/support/user/team/group/api'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import MemberTag from '../../../../components/support/user/team/Info/MemberTag'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; @@ -39,9 +39,19 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); const { userInfo } = useUserStore(); - const { groups, refetchGroups, members, teamSize } = useContextSelector(TeamContext, (v) => v); + const { members, teamSize } = useContextSelector(TeamContext, (v) => v); + + const { + data: groups = [], + loading: isLoadingGroups, + refresh: refetchGroups + } = useRequest2(getGroupList, { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + }); const [editGroup, setEditGroup] = useState(); + const { isOpen: isOpenGroupInfo, onOpen: onOpenGroupInfo, @@ -106,7 +116,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { )} - + @@ -230,10 +240,17 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { {isOpenChangeOwner && editGroup && ( - + )} {isOpenGroupInfo && ( { onCloseGroupInfo(); setEditGroup(undefined); @@ -243,6 +260,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { )} {isOpenManageGroupMember && editGroup && ( { onCloseManageGroupMember(); setEditGroup(undefined); diff --git a/projects/app/src/pageComponents/account/team/MemberTable.tsx b/projects/app/src/pageComponents/account/team/MemberTable.tsx index 994008bc6..edc1eedf4 100644 --- a/projects/app/src/pageComponents/account/team/MemberTable.tsx +++ b/projects/app/src/pageComponents/account/team/MemberTable.tsx @@ -44,24 +44,20 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import { useState } from 'react'; import { downloadFetch } from '@/web/common/system/utils'; import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type'; +import { useToast } from '@fastgpt/web/hooks/useToast'; const InviteModal = dynamic(() => import('./Invite/InviteModal')); const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal')); function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); + const { toast } = useToast(); + const { userInfo } = useUserStore(); const { feConfigs } = useSystemStore(); - const { - refetchGroups, - myTeams, - refetchTeams, - members, - refetchMembers, - onSwitchTeam, - MemberScrollData - } = useContextSelector(TeamContext, (v) => v); + const { myTeams, refetchTeams, members, refetchMembers, onSwitchTeam, MemberScrollData } = + useContextSelector(TeamContext, (v) => v); const { isOpen: isOpenTeamTagsAsync, @@ -85,7 +81,10 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const isSyncMember = feConfigs.register_method?.includes('sync'); const { data: searchMembersData } = useRequest2( - () => GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false }), + async () => { + if (!searchText) return Promise.resolve(); + return GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false }); + }, { manual: false, throttleWait: 500, @@ -137,13 +136,12 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { canEmpty: false, rows: 1 }); - const handleEditMemberName = (tmbId: string, memberName: string) => { openEditMemberName({ defaultVal: memberName, onSuccess: (newName: string) => { return putUpdateMemberNameByManager(tmbId, newName).then(() => { - Promise.all([refetchGroups(), refetchMembers()]); + refetchMembers(); }); }, onError: (err) => { @@ -323,10 +321,27 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { }} onClick={() => { openRemoveMember( - () => - delRemoveMember(member.tmbId).then(() => - Promise.all([refetchGroups(), refetchMembers()]) - ), + () => delRemoveMember(member.tmbId).then(refetchMembers), + undefined, + t('account_team:remove_tip', { + username: member.memberName + }) + )(); + }} + /> + { + openRemoveMember( + () => delRemoveMember(member.tmbId).then(refetchMembers), undefined, t('account_team:remove_tip', { username: member.memberName diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx index c992fbead..01d81362d 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgInfoModal.tsx @@ -14,8 +14,7 @@ export type OrgFormType = { avatar: string; description?: string; name: string; - path: string; - parentId?: string; + path?: string; }; export const defaultOrgForm: OrgFormType = { @@ -29,11 +28,13 @@ export const defaultOrgForm: OrgFormType = { function OrgInfoModal({ editOrg, onClose, - onSuccess + onSuccess, + updateCurrentOrg }: { editOrg: OrgFormType; onClose: () => void; onSuccess: () => void; + updateCurrentOrg: (data: { name?: string; avatar?: string; description?: string }) => void; }) { const { t } = useTranslation(); @@ -50,11 +51,10 @@ function OrgInfoModal({ const { run: onCreate, loading: isLoadingCreate } = useRequest2( async (data: OrgFormType) => { - if (!editOrg.parentId) return; return postCreateOrg({ name: data.name, avatar: data.avatar, - parentId: editOrg.parentId, + path: editOrg.path, description: data.description }); }, @@ -67,7 +67,7 @@ function OrgInfoModal({ } ); - const { run: onUpdate, loading: isLoadingUpdate } = useRequest2( + const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2( async (data: OrgFormType) => { if (!editOrg._id) return; return putUpdateOrg({ @@ -144,7 +144,9 @@ function OrgInfoModal({ isLoading={isLoading} onClick={handleSubmit((data) => { if (isEdit) { - onUpdate(data); + onUpdate(data).then(() => { + updateCurrentOrg(data); + }); } else { onCreate(data); } diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx index 13a3bb204..aec30c1b0 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgMemberManageModal.tsx @@ -17,12 +17,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 type React from 'react'; import { useEffect, useMemo, useState } from 'react'; -import { useContextSelector } from 'use-context-selector'; -import { TeamContext } from '../context'; -import { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type'; +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'; export type GroupFormType = { members: { @@ -51,20 +50,39 @@ function OrgMemberManageModal({ onClose: () => void; }) { const { t } = useTranslation(); - const { members: allMembers, MemberScrollData } = useContextSelector(TeamContext, (v) => v); - const { data: orgMembers, ScrollData: OrgMemberScrollData } = useScrollPagination(getOrgMembers, { + + const { + data: allMembers, + ScrollData: MemberScrollData, + isLoading: isLoadingMembers + } = useScrollPagination(getTeamMembers, { pageSize: 20, params: { - orgId: currentOrg?._id ?? '' + withLeaved: false } }); - const [selectedMembers, setSelectedMembers] = useState( - orgMembers.map((item) => item.tmbId) - ); + const { + data: orgMembers, + ScrollData: OrgMemberScrollData, + isLoading: isLoadingOrgMembers + } = useScrollPagination(getOrgMembers, { + pageSize: 20, + params: { + orgPath: getOrgChildrenPath(currentOrg) + } + }); + + const [selected, setSelected] = useState<{ name: string; tmbId: string; avatar: string }[]>([]); useEffect(() => { - setSelectedMembers(orgMembers.map((item) => item.tmbId)); + setSelected( + orgMembers.map((item) => ({ + name: item.memberName, + tmbId: item.tmbId, + avatar: item.avatar + })) + ); }, [orgMembers]); const [searchKey, setSearchKey] = useState(''); @@ -78,8 +96,8 @@ function OrgMemberManageModal({ () => { return putUpdateOrgMembers({ orgId: currentOrg._id, - members: selectedMembers.map((tmbId) => ({ - tmbId + members: selected.map((member) => ({ + tmbId: member.tmbId })) }); }, @@ -92,15 +110,25 @@ function OrgMemberManageModal({ } ); - const isSelected = (memberId: string) => { - return selectedMembers.find((tmbId) => tmbId === memberId); + const isSelected = (tmbId: string) => { + return selected.find((tmb) => tmb.tmbId === tmbId); }; - const handleToggleSelect = (memberId: string) => { - if (isSelected(memberId)) { - setSelectedMembers((state) => state.filter((tmbId) => tmbId !== memberId)); + const handleToggleSelect = (tmbId: string) => { + if (isSelected(tmbId)) { + setSelected((state) => state.filter((tmb) => tmb.tmbId !== tmbId)); + // setSelectedTmbIds((state) => state.filter((tmbId) => tmbId !== memberId)); } else { - setSelectedMembers((state) => [...state, memberId]); + // setSelectedTmbIds((state) => [...state, memberId]); + const member = allMembers.find((item) => item.tmbId === tmbId)!; + setSelected((state) => [ + ...state, + { + name: member.memberName, + tmbId, + avatar: member.avatar + } + ]); } }; @@ -123,7 +151,14 @@ function OrgMemberManageModal({ gridTemplateColumns="1fr 1fr" h={'100%'} > - + - + {filterMembers.map((member) => { return ( {/* */} - - {`${t('common:chosen')}:${selectedMembers.length}`} - {selectedMembers.map((tmbId) => { - const member = allMembers.find((item) => item.tmbId === tmbId)!; + + {`${t('common:chosen')}:${selected.length}`} + {selected.map((member) => { return ( - {member?.memberName} + {member?.name} handleToggleSelect(tmbId)} + onClick={() => handleToggleSelect(member.tmbId)} /> ); diff --git a/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx index 96e2e705a..ad005413c 100644 --- a/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx +++ b/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx @@ -1,4 +1,4 @@ -import { putMoveOrg } from '@/web/support/user/team/org/api'; +import { getOrgList, putMoveOrg } from '@/web/support/user/team/org/api'; import { Button, ModalBody, ModalFooter } from '@chakra-ui/react'; import type { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type'; import MyModal from '@fastgpt/web/components/common/MyModal'; @@ -6,17 +6,15 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useTranslation } from 'next-i18next'; import { useMemo, useState } from 'react'; import OrgTree from './OrgTree'; -import dynamic from 'next/dynamic'; import { useUserStore } from '@/web/support/user/useUserStore'; +import useOrg from '@/web/support/user/team/org/hooks/useOrg'; function OrgMoveModal({ movingOrg, - orgs, onClose, onSuccess }: { movingOrg: OrgListItemType; - orgs: OrgListItemType[]; onClose: () => void; onSuccess: () => void; }) { @@ -32,11 +30,6 @@ function OrgMoveModal({ } }); - const filterMovingOrgs = useMemo( - () => orgs.filter((org) => org._id !== movingOrg._id), - [movingOrg._id, orgs] - ); - return ( - +