mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 09:44:47 +00:00
Group role (#2993)
* feat: app/dataset support group (#2898) * pref: member-group (#2862) * feat: group list ordered by updateTime * fix: transfer ownership of group when deleting member * fix: i18n fix * feat: can not set member as admin/owner when user is not active * fix: GroupInfoModal hover input do not change color * fix(fe): searchinput do not scroll * feat: app collaborator with group, remove default permission * feat: dataset collaborator with group, remove default permission * chore(test): pref mock * chore: remove useless code * chore: adjust * fix: add self as collaborator when creating folder * fix(fe): folder manage menu do not show when user has write permission only * fix: dataset folder create * feat: Add code comment * Pref: app move (#2952) * perf: app schema * doc --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Flex, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
@@ -11,6 +11,7 @@ import { useMemoizedFn, useMount } from 'ahooks';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import LightTip from '@fastgpt/web/components/common/LightTip';
|
||||
|
||||
type FolderItemType = {
|
||||
id: string;
|
||||
@@ -27,9 +28,10 @@ type Props = {
|
||||
server: (e: GetResourceFolderListProps) => Promise<GetResourceFolderListItemResponse[]>;
|
||||
onConfirm: (id: ParentIdType) => Promise<any>;
|
||||
onClose: () => void;
|
||||
moveHint?: string;
|
||||
};
|
||||
|
||||
const MoveModal = ({ moveResourceId, title, server, onConfirm, onClose }: Props) => {
|
||||
const MoveModal = ({ moveResourceId, title, server, onConfirm, onClose, moveHint }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedId, setSelectedId] = React.useState<string>();
|
||||
const [requestingIdList, setRequestingIdList] = useState<ParentIdType[]>([]);
|
||||
@@ -170,6 +172,7 @@ const MoveModal = ({ moveResourceId, title, server, onConfirm, onClose }: Props)
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody flex={'1 0 0'} overflow={'auto'} minH={'400px'}>
|
||||
{moveHint && <LightTip text={moveHint} />}
|
||||
<RenderList list={folderList} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
|
||||
@@ -40,7 +39,7 @@ const FolderSlideCard = ({
|
||||
deleteTip: string;
|
||||
onDelete: () => void;
|
||||
|
||||
defaultPer: {
|
||||
defaultPer?: {
|
||||
value: PermissionValueType;
|
||||
defaultValue: PermissionValueType;
|
||||
onChange: (v: PermissionValueType) => Promise<any>;
|
||||
@@ -54,7 +53,6 @@ const FolderSlideCard = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
type: 'delete',
|
||||
@@ -136,7 +134,7 @@ const FolderSlideCard = ({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{managePer.permission.hasManagePer && (
|
||||
{managePer.permission.hasManagePer && !!defaultPer && (
|
||||
<Box mt={5}>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('common:permission.Default permission')}
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import CollaboratorContextProvider, { MemberManagerInputPropsType } from '../MemberManager/context';
|
||||
import { Box, Button, Flex, HStack, ModalBody, useDisclosure } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import DefaultPermissionList from '../DefaultPerList';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import ResumeInherit from '../ResumeInheritText';
|
||||
import { ChangeOwnerModal } from '../ChangeOwnerModal';
|
||||
@@ -14,11 +12,6 @@ export type ConfigPerModalProps = {
|
||||
avatar?: string;
|
||||
name: string;
|
||||
|
||||
defaultPer: {
|
||||
value: PermissionValueType;
|
||||
defaultValue: PermissionValueType;
|
||||
onChange: (v: PermissionValueType) => Promise<any>;
|
||||
};
|
||||
managePer: MemberManagerInputPropsType;
|
||||
isInheritPermission?: boolean;
|
||||
resumeInheritPermission?: () => void;
|
||||
@@ -30,7 +23,6 @@ export type ConfigPerModalProps = {
|
||||
const ConfigPerModal = ({
|
||||
avatar,
|
||||
name,
|
||||
defaultPer,
|
||||
managePer,
|
||||
isInheritPermission,
|
||||
resumeInheritPermission,
|
||||
@@ -66,17 +58,6 @@ const ConfigPerModal = ({
|
||||
<ResumeInherit onResume={resumeInheritPermission} />
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={5}>
|
||||
<Box fontSize={'sm'}>{t('common:permission.Default permission')}</Box>
|
||||
<DefaultPermissionList
|
||||
mt="1"
|
||||
per={defaultPer.value}
|
||||
defaultPer={defaultPer.defaultValue}
|
||||
isInheritPermission={isInheritPermission}
|
||||
onChange={(v) => defaultPer.onChange(v)}
|
||||
hasParent={hasParent}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<CollaboratorContextProvider
|
||||
{...managePer}
|
||||
|
@@ -1,35 +1,22 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { PermissionTypeEnum, PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
|
||||
import React from 'react';
|
||||
import { PermissionTypeMap } from '@fastgpt/global/support/permission/constant';
|
||||
import { Box, StackProps, HStack } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||
|
||||
const PermissionIconText = ({
|
||||
permission,
|
||||
defaultPermission,
|
||||
w = '1rem',
|
||||
fontSize = 'mini',
|
||||
iconColor = 'myGray.500',
|
||||
private: Private = false,
|
||||
...props
|
||||
}: {
|
||||
permission?: `${PermissionTypeEnum}`;
|
||||
defaultPermission?: PermissionValueType;
|
||||
private?: boolean;
|
||||
iconColor?: string;
|
||||
} & StackProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const per = useMemo(() => {
|
||||
if (permission) return permission;
|
||||
if (defaultPermission !== undefined) {
|
||||
const Per = new Permission({ per: defaultPermission });
|
||||
if (Per.hasWritePer) return PermissionTypeEnum.publicWrite;
|
||||
if (Per.hasReadPer) return PermissionTypeEnum.publicRead;
|
||||
return PermissionTypeEnum.clbPrivate;
|
||||
}
|
||||
return 'private';
|
||||
}, [defaultPermission, permission]);
|
||||
const per = Private ? 'private' : 'public';
|
||||
|
||||
return PermissionTypeMap[per] ? (
|
||||
<HStack spacing={1} fontSize={fontSize} {...props}>
|
||||
|
@@ -2,12 +2,11 @@ import {
|
||||
Flex,
|
||||
Box,
|
||||
ModalBody,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
Input,
|
||||
Checkbox,
|
||||
ModalFooter,
|
||||
Button
|
||||
Button,
|
||||
Grid,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -18,63 +17,76 @@ import PermissionSelect from './PermissionSelect';
|
||||
import PermissionTags from './PermissionTags';
|
||||
import { CollaboratorContext } from './context';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
|
||||
export type AddModalPropsType = {
|
||||
onClose: () => void;
|
||||
mode?: 'member' | 'all';
|
||||
};
|
||||
|
||||
function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
function AddMemberModal({ onClose, mode = 'member' }: AddModalPropsType) {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, loadAndGetTeamMembers } = useUserStore();
|
||||
const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, myGroups } = useUserStore();
|
||||
|
||||
const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList } =
|
||||
const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList, permission } =
|
||||
useContextSelector(CollaboratorContext, (v) => v);
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
|
||||
const { data: members = [], loading: loadingMembers } = useRequest2(
|
||||
const { data: [members = [], groups = []] = [], loading: loadingMembersAndGroups } = useRequest2(
|
||||
async () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
const members = await loadAndGetTeamMembers(true);
|
||||
return members;
|
||||
if (!userInfo?.team?.teamId) return [[], []];
|
||||
return await Promise.all([loadAndGetTeamMembers(true), loadAndGetGroups(true)]);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [userInfo?.team?.teamId]
|
||||
}
|
||||
);
|
||||
|
||||
const filterMembers = useMemo(() => {
|
||||
return members.filter((item) => {
|
||||
// if (item.permission.isOwner) return false;
|
||||
if (item.tmbId === userInfo?.team?.tmbId) return false;
|
||||
if (!searchText) return true;
|
||||
return item.memberName.includes(searchText);
|
||||
});
|
||||
}, [members, searchText, userInfo?.team?.tmbId]);
|
||||
|
||||
const filterGroups = useMemo(() => {
|
||||
if (mode !== 'all') return [];
|
||||
return groups.filter((item) => {
|
||||
if (permission.isOwner) return true; // owner can see all groups
|
||||
if (myGroups.find((i) => String(i._id) === String(item._id))) return false;
|
||||
if (!searchText) return true;
|
||||
return item.name.includes(searchText);
|
||||
});
|
||||
}, [groups, searchText, myGroups, mode, permission]);
|
||||
|
||||
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
||||
const [selectedGroupIdList, setSelectedGroupIdList] = useState<string[]>([]);
|
||||
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
|
||||
const perLabel = useMemo(() => {
|
||||
return getPerLabelList(selectedPermission).join('、');
|
||||
}, [getPerLabelList, selectedPermission]);
|
||||
|
||||
const { mutate: onConfirm, isLoading: isUpdating } = useRequest({
|
||||
mutationFn: () => {
|
||||
return onUpdateCollaborators({
|
||||
const { runAsync: onConfirm, loading: isUpdating } = useRequest2(
|
||||
() =>
|
||||
onUpdateCollaborators({
|
||||
members: selectedMemberIdList,
|
||||
groups: selectedGroupIdList,
|
||||
permission: selectedPermission
|
||||
});
|
||||
},
|
||||
successToast: t('common:common.Add Success'),
|
||||
errorToast: 'Error',
|
||||
onSuccess() {
|
||||
onClose();
|
||||
}),
|
||||
{
|
||||
successToast: t('common:common.Add Success'),
|
||||
errorToast: 'Error',
|
||||
onSuccess() {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
@@ -83,17 +95,15 @@ function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
iconSrc="modal/AddClb"
|
||||
title={t('user:team.add_collaborator')}
|
||||
minW="800px"
|
||||
isCentered
|
||||
isLoading={loadingMembersAndGroups}
|
||||
>
|
||||
<ModalBody>
|
||||
<MyBox
|
||||
isLoading={loadingMembers}
|
||||
display={'grid'}
|
||||
minH="400px"
|
||||
<Grid
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
borderRadius="0.5rem"
|
||||
gridTemplateColumns="55% 45%"
|
||||
fontSize={'sm'}
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
>
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
@@ -102,17 +112,53 @@ function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
p="4"
|
||||
minH="200px"
|
||||
>
|
||||
<InputGroup alignItems="center" size="sm">
|
||||
<InputLeftElement>
|
||||
<MyIcon name="common/searchLight" w="16px" color={'myGray.500'} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
placeholder={t('user:search_user')}
|
||||
bgColor="myGray.50"
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
<Flex flexDirection="column" mt="2">
|
||||
<SearchInput
|
||||
placeholder={t('user:search_user')}
|
||||
bgColor="myGray.50"
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
|
||||
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
|
||||
{filterGroups.map((group) => {
|
||||
const onChange = () => {
|
||||
if (selectedGroupIdList.includes(group._id)) {
|
||||
setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== group._id));
|
||||
} else {
|
||||
setSelectedGroupIdList([...selectedGroupIdList, 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={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer',
|
||||
...(!selectedGroupIdList.includes(group._id)
|
||||
? { svg: { color: 'myGray.50' } }
|
||||
: {})
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox
|
||||
isChecked={selectedGroupIdList.includes(group._id)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
/>
|
||||
<MyAvatar src={group.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box ml="2" w="full">
|
||||
{group.name === DefaultGroupName ? userInfo?.team.teamName : group.name}
|
||||
</Box>
|
||||
{!!collaborator && (
|
||||
<PermissionTags permission={collaborator.permission.value} />
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{filterMembers.map((member) => {
|
||||
const onChange = () => {
|
||||
if (selectedMemberIdList.includes(member.tmbId)) {
|
||||
@@ -123,10 +169,10 @@ function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
};
|
||||
const collaborator = collaboratorList.find((v) => v.tmbId === member.tmbId);
|
||||
return (
|
||||
<Flex
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={member.tmbId}
|
||||
mt="1"
|
||||
py="1"
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
@@ -137,51 +183,87 @@ function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
? { svg: { color: 'myGray.50' } }
|
||||
: {})
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
<Checkbox
|
||||
mr="3"
|
||||
isChecked={selectedMemberIdList.includes(member.tmbId)}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
onClick={onChange}
|
||||
w="full"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Flex flexDirection="row" alignItems="center">
|
||||
<MyAvatar src={member.avatar} w="32px" />
|
||||
<Box ml="2">{member.memberName}</Box>
|
||||
</Flex>
|
||||
{!!collaborator && (
|
||||
<PermissionTags permission={collaborator.permission.value} />
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<MyAvatar src={member.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box w="full" ml="2">
|
||||
{member.memberName}
|
||||
</Box>
|
||||
{!!collaborator && (
|
||||
<PermissionTags permission={collaborator.permission.value} />
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex p="4" flexDirection="column">
|
||||
<Box>
|
||||
{t('user:has_chosen') + ': '}+ {selectedMemberIdList.length}
|
||||
{t('user:has_chosen') + ': '}{' '}
|
||||
{selectedMemberIdList.length + selectedGroupIdList.length}
|
||||
</Box>
|
||||
<Flex flexDirection="column" mt="2">
|
||||
<Flex flexDirection="column" mt="2" overflow={'auto'} maxH="400px">
|
||||
{selectedGroupIdList.map((groupId) => {
|
||||
const onChange = () => {
|
||||
if (selectedGroupIdList.includes(groupId)) {
|
||||
setSelectedGroupIdList(selectedGroupIdList.filter((v) => v !== groupId));
|
||||
} else {
|
||||
setSelectedGroupIdList([...selectedGroupIdList, groupId]);
|
||||
}
|
||||
};
|
||||
const group = groups.find((v) => String(v._id) === groupId);
|
||||
return (
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={groupId}
|
||||
py="2"
|
||||
px="3"
|
||||
borderRadius="sm"
|
||||
alignItems="center"
|
||||
_hover={{
|
||||
bgColor: 'myGray.50',
|
||||
cursor: 'pointer',
|
||||
...(!selectedGroupIdList.includes(groupId)
|
||||
? { svg: { color: 'myGray.50' } }
|
||||
: {})
|
||||
}}
|
||||
onClick={onChange}
|
||||
>
|
||||
<MyAvatar src={group?.avatar} w="1.5rem" borderRadius={'50%'} />
|
||||
<Box w="full" ml="2">
|
||||
{group?.name === DefaultGroupName ? userInfo?.team.teamName : group?.name}
|
||||
</Box>
|
||||
<MyIcon
|
||||
name="common/closeLight"
|
||||
w="16px"
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
);
|
||||
})}
|
||||
{selectedMemberIdList.map((tmbId) => {
|
||||
const member = filterMembers.find((v) => v.tmbId === tmbId);
|
||||
return member ? (
|
||||
<Flex
|
||||
<HStack
|
||||
justifyContent="space-between"
|
||||
key={tmbId}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
py="2"
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
_notLast={{ mb: 2 }}
|
||||
onClick={() =>
|
||||
setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId))
|
||||
}
|
||||
>
|
||||
<Avatar src={member.avatar} w="24px" />
|
||||
<MyAvatar src={member.avatar} w="1.5rem" borderRadius="50%" />
|
||||
<Box w="full" ml={2}>
|
||||
{member.memberName}
|
||||
</Box>
|
||||
@@ -192,16 +274,13 @@ function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||
_hover={{
|
||||
color: 'red.600'
|
||||
}}
|
||||
onClick={() =>
|
||||
setSelectedMembers(selectedMemberIdList.filter((v) => v !== tmbId))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</HStack>
|
||||
) : null;
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<PermissionSelect
|
||||
|
@@ -8,11 +8,12 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { CollaboratorContext } from './context';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import Loading from '@fastgpt/web/components/common/MyLoading';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
export type ManageModalProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
@@ -23,21 +24,12 @@ function ManageModal({ onClose }: ManageModalProps) {
|
||||
const { permission, collaboratorList, onUpdateCollaborators, onDelOneCollaborator } =
|
||||
useContextSelector(CollaboratorContext, (v) => v);
|
||||
|
||||
const { runAsync: onDelete, loading: isDeleting } = useRequest2((tmbId: string) =>
|
||||
onDelOneCollaborator(tmbId)
|
||||
);
|
||||
const { runAsync: onDelete, loading: isDeleting } = useRequest2(onDelOneCollaborator);
|
||||
|
||||
const { runAsync: onUpdate, loading: isUpdating } = useRequest2(
|
||||
({ tmbId, per }: { tmbId: string; per: PermissionValueType }) =>
|
||||
onUpdateCollaborators({
|
||||
members: [tmbId],
|
||||
permission: per
|
||||
}),
|
||||
{
|
||||
successToast: t('common.Update Success'),
|
||||
errorToast: 'Error'
|
||||
}
|
||||
);
|
||||
const { runAsync: onUpdate, loading: isUpdating } = useRequest2(onUpdateCollaborators, {
|
||||
successToast: t('common.Update Success'),
|
||||
errorToast: 'Error'
|
||||
});
|
||||
|
||||
const loading = isDeleting || isUpdating;
|
||||
|
||||
@@ -74,7 +66,7 @@ function ManageModal({ onClose }: ManageModalProps) {
|
||||
<Td border="none">
|
||||
<Flex alignItems="center">
|
||||
<Avatar src={item.avatar} w="24px" mr={2} />
|
||||
{item.name}
|
||||
{item.name === DefaultGroupName ? userInfo?.team.teamName : item.name}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td border="none">
|
||||
@@ -89,14 +81,18 @@ function ManageModal({ onClose }: ManageModalProps) {
|
||||
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
|
||||
}
|
||||
value={item.permission.value}
|
||||
onChange={(per) => {
|
||||
onChange={(permission) => {
|
||||
onUpdate({
|
||||
tmbId: item.tmbId,
|
||||
per
|
||||
members: item.tmbId ? [item.tmbId] : undefined,
|
||||
groups: item.groupId ? [item.groupId] : undefined,
|
||||
permission
|
||||
});
|
||||
}}
|
||||
onDelete={() => {
|
||||
onDelete(item.tmbId);
|
||||
onDelete({
|
||||
tmbId: item.tmbId,
|
||||
groupId: item.groupId
|
||||
} as RequireOnlyOne<{ tmbId: string; groupId: string }>);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@@ -6,11 +6,14 @@ import { CollaboratorContext } from './context';
|
||||
import Tag, { TagProps } from '@fastgpt/web/components/common/Tag';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
export type MemberListCardProps = BoxProps & { tagStyle?: Omit<TagProps, 'children'> };
|
||||
|
||||
const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const { collaboratorList, isFetchingCollaborator } = useContextSelector(
|
||||
CollaboratorContext,
|
||||
@@ -27,10 +30,15 @@ const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => {
|
||||
<Flex gap="2" flexWrap={'wrap'}>
|
||||
{collaboratorList?.map((member) => {
|
||||
return (
|
||||
<Tag key={member.tmbId} type={'fill'} colorSchema="white" {...tagStyle}>
|
||||
<Tag
|
||||
key={member.tmbId || member.groupId}
|
||||
type={'fill'}
|
||||
colorSchema="white"
|
||||
{...tagStyle}
|
||||
>
|
||||
<Avatar src={member.avatar} w="1.25rem" />
|
||||
<Box fontSize={'sm'} ml={1}>
|
||||
{member.name}
|
||||
{member.name === DefaultGroupName ? userInfo?.team.teamName : member.name}
|
||||
</Box>
|
||||
</Tag>
|
||||
);
|
||||
|
@@ -15,6 +15,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
const AddMemberModal = dynamic(() => import('./AddMemberModal'));
|
||||
const ManageModal = dynamic(() => import('./ManageModal'));
|
||||
|
||||
@@ -22,10 +23,12 @@ export type MemberManagerInputPropsType = {
|
||||
permission: Permission;
|
||||
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
|
||||
permissionList: PermissionListType;
|
||||
onUpdateCollaborators: (props: any) => any; // TODO: type. should be UpdatePermissionBody after app and dataset permission refactored
|
||||
onDelOneCollaborator: (tmbId: string) => any;
|
||||
onUpdateCollaborators: (props: UpdateClbPermissionProps) => Promise<any>;
|
||||
onDelOneCollaborator: (props: RequireOnlyOne<{ tmbId: string; groupId: string }>) => Promise<any>;
|
||||
refreshDeps?: any[];
|
||||
mode?: 'member' | 'all';
|
||||
};
|
||||
|
||||
export type MemberManagerPropsType = MemberManagerInputPropsType & {
|
||||
collaboratorList: CollaboratorItemType[];
|
||||
refetchCollaboratorList: () => void;
|
||||
@@ -72,7 +75,8 @@ const CollaboratorContextProvider = ({
|
||||
refetchResource,
|
||||
refreshDeps = [],
|
||||
isInheritPermission,
|
||||
hasParent
|
||||
hasParent,
|
||||
mode = 'member'
|
||||
}: MemberManagerInputPropsType & {
|
||||
children: (props: ChildrenProps) => ReactNode;
|
||||
refetchResource?: () => void;
|
||||
@@ -83,8 +87,10 @@ const CollaboratorContextProvider = ({
|
||||
await onUpdateCollaborators(props);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
const onDelOneCollaboratorThen = async (tmbId: string) => {
|
||||
await onDelOneCollaborator(tmbId);
|
||||
const onDelOneCollaboratorThen = async (
|
||||
props: RequireOnlyOne<{ tmbId: string; groupId: string }>
|
||||
) => {
|
||||
await onDelOneCollaborator(props);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
|
||||
@@ -197,6 +203,7 @@ const CollaboratorContextProvider = ({
|
||||
onCloseAddMember();
|
||||
refetchResource?.();
|
||||
}}
|
||||
mode={mode}
|
||||
/>
|
||||
)}
|
||||
{isOpenManageModal && (
|
||||
|
@@ -1,14 +1,5 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
Flex,
|
||||
Grid,
|
||||
HStack,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Checkbox, Flex, Grid, HStack } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -16,6 +7,7 @@ import { Control, Controller } from 'react-hook-form';
|
||||
import { RequireAtLeastOne } from '@fastgpt/global/common/type/utils';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
|
||||
type memberType = {
|
||||
type: 'member';
|
||||
@@ -120,19 +112,14 @@ function SelectMember({
|
||||
h={'100%'}
|
||||
>
|
||||
<Flex flexDirection="column" p="4" h={'100%'} overflow={'auto'}>
|
||||
<InputGroup alignItems="center" size={'sm'}>
|
||||
<InputLeftElement>
|
||||
<MyIcon name="common/searchLight" w="16px" color={'myGray.500'} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
placeholder={t('user:search_user')}
|
||||
fontSize="sm"
|
||||
bg={'myGray.50'}
|
||||
onChange={(e) => {
|
||||
setSearchKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
<SearchInput
|
||||
placeholder={t('user:search_user')}
|
||||
fontSize="sm"
|
||||
bg={'myGray.50'}
|
||||
onChange={(e) => {
|
||||
setSearchKey(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Flex flexDirection="column" mt={3}>
|
||||
{filtered.map((member) => {
|
||||
return (
|
||||
|
Reference in New Issue
Block a user