13
docSite/content/zh-cn/docs/development/upgrading/4818.md
Normal file
13
docSite/content/zh-cn/docs/development/upgrading/4818.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: 'V4.8.18(进行中)'
|
||||||
|
description: 'FastGPT V4.8.18 更新说明'
|
||||||
|
icon: 'upgrade'
|
||||||
|
draft: false
|
||||||
|
toc: true
|
||||||
|
weight: 806
|
||||||
|
---
|
||||||
|
|
||||||
|
## 完整更新内容
|
||||||
|
|
||||||
|
1.
|
||||||
|
2. 新增 - 支持组织架构权限模式
|
||||||
10
packages/global/support/user/team/org/api.d.ts
vendored
10
packages/global/support/user/team/org/api.d.ts
vendored
@@ -20,15 +20,9 @@ export type putUpdateOrgData = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type putMoveOrgData = {
|
export type putMoveOrgType = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
parentId: string;
|
targetOrgId: string;
|
||||||
};
|
|
||||||
|
|
||||||
export type putMoveOrgMemberData = {
|
|
||||||
orgId: string;
|
|
||||||
tmbId: string;
|
|
||||||
newOrgId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// type putChnageOrgOwnerData = {
|
// type putChnageOrgOwnerData = {
|
||||||
|
|||||||
1
packages/global/support/user/team/type.d.ts
vendored
1
packages/global/support/user/team/type.d.ts
vendored
@@ -55,6 +55,7 @@ export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & {
|
|||||||
export type TeamTmbItemType = {
|
export type TeamTmbItemType = {
|
||||||
userId: string;
|
userId: string;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
|
teamAvatar?: string;
|
||||||
teamName: string;
|
teamName: string;
|
||||||
memberName: string;
|
memberName: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||||
import { AuthModeType, AuthResponseType } from '../type';
|
import { AuthModeType, AuthResponseType } from '../type';
|
||||||
import { parseHeaderCert } from '../controller';
|
|
||||||
import { getTmbInfoByTmbId } from '../../user/team/controller';
|
|
||||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
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 ({
|
export const authOrgMember = async ({
|
||||||
orgIds,
|
orgIds,
|
||||||
req,
|
...props
|
||||||
authToken = false,
|
|
||||||
authRoot = false,
|
|
||||||
authApiKey = false
|
|
||||||
}: {
|
}: {
|
||||||
orgIds: string | string[];
|
orgIds: string | string[];
|
||||||
} & AuthModeType): Promise<AuthResponseType> => {
|
} & AuthModeType): Promise<AuthResponseType> => {
|
||||||
const result = await parseHeaderCert({ req, authToken, authApiKey, authRoot });
|
const result = await authUserPer({
|
||||||
const { teamId, tmbId, isRoot } = result;
|
...props,
|
||||||
|
per: ManagePermissionVal
|
||||||
|
});
|
||||||
|
const { teamId, tmbId, isRoot, tmb } = result;
|
||||||
|
|
||||||
if (isRoot) {
|
if (isRoot) {
|
||||||
return {
|
return {
|
||||||
teamId,
|
teamId,
|
||||||
@@ -28,13 +32,6 @@ export const authOrgMember = async ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(orgIds)) {
|
|
||||||
orgIds = [orgIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
// const promises = orgIds.map((orgId) => getOrgMemberRole({ orgId, tmbId }));
|
|
||||||
|
|
||||||
const tmb = await getTmbInfoByTmbId({ tmbId });
|
|
||||||
if (tmb.permission.hasManagePer) {
|
if (tmb.permission.hasManagePer) {
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
@@ -43,16 +40,4 @@ export const authOrgMember = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(TeamErrEnum.unAuthTeam);
|
return Promise.reject(TeamErrEnum.unAuthTeam);
|
||||||
|
|
||||||
// const targetRole = OrgMemberRole[role];
|
|
||||||
// for (const orgRole of orgRoles) {
|
|
||||||
// if (!orgRole || checkOrgRole(orgRole, targetRole)) {
|
|
||||||
// return Promise.reject(TeamErrEnum.unAuthTeam);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// ...result,
|
|
||||||
// permission: tmb.permission
|
|
||||||
// };
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||||
import type {
|
import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
OrgMemberSchemaType,
|
|
||||||
OrgSchemaType
|
|
||||||
} from '@fastgpt/global/support/user/team/org/type';
|
|
||||||
import type { ClientSession } from 'mongoose';
|
import type { ClientSession } from 'mongoose';
|
||||||
import { MongoOrgModel } from './orgSchema';
|
import { MongoOrgModel } from './orgSchema';
|
||||||
import { MongoOrgMemberModel } from './orgMemberSchema';
|
import { MongoOrgMemberModel } from './orgMemberSchema';
|
||||||
@@ -36,16 +33,6 @@ import { MongoOrgMemberModel } from './orgMemberSchema';
|
|||||||
// return compareRole(role, targetRole) >= 0;
|
// return compareRole(role, targetRole) >= 0;
|
||||||
// };
|
// };
|
||||||
|
|
||||||
export const getOrgsByTeamId = async (teamId: string) => {
|
|
||||||
const orgs = await MongoOrgModel.find({
|
|
||||||
teamId
|
|
||||||
})
|
|
||||||
.populate<{ members: OrgMemberSchemaType }>('members')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
return orgs;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) =>
|
export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) =>
|
||||||
MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean();
|
MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean();
|
||||||
|
|
||||||
|
|||||||
@@ -53,15 +53,10 @@ OrgSchema.virtual('permission', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OrgSchema.index(
|
OrgSchema.index({
|
||||||
{
|
teamId: 1,
|
||||||
teamId: 1,
|
path: 1
|
||||||
path: 1
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
unique: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
|
|||||||
return {
|
return {
|
||||||
userId: String(tmb.userId),
|
userId: String(tmb.userId),
|
||||||
teamId: String(tmb.teamId),
|
teamId: String(tmb.teamId),
|
||||||
|
teamAvatar: tmb.team.avatar,
|
||||||
teamName: tmb.team.name,
|
teamName: tmb.team.name,
|
||||||
memberName: tmb.name,
|
memberName: tmb.name,
|
||||||
avatar: tmb.team.avatar,
|
avatar: tmb.team.avatar,
|
||||||
|
|||||||
@@ -44,6 +44,21 @@ function embedChatbot() {
|
|||||||
|
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'data-bot-src') {
|
||||||
|
const newBotSrc = script.getAttribute('data-bot-src');
|
||||||
|
if (newBotSrc) {
|
||||||
|
iframe.src = newBotSrc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
observer.observe(script, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-bot-src']
|
||||||
|
});
|
||||||
|
|
||||||
let chatBtnDragged = false;
|
let chatBtnDragged = false;
|
||||||
let chatBtnDown = false;
|
let chatBtnDown = false;
|
||||||
let chatBtnMouseX;
|
let chatBtnMouseX;
|
||||||
@@ -96,3 +111,4 @@ function embedChatbot() {
|
|||||||
document.body.appendChild(ChatBtn);
|
document.body.appendChild(ChatBtn);
|
||||||
}
|
}
|
||||||
window.addEventListener('load', embedChatbot);
|
window.addEventListener('load', embedChatbot);
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import MemberTag from '../../../../../components/support/user/team/Info/MemberTa
|
|||||||
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';
|
||||||
|
|
||||||
const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal'));
|
const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal'));
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ function MemberTable({
|
|||||||
<Td>
|
<Td>
|
||||||
{hasGroupManagePer(group) && group.name !== DefaultGroupName && (
|
{hasGroupManagePer(group) && group.name !== DefaultGroupName && (
|
||||||
<MyMenu
|
<MyMenu
|
||||||
Button={<MyIcon name={'edit'} cursor={'pointer'} w="1rem" />}
|
Button={<IconButton name={'more'} />}
|
||||||
menuList={[
|
menuList={[
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
|
import { IconProps } from '@chakra-ui/react';
|
||||||
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';
|
||||||
|
|
||||||
function IconButton({ name, onClick }: { name: IconNameType; onClick: () => void }) {
|
function IconButton({
|
||||||
|
name,
|
||||||
|
w = '1rem',
|
||||||
|
h = '1rem',
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
name: IconNameType;
|
||||||
|
} & IconProps) {
|
||||||
return (
|
return (
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name={name}
|
name={name}
|
||||||
w={'1rem'}
|
w={w}
|
||||||
h={'1rem'}
|
h={h}
|
||||||
transition={'background 0.1s'}
|
transition={'background 0.1s'}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
p="1"
|
p="1"
|
||||||
@@ -15,7 +23,7 @@ function IconButton({ name, onClick }: { name: IconNameType; onClick: () => void
|
|||||||
bg: 'myGray.05',
|
bg: 'myGray.05',
|
||||||
color: 'primary.600'
|
color: 'primary.600'
|
||||||
}}
|
}}
|
||||||
onClick={onClick}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,86 +4,95 @@ import { postCreateOrg, putUpdateOrg } from '@/web/support/user/team/org/api';
|
|||||||
import { Button, HStack, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
|
import { Button, HStack, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
|
||||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||||
import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants';
|
import { DEFAULT_ORG_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||||
import type { OrgType } 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 FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
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 { useEffect } from 'react';
|
import dynamic from 'next/dynamic';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
export type OrgFormType = {
|
export type OrgFormType = {
|
||||||
|
_id: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
path: string;
|
||||||
|
parentId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultOrgForm: OrgFormType = {
|
||||||
|
_id: '',
|
||||||
|
avatar: '',
|
||||||
|
description: '',
|
||||||
|
name: '',
|
||||||
|
path: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
function OrgInfoModal({
|
function OrgInfoModal({
|
||||||
editOrg,
|
editOrg,
|
||||||
createOrgParentId: parentId,
|
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess
|
onSuccess
|
||||||
}: {
|
}: {
|
||||||
editOrg?: OrgType;
|
editOrg: OrgFormType;
|
||||||
createOrgParentId?: string;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess?: () => void;
|
onSuccess: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { File: AvatarSelect, onOpen: onOpenSelectAvatar } = useSelectFile({
|
|
||||||
fileType: '.jpg, .jpeg, .png',
|
|
||||||
multiple: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const { register, handleSubmit, getValues, setValue } = useForm<OrgFormType>({
|
const isEdit = !!editOrg._id;
|
||||||
|
|
||||||
|
const { register, handleSubmit, setValue, watch } = useForm<OrgFormType>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: '',
|
name: editOrg.name,
|
||||||
avatar: DEFAULT_ORG_AVATAR,
|
avatar: editOrg.avatar,
|
||||||
description: undefined
|
description: editOrg.description
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const avatar = watch('avatar');
|
||||||
useEffect(() => {
|
|
||||||
setValue('name', editOrg?.name ?? '');
|
|
||||||
setValue('avatar', editOrg?.avatar || DEFAULT_ORG_AVATAR);
|
|
||||||
setValue('description', editOrg?.description);
|
|
||||||
}, [editOrg, setValue]);
|
|
||||||
|
|
||||||
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
|
const { run: onCreate, loading: isLoadingCreate } = useRequest2(
|
||||||
(data: OrgFormType, parentId: string) => {
|
async (data: OrgFormType) => {
|
||||||
|
if (!editOrg.parentId) return;
|
||||||
return postCreateOrg({
|
return postCreateOrg({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
avatar: data.avatar,
|
avatar: data.avatar,
|
||||||
parentId,
|
parentId: editOrg.parentId,
|
||||||
description: data.description
|
description: data.description
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
successToast: t('common:common.Create Success'),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
onClose();
|
onClose();
|
||||||
onSuccess?.();
|
onSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||||
(data: OrgFormType, orgId: string) => {
|
async (data: OrgFormType) => {
|
||||||
|
if (!editOrg._id) return;
|
||||||
return putUpdateOrg({
|
return putUpdateOrg({
|
||||||
orgId,
|
orgId: editOrg._id,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
avatar: data.avatar,
|
avatar: data.avatar,
|
||||||
description: data.description
|
description: data.description
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
successToast: t('common:common.Update Success'),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
onClose();
|
onClose();
|
||||||
onSuccess?.();
|
onSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { File: AvatarSelect, onOpen: onOpenSelectAvatar } = useSelectFile({
|
||||||
|
fileType: '.jpg, .jpeg, .png',
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
const { loading: uploadingAvatar, run: onSelectAvatar } = useRequest2(
|
const { loading: uploadingAvatar, run: onSelectAvatar } = useRequest2(
|
||||||
async (file: File[]) => {
|
async (file: File[]) => {
|
||||||
const src = await compressImgFileAndUpload({
|
const src = await compressImgFileAndUpload({
|
||||||
@@ -101,20 +110,20 @@ function OrgInfoModal({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = uploadingAvatar;
|
const isLoading = uploadingAvatar || isLoadingUpdate || isLoadingCreate;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
isOpen={!!(editOrg || parentId)}
|
isOpen
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={editOrg ? t('account_team:edit_org_info') : t('account_team:create_org')}
|
title={isEdit ? t('account_team:edit_org_info') : t('account_team:create_org')}
|
||||||
iconSrc={editOrg?.avatar || DEFAULT_ORG_AVATAR}
|
iconSrc={'modal/edit'}
|
||||||
>
|
>
|
||||||
<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>
|
||||||
<HStack>
|
<HStack>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={getValues('avatar') || DEFAULT_ORG_AVATAR}
|
src={avatar || DEFAULT_ORG_AVATAR}
|
||||||
onClick={onOpenSelectAvatar}
|
onClick={onOpenSelectAvatar}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
@@ -132,14 +141,14 @@ function OrgInfoModal({
|
|||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onClick={handleSubmit((data) => {
|
onClick={handleSubmit((data) => {
|
||||||
if (editOrg) {
|
if (isEdit) {
|
||||||
onUpdate(data, editOrg._id);
|
onUpdate(data);
|
||||||
} else if (parentId) {
|
} else {
|
||||||
onCreate(data, parentId);
|
onCreate(data);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{editOrg ? t('common:common.Save') : t('common:new_create')}
|
{isEdit ? t('common:common.Save') : t('common:new_create')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
<AvatarSelect onSelect={onSelectAvatar} />
|
<AvatarSelect onSelect={onSelectAvatar} />
|
||||||
@@ -147,4 +156,4 @@ function OrgInfoModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OrgInfoModal;
|
export default dynamic(() => Promise.resolve(OrgInfoModal), { ssr: false });
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
|||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { TeamContext } from '../context';
|
import { TeamContext } from '../context';
|
||||||
|
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
export type GroupFormType = {
|
export type GroupFormType = {
|
||||||
members: {
|
members: {
|
||||||
@@ -39,69 +41,67 @@ function CheckboxIcon({
|
|||||||
return <MyIcon name={name} w="12px" />;
|
return <MyIcon name={name} w="12px" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId?: string }) {
|
function OrgMemberManageModal({
|
||||||
// 1. Owner can not be deleted, toast
|
currentOrg,
|
||||||
// 2. Owner/Admin can manage members
|
refetchOrgs,
|
||||||
// 3. Owner can add/remove admins
|
onClose
|
||||||
|
}: {
|
||||||
|
currentOrg: OrgType;
|
||||||
|
refetchOrgs: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const allMembers = useContextSelector(TeamContext, (v) => v.members);
|
||||||
members: allMembers,
|
|
||||||
orgs,
|
|
||||||
refetchOrgs,
|
|
||||||
refetchMembers
|
|
||||||
} = useContextSelector(TeamContext, (v) => v);
|
|
||||||
|
|
||||||
const org = useMemo(() => orgs.find((item) => item._id === editOrgId), [editOrgId, orgs]);
|
const [selectedMembers, setSelectedMembers] = useState<string[]>(
|
||||||
|
currentOrg.members.map((item) => item.tmbId)
|
||||||
const [members, setMembers] = useState<{ tmbId: string }[]>(org?.members || []);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMembers(org?.members || []);
|
|
||||||
}, [org]);
|
|
||||||
|
|
||||||
const [searchKey, setSearchKey] = useState('');
|
const [searchKey, setSearchKey] = useState('');
|
||||||
const filtered = useMemo(() => {
|
const filterMembers = useMemo(() => {
|
||||||
return [
|
if (!searchKey) return allMembers;
|
||||||
...allMembers.filter((member) => {
|
const regx = new RegExp(searchKey, 'i');
|
||||||
if (member.memberName.toLowerCase().includes(searchKey.toLowerCase())) return true;
|
return allMembers.filter((member) => regx.test(member.memberName));
|
||||||
return false;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}, [searchKey, allMembers]);
|
}, [searchKey, allMembers]);
|
||||||
|
|
||||||
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
const { run: onUpdate, loading: isLoadingUpdate } = useRequest2(
|
||||||
async () => {
|
() => {
|
||||||
if (!editOrgId) return;
|
|
||||||
return putUpdateOrgMembers({
|
return putUpdateOrgMembers({
|
||||||
orgId: editOrgId,
|
orgId: currentOrg._id,
|
||||||
members
|
members: selectedMembers.map((tmbId) => ({
|
||||||
|
tmbId
|
||||||
|
}))
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => Promise.all([onClose(), refetchOrgs(), refetchMembers()])
|
successToast: t('common:common.Update Success'),
|
||||||
|
onSuccess() {
|
||||||
|
refetchOrgs();
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSelected = (memberId: string) => {
|
const isSelected = (memberId: string) => {
|
||||||
return members.find((item) => item.tmbId === memberId);
|
return selectedMembers.find((tmbId) => tmbId === memberId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleSelect = (memberId: string) => {
|
const handleToggleSelect = (memberId: string) => {
|
||||||
if (isSelected(memberId)) {
|
if (isSelected(memberId)) {
|
||||||
setMembers(members.filter((item) => item.tmbId !== memberId));
|
setSelectedMembers((state) => state.filter((tmbId) => tmbId !== memberId));
|
||||||
} else {
|
} else {
|
||||||
setMembers([...members, { tmbId: memberId }]);
|
setSelectedMembers((state) => [...state, memberId]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLoading = isLoadingUpdate;
|
const isLoading = isLoadingUpdate;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
isOpen={!!editOrgId}
|
isOpen
|
||||||
title={t('user:team.group.manage_member')}
|
title={t('user:team.group.manage_member')}
|
||||||
iconSrc={org?.avatar}
|
iconSrc={currentOrg?.avatar}
|
||||||
iconColor="primary.600"
|
|
||||||
minW="800px"
|
minW="800px"
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
isCentered
|
isCentered
|
||||||
@@ -124,7 +124,7 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Flex flexDirection="column" mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
<Flex flexDirection="column" mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||||
{filtered.map((member) => {
|
{filterMembers.map((member) => {
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
py="2"
|
py="2"
|
||||||
@@ -153,35 +153,30 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
|
<Flex borderLeft="1px" borderColor="myGray.200" flexDirection="column" p="4" h={'100%'}>
|
||||||
<Box mt={2}>{`${t('common:chosen')}:${members.length}`}</Box>
|
<Box mt={2}>{`${t('common:chosen')}:${selectedMembers.length}`}</Box>
|
||||||
<Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
<Flex mt={3} flexDirection="column" flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||||
{members.map((member) => {
|
{selectedMembers.map((tmbId) => {
|
||||||
|
const member = allMembers.find((item) => item.tmbId === tmbId)!;
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
py="2"
|
py="2"
|
||||||
px={3}
|
px={3}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
key={member.tmbId}
|
key={tmbId}
|
||||||
_hover={{ bg: 'myGray.50' }}
|
_hover={{ bg: 'myGray.50' }}
|
||||||
_notLast={{ mb: 2 }}
|
_notLast={{ mb: 2 }}
|
||||||
>
|
>
|
||||||
<HStack>
|
<HStack>
|
||||||
<Avatar
|
<Avatar src={member?.avatar} w="1.5rem" borderRadius={'md'} />
|
||||||
src={allMembers.find((item) => item.tmbId === member.tmbId)?.avatar}
|
<Box>{member?.memberName}</Box>
|
||||||
w="1.5rem"
|
|
||||||
borderRadius={'md'}
|
|
||||||
/>
|
|
||||||
<Box>
|
|
||||||
{allMembers.find((item) => item.tmbId === member.tmbId)?.memberName}
|
|
||||||
</Box>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name={'common/closeLight'}
|
name={'common/closeLight'}
|
||||||
w={'1rem'}
|
w={'1rem'}
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
_hover={{ color: 'red.600' }}
|
_hover={{ color: 'red.600' }}
|
||||||
onClick={() => handleToggleSelect(member.tmbId)}
|
onClick={() => handleToggleSelect(tmbId)}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
@@ -199,4 +194,4 @@ function OrgMemberModal({ onClose, editOrgId }: { onClose: () => void; editOrgId
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OrgMemberModal;
|
export default dynamic(() => Promise.resolve(OrgMemberManageModal), { ssr: false });
|
||||||
@@ -1,75 +1,69 @@
|
|||||||
import { putMoveOrg, putMoveOrgMember } from '@/web/support/user/team/org/api';
|
import { putMoveOrg } from '@/web/support/user/team/org/api';
|
||||||
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
import { Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import type { TeamTmbItemType } from '@fastgpt/global/support/user/team/type';
|
|
||||||
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 { useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import OrgTree from './OrgTree';
|
import OrgTree from './OrgTree';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
|
|
||||||
function OrgMoveModal({
|
function OrgMoveModal({
|
||||||
movingOrg,
|
movingOrg,
|
||||||
movingTmb,
|
|
||||||
orgs,
|
orgs,
|
||||||
team,
|
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess
|
onSuccess
|
||||||
}: {
|
}: {
|
||||||
movingOrg?: OrgType;
|
movingOrg: OrgType;
|
||||||
movingTmb?: { tmbId: string; orgId: string };
|
|
||||||
orgs: OrgType[];
|
orgs: OrgType[];
|
||||||
team: TeamTmbItemType;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedOrg, selectOrg] = useState<OrgType>();
|
const [selectedOrg, setSelectedOrg] = useState<OrgType>();
|
||||||
|
const { userInfo } = useUserStore();
|
||||||
|
const team = userInfo?.team!;
|
||||||
|
|
||||||
const { runAsync: moveOrg, loading: loadingOrg } = useRequest2(putMoveOrg, {
|
const { runAsync: onMoveOrg, loading } = useRequest2(putMoveOrg, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
onClose();
|
onClose();
|
||||||
onSuccess();
|
onSuccess();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { runAsync: moveTmb, loading: loadingTmb } = useRequest2(putMoveOrgMember, {
|
const filterMovingOrgs = useMemo(
|
||||||
onSuccess: () => {
|
() => orgs.filter((org) => org._id !== movingOrg._id),
|
||||||
onClose();
|
[movingOrg._id, orgs]
|
||||||
onSuccess();
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
if (!selectedOrg) return;
|
|
||||||
if (movingTmb) {
|
|
||||||
moveTmb({ orgId: movingTmb.orgId, tmbId: movingTmb.tmbId, newOrgId: selectedOrg._id });
|
|
||||||
} else if (movingOrg) {
|
|
||||||
moveOrg(movingOrg._id, selectedOrg._id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loading = loadingOrg || loadingTmb;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
isOpen={!!movingOrg || !!movingTmb}
|
isOpen
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={movingOrg ? t('account_team:move_org') : t('account_team:move_member')}
|
title={t('account_team:move_org')}
|
||||||
iconSrc="common/file/move"
|
iconSrc="common/file/move"
|
||||||
iconColor="blue.600"
|
iconColor="primary.600"
|
||||||
>
|
>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<OrgTree
|
<OrgTree
|
||||||
orgs={orgs}
|
orgs={filterMovingOrgs}
|
||||||
teamName={team.teamName}
|
|
||||||
teamAvatar={team.avatar}
|
|
||||||
selectedOrg={selectedOrg}
|
selectedOrg={selectedOrg}
|
||||||
selectOrg={selectOrg}
|
setSelectedOrg={setSelectedOrg}
|
||||||
/>
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button isDisabled={!selectedOrg} isLoading={loading} onClick={() => handleConfirm()}>
|
<Button
|
||||||
|
isDisabled={!selectedOrg}
|
||||||
|
isLoading={loading}
|
||||||
|
onClick={() => {
|
||||||
|
if (!selectedOrg) return;
|
||||||
|
return onMoveOrg({
|
||||||
|
orgId: movingOrg._id,
|
||||||
|
targetOrgId: selectedOrg._id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('common:common.Confirm')}
|
{t('common:common.Confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
@@ -77,4 +71,4 @@ function OrgMoveModal({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OrgMoveModal;
|
export default dynamic(() => Promise.resolve(OrgMoveModal), { ssr: false });
|
||||||
|
|||||||
@@ -1,114 +1,101 @@
|
|||||||
import { Box, HStack, Text, VStack } from '@chakra-ui/react';
|
import { Box, HStack, VStack } from '@chakra-ui/react';
|
||||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import type { OrgType } 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 { useToggle } from 'ahooks';
|
import { useToggle } from 'ahooks';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
|
|
||||||
function OrgTreeNode({
|
function OrgTreeNode({
|
||||||
org,
|
org,
|
||||||
list,
|
list,
|
||||||
selectedOrg,
|
selectedOrg,
|
||||||
selectOrg,
|
setSelectedOrg,
|
||||||
indent = 0
|
index = 0
|
||||||
}: {
|
}: {
|
||||||
org: OrgType;
|
org: OrgType;
|
||||||
list: OrgType[];
|
list: OrgType[];
|
||||||
selectedOrg?: OrgType;
|
selectedOrg?: OrgType;
|
||||||
selectOrg?: (org?: OrgType) => void;
|
setSelectedOrg: (org?: OrgType) => void;
|
||||||
indent?: number;
|
index?: number;
|
||||||
}) {
|
}) {
|
||||||
const children = useMemo(
|
const children = useMemo(
|
||||||
() => list.filter((item) => item.path === `${org.path}/${org._id}`),
|
() => list.filter((item) => item.path === `${org.path}/${org._id}`),
|
||||||
[org, list]
|
[org, list]
|
||||||
);
|
);
|
||||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
const [isExpanded, toggleIsExpanded] = useToggle(index === 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack alignItems={'start'} w="full" gap={'8px'}>
|
<Box userSelect={'none'}>
|
||||||
<HStack
|
<HStack
|
||||||
w="full"
|
borderRadius="sm"
|
||||||
_hover={{ bgColor: selectedOrg === org ? 'blue.200' : 'gray.100' }}
|
_hover={{ bg: 'myGray.100' }}
|
||||||
borderRadius="4px"
|
py={1}
|
||||||
boxSizing="border-box"
|
pr={2}
|
||||||
py="4px"
|
pl={index === 0 ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`}
|
||||||
pl={`calc(${indent}rem + 4px)`}
|
cursor={'pointer'}
|
||||||
transition={'background 0.1s'}
|
{...(selectedOrg === org
|
||||||
{...(selectedOrg === org ? { bgColor: 'blue.100' } : {})}
|
? {
|
||||||
|
bg: 'primary.50 !important',
|
||||||
|
onClick: () => setSelectedOrg(undefined)
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
onClick: () => setSelectedOrg(org)
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{children.length > 0 ? (
|
{index > 0 && (
|
||||||
<IconButton
|
<IconButton
|
||||||
name={isExpanded ? 'common/downArrowFill' : 'common/rightArrowFill'}
|
name={isExpanded ? 'common/downArrowFill' : 'common/rightArrowFill'}
|
||||||
onClick={() => toggleIsExpanded.toggle()}
|
color={'myGray.500'}
|
||||||
|
p={0}
|
||||||
|
w={'1.25rem'}
|
||||||
|
visibility={children.length > 0 ? 'visible' : 'hidden'}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleIsExpanded.toggle();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<Box w={'1rem'} h={'1rem'} m="1" />
|
|
||||||
)}
|
)}
|
||||||
<HStack onClick={() => selectOrg?.(org)} cursor="pointer">
|
<HStack
|
||||||
<Avatar src={org.avatar} w="20px" h="20px" rounded={'50%'} />
|
flex={'1 0 0'}
|
||||||
<Text>{org.name}</Text>
|
onClick={() => setSelectedOrg(org)}
|
||||||
|
cursor={'pointer'}
|
||||||
|
borderRadius={'xs'}
|
||||||
|
>
|
||||||
|
<Avatar src={org.avatar} w={'1.25rem'} borderRadius={'xs'} />
|
||||||
|
<Box>{org.name}</Box>
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
{isExpanded &&
|
{isExpanded &&
|
||||||
children.length > 0 &&
|
children.length > 0 &&
|
||||||
children.map((child) => (
|
children.map((child) => (
|
||||||
<OrgTreeNode
|
<Box key={child._id} mt={0.5}>
|
||||||
key={child._id}
|
<OrgTreeNode
|
||||||
org={child}
|
org={child}
|
||||||
indent={indent + 1}
|
index={index + 1}
|
||||||
list={list}
|
list={list}
|
||||||
selectedOrg={selectedOrg}
|
selectedOrg={selectedOrg}
|
||||||
selectOrg={selectOrg}
|
setSelectedOrg={setSelectedOrg}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
))}
|
))}
|
||||||
</VStack>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrgTree({
|
function OrgTree({
|
||||||
orgs,
|
orgs,
|
||||||
teamName,
|
|
||||||
teamAvatar,
|
|
||||||
selectedOrg,
|
selectedOrg,
|
||||||
selectOrg
|
setSelectedOrg
|
||||||
}: {
|
}: {
|
||||||
orgs: OrgType[];
|
orgs: OrgType[];
|
||||||
teamAvatar: string;
|
|
||||||
teamName: string;
|
|
||||||
selectedOrg?: OrgType;
|
selectedOrg?: OrgType;
|
||||||
selectOrg?: (org?: OrgType) => void;
|
setSelectedOrg: (org?: OrgType) => void;
|
||||||
}) {
|
}) {
|
||||||
const root = orgs[0];
|
const root = orgs[0];
|
||||||
if (!root) return null;
|
if (!root) return;
|
||||||
const children = useMemo(
|
|
||||||
() => orgs.filter((item) => item.path === `${root.path}/${root._id}`),
|
|
||||||
[root, orgs]
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<VStack alignItems={'start'} gap={'8px'}>
|
<OrgTreeNode org={root} list={orgs} setSelectedOrg={setSelectedOrg} selectedOrg={selectedOrg} />
|
||||||
<HStack
|
|
||||||
w="full"
|
|
||||||
onClick={() => selectOrg?.(root)}
|
|
||||||
cursor="pointer"
|
|
||||||
_hover={{ bgColor: selectedOrg === root ? 'blue.200' : 'gray.100' }}
|
|
||||||
borderRadius="4px"
|
|
||||||
p="4px"
|
|
||||||
transition={'background 0.1s'}
|
|
||||||
{...(selectedOrg === root ? { bgColor: 'blue.100' } : {})}
|
|
||||||
>
|
|
||||||
<Avatar src={teamAvatar} w="20px" h="20px" rounded={'50%'} />
|
|
||||||
<Text>{teamName}</Text>
|
|
||||||
</HStack>
|
|
||||||
{children.map((child) => (
|
|
||||||
<OrgTreeNode
|
|
||||||
key={child._id}
|
|
||||||
org={child}
|
|
||||||
list={orgs}
|
|
||||||
selectOrg={selectOrg}
|
|
||||||
selectedOrg={selectedOrg}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</VStack>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,22 +2,18 @@ import { deleteOrg, deleteOrgMember } from '@/web/support/user/team/org/api';
|
|||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
Divider,
|
Divider,
|
||||||
|
Flex,
|
||||||
HStack,
|
HStack,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Tag,
|
Tag,
|
||||||
Tbody,
|
Tbody,
|
||||||
Td,
|
Td,
|
||||||
Text,
|
|
||||||
Th,
|
Th,
|
||||||
Thead,
|
Thead,
|
||||||
Tr,
|
Tr,
|
||||||
VStack,
|
VStack
|
||||||
useDisclosure
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
@@ -27,14 +23,21 @@ import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
|||||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
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 { useEffect, useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import MemberTag from '../../../../../components/support/user/team/Info/MemberTag';
|
import MemberTag from '@/components/support/user/team/Info/MemberTag';
|
||||||
import { TeamContext } from '../context';
|
import { TeamContext } from '../context';
|
||||||
|
import { getOrgList } from '@/web/support/user/team/org/api';
|
||||||
|
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
import OrgInfoModal from './OrgInfoModal';
|
import OrgInfoModal, { defaultOrgForm, OrgFormType } from './OrgInfoModal';
|
||||||
import OrgMemberModal from './OrgMemberModal';
|
import OrgMemberManageModal from './OrgMemberManageModal';
|
||||||
|
|
||||||
import OrgMoveModal from './OrgMoveModal';
|
import OrgMoveModal from './OrgMoveModal';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
|
import Path from '@/components/common/folder/Path';
|
||||||
|
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
|
||||||
function ActionButton({
|
function ActionButton({
|
||||||
icon,
|
icon,
|
||||||
@@ -59,118 +62,101 @@ function ActionButton({
|
|||||||
}}
|
}}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<MyIcon name={icon} w="16px" h="16px" p="1" />
|
<MyIcon name={icon} w="1rem" h="1rem" />
|
||||||
<Text fontSize={'12px'} lineHeight={'16px'}>
|
<Box fontSize={'sm'}>{text}</Box>
|
||||||
{text}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MemberTable() {
|
function MemberTable() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo, isTeamAdmin } = useUserStore();
|
||||||
|
|
||||||
const { orgs, refetchOrgs, members, refetchMembers, isLoading } = useContextSelector(
|
const { members } = useContextSelector(TeamContext, (v) => v);
|
||||||
TeamContext,
|
|
||||||
(v) => v
|
|
||||||
);
|
|
||||||
const [currentOrg, setCurrentOrg] = useState<OrgType>();
|
|
||||||
|
|
||||||
// Set current org by hash
|
const [parentPath, setParentPath] = useState('');
|
||||||
useEffect(() => {
|
const {
|
||||||
const hash = window.location.hash.substring(1);
|
data: orgs = [],
|
||||||
const initialOrg = orgs.find((org) => org._id === hash) || orgs[0];
|
loading: isLoadingOrgs,
|
||||||
setCurrentOrg(initialOrg);
|
refresh: refetchOrgs
|
||||||
}, [orgs, isLoading]);
|
} = useRequest2(getOrgList, {
|
||||||
// Update hash when current org changes
|
manual: false,
|
||||||
useEffect(() => {
|
refreshDeps: [userInfo?.team?.teamId]
|
||||||
if (currentOrg) {
|
});
|
||||||
window.location.hash = currentOrg._id;
|
const currentOrgs = useMemo(() => {
|
||||||
|
if (orgs.length === 0) return [];
|
||||||
|
if (parentPath === '') {
|
||||||
|
setParentPath(`/${orgs[0]._id}`);
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
}, [currentOrg]);
|
return orgs
|
||||||
|
.filter((org) => org.path === parentPath)
|
||||||
|
.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
count:
|
||||||
|
item.members.length +
|
||||||
|
orgs.filter((org) => org.path === `${item.path}/${item._id}`).length
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [orgs, parentPath]);
|
||||||
|
const currentOrg = useMemo(() => {
|
||||||
|
const splitPath = parentPath.split('/');
|
||||||
|
const currentOrgId = splitPath[splitPath.length - 1];
|
||||||
|
if (!currentOrgId) return;
|
||||||
|
|
||||||
const currentPath = useMemo<{ path: string; parents: OrgType[] }>(
|
return orgs.find((org) => org._id === currentOrgId);
|
||||||
() => ({
|
}, [orgs, parentPath]);
|
||||||
path: currentOrg ? `${currentOrg.path}/${currentOrg._id}` : '',
|
const paths = useMemo(() => {
|
||||||
parents: currentOrg
|
const splitPath = parentPath.split('/').filter(Boolean);
|
||||||
? currentOrg.path
|
return splitPath
|
||||||
.split('/')
|
.map((id) => {
|
||||||
.filter(Boolean)
|
const org = orgs.find((org) => org._id === id)!;
|
||||||
.map((orgId) => orgs.find((org) => org._id === orgId)!)
|
|
||||||
: []
|
|
||||||
}),
|
|
||||||
[orgs, currentOrg]
|
|
||||||
);
|
|
||||||
|
|
||||||
const orgList = useMemo(
|
if (org.path === '') return;
|
||||||
() =>
|
|
||||||
orgs
|
|
||||||
.filter((org) => org.path === currentPath.path)
|
|
||||||
.map((org) => {
|
|
||||||
// calc org members count
|
|
||||||
let count = org.members.length;
|
|
||||||
for (const item of orgs.filter((item) =>
|
|
||||||
item.path.startsWith(`${org.path}/${org._id}`)
|
|
||||||
)) {
|
|
||||||
count += item.members.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { ...org, count };
|
return {
|
||||||
}),
|
parentId: `${org.path}/${org._id}`,
|
||||||
[orgs, currentPath]
|
parentName: org.name
|
||||||
);
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as ParentTreePathItemType[];
|
||||||
|
}, [parentPath, orgs]);
|
||||||
|
|
||||||
const [editOrg, setEditOrg] = useState<OrgType | undefined>();
|
const [editOrg, setEditOrg] = useState<OrgFormType>();
|
||||||
const [editMemberOrgId, setEditMemberOrgId] = useState<string | undefined>();
|
const [manageMemberOrg, setManageMemberOrg] = useState<OrgType>();
|
||||||
const [movingOrg, setMovingOrg] = useState<OrgType | undefined>();
|
const [movingOrg, setMovingOrg] = useState<OrgType>();
|
||||||
const [movingTmb, setMovingTmb] = useState<{ tmbId: string; orgId: string } | undefined>();
|
|
||||||
const [createOrgParentId, setCreateOrgParentId] = useState<string | undefined>();
|
|
||||||
|
|
||||||
|
// Delete org
|
||||||
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
|
const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
content: t('account_team:confirm_delete_org')
|
content: t('account_team:confirm_delete_org')
|
||||||
});
|
});
|
||||||
|
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
|
||||||
|
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
|
||||||
|
onSuccess: () => {
|
||||||
|
refetchOrgs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete member
|
||||||
const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({
|
const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({
|
||||||
type: 'delete',
|
type: 'delete',
|
||||||
content: t('account_team:confirm_delete_member')
|
content: t('account_team:confirm_delete_member')
|
||||||
});
|
});
|
||||||
|
|
||||||
const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, {
|
|
||||||
onSuccess: () => {
|
|
||||||
refetchOrgs();
|
|
||||||
refetchMembers();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
refetchOrgs();
|
refetchOrgs();
|
||||||
refetchMembers();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))();
|
|
||||||
const deleteMemberHandler = (orgId: string, tmbId: string) =>
|
|
||||||
openDeleteMemberModal(() => deleteMemberReq(orgId, tmbId))();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack>
|
<MyBox isLoading={isLoadingOrgs}>
|
||||||
<Breadcrumb mr={'auto'}>
|
<Box mb={3}>
|
||||||
{currentPath.parents.map((parent) => (
|
<Path paths={paths} rootName={userInfo?.team?.teamName} onClick={setParentPath} />
|
||||||
<BreadcrumbItem key={parent._id}>
|
</Box>
|
||||||
<BreadcrumbLink onClick={() => setCurrentOrg(parent)}>
|
<Flex w={'100%'} gap={'4'}>
|
||||||
{parent.path === '' ? userInfo?.team.teamName : parent.name}
|
{/* Table */}
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
))}
|
|
||||||
<BreadcrumbItem isCurrentPage>
|
|
||||||
<BreadcrumbLink color="myGray.900" fontWeight={500}>
|
|
||||||
{currentOrg?.path === '' ? userInfo?.team.teamName : currentOrg?.name}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</Breadcrumb>
|
|
||||||
<HStack w={'100%'} gap={'16px'} alignItems={'start'}>
|
|
||||||
<TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}>
|
<TableContainer overflow={'unset'} fontSize={'sm'} flexGrow={1}>
|
||||||
<Table overflow={'unset'}>
|
<Table overflow={'unset'}>
|
||||||
<Thead>
|
<Thead>
|
||||||
@@ -184,94 +170,86 @@ function MemberTable() {
|
|||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{orgList.map((org) => (
|
{currentOrgs.map((org) => (
|
||||||
<Tr key={org._id} overflow={'unset'}>
|
<Tr key={org._id} overflow={'unset'}>
|
||||||
<Td>
|
<Td>
|
||||||
<HStack w="fit-content" cursor={'pointer'} onClick={() => setCurrentOrg(org)}>
|
<HStack
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={() => setParentPath(`${org.path}/${org._id}`)}
|
||||||
|
>
|
||||||
<MemberTag name={org.name} avatar={org.avatar} />
|
<MemberTag name={org.name} avatar={org.avatar} />
|
||||||
<Tag size="sm">{org.count}</Tag>
|
<Tag size="sm">{org.count}</Tag>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name="core/chat/chevronRight"
|
name="core/chat/chevronRight"
|
||||||
w={'12px'}
|
w={'1rem'}
|
||||||
h={'12px'}
|
h={'1rem'}
|
||||||
color={'myGray.400'}
|
color={'myGray.500'}
|
||||||
/>
|
/>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Td>
|
</Td>
|
||||||
|
|
||||||
<Td w={'6rem'}>
|
<Td w={'6rem'}>
|
||||||
<MyMenu
|
{isTeamAdmin && (
|
||||||
trigger="click"
|
<MyMenu
|
||||||
Button={
|
trigger="hover"
|
||||||
<MyIcon name="more" w={'1rem'} cursor={'pointer'} p="1" rounded={'sm'} />
|
Button={<IconButton name="more" />}
|
||||||
}
|
menuList={[
|
||||||
menuList={[
|
{
|
||||||
{
|
children: [
|
||||||
children: [
|
{
|
||||||
{
|
icon: 'edit',
|
||||||
icon: 'edit',
|
label: t('account_team:edit_info'),
|
||||||
label: t('account_team:edit_info'),
|
onClick: () => setEditOrg(org)
|
||||||
onClick: () => setEditOrg(org)
|
},
|
||||||
},
|
{
|
||||||
{
|
icon: 'common/file/move',
|
||||||
icon: 'common/file/move',
|
label: t('common:Move'),
|
||||||
label: t('common:Move'),
|
onClick: () => setMovingOrg(org)
|
||||||
onClick: () => setMovingOrg(org)
|
},
|
||||||
},
|
{
|
||||||
{
|
icon: 'delete',
|
||||||
icon: 'delete',
|
label: t('account_team:delete'),
|
||||||
label: t('account_team:delete'),
|
type: 'danger',
|
||||||
type: 'danger',
|
onClick: () => deleteOrgHandler(org._id)
|
||||||
onClick: () => deleteOrgHandler(org._id)
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
]}
|
||||||
]}
|
/>
|
||||||
/>
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))}
|
))}
|
||||||
{currentOrg?.members.map((member) => {
|
{currentOrg?.members.map((member) => {
|
||||||
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
||||||
if (!memberInfo) return null;
|
if (!memberInfo) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr key={member.tmbId} overflow={'unset'}>
|
<Tr key={member.tmbId}>
|
||||||
<Td>
|
<Td>
|
||||||
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
|
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
|
||||||
</Td>
|
</Td>
|
||||||
<Td w={'6rem'}>
|
<Td w={'6rem'}>
|
||||||
<MyMenu
|
{isTeamAdmin && (
|
||||||
trigger={'click'}
|
<MyMenu
|
||||||
Button={
|
trigger={'hover'}
|
||||||
<MyIcon name="more" w={'1rem'} cursor={'pointer'} p="1" rounded={'sm'} />
|
Button={<IconButton name="more" />}
|
||||||
}
|
menuList={[
|
||||||
menuList={[
|
{
|
||||||
{
|
children: [
|
||||||
children: [
|
{
|
||||||
// {
|
icon: 'delete',
|
||||||
// icon: 'edit',
|
label: t('account_team:delete'),
|
||||||
// label: t('account_team:remark'),
|
type: 'danger',
|
||||||
// onClick: () => {
|
onClick: () =>
|
||||||
// // TODO
|
openDeleteMemberModal(() =>
|
||||||
// console.log(member.tmbId);
|
deleteMemberReq(currentOrg._id, member.tmbId)
|
||||||
// }
|
)()
|
||||||
// },
|
}
|
||||||
{
|
]
|
||||||
icon: 'common/file/move',
|
}
|
||||||
label: t('common:Move'),
|
]}
|
||||||
onClick: () =>
|
/>
|
||||||
setMovingTmb({ tmbId: member.tmbId, orgId: currentOrg!._id })
|
)}
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'delete',
|
|
||||||
label: t('account_team:delete'),
|
|
||||||
type: 'danger',
|
|
||||||
onClick: () => deleteMemberHandler(currentOrg!._id, member.tmbId)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
);
|
);
|
||||||
@@ -279,92 +257,87 @@ function MemberTable() {
|
|||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
{/* Slider */}
|
||||||
<VStack w={'220px'} alignItems={'start'}>
|
<VStack w={'180px'} alignItems={'start'}>
|
||||||
<HStack gap={'6px'}>
|
<HStack gap={'6px'}>
|
||||||
<Avatar
|
<Avatar src={currentOrg?.avatar} w={'1rem'} h={'1rem'} rounded={'xs'} />
|
||||||
src={currentOrg?.path === '' ? userInfo?.team.avatar : currentOrg?.avatar}
|
<Box fontWeight={500} color={'myGray.900'}>
|
||||||
w={'16px'}
|
{currentOrg?.name}
|
||||||
h={'16px'}
|
</Box>
|
||||||
rounded={'20%'}
|
|
||||||
/>
|
|
||||||
<Text fontWeight={500} fontSize={'14px'} color={'myGray.900'} lineHeight={'20px'}>
|
|
||||||
{currentOrg?.path === '' ? userInfo?.team.teamName : currentOrg?.name}
|
|
||||||
</Text>
|
|
||||||
{currentOrg?.path !== '' && (
|
{currentOrg?.path !== '' && (
|
||||||
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
|
<IconButton name="edit" onClick={() => setEditOrg(currentOrg)} />
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
<Text fontSize={12} lineHeight={'16px'} w={'full'}>
|
<Box fontSize={'xs'}>{currentOrg?.description || t('common:common.no_intro')}</Box>
|
||||||
{currentOrg?.description ?? t('common:common.no_intro')}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Divider my={'20px'} />
|
<Divider my={'20px'} />
|
||||||
|
|
||||||
<Text fontWeight={500} mb="13px" fontSize="14px" color="myGray.900" lineHeight="20px">
|
<Box fontWeight={500} fontSize="sm" color="myGray.900">
|
||||||
{t('common:common.Action')}
|
{t('common:common.Action')}
|
||||||
</Text>
|
</Box>
|
||||||
<VStack gap="13px" w="100%">
|
{currentOrg && isTeamAdmin && (
|
||||||
<ActionButton
|
<VStack gap="13px" w="100%">
|
||||||
icon="common/add2"
|
<ActionButton
|
||||||
text={t('account_team:create_sub_org')}
|
icon="common/add2"
|
||||||
onClick={() => {
|
text={t('account_team:create_sub_org')}
|
||||||
setCreateOrgParentId(currentOrg?._id);
|
onClick={() => {
|
||||||
}}
|
setEditOrg({
|
||||||
/>
|
...defaultOrgForm,
|
||||||
<ActionButton
|
parentId: currentOrg?._id
|
||||||
icon="common/administrator"
|
});
|
||||||
text={t('account_team:manage_member')}
|
}}
|
||||||
onClick={() => setEditMemberOrgId(currentOrg?._id)}
|
/>
|
||||||
/>
|
{currentOrg?.path !== '' && (
|
||||||
{currentOrg?.path !== '' && (
|
<>
|
||||||
<>
|
<ActionButton
|
||||||
<ActionButton
|
icon="common/administrator"
|
||||||
icon="common/file/move"
|
text={t('account_team:manage_member')}
|
||||||
text={t('account_team:move_org')}
|
onClick={() => setManageMemberOrg(currentOrg)}
|
||||||
onClick={() => setMovingOrg(currentOrg)}
|
/>
|
||||||
/>
|
<ActionButton
|
||||||
<ActionButton
|
icon="common/file/move"
|
||||||
icon="delete"
|
text={t('account_team:move_org')}
|
||||||
text={t('account_team:delete_org')}
|
onClick={() => setMovingOrg(currentOrg)}
|
||||||
onClick={() => deleteOrgHandler(currentOrg?._id ?? '')}
|
/>
|
||||||
/>
|
<ActionButton
|
||||||
</>
|
icon="delete"
|
||||||
)}
|
text={t('account_team:delete_org')}
|
||||||
</VStack>
|
onClick={() => deleteOrgHandler(currentOrg._id)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
)}
|
||||||
</VStack>
|
</VStack>
|
||||||
</HStack>
|
</Flex>
|
||||||
<OrgInfoModal
|
|
||||||
editOrg={editOrg}
|
{!!editOrg && (
|
||||||
createOrgParentId={createOrgParentId}
|
<OrgInfoModal
|
||||||
onClose={() => {
|
editOrg={editOrg}
|
||||||
setEditOrg(undefined);
|
onClose={() => setEditOrg(undefined)}
|
||||||
setCreateOrgParentId(undefined);
|
onSuccess={refetchOrgs}
|
||||||
}}
|
/>
|
||||||
onSuccess={() => {
|
)}
|
||||||
refetchOrgs();
|
{!!movingOrg && (
|
||||||
refetchMembers();
|
<OrgMoveModal
|
||||||
}}
|
orgs={orgs}
|
||||||
/>
|
movingOrg={movingOrg}
|
||||||
<OrgMoveModal
|
onClose={() => setMovingOrg(undefined)}
|
||||||
orgs={orgs}
|
onSuccess={refetchOrgs}
|
||||||
team={userInfo?.team!}
|
/>
|
||||||
movingOrg={movingOrg}
|
)}
|
||||||
movingTmb={movingTmb}
|
{!!manageMemberOrg && (
|
||||||
onClose={() => {
|
<OrgMemberManageModal
|
||||||
setMovingOrg(undefined);
|
currentOrg={manageMemberOrg}
|
||||||
setMovingTmb(undefined);
|
refetchOrgs={refetchOrgs}
|
||||||
}}
|
onClose={() => setManageMemberOrg(undefined)}
|
||||||
onSuccess={() => {
|
/>
|
||||||
refetchOrgs();
|
)}
|
||||||
refetchMembers();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<OrgMemberModal editOrgId={editMemberOrgId} onClose={() => setEditMemberOrgId(undefined)} />
|
|
||||||
<ConfirmDeleteOrgModal />
|
<ConfirmDeleteOrgModal />
|
||||||
<ConfirmDeleteMember />
|
<ConfirmDeleteMember />
|
||||||
</VStack>
|
</MyBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MemberTable;
|
export default dynamic(() => Promise.resolve(MemberTable), { ssr: false });
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import type { TeamTmbItemType, TeamMemberItemType } from '@fastgpt/global/suppor
|
|||||||
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 { getGroupList } from '@/web/support/user/team/group/api';
|
||||||
import { getOrgList } from '@/web/support/user/team/org/api';
|
|
||||||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||||
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
|
|
||||||
@@ -19,7 +18,6 @@ type TeamModalContextType = {
|
|||||||
myTeams: TeamTmbItemType[];
|
myTeams: TeamTmbItemType[];
|
||||||
members: TeamMemberItemType[];
|
members: TeamMemberItemType[];
|
||||||
groups: MemberGroupListType;
|
groups: MemberGroupListType;
|
||||||
orgs: OrgType[];
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
onSwitchTeam: (teamId: string) => void;
|
onSwitchTeam: (teamId: string) => void;
|
||||||
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
|
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
|
||||||
@@ -27,7 +25,6 @@ type TeamModalContextType = {
|
|||||||
refetchMembers: () => void;
|
refetchMembers: () => void;
|
||||||
refetchTeams: () => void;
|
refetchTeams: () => void;
|
||||||
refetchGroups: () => void;
|
refetchGroups: () => void;
|
||||||
refetchOrgs: () => void;
|
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
setSearchKey: React.Dispatch<React.SetStateAction<string>>;
|
setSearchKey: React.Dispatch<React.SetStateAction<string>>;
|
||||||
teamSize: number;
|
teamSize: number;
|
||||||
@@ -37,7 +34,6 @@ export const TeamContext = createContext<TeamModalContextType>({
|
|||||||
myTeams: [],
|
myTeams: [],
|
||||||
groups: [],
|
groups: [],
|
||||||
members: [],
|
members: [],
|
||||||
orgs: [],
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
onSwitchTeam: function (_teamId: string): void {
|
onSwitchTeam: function (_teamId: string): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
@@ -54,9 +50,6 @@ export const TeamContext = createContext<TeamModalContextType>({
|
|||||||
refetchGroups: function (): void {
|
refetchGroups: function (): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
refetchOrgs: function (): void {
|
|
||||||
throw new Error('Function not implemented.');
|
|
||||||
},
|
|
||||||
|
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
setSearchKey: function (_value: React.SetStateAction<string>): void {
|
setSearchKey: function (_value: React.SetStateAction<string>): void {
|
||||||
@@ -115,17 +108,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
refreshDeps: [userInfo?.team?.teamId]
|
refreshDeps: [userInfo?.team?.teamId]
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups;
|
||||||
data: orgs = [],
|
|
||||||
loading: isLoadingOrgs,
|
|
||||||
refresh: refetchOrgs
|
|
||||||
} = useRequest2(getOrgList, {
|
|
||||||
manual: false,
|
|
||||||
refreshDeps: [userInfo?.team?.teamId]
|
|
||||||
});
|
|
||||||
|
|
||||||
const isLoading =
|
|
||||||
isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups || isLoadingOrgs;
|
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
myTeams,
|
myTeams,
|
||||||
@@ -141,8 +124,6 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
|||||||
refetchMembers,
|
refetchMembers,
|
||||||
groups,
|
groups,
|
||||||
refetchGroups,
|
refetchGroups,
|
||||||
orgs,
|
|
||||||
refetchOrgs,
|
|
||||||
teamSize: members.length
|
teamSize: members.length
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ import dynamic from 'next/dynamic';
|
|||||||
import TeamTagModal from '@/components/support/user/team/TeamTagModal';
|
import TeamTagModal from '@/components/support/user/team/TeamTagModal';
|
||||||
import MemberTable from './components/MemberTable';
|
import MemberTable from './components/MemberTable';
|
||||||
|
|
||||||
|
import OrgManage from './components/OrgManage/index';
|
||||||
|
|
||||||
const InviteModal = dynamic(() => import('./components/InviteModal'));
|
const InviteModal = dynamic(() => import('./components/InviteModal'));
|
||||||
const PermissionManage = dynamic(() => import('./components/PermissionManage/index'));
|
const PermissionManage = dynamic(() => import('./components/PermissionManage/index'));
|
||||||
const GroupManage = dynamic(() => import('./components/GroupManage/index'));
|
const GroupManage = dynamic(() => import('./components/GroupManage/index'));
|
||||||
const OrgManage = dynamic(() => import('./components/OrgManage/index'));
|
|
||||||
const GroupInfoModal = dynamic(() => import('./components/GroupManage/GroupInfoModal'));
|
const GroupInfoModal = dynamic(() => import('./components/GroupManage/GroupInfoModal'));
|
||||||
const ManageGroupMemberModal = dynamic(() => import('./components/GroupManage/GroupManageMember'));
|
const ManageGroupMemberModal = dynamic(() => import('./components/GroupManage/GroupManageMember'));
|
||||||
|
|
||||||
@@ -272,7 +273,7 @@ const Team = () => {
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
|
<Box flex={'1 0 0'} overflow={'auto'}>
|
||||||
{teamTab === TeamTabEnum.member && <MemberTable />}
|
{teamTab === TeamTabEnum.member && <MemberTable />}
|
||||||
{teamTab === TeamTabEnum.group && (
|
{teamTab === TeamTabEnum.group && (
|
||||||
<GroupManage onEditGroup={onEditGroup} onManageMember={onManageMember} />
|
<GroupManage onEditGroup={onEditGroup} onManageMember={onManageMember} />
|
||||||
|
|||||||
@@ -142,7 +142,9 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
appId: appDetail._id
|
appId: appDetail._id
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDelCollaborator = async (props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }>) =>
|
const onDelCollaborator = async (
|
||||||
|
props: RequireOnlyOne<{ tmbId: string; groupId: string; orgId: string }>
|
||||||
|
) =>
|
||||||
deleteAppCollaborators({
|
deleteAppCollaborators({
|
||||||
appId: appDetail._id,
|
appId: appDetail._id,
|
||||||
...props
|
...props
|
||||||
|
|||||||
@@ -258,20 +258,20 @@ const ListItem = () => {
|
|||||||
{(AppFolderTypeList.includes(app.type)
|
{(AppFolderTypeList.includes(app.type)
|
||||||
? app.permission.hasManagePer
|
? app.permission.hasManagePer
|
||||||
: app.permission.hasWritePer) && (
|
: app.permission.hasWritePer) && (
|
||||||
<Box className="more" display={['', 'none']}>
|
<Box className="more" display={['', 'none']}>
|
||||||
<MyMenu
|
<MyMenu
|
||||||
size={'xs'}
|
size={'xs'}
|
||||||
Button={
|
Button={
|
||||||
<IconButton
|
<IconButton
|
||||||
size={'xsSquare'}
|
size={'xsSquare'}
|
||||||
variant={'transparentBase'}
|
variant={'transparentBase'}
|
||||||
icon={<MyIcon name={'more'} w={'0.875rem'} color={'myGray.500'} />}
|
icon={<MyIcon name={'more'} w={'0.875rem'} color={'myGray.500'} />}
|
||||||
aria-label={''}
|
aria-label={''}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
menuList={[
|
menuList={[
|
||||||
...([AppTypeEnum.simple, AppTypeEnum.workflow].includes(app.type)
|
...([AppTypeEnum.simple, AppTypeEnum.workflow].includes(app.type)
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -285,9 +285,9 @@ const ListItem = () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...([AppTypeEnum.plugin].includes(app.type)
|
...([AppTypeEnum.plugin].includes(app.type)
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -301,9 +301,9 @@ const ListItem = () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(app.permission.hasManagePer
|
...(app.permission.hasManagePer
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -330,34 +330,34 @@ const ListItem = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
...(folderDetail?.type === AppTypeEnum.httpPlugin &&
|
...(folderDetail?.type === AppTypeEnum.httpPlugin &&
|
||||||
!(parentApp ? parentApp.permission : app.permission)
|
!(parentApp ? parentApp.permission : app.permission)
|
||||||
.hasManagePer
|
.hasManagePer
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
icon: 'common/file/move',
|
icon: 'common/file/move',
|
||||||
type: 'grayBg' as MenuItemType,
|
type: 'grayBg' as MenuItemType,
|
||||||
label: t('common:common.folder.Move to'),
|
label: t('common:common.folder.Move to'),
|
||||||
onClick: () => setMoveAppId(app._id)
|
onClick: () => setMoveAppId(app._id)
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
...(app.permission.hasManagePer
|
...(app.permission.hasManagePer
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
icon: 'support/team/key',
|
icon: 'support/team/key',
|
||||||
type: 'grayBg' as MenuItemType,
|
type: 'grayBg' as MenuItemType,
|
||||||
label: t('common:permission.Permission'),
|
label: t('common:permission.Permission'),
|
||||||
onClick: () => setEditPerAppIndex(index)
|
onClick: () => setEditPerAppIndex(index)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [])
|
: [])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(AppFolderTypeList.includes(app.type)
|
...(AppFolderTypeList.includes(app.type)
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -370,8 +370,8 @@ const ListItem = () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]),
|
]),
|
||||||
...(app.permission.isOwner
|
...(app.permission.isOwner
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -390,11 +390,11 @@ const ListItem = () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [])
|
: [])
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
</MyBox>
|
</MyBox>
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { DELETE, GET, POST, PUT } from '@/web/common/api/request';
|
|||||||
import type {
|
import type {
|
||||||
postCreateOrgData,
|
postCreateOrgData,
|
||||||
putUpdateOrgData,
|
putUpdateOrgData,
|
||||||
putUpdateOrgMembersData,
|
putUpdateOrgMembersData
|
||||||
putMoveOrgMemberData
|
|
||||||
} from '@fastgpt/global/support/user/team/org/api';
|
} from '@fastgpt/global/support/user/team/org/api';
|
||||||
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
import type { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||||
|
import { putMoveOrgType } from '@fastgpt/global/support/user/team/org/api';
|
||||||
|
|
||||||
export const getOrgList = () => GET<OrgType[]>('/proApi/support/user/team/org/list');
|
export const getOrgList = () => GET<OrgType[]>('/proApi/support/user/team/org/list');
|
||||||
|
|
||||||
@@ -18,11 +18,7 @@ export const deleteOrg = (orgId: string) =>
|
|||||||
export const deleteOrgMember = (orgId: string, tmbId: string) =>
|
export const deleteOrgMember = (orgId: string, tmbId: string) =>
|
||||||
DELETE('/proApi/support/user/team/org/deleteMember', { orgId, tmbId });
|
DELETE('/proApi/support/user/team/org/deleteMember', { orgId, tmbId });
|
||||||
|
|
||||||
export const putMoveOrg = (orgId: string, parentId: string) =>
|
export const putMoveOrg = (data: putMoveOrgType) => PUT('/proApi/support/user/team/org/move', data);
|
||||||
PUT('/proApi/support/user/team/org/move', { orgId, parentId });
|
|
||||||
|
|
||||||
export const putMoveOrgMember = (data: putMoveOrgMemberData) =>
|
|
||||||
PUT('/proApi/support/user/team/org/moveMember', data);
|
|
||||||
|
|
||||||
export const putUpdateOrg = (data: putUpdateOrgData) =>
|
export const putUpdateOrg = (data: putUpdateOrgData) =>
|
||||||
PUT('/proApi/support/user/team/org/update', data);
|
PUT('/proApi/support/user/team/org/update', data);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type State = {
|
|||||||
setSysMsgReadId: (id: string) => void;
|
setSysMsgReadId: (id: string) => void;
|
||||||
|
|
||||||
userInfo: UserType | null;
|
userInfo: UserType | null;
|
||||||
|
isTeamAdmin: boolean;
|
||||||
initUserInfo: () => Promise<UserType>;
|
initUserInfo: () => Promise<UserType>;
|
||||||
setUserInfo: (user: UserType | null) => void;
|
setUserInfo: (user: UserType | null) => void;
|
||||||
updateUserInfo: (user: UserUpdateParams) => Promise<void>;
|
updateUserInfo: (user: UserUpdateParams) => Promise<void>;
|
||||||
@@ -50,6 +51,7 @@ export const useUserStore = create<State>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
userInfo: null,
|
userInfo: null,
|
||||||
|
isTeamAdmin: false,
|
||||||
async initUserInfo() {
|
async initUserInfo() {
|
||||||
get().initTeamPlanStatus();
|
get().initTeamPlanStatus();
|
||||||
|
|
||||||
@@ -67,6 +69,7 @@ export const useUserStore = create<State>()(
|
|||||||
setUserInfo(user: UserType | null) {
|
setUserInfo(user: UserType | null) {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.userInfo = user ? user : null;
|
state.userInfo = user ? user : null;
|
||||||
|
state.isTeamAdmin = !!user?.team?.permission?.hasManagePer;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async updateUserInfo(user: UserUpdateParams) {
|
async updateUserInfo(user: UserUpdateParams) {
|
||||||
|
|||||||
Reference in New Issue
Block a user