mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
chore: team, orgs, search and so on (#3807)
* feat: clb search support username, memberName, contacts * feat: popup org names * feat: update team member table * feat: restore the member * feat: search user in team member table * feat: bind contact * feat: export members * feat: org tab could delete member * feat: org table search * feat: team notification account bind * feat: permission tab search * fix: wecom sso * chore(init): copy notificationAccount to user.contact * chore: adjust * fix: ts error * fix: useConfirm iconColor customization * pref: fe * fix: style * fix: fix team member manage * pref: enlarge team member pagesize * pref: initv4822 * fix: pageSize * pref: initscritpt
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.8.21",
|
||||
"version": "4.8.22",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
@@ -21,9 +21,7 @@ const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/U
|
||||
const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'));
|
||||
const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal'));
|
||||
const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform'));
|
||||
const UpdateNotification = dynamic(
|
||||
() => import('@/components/support/user/inform/UpdateNotificationModal')
|
||||
);
|
||||
const UpdateContact = dynamic(() => import('@/components/support/user/inform/UpdateContactModal'));
|
||||
|
||||
const pcUnShowLayoutRoute: Record<string, boolean> = {
|
||||
'/': true,
|
||||
@@ -156,7 +154,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
{notSufficientModalType && <NotSufficientModal type={notSufficientModalType} />}
|
||||
{!!userInfo && <SystemMsgModal />}
|
||||
{showUpdateNotification && (
|
||||
<UpdateNotification onClose={() => setIsUpdateNotification(false)} />
|
||||
<UpdateContact onClose={() => setIsUpdateNotification(false)} mode="contact" />
|
||||
)}
|
||||
{!!userInfo && importantInforms.length > 0 && (
|
||||
<ImportantInform informs={importantInforms} refetch={refetchUnRead} />
|
||||
|
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import { Box, Checkbox, HStack, VStack } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import OrgTags from '../../user/team/OrgTags';
|
||||
|
||||
function MemberItemCard({
|
||||
avatar,
|
||||
key,
|
||||
onChange,
|
||||
isChecked,
|
||||
onDelete,
|
||||
name,
|
||||
permission,
|
||||
orgs
|
||||
}: {
|
||||
avatar: string;
|
||||
key: string;
|
||||
onChange: () => void;
|
||||
isChecked?: boolean;
|
||||
onDelete?: () => void;
|
||||
name: string;
|
||||
permission?: PermissionValueType;
|
||||
orgs?: string[];
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
key={key}
|
||||
px="3"
|
||||
py="2"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
{isChecked !== undefined && <Checkbox isChecked={isChecked} pointerEvents="none" />}
|
||||
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
|
||||
<VStack w="full" gap={0}>
|
||||
<Box w="full">{name}</Box>
|
||||
<Box w="full">{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
|
||||
</VStack>
|
||||
{permission && <PermissionTags permission={permission} />}
|
||||
{onDelete !== undefined && (
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="1rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MemberItemCard;
|
@@ -37,6 +37,8 @@ import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import { getGroupList } from '@/web/support/user/team/group/api';
|
||||
import { getOrgList } from '@/web/support/user/team/org/api';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import MemberItemCard from './MemberItemCard';
|
||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||
|
||||
const HoverBoxStyle = {
|
||||
bgColor: 'myGray.50',
|
||||
@@ -72,6 +74,12 @@ function MemberModal({
|
||||
|
||||
const [parentPath, setParentPath] = useState('');
|
||||
|
||||
const { data: searchedData } = useRequest2(() => GetSearchUserGroupOrg(searchText), {
|
||||
manual: false,
|
||||
throttleWait: 500,
|
||||
refreshDeps: [searchText]
|
||||
});
|
||||
|
||||
const paths = useMemo(() => {
|
||||
const splitPath = parentPath.split('/').filter(Boolean);
|
||||
return splitPath
|
||||
@@ -97,7 +105,10 @@ function MemberModal({
|
||||
return orgs.find((org) => org.pathId === currentOrgId);
|
||||
}, [orgs, parentPath]);
|
||||
const filterOrgs: (OrgType & { count?: number })[] = useMemo(() => {
|
||||
if (searchText) return orgs.filter((item) => item.name.includes(searchText));
|
||||
if (searchText && searchedData) {
|
||||
const orgids = searchedData.orgs.map((item) => item._id);
|
||||
return orgs.filter((org) => orgids.includes(String(org._id)));
|
||||
}
|
||||
if (!searchText && filterClass !== 'org') return [];
|
||||
if (parentPath === '') {
|
||||
setParentPath(`/${orgs[0].pathId}`);
|
||||
@@ -110,27 +121,34 @@ function MemberModal({
|
||||
count:
|
||||
item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length
|
||||
}));
|
||||
}, [orgs, searchText, filterClass, parentPath]);
|
||||
}, [searchText, filterClass, parentPath, orgs, searchedData]);
|
||||
|
||||
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
||||
const filterMembers = useMemo(() => {
|
||||
if (searchText) return members.filter((item) => item.memberName.includes(searchText));
|
||||
if (searchText) {
|
||||
return searchedData?.members;
|
||||
}
|
||||
if (!searchText && filterClass !== 'member' && filterClass !== 'org') return [];
|
||||
|
||||
if (currentOrg && filterClass === 'org') {
|
||||
return members.filter((item) => currentOrg.members.find((v) => v.tmbId === item.tmbId));
|
||||
}
|
||||
|
||||
return members;
|
||||
}, [members, searchText, filterClass, currentOrg]);
|
||||
}, [members, searchedData, searchText, filterClass, currentOrg]);
|
||||
|
||||
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
|
||||
const filterGroups = useMemo(() => {
|
||||
if (searchText) return groups.filter((item) => item.name.includes(searchText));
|
||||
if (searchText) {
|
||||
return searchedData?.groups.map((item) => ({
|
||||
groupName: item.name,
|
||||
_id: item.id,
|
||||
...item
|
||||
}));
|
||||
}
|
||||
if (!searchText && filterClass !== 'group') return [];
|
||||
|
||||
return groups;
|
||||
}, [groups, searchText, filterClass]);
|
||||
}, [searchText, filterClass, groups, searchedData]);
|
||||
|
||||
const permissionList = useContextSelector(CollaboratorContext, (v) => v.permissionList);
|
||||
const getPerLabelList = useContextSelector(CollaboratorContext, (v) => v.getPerLabelList);
|
||||
@@ -146,6 +164,7 @@ function MemberModal({
|
||||
CollaboratorContext,
|
||||
(v) => v.onUpdateCollaborators
|
||||
);
|
||||
|
||||
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
|
||||
() =>
|
||||
onUpdateCollaborators({
|
||||
@@ -210,6 +229,7 @@ function MemberModal({
|
||||
iconSrc={addOnly ? 'keyPrimary' : 'modal/AddClb'}
|
||||
title={addOnly ? t('user:team.add_permission') : t('user:team.add_collaborator')}
|
||||
minW="800px"
|
||||
maxW={'60vw'}
|
||||
h={'100%'}
|
||||
maxH={'90vh'}
|
||||
isCentered
|
||||
@@ -300,136 +320,122 @@ function MemberModal({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{filterClass && (
|
||||
<ScrollData
|
||||
flexDirection={'column'}
|
||||
gap={1}
|
||||
userSelect={'none'}
|
||||
height={'fit-content'}
|
||||
>
|
||||
{filterOrgs.map((org) => {
|
||||
const onChange = () => {
|
||||
setSelectedOrgIdList((state) => {
|
||||
if (state.includes(org._id)) {
|
||||
return state.filter((v) => v !== org._id);
|
||||
}
|
||||
return [...state, org._id];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={org._id}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={selectedOrgIdList.includes(org._id)}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<HStack ml="2" w="full" gap="5px">
|
||||
<Text>{org.name}</Text>
|
||||
{org.count && (
|
||||
<>
|
||||
<Tag size="sm" my="auto">
|
||||
{org.count}
|
||||
</Tag>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
<PermissionTags permission={collaborator?.permission.value} />
|
||||
<ScrollData
|
||||
flexDirection={'column'}
|
||||
gap={1}
|
||||
userSelect={'none'}
|
||||
height={'fit-content'}
|
||||
>
|
||||
{filterOrgs?.map((org) => {
|
||||
const onChange = () => {
|
||||
setSelectedOrgIdList((state) => {
|
||||
if (state.includes(org._id)) {
|
||||
return state.filter((v) => v !== org._id);
|
||||
}
|
||||
return [...state, org._id];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={org._id}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={selectedOrgIdList.includes(org._id)}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<HStack ml="2" w="full" gap="5px">
|
||||
<Text>{org.name}</Text>
|
||||
{org.count && (
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w="16px"
|
||||
p="4px"
|
||||
rounded={'6px'}
|
||||
_hover={{
|
||||
bgColor: 'myGray.200'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
setParentPath(getOrgChildrenPath(org));
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<Tag size="sm" my="auto">
|
||||
{org.count}
|
||||
</Tag>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{filterMembers.map((member) => {
|
||||
const onChange = () => {
|
||||
setSelectedMembers((state) => {
|
||||
if (state.includes(member.tmbId)) {
|
||||
return state.filter((v) => v !== member.tmbId);
|
||||
}
|
||||
return [...state, member.tmbId];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={member.tmbId}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={selectedMemberIdList.includes(member.tmbId)}
|
||||
pointerEvents="none"
|
||||
<PermissionTags permission={collaborator?.permission.value} />
|
||||
{org.count && (
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w="16px"
|
||||
p="4px"
|
||||
rounded={'6px'}
|
||||
_hover={{
|
||||
bgColor: 'myGray.200'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
setParentPath(getOrgChildrenPath(org));
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box w="full" ml="2">
|
||||
{member.memberName}
|
||||
</Box>
|
||||
<PermissionTags permission={collaborator?.permission.value} />
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{filterGroups.map((group) => {
|
||||
const onChange = () => {
|
||||
setSelectedGroupIdList((state) => {
|
||||
if (state.includes(group._id)) {
|
||||
return state.filter((v) => v !== group._id);
|
||||
}
|
||||
return [...state, group._id];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={group._id}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={selectedGroupIdList.includes(group._id)}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box ml="2" w="full">
|
||||
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
|
||||
</Box>
|
||||
<PermissionTags permission={collaborator?.permission.value} />
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</ScrollData>
|
||||
)}
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{filterMembers?.map((member) => {
|
||||
const onChange = () => {
|
||||
setSelectedMembers((state) => {
|
||||
if (state.includes(member.tmbId)) {
|
||||
return state.filter((v) => v !== member.tmbId);
|
||||
}
|
||||
return [...state, member.tmbId];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
|
||||
const memberOrgs = orgs.filter((org) =>
|
||||
org.members.find((v) => String(v.tmbId) === String(member.tmbId))
|
||||
);
|
||||
const memberPathIds = memberOrgs.map((org) =>
|
||||
(org.path + '/' + org.pathId).split('/').slice(0)
|
||||
);
|
||||
const memberOrgNames = memberPathIds.map((pathIds) =>
|
||||
pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/')
|
||||
);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={member.avatar}
|
||||
key={member.tmbId}
|
||||
name={member.memberName}
|
||||
permission={collaborator?.permission.value}
|
||||
onChange={onChange}
|
||||
isChecked={selectedMemberIdList.includes(member.tmbId)}
|
||||
orgs={memberOrgNames}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{filterGroups?.map((group) => {
|
||||
const onChange = () => {
|
||||
setSelectedGroupIdList((state) => {
|
||||
if (state.includes(group._id)) {
|
||||
return state.filter((v) => v !== group._id);
|
||||
}
|
||||
return [...state, group._id];
|
||||
});
|
||||
};
|
||||
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
|
||||
return (
|
||||
<MemberItemCard
|
||||
avatar={group.avatar}
|
||||
key={group._id}
|
||||
name={
|
||||
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
|
||||
}
|
||||
permission={collaborator?.permission.value}
|
||||
onChange={onChange}
|
||||
isChecked={selectedGroupIdList.includes(group._id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ScrollData>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -441,29 +447,27 @@ function MemberModal({
|
||||
<Flex flexDirection="column" mt="2" gap={1} overflow={'auto'} flex={'1 0 0'} h={0}>
|
||||
{selectedList.map((item) => {
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
<MemberItemCard
|
||||
key={item.id}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={HoverBoxStyle}
|
||||
>
|
||||
<MyAvatar src={item.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box w="full" ml="2">
|
||||
{item.name}
|
||||
</Box>
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="1rem"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={item.onDelete}
|
||||
/>
|
||||
</HStack>
|
||||
avatar={item.avatar}
|
||||
name={item.name ?? ''}
|
||||
onChange={item.onDelete}
|
||||
onDelete={item.onDelete}
|
||||
orgs={(() => {
|
||||
if (!item.id.startsWith('member-')) return [];
|
||||
const id = item.id.replace('member-', '');
|
||||
const memberOrgs = orgs.filter((org) =>
|
||||
org.members.find((v) => v.tmbId === id)
|
||||
);
|
||||
const memberPathIds = memberOrgs.map((org) =>
|
||||
(org.path + '/' + org.pathId).split('/').slice(0)
|
||||
);
|
||||
const memberOrgNames = memberPathIds.map((pathIds) =>
|
||||
pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/')
|
||||
);
|
||||
return memberOrgNames;
|
||||
})()}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
|
@@ -4,34 +4,48 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { updateNotificationAccount } from '@/web/support/user/api';
|
||||
import { updateContact, updateNotificationAccount } from '@/web/support/user/api';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
type FormType = {
|
||||
account: string;
|
||||
contact: string;
|
||||
verifyCode: string;
|
||||
};
|
||||
|
||||
const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const UpdateContactModal = ({
|
||||
onClose,
|
||||
mode
|
||||
}: {
|
||||
onClose: () => void;
|
||||
mode: 'contact' | 'notification_account';
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { initUserInfo } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { register, handleSubmit, watch } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
account: '',
|
||||
contact: '',
|
||||
verifyCode: ''
|
||||
}
|
||||
});
|
||||
const account = watch('account');
|
||||
|
||||
const account = watch('contact');
|
||||
const verifyCode = watch('verifyCode');
|
||||
|
||||
const { runAsync: onSubmit, loading: isLoading } = useRequest2(
|
||||
(data: FormType) => {
|
||||
return updateNotificationAccount(data);
|
||||
if (mode === 'contact') {
|
||||
return updateContact(data);
|
||||
} else {
|
||||
return updateNotificationAccount({
|
||||
account: data.contact,
|
||||
verifyCode: data.verifyCode
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
@@ -62,7 +76,11 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
|
||||
isOpen
|
||||
iconSrc="common/settingLight"
|
||||
w={'32rem'}
|
||||
title={t('common:support.user.info.notification_receiving_hint')}
|
||||
title={
|
||||
mode === 'notification_account'
|
||||
? t('common:support.user.info.notification_receiving_hint')
|
||||
: t('account_info:contact')
|
||||
}
|
||||
>
|
||||
<ModalBody px={10}>
|
||||
<Flex flexDirection="column">
|
||||
@@ -75,7 +93,7 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
|
||||
<Input
|
||||
flex={1}
|
||||
bg={'myGray.50'}
|
||||
{...register('account', { required: true })}
|
||||
{...register('contact', { required: true })}
|
||||
placeholder={placeholder}
|
||||
></Input>
|
||||
</Flex>
|
||||
@@ -108,4 +126,4 @@ const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateNotificationModal;
|
||||
export default UpdateContactModal;
|
@@ -0,0 +1,39 @@
|
||||
import { Box, Flex, VStack } from '@chakra-ui/react';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import React from 'react';
|
||||
|
||||
function OrgTags({ orgs, type = 'simple' }: { orgs: string[]; type?: 'simple' | 'tag' }) {
|
||||
return (
|
||||
<MyTooltip
|
||||
label={
|
||||
<VStack gap="1" alignItems={'start'}>
|
||||
{orgs.map((org, index) => (
|
||||
<Box key={index} fontSize="sm" fontWeight={400} color="myGray.500">
|
||||
{org.slice(1)}
|
||||
</Box>
|
||||
))}
|
||||
</VStack>
|
||||
}
|
||||
>
|
||||
{type === 'simple' ? (
|
||||
<Box fontSize="sm" fontWeight={400} w="full" color="myGray.500" whiteSpace={'nowrap'}>
|
||||
{orgs
|
||||
.map((org) => org.split('/').pop())
|
||||
.join(', ')
|
||||
.slice(0, 30)}
|
||||
{orgs.length > 1 && '...'}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex direction="row" gap="1" p="2" alignItems={'start'} wrap={'wrap'}>
|
||||
{orgs.map((org, index) => (
|
||||
<Tag key={index}>{org.split('/').pop()}</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
</MyTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrgTags;
|
@@ -2,18 +2,30 @@ import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { Box, Button, Flex, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
HStack,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { postCreateTeam, putUpdateTeam } from '@/web/support/user/team/api';
|
||||
import { CreateTeamProps } from '@fastgpt/global/support/user/team/controller.d';
|
||||
import { DEFAULT_TEAM_AVATAR } from '@fastgpt/global/common/system/constants';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
const UpdateContact = dynamic(() => import('@/components/support/user/inform/UpdateContactModal'));
|
||||
|
||||
export type EditTeamFormDataType = CreateTeamProps & {
|
||||
id?: string;
|
||||
notificationAccount?: string;
|
||||
};
|
||||
|
||||
export const defaultForm = {
|
||||
@@ -31,12 +43,12 @@ function EditModal({
|
||||
onSuccess: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { register, setValue, handleSubmit, watch } = useForm<CreateTeamProps>({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
const avatar = watch('avatar');
|
||||
const notificationAccount = watch('notificationAccount');
|
||||
|
||||
const {
|
||||
File,
|
||||
@@ -74,6 +86,8 @@ function EditModal({
|
||||
errorToast: t('common:common.Update Failed')
|
||||
});
|
||||
|
||||
const { isOpen: isOpenContact, onClose: onCloseContact, onOpen: onOpenContact } = useDisclosure();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
@@ -84,7 +98,7 @@ function EditModal({
|
||||
>
|
||||
<ModalBody>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('user:team.Set Name')}
|
||||
{t('account_team:set_name_avatar')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<MyTooltip label={t('common:common.Set Avatar')}>
|
||||
@@ -110,6 +124,38 @@ function EditModal({
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'} mt={4}>
|
||||
{t('account_team:notification_recieve')}
|
||||
</Box>
|
||||
<HStack w="full" justifyContent={'space-between'}>
|
||||
{(() => {
|
||||
return notificationAccount ? (
|
||||
<Box width="full">{notificationAccount}</Box>
|
||||
) : (
|
||||
<HStack
|
||||
px="3"
|
||||
py="1"
|
||||
color="red.600"
|
||||
bgColor="red.50"
|
||||
borderRadius="md"
|
||||
fontSize={'sm'}
|
||||
width={'fit-content'}
|
||||
>
|
||||
<Icon name="common/info" w="1rem" />
|
||||
<Box width="fit-content">{t('account_info:please_bind_contact')}</Box>
|
||||
</HStack>
|
||||
);
|
||||
})()}
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<Icon name="common/setting" w="1rem" />}
|
||||
onClick={() => {
|
||||
onOpenContact();
|
||||
}}
|
||||
>
|
||||
{t('common:common.Setting')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
@@ -142,6 +188,14 @@ function EditModal({
|
||||
})
|
||||
}
|
||||
/>
|
||||
{isOpenContact && (
|
||||
<UpdateContact
|
||||
onClose={() => {
|
||||
onCloseContact();
|
||||
}}
|
||||
mode="notification_account"
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
@@ -148,7 +148,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
h={'100%'}
|
||||
>
|
||||
<Flex flexDirection="column" p="4">
|
||||
<Flex flexDirection="column" p="4" overflowY={'auto'} overflowX={'hidden'}>
|
||||
<SearchInput
|
||||
placeholder={t('user:search_user')}
|
||||
fontSize="sm"
|
||||
@@ -157,7 +157,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro
|
||||
setSearchKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
|
||||
{filtered.map((member) => {
|
||||
return (
|
||||
<HStack
|
||||
|
@@ -11,15 +11,15 @@ import {
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
useDisclosure
|
||||
useDisclosure,
|
||||
VStack
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { delRemoveMember } from '@/web/support/user/team/api';
|
||||
import { delRemoveMember, updateStatus } from '@/web/support/user/team/api';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import GroupTags from '@/components/support/permission/Group/GroupTags';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from './context';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -29,9 +29,17 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { delLeaveTeam } from '@/web/support/user/team/api';
|
||||
import { postSyncMembers } from '@/web/support/user/api';
|
||||
import { GetSearchUserGroupOrg, postSyncMembers } from '@/web/support/user/api';
|
||||
import MyLoading from '@fastgpt/web/components/common/MyLoading';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import {
|
||||
TeamMemberRoleEnum,
|
||||
TeamMemberStatusEnum
|
||||
} from '@fastgpt/global/support/user/team/constant';
|
||||
import format from 'date-fns/format';
|
||||
import OrgTags from '@/components/support/user/team/OrgTags';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { useState } from 'react';
|
||||
import { downloadFetch } from '@/web/common/system/utils';
|
||||
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||
@@ -43,14 +51,14 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const { feConfigs, setNotSufficientModalType } = useSystemStore();
|
||||
|
||||
const {
|
||||
groups,
|
||||
refetchGroups,
|
||||
myTeams,
|
||||
refetchTeams,
|
||||
members,
|
||||
refetchMembers,
|
||||
onSwitchTeam,
|
||||
MemberScrollData
|
||||
MemberScrollData,
|
||||
orgs
|
||||
} = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const {
|
||||
@@ -64,8 +72,25 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
type: 'delete'
|
||||
});
|
||||
|
||||
const { ConfirmModal: ConfirmRestoreMemberModal, openConfirm: openRestoreMember } = useConfirm({
|
||||
type: 'common',
|
||||
title: t('account_team:restore_tip_title'),
|
||||
iconSrc: 'common/confirm/restoreTip',
|
||||
iconColor: 'primary.500'
|
||||
});
|
||||
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const isSyncMember = feConfigs.register_method?.includes('sync');
|
||||
|
||||
const { data: searchMembersData } = useRequest2(
|
||||
() => GetSearchUserGroupOrg(searchText, { members: true, orgs: false, groups: false }),
|
||||
{
|
||||
manual: false,
|
||||
throttleWait: 500,
|
||||
refreshDeps: [searchText]
|
||||
}
|
||||
);
|
||||
|
||||
const { runAsync: onLeaveTeam } = useRequest2(
|
||||
async () => {
|
||||
const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0];
|
||||
@@ -92,12 +117,28 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
errorToast: t('account_team:sync_member_failed')
|
||||
});
|
||||
|
||||
const { runAsync: onRestore, loading: isUpdateInvite } = useRequest2(updateStatus, {
|
||||
onSuccess() {
|
||||
refetchMembers();
|
||||
},
|
||||
successToast: t('common:user.team.invite.Accepted'),
|
||||
errorToast: t('common:user.team.invite.Reject')
|
||||
});
|
||||
|
||||
const isLoading = isUpdateInvite || isSyncing;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSyncing && <MyLoading />}
|
||||
{isLoading && <MyLoading />}
|
||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||
{Tabs}
|
||||
<HStack>
|
||||
<HStack alignItems={'center'}>
|
||||
<Box width={'200px'}>
|
||||
<SearchInput
|
||||
placeholder={t('account_team:search_member')}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
{userInfo?.team.permission.hasManagePer && feConfigs?.show_team_chat && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
@@ -165,6 +206,23 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
{t('account_team:user_team_leave_team')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo?.team.permission.hasManagePer && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="md"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name="export" w={'16px'} />}
|
||||
onClick={() => {
|
||||
downloadFetch({
|
||||
url: '/api/proApi/support/user/team/member/export',
|
||||
filename: `${userInfo.team.teamName}-${format(new Date(), 'yyyyMMddHHmmss')}.csv`
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('account_team:export_members')}
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
@@ -177,45 +235,66 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{t('account_team:user_name')}
|
||||
</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:member_group')}</Th>
|
||||
{!isSyncMember && (
|
||||
<Th borderRightRadius="6px" bgColor="myGray.100">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
)}
|
||||
<Th bgColor="myGray.100">{t('account_team:contact')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:org')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:join_update_time')}</Th>
|
||||
<Th borderRightRadius="6px" bgColor="myGray.100">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{members?.map((item) => (
|
||||
<Tr key={item.userId} overflow={'unset'}>
|
||||
<Td>
|
||||
<HStack>
|
||||
<Avatar src={item.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
||||
<Box className={'textEllipsis'}>
|
||||
{item.memberName}
|
||||
{item.status === 'waiting' && (
|
||||
<Tag ml="2" colorSchema="yellow">
|
||||
{t('account_team:waiting')}
|
||||
</Tag>
|
||||
)}
|
||||
</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td maxW={'300px'}>
|
||||
<GroupTags
|
||||
names={groups
|
||||
?.filter((group) =>
|
||||
group.members.map((m) => m.tmbId).includes(item.tmbId)
|
||||
)
|
||||
.map((g) => g.name)}
|
||||
max={3}
|
||||
/>
|
||||
</Td>
|
||||
{!isSyncMember && (
|
||||
{(searchText && searchMembersData ? searchMembersData.members : members).map(
|
||||
(member) => (
|
||||
<Tr key={member.tmbId} overflow={'unset'}>
|
||||
<Td>
|
||||
<HStack>
|
||||
<Avatar src={member.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
||||
<Box className={'textEllipsis'}>
|
||||
{member.memberName}
|
||||
{member.status === 'waiting' && (
|
||||
<Tag ml="2" colorSchema="yellow">
|
||||
{t('account_team:waiting')}
|
||||
</Tag>
|
||||
)}
|
||||
{member.status === 'leave' && (
|
||||
<Tag ml="2" colorSchema="gray">
|
||||
{t('account_team:leave')}
|
||||
</Tag>
|
||||
)}
|
||||
</Box>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td maxW={'300px'}>{member.contact || '-'}</Td>
|
||||
<Td maxWidth="300px">
|
||||
{(() => {
|
||||
const memberOrgs = orgs.filter((org) =>
|
||||
org.members.find((v) => String(v.tmbId) === String(member.tmbId))
|
||||
);
|
||||
const memberPathIds = memberOrgs.map((org) =>
|
||||
(org.path + '/' + org.pathId).split('/').slice(0)
|
||||
);
|
||||
const memberOrgNames = memberPathIds.map((pathIds) =>
|
||||
pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/')
|
||||
);
|
||||
return <OrgTags orgs={memberOrgNames} type="tag" />;
|
||||
})()}
|
||||
</Td>
|
||||
<Td maxW={'300px'}>
|
||||
<VStack gap={0}>
|
||||
<Box>{format(new Date(member.createTime), 'yyyy-MM-dd HH:mm:ss')}</Box>
|
||||
<Box>
|
||||
{member.updateTime
|
||||
? format(new Date(member.updateTime), 'yyyy-MM-dd HH:mm:ss')
|
||||
: '-'}
|
||||
</Box>
|
||||
</VStack>
|
||||
</Td>
|
||||
<Td>
|
||||
{userInfo?.team.permission.hasManagePer &&
|
||||
item.role !== TeamMemberRoleEnum.owner &&
|
||||
item.tmbId !== userInfo?.team.tmbId && (
|
||||
member.role !== TeamMemberRoleEnum.owner &&
|
||||
member.tmbId !== userInfo?.team.tmbId &&
|
||||
(member.status !== TeamMemberStatusEnum.leave ? (
|
||||
<Icon
|
||||
name={'common/trash'}
|
||||
cursor={'pointer'}
|
||||
@@ -229,24 +308,50 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
onClick={() => {
|
||||
openRemoveMember(
|
||||
() =>
|
||||
delRemoveMember(item.tmbId).then(() =>
|
||||
delRemoveMember(member.tmbId).then(() =>
|
||||
Promise.all([refetchGroups(), refetchMembers()])
|
||||
),
|
||||
undefined,
|
||||
t('account_team:remove_tip', {
|
||||
username: item.memberName
|
||||
username: member.memberName
|
||||
})
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
) : (
|
||||
<Icon
|
||||
name={'common/confirm/restoreTip'}
|
||||
cursor={'pointer'}
|
||||
w="1rem"
|
||||
p="1"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
color: 'primary.500',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
openRestoreMember(
|
||||
() =>
|
||||
onRestore({
|
||||
tmbId: member.tmbId,
|
||||
status: TeamMemberStatusEnum.active
|
||||
}),
|
||||
undefined,
|
||||
t('account_team:restore_tip', {
|
||||
username: member.memberName
|
||||
})
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
</Tr>
|
||||
)
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
<ConfirmRemoveMemberModal />
|
||||
<ConfirmRestoreMemberModal />
|
||||
</TableContainer>
|
||||
</MemberScrollData>
|
||||
</Box>
|
||||
|
@@ -112,7 +112,7 @@ function OrgMemberManageModal({
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
h={'100%'}
|
||||
>
|
||||
<Flex flexDirection="column" p="4">
|
||||
<Flex flexDirection="column" p="4" overflowY="auto" overflowX="hidden">
|
||||
<SearchInput
|
||||
placeholder={t('user:search_user')}
|
||||
fontSize="sm"
|
||||
@@ -121,7 +121,7 @@ function OrgMemberManageModal({
|
||||
setSearchKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'} maxH={'400px'}>
|
||||
<MemberScrollData mt={3} flexGrow="1" overflow={'auto'}>
|
||||
{filterMembers.map((member) => {
|
||||
return (
|
||||
<HStack
|
||||
|
@@ -37,6 +37,8 @@ import Path from '@/components/common/folder/Path';
|
||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { delRemoveMember } from '@/web/support/user/team/api';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
|
||||
const OrgInfoModal = dynamic(() => import('./OrgInfoModal'));
|
||||
const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal'));
|
||||
@@ -74,8 +76,9 @@ function ActionButton({
|
||||
function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, isTeamAdmin } = useUserStore();
|
||||
const [searchOrg, setSearchOrg] = useState('');
|
||||
|
||||
const { members, MemberScrollData } = useContextSelector(TeamContext, (v) => v);
|
||||
const { members, MemberScrollData, refetchMembers } = useContextSelector(TeamContext, (v) => v);
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const isSyncMember = feConfigs.register_method?.includes('sync');
|
||||
@@ -88,6 +91,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
|
||||
const currentOrgs = useMemo(() => {
|
||||
if (orgs.length === 0) return [];
|
||||
// Auto select the first org(root org is team)
|
||||
@@ -107,6 +111,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
};
|
||||
});
|
||||
}, [orgs, parentPath]);
|
||||
|
||||
const currentOrg = useMemo(() => {
|
||||
const splitPath = parentPath.split('/');
|
||||
const currentOrgId = splitPath[splitPath.length - 1];
|
||||
@@ -121,7 +126,7 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
.map((id) => {
|
||||
const org = orgs.find((org) => org.pathId === id)!;
|
||||
|
||||
if (org.path === '') return;
|
||||
if (org?.path === '') return;
|
||||
|
||||
return {
|
||||
parentId: getOrgChildrenPath(org),
|
||||
@@ -148,20 +153,52 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
});
|
||||
|
||||
// Delete member
|
||||
const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({
|
||||
type: 'delete',
|
||||
content: t('account_team:confirm_delete_member')
|
||||
});
|
||||
const { ConfirmModal: ConfirmDeleteMemberFromOrg, openConfirm: openDeleteMemberFromOrgModal } =
|
||||
useConfirm({
|
||||
type: 'delete'
|
||||
});
|
||||
|
||||
const { ConfirmModal: ConfirmDeleteMemberFromTeam, openConfirm: openDeleteMemberFromTeamModal } =
|
||||
useConfirm({
|
||||
type: 'delete'
|
||||
});
|
||||
|
||||
const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
}
|
||||
});
|
||||
|
||||
const { runAsync: deleteMemberFromTeamReq } = useRequest2(delRemoveMember, {
|
||||
onSuccess: () => {
|
||||
refetchOrgs();
|
||||
refetchMembers();
|
||||
}
|
||||
});
|
||||
|
||||
const searchedOrgs = useMemo(() => {
|
||||
if (!searchOrg) return [];
|
||||
|
||||
return orgs
|
||||
.filter((org) => org.name.includes(searchOrg))
|
||||
.map((org) => ({
|
||||
...org,
|
||||
count:
|
||||
org.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(org)).length
|
||||
}));
|
||||
}, [orgs, searchOrg]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||
{Tabs}
|
||||
<Box w="200px">
|
||||
<SearchInput
|
||||
placeholder={t('account_team:search_org')}
|
||||
value={searchOrg}
|
||||
onChange={(e) => setSearchOrg(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<MyBox
|
||||
flex={'1 0 0'}
|
||||
@@ -183,20 +220,25 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Th bg="myGray.100" borderLeftRadius="6px">
|
||||
{t('common:Name')}
|
||||
</Th>
|
||||
{!isSyncMember && (
|
||||
<Th bg="myGray.100" borderRightRadius="6px">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
)}
|
||||
<Th bg="myGray.100" borderRightRadius="6px">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{currentOrgs.map((org) => (
|
||||
<Tr key={org._id} overflow={'unset'}>
|
||||
{searchedOrgs.map((org) => (
|
||||
<Tr
|
||||
key={org._id}
|
||||
overflow={'unset'}
|
||||
onClick={() => setParentPath(getOrgChildrenPath(org))}
|
||||
>
|
||||
<Td>
|
||||
<HStack
|
||||
cursor={'pointer'}
|
||||
onClick={() => setParentPath(getOrgChildrenPath(org))}
|
||||
onClick={() => {
|
||||
setParentPath(getOrgChildrenPath(org));
|
||||
setSearchOrg('');
|
||||
}}
|
||||
>
|
||||
<MemberTag name={org.name} avatar={org.avatar} />
|
||||
<Tag size="sm">{org.count}</Tag>
|
||||
@@ -208,73 +250,130 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
{isTeamAdmin && !isSyncMember && (
|
||||
<Td w={'6rem'}>
|
||||
<MyMenu
|
||||
trigger="hover"
|
||||
Button={<IconButton name="more" />}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('account_team:edit_info'),
|
||||
onClick: () => setEditOrg(org)
|
||||
},
|
||||
{
|
||||
icon: 'common/file/move',
|
||||
label: t('common:Move'),
|
||||
onClick: () => setMovingOrg(org)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('account_team:delete'),
|
||||
type: 'danger',
|
||||
onClick: () => deleteOrgHandler(org._id)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
{currentOrg?.members.map((member) => {
|
||||
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
||||
if (!memberInfo) return null;
|
||||
|
||||
return (
|
||||
<Tr key={member.tmbId}>
|
||||
{!searchOrg &&
|
||||
currentOrgs.map((org) => (
|
||||
<Tr key={org._id} overflow={'unset'}>
|
||||
<Td>
|
||||
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
|
||||
<HStack
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
setParentPath(getOrgChildrenPath(org));
|
||||
setSearchOrg('');
|
||||
}}
|
||||
>
|
||||
<MemberTag name={org.name} avatar={org.avatar} />
|
||||
<Tag size="sm">{org.count}</Tag>
|
||||
<MyIcon
|
||||
name="core/chat/chevronRight"
|
||||
w={'1rem'}
|
||||
h={'1rem'}
|
||||
color={'myGray.500'}
|
||||
/>
|
||||
</HStack>
|
||||
</Td>
|
||||
<Td w={'6rem'}>
|
||||
{isTeamAdmin && !isSyncMember && (
|
||||
{isTeamAdmin && !isSyncMember && (
|
||||
<Td w={'6rem'}>
|
||||
<MyMenu
|
||||
trigger={'hover'}
|
||||
trigger="hover"
|
||||
Button={<IconButton name="more" />}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('account_team:edit_info'),
|
||||
onClick: () => setEditOrg(org)
|
||||
},
|
||||
{
|
||||
icon: 'common/file/move',
|
||||
label: t('common:Move'),
|
||||
onClick: () => setMovingOrg(org)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('account_team:delete'),
|
||||
type: 'danger',
|
||||
onClick: () =>
|
||||
openDeleteMemberModal(() =>
|
||||
deleteMemberReq(currentOrg._id, member.tmbId)
|
||||
)()
|
||||
onClick: () => deleteOrgHandler(org._id)
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
{!searchOrg &&
|
||||
currentOrg?.members.map((member) => {
|
||||
const memberInfo = members.find((m) => m.tmbId === member.tmbId);
|
||||
if (!memberInfo) return null;
|
||||
|
||||
return (
|
||||
<Tr key={member.tmbId}>
|
||||
<Td>
|
||||
<MemberTag name={memberInfo.memberName} avatar={memberInfo.avatar} />
|
||||
</Td>
|
||||
<Td w={'6rem'}>
|
||||
{isTeamAdmin && (
|
||||
<MyMenu
|
||||
trigger={'hover'}
|
||||
Button={<IconButton name="more" />}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
menuItemStyles: {
|
||||
_hover: {
|
||||
color: 'red.600',
|
||||
backgroundColor: 'red.50'
|
||||
}
|
||||
},
|
||||
label: t('account_team:delete_from_team', {
|
||||
username: memberInfo.memberName
|
||||
}),
|
||||
onClick: () => {
|
||||
openDeleteMemberFromTeamModal(
|
||||
() => deleteMemberFromTeamReq(member.tmbId),
|
||||
undefined,
|
||||
t('account_team:confirm_delete_from_team', {
|
||||
username: memberInfo.memberName
|
||||
})
|
||||
)();
|
||||
}
|
||||
},
|
||||
...(isSyncMember
|
||||
? []
|
||||
: [
|
||||
{
|
||||
menuItemStyles: {
|
||||
_hover: {
|
||||
color: 'red.600',
|
||||
bgColor: 'red.50'
|
||||
}
|
||||
},
|
||||
label: t('account_team:delete_from_org'),
|
||||
onClick: () =>
|
||||
openDeleteMemberFromOrgModal(
|
||||
() =>
|
||||
deleteMemberReq(currentOrg._id, member.tmbId),
|
||||
undefined,
|
||||
t('account_team:confirm_delete_from_org', {
|
||||
username: memberInfo.memberName
|
||||
})
|
||||
)()
|
||||
}
|
||||
])
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
@@ -361,7 +460,8 @@ function OrgTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
)}
|
||||
|
||||
<ConfirmDeleteOrgModal />
|
||||
<ConfirmDeleteMember />
|
||||
<ConfirmDeleteMemberFromOrg />
|
||||
<ConfirmDeleteMemberFromTeam />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
@@ -40,7 +40,8 @@ import CollaboratorContextProvider, {
|
||||
} from '@/components/support/permission/MemberManager/context';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { GetSearchUserGroupOrg } from '@/web/support/user/api';
|
||||
|
||||
function PermissionManage({
|
||||
Tabs,
|
||||
@@ -69,27 +70,37 @@ function PermissionManage({
|
||||
const [isExpandGroup, setExpandGroup] = useToggle(true);
|
||||
const [isExpandOrg, setExpandOrg] = useToggle(true);
|
||||
|
||||
const { tmbList, groupList, orgList } = useMemo(() => {
|
||||
const tmbList: CollaboratorItemType[] = [];
|
||||
const groupList: CollaboratorItemType[] = [];
|
||||
const orgList: CollaboratorItemType[] = [];
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
|
||||
collaboratorList.forEach((item) => {
|
||||
if (item.tmbId) {
|
||||
tmbList.push(item);
|
||||
} else if (item.groupId) {
|
||||
groupList.push(item);
|
||||
} else if (item.orgId) {
|
||||
orgList.push(item);
|
||||
}
|
||||
});
|
||||
const { data: searchResult } = useRequest2(() => GetSearchUserGroupOrg(searchKey), {
|
||||
manual: false,
|
||||
throttleWait: 500,
|
||||
refreshDeps: [searchKey]
|
||||
});
|
||||
|
||||
const { tmbList, groupList, orgList } = useMemo(() => {
|
||||
const tmbList = collaboratorList.filter(
|
||||
(item) =>
|
||||
Object.keys(item).includes('tmbId') &&
|
||||
(!searchKey || searchResult?.members.find((member) => member.tmbId === item.tmbId))
|
||||
);
|
||||
const groupList = collaboratorList.filter(
|
||||
(item) =>
|
||||
Object.keys(item).includes('groupId') &&
|
||||
(!searchKey || searchResult?.groups.find((group) => group.groupId === item.groupId))
|
||||
);
|
||||
const orgList = collaboratorList.filter(
|
||||
(item) =>
|
||||
Object.keys(item).includes('orgId') &&
|
||||
(!searchKey || searchResult?.orgs.find((org) => org._id === item.orgId))
|
||||
);
|
||||
|
||||
return {
|
||||
tmbList,
|
||||
groupList,
|
||||
orgList
|
||||
};
|
||||
}, [collaboratorList]);
|
||||
}, [collaboratorList, searchResult, searchKey]);
|
||||
|
||||
const { runAsync: onUpdatePermission, loading: addLoading } = useRequest2(
|
||||
async ({ id, type, per }: { id: string; type: 'add' | 'remove'; per: 'write' | 'manage' }) => {
|
||||
@@ -131,12 +142,12 @@ function PermissionManage({
|
||||
<Flex justify={'space-between'} align={'center'} pb={'1rem'}>
|
||||
{Tabs}
|
||||
<Box ml="auto">
|
||||
{/* <SearchInput
|
||||
<SearchInput
|
||||
placeholder={t('user:search_group_org_user')}
|
||||
w="200px"
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value)}
|
||||
/> */}
|
||||
/>
|
||||
</Box>
|
||||
{userInfo?.team.permission.hasManagePer && (
|
||||
<Button
|
||||
|
@@ -11,6 +11,8 @@ import { useTranslation } from 'next-i18next';
|
||||
import { getGroupList } from '@/web/support/user/team/group/api';
|
||||
import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { getOrgList } from '@/web/support/user/team/org/api';
|
||||
import { OrgType } from '@fastgpt/global/support/user/team/org/type';
|
||||
|
||||
const EditInfoModal = dynamic(() => import('./EditInfoModal'));
|
||||
|
||||
@@ -18,6 +20,7 @@ type TeamModalContextType = {
|
||||
myTeams: TeamTmbItemType[];
|
||||
members: TeamMemberItemType[];
|
||||
groups: MemberGroupListType;
|
||||
orgs: OrgType[];
|
||||
isLoading: boolean;
|
||||
onSwitchTeam: (teamId: string) => void;
|
||||
setEditTeamData: React.Dispatch<React.SetStateAction<EditTeamFormDataType | undefined>>;
|
||||
@@ -25,6 +28,7 @@ type TeamModalContextType = {
|
||||
refetchMembers: () => void;
|
||||
refetchTeams: () => void;
|
||||
refetchGroups: () => void;
|
||||
refetchOrgs: () => void;
|
||||
teamSize: number;
|
||||
MemberScrollData: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||
};
|
||||
@@ -33,6 +37,7 @@ export const TeamContext = createContext<TeamModalContextType>({
|
||||
myTeams: [],
|
||||
groups: [],
|
||||
members: [],
|
||||
orgs: [],
|
||||
isLoading: false,
|
||||
onSwitchTeam: function (_teamId: string): void {
|
||||
throw new Error('Function not implemented.');
|
||||
@@ -49,7 +54,9 @@ export const TeamContext = createContext<TeamModalContextType>({
|
||||
refetchGroups: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
|
||||
refetchOrgs: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
teamSize: 0,
|
||||
MemberScrollData: () => <></>
|
||||
});
|
||||
@@ -68,6 +75,15 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
refreshDeps: [userInfo?._id]
|
||||
});
|
||||
|
||||
const {
|
||||
data: orgs = [],
|
||||
loading: isLoadingOrgs,
|
||||
refresh: refetchOrgs
|
||||
} = useRequest2(getOrgList, {
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
|
||||
// member action
|
||||
const {
|
||||
data: members = [],
|
||||
@@ -75,7 +91,12 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
refreshList: refetchMembers,
|
||||
total: memberTotal,
|
||||
ScrollData: MemberScrollData
|
||||
} = useScrollPagination(getTeamMembers, {});
|
||||
} = useScrollPagination(getTeamMembers, {
|
||||
pageSize: 20,
|
||||
params: {
|
||||
withLeaved: true
|
||||
}
|
||||
});
|
||||
|
||||
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
||||
async (teamId: string) => {
|
||||
@@ -96,13 +117,16 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
});
|
||||
|
||||
const isLoading = isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups;
|
||||
const isLoading =
|
||||
isLoadingTeams || isSwitchingTeam || loadingMembers || isLoadingGroups || isLoadingOrgs;
|
||||
|
||||
const contextValue = {
|
||||
myTeams,
|
||||
refetchTeams,
|
||||
isLoading,
|
||||
onSwitchTeam,
|
||||
orgs,
|
||||
refetchOrgs,
|
||||
|
||||
// create | update team
|
||||
setEditTeamData,
|
||||
|
@@ -111,7 +111,7 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
|
||||
provider: OAuthEnum.wecom,
|
||||
icon: 'common/wecom',
|
||||
redirectUrl: isWecomWorkTerminal
|
||||
? `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${feConfigs?.oauth?.wecom?.corpid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base&agentid=${feConfigs?.oauth?.wecom?.agentid}&state=${state.current}#wechat_redirect`
|
||||
? `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${feConfigs?.oauth?.wecom?.corpid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_privateinfo&agentid=${feConfigs?.oauth?.wecom?.agentid}&state=${state.current}#wechat_redirect`
|
||||
: `https://login.work.weixin.qq.com/wwlogin/sso/login?login_type=CorpApp&appid=${feConfigs?.oauth?.wecom?.corpid}&agentid=${feConfigs?.oauth?.wecom?.agentid}&redirect_uri=${redirectUri}&state=${state.current}`
|
||||
}
|
||||
]
|
||||
|
@@ -49,9 +49,7 @@ const StandDetailModal = dynamic(
|
||||
);
|
||||
const ConversionModal = dynamic(() => import('@/pageComponents/account/info/ConversionModal'));
|
||||
const UpdatePswModal = dynamic(() => import('@/pageComponents/account/info/UpdatePswModal'));
|
||||
const UpdateNotification = dynamic(
|
||||
() => import('@/components/support/user/inform/UpdateNotificationModal')
|
||||
);
|
||||
const UpdateContact = dynamic(() => import('@/components/support/user/inform/UpdateContactModal'));
|
||||
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
|
||||
|
||||
const ModelPriceModal = dynamic(() =>
|
||||
@@ -129,9 +127,9 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
onOpen: onOpenUpdatePsw
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenUpdateNotification,
|
||||
onClose: onCloseUpdateNotification,
|
||||
onOpen: onOpenUpdateNotification
|
||||
isOpen: isOpenUpdateContact,
|
||||
onClose: onCloseUpdateContact,
|
||||
onOpen: onOpenUpdateContact
|
||||
} = useDisclosure();
|
||||
const {
|
||||
File,
|
||||
@@ -259,25 +257,14 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
)}
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('account_info:notification_receiving')}: </Box>
|
||||
<Box
|
||||
flex={1}
|
||||
{...(!userInfo?.team.notificationAccount && userInfo?.permission.isOwner
|
||||
? { color: 'red.600' }
|
||||
: {})}
|
||||
>
|
||||
{userInfo?.team.notificationAccount
|
||||
? userInfo?.team.notificationAccount
|
||||
: userInfo?.permission.isOwner
|
||||
? t('account_info:please_bind_notification_receiving_path')
|
||||
: t('account_info:reminder_create_bound_notification_account')}
|
||||
<Box {...labelStyles}>{t('account_info:contact')}: </Box>
|
||||
<Box flex={1} {...(!userInfo?.contact ? { color: 'red.600' } : {})}>
|
||||
{userInfo?.contact ? userInfo?.contact : t('account_info:please_bind_contact')}
|
||||
</Box>
|
||||
|
||||
{userInfo?.permission.isOwner && (
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdateNotification}>
|
||||
{t('account_info:change')}
|
||||
</Button>
|
||||
)}
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdateContact}>
|
||||
{t('account_info:change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
{feConfigs.isPlus && (
|
||||
@@ -310,7 +297,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
<ConversionModal onClose={onCloseConversionModal} onOpenContact={onOpenContact} />
|
||||
)}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
{isOpenUpdateNotification && <UpdateNotification onClose={onCloseUpdateNotification} />}
|
||||
{isOpenUpdateContact && <UpdateContact onClose={onCloseUpdateContact} mode="contact" />}
|
||||
<File
|
||||
onSelect={(e) =>
|
||||
onSelectImage(e, {
|
||||
|
@@ -104,7 +104,8 @@ const Team = () => {
|
||||
setEditTeamData({
|
||||
id: userInfo.team.teamId,
|
||||
name: userInfo.team.teamName,
|
||||
avatar: userInfo.team.avatar
|
||||
avatar: userInfo.team.avatar,
|
||||
notificationAccount: userInfo.team.notificationAccount
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
29
projects/app/src/pages/api/admin/initv4822.ts
Normal file
29
projects/app/src/pages/api/admin/initv4822.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
/*
|
||||
* 复制 Team 表中的 notificationAccount 到 User 表的 contact 中
|
||||
*/
|
||||
async function handler(req: NextApiRequest, _res: NextApiResponse) {
|
||||
await authCert({ req, authRoot: true });
|
||||
const users = await MongoUser.find();
|
||||
const teams = await MongoTeam.find();
|
||||
for await (const user of users) {
|
||||
try {
|
||||
const team = teams.find((team) => String(team.ownerId) === String(user._id));
|
||||
if (team && !user.contact) {
|
||||
user.contact = team.notificationAccount;
|
||||
}
|
||||
await user.save();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
@@ -7,7 +7,8 @@ import { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import type {
|
||||
FastLoginProps,
|
||||
OauthLoginProps,
|
||||
PostLoginProps
|
||||
PostLoginProps,
|
||||
SearchResult
|
||||
} from '@fastgpt/global/support/user/api.d';
|
||||
import {
|
||||
AccountRegisterBody,
|
||||
@@ -70,6 +71,10 @@ export const updatePasswordByOld = ({ oldPsw, newPsw }: { oldPsw: string; newPsw
|
||||
export const updateNotificationAccount = (data: { account: string; verifyCode: string }) =>
|
||||
PUT('/proApi/support/user/team/updateNotificationAccount', data);
|
||||
|
||||
export const updateContact = (data: { contact: string; verifyCode: string }) => {
|
||||
return PUT('/proApi/support/user/account/updateContact', data);
|
||||
};
|
||||
|
||||
export const postLogin = ({ password, ...props }: PostLoginProps) =>
|
||||
POST<ResLogin>('/support/user/account/loginByPassword', {
|
||||
...props,
|
||||
@@ -92,3 +97,14 @@ export const getCaptchaPic = (username: string) =>
|
||||
}>('/proApi/support/user/account/captcha/getImgCaptcha', { username });
|
||||
|
||||
export const postSyncMembers = () => POST('/proApi/support/user/team/org/sync');
|
||||
|
||||
export const GetSearchUserGroupOrg = (
|
||||
searchKey: string,
|
||||
options?: {
|
||||
members?: boolean;
|
||||
orgs?: boolean;
|
||||
groups?: boolean;
|
||||
}
|
||||
) => GET<SearchResult>('/proApi/support/user/search', { searchKey, ...options });
|
||||
|
||||
export const ExportMembers = () => GET<{ csv: string }>('/proApi/support/user/team/member/export');
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
InviteMemberProps,
|
||||
InviteMemberResponse,
|
||||
UpdateInviteProps,
|
||||
UpdateStatusProps,
|
||||
UpdateTeamProps
|
||||
} from '@fastgpt/global/support/user/team/controller.d';
|
||||
import type { TeamTagItemType, TeamTagSchema } from '@fastgpt/global/support/user/team/type';
|
||||
@@ -31,7 +32,7 @@ export const putSwitchTeam = (teamId: string) =>
|
||||
PUT<string>(`/proApi/support/user/team/switch`, { teamId });
|
||||
|
||||
/* --------------- team member ---------------- */
|
||||
export const getTeamMembers = (props: PaginationProps) =>
|
||||
export const getTeamMembers = (props: PaginationProps<{ withLeaved?: boolean }>) =>
|
||||
GET<PaginationResponse<TeamMemberItemType>>(`/proApi/support/user/team/member/list`, props);
|
||||
export const postInviteTeamMember = (data: InviteMemberProps) =>
|
||||
POST<InviteMemberResponse>(`/proApi/support/user/team/member/invite`, data);
|
||||
@@ -41,6 +42,8 @@ export const delRemoveMember = (tmbId: string) =>
|
||||
DELETE(`/proApi/support/user/team/member/delete`, { tmbId });
|
||||
export const updateInviteResult = (data: UpdateInviteProps) =>
|
||||
PUT('/proApi/support/user/team/member/updateInvite', data);
|
||||
export const updateStatus = (data: UpdateStatusProps) =>
|
||||
PUT('/proApi/support/user/team/member/updateStatus', data);
|
||||
export const delLeaveTeam = () => DELETE('/proApi/support/user/team/member/leave');
|
||||
|
||||
/* -------------- team collaborator -------------------- */
|
||||
|
Reference in New Issue
Block a user