From 07cc849877c7dc7b3d7c9ff1eb3f992f70410839 Mon Sep 17 00:00:00 2001 From: "a.e." <49438478+I-Info@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:19:23 +0800 Subject: [PATCH] refactor: team permission manager (#3535) * perf: classify org, group and member * refactor: team per manager * fix: missing functions --- .../support/permission/collaborator.d.ts | 24 + .../service/support/permission/controller.ts | 3 + packages/web/i18n/en/user.json | 1 + packages/web/i18n/zh-CN/user.json | 1 + packages/web/i18n/zh-Hant/user.json | 1 + .../MemberManager/AddMemberModal.tsx | 396 +----------- .../permission/MemberManager/MemberModal.tsx | 569 ++++++++++++++++++ .../components/PermissionManage/index.tsx | 428 +++++++------ projects/app/src/pages/account/team/index.tsx | 24 +- projects/app/src/web/support/user/team/api.ts | 16 +- 10 files changed, 892 insertions(+), 571 deletions(-) create mode 100644 projects/app/src/components/support/permission/MemberManager/MemberModal.tsx diff --git a/packages/global/support/permission/collaborator.d.ts b/packages/global/support/permission/collaborator.d.ts index d5c9b3060..faa1d3948 100644 --- a/packages/global/support/permission/collaborator.d.ts +++ b/packages/global/support/permission/collaborator.d.ts @@ -33,3 +33,27 @@ export type UpdatePermissionBody = { groupId: string; orgId: string; }>; + +export type CreatePermissionBody = { + tmbId: string[]; + groupId: string[]; + orgId: string[]; +}; + +export type DeletePermissionQuery = RequireOnlyOne<{ + tmbId?: string; + groupId?: string; + orgId?: string; +}>; + +export type TeamClbsListType = { + permission: number; + name: string; + avatar: string; +}; + +export type ListPermissionResponse = { + tmb: (TeamClbsListType & { tmbId: string })[]; + group: (TeamClbsListType & { groupId: string })[]; + org: (TeamClbsListType & { orgId: string })[]; +}; diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index d0b2fd095..1b6a0767c 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -133,6 +133,9 @@ export async function getResourceAllClbs({ resourceId, groupId: { $exists: false + }, + orgId: { + $exists: false } }, null, diff --git a/packages/web/i18n/en/user.json b/packages/web/i18n/en/user.json index 7600a9c27..1ca785b52 100644 --- a/packages/web/i18n/en/user.json +++ b/packages/web/i18n/en/user.json @@ -106,6 +106,7 @@ "team.group.set_as_admin": "Set as administrator", "team.group.toast.can_not_delete_owner": "Owner cannot be deleted, please transfer first", "team.group.transfer_owner": "transfer owner", + "team.org.org": "Organization", "team.manage_collaborators": "Manage Collaborators", "team.no_collaborators": "No Collaborators", "team.write_role_member": "", diff --git a/packages/web/i18n/zh-CN/user.json b/packages/web/i18n/zh-CN/user.json index 62c7acd15..d0ea685aa 100644 --- a/packages/web/i18n/zh-CN/user.json +++ b/packages/web/i18n/zh-CN/user.json @@ -106,6 +106,7 @@ "team.group.set_as_admin": "设为管理员", "team.group.toast.can_not_delete_owner": "不能删除所有者, 请先转让", "team.group.transfer_owner": "转让所有者", + "team.org.org": "组织", "team.manage_collaborators": "管理协作者", "team.no_collaborators": "暂无协作者", "team.write_role_member": "可写权限", diff --git a/packages/web/i18n/zh-Hant/user.json b/packages/web/i18n/zh-Hant/user.json index 9bd8e3576..f259aebd3 100644 --- a/packages/web/i18n/zh-Hant/user.json +++ b/packages/web/i18n/zh-Hant/user.json @@ -106,6 +106,7 @@ "team.group.set_as_admin": "設為管理員", "team.group.toast.can_not_delete_owner": "無法刪除擁有者,請先轉移擁有權", "team.group.transfer_owner": "轉移擁有者", + "team.org.org": "組織", "team.manage_collaborators": "管理協作者", "team.no_collaborators": "目前沒有協作者", "team.write_role_member": "可寫入權限", diff --git a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx index 18fddf866..8798da50b 100644 --- a/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/AddMemberModal.tsx @@ -1,399 +1,11 @@ -import { useUserStore } from '@/web/support/user/useUserStore'; -import { ChevronDownIcon } from '@chakra-ui/icons'; -import { - Box, - Button, - Checkbox, - Flex, - Grid, - HStack, - ModalBody, - ModalFooter -} from '@chakra-ui/react'; -import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; -import MyAvatar from '@fastgpt/web/components/common/Avatar'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -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 { useMemo, useState } from 'react'; import { useContextSelector } from 'use-context-selector'; -import PermissionSelect from './PermissionSelect'; -import PermissionTags from './PermissionTags'; import { CollaboratorContext } from './context'; - -export type AddModalPropsType = { - onClose: () => void; - mode?: 'member' | 'all'; -}; +import { AddModalPropsType } from './MemberModal'; +import MemberModal from './MemberModal'; function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) { - const { t } = useTranslation(); - const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs, myOrgs } = - useUserStore(); - - const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList, permission } = - useContextSelector(CollaboratorContext, (v) => v); - const [searchText, setSearchText] = useState(''); - - const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = - useRequest2( - async () => { - if (!userInfo?.team?.teamId) return [[], []]; - return Promise.all([ - loadAndGetTeamMembers(true), - loadAndGetGroups(true), - loadAndGetOrgs(true) - ]); - }, - { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - } - ); - - const filterMembers = useMemo(() => { - return members.filter((item) => { - if (item.tmbId === userInfo?.team?.tmbId) return false; - if (!searchText) return true; - return item.memberName.includes(searchText); - }); - }, [members, searchText, userInfo?.team?.tmbId]); - - const filterGroups = useMemo(() => { - if (mode !== 'all') return []; - return groups.filter((item) => { - if (permission.isOwner) return true; // owner can see all groups - if (myGroups.find((i) => String(i._id) === String(item._id))) return false; - if (!searchText) return true; - return item.name.includes(searchText); - }); - }, [groups, searchText, myGroups, mode, permission]); - - const filterOrgs = useMemo(() => { - if (mode !== 'all') return []; - return orgs.filter((item) => { - if (item.path === '') return false; // exclude root org - if (!permission.isOwner && !myOrgs.find((i) => String(i._id) === String(item._id))) - return false; - if (!searchText) return true; - return item.name.includes(searchText); - }); - }, [orgs, searchText, myOrgs, mode, permission]); - - const [selectedMemberIdList, setSelectedMembers] = useState([]); - const [selectedGroupIdList, setSelectedGroupIdList] = useState([]); - const [selectedOrgIdList, setSelectedOrgIdList] = useState([]); - const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value); - const perLabel = useMemo(() => { - return getPerLabelList(selectedPermission).join('、'); - }, [getPerLabelList, selectedPermission]); - - const { runAsync: onConfirm, loading: isUpdating } = useRequest2( - () => - onUpdateCollaborators({ - members: selectedMemberIdList, - groups: selectedGroupIdList, - orgs: selectedOrgIdList, - permission: selectedPermission - }), - { - successToast: t('common:common.Add Success'), - errorToast: 'Error', - onSuccess() { - onClose(); - } - } - ); - - return ( - - - - - setSearchText(e.target.value)} - /> - - - {filterOrgs.map((org) => { - const onChange = () => { - setSelectedOrgIdList((state) => { - if (state.includes(org._id)) { - return state.filter((v) => v !== org._id); - } - return [...state, org._id]; - }); - }; - const collaborator = collaboratorList.find((v) => v.orgId === org._id); - return ( - - - - - {org.name} - - {!!collaborator && ( - - )} - - ); - })} - {filterGroups.map((group) => { - const onChange = () => { - setSelectedGroupIdList((state) => { - if (state.includes(group._id)) { - return state.filter((v) => v !== group._id); - } - return [...state, group._id]; - }); - }; - const collaborator = collaboratorList.find((v) => v.groupId === group._id); - return ( - - - - - {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} - - {!!collaborator && ( - - )} - - ); - })} - {filterMembers.map((member) => { - const onChange = () => { - setSelectedMembers((state) => { - if (state.includes(member.tmbId)) { - return state.filter((v) => v !== member.tmbId); - } - return [...state, member.tmbId]; - }); - }; - const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId); - return ( - - } - /> - - - {member.memberName} - - {!!collaborator && ( - - )} - - ); - })} - - - - - {`${t('user:has_chosen')}: `} - {selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length} - - - {selectedOrgIdList.map((orgId) => { - const org = orgs.find((v) => String(v._id) === orgId); - return ( - - setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== orgId)) - } - > - - - {org?.name} - - - - ); - })} - {selectedGroupIdList.map((groupId) => { - const onChange = () => { - setSelectedGroupIdList((state) => { - if (state.includes(groupId)) { - return state.filter((v) => v !== groupId); - } - return [...state, groupId]; - }); - }; - const group = groups.find((v) => String(v._id) === groupId); - return ( - - - - {group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name} - - - - ); - })} - {selectedMemberIdList.map((tmbId) => { - const member = filterMembers.find((v) => v.tmbId === tmbId); - return member ? ( - - setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId)) - } - > - - - {member.memberName} - - - - ) : null; - })} - - - - - - - {t(perLabel as any)} - - - } - onChange={(v) => setSelectedPermission(v)} - /> - - - - ); + const context = useContextSelector(CollaboratorContext, (v) => v); + return ; } export default AddMemberModal; diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx new file mode 100644 index 000000000..e7e08c5f2 --- /dev/null +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -0,0 +1,569 @@ +import { useUserStore } from '@/web/support/user/useUserStore'; +import { ChevronDownIcon } from '@chakra-ui/icons'; +import { + Box, + Button, + Checkbox, + Flex, + Grid, + HStack, + ModalBody, + ModalFooter, + Tag, + Text +} from '@chakra-ui/react'; +import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; +import MyAvatar from '@fastgpt/web/components/common/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +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 { useMemo, useState } from 'react'; +import { useContextSelector } from 'use-context-selector'; +import PermissionSelect from './PermissionSelect'; +import PermissionTags from './PermissionTags'; +import { MemberManagerPropsType } from './context'; +import { DEFAULT_ORG_AVATAR, DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants'; +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 { createMemberPermission } from '@/web/support/user/team/api'; + +export type AddModalPropsType = { + onClose: () => void; + mode?: 'member' | 'all'; +}; + +function MemberModal({ + onClose, + mode = 'member', + collaboratorContext: context +}: AddModalPropsType & { collaboratorContext?: MemberManagerPropsType }) { + const { t } = useTranslation(); + const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups, loadAndGetOrgs } = + useUserStore(); + + const [searchText, setSearchText] = useState(''); + const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>(); + const [parentPath, setParentPath] = useState(''); + + const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = + useRequest2( + async () => { + if (!userInfo?.team?.teamId) return [[], []]; + return Promise.all([ + loadAndGetTeamMembers(true), + loadAndGetGroups(true), + loadAndGetOrgs(true) + ]); + }, + { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + } + ); + + 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)!; + + if (org.path === '') return; + + return { + parentId: getOrgChildrenPath(org), + parentName: org.name + }; + }) + .filter(Boolean) as ParentTreePathItemType[]; + }, [parentPath, orgs]); + + const filterMembers = useMemo(() => { + if (!searchText && filterClass !== 'member' && filterClass !== 'org') return []; + if (searchText) return members.filter((item) => item.memberName.includes(searchText)); + if (filterClass === 'org') { + if (!currentOrg) return []; + return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId)); + } + return members; + }, [members, searchText, filterClass, currentOrg]); + + const filterGroups = useMemo(() => { + if (mode !== 'all') return []; + if (!searchText && filterClass !== 'group') return []; + if (searchText) return groups.filter((item) => item.name.includes(searchText)); + return groups.filter((item) => { + if (context === undefined || context.permission.isOwner) return true; // owner can see all groups + return !myGroups.find((i) => String(i._id) === String(item._id)); + }); + }, [groups, searchText, filterClass, myGroups, mode, context]); + + const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => { + if (mode !== 'all') return []; + if (!searchText && filterClass !== 'org') return []; + if (searchText) return orgs.filter((item) => item.name.includes(searchText)); + if (parentPath === '') { + setParentPath(`/${orgs[0].pathId}`); + return []; + } + return orgs + .filter((org) => org.path === parentPath) + .map((item) => ({ + ...item, + count: + item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length + })); + }, [orgs, searchText, filterClass, mode, parentPath]); + + const [selectedMemberIdList, setSelectedMembers] = useState([]); + const [selectedGroupIdList, setSelectedGroupIdList] = useState([]); + const [selectedOrgIdList, setSelectedOrgIdList] = useState([]); + const [selectedPermission, setSelectedPermission] = useState( + context?.permissionList['read'].value + ); + const perLabel = useMemo(() => { + if (context) return context.getPerLabelList(selectedPermission!).join('、'); + }, [context, selectedPermission]); + + const { runAsync: onConfirm, loading: isUpdating } = useRequest2( + () => { + if (context) { + return context.onUpdateCollaborators({ + members: selectedMemberIdList, + groups: selectedGroupIdList, + orgs: selectedOrgIdList, + permission: selectedPermission! + }); + } else { + return createMemberPermission({ + tmbId: selectedMemberIdList, + groupId: selectedGroupIdList, + orgId: selectedOrgIdList + }); + } + }, + { + successToast: t('common:common.Add Success'), + errorToast: 'Error', + onSuccess() { + onClose(); + } + } + ); + + return ( + + + + + setSearchText(e.target.value)} + /> + + + {!searchText && + (filterClass === undefined ? ( + <> + setFilterClass('member')} + > + + + {t('user:team.group.members')} + + + + setFilterClass('org')} + > + + + {t('user:team.org.org')} + + + + setFilterClass('group')} + > + + + {t('user:team.group.group')} + + + + + ) : ( + { + if (parentId === '') { + setFilterClass(undefined); + setParentPath(''); + } else if ( + parentId === 'member' || + parentId === 'org' || + parentId === 'group' + ) { + setFilterClass(parentId); + setParentPath(''); + } else { + setParentPath(parentId); + } + }} + rootName={t('common:common.Team')} + /> + ))} + {filterOrgs.map((org) => { + const onChange = () => { + setSelectedOrgIdList((state) => { + if (state.includes(org._id)) { + return state.filter((v) => v !== org._id); + } + return [...state, org._id]; + }); + }; + const collaborator = context?.collaboratorList?.find((v) => v.orgId === org._id); + return ( + + + + + {org.name} + {org.count && ( + <> + + {org.count} + + + )} + + {!!collaborator && ( + + )} + {org.count && ( + { + setParentPath(getOrgChildrenPath(org)); + }} + /> + )} + + ); + })} + {filterGroups.map((group) => { + const onChange = () => { + setSelectedGroupIdList((state) => { + if (state.includes(group._id)) { + return state.filter((v) => v !== group._id); + } + return [...state, group._id]; + }); + }; + const collaborator = context?.collaboratorList?.find( + (v) => v.groupId === group._id + ); + return ( + + + + + {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} + + {!!collaborator && ( + + )} + + ); + })} + {filterMembers.map((member) => { + const onChange = () => { + setSelectedMembers((state) => { + if (state.includes(member.tmbId)) { + return state.filter((v) => v !== member.tmbId); + } + return [...state, member.tmbId]; + }); + }; + const collaborator = context?.collaboratorList?.find( + (v) => v.tmbId === member.tmbId + ); + return ( + + + + + {member.memberName} + + {!!collaborator && ( + + )} + + ); + })} + + + + + {`${t('user:has_chosen')}: `} + {selectedMemberIdList.length + selectedGroupIdList.length + selectedOrgIdList.length} + + + {selectedOrgIdList.map((orgId) => { + const org = orgs.find((v) => String(v._id) === orgId); + return ( + + setSelectedOrgIdList(selectedOrgIdList.filter((v) => v !== orgId)) + } + > + + + {org?.name} + + + + ); + })} + {selectedGroupIdList.map((groupId) => { + const onChange = () => { + setSelectedGroupIdList((state) => { + if (state.includes(groupId)) { + return state.filter((v) => v !== groupId); + } + return [...state, groupId]; + }); + }; + const group = groups.find((v) => String(v._id) === groupId); + return ( + + + + {group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name} + + + + ); + })} + {selectedMemberIdList.map((tmbId) => { + const member = members.find((v) => v.tmbId === tmbId); + return member ? ( + + setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId)) + } + > + + + {member.memberName} + + + + ) : null; + })} + + + + + + {selectedPermission && ( + + {t(perLabel as any)} + + + } + onChange={(v) => setSelectedPermission(v)} + /> + )} + + + + ); +} + +export default MemberModal; diff --git a/projects/app/src/pages/account/team/components/PermissionManage/index.tsx b/projects/app/src/pages/account/team/components/PermissionManage/index.tsx index 60407debd..4c7b89bd7 100644 --- a/projects/app/src/pages/account/team/components/PermissionManage/index.tsx +++ b/projects/app/src/pages/account/team/components/PermissionManage/index.tsx @@ -9,15 +9,17 @@ import { Td, Th, Thead, + Text, Tr } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; -import { useContextSelector } from 'use-context-selector'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { getTeamClbs, updateMemberPermission } from '@/web/support/user/team/api'; +import { + deleteMemberPermission, + getTeamClbs, + updateMemberPermission +} from '@/web/support/user/team/api'; import { useUserStore } from '@/web/support/user/useUserStore'; - -import { TeamContext } from '../context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MemberTag from '../../../../../components/support/user/team/Info/MemberTag'; @@ -27,65 +29,62 @@ import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant'; import { TeamPermission } from '@fastgpt/global/support/permission/user/controller'; -import { useCreation } from 'ahooks'; -import { getOrgList } from '@/web/support/user/team/org/api'; +import { useCreation, useToggle } from 'ahooks'; +import MyIconButton from '@fastgpt/web/components/common/Icon/button'; +import MemberModal from '@/components/support/permission/MemberManager/MemberModal'; -function PermissionManage() { +function PermissionManage({ + isOpenAddPermission, + onCloseAddPermission +}: { + isOpenAddPermission: boolean; + onCloseAddPermission: () => void; +}) { const { t } = useTranslation(); const { userInfo } = useUserStore(); - const { groups, refetchMembers, refetchGroups, members, searchKey } = useContextSelector( - TeamContext, - (v) => v + + const { runAsync: refetchClbs, data: clbs = { tmb: [], group: [], org: [] } } = useRequest2( + getTeamClbs, + { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + } ); - const { - data: orgs = [], - loading: isLoadingOrgs, - refresh: refetchOrgs - } = useRequest2(getOrgList, { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - }); + const [isExpandMember, setExpandMember] = useToggle(true); + const [isExpandGroup, setExpandGroup] = useToggle(true); + const [isExpandOrg, setExpandOrg] = useToggle(true); - const filteredOrgs = useCreation( + const members = useCreation( () => - orgs.filter( - (org) => org.path !== '' && org.name.toLowerCase().includes(searchKey.toLowerCase()) - ), - [orgs, searchKey] + clbs.tmb.map((item) => ({ + ...item, + permission: new TeamPermission({ per: item.permission }) + })), + [clbs] ); - const { runAsync: refetchClbs, data: clbs = [] } = useRequest2(getTeamClbs, { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - }); - - const filteredGroups = useCreation( - () => groups?.filter((group) => group.name.toLowerCase().includes(searchKey.toLowerCase())), - [groups, searchKey] - ); - const filteredMembers = useCreation( + const groups = useCreation( () => - members - ?.filter((member) => member.memberName.toLowerCase().includes(searchKey.toLowerCase())) - .map((member) => { - const clb = clbs?.find((clb) => String(clb.tmbId) === String(member.tmbId)); - const permission = - member.role === 'owner' - ? new TeamPermission({ isOwner: true }) - : new TeamPermission({ per: clb?.permission }); + clbs.group.map((item) => ({ + ...item, + permission: new TeamPermission({ per: item.permission }) + })), + [clbs] + ); - return { ...member, permission }; - }), - [clbs, members, searchKey] + const orgs = useCreation( + () => + clbs.org.map((item) => ({ + ...item, + permission: new TeamPermission({ per: item.permission }) + })), + [clbs] ); const { runAsync: onUpdateMemberPermission } = useRequest2(updateMemberPermission, { onSuccess: () => { - refetchGroups(); - refetchMembers(); refetchClbs(); - refetchOrgs(); } }); @@ -102,60 +101,60 @@ function PermissionManage() { per: 'write' | 'manage'; }) => { if (groupId) { - const group = groups?.find((group) => group._id === groupId); + const group = groups?.find((group) => group.groupId === groupId); if (group) { const permission = new TeamPermission({ per: group.permission.value }); switch (per) { case 'write': permission.addPer(TeamWritePermissionVal); return onUpdateMemberPermission({ - groupId: group._id, + groupId: group.groupId, permission: permission.value }); case 'manage': permission.addPer(TeamManagePermissionVal); return onUpdateMemberPermission({ - groupId: group._id, + groupId: group.groupId, permission: permission.value }); } } } if (orgId) { - const org = orgs.find((org) => String(org._id) === orgId); + const org = orgs.find((org) => String(org.orgId) === orgId); if (org) { const permission = new TeamPermission({ per: org.permission.value }); switch (per) { case 'write': permission.addPer(TeamWritePermissionVal); return onUpdateMemberPermission({ - orgId: org._id, + orgId: org.orgId, permission: permission.value }); case 'manage': permission.addPer(TeamManagePermissionVal); return onUpdateMemberPermission({ - orgId: org._id, + orgId: org.orgId, permission: permission.value }); } } } if (memberId) { - const member = filteredMembers?.find((member) => String(member.tmbId) === memberId); + const member = members?.find((member) => member.tmbId === memberId); if (member) { const permission = new TeamPermission({ per: member.permission.value }); switch (per) { case 'write': permission.addPer(TeamWritePermissionVal); return onUpdateMemberPermission({ - memberId: String(member.tmbId), + memberId: member.tmbId, permission: permission.value }); case 'manage': permission.addPer(TeamManagePermissionVal); return onUpdateMemberPermission({ - memberId: String(member.tmbId), + memberId: member.tmbId, permission: permission.value }); } @@ -177,40 +176,40 @@ function PermissionManage() { per: 'write' | 'manage'; }) => { if (groupId) { - const group = groups?.find((group) => group._id === groupId); + const group = groups?.find((group) => group.groupId === groupId); if (group) { const permission = new TeamPermission({ per: group.permission.value }); switch (per) { case 'write': permission.removePer(TeamWritePermissionVal); return onUpdateMemberPermission({ - groupId: group._id, + groupId: group.groupId, permission: permission.value }); case 'manage': permission.removePer(TeamManagePermissionVal); return onUpdateMemberPermission({ - groupId: group._id, + groupId: group.groupId, permission: permission.value }); } } } if (orgId) { - const org = orgs.find((org) => String(org._id) === orgId); + const org = orgs.find((org) => String(org.orgId) === orgId); if (org) { const permission = new TeamPermission({ per: org.permission.value }); switch (per) { case 'write': permission.removePer(TeamWritePermissionVal); return onUpdateMemberPermission({ - orgId: org._id, + orgId: org.orgId, permission: permission.value }); case 'manage': permission.removePer(TeamManagePermissionVal); return onUpdateMemberPermission({ - orgId: org._id, + orgId: org.orgId, permission: permission.value }); } @@ -239,6 +238,12 @@ function PermissionManage() { } ); + const { runAsync: onDeleteMemberPermission } = useRequest2(deleteMemberPermission, { + onSuccess: () => { + refetchClbs(); + } + }); + const userManage = userInfo?.permission.hasManagePer; return ( @@ -247,7 +252,7 @@ function PermissionManage() { - {t('user:team.group.group')} / {t('user:team.group.members')} + {`${t('user:team.group.members')} / ${t('user:team.org.org')} / ${t('user:team.group.group')}`} @@ -255,133 +260,206 @@ function PermissionManage() { {t('user:team.group.permission.write')} - + {t('user:team.group.permission.manage')} + + + {t('common:common.Action')} + + - {filteredGroups?.map((group) => ( - - - - - - - - e.target.checked - ? onAddPermission({ groupId: group._id, per: 'write' }) - : onRemovePermission({ groupId: group._id, per: 'write' }) - } - /> - - - - - - e.target.checked - ? onAddPermission({ groupId: group._id, per: 'manage' }) - : onRemovePermission({ groupId: group._id, per: 'manage' }) - } - /> - - - - ))} - {filteredGroups?.length > 0 && filteredOrgs?.length > 0 && ( - - )} + + + + {t('user:team.group.members')} + + + {isExpandMember && + members.map((member) => ( + + + + + {member.name} + + + + + + e.target.checked + ? onAddPermission({ memberId: String(member.tmbId), per: 'write' }) + : onRemovePermission({ memberId: String(member.tmbId), per: 'write' }) + } + /> + + + + + + e.target.checked + ? onAddPermission({ memberId: String(member.tmbId), per: 'manage' }) + : onRemovePermission({ memberId: String(member.tmbId), per: 'manage' }) + } + /> + + + {userManage && + !member.permission.isOwner && + userInfo?.team.tmbId !== member.tmbId && ( + + + onDeleteMemberPermission({ tmbId: String(member.tmbId) })} + /> + + + )} + + ))} - {filteredOrgs?.map((org) => ( - - - - - - - - e.target.checked - ? onAddPermission({ orgId: org._id, per: 'write' }) - : onRemovePermission({ orgId: org._id, per: 'write' }) - } - /> - - - - - - e.target.checked - ? onAddPermission({ orgId: org._id, per: 'manage' }) - : onRemovePermission({ orgId: org._id, per: 'manage' }) - } - /> - - - - ))} + + + + + {t('user:team.org.org')} + + - {filteredOrgs?.length > 0 && filteredMembers?.length > 0 && ( - - )} + {isExpandOrg && + orgs.map((org) => ( + + + + + + + + e.target.checked + ? onAddPermission({ orgId: org.orgId, per: 'write' }) + : onRemovePermission({ orgId: org.orgId, per: 'write' }) + } + /> + + + + + + e.target.checked + ? onAddPermission({ orgId: org.orgId, per: 'manage' }) + : onRemovePermission({ orgId: org.orgId, per: 'manage' }) + } + /> + + + {userInfo?.permission.isOwner && ( + + + onDeleteMemberPermission({ orgId: org.orgId })} + /> + + + )} + + ))} - {filteredMembers?.map((member) => ( - - - - - {member.memberName} - - - - - - e.target.checked - ? onAddPermission({ memberId: String(member.tmbId), per: 'write' }) - : onRemovePermission({ memberId: String(member.tmbId), per: 'write' }) + + + + + {t('user:team.group.group')} + + + + {isExpandGroup && + groups.map((group) => ( + + + - - - - - - e.target.checked - ? onAddPermission({ memberId: String(member.tmbId), per: 'manage' }) - : onRemovePermission({ memberId: String(member.tmbId), per: 'manage' }) - } - /> - - - - ))} + + + + + e.target.checked + ? onAddPermission({ groupId: group.groupId, per: 'write' }) + : onRemovePermission({ groupId: group.groupId, per: 'write' }) + } + /> + + + + + + e.target.checked + ? onAddPermission({ groupId: group.groupId, per: 'manage' }) + : onRemovePermission({ groupId: group.groupId, per: 'manage' }) + } + /> + + + {userInfo?.permission.isOwner && ( + + + onDeleteMemberPermission({ groupId: group.groupId })} + /> + + + )} + + ))} + {isOpenAddPermission && ( + { + refetchClbs(); + onCloseAddPermission(); + }} + mode="all" + /> + )} ); } diff --git a/projects/app/src/pages/account/team/index.tsx b/projects/app/src/pages/account/team/index.tsx index 702183f0e..772326870 100644 --- a/projects/app/src/pages/account/team/index.tsx +++ b/projects/app/src/pages/account/team/index.tsx @@ -75,6 +75,11 @@ const Team = () => { onOpen: onOpenManageGroupMember, onClose: onCloseManageGroupMember } = useDisclosure(); + const { + isOpen: isOpenAddPermission, + onOpen: onOpenAddPermission, + onClose: onCloseAddPermission + } = useDisclosure(); const { runAsync: onLeaveTeam } = useRequest2( async () => { @@ -268,6 +273,18 @@ const Team = () => { /> )} + {teamTab === TeamTabEnum.permission && userInfo?.team.permission.hasManagePer && ( + + )} @@ -276,7 +293,12 @@ const Team = () => { )} {teamTab === TeamTabEnum.org && } - {teamTab === TeamTabEnum.permission && } + {teamTab === TeamTabEnum.permission && ( + + )} {isOpenInvite && userInfo?.team?.teamId && ( diff --git a/projects/app/src/web/support/user/team/api.ts b/projects/app/src/web/support/user/team/api.ts index d5b3410f1..85aabfc91 100644 --- a/projects/app/src/web/support/user/team/api.ts +++ b/projects/app/src/web/support/user/team/api.ts @@ -1,5 +1,10 @@ import { GET, POST, PUT, DELETE } from '@/web/common/api/request'; -import { UpdatePermissionBody } from '@fastgpt/global/support/permission/collaborator'; +import { + CreatePermissionBody, + DeletePermissionQuery, + ListPermissionResponse, + UpdatePermissionBody +} from '@fastgpt/global/support/permission/collaborator'; import { CreateTeamProps, InviteMemberProps, @@ -15,7 +20,6 @@ import { } from '@fastgpt/global/support/user/team/type.d'; import { FeTeamPlanStatusType, TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type'; import { TeamInvoiceHeaderType } from '@fastgpt/global/support/user/team/type'; -import { ResourcePermissionType } from '@fastgpt/global/support/permission/type'; /* --------------- team ---------------- */ export const getTeamList = (status: `${TeamMemberSchema['status']}`) => @@ -40,12 +44,18 @@ export const updateInviteResult = (data: UpdateInviteProps) => export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave'); export const getTeamClbs = () => - GET(`/proApi/support/user/team/collaborator/list`); + GET(`/proApi/support/user/team/collaborator/list`); /* -------------- team collaborator -------------------- */ export const updateMemberPermission = (data: UpdatePermissionBody) => PUT('/proApi/support/user/team/collaborator/updatePermission', data); +export const createMemberPermission = (data: CreatePermissionBody) => + POST('/proApi/support/user/team/collaborator/create', data); + +export const deleteMemberPermission = (id: DeletePermissionQuery) => + DELETE('/proApi/support/user/team/collaborator/delete', id); + /* --------------- team tags ---------------- */ export const getTeamsTags = () => GET(`/proApi/support/user/team/tag/list`); export const loadTeamTagsByDomain = (domain: string) =>