mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-22 12:20:34 +00:00
perf: member group (#4324)
* sync collection * remove lock * perf: member group
This commit is contained in:
4
packages/global/support/permission/memberGroup/api.d.ts
vendored
Normal file
4
packages/global/support/permission/memberGroup/api.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export type GetGroupListBody = {
|
||||||
|
searchKey?: string;
|
||||||
|
withMembers?: boolean;
|
||||||
|
};
|
@@ -1,6 +1,7 @@
|
|||||||
import { TeamMemberItemType } from 'support/user/team/type';
|
import { TeamMemberItemType } from 'support/user/team/type';
|
||||||
import { TeamPermission } from '../user/controller';
|
import { TeamPermission } from '../user/controller';
|
||||||
import { GroupMemberRole } from './constant';
|
import { GroupMemberRole } from './constant';
|
||||||
|
import { Permission } from '../controller';
|
||||||
|
|
||||||
type MemberGroupSchemaType = {
|
type MemberGroupSchemaType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -16,23 +17,25 @@ type GroupMemberSchemaType = {
|
|||||||
role: `${GroupMemberRole}`;
|
role: `${GroupMemberRole}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MemberGroupType = MemberGroupSchemaType & {
|
type MemberGroupListItemType<T extends boolean | undefined> = MemberGroupSchemaType & {
|
||||||
members: {
|
members: T extends true
|
||||||
tmbId: string;
|
? {
|
||||||
name: string;
|
tmbId: string;
|
||||||
avatar: string;
|
name: string;
|
||||||
}[];
|
avatar: string;
|
||||||
count: number;
|
}[]
|
||||||
owner: {
|
: undefined;
|
||||||
tmbId: string;
|
count: T extends true ? number : undefined;
|
||||||
name: string;
|
owner?: T extends true
|
||||||
avatar: string;
|
? {
|
||||||
};
|
tmbId: string;
|
||||||
canEdit: boolean;
|
name: string;
|
||||||
|
avatar: string;
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
permission: T extends true ? Permission : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MemberGroupListType = MemberGroupType[];
|
|
||||||
|
|
||||||
type GroupMemberItemType = {
|
type GroupMemberItemType = {
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
5
packages/global/support/user/api.d.ts
vendored
5
packages/global/support/user/api.d.ts
vendored
@@ -1,4 +1,7 @@
|
|||||||
import { MemberGroupSchemaType, MemberGroupType } from 'support/permission/memberGroup/type';
|
import {
|
||||||
|
MemberGroupSchemaType,
|
||||||
|
MemberGroupListItemType
|
||||||
|
} from 'support/permission/memberGroup/type';
|
||||||
import { OAuthEnum } from './constant';
|
import { OAuthEnum } from './constant';
|
||||||
import { TrackRegisterParams } from './login/api';
|
import { TrackRegisterParams } from './login/api';
|
||||||
import { TeamMemberStatusEnum } from './team/constant';
|
import { TeamMemberStatusEnum } from './team/constant';
|
||||||
|
@@ -55,6 +55,14 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getTeamOwner = async (teamId: string) => {
|
||||||
|
const tmb = await MongoTeamMember.findOne({
|
||||||
|
teamId,
|
||||||
|
role: TeamMemberRoleEnum.owner
|
||||||
|
}).lean();
|
||||||
|
return tmb;
|
||||||
|
};
|
||||||
|
|
||||||
export async function getTmbInfoByTmbId({ tmbId }: { tmbId: string }) {
|
export async function getTmbInfoByTmbId({ tmbId }: { tmbId: string }) {
|
||||||
if (!tmbId) {
|
if (!tmbId) {
|
||||||
return Promise.reject('tmbId or userId is required');
|
return Promise.reject('tmbId or userId is required');
|
||||||
|
@@ -28,20 +28,17 @@ import {
|
|||||||
DEFAULT_USER_AVATAR
|
DEFAULT_USER_AVATAR
|
||||||
} from '@fastgpt/global/common/system/constants';
|
} from '@fastgpt/global/common/system/constants';
|
||||||
import Path from '@/components/common/folder/Path';
|
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 { OrgListItemType, 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 { useContextSelector } from 'use-context-selector';
|
||||||
import { CollaboratorContext } from './context';
|
import { CollaboratorContext } from './context';
|
||||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||||
import { getGroupList } from '@/web/support/user/team/group/api';
|
import { getGroupList } from '@/web/support/user/team/group/api';
|
||||||
import { getOrgList, getOrgMembers } from '@/web/support/user/team/org/api';
|
|
||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
import MemberItemCard from './MemberItemCard';
|
import MemberItemCard from './MemberItemCard';
|
||||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||||
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
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';
|
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
|
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
|
|
||||||
const HoverBoxStyle = {
|
const HoverBoxStyle = {
|
||||||
bgColor: 'myGray.50',
|
bgColor: 'myGray.50',
|
||||||
@@ -76,7 +73,9 @@ function MemberModal({
|
|||||||
const { data: groups = [], loading: loadingGroupsAndOrgs } = useRequest2(
|
const { data: groups = [], loading: loadingGroupsAndOrgs } = useRequest2(
|
||||||
async () => {
|
async () => {
|
||||||
if (!userInfo?.team?.teamId) return [];
|
if (!userInfo?.team?.teamId) return [];
|
||||||
return getGroupList();
|
return getGroupList<false>({
|
||||||
|
withMembers: false
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
manual: false,
|
manual: false,
|
||||||
@@ -117,7 +116,7 @@ function MemberModal({
|
|||||||
return members;
|
return members;
|
||||||
}, [searchText, members, searchedData?.members]);
|
}, [searchText, members, searchedData?.members]);
|
||||||
|
|
||||||
const [selectedGroupList, setSelectedGroupList] = useState<MemberGroupListType>([]);
|
const [selectedGroupList, setSelectedGroupList] = useState<MemberGroupListItemType<false>[]>([]);
|
||||||
const filterGroups = useMemo(() => {
|
const filterGroups = useMemo(() => {
|
||||||
if (searchText) {
|
if (searchText) {
|
||||||
return searchedData?.groups.map((item) => ({
|
return searchedData?.groups.map((item) => ({
|
||||||
|
@@ -3,16 +3,16 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string;
|
name?: string;
|
||||||
avatar: string;
|
avatar?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MemberTag({ name, avatar }: Props) {
|
function MemberTag({ name, avatar }: Props) {
|
||||||
return (
|
return (
|
||||||
<HStack>
|
<HStack>
|
||||||
<Avatar src={avatar} w={['18px', '22px']} rounded="50%" />
|
{avatar && <Avatar src={avatar} w={['18px', '22px']} rounded="50%" />}
|
||||||
<Box maxW={'150px'} className={'textEllipsis'}>
|
<Box maxW={'150px'} className={'textEllipsis'}>
|
||||||
{name}
|
{name || '-'}
|
||||||
</Box>
|
</Box>
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
|
@@ -4,15 +4,13 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
|||||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
|
||||||
import { TeamContext } from '../context';
|
|
||||||
import { postCreateGroup, putUpdateGroup } from '@/web/support/user/team/group/api';
|
import { postCreateGroup, putUpdateGroup } from '@/web/support/user/team/group/api';
|
||||||
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
|
|
||||||
export type GroupFormType = {
|
export type GroupFormType = {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
@@ -21,17 +19,15 @@ export type GroupFormType = {
|
|||||||
|
|
||||||
function GroupInfoModal({
|
function GroupInfoModal({
|
||||||
onClose,
|
onClose,
|
||||||
editGroupId,
|
editGroup,
|
||||||
groups,
|
onSuccess
|
||||||
refetchGroups
|
|
||||||
}: {
|
}: {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
editGroupId?: string;
|
editGroup?: MemberGroupListItemType<true>;
|
||||||
groups: MemberGroupListType;
|
onSuccess: () => void;
|
||||||
refetchGroups: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
const { refetchMembers } = useContextSelector(TeamContext, (v) => v);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
File: AvatarSelect,
|
File: AvatarSelect,
|
||||||
onOpen: onOpenSelectAvatar,
|
onOpen: onOpenSelectAvatar,
|
||||||
@@ -41,14 +37,10 @@ function GroupInfoModal({
|
|||||||
multiple: false
|
multiple: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const group = useMemo(() => {
|
|
||||||
return groups.find((item) => item._id === editGroupId);
|
|
||||||
}, [editGroupId, groups]);
|
|
||||||
|
|
||||||
const { register, handleSubmit, getValues, setValue } = useForm<GroupFormType>({
|
const { register, handleSubmit, getValues, setValue } = useForm<GroupFormType>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: group?.name || '',
|
name: editGroup?.name || '',
|
||||||
avatar: group?.avatar || DEFAULT_TEAM_AVATAR
|
avatar: editGroup?.avatar || DEFAULT_TEAM_AVATAR
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,21 +66,21 @@ function GroupInfoModal({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => Promise.all([onClose(), refetchGroups(), refetchMembers()])
|
onSuccess: () => Promise.all([onClose(), onSuccess()])
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||||
async (data: GroupFormType) => {
|
async (data: GroupFormType) => {
|
||||||
if (!editGroupId) return;
|
if (!editGroup) return;
|
||||||
return putUpdateGroup({
|
return putUpdateGroup({
|
||||||
groupId: editGroupId,
|
groupId: editGroup._id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
avatar: data.avatar
|
avatar: data.avatar
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => Promise.all([onClose(), refetchGroups(), refetchMembers()])
|
onSuccess: () => Promise.all([onClose(), onSuccess()])
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -97,8 +89,8 @@ function GroupInfoModal({
|
|||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={editGroupId ? t('user:team.group.edit') : t('user:team.group.create')}
|
title={editGroup ? t('user:team.group.edit') : t('user:team.group.create')}
|
||||||
iconSrc={group?.avatar ?? DEFAULT_TEAM_AVATAR}
|
iconSrc={editGroup?.avatar ?? DEFAULT_TEAM_AVATAR}
|
||||||
>
|
>
|
||||||
<ModalBody flex={1} overflow={'auto'} display={'flex'} flexDirection={'column'} gap={4}>
|
<ModalBody flex={1} overflow={'auto'} display={'flex'} flexDirection={'column'} gap={4}>
|
||||||
<FormLabel w="80px">{t('user:team.avatar_and_name')}</FormLabel>
|
<FormLabel w="80px">{t('user:team.avatar_and_name')}</FormLabel>
|
||||||
@@ -120,14 +112,14 @@ function GroupInfoModal({
|
|||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onClick={handleSubmit((data) => {
|
onClick={handleSubmit((data) => {
|
||||||
if (editGroupId) {
|
if (editGroup) {
|
||||||
onUpdate(data);
|
onUpdate(data);
|
||||||
} else {
|
} else {
|
||||||
onCreate(data);
|
onCreate(data);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{editGroupId ? t('common:common.Save') : t('common:new_create')}
|
{editGroup ? t('common:common.Save') : t('common:new_create')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
<AvatarSelect onSelect={onSelectAvatar} />
|
<AvatarSelect onSelect={onSelectAvatar} />
|
||||||
|
@@ -26,7 +26,7 @@ import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
|||||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
import {
|
import {
|
||||||
GroupMemberItemType,
|
GroupMemberItemType,
|
||||||
MemberGroupListType
|
MemberGroupListItemType
|
||||||
} from '@fastgpt/global/support/permission/memberGroup/type';
|
} from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
import { useMount } from 'ahooks';
|
import { useMount } from 'ahooks';
|
||||||
|
|
||||||
@@ -43,24 +43,19 @@ export type GroupFormType = {
|
|||||||
function GroupEditModal({
|
function GroupEditModal({
|
||||||
onClose,
|
onClose,
|
||||||
editGroupId,
|
editGroupId,
|
||||||
groups,
|
group,
|
||||||
refetchGroups
|
onSuccess
|
||||||
}: {
|
}: {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
editGroupId?: string;
|
editGroupId?: string;
|
||||||
groups: MemberGroupListType;
|
group: MemberGroupListItemType<true>;
|
||||||
refetchGroups: () => void;
|
onSuccess: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const group = useMemo(() => {
|
|
||||||
return groups.find((item) => item._id === editGroupId);
|
|
||||||
}, [editGroupId, groups]);
|
|
||||||
|
|
||||||
const allMembers = useContextSelector(TeamContext, (v) => v.members);
|
const allMembers = useContextSelector(TeamContext, (v) => v.members);
|
||||||
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>();
|
||||||
|
|
||||||
@@ -94,7 +89,7 @@ function GroupEditModal({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => Promise.all([onClose(), refetchGroups(), refetchMembers()])
|
onSuccess: () => Promise.all([onClose(), onSuccess()])
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { putGroupChangeOwner, putUpdateGroup } from '@/web/support/user/team/group/api';
|
import { putGroupChangeOwner } from '@/web/support/user/team/group/api';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -15,26 +15,24 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
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 React, { useMemo, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { TeamContext } from '../context';
|
import { TeamContext } from '../context';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||||
import { Omit } from '@fastgpt/web/components/common/DndDrag';
|
import { Omit } from '@fastgpt/web/components/common/DndDrag';
|
||||||
|
|
||||||
export type ChangeOwnerModalProps = {
|
|
||||||
groupId: string;
|
|
||||||
groups: MemberGroupListType;
|
|
||||||
refetchGroups: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ChangeOwnerModal({
|
export function ChangeOwnerModal({
|
||||||
onClose,
|
group,
|
||||||
groupId,
|
onSuccess,
|
||||||
groups,
|
onClose
|
||||||
refetchGroups
|
}: {
|
||||||
}: ChangeOwnerModalProps & { onClose: () => void }) {
|
group: MemberGroupListItemType<true>;
|
||||||
|
onSuccess: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [inputValue, setInputValue] = React.useState('');
|
const [inputValue, setInputValue] = React.useState('');
|
||||||
const { data: searchedData } = useRequest2(
|
const { data: searchedData } = useRequest2(
|
||||||
async () => {
|
async () => {
|
||||||
@@ -50,14 +48,8 @@ export function ChangeOwnerModal({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { members: allMembers } = useContextSelector(TeamContext, (v) => v);
|
const { members: allMembers } = useContextSelector(TeamContext, (v) => v);
|
||||||
const group = useMemo(() => {
|
|
||||||
return groups.find((item) => item._id === groupId);
|
|
||||||
}, [groupId, groups]);
|
|
||||||
|
|
||||||
const memberList = searchedData ? searchedData.members : allMembers;
|
const memberList = searchedData ? searchedData.members : allMembers;
|
||||||
|
|
||||||
const [keepAdmin, setKeepAdmin] = useState(true);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenMemberListMenu,
|
isOpen: isOpenMemberListMenu,
|
||||||
onClose: onCloseMemberListMenu,
|
onClose: onCloseMemberListMenu,
|
||||||
@@ -69,10 +61,12 @@ export function ChangeOwnerModal({
|
|||||||
'permission' | 'teamId'
|
'permission' | 'teamId'
|
||||||
> | null>(null);
|
> | null>(null);
|
||||||
|
|
||||||
const { runAsync, loading } = useRequest2(
|
const [keepAdmin, setKeepAdmin] = useState(true);
|
||||||
(tmbId: string) => putGroupChangeOwner(groupId, tmbId),
|
|
||||||
|
const { runAsync: onTransfer, loading } = useRequest2(
|
||||||
|
(tmbId: string) => putGroupChangeOwner(group._id, tmbId),
|
||||||
{
|
{
|
||||||
onSuccess: () => Promise.all([onClose(), refetchGroups()]),
|
onSuccess: () => Promise.all([onClose(), onSuccess()]),
|
||||||
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')
|
||||||
}
|
}
|
||||||
@@ -82,7 +76,7 @@ export function ChangeOwnerModal({
|
|||||||
if (!selectedMember) {
|
if (!selectedMember) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await runAsync(selectedMember.tmbId);
|
await onTransfer(selectedMember.tmbId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -92,7 +86,6 @@ export function ChangeOwnerModal({
|
|||||||
iconColor="primary.600"
|
iconColor="primary.600"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t('common:permission.change_owner')}
|
title={t('common:permission.change_owner')}
|
||||||
isLoading={loading}
|
|
||||||
>
|
>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<HStack>
|
<HStack>
|
||||||
@@ -181,7 +174,9 @@ export function ChangeOwnerModal({
|
|||||||
<Button onClick={onClose} variant={'whiteBase'}>
|
<Button onClick={onClose} variant={'whiteBase'}>
|
||||||
{t('common:common.Cancel')}
|
{t('common:common.Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onConfirm}>{t('common:common.Confirm')}</Button>
|
<Button isLoading={loading} isDisabled={!selectedMember} onClick={onConfirm}>
|
||||||
|
{t('common:common.Confirm')}
|
||||||
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</MyModal>
|
</MyModal>
|
||||||
|
@@ -3,7 +3,6 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
HStack,
|
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Tbody,
|
Tbody,
|
||||||
@@ -16,20 +15,18 @@ import {
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
|
||||||
import { TeamContext } from '../context';
|
|
||||||
import MyMenu, { MenuItemType } from '@fastgpt/web/components/common/MyMenu';
|
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, getGroupList, getGroupMembers } from '@/web/support/user/team/group/api';
|
import { deleteGroup, getGroupList } 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';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import IconButton from '../OrgManage/IconButton';
|
import IconButton from '../OrgManage/IconButton';
|
||||||
import { MemberGroupType } from '@fastgpt/global/support/permission/memberGroup/type';
|
import { MemberGroupListItemType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
|
|
||||||
const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal'));
|
const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal'));
|
||||||
const GroupInfoModal = dynamic(() => import('./GroupInfoModal'));
|
const GroupInfoModal = dynamic(() => import('./GroupInfoModal'));
|
||||||
@@ -39,26 +36,23 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
const { members, teamSize } = useContextSelector(TeamContext, (v) => v);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: groups = [],
|
data: groups = [],
|
||||||
loading: isLoadingGroups,
|
loading: isLoadingGroups,
|
||||||
refresh: refetchGroups
|
refresh: refetchGroups
|
||||||
} = useRequest2(getGroupList, {
|
} = useRequest2(() => getGroupList<true>({ withMembers: true }), {
|
||||||
manual: false,
|
manual: false,
|
||||||
refreshDeps: [userInfo?.team?.teamId]
|
refreshDeps: [userInfo?.team?.teamId]
|
||||||
});
|
});
|
||||||
|
|
||||||
const [editGroup, setEditGroup] = useState<MemberGroupType>();
|
const [editGroup, setEditGroup] = useState<MemberGroupListItemType<true>>();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenGroupInfo,
|
isOpen: isOpenGroupInfo,
|
||||||
onOpen: onOpenGroupInfo,
|
onOpen: onOpenGroupInfo,
|
||||||
onClose: onCloseGroupInfo
|
onClose: onCloseGroupInfo
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
const onEditGroupInfo = (e: MemberGroupListItemType<true>) => {
|
||||||
const onEditGroupInfo = (e: MemberGroupType) => {
|
|
||||||
setEditGroup(e);
|
setEditGroup(e);
|
||||||
onOpenGroupInfo();
|
onOpenGroupInfo();
|
||||||
};
|
};
|
||||||
@@ -67,7 +61,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
type: 'delete',
|
type: 'delete',
|
||||||
content: t('account_team:confirm_delete_group')
|
content: t('account_team:confirm_delete_group')
|
||||||
});
|
});
|
||||||
|
|
||||||
const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, {
|
const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetchGroups();
|
refetchGroups();
|
||||||
@@ -79,21 +72,17 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
onOpen: onOpenManageGroupMember,
|
onOpen: onOpenManageGroupMember,
|
||||||
onClose: onCloseManageGroupMember
|
onClose: onCloseManageGroupMember
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
const onManageMember = (e: MemberGroupType) => {
|
const onManageMember = (e: MemberGroupListItemType<true>) => {
|
||||||
setEditGroup(e);
|
setEditGroup(e);
|
||||||
onOpenManageGroupMember();
|
onOpenManageGroupMember();
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasGroupManagePer = (group: (typeof groups)[0]) => userInfo?.team.permission.hasManagePer;
|
|
||||||
|
|
||||||
const isGroupOwner = (group: (typeof groups)[0]) => userInfo?.team.permission.hasManagePer;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenChangeOwner,
|
isOpen: isOpenChangeOwner,
|
||||||
onOpen: onOpenChangeOwner,
|
onOpen: onOpenChangeOwner,
|
||||||
onClose: onCloseChangeOwner
|
onClose: onCloseChangeOwner
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
const onChangeOwner = (e: MemberGroupType) => {
|
const onChangeOwner = (e: MemberGroupListItemType<true>) => {
|
||||||
setEditGroup(e);
|
setEditGroup(e);
|
||||||
onOpenChangeOwner();
|
onOpenChangeOwner();
|
||||||
};
|
};
|
||||||
@@ -134,58 +123,38 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
<Tbody>
|
<Tbody>
|
||||||
{groups?.map((group) => (
|
{groups?.map((group) => (
|
||||||
<Tr key={group._id} overflow={'unset'}>
|
<Tr key={group._id} overflow={'unset'}>
|
||||||
<Td>
|
|
||||||
<HStack>
|
|
||||||
<MemberTag
|
|
||||||
name={
|
|
||||||
group.name === DefaultGroupName
|
|
||||||
? userInfo?.team.teamName ?? ''
|
|
||||||
: group.name
|
|
||||||
}
|
|
||||||
avatar={group.avatar}
|
|
||||||
/>
|
|
||||||
<Box>({group.name === DefaultGroupName ? teamSize : group.count})</Box>
|
|
||||||
</HStack>
|
|
||||||
</Td>
|
|
||||||
<Td>
|
<Td>
|
||||||
<MemberTag
|
<MemberTag
|
||||||
name={
|
name={
|
||||||
group.name === DefaultGroupName
|
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
|
||||||
? members.find((item) => item.role === 'owner')?.memberName ?? ''
|
|
||||||
: group.owner.name
|
|
||||||
}
|
|
||||||
avatar={
|
|
||||||
group.name === DefaultGroupName
|
|
||||||
? members.find((item) => item.role === 'owner')?.avatar ?? ''
|
|
||||||
: group.owner.avatar
|
|
||||||
}
|
}
|
||||||
|
avatar={group.avatar}
|
||||||
/>
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{group.name === DefaultGroupName ? (
|
<MemberTag name={group.owner?.name} avatar={group.owner?.avatar} />
|
||||||
<AvatarGroup avatars={members.map((v) => v.avatar)} total={teamSize} />
|
|
||||||
) : hasGroupManagePer(group) ? (
|
|
||||||
<MyTooltip label={t('account_team:manage_member')}>
|
|
||||||
<Box cursor="pointer" onClick={() => onManageMember(group)}>
|
|
||||||
<AvatarGroup
|
|
||||||
avatars={group.members.map(
|
|
||||||
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
|
|
||||||
)}
|
|
||||||
total={group.count}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</MyTooltip>
|
|
||||||
) : (
|
|
||||||
<AvatarGroup
|
|
||||||
avatars={group.members.map(
|
|
||||||
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
|
|
||||||
)}
|
|
||||||
total={group.count}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{hasGroupManagePer(group) && group.name !== DefaultGroupName && (
|
<MyTooltip
|
||||||
|
label={group.permission?.hasManagePer ? t('account_team:manage_member') : ''}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
{...(group.permission?.hasManagePer
|
||||||
|
? {
|
||||||
|
cursor: 'pointer',
|
||||||
|
onClick: () => onManageMember(group)
|
||||||
|
}
|
||||||
|
: {})}
|
||||||
|
>
|
||||||
|
<AvatarGroup
|
||||||
|
avatars={group?.members.map((v) => v.avatar)}
|
||||||
|
total={group.count}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</MyTooltip>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{group.permission?.hasManagePer && (
|
||||||
<MyMenu
|
<MyMenu
|
||||||
Button={<IconButton name={'more'} />}
|
Button={<IconButton name={'more'} />}
|
||||||
menuList={[
|
menuList={[
|
||||||
@@ -205,7 +174,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
onManageMember(group);
|
onManageMember(group);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...(isGroupOwner(group)
|
...(group.permission?.isOwner
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t('account_team:transfer_ownership'),
|
label: t('account_team:transfer_ownership'),
|
||||||
@@ -239,34 +208,33 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
</MyBox>
|
</MyBox>
|
||||||
|
|
||||||
<ConfirmDeleteGroupModal />
|
<ConfirmDeleteGroupModal />
|
||||||
{isOpenChangeOwner && editGroup && (
|
|
||||||
<ChangeOwnerModal
|
|
||||||
groupId={editGroup._id}
|
|
||||||
onClose={onCloseChangeOwner}
|
|
||||||
groups={groups}
|
|
||||||
refetchGroups={refetchGroups}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isOpenGroupInfo && (
|
{isOpenGroupInfo && (
|
||||||
<GroupInfoModal
|
<GroupInfoModal
|
||||||
groups={groups}
|
editGroup={editGroup}
|
||||||
refetchGroups={refetchGroups}
|
onSuccess={refetchGroups}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
onCloseGroupInfo();
|
onCloseGroupInfo();
|
||||||
setEditGroup(undefined);
|
setEditGroup(undefined);
|
||||||
}}
|
}}
|
||||||
editGroupId={editGroup?._id}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isOpenChangeOwner && editGroup && (
|
||||||
|
<ChangeOwnerModal
|
||||||
|
group={editGroup}
|
||||||
|
onClose={onCloseChangeOwner}
|
||||||
|
onSuccess={refetchGroups}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{isOpenManageGroupMember && editGroup && (
|
{isOpenManageGroupMember && editGroup && (
|
||||||
<GroupManageMember
|
<GroupManageMember
|
||||||
groups={groups}
|
group={editGroup}
|
||||||
refetchGroups={refetchGroups}
|
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
onCloseManageGroupMember();
|
onCloseManageGroupMember();
|
||||||
setEditGroup(undefined);
|
setEditGroup(undefined);
|
||||||
}}
|
}}
|
||||||
editGroupId={editGroup._id}
|
onSuccess={refetchGroups}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@@ -20,8 +20,9 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
|||||||
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
||||||
import {
|
import {
|
||||||
delRemoveMember,
|
delRemoveMember,
|
||||||
postRestoreMember,
|
getTeamMembers,
|
||||||
putUpdateMemberNameByManager
|
putUpdateMemberNameByManager,
|
||||||
|
postRestoreMember
|
||||||
} from '@/web/support/user/team/api';
|
} from '@/web/support/user/team/api';
|
||||||
import Tag from '@fastgpt/web/components/common/Tag';
|
import Tag from '@fastgpt/web/components/common/Tag';
|
||||||
import Icon from '@fastgpt/web/components/common/Icon';
|
import Icon from '@fastgpt/web/components/common/Icon';
|
||||||
@@ -33,7 +34,6 @@ import dynamic from 'next/dynamic';
|
|||||||
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';
|
||||||
import MyLoading from '@fastgpt/web/components/common/MyLoading';
|
|
||||||
import {
|
import {
|
||||||
TeamMemberRoleEnum,
|
TeamMemberRoleEnum,
|
||||||
TeamMemberStatusEnum
|
TeamMemberStatusEnum
|
||||||
@@ -41,10 +41,12 @@ import {
|
|||||||
import format from 'date-fns/format';
|
import format from 'date-fns/format';
|
||||||
import OrgTags from '@/components/support/user/team/OrgTags';
|
import OrgTags from '@/components/support/user/team/OrgTags';
|
||||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
import { useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { downloadFetch } from '@/web/common/system/utils';
|
import { downloadFetch } from '@/web/common/system/utils';
|
||||||
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
import { TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
|
|
||||||
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
|
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
|
||||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||||
@@ -55,20 +57,72 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
|
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { feConfigs } = useSystemStore();
|
const { feConfigs } = useSystemStore();
|
||||||
|
const isSyncMember = feConfigs?.register_method?.includes('sync');
|
||||||
|
|
||||||
const { myTeams, refetchTeams, members, refetchMembers, onSwitchTeam, MemberScrollData } =
|
const { myTeams, onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
|
||||||
useContextSelector(TeamContext, (v) => v);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenTeamTagsAsync,
|
isOpen: isOpenTeamTagsAsync,
|
||||||
onOpen: onOpenTeamTagsAsync,
|
onOpen: onOpenTeamTagsAsync,
|
||||||
onClose: onCloseTeamTagsAsync
|
onClose: onCloseTeamTagsAsync
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
|
// member action
|
||||||
|
const {
|
||||||
|
data: members = [],
|
||||||
|
isLoading: loadingMembers,
|
||||||
|
refreshList: refetchMemberList,
|
||||||
|
ScrollData: MemberScrollData
|
||||||
|
} = useScrollPagination(getTeamMembers, {
|
||||||
|
pageSize: 20,
|
||||||
|
params: {
|
||||||
|
withLeaved: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
|
const { data: searchMembersData, run: refreshSearchMembers } = useRequest2(
|
||||||
|
async () => {
|
||||||
|
if (!searchText) return Promise.resolve();
|
||||||
|
return GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: false,
|
||||||
|
throttleWait: 500,
|
||||||
|
refreshDeps: [searchText]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const onRefreshMembers = useCallback(() => {
|
||||||
|
refetchMemberList();
|
||||||
|
refreshSearchMembers();
|
||||||
|
}, [refetchMemberList, refreshSearchMembers]);
|
||||||
|
|
||||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||||
|
|
||||||
|
const { runAsync: onSyncMember, loading: isSyncing } = useRequest2(postSyncMembers, {
|
||||||
|
onSuccess: onRefreshMembers,
|
||||||
|
successToast: t('account_team:sync_member_success'),
|
||||||
|
errorToast: t('account_team:sync_member_failed')
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||||
|
content: t('account_team:confirm_leave_team')
|
||||||
|
});
|
||||||
|
const { runAsync: onLeaveTeam } = useRequest2(delLeaveTeam, {
|
||||||
|
onSuccess() {
|
||||||
|
const defaultTeam = myTeams[0];
|
||||||
|
onSwitchTeam(defaultTeam.teamId);
|
||||||
|
},
|
||||||
|
errorToast: t('account_team:user_team_leave_team_failed')
|
||||||
|
});
|
||||||
|
|
||||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm({
|
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm({
|
||||||
type: 'delete'
|
type: 'delete'
|
||||||
});
|
});
|
||||||
|
const { runAsync: onRemoveMember } = useRequest2(delRemoveMember, {
|
||||||
|
onSuccess: onRefreshMembers
|
||||||
|
});
|
||||||
|
|
||||||
const { ConfirmModal: ConfirmRestoreMemberModal, openConfirm: openRestoreMember } = useConfirm({
|
const { ConfirmModal: ConfirmRestoreMemberModal, openConfirm: openRestoreMember } = useConfirm({
|
||||||
type: 'common',
|
type: 'common',
|
||||||
@@ -76,59 +130,13 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
iconSrc: 'common/confirm/restoreTip',
|
iconSrc: 'common/confirm/restoreTip',
|
||||||
iconColor: 'primary.500'
|
iconColor: 'primary.500'
|
||||||
});
|
});
|
||||||
|
const { runAsync: onRestore } = useRequest2(postRestoreMember, {
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
onSuccess: onRefreshMembers,
|
||||||
const isSyncMember = feConfigs.register_method?.includes('sync');
|
successToast: t('common:common.Success'),
|
||||||
|
|
||||||
const { data: searchMembersData } = useRequest2(
|
|
||||||
async () => {
|
|
||||||
if (!searchText) return Promise.resolve();
|
|
||||||
return GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false });
|
|
||||||
},
|
|
||||||
{
|
|
||||||
manual: false,
|
|
||||||
throttleWait: 500,
|
|
||||||
debounceWait: 200,
|
|
||||||
refreshDeps: [searchText]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const { runAsync: onLeaveTeam } = useRequest2(
|
|
||||||
async () => {
|
|
||||||
const defaultTeam = myTeams[0];
|
|
||||||
// change to personal team
|
|
||||||
onSwitchTeam(defaultTeam.teamId);
|
|
||||||
return delLeaveTeam();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess() {
|
|
||||||
refetchTeams();
|
|
||||||
refetchMembers();
|
|
||||||
},
|
|
||||||
errorToast: t('account_team:user_team_leave_team_failed')
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
|
||||||
content: t('account_team:confirm_leave_team')
|
|
||||||
});
|
|
||||||
|
|
||||||
const { runAsync: onSyncMember, loading: isSyncing } = useRequest2(postSyncMembers, {
|
|
||||||
onSuccess() {
|
|
||||||
refetchMembers();
|
|
||||||
},
|
|
||||||
successToast: t('account_team:sync_member_success'),
|
|
||||||
errorToast: t('account_team:sync_member_failed')
|
|
||||||
});
|
|
||||||
|
|
||||||
const { runAsync: onRestore, loading: isUpdateInvite } = useRequest2(postRestoreMember, {
|
|
||||||
onSuccess() {
|
|
||||||
refetchMembers();
|
|
||||||
},
|
|
||||||
successToast: t('common:user.team.invite.Accepted'),
|
|
||||||
errorToast: t('common:user.team.invite.Reject')
|
errorToast: t('common:user.team.invite.Reject')
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLoading = isUpdateInvite || isSyncing;
|
const isLoading = loadingMembers || isSyncing;
|
||||||
|
|
||||||
const { EditModal: EditMemberNameModal, onOpenModal: openEditMemberName } = useEditTextarea({
|
const { EditModal: EditMemberNameModal, onOpenModal: openEditMemberName } = useEditTextarea({
|
||||||
title: t('account_team:edit_member'),
|
title: t('account_team:edit_member'),
|
||||||
@@ -141,7 +149,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
defaultVal: memberName,
|
defaultVal: memberName,
|
||||||
onSuccess: (newName: string) => {
|
onSuccess: (newName: string) => {
|
||||||
return putUpdateMemberNameByManager(tmbId, newName).then(() => {
|
return putUpdateMemberNameByManager(tmbId, newName).then(() => {
|
||||||
refetchMembers();
|
onRefreshMembers();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
@@ -155,7 +163,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && <MyLoading />}
|
|
||||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||||
{Tabs}
|
{Tabs}
|
||||||
<HStack alignItems={'center'}>
|
<HStack alignItems={'center'}>
|
||||||
@@ -237,7 +244,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Box flex={'1 0 0'} overflow={'auto'}>
|
<MyBox isLoading={isLoading} flex={'1 0 0'} overflow={'auto'}>
|
||||||
<MemberScrollData>
|
<MemberScrollData>
|
||||||
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
<TableContainer overflow={'unset'} fontSize={'sm'}>
|
||||||
<Table overflow={'unset'}>
|
<Table overflow={'unset'}>
|
||||||
@@ -321,27 +328,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openRemoveMember(
|
openRemoveMember(
|
||||||
() => delRemoveMember(member.tmbId).then(refetchMembers),
|
() => onRemoveMember(member.tmbId),
|
||||||
undefined,
|
|
||||||
t('account_team:remove_tip', {
|
|
||||||
username: member.memberName
|
|
||||||
})
|
|
||||||
)();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Icon
|
|
||||||
name={'common/trash'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
w="1rem"
|
|
||||||
p="1"
|
|
||||||
borderRadius="sm"
|
|
||||||
_hover={{
|
|
||||||
color: 'red.600',
|
|
||||||
bgColor: 'myGray.100'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
openRemoveMember(
|
|
||||||
() => delRemoveMember(member.tmbId).then(refetchMembers),
|
|
||||||
undefined,
|
undefined,
|
||||||
t('account_team:remove_tip', {
|
t('account_team:remove_tip', {
|
||||||
username: member.memberName
|
username: member.memberName
|
||||||
@@ -385,7 +372,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
|||||||
<EditMemberNameModal />
|
<EditMemberNameModal />
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</MemberScrollData>
|
</MemberScrollData>
|
||||||
</Box>
|
</MyBox>
|
||||||
|
|
||||||
<ConfirmLeaveTeamModal />
|
<ConfirmLeaveTeamModal />
|
||||||
{isOpenInvite && userInfo?.team?.teamId && <InviteModal onClose={onCloseInvite} />}
|
{isOpenInvite && userInfo?.team?.teamId && <InviteModal onClose={onCloseInvite} />}
|
||||||
|
@@ -14,7 +14,7 @@ import {
|
|||||||
Tr,
|
Tr,
|
||||||
VStack
|
VStack
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import type { OrgListItemType, OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import type { OrgListItemType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
|
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
|
||||||
@@ -32,12 +32,9 @@ import { defaultOrgForm, type OrgFormType } from './OrgInfoModal';
|
|||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
import Path from '@/components/common/folder/Path';
|
import Path from '@/components/common/folder/Path';
|
||||||
import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
|
||||||
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import { delRemoveMember } from '@/web/support/user/team/api';
|
import { delRemoveMember } from '@/web/support/user/team/api';
|
||||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
|
||||||
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
import useOrg from '@/web/support/user/team/org/hooks/useOrg';
|
||||||
|
|
||||||
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
|
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
|
||||||
|
@@ -13,10 +13,8 @@ import { useUserStore } from '@/web/support/user/useUserStore';
|
|||||||
import type { TeamTmbItemType, TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
import type { TeamTmbItemType, TeamMemberItemType } from '@fastgpt/global/support/user/team/type';
|
||||||
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 { getGroupList } from '@/web/support/user/team/group/api';
|
|
||||||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
|
||||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||||
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
const EditInfoModal = dynamic(() => import('./EditInfoModal'));
|
const EditInfoModal = dynamic(() => import('./EditInfoModal'));
|
||||||
|
|
||||||
@@ -55,6 +53,8 @@ export const TeamContext = createContext<TeamModalContextType>({
|
|||||||
|
|
||||||
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const [editTeamData, setEditTeamData] = useState<EditTeamFormDataType>();
|
const [editTeamData, setEditTeamData] = useState<EditTeamFormDataType>();
|
||||||
const { userInfo, initUserInfo } = useUserStore();
|
const { userInfo, initUserInfo } = useUserStore();
|
||||||
|
|
||||||
@@ -96,10 +96,12 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
||||||
async (teamId: string) => {
|
async (teamId: string) => {
|
||||||
await putSwitchTeam(teamId);
|
await putSwitchTeam(teamId);
|
||||||
refetchMembers();
|
|
||||||
return initUserInfo();
|
return initUserInfo();
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
router.reload();
|
||||||
|
},
|
||||||
errorToast: t('common:user.team.Switch Team Failed')
|
errorToast: t('common:user.team.Switch Team Failed')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -1,14 +1,19 @@
|
|||||||
import { DELETE, GET, POST, PUT } from '@/web/common/api/request';
|
import { DELETE, GET, POST, PUT } from '@/web/common/api/request';
|
||||||
|
import { GetGroupListBody } from '@fastgpt/global/support/permission/memberGroup/api';
|
||||||
import type {
|
import type {
|
||||||
GroupMemberItemType,
|
GroupMemberItemType,
|
||||||
MemberGroupListType
|
MemberGroupListItemType
|
||||||
} from '@fastgpt/global/support/permission/memberGroup/type';
|
} from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
import type {
|
import type {
|
||||||
postCreateGroupData,
|
postCreateGroupData,
|
||||||
putUpdateGroupData
|
putUpdateGroupData
|
||||||
} from '@fastgpt/global/support/user/team/group/api';
|
} from '@fastgpt/global/support/user/team/group/api';
|
||||||
|
|
||||||
export const getGroupList = () => GET<MemberGroupListType>('/proApi/support/user/team/group/list');
|
export const getGroupList = <T extends boolean>(data: GetGroupListBody) =>
|
||||||
|
POST<MemberGroupListItemType<T>[]>('/proApi/support/user/team/group/list', data).then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
|
||||||
export const postCreateGroup = (data: postCreateGroupData) =>
|
export const postCreateGroup = (data: postCreateGroupData) =>
|
||||||
POST('/proApi/support/user/team/group/create', data);
|
POST('/proApi/support/user/team/group/create', data);
|
||||||
|
Reference in New Issue
Block a user