mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-03 13:38:00 +00:00
v4.6 -1 (#459)
This commit is contained in:
@@ -26,7 +26,7 @@ import {
|
||||
delOpenApiById,
|
||||
putOpenApiKey
|
||||
} from '@/web/support/openapi/api';
|
||||
import type { EditApiKeyProps } from '@/global/support/api/openapiReq';
|
||||
import type { EditApiKeyProps } from '@/global/support/openapi/api.d';
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import dayjs from 'dayjs';
|
||||
|
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
|
||||
import { Box, Flex, FlexProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const PermissionIconText = ({
|
||||
permission,
|
||||
...props
|
||||
}: { permission: `${PermissionTypeEnum}` } & FlexProps) => {
|
||||
const { t } = useTranslation();
|
||||
return PermissionTypeMap[permission] ? (
|
||||
<Flex alignItems={'center'} {...props}>
|
||||
<MyIcon name={PermissionTypeMap[permission]?.iconLight as any} w={'14px'} />
|
||||
<Box ml={'1px'}>{t(PermissionTypeMap[permission]?.label)}</Box>
|
||||
</Flex>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default PermissionIconText;
|
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import MyRadio from '@/components/Radio';
|
||||
import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const PermissionRadio = ({
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
value: `${PermissionTypeEnum}`;
|
||||
onChange: (e: `${PermissionTypeEnum}`) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyRadio
|
||||
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)']}
|
||||
list={[
|
||||
{
|
||||
icon: 'support/permission/privateLight',
|
||||
title: t('permission.Private'),
|
||||
desc: t('permission.Private Tip'),
|
||||
value: PermissionTypeEnum.private
|
||||
},
|
||||
{
|
||||
icon: 'support/permission/publicLight',
|
||||
title: t('permission.Public'),
|
||||
desc: t('permission.Public Tip'),
|
||||
value: PermissionTypeEnum.public
|
||||
}
|
||||
]}
|
||||
value={value}
|
||||
onChange={(e) => onChange(e as `${PermissionTypeEnum}`)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PermissionRadio;
|
@@ -0,0 +1,159 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgAndUpload } from '@/web/common/file/controller';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api';
|
||||
import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d';
|
||||
|
||||
export type FormDataType = CreateTeamProps & {
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export const defaultForm = {
|
||||
name: '',
|
||||
avatar: '/icon/logo.svg'
|
||||
};
|
||||
|
||||
function EditModal({
|
||||
defaultData = defaultForm,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultData?: FormDataType;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<CreateTeamProps>({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png,.svg',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async (e: File[]) => {
|
||||
const file = e[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
const src = await compressImgAndUpload({
|
||||
file,
|
||||
maxW: 100,
|
||||
maxH: 100
|
||||
});
|
||||
setValue('avatar', src);
|
||||
setRefresh((state) => !state);
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: getErrText(err, t('common.Select File Failed')),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
},
|
||||
[setValue, t, toast]
|
||||
);
|
||||
|
||||
const { mutate: onclickCreate, isLoading: creating } = useRequest({
|
||||
mutationFn: async (data: CreateTeamProps) => {
|
||||
return postCreateTeam(data);
|
||||
},
|
||||
onSuccess() {
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common.Create Success'),
|
||||
errorToast: t('common.Create Failed')
|
||||
});
|
||||
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
|
||||
mutationFn: async (data: FormDataType) => {
|
||||
if (!data.id) return Promise.resolve('');
|
||||
return putUpdateTeam({
|
||||
teamId: data.id,
|
||||
name: data.name,
|
||||
avatar: data.avatar
|
||||
});
|
||||
},
|
||||
onSuccess() {
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common.Update Success'),
|
||||
errorToast: t('common.Update Failed')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
title={defaultData.id ? t('user.team.Update Team') : t('user.team.Create Team')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('user.team.Set Name')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<MyTooltip label={t('common.Set Avatar')}>
|
||||
<Avatar
|
||||
flexShrink={0}
|
||||
src={getValues('avatar')}
|
||||
w={['28px', '32px']}
|
||||
h={['28px', '32px']}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={onOpenSelectFile}
|
||||
/>
|
||||
</MyTooltip>
|
||||
<Input
|
||||
flex={1}
|
||||
ml={4}
|
||||
autoFocus
|
||||
bg={'myWhite.600'}
|
||||
maxLength={20}
|
||||
placeholder={t('user.team.Team Name')}
|
||||
{...register('name', {
|
||||
required: t('common.Please Input Name')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{!!defaultData.id ? (
|
||||
<>
|
||||
<Box flex={1} />
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button isLoading={updating} onClick={handleSubmit((data) => onclickUpdate(data))}>
|
||||
{t('common.Confirm Update')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
w={'100%'}
|
||||
isLoading={creating}
|
||||
onClick={handleSubmit((data) => onclickCreate(data))}
|
||||
>
|
||||
{t('common.Confirm Create')}
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
<File onSelect={onSelectFile} />
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(EditModal);
|
@@ -0,0 +1,106 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import TagTextarea from '@/components/common/Textarea/TagTextarea';
|
||||
import MySelect from '@/components/Select';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { postInviteTeamMember } from '@/web/support/user/team/api';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import type { InviteMemberResponse } from '@fastgpt/global/support/user/team/controller.d';
|
||||
|
||||
const InviteModal = ({
|
||||
teamId,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
teamId: string;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
title: t('user.team.Invite Member Result Tip'),
|
||||
showCancel: false
|
||||
});
|
||||
const [inviteUsernames, setInviteUsernames] = useState<string[]>([]);
|
||||
const inviteTypes = useMemo(
|
||||
() => [
|
||||
{
|
||||
alias: t('user.team.Invite Role Visitor Alias'),
|
||||
label: t('user.team.Invite Role Visitor Tip'),
|
||||
value: TeamMemberRoleEnum.visitor
|
||||
},
|
||||
{
|
||||
alias: t('user.team.Invite Role Admin Alias'),
|
||||
label: t('user.team.Invite Role Admin Tip'),
|
||||
value: TeamMemberRoleEnum.admin
|
||||
}
|
||||
],
|
||||
[t]
|
||||
);
|
||||
const [selectedInviteType, setSelectInviteType] = useState(inviteTypes[0].value);
|
||||
|
||||
const { mutate: onInvite, isLoading } = useRequest({
|
||||
mutationFn: () => {
|
||||
return postInviteTeamMember({
|
||||
teamId,
|
||||
usernames: inviteUsernames,
|
||||
role: selectedInviteType
|
||||
});
|
||||
},
|
||||
onSuccess(res: InviteMemberResponse) {
|
||||
onSuccess();
|
||||
openConfirm(
|
||||
() => onClose(),
|
||||
undefined,
|
||||
t('user.team.Invite Member Success Tip', {
|
||||
success: res.invite.length,
|
||||
inValid: res.inValid.join(', '),
|
||||
inTeam: res.inTeam.join(', ')
|
||||
})
|
||||
)();
|
||||
},
|
||||
errorToast: t('user.team.Invite Member Failed Tip')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
title={
|
||||
<>
|
||||
<Box>{t('user.team.Invite Member')}</Box>
|
||||
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
|
||||
{t('user.team.Invite Member Tips')}
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
maxW={['90vw', '400px']}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Box mb={2}>{t('common.Username')}</Box>
|
||||
<TagTextarea defaultValues={inviteUsernames} onUpdate={setInviteUsernames} />
|
||||
<Box mt={4}>
|
||||
<MySelect list={inviteTypes} value={selectedInviteType} onchange={setSelectInviteType} />
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
w={'100%'}
|
||||
h={'34px'}
|
||||
isDisabled={inviteUsernames.length === 0}
|
||||
isLoading={isLoading}
|
||||
onClick={onInvite}
|
||||
>
|
||||
{t('user.team.Confirm Invite')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
<ConfirmModal />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default InviteModal;
|
@@ -0,0 +1,436 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
getTeamList,
|
||||
getTeamMembers,
|
||||
putSwitchTeam,
|
||||
putUpdateMember,
|
||||
delRemoveMember,
|
||||
delLeaveTeam
|
||||
} from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
IconButton,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
useTheme,
|
||||
useDisclosure,
|
||||
MenuButton
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
TeamMemberRoleEnum,
|
||||
TeamMemberRoleMap,
|
||||
TeamMemberStatusEnum,
|
||||
TeamMemberStatusMap
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { setToken } from '@/web/support/user/auth';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import { FormDataType, defaultForm } from './EditModal';
|
||||
import MyMenu from '@/components/MyMenu';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const EditModal = dynamic(() => import('./EditModal'));
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
|
||||
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
|
||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||
content: t('user.team.member.Confirm Leave')
|
||||
});
|
||||
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
const [editTeamData, setEditTeamData] = useState<FormDataType>();
|
||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||
|
||||
const {
|
||||
data: myTeams = [],
|
||||
isLoading: isLoadingTeams,
|
||||
refetch: refetchTeam
|
||||
} = useQuery(['getTeams', userInfo?._id], () => getTeamList(TeamMemberStatusEnum.active));
|
||||
const defaultTeam = useMemo(
|
||||
() => myTeams.find((item) => item.defaultTeam) || myTeams[0],
|
||||
[myTeams]
|
||||
);
|
||||
|
||||
const { mutate: onSwitchTeam, isLoading: isSwitchTeam } = useRequest({
|
||||
mutationFn: async (teamId: string) => {
|
||||
const token = await putSwitchTeam(teamId);
|
||||
setToken(token);
|
||||
return initUserInfo();
|
||||
},
|
||||
errorToast: t('user.team.Switch Team Failed')
|
||||
});
|
||||
|
||||
// member action
|
||||
const { data: members = [], refetch: refetchMembers } = useQuery(
|
||||
['getMembers', userInfo?.team?.teamId],
|
||||
() => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getTeamMembers(userInfo.team.teamId);
|
||||
}
|
||||
);
|
||||
|
||||
const { mutate: onUpdateMember, isLoading: isLoadingUpdateMember } = useRequest({
|
||||
mutationFn: putUpdateMember,
|
||||
onSuccess() {
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
const { mutate: onRemoveMember, isLoading: isLoadingRemoveMember } = useRequest({
|
||||
mutationFn: delRemoveMember,
|
||||
onSuccess() {
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
const { mutate: onLeaveTeam, isLoading: isLoadingLeaveTeam } = useRequest({
|
||||
mutationFn: async (teamId?: string) => {
|
||||
if (!teamId) return;
|
||||
// change to personal team
|
||||
await onSwitchTeam(defaultTeam.teamId);
|
||||
return delLeaveTeam(teamId);
|
||||
},
|
||||
onSuccess() {
|
||||
refetchTeam();
|
||||
},
|
||||
errorToast: t('user.team.Leave Team Failed')
|
||||
});
|
||||
|
||||
return !!userInfo?.team ? (
|
||||
<>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
maxW={['90vw', '1000px']}
|
||||
w={'100%'}
|
||||
h={'550px'}
|
||||
isCentered
|
||||
bg={'myWhite.600'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
<Box display={['block', 'flex']} flex={1} position={'relative'} overflow={'auto'}>
|
||||
{/* teams */}
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
w={['auto', '270px']}
|
||||
h={['auto', '100%']}
|
||||
pt={3}
|
||||
px={5}
|
||||
mb={[2, 0]}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
py={2}
|
||||
h={'40px'}
|
||||
borderBottom={'1.5px solid rgba(0, 0, 0, 0.05)'}
|
||||
>
|
||||
<Box flex={['0 0 auto', 1]} fontWeight={'bold'} fontSize={['md', 'lg']}>
|
||||
{t('common.Team')}
|
||||
</Box>
|
||||
{myTeams.length < 1 && (
|
||||
<IconButton
|
||||
variant={'ghost'}
|
||||
border={'none'}
|
||||
icon={
|
||||
<MyIcon
|
||||
name={'addCircle'}
|
||||
w={['16px', '18px']}
|
||||
color={'myBlue.600'}
|
||||
cursor={'pointer'}
|
||||
/>
|
||||
}
|
||||
aria-label={''}
|
||||
onClick={() => setEditTeamData(defaultForm)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box flex={['auto', '1 0 0']} overflow={'auto'}>
|
||||
{myTeams.map((team) => (
|
||||
<Flex
|
||||
key={team.teamId}
|
||||
alignItems={'center'}
|
||||
mt={3}
|
||||
borderRadius={'md'}
|
||||
p={3}
|
||||
cursor={'default'}
|
||||
gap={3}
|
||||
{...(userInfo?.team?.teamId === team.teamId
|
||||
? {
|
||||
bg: 'myBlue.300'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Avatar src={team.avatar} w={['18px', '22px']} />
|
||||
<Box
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
{...(team.role === TeamMemberRoleEnum.owner
|
||||
? {
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{team.teamName}
|
||||
</Box>
|
||||
{userInfo?.team?.teamId === team.teamId ? (
|
||||
<MyIcon name={'common/tickFill'} w={'16px'} color={'myBlue.600'} />
|
||||
) : (
|
||||
<Button size={'xs'} variant={'base'} onClick={() => onSwitchTeam(team.teamId)}>
|
||||
{t('user.team.Check Team')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Flex>
|
||||
{/* team card */}
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
flex={'1'}
|
||||
h={['auto', '100%']}
|
||||
bg={'white'}
|
||||
minH={['50vh', 'auto']}
|
||||
borderRadius={['8px 8px 0 0', '8px 0 0 8px']}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={5}
|
||||
py={4}
|
||||
borderBottom={'1.5px solid'}
|
||||
borderBottomColor={'myGray.100'}
|
||||
mb={3}
|
||||
>
|
||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'}>
|
||||
{userInfo.team.teamName}
|
||||
</Box>
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
<MyIcon
|
||||
name="edit"
|
||||
w={'14px'}
|
||||
ml={2}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'myBlue.600'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!userInfo?.team) return;
|
||||
setEditTeamData({
|
||||
id: userInfo.team.teamId,
|
||||
name: userInfo.team.teamName,
|
||||
avatar: userInfo.team.avatar
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex px={5} alignItems={'center'}>
|
||||
<MyIcon name="support/team/memberLight" w={'14px'} />
|
||||
<Box ml={1}>{t('user.team.Member')}</Box>
|
||||
<Box ml={2} bg={'myGray.100'} borderRadius={'20px'} px={3} fontSize={'xs'}>
|
||||
{members.length}
|
||||
</Box>
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
<Button
|
||||
variant={'base'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'myBlue.600'} />}
|
||||
onClick={() => {
|
||||
if (userInfo.team.maxSize <= members.length) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Over Max Member Tip', { max: userInfo.team.maxSize })
|
||||
});
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Invite Member')}
|
||||
</Button>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
{userInfo.team.role !== TeamMemberRoleEnum.owner && (
|
||||
<Button
|
||||
variant={'base'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'loginoutLight'} w={'14px'} color={'myBlue.600'} />}
|
||||
onClick={() => {
|
||||
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
|
||||
}}
|
||||
>
|
||||
{t('user.team.Leave Team')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={3} flex={'1 0 0'} overflow={'auto'}>
|
||||
<TableContainer overflow={'unset'}>
|
||||
<Table overflow={'unset'}>
|
||||
<Thead bg={'myWhite.400'}>
|
||||
<Tr>
|
||||
<Th>{t('common.Username')}</Th>
|
||||
<Th>{t('user.team.Role')}</Th>
|
||||
<Th>{t('common.Status')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{members.map((item) => (
|
||||
<Tr key={item.userId} overflow={'unset'}>
|
||||
<Td display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={['18px', '22px']} />
|
||||
<Box flex={'1 0 0'} w={0} ml={1} className={'textEllipsis'}>
|
||||
{item.memberUsername}
|
||||
</Box>
|
||||
</Td>
|
||||
<Td>{t(TeamMemberRoleMap[item.role]?.label || '')}</Td>
|
||||
<Td color={TeamMemberStatusMap[item.status].color}>
|
||||
{t(TeamMemberStatusMap[item.status]?.label || '')}
|
||||
</Td>
|
||||
<Td>
|
||||
{userInfo?.team?.role === TeamMemberRoleEnum.owner &&
|
||||
item.role !== TeamMemberRoleEnum.owner && (
|
||||
<MyMenu
|
||||
width={20}
|
||||
Button={
|
||||
<MenuButton
|
||||
_hover={{
|
||||
bg: 'myWhite.600'
|
||||
}}
|
||||
px={2}
|
||||
py={1}
|
||||
lineHeight={1}
|
||||
>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
cursor={'pointer'}
|
||||
w="14px"
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
/>
|
||||
</MenuButton>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
isActive: item.role === TeamMemberRoleEnum.visitor,
|
||||
child: t('user.team.Invite Role Visitor Tip'),
|
||||
onClick: () => {
|
||||
onUpdateMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId,
|
||||
role: TeamMemberRoleEnum.visitor
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
isActive: item.role === TeamMemberRoleEnum.admin,
|
||||
child: t('user.team.Invite Role Admin Tip'),
|
||||
onClick: () => {
|
||||
onUpdateMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId,
|
||||
role: TeamMemberRoleEnum.admin
|
||||
});
|
||||
}
|
||||
},
|
||||
...(item.status === TeamMemberStatusEnum.reject
|
||||
? [
|
||||
{
|
||||
child: t('user.team.Reinvite'),
|
||||
onClick: () => {
|
||||
onUpdateMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId,
|
||||
status: TeamMemberStatusEnum.waiting
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
child: t('user.team.Remove Member Tip'),
|
||||
onClick: () =>
|
||||
openRemoveMember(
|
||||
() =>
|
||||
onRemoveMember({
|
||||
teamId: item.teamId,
|
||||
memberId: item.tmbId
|
||||
}),
|
||||
undefined,
|
||||
t('user.team.Remove Member Confirm Tip', {
|
||||
username: item.memberUsername
|
||||
})
|
||||
)()
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Loading
|
||||
loading={
|
||||
isSwitchTeam ||
|
||||
isLoadingTeams ||
|
||||
isLoadingUpdateMember ||
|
||||
isLoadingRemoveMember ||
|
||||
isLoadingLeaveTeam
|
||||
}
|
||||
fixed={false}
|
||||
/>
|
||||
</Box>
|
||||
</MyModal>
|
||||
{!!editTeamData && (
|
||||
<EditModal
|
||||
defaultData={editTeamData}
|
||||
onClose={() => setEditTeamData(undefined)}
|
||||
onSuccess={() => {
|
||||
refetchTeam();
|
||||
initUserInfo();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenInvite && userInfo?.team?.teamId && (
|
||||
<InviteModal
|
||||
teamId={userInfo.team.teamId}
|
||||
onClose={onCloseInvite}
|
||||
onSuccess={refetchMembers}
|
||||
/>
|
||||
)}
|
||||
<ConfirmRemoveMemberModal />
|
||||
<ConfirmLeaveTeamModal />
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default React.memo(TeamManageModal);
|
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Flex, Image, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
|
||||
const TeamManageModal = dynamic(() => import('../TeamManageModal'));
|
||||
|
||||
const TeamMenu = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={'base'}
|
||||
userSelect={'none'}
|
||||
w={'100%'}
|
||||
display={'block'}
|
||||
h={'34px'}
|
||||
px={3}
|
||||
css={{
|
||||
'& span': {
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
transform={'none !important'}
|
||||
onClick={() => {
|
||||
if (feConfigs.isPlus) {
|
||||
onOpen();
|
||||
} else {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common.Business edition features')
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={t('user.team.Select Team')}>
|
||||
<Flex w={'100%'} alignItems={'center'}>
|
||||
{userInfo?.team ? (
|
||||
<>
|
||||
<Image src={userInfo.team.avatar} alt={''} w={'16px'} />
|
||||
<Box ml={2}>{userInfo.team.teamName}</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Box w={'8px'} h={'8px'} mr={3} borderRadius={'50%'} bg={'#67c13b'} />
|
||||
{t('user.team.Personal Team')}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
{isOpen && <TeamManageModal onClose={onClose} />}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamMenu;
|
@@ -0,0 +1,133 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import {
|
||||
Button,
|
||||
ModalFooter,
|
||||
useDisclosure,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Box,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getTeamList, updateInviteResult } from '@/web/support/user/team/api';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
|
||||
const UpdateInviteModal = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
|
||||
const { data: inviteList = [], refetch } = useQuery(['getInviteList'], () =>
|
||||
feConfigs.isPlus ? getTeamList(TeamMemberStatusEnum.waiting) : []
|
||||
);
|
||||
|
||||
const { mutate: onAccept, isLoading: isLoadingAccept } = useRequest({
|
||||
mutationFn: updateInviteResult,
|
||||
onSuccess() {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('user.team.invite.Accepted')
|
||||
});
|
||||
refetch();
|
||||
}
|
||||
});
|
||||
const { mutate: onReject, isLoading: isLoadingReject } = useRequest({
|
||||
mutationFn: updateInviteResult,
|
||||
onSuccess() {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('user.team.invite.Reject')
|
||||
});
|
||||
refetch();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={inviteList.length > 0}
|
||||
title={
|
||||
<>
|
||||
<Box>{t('user.team.Processing invitations')}</Box>
|
||||
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('user.team.Processing invitations Tips', { amount: inviteList.length })}
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
maxW={['90vw', '500px']}
|
||||
>
|
||||
<ModalBody>
|
||||
{inviteList.map((item) => (
|
||||
<Flex
|
||||
key={item.teamId}
|
||||
alignItems={'center'}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
py={2}
|
||||
_notFirst={{
|
||||
mt: 3
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.avatar} w={['16px', '23px']} />
|
||||
<Box mx={2}>{item.teamName}</Box>
|
||||
<Box flex={1} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={'solid'}
|
||||
colorScheme="green"
|
||||
isLoading={isLoadingAccept}
|
||||
onClick={() => {
|
||||
openConfirm(
|
||||
() =>
|
||||
onAccept({
|
||||
tmbId: item.tmbId,
|
||||
status: TeamMemberStatusEnum.active
|
||||
}),
|
||||
undefined,
|
||||
t('user.team.invite.Accept Confirm')
|
||||
)();
|
||||
}}
|
||||
>
|
||||
{t('user.team.invite.accept')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
ml={2}
|
||||
variant={'solid'}
|
||||
colorScheme="red"
|
||||
isLoading={isLoadingReject}
|
||||
onClick={() => {
|
||||
openConfirm(
|
||||
() =>
|
||||
onReject({
|
||||
tmbId: item.tmbId,
|
||||
status: TeamMemberStatusEnum.reject
|
||||
}),
|
||||
undefined,
|
||||
t('user.team.invite.Reject Confirm')
|
||||
)();
|
||||
}}
|
||||
>
|
||||
{t('user.team.invite.reject')}
|
||||
</Button>
|
||||
</Flex>
|
||||
))}
|
||||
</ModalBody>
|
||||
<ModalFooter justifyContent={'center'}>
|
||||
<Box>{t('user.team.invite.Deal Width Footer Tip')}</Box>
|
||||
</ModalFooter>
|
||||
|
||||
<ConfirmModal />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UpdateInviteModal);
|
Reference in New Issue
Block a user